贝叶斯定理 (Bayes' Theorem)
概率关乎你的预期,贝叶斯定理关乎你的学习。
类型: 构建 语言: Python 前置知识: 第一阶段,第06课(概率基础) 时间: ~75 分钟
学习目标
- 应用贝叶斯定理(Bayes' theorem),从先验、似然和证据计算后验概率
- 从头构建带拉普拉斯平滑(Laplace smoothing)和对数空间计算的朴素贝叶斯(Naive Bayes)文本分类器
- 比较最大似然估计(MLE)和最大后验估计(MAP),解释 MAP 如何对应 L2 正则化
- 使用 Beta-二项式共轭先验实现贝叶斯序列更新,用于 A/B 测试
问题所在
一种医学检测的准确率为 99%,你检测结果为阳性。你实际患病的概率是多少?
大多数人会说 99%。真实答案取决于这种病有多罕见。如果每 10,000 人中只有 1 人患病,阳性结果只给你约 1% 的患病概率。另外 99% 的阳性结果都是来自健康人的假阳性。
这不是一道脑筋急转弯,而是贝叶斯定理。每个垃圾邮件过滤器、每个医疗诊断、每个量化不确定性的机器学习模型都使用这套推理。你从一个信念开始,看到证据,然后更新。
如果构建机器学习系统时不理解这一点,你就会误读模型输出、设置错误的阈值,并发布过度自信的预测。
概念讲解
从联合概率到贝叶斯定理 (From Joint Probability to Bayes)
你在第06课已经知道,条件概率(conditional probability)是:
P(A|B) = P(A and B) / P(B)对称地:
P(B|A) = P(A and B) / P(A)两个表达式共享同一个分子:P(A 和 B)。令它们相等并整理:
P(A and B) = P(A|B) * P(B) = P(B|A) * P(A)
Therefore:
P(A|B) = P(B|A) * P(A) / P(B)这就是贝叶斯定理,四个量,一个方程。
四个组成部分 (The Four Parts)
| 部分 | 名称 | 含义 |
|---|---|---|
| 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|not A) * P(not A)医学检测示例 (Medical Test Example)
一种疾病影响每 10,000 人中的 1 人。检测准确率为 99%(能检出 99% 的患者,有 1% 的假阳性率)。
P(sick) = 0.0001 (prior: disease is rare)
P(positive|sick) = 0.99 (likelihood: test catches it)
P(positive|healthy) = 0.01 (false positive rate)
P(positive) = P(positive|sick) * P(sick) + P(positive|healthy) * P(healthy)
= 0.99 * 0.0001 + 0.01 * 0.9999
= 0.000099 + 0.009999
= 0.010098
P(sick|positive) = P(positive|sick) * P(sick) / P(positive)
= 0.99 * 0.0001 / 0.010098
= 0.0098
= 0.98%不到 1%。先验主导了结果。当某种情况很罕见时,即使是准确的检测也会产生大量假阳性。这就是医生要求复查的原因。
垃圾邮件过滤示例 (Spam Filter Example)
你收到一封含有"lottery"(彩票)一词的邮件,它是垃圾邮件吗?
P(spam) = 0.3 (30% of email is spam)
P("lottery"|spam) = 0.05 (5% of spam emails contain "lottery")
P("lottery"|not spam) = 0.001 (0.1% of legitimate emails contain "lottery")
P("lottery") = 0.05 * 0.3 + 0.001 * 0.7
= 0.015 + 0.0007
= 0.0157
P(spam|"lottery") = 0.05 * 0.3 / 0.0157
= 0.955
= 95.5%一个词将概率从 30% 提升到 95.5%。真实的垃圾邮件过滤器会同时对数百个词应用贝叶斯推断。
朴素贝叶斯:独立性假设 (Naive Bayes: Independence Assumption)
朴素贝叶斯通过假设所有特征在给定类别下条件独立,将其推广到多特征情况:
P(class | feature_1, feature_2, ..., feature_n)
= P(class) * P(feature_1|class) * P(feature_2|class) * ... * P(feature_n|class)
/ P(feature_1, feature_2, ..., feature_n)"朴素"体现在独立性假设上。在文本中,词的出现并非独立的("New"和"York"是相关的),但这个假设在实践中出人意料地有效,因为分类器只需对类别排序,不需要产生准确校准的概率。
由于分母对所有类别相同,可以跳过它,只比较分子:
score(class) = P(class) * product of P(feature_i | class)选择得分最高的类别。
最大似然估计(MLE) (Maximum Likelihood Estimation)
如何从训练数据中获得 P(特征|类别)?计数。
P("free"|spam) = (number of spam emails containing "free") / (total spam emails)这就是最大似然估计(MLE,maximum likelihood estimation):选择使观测数据最可能出现的参数值。对离散计数来说,最大化似然函数就等于计算相对频率。
问题:如果某个词在训练时从未出现在垃圾邮件中,MLE 给它的概率为零,一个未见过的词就会使整个乘积归零。用拉普拉斯平滑来修复:
P(word|class) = (count(word, class) + 1) / (total_words_in_class + vocabulary_size)对每个计数加 1,确保任何概率都不为零。
最大后验估计(MAP) (Maximum A Posteriori Estimation)
最大似然估计问:什么参数使 P(数据|参数) 最大?
最大后验估计(MAP,maximum a posteriori estimation)问:什么参数使 P(参数|数据) 最大?
由贝叶斯定理:
P(parameters|data) proportional to P(data|parameters) * P(parameters)MAP 在参数上加了一个先验。如果你相信参数应该很小,可以将其编码为一个惩罚大值的先验,这与机器学习中的 L2 正则化完全等价。岭回归(ridge regression)中的"岭"惩罚项从字面上就是权重的高斯先验。
| 估计方法 | 优化目标 | 机器学习等价物 |
|---|---|---|
| 最大似然估计(MLE) | P(数据|参数) | 无正则化训练 |
| 最大后验估计(MAP) | P(数据|参数) * P(参数) | L2 / L1 正则化 |
贝叶斯派与频率派:实践中的区别 (Bayesian vs Frequentist: The Practical Difference)
频率派(frequentist)将参数视为固定的未知量,问:"如果我多次重复这个实验会发生什么?"
贝叶斯派(Bayesian)将参数视为分布,问:"基于我所观测到的,我对参数有什么信念?"
对于构建机器学习系统,实践中的区别:
| 方面 | 频率派 | 贝叶斯派 |
|---|---|---|
| 输出 | 点估计 | 取值上的分布 |
| 不确定性 | 置信区间(关于过程) | 可信区间(关于参数) |
| 小数据 | 可能过拟合 | 先验起正则化作用 |
| 计算 | 通常更快 | 通常需要采样(MCMC) |
大多数生产机器学习是频率派的(随机梯度下降、点估计)。贝叶斯方法在需要校准不确定性(医疗决策、安全关键系统)或数据稀缺时(少样本学习、冷启动)大放异彩。
贝叶斯思维对机器学习的意义 (Why Bayesian Thinking Matters for ML)
这种联系比类比更深层:
先验就是正则化。 权重的高斯先验就是 L2 正则化,拉普拉斯先验就是 L1 正则化。每次你添加正则化项,你都在做一个关于期望参数值的贝叶斯声明。
后验就是不确定性。 单一预测概率并不告诉你模型对该估计有多自信。贝叶斯方法给你一个分布:"我认为 P(垃圾邮件) 在 0.8 到 0.95 之间。"
贝叶斯更新就是在线学习。 今天的后验成为明天的先验。当模型看到新数据时,它逐步更新信念,而不是从头重新训练。
模型比较是贝叶斯式的。 贝叶斯信息准则(BIC)、边缘似然和贝叶斯因子都使用贝叶斯推理在不过拟合的情况下选择模型。
动手实现
第一步:贝叶斯定理函数
def bayes(prior, likelihood, false_positive_rate):
evidence = likelihood * prior + false_positive_rate * (1 - prior)
posterior = likelihood * prior / evidence
return posterior
result = bayes(prior=0.0001, likelihood=0.99, false_positive_rate=0.01)
print(f"P(sick|positive) = {result:.4f}")第二步:朴素贝叶斯分类器
import math
from collections import defaultdict
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
words = doc.lower().split()
for word in words:
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())
vocab_size = len(self.vocab)
best_class = None
best_score = float("-inf")
for cls in self.class_counts:
score = math.log(self.class_counts[cls] / total_docs)
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 * vocab_size))
if score > best_score:
best_score = score
best_class = cls
return best_class对数概率防止下溢。将许多小概率相乘会产生浮点数无法表示的极小值。对数概率求和在数值上是稳定的,数学上等价。
第三步:在垃圾邮件数据上训练
train_docs = [
"win free money now",
"free lottery ticket winner",
"claim your prize today free",
"urgent offer free cash",
"congratulations you won free",
"meeting tomorrow at noon",
"project update attached",
"can we schedule a call",
"quarterly report review",
"lunch on thursday sounds good",
"team standup notes attached",
"please review the pull request",
]
train_labels = [
"spam", "spam", "spam", "spam", "spam",
"ham", "ham", "ham", "ham", "ham", "ham", "ham",
]
classifier = NaiveBayes()
classifier.train(train_docs, train_labels)
test_messages = [
"free money waiting for you",
"meeting rescheduled to friday",
"you won a free prize",
"please review the attached report",
]
for msg in test_messages:
print(f" '{msg}' -> {classifier.predict(msg)}")第四步:检查学到的概率
def show_top_words(classifier, cls, n=5):
vocab_size = len(classifier.vocab)
total = classifier.class_word_totals[cls]
probs = {}
for word in classifier.vocab:
count = classifier.word_counts[cls].get(word, 0)
probs[word] = (count + classifier.smoothing) / (total + classifier.smoothing * vocab_size)
sorted_words = sorted(probs.items(), key=lambda x: x[1], reverse=True)
for word, prob in sorted_words[:n]:
print(f" {word}: {prob:.4f}")
print("\nTop spam words:")
show_top_words(classifier, "spam")
print("\nTop ham words:")
show_top_words(classifier, "ham")实际使用
Scikit-learn 提供了生产就绪的朴素贝叶斯实现:
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import classification_report
vectorizer = CountVectorizer()
X_train = vectorizer.fit_transform(train_docs)
clf = MultinomialNB()
clf.fit(X_train, train_labels)
X_test = vectorizer.transform(test_messages)
predictions = clf.predict(X_test)
for msg, pred in zip(test_messages, predictions):
print(f" '{msg}' -> {pred}")算法相同。CountVectorizer 处理分词和词汇构建,MultinomialNB 在内部处理平滑和对数概率。你的从头实现用 40 行代码做了同样的事情。
交付成果
此处构建的 NaiveBayes 类演示了完整流程:分词、带拉普拉斯平滑的概率估计、对数空间预测。code/bayes.py 中的代码无需 Python 标准库之外的任何依赖即可端到端运行。
共轭先验 (Conjugate Priors)
当先验和后验属于同一分布族时,该先验被称为"共轭"(conjugate)先验。这使贝叶斯更新在代数上简洁——无需数值积分即可得到闭合形式的后验。
| 似然 | 共轭先验 | 后验 | 示例 |
|---|---|---|---|
| 伯努利分布 | Beta(a, b) | Beta(a + 成功次数, b + 失败次数) | 硬币偏差估计 |
| 正态分布(已知方差) | Normal(mu_0, sigma_0) | Normal(加权均值, 更小方差) | 传感器校准 |
| 泊松分布 | Gamma(a, b) | Gamma(a + 计数之和, b + n) | 到达率建模 |
| 多项分布 | Dirichlet(alpha) | Dirichlet(alpha + 计数) | 主题建模、语言模型 |
为何重要:没有共轭先验,你需要蒙特卡洛采样或变分推断来近似后验。有了共轭先验,只需更新两个数字。
Beta 分布是实践中最常见的共轭先验。Beta(a, b) 代表你对某个概率参数的信念,均值为 a/(a+b),a+b 越大,分布越集中(越自信)。
Beta 先验的特殊情况:
- Beta(1, 1) = 均匀分布。你对参数没有任何看法。
- Beta(10, 10) = 在 0.5 处有峰值。你强烈相信参数接近 0.5。
- Beta(1, 10) = 偏向 0。你认为参数很小。
更新规则极为简单:
Prior: Beta(a, b)
Data: s successes, f failures
Posterior: Beta(a + s, b + f)无需积分,无需采样,只需相加。
贝叶斯序列更新 (Sequential Bayesian Updating)
贝叶斯推断天然是序列化的。今天的后验成为明天的先验。这正是真实系统在不重新处理所有历史数据的情况下增量学习的方式。
具体示例:估计一枚硬币是否公平。
第1天:尚无数据。 从 Beta(1, 1) ——均匀先验开始。你没有任何看法。
- 先验均值:0.5
- 先验在 [0, 1] 上是平坦的
第2天:观测到 7 次正面,3 次反面。 后验 = Beta(1 + 7, 1 + 3) = Beta(8, 4)
- 后验均值:8/12 = 0.667
- 证据表明硬币偏向正面
第3天:又观测到 5 次正面,5 次反面。 使用昨天的后验作为今天的先验。 后验 = Beta(8 + 5, 4 + 5) = Beta(13, 9)
- 后验均值:13/22 = 0.591
- 均衡的新数据将估计值拉回 0.5
graph LR
A["先验<br/>Beta(1,1)<br/>均值 = 0.50"] -->|"7正面,3反面"| B["后验 1<br/>Beta(8,4)<br/>均值 = 0.67"]
B -->|"成为先验"| C["先验 2<br/>Beta(8,4)"]
C -->|"5正面,5反面"| D["后验 2<br/>Beta(13,9)<br/>均值 = 0.59"]观测顺序无关紧要。Beta(1,1) 一次性更新所有 12 次正面和 8 次反面后得到 Beta(13, 9)——与序列更新结果相同。序列更新和批量更新在数学上等价,但序列更新允许你在每一步做出决策,而无需存储原始数据。
这是生产机器学习系统中在线学习的基础。赌博机(bandit)问题中的汤普森采样(Thompson sampling)、增量推荐系统和流式异常检测器都使用这一模式。
A/B 测试的联系 (Connection to A/B Testing)
A/B 测试本质上是贝叶斯推断的伪装形式。
场景:你在测试两种按钮颜色——变体 A(蓝色)和变体 B(绿色),想知道哪个点击率更高。
贝叶斯 A/B 测试:
- 先验。 两个变体均从 Beta(1, 1) 开始,无先验偏好。
- 数据。 变体 A:1000 次展示中 50 次点击。变体 B:1000 次展示中 65 次点击。
- 后验。
- A:Beta(1 + 50, 1 + 950) = Beta(51, 951),均值 = 0.051
- B:Beta(1 + 65, 1 + 935) = Beta(66, 936),均值 = 0.066
- 决策。 计算 P(B > A) ——B 的真实转化率高于 A 的概率。
解析计算 P(B > A) 较难,但蒙特卡洛方法让其变得简单:
1. Draw 100,000 samples from Beta(51, 951) -> samples_A
2. Draw 100,000 samples from Beta(66, 936) -> samples_B
3. P(B > A) = fraction of samples where B > A若 P(B > A) > 0.95,则上线变体 B;若在 0.05 到 0.95 之间,继续收集数据;若 P(B > A) < 0.05,则上线变体 A。
相比频率派 A/B 测试的优势:
- 你得到直接的概率陈述:"B 更好的概率为 97%"
- 无 p 值混淆,无"未能拒绝零假设"的迂回说法
- 可以随时查看结果,不会增加误报率(无"偷看问题")
- 可以融入先验知识(例如,之前的测试表明转化率通常为 3-8%)
| 方面 | 频率派 A/B | 贝叶斯 A/B |
|---|---|---|
| 输出 | p 值 | P(B > A) |
| 解读 | "若 A=B,此数据有多意外?" | "B 优于 A 的可能性有多大?" |
| 提前停止 | 增加误报率 | 任何时点均安全(在选择良好的先验和正确指定的模型条件下) |
| 先验知识 | 未使用 | 编码为 Beta 先验 |
| 决策规则 | p < 0.05 | P(B > A) > 阈值 |
练习
多次检测。 一名患者在两项独立检测中均呈阳性(均为 99% 准确率,患病率 1/10,000)。两次检测后 P(患病) 是多少?使用第一次检测的后验作为第二次的先验。
平滑的影响。 分别用 0.01、0.1、1.0 和 10.0 的平滑值运行垃圾邮件分类器。高频词的概率如何变化?当平滑=0 且某个词只出现在正常邮件中时会发生什么?
添加特征。 扩展 NaiveBayes 类,将邮件长度(短/长)作为词计数之外的额外特征。从训练数据估计 P(短|垃圾) 和 P(短|正常),并将其纳入预测分数。
手动计算 MAP。 给定观测数据(10 次抛硬币中 7 次正面),用 Beta(2,2) 先验计算偏差的 MAP 估计值。与 MLE 估计值(7/10)进行比较。
关键术语
| 术语 | 常见说法 | 实际含义 |
|---|---|---|
| 先验 (prior) | "初始猜测" | 在观测到证据之前的 P(假设),在机器学习中对应正则化项 |
| 似然 (likelihood) | "数据拟合程度" | P(证据|假设),在特定假设下观测数据的概率 |
| 后验 (posterior) | "更新后的信念" | P(假设|证据),先验乘以似然后归一化 |
| 证据 (evidence) | "归一化常数" | 所有假设下的 P(数据),确保后验加和为 1 |
| 朴素贝叶斯 (naive Bayes) | "那个简单的文本分类器" | 假设给定类别条件下特征独立的分类器,尽管假设有误,效果仍好 |
| 拉普拉斯平滑 (Laplace smoothing) | "加一平滑" | 对每个特征加一个小计数,防止未见数据导致零概率 |
| 最大似然估计 (MLE) | "直接用频率" | 选择使 P(数据|参数) 最大的参数,无先验,数据少时可能过拟合 |
| 最大后验估计 (MAP) | "带先验的 MLE" | 选择使 P(数据|参数) * P(参数) 最大的参数,等价于正则化的 MLE |
| 对数概率 (log-probability) | "在对数空间工作" | 使用 log(P) 代替 P,避免乘以大量小数时的浮点下溢 |
| 假阳性 (false positive) | "错误警报" | 检测说阳性,但真实状态是阴性,导致基率谬误(base rate fallacy)的根源 |
延伸阅读
- 3Blue1Brown:贝叶斯定理 —— 配合医学检测示例的可视化解释
- 斯坦福 CS229:生成式学习算法 —— 朴素贝叶斯及其与判别式模型的联系
- Think Bayes —— 免费电子书,带 Python 代码的贝叶斯统计
- scikit-learn 朴素贝叶斯 —— 生产实现及各变体的使用时机