首页 / 模块 2 · 概率统计、信息论与最优化 / 第 3 课(共 10 课)

条件概率与贝叶斯

从零到前沿 ML 自学课程 · 阶段0:数学与工具基础 · 能力点:贝叶斯思维——先验/似然/后验;自回归 LM 的概率根 P(x)=∏P(xₜ|x_<t)

读完这一课,你将能够

  • 用"把样本空间缩小到 \(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\) 这一块,然后在这一小块里重新把概率归一化到 1"。原来 \(B\) 只占一部分,现在 \(B\) 就是全世界。所谓"归一化",机械含义只有一个动作:把每一份的概率都除以这一块的总概率,让它们加起来正好等于 1。这一课里它会出现三次(这里除以 \(P(B)\)、第 5 节除以证据 \(P(D)\)、第 8 节用得分总和去除),认准它是同一个动作。

怎么把"在 \(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}\),和数面积得到的一致。

条件概率:缩小样本空间并重新归一原样本空间 Ω11/621/631/641/651/661/6B(偶数)= {2,4,6}A = {6},A∩B = {6}P(A) = P(6) = 1/6缩小到 B 内+ 重新归一已知 B:缩小并重新归一新全世界 = {2,4,6}21/341/361/3P(A|B) = 1/3= (1/6) / (3/6)
条件概率 = 缩小样本空间再归一:掷骰子的 6 种等可能结果中,已知"点数为偶数"后世界缩小到 {2,4,6},在这块新全世界里"是 6"的概率从 1/6 变成 1/3。左图为完整样本空间(A∩B 高亮),右图为缩小并重新归一后的条件世界。
关键结论:条件概率 \(P(A\mid B)\) 与 \(P(B\mid A)\) 一般不相等(分母不同)。把它们混为一谈,是后面"基率谬误"的根源,也是日常推理最常见的错误之一。

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_{ \[ \boxed{\;P(x_1,\dots,x_n)=\prod_{t=1}^{n}P(x_t\mid 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_{

链式法则即自回归语言模型:P(我,爱,学习) 拆成逐 token 条件概率之积 联合概率 P(我, 爱, 学习) 等于 P(我)·P(爱|我)·P(学习|我,爱),值依次为 0.20、0.10、0.30,乘积 0.006,体现从左到右逐 token 的自回归生成。 联合概率 P(我, 爱, 学习) = P_θ(x_t | x_<t) P(我) 句子开头 0.20 × P_θ(x_t | x_<t) P(爱 | 我) 看到『我』预测下一个 0.10 × P_θ(x_t | x_<t) P(学习 | 我,爱) 看到『我爱』预测下一个 0.30 = 0.006 x₁ x_t 自回归 autoregressive:条件越来越长,从左到右逐 token 灰色箭头 = 上下文流(前文作为下一步的条件) 这正是 GPT 逐 token 生成的机制
链式法则即自回归语言模型:把句子"我 爱 学习"的联合概率 P(我,爱,学习) 拆成 P(我)·P(爱|我)·P(学习|我,爱),每一项是"给定前文、预测下一个 token"的条件概率——这正是 GPT 逐 token 生成的机制。

例题:展开一个 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. \]
直觉:夏天里"冰淇淋销量高"和"溺水事故多"看起来正相关,但它们之间并没有因果关系——真正的推手是气温这个共同原因 \(C\):天热既让人买冰淇淋,也让人下水游泳。一旦给定气温(比如只看所有 35℃ 的日子),冰淇淋销量就不再能告诉你溺水多不多了——两者变成条件独立。条件独立的本质就是:相关性常常来自一个共同原因 \(C\),把 \(C\) 固定住,虚假的关联就消失了。
关键结论:独立 \(\not\Rightarrow\) 条件独立,条件独立 \(\not\Rightarrow\) 独立——这是两个独立的概念。条件独立是几乎所有概率图模型(朴素贝叶斯、HMM、贝叶斯网络)赖以"把大联合分布拆小"的核心假设。

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}}} \]
一句话记牢:\(\text{后验}\propto\text{似然}\times\text{先验}\)。这里的 \(\propto\) 读作"正比于":左右两边只差一个不依赖 \(\theta\) 的公共常数(就是分母 \(P(D)\),它对每个 \(\theta\) 都一样)。所以当我们只想比较哪个 \(\theta\) 的后验更大时,可以直接把这个公共分母扔掉——这个小技巧第 8 节朴素贝叶斯会立刻用上。新信念 = 旧信念 被 数据的支持度 重新加权。学习,就是不断用证据更新先验得到后验的过程。
贝叶斯更新:先验经似然重新加权得到后验posterior ∝ likelihood × prior先验 prior P(θ)看数据前:分散、偏左θ似然 likelihood P(D|θ)数据在这附近最像θ后验 posterior P(θ|D)更集中、移向数据θ得到看到数据后:信念更集中、移向被数据支持处
贝叶斯更新:先验经过似然(看到数据)的重新加权,得到后验。后验 ∝ 似然 × 先验——证据让信念从分散变集中、从旧位置移到被数据支持的位置。

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 人来体检(为了让各格相加严丝合缝,下面保留一位小数,不做四舍五入):

基率谬误的人群方框分流图:稀有病阳性者多数没病 10000人按0.5%患病率分为50患病与9950健康,患病几乎全被检出(真阳性约50),健康人群5%误报产生约498假阳性,所有约547个阳性里真患病仅约9%。 10000 人 受检人群 患病 50 人 基率 0.5% 健康 9950 人 占人群 99.5%(面积远大于患病) 真阳性 TP ≈ 50(敏感度99%) 假阴 ≈ 0 假阳性 FP ≈ 498(误报5%) 真阴性 TN ≈ 9452 所有阳性 ≈ 547 人 把上面两个阳性块并排,按面积比例 TP 50 假阳性 FP ≈ 498 健康人群的误报淹没了真患者 P(病 | +) = 50 / 547 ≈ 9% 稀有病:庞大健康人群的假阳性淹没了真阳性 真阳/真患者 阳性/假阳 健康/真阴
基率谬误的人群方框图:10000 人按患病率 0.5% 分为 50 患病、9950 健康,再各自分真阳/假阳。敏感度 99% 抓到约 50 个真阳性,但健康人群即使只误报 5% 也产生约 498 个假阳性——所以在全部约 547 个阳性里,真患病的只占约 9%。
真患病(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\),与前面贝叶斯定理算出的后验分毫不差稀有的事件,再灵敏的检测也架不住庞大的健康人群贡献的假阳性洪流。

易错:很多人把"敏感度 \(P(+\mid D)=99\%\)"直接当成"阳性就 99% 有病 \(P(D\mid +)\)"——这正是把 \(P(B\mid A)\) 和 \(P(A\mid B)\) 弄反(第 1 节的警告)。两者通过贝叶斯定理相连,差的那个因子就是基率。忽略基率,是医生、陪审团、AI 误判的共同陷阱。基率越低,这个差距越夸张。

7. 频率派 vs 贝叶斯派;判别式 vs 生成式

频率派 vs 贝叶斯派,一句话区别在于怎么看待参数 \(\theta\)

这里争论的 \(\theta\),正是第 2 节那个语言模型的 \(\theta\)(动辄几亿、几千亿个参数):深度学习平时把它当成一个待求的常数、用数据去拟合它(下一课的 MLE 就是这种频率派精神),而贝叶斯神经网络则给每个参数配一个先验分布、更新成后验,是贝叶斯味道。下一课的 MAP(最大后验)带上了先验,正好站在两者之间——届时你会看到这条分界如何变成两种损失函数。

判别式 vs 生成式,区别在于建模哪个概率(设 \(x\) 是输入特征,\(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). \]
直觉:这个假设其实不真——"机器"和"学习"明显常一起出现,并不独立。但"装傻"换来了巨大的简化:每个 \(P(w_i\mid c)\) 只要数一数"词 \(w_i\) 在该类文档里出现的频率"就能估出来。神奇的是,即便假设错了,朴素贝叶斯在文本分类上效果常常出奇地好——因为我们只需要比较哪一类的得分更大,排序往往对,绝对概率值算偏一点也无妨(这正是上面那个"只比大小、可扔掉公共分母"的好处)。

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

动手练习

  1. 条件概率手算。一副去掉大小王的 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
  2. 链式法则展开。把 4 个 token 的句子概率用链式法则写成 4 个条件概率的乘积,并给定一组数值算出联合概率。补全骨架:
    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))) # 语言模型实际用对数相加, 防下溢
    想一想:为什么真实语言模型用对数相加而不是概率连乘?一句话里动辄几百个 token,每个条件概率都小于 1,几百个这样的数连乘,结果会小到突破浮点数能表示的最小正数(约 \(10^{-308}\)),被计算机直接截断成 0——这叫数值下溢 underflow,一旦发生,概率信息全丢、梯度也没法回传。取对数后,"连乘"变成"相加"(\(\log\prod p_i=\sum\log p_i\)),几百个负数相加完全在浮点数的舒适区,又稳又快。
  3. 基率扫描表。修改第 9 节代码,固定敏感度 99%、特异度 90%,扫描患病率从 0.001 到 0.2,打印后验表。观察:相比特异度 95% 的版本,后验整体是更高还是更低?为什么?
  4. (可选)最小朴素贝叶斯文本分类。用现场合成的小词频做一个二类分类器,体会"条件独立 + 比较得分"。
    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")

掌握自检

下一课,我们把这一课的"概率假设"正式翻译成损失函数:最大似然估计 MLE 让"数据出现概率最大",最大后验 MAP 再乘上先验——你会看到熟悉的交叉熵均方误差正是从 \(\prod_t P(x_t\mid x_{