读完这一课,你将能够
- 用"把样本空间缩小到 \(B\) 内重新归一"解释条件概率 \(P(A\mid B)=P(A\cap B)/P(B)\),并算出具体数字。
- 把任意联合概率用链式法则 \(P(x_1,\dots,x_n)=\prod_t P(x_t\mid x_{
自回归语言模型逐 token 建模的数学根。 - 分清贝叶斯定理里的先验 / 似然 / 后验 / 证据四者,并独立做完一道医疗检测题,解释"基率谬误"为何反直觉。
- 说出条件独立假设是朴素贝叶斯的核心,并区分判别式 \(P(y\mid x)\) 与生成式 \(P(x,y)\) 两类模型。
- 用 numpy 扫描患病率,看到"基率越低、阳性后验越低"的数值规律。
上一课我们认识了各种分布与协方差——那讲的是"一个随机变量自己长什么样、两个变量怎样一起波动"。这一课我们问一个更动态的问题:知道了一件事,会怎样改变我们对另一件事的判断? 这就是条件概率 conditional probability。把它链起来,就得到现代语言模型的骨架;把它倒过来,就得到贝叶斯推断——机器从证据更新信念的通用法则。
1. 条件概率:把样本空间缩小,再重新归一
先别背公式,先想画面。掷一颗公平骰子,"点数是 6"的概率是 \(1/6\)。现在有人偷看了一眼告诉你"点数是偶数"。这条信息一进来,世界就变小了:原本 6 种等可能结果 \(\{1,2,3,4,5,6\}\),现在只剩 \(\{2,4,6\}\) 三种。在这个缩小后的世界里,"是 6"的概率变成了 \(1/3\)。
怎么把"在 \(B\) 里重新归一"写成公式?我们要的是"既在 \(A\) 又在 \(B\)"的那部分,占"整个 \(B\)"的比例:
\[ P(A\mid B)=\frac{P(A\cap B)}{P(B)},\qquad P(B)>0. \]分子 \(P(A\cap B)\) 是 \(A\) 与 \(B\) 同时发生(交集)的概率,分母 \(P(B)\) 就是那块"新全世界"的大小,除以它正是"重新归一"。回到骰子:\(A=\{6\}\),\(B=\{2,4,6\}\),\(A\cap B=\{6\}\),于是 \(P(A\mid B)=\frac{1/6}{3/6}=\frac{1}{3}\),和数面积得到的一致。
2. 乘法法则与链式法则:联合概率的"拆分术"
把上面的定义两边乘以 \(P(B)\),立刻得到乘法法则 product rule:
\[ P(A\cap B)=P(B)\,P(A\mid B)=P(A)\,P(B\mid A). \]意思很朴素:要 \(A\) 和 \(B\) 同时发生,可以"先发生 \(B\)(概率 \(P(B)\)),再在 \(B\) 已发生的条件下发生 \(A\)(概率 \(P(A\mid B)\))"。两个事件如此,三个、\(n\) 个也能一层层接龙——这就是链式法则 chain rule of probability:
\[ P(x_1,x_2,\dots,x_n)=P(x_1)\,P(x_2\mid x_1)\,P(x_3\mid x_1,x_2)\cdots P(x_n\mid x_1,\dots,x_{n-1}) \]写紧凑一点,记 \(x_{
ML 和 ML 的联系:这就是自回归语言模型
把 \(x_1,\dots,x_n\) 换成一句话里的 token(可以先粗略理解为"一个词或一个字",是模型处理文本的最小单位;它到底按词、按字、还是按"子词"切分,细节留到第 9 模块讲,本课不影响理解),链式法则就原封不动变成了 GPT 这类自回归语言模型 autoregressive LM 的定义。"一整句话的概率"被拆成"逐个 token 的条件概率"的乘积:
\[ P(\text{一句话})=\prod_{t}P(\text{第 }t\text{ 个 token}\mid \text{前面所有 token}). \]模型(一个巨大的神经网络 \(\theta\))要学的,从头到尾就是这一个条件分布 \(P_\theta(x_t\mid x_{
例题:展开一个 3 词"句子"的概率
把句子"我 爱 学习"当作三个 token \(x_1=\text{我},\ x_2=\text{爱},\ x_3=\text{学习}\)。用链式法则(\(n=3\))展开它的联合概率:
\[ P(\text{我},\text{爱},\text{学习})=P(\text{我})\cdot P(\text{爱}\mid\text{我})\cdot P(\text{学习}\mid\text{我},\text{爱}). \]逐项读:\(P(\text{我})\) 是"句子以『我』开头"的概率;\(P(\text{爱}\mid\text{我})\) 是"已经写了『我』,下一个是『爱』"的概率;\(P(\text{学习}\mid\text{我},\text{爱})\) 是"已经写了『我爱』,下一个是『学习』"的概率。假设语言模型给出 \(P(\text{我})=0.20\)、\(P(\text{爱}\mid\text{我})=0.10\)、\(P(\text{学习}\mid\text{我},\text{爱})=0.30\),则这句话的概率是
\[ 0.20\times0.10\times0.30=0.006. \]训练语言模型,就是让真实出现过的句子的这个乘积变大、没出现过的变小。注意每个因子的条件都在变长——这正是自回归"越往后看的上下文越多"的来源。
3. 独立与条件独立:什么时候可以"忽略历史"
链式法则总成立,但每个 \(P(x_t\mid x_{
独立 independence:若 \(P(A\cap B)=P(A)P(B)\),称 \(A,B\) 独立。等价地(当 \(P(B)>0\))\(P(A\mid B)=P(A)\)——知道 \(B\) 没有改变对 \(A\) 的判断。两次抛硬币、两颗骰子就是典型。
但完全独立太奢侈,现实里更常见、也更有用的是条件独立 conditional independence:在给定第三个变量 \(C\) 之后才独立:
\[ P(A\cap B\mid C)=P(A\mid C)\,P(B\mid C),\quad\text{记作}\ A\perp B\mid C. \]4. 全概率公式:把"证据"按情况拆开算
有时直接算 \(P(B)\) 很难,但如果世界能被切成几块情况 \(A_1,\dots,A_k\),我们就可以分情况加权求和。这几块要构成一个划分 partition,必须同时满足两条性质:互斥(两两不重叠,保证不重复计数)与穷尽(合起来铺满全部可能,保证不漏掉任何路径)。比如"患病 / 健康"就是一个划分(一个人非病即健,不会既病又健,也没有第三种),后面第 8 节的"spam / ham"同样是这样的划分。有了划分:
\[ P(B)=\sum_{i=1}^{k}P(A_i)\,P(B\mid A_i). \]这就是全概率公式 law of total probability。直觉:要到达终点 \(B\),必经过某一块 \(A_i\);把"先落在 \(A_i\)(权重 \(P(A_i)\))、再从 \(A_i\) 到 \(B\)(概率 \(P(B\mid A_i)\))"的所有路径概率加起来,就是 \(B\) 的总概率。互斥让这些路径不重叠相加,穷尽让我们一条不漏。它正是下面贝叶斯定理分母"证据"的算法。
5. 贝叶斯定理:把条件概率倒过来
我们常常能算 \(P(\text{证据}\mid\text{原因})\)(比如"真的患病时检测呈阳性的概率",这是仪器的物理特性,好测),但真正想知道的是反过来的 \(P(\text{原因}\mid\text{证据})\)("看到阳性了,到底有没有病")。把第 2 节乘法法则的两种写法 \(P(A\cap B)=P(B)P(A\mid B)=P(A)P(B\mid A)\) 一除,就得到贝叶斯定理 Bayes' theorem:
\[ \boxed{\;P(A\mid B)=\frac{P(B\mid A)\,P(A)}{P(B)}\;} \]换上推断的语言,记参数/假设为 \(\theta\)、观测数据为 \(D\):
\[ \underbrace{P(\theta\mid D)}_{\text{后验 posterior}}=\frac{\overbrace{P(D\mid\theta)}^{\text{似然 likelihood}}\ \overbrace{P(\theta)}^{\text{先验 prior}}}{\underbrace{P(D)}_{\text{证据 evidence}}} \]- 先验 prior \(P(\theta)\):看到数据之前,你对 \(\theta\) 的信念(如人群患病率)。
- 似然 likelihood \(P(D\mid\theta)\):假设 \(\theta\) 为真时,观测到这份数据有多"像"(如真患病时阳性的概率=敏感度)。注意它把数据 \(D\) 固定住、看作 \(\theta\) 的函数,问"哪个 \(\theta\) 让这份数据最像真的"。它不是关于 \(\theta\) 的概率分布——因为把它对所有可能的 \(\theta\) 加起来(或积分)一般不等于 1,没有"归一"这回事,所以谈不上是 \(\theta\) 的分布,它只是一条衡量"贴合度"的曲线。
- 后验 posterior \(P(\theta\mid D)\):看完数据后更新过的信念——我们最终想要的。
- 证据 evidence \(P(D)=\sum_\theta P(D\mid\theta)P(\theta)\):数据本身的总概率,由全概率公式算出,是个归一化常数,除以它就是第 1 节那个"重新归一"的动作,保证后验加起来为 1。
6. 经典例题:医疗检测与基率谬误
例题:阳性了,到底有多大概率真患病?
某病在人群中的患病率(基率 base rate)= 0.5%。一种检测的敏感度 sensitivity = 99%(真患病时检出阳性的概率),特异度 specificity = 95%(真健康时检出阴性的概率,于是健康者误报阳性的概率 = 5%)。你拿到一张阳性报告,请问你真患病的概率 \(P(\text{病}\mid+)\) 是多少?
记 \(D\)="患病",\(+\)="检测阳性"。已知:先验 \(P(D)=0.005\),似然 \(P(+\mid D)=0.99\),\(P(+\mid \bar D)=1-0.95=0.05\)。
第一步,全概率公式算证据 \(P(+)\):
\[ P(+)=P(+\mid D)P(D)+P(+\mid\bar D)P(\bar D)=0.99\times0.005+0.05\times0.995=0.0495+0.04975=0.05470. \]第二步,贝叶斯定理算后验:
\[ P(D\mid+)=\frac{P(+\mid D)P(D)}{P(+)}=\frac{0.99\times0.005}{0.05470}=\frac{0.00495}{0.05470}\approx 0.0905. \]也就是说,阳性者真正患病的概率只有约 9%,仍有约 91% 是虚惊一场(假阳性)。敏感度高达 99%,结论却如此反直觉——这就是基率谬误 base rate fallacy。
为什么反直觉?换成具体人头数就一目了然。设想 10000 人来体检(为了让各格相加严丝合缝,下面保留一位小数,不做四舍五入):
| 真患病(50 人) | 真健康(9950 人) | 合计 | |
|---|---|---|---|
| 检测阳性 | 真阳性 = 49.5 | 假阳性 = 497.5 | = 547.0 |
| 检测阴性 | 假阴性 = 0.5 | 真阴性 = 9452.5 | = 9453.0 |
关键在于分母悬殊:患病的人本来就只有 50 个(基率 0.5% 太低),即使敏感度 99% 几乎全抓到,也只有 49.5 个真阳性;而健康人有 9950 个,哪怕只误报 5%,也制造了 497.5 个假阳性。所以在所有 547 个阳性里,真患病的只占 \(49.5/547\approx 0.0905\),与前面贝叶斯定理算出的后验分毫不差。稀有的事件,再灵敏的检测也架不住庞大的健康人群贡献的假阳性洪流。
7. 频率派 vs 贝叶斯派;判别式 vs 生成式
频率派 vs 贝叶斯派,一句话区别在于怎么看待参数 \(\theta\):
- 频率派 frequentist:\(\theta\) 是一个固定但未知的常数,概率是"长期重复试验的频率"。它问"在这个 \(\theta\) 下数据出现的概率",不给 \(\theta\) 配分布。
- 贝叶斯派 Bayesian:\(\theta\) 本身是一个随机变量、有分布,概率是"信念的程度"。它给 \(\theta\) 一个先验,再用数据更新成后验。
这里争论的 \(\theta\),正是第 2 节那个语言模型的 \(\theta\)(动辄几亿、几千亿个参数):深度学习平时把它当成一个待求的常数、用数据去拟合它(下一课的 MLE 就是这种频率派精神),而贝叶斯神经网络则给每个参数配一个先验分布、更新成后验,是贝叶斯味道。下一课的 MAP(最大后验)带上了先验,正好站在两者之间——届时你会看到这条分界如何变成两种损失函数。
判别式 vs 生成式,区别在于建模哪个概率(设 \(x\) 是输入特征,\(y\) 是标签):
- 判别式 discriminative:直接学条件分布 \(P(y\mid x)\)——"给定这张图,它是猫的概率"。逻辑回归、神经网络分类器都属此类,只关心决策边界,不操心 \(x\) 自己怎么生成。
- 生成式 generative:学联合分布 \(P(x,y)=P(x\mid y)P(y)\)——既知道每类长什么样 \(P(x\mid y)\),又知道类的先验 \(P(y)\)。它能生成新样本,也能用贝叶斯定理反推 \(P(y\mid x)\propto P(x\mid y)P(y)\) 来做分类。
ML 串起来看
第 2 节的自回归语言模型 \(P_\theta(x_1,\dots,x_n)\) 直接建模文本的联合分布,是一个不带标签的纯生成式模型;而把图片判成猫狗的 CNN 分类器是判别式。下面要讲的朴素贝叶斯则是最经典的生成式分类器。两条路线后面会反复出现,记住这条 \(P(y\mid x)\) vs \(P(x,y)\) 的分界即可。
8. 朴素贝叶斯:一个"敢于装傻"的条件独立假设
用生成式思路做文本分类(如判断邮件是否垃圾邮件 spam):给定一封含词 \(w_1,\dots,w_n\) 的邮件,想算 \(P(\text{spam}\mid w_1,\dots,w_n)\)。由贝叶斯定理,它正比于 \(P(w_1,\dots,w_n\mid\text{spam})\,P(\text{spam})\)(这里就用上了第 5 节的 \(\propto\):把公共分母 \(P(w_1,\dots,w_n)\) 扔掉,因为对 spam、ham 两类都一样,只比大小用不着它)。可"一整封邮件里所有词的联合似然" \(P(w_1,\dots,w_n\mid\text{spam})\) 维度极高,根本估不出来。
朴素贝叶斯 naive Bayes 的"朴素"之处,就是大胆假设:给定类别后,各个词条件独立(第 3 节的条件独立)。于是高维似然瞬间拆成连乘:
\[ P(w_1,\dots,w_n\mid c)\approx\prod_{i=1}^{n}P(w_i\mid c),\qquad P(c\mid w_1,\dots,w_n)\propto P(c)\prod_{i=1}^{n}P(w_i\mid c). \]9. 动手算:用 numpy 看基率如何左右后验
先把上面的医疗例题复现一遍,再扫描患病率(基率),亲眼看到"基率越低、阳性后验越低"。下面把整张扫描表向量化:把所有患病率装进一个 numpy 数组,一次广播算出全部后验,名副其实地用上 numpy。代码只用 numpy + print。
import numpy as np
def posterior(prev, sens=0.99, spec=0.95):
# prev: 患病率(基率, 可为标量或 numpy 数组), sens: 敏感度 P(+|病), spec: 特异度 P(-|健康)
p_pos = sens * prev + (1 - spec) * (1 - prev) # 全概率公式: 证据 P(+)
return sens * prev / p_pos # 贝叶斯定理: 后验 P(病|+)
# 复现例题: 患病率 0.5%
print("例题 后验 P(病|+) =", round(float(posterior(0.005)), 4)) # 约 0.0905
# 扫描基率(患病率): 一个数组一次广播算出全部后验
prevs = np.array([0.0005, 0.001, 0.005, 0.01, 0.05, 0.1, 0.5])
posts = posterior(prevs) # numpy 广播, 一次算完
print("\n患病率(基率) 阳性后验 P(病|+)")
for prev, post in zip(prevs, posts):
print(f" {prev:7.4f} {post:.4f}")
运行后会打印出一张表:患病率 0.0005 时后验仅约 0.0098(不到 1%),0.005 时约 0.0905,0.05 时约 0.51,到 0.5 时高达约 0.95。同一台仪器,仅仅因为人群基率不同,"阳性意味着患病"的可信度可以从 1% 跳到 95%。这就是为什么大规模筛查罕见病时,光看一次阳性远远不够。
调一调,观察现象
下面每个小任务都改一个数,预测会发生什么,再跑代码验证。请先在脑中算一遍方向,再看数字对不对。
实验 1:把敏感度推到 100%,后验会变成 100% 吗?
改什么:保持患病率 0.5%、特异度 95%,把 sens 从 0.99 提到 1.0(完美敏感度)。
预期看到:后验只从约 0.0905 微升到约 0.0913,远不到 100%。
为什么:敏感度只管"别漏掉病人",可阳性里绝大多数是健康人的假阳性。要提升后验,真正该动的是特异度(减少假阳性)和基率,而不是敏感度。
import numpy as np
def posterior(prev, sens, spec):
p_pos = sens*prev + (1-spec)*(1-prev)
return sens*prev/p_pos
print("sens=0.99 ->", round(posterior(0.005, 0.99, 0.95), 4)) # 0.0905
print("sens=1.00 ->", round(posterior(0.005, 1.00, 0.95), 4)) # 0.0913实验 2:把特异度从 95% 提到 99%,后验跳多少?
改什么:患病率 0.5%、敏感度 99% 不变,把 spec 从 0.95 提到 0.99。
预期看到:后验从约 0.0905 大涨到约 0.3322,翻了三倍多。
为什么:特异度 0.95→0.99 把健康人的误报率从 5% 砍到 1%,假阳性从约 498 人骤降到约 100 人,分母塌缩,真患病者的占比自然飙升。对罕见病,减少假阳性比提高检出率更能提升阳性的含金量。
import numpy as np
def posterior(prev, sens, spec):
p_pos = sens*prev + (1-spec)*(1-prev)
return sens*prev/p_pos
print("spec=0.95 ->", round(posterior(0.005, 0.99, 0.95), 4)) # 0.0905
print("spec=0.99 ->", round(posterior(0.005, 0.99, 0.99), 4)) # 0.3322实验 3:连做两次检测(朴素贝叶斯式的独立假设)
改什么:用第一次阳性算出的后验当作新的先验,再代入第二次阳性(假设两次检测条件独立)。
预期看到:第一次阳性后约 0.0905,把它当先验再算第二次阳性,后验跃升到约 0.6633。
为什么:这正是贝叶斯"后验变先验、逐步更新"的精髓,也暗含朴素贝叶斯的条件独立假设——两条独立证据相乘,比单条证据强得多。这就是医学上为何要复检。
import numpy as np
def posterior(prev, sens=0.99, spec=0.95):
p_pos = sens*prev + (1-spec)*(1-prev)
return sens*prev/p_pos
p1 = posterior(0.005) # 第一次阳性后的后验
p2 = posterior(p1) # 把它当新先验, 再来一次阳性
print("第一次阳性后:", round(p1, 4)) # 0.0905
print("第二次阳性后:", round(p2, 4)) # 0.6633动手练习
- 条件概率手算。一副去掉大小王的 52 张牌,已知抽到的是红色牌,求它是红桃 K 的条件概率。用 \(P(A\mid B)=P(A\cap B)/P(B)\) 验证:答案应为 \(1/26\)。
import numpy as np # 红色牌共 26 张, 其中红桃K 1 张 p_AB = 1/52 # P(红桃K 且 红色) = P(红桃K) p_B = 26/52 # P(红色) print("P(红桃K|红色) =", p_AB/p_B) # 应为 1/26 ≈ 0.0385 - 链式法则展开。把 4 个 token 的句子概率用链式法则写成 4 个条件概率的乘积,并给定一组数值算出联合概率。补全骨架:
想一想:为什么真实语言模型用对数相加而不是概率连乘?一句话里动辄几百个 token,每个条件概率都小于 1,几百个这样的数连乘,结果会小到突破浮点数能表示的最小正数(约 \(10^{-308}\)),被计算机直接截断成 0——这叫数值下溢 underflow,一旦发生,概率信息全丢、梯度也没法回传。取对数后,"连乘"变成"相加"(\(\log\prod p_i=\sum\log p_i\)),几百个负数相加完全在浮点数的舒适区,又稳又快。import numpy as np # P(x1,x2,x3,x4) = P(x1)P(x2|x1)P(x3|x1,x2)P(x4|x1,x2,x3) p = np.array([0.3, 0.5, 0.4, 0.6]) # 四个逐 token 条件概率 print("联合概率 =", np.prod(p)) # = 0.036 print("对数概率 =", np.sum(np.log(p))) # 语言模型实际用对数相加, 防下溢 - 基率扫描表。修改第 9 节代码,固定敏感度 99%、特异度 90%,扫描患病率从 0.001 到 0.2,打印后验表。观察:相比特异度 95% 的版本,后验整体是更高还是更低?为什么?
- (可选)最小朴素贝叶斯文本分类。用现场合成的小词频做一个二类分类器,体会"条件独立 + 比较得分"。
import numpy as np # 词表: [免费, 学习, 中奖, 课程], 两类: spam / ham # P(word|class): 每行一类, 每列一词 (已做简单平滑) P_w = np.array([[0.4, 0.05, 0.4, 0.05], # spam [0.05, 0.4, 0.05, 0.4]]) # ham P_c = np.array([0.3, 0.7]) # 先验: 30% spam doc = np.array([1, 0, 1, 0]) # 邮件含 "免费" 和 "中奖" # 朴素贝叶斯: log P(c) + sum_i doc_i * log P(w_i|c) scores = np.log(P_c) + doc @ np.log(P_w.T) post = np.exp(scores) / np.exp(scores).sum() print("后验 [spam, ham] =", np.round(post, 4)) # spam 占压倒多数 print("判为:", "spam" if post[0] > post[1] else "ham")
掌握自检
- 我能用"缩小样本空间再归一"向别人解释 \(P(A\mid B)\),并说清它和 \(P(B\mid A)\) 为什么不同。
- 给一个长度为 \(n\) 的序列,我能立刻写出它的链式法则展开 \(\prod_t P(x_t\mid x_{
- 面对一道医疗/检测题,我能分清先验、似然、后验、证据,用全概率公式算分母、贝叶斯定理算后验,并算出反直觉的低后验。
- 我能用"基率太低、假阳性洪流"解释基率谬误,并说出提高后验最该动特异度还是敏感度。
- 我能一句话说清频率派 vs 贝叶斯派(参数是常数 vs 分布)、判别式 vs 生成式(\(P(y\mid x)\) vs \(P(x,y)\)),并指出朴素贝叶斯靠的是条件独立假设。
下一课,我们把这一课的"概率假设"正式翻译成损失函数:最大似然估计 MLE 让"数据出现概率最大",最大后验 MAP 再乘上先验——你会看到熟悉的交叉熵、均方误差正是从 \(\prod_t P(x_t\mid x_{可以先放过的点