贝叶斯定理:用证据更新信念 本节摘要:概率描述你「期望」什么,贝叶斯定理描述你「学到」了什么。一个医学检测 99% 准确,你测出阳性——真正患病的概率是多少?多数人答 99%,但若该病发病率是万分之一,真实答案只有约 1%,其余 99% 的阳性都是健康人的假警报。这就是贝叶斯定理。每一个垃圾邮件过滤器、每一台医学诊断、每一个量化不确定性的 ML 模型,都基于这套推理:先有信念,见到证据,更新信念。
本节摘要:概率描述你「期望」什么,贝叶斯定理描述你「学到」了什么。一个医学检测 99% 准确,你测出阳性——真正患病的概率是多少?多数人答 99%,但若该病发病率是万分之一,真实答案只有约 1%,其余 99% 的阳性都是健康人的假警报。这就是贝叶斯定理。每一个垃圾邮件过滤器、每一台医学诊断、每一个量化不确定性的 ML 模型,都基于这套推理:先有信念,见到证据,更新信念。本节从条件概率的对称性推出贝叶斯定理的四要素(后验、似然、先验、证据);用医学检测与垃圾邮件两个经典例子演示「先验主导」与「一词扭转概率」;构建带拉普拉斯平滑与对数空间计算的朴素贝叶斯文本分类器;厘清 MLE 与 MAP 的差别以及 MAP 与 L2 正则化的等价性;最后用 Beta-二项共轭先验做序贯贝叶斯更新,并把它接上 A/B 测试——你会看到「今天的后验就是明天的先验」如何支撑在线学习。
对应原课程:Phase 01 · Lesson 07 ·
bayes-theorem(原英文phases/01-math-foundations/07-bayes-theorem/docs/en.md)。前置:第 6 节(概率基础)。
阅读完本节,你应当能够:
一个医学检测 99% 准确,你测出阳性,真正有病的概率是多少?多数人说 99%。真实答案取决于病有多罕见:若万分之一的人患病,阳性结果只给你约 1% 的患病概率——其余 99% 的阳性都是健康人的假警报。
这不是脑筋急转弯,这就是贝叶斯定理。每个垃圾过滤器、医学诊断、量化不确定性的 ML 模型都用这套推理:先有信念、见证据、再更新。不懂它,你会误读模型输出、设错阈值、上线过度自信的预测。
第 6 节已知条件概率:
P(A|B) = P(A 且 B) / P(B) P(B|A) = P(A 且 B) / P(A)
两式共享分子 P(A 且 B),联立整理:
P(A 且 B) = P(A|B)·P(B) = P(B|A)·P(A) 因此: P(A|B) = P(B|A) · P(A) / P(B)
这就是贝叶斯定理——四个量、一个等式。
| 部分 | 名称 | 含义 |
|---|---|---|
| P(A|B) | 后验(Posterior) | 见到证据 B 后对 A 的更新信念 |
| P(B|A) | 似然(Likelihood) | 若 A 为真,证据 B 出现的概率 |
| P(A) | 先验(Prior) | 见证据前对 A 的信念 |
| P(B) | 证据(Evidence) | 在所有可能下见到 B 的总概率 |
证据项 P(B) 是归一化常数,可用全概率公式展开:P(B) = P(B|A)·P(A) + P(B|¬A)·P(¬A)。
病发病率 1/10000,检测 99% 准确(抓到 99% 患者、1% 假阳性)。
P(病) = 0.0001 (先验:病罕见) P(阳性|病) = 0.99 P(阳性|健康) = 0.01 P(阳性) = 0.99·0.0001 + 0.01·0.9999 = 0.000099 + 0.009999 = 0.010098 P(病|阳性) = 0.99·0.0001 / 0.010098 = 0.0098 ≈ 0.98%
不到 1%。先验主导:当条件罕见,即便检测很准也 mostly 假阳性。这就是医生要复检的原因。
收到含「lottery」的邮件,是不是垃圾?
P(垃圾) = 0.3 P("lottery"|垃圾) = 0.05 P("lottery"|非垃圾) = 0.001 P("lottery") = 0.05·0.3 + 0.001·0.7 = 0.0157 P(垃圾|"lottery") = 0.05·0.3 / 0.0157 = 0.955 = 95.5%
一个词把概率从 30% 推到 95.5%。真实过滤器会同时对数百词套贝叶斯。
朴素贝叶斯把它推广到多特征,假设「给定类别,所有特征条件独立」:
P(类 | f₁,f₂,…,fₙ) = P(类)·∏ᵢ P(fᵢ|类) / P(f₁,…,fₙ)
「朴素」就在这个独立性假设。文本里词出现并不独立(「New」与「York」相关),但实践中效果出奇地好——因为分类器只需给类别排序,不必产出校准的概率。
由于分母对所有类相同,可以跳过,只比分子:score(类) = P(类)·∏ᵢ P(fᵢ|类),取最高分类。
P(特征|类) 怎么从训练数据来?数频次。
P("free"|垃圾) = (含 "free" 的垃圾邮件数) / (垃圾邮件总数)
这就是 MLE:选让观测数据似然最大的参数值,对离散计数就是相对频率。
问题:若某词在训练垃圾邮件中从未出现,MLE 给概率 0,一个未见词就杀掉整个乘积。拉普拉斯平滑修复之:
P(词|类) = (count(词,类) + 1) / (该类总词数 + 词表大小)
每个计数加 1,确保没有任何概率为零。
MLE 问:什么参数最大化 P(数据|参数)?
MAP 问:什么参数最大化 P(参数|数据)?
由贝叶斯:P(参数|数据) ∝ P(数据|参数)·P(参数)。MAP 在参数本身上加先验。若你相信参数应当小,就用惩罚大值的先验表达——这与 ML 的 L2 正则化完全等价。岭回归的「岭」惩罚,字面意义上就是权重上的高斯先验。
| 估计 | 优化 | ML 等价 |
|---|---|---|
| MLE | P(数据|参数) | 无正则训练 |
| MAP | P(数据|参数)·P(参数) | L2/L1 正则化 |
频率派把参数当固定未知量:「若重复实验很多次,会发生什么?」贝叶斯派把参数当分布:「给定观测,我对参数的信念是什么?」
| 维度 | 频率派 | 贝叶斯派 |
|---|---|---|
| 输出 | 点估计 | 取值上的分布 |
| 不确定性 | 置信区间(关于过程) | 可信区间(关于参数) |
| 小数据 | 易过拟合 | 先验充当正则 |
| 计算 | 通常更快 | 常需采样(MCMC) |
生产 ML 多是频率派(SGD、点估计)。贝叶斯方法在你需要校准的不确定性(医学决策、安全关键系统)或数据稀缺(小样本学习、冷启动)时大放异彩。
完整源码见 phases/01-math-foundations/07-bayes-theorem/code/bayes.py。
def bayes(prior, likelihood, false_positive_rate): evidence = likelihood * prior + false_positive_rate * (1 - prior) return likelihood * prior / evidence
class NaiveBayes: def __init__(self, smoothing=1.0): self.smoothing = smoothing self.class_counts = defaultdict(int) self.word_counts = defaultdict(lambda: defaultdict(int)) self.class_word_totals = defaultdict(int) self.vocab = set() def train(self, documents, labels): for doc, label in zip(documents, labels): self.class_counts[label] += 1 for word in doc.lower().split(): self.word_counts[label][word] += 1 self.class_word_totals[label] += 1 self.vocab.add(word) def predict(self, document): words = document.lower().split() total_docs = sum(self.class_counts.values()) V = len(self.vocab) best_class, best_score = None, float("-inf") for cls in self.class_counts: score = math.log(self.class_counts[cls] / total_docs) # log 先验 for word in words: count = self.word_counts[cls].get(word, 0) total = self.class_word_totals[cls] # 拉普拉斯平滑 + 对数空间 score += math.log((count + self.smoothing) / (total + self.smoothing * V)) if score > best_score: best_score, best_class = score, cls return best_class
设计要点:用
log把连乘变连加,避免下溢;拉普拉斯平滑+1保证未见词概率非零。这两点是「教科书公式 → 生产代码」的鸿沟所在。
classifier = NaiveBayes() classifier.train(train_docs, train_labels) for msg in test_messages: print(f" '{msg}' -> {classifier.predict(msg)}")
scikit-learn 提供生产级朴素贝叶斯:
from sklearn.feature_extraction.text import CountVectorizer from sklearn.naive_bayes import MultinomialNB vectorizer = CountVectorizer() X_train = vectorizer.fit_transform(train_docs) clf = MultinomialNB() clf.fit(X_train, train_labels) preds = clf.predict(vectorizer.transform(test_messages))
同一算法。CountVectorizer 负责分词与词表,MultinomialNB 内部处理平滑与对数概率。你的 40 行从零版本做的事完全一样。
code/bayes.py:端到端、零依赖(仅标准库)的朴素贝叶斯,演示完整流水线:分词、带拉普拉斯平滑的概率估计、对数空间预测。outputs/。当先验与后验属同一分布族,称「共轭」,后验有闭式解,无需数值积分。
| 似然 | 共轭先验 | 后验 | 示例 |
|---|---|---|---|
| 伯努利 | Beta(a,b) | Beta(a+成功, b+失败) | 抛硬币偏度估计 |
| 正态(方差已知) | 正态(μ₀,σ₀) | 正态(加权均值, 更小方差) | 传感器校准 |
| 泊松 | Gamma(a,b) | Gamma(a+总计数, b+n) | 到达率建模 |
| 多项 | Dirichlet(α) | Dirichlet(α+计数) | 主题模型、语言模型 |
Beta 是实践中最常见的共轭先验。Beta(a,b) 表达对某概率参数的信念,均值 a/(a+b),a+b 越大越集中(越自信)。特例:Beta(1,1)=均匀(无意见);Beta(10,10)=在 0.5 处尖峰(坚信接近 0.5);Beta(1,10)=偏向 0(认为小)。
更新规则极简:先验 Beta(a,b) + 数据(s 成功, f 失败) → 后验 Beta(a+s, b+f)。无积分、无采样,只做加法。
贝叶斯推理天然序贯:今天的后验是明天的先验。估计硬币是否公平:
观测顺序无关紧要:一次喂入全部 12 正 8 反也得 Beta(13,9)。序贯更新等价于批量更新,但前者让你每步都能决策,无需存储原始数据。这是生产 ML 在线学习(Thompson 采样 bandit、增量推荐、流式异常检测)的基础。
A/B 测试是贝叶斯推理的伪装。测试两个按钮颜色,变体 A(蓝)与 B(绿),看哪个点击率高:
P(B > A)。解析难,蒙特卡洛易:从两个 Beta 各抽 10 万样本,统计 B > A 的比例。若 P(B>A) > 0.95,上线 B;在 0.05~0.95 间继续收集;< 0.05 则上线 A。优势:直接得概率陈述(「B 更好的概率 97%」)、无 p 值困惑、可随时查看(无「偷看问题」)、可融入先验知识。
| 维度 | 频率派 A/B | 贝叶斯 A/B |
|---|---|---|
| 输出 | p 值 | P(B>A) |
| 解释 | 「若 A=B,数据有多意外」 | 「B 比 A 好的可能性」 |
| 早停 | 抬高假阳性 | 任意时刻安全(给定合适先验) |
| 决策规则 | p < 0.05 | P(B>A) > 阈值 |
NaiveBayes,把消息长度(短/长)作为额外特征,估计 P(短|垃圾)、P(短|ham) 并纳入打分。P(A|B)=P(B|A)·P(A)/P(B),把条件概率对称化,是「用证据更新信念」的形式化机器。下一节,我们把「让分布更像另一个分布」变成显式目标——优化:梯度下降、动量、Adam,以及如何把损失曲面一点点压到谷底。