某种病的患病率是 1%,检测的准确率是 95%。你测出阳性,真正患病的概率是多少?大多数人(包括不少医生)脱口而出”95%“,正确答案不到 20%。概率论的价值就在这里:直觉在不确定性面前很容易出错,而一套简单的计算规则能把它纠正过来。
前置:会用 Python 写循环和函数即可。
从数数开始
最朴素的概率定义是:所有结果等可能时,
于是概率问题的第一步往往是计数。经典的生日问题:一个房间里有 个人,至少两人同一天生日的概率是多少?
直接数”至少两人相同”很麻烦,数反面容易得多:所有人生日都不同。第一个人有 365 种选择,第二个人 364 种,……,第 个人 种。
import numpy as np
def p_shared(k):
p_distinct = 1.0
for i in range(k):
p_distinct *= (365 - i) / 365
return 1 - p_distinct
for k in [10, 23, 50, 70]:
print(k, round(p_shared(k), 4))
# 用模拟交叉验证:公式和实验应当吻合
rng = np.random.default_rng(0)
trials = rng.integers(0, 365, size=(100_000, 23))
hit = np.mean([len(set(row)) < 23 for row in trials])
print("simulated k=23:", hit)输出:
10 0.1169
23 0.5073
50 0.9704
70 0.9992
simulated k=23: 0.5069923 个人就超过一半。直觉错在哪?我们下意识地在想”有人和我同一天生日”,但实际要比较的是所有人对:23 个人有 对,每对撞上的概率是 ,机会比想象的多得多。这和哈希冲突是同一个现象:往 个桶里扔大约 个元素就很可能撞上(见 数据结构 里的哈希表)。
两个技巧值得留下:一是算补集,“至少一个”的问题几乎总该从”一个也没有”算起;二是用模拟验证,几行 numpy 就能检查一个公式是否算错,这在后面会反复使用。计数本身的系统方法(排列、组合、容斥)见 离散数学。
朴素定义的前提”等可能”经常被悄悄违反。掷两颗骰子,点数和为 7 有 6 种结果、和为 12 只有 1 种,如果把”和”的 11 个取值当成等可能,就会错。判断标准是回到最细的结果(有序的两颗骰子点数对,共 36 个),确认它们对称,再去数。
一个哈希表有 10000 个桶,键均匀随机地落入桶中。插入大约多少个键时,出现至少一次冲突的概率就超过一半?
这是桶数为 10000 的生日问题。k 个键约有 k 乘 k 除以 2 个键对,每对冲突概率是万分之一,期望冲突对数到 0.7 左右时概率就过半,对应 k 约为 1.18 乘以根号 10000,即约 118。
5000 是按"桶占满一半"去想,忽略了键对的数量随 k 平方增长。1000 已远超阈值。23 是 365 天时的答案,不能直接搬到 10000 个桶上。
条件概率与贝叶斯
现在回到开头的检测问题。我们需要的工具是条件概率:已知 发生, 发生的概率
直觉是:既然知道 发生了,样本空间就缩小成 ,在里面看 占多少。
把定义改写一下,,再代回去,就得到贝叶斯公式:
分母 通常用全概率公式展开:把世界按 和非 切成两块,。
代入数字。设 = 患病, = 阳性。,,。
不用公式也能想明白:想象 10000 个人,其中 100 人患病,95 人测出阳性;9900 个健康人里有 5% 也就是 495 人假阳性。阳性一共 590 人,真病人只有 95 个。先验太小时,即便检测很准,假阳性的人数也会压倒真阳性。
import numpy as np
rng = np.random.default_rng(1)
n = 1_000_000
sick = rng.random(n) < 0.01
# 病人以 0.95 测阳,健康人以 0.05 测阳
positive = np.where(sick, rng.random(n) < 0.95, rng.random(n) < 0.05)
print("P(sick | positive) ≈", sick[positive].mean().round(4))
# 再测一次也阳性:上一次的后验成为这一次的先验
both = positive & np.where(sick, rng.random(n) < 0.95, rng.random(n) < 0.05)
print("P(sick | 两次阳性) ≈", sick[both].mean().round(4))输出:
P(sick | positive) ≈ 0.1617
P(sick | 两次阳性) ≈ 0.7849第二次检测把概率从 16% 推到 78% 左右。这体现了贝叶斯思维的核心:后验 ∝ 似然 × 先验,新证据不断更新旧信念。这个前提是两次检测在给定是否患病的条件下相互独立。
独立的定义是 ,等价于 :知道 不改变你对 的判断。独立和互斥是两码事,互斥的两个事件(概率都大于 0)一定不独立,因为知道一个发生,另一个就不可能发生了。
上面两次检测用到的是更细的概念:条件独立。给定是否患病,两次检测结果互不影响;但不给定这个条件时,两次结果是强相关的,第一次阳性会让第二次阳性的概率明显上升,因为它提高了”患病”的可能。条件独立和独立互不蕴含。朴素贝叶斯分类器的”朴素”,就是假设给定类别后各个特征条件独立,这个假设通常不成立,但分类效果往往依然不错。
条件概率还有一个经典陷阱:Monty Hall。三扇门后一辆车、两只羊,你选 1 号门,知道答案的主持人打开另一扇有羊的门,问你换不换。换门赢的概率是 2/3。关键在于主持人的行为不是随机的,他必须开一扇羊门,这个约束本身就携带了信息。条件概率的计算永远要问清楚:信息是怎么产生的。
某垃圾邮件过滤器:垃圾邮件占 20%,它对垃圾邮件报警的概率是 90%,对正常邮件误报的概率是 10%。一封邮件被报警,它是垃圾邮件的概率最接近多少?
按全概率,报警的总概率是 0.9 乘 0.2 加 0.1 乘 0.8,等于 0.26;其中垃圾邮件贡献 0.18,所以后验是 0.18 除以 0.26,约 69%。
90% 是似然,把条件方向弄反了。20% 是先验,忽略了报警这个证据。18% 是联合概率,忘了除以报警的总概率。
随机变量与期望
事件只能回答”是/否”,很多问题关心的是一个数:掷 10 次硬币几次正面?一个请求的延迟多少毫秒?随机变量 就是把每个结果映射到一个数的函数,它的分布描述了每个取值的概率。
最常用的汇总量是期望,即按概率加权的平均:。期望有一条几乎被低估的性质,线性性:
它不需要 和 独立。这让很多看起来很难的问题变得一行可解,诀窍是指示变量:把复杂的计数拆成一堆 0/1 变量之和。
例子: 个人把帽子扔进一堆,随机各取一顶,平均有几个人拿回自己的帽子?设 表示第 个人拿回自己的帽子,那么 ,所以
无论 是 3 还是 100 万,答案都是 1。各 之间显然不独立(前 个人都拿对了,最后一个人一定拿对),但线性性不在乎。
import numpy as np
rng = np.random.default_rng(2)
for n in [3, 10, 1000]:
matches = [(rng.permutation(n) == np.arange(n)).sum() for _ in range(20000)]
print(n, np.mean(matches).round(3), "P(0 matches) =", np.mean(np.array(matches) == 0).round(3))输出:
3 1.001 P(0 matches) = 0.333
10 1.001 P(0 matches) = 0.367
1000 1.001 P(0 matches) = 0.367顺便看到一个漂亮的数: 大时一个都没拿对的概率趋向 。期望告诉我们中心在哪,方差告诉我们离中心有多散:
方差不是线性的:;只有 独立(更准确地说不相关)时才有 。一般情况要加上 ,协方差 衡量两者一起变动的倾向,归一化后就是相关系数 。注意相关系数为 0 只说明没有线性关系: 在 上均匀分布, 完全由 决定,但两者相关系数为 0。
还有一个计算期望的利器:全期望公式 ,即先在每种情况下求条件期望,再按情况的概率加权平均。它的用法是”对第一步取条件”。
例子:反复掷硬币直到出现正面,平均要掷几次?设答案为 。第一次掷出正面(概率 ),总共 1 次;掷出反面(概率 ),已经用掉 1 次,而且局面和开始时一模一样,还要平均再掷 次。于是
这正是几何分布的期望。同样的套路可以求”连续两次正面”要等多久:设三个状态(刚开始、上一次是正面、已完成),对每个状态列一个方程,解一个小线性方程组,答案是 6 次。```python import numpy as np rng = np.random.default_rng(6) def flips_until_HH(): count, prev_head = 0, False while True: count += 1 head = rng.random() < 0.5 if head and prev_head: return count prev_head = head print(“E[等到 HH 的次数] ≈”, np.mean([flips_until_HH() for _ in range(100_000)]).round(3))
输出:
```text
E[等到 HH 的次数] ≈ 5.994
这种”按下一步的情况拆开,得到关于期望的方程”的思路,在强化学习里就是 Bellman 方程。
关于期望和方差,哪些说法正确?
期望的线性性对任何随机变量都成立,这正是指示变量方法的基础。方差是平方量,常数提出来要平方。不相关只排除线性关系,Y = X^2 就是不相关但完全依赖的例子。
方差相加需要协方差为 0;一般情况下要加上两倍协方差,比如 X 与自身相加,方差是 4 倍而不是 2 倍。
几个反复出现的分布
具体问题里的随机变量,大多数属于少数几个”家族”。记住它们各自描述什么故事,比记住公式更有用。
| 分布 | 故事 | 期望 | 方差 |
|---|---|---|---|
| Bernoulli() | 一次试验成功与否 | ||
| Binomial() | 次独立试验的成功次数 | ||
| Geometric() | 直到第一次成功所需的试验次数 | ||
| Poisson() | 单位时间内稀有事件发生次数 | ||
| Uniform() | 区间上等可能 | ||
| Exponential() | 稀有事件之间的等待时间 | ||
| Normal() | 许多小扰动之和 |
它们之间有联系。Binomial 是 个 Bernoulli 之和,所以期望 可以直接由线性性得到。 很大、 很小、 固定时,Binomial 近似 Poisson:一个网站每秒有一百万用户、每人以百万分之三的概率点击,每秒点击数大约是 Poisson(3)。Poisson 过程里两次事件之间的间隔服从指数分布。
连续分布用概率密度 描述,。密度本身不是概率,可以大于 1,只有积分才是。
指数分布有一个反直觉的性质:无记忆性。。已经等了 10 分钟的公交(如果到达间隔真的是指数分布),剩余等待时间的分布和刚到站时完全一样。几何分布是它的离散版本。
import numpy as np
rng = np.random.default_rng(3)
x = rng.exponential(scale=5.0, size=1_000_000) # 平均等待 5 分钟
print("E[X] ≈", x.mean().round(3))
print("E[X - 10 | X>10] ≈", (x[x > 10] - 10).mean().round(3)) # 已经等了 10 分钟
# Binomial(n=1000, p=0.003) 与 Poisson(3) 的前几项概率
from math import comb, exp, factorial
for k in range(5):
b = comb(1000, k) * 0.003**k * 0.997**(1000 - k)
p = exp(-3) * 3**k / factorial(k)
print(k, round(b, 4), round(p, 4))输出:
E[X] ≈ 5.004
E[X - 10 | X>10] ≈ 4.99
0 0.0496 0.0498
1 0.1491 0.1494
2 0.2242 0.224
3 0.2244 0.224
4 0.1683 0.168正态分布在表里看似只是一员,但它的地位特殊,原因在下一节。
服务器平均每分钟收到 2 个故障报警,报警按 Poisson 过程到达。刚刚收到一个报警,到下一个报警的平均等待时间是多少?
Poisson 过程的到达间隔服从参数为 2(每分钟)的指数分布,期望是 1 除以 2,即半分钟。
2 是每分钟的平均次数,不是间隔。1 分钟没有依据。指数分布无记忆,过去等了多久不影响未来的等待时间。
大数定律与中心极限定理
前面一直在用模拟验证答案:扔 10 万次,取平均,和公式对上。为什么这么做是对的?
设 独立同分布,均值 ,方差 。样本均值 的期望是 ,方差是 (独立时方差相加,再除以 )。方差随 趋于 0,所以:
大数定律:。模拟有效、蒙特卡洛估计有效、小批量梯度是全量梯度的无偏估计,都是这条定律在背书。
方差 还告诉我们误差缩得多快:标准差是 。想把误差减半,样本要变成 4 倍;想多一位有效数字,样本要变成 100 倍。
大数定律只说均值收敛到哪,没说它在收敛途中长什么样。中心极限定理回答了这个:不管 本身是什么分布(只要方差有限),
这就是正态分布无处不在的原因:许多独立小扰动的叠加,结果都像正态。
import numpy as np
rng = np.random.default_rng(4)
# 指数分布严重右偏,看看它的样本均值会怎样
for n in [1, 5, 30, 200]:
means = rng.exponential(1.0, size=(100_000, n)).mean(axis=1)
z = (means - 1.0) / (1.0 / np.sqrt(n))
skew = np.mean(z**3)
inside = np.mean(np.abs(z) < 1.96)
print(f"n={n:3d} std(mean)={means.std():.4f} skew={skew:.3f} P(|z|<1.96)={inside:.3f}")输出:
n= 1 std(mean)=0.9956 skew=1.981 P(|z|<1.96)=0.949
n= 5 std(mean)=0.4471 skew=0.895 P(|z|<1.96)=0.956
n= 30 std(mean)=0.1818 skew=0.347 P(|z|<1.96)=0.953
n=200 std(mean)=0.0707 skew=0.145 P(|z|<1.96)=0.951指数分布的偏度是 2,取 30 个平均后偏度降到 0.35 左右,200 个时只剩 0.15,大致按 衰减,分布越来越对称。样本均值的标准差也精确地按 缩小。最后一列要谨慎解读: 时落在 内的比例也恰好接近 95%,这只是巧合,单看一个区间概率并不能说明分布已经是正态的,偏度这种形状指标更能说明问题。
这两个定理有两个直接的工程含义。第一,误差棒:报告一个从 次实验估计出的指标时, 给出约 95% 的置信区间。比较两个模型在 benchmark 上的分数时,如果题目只有几百道,1–2 个百分点的差距可能完全在噪声里(见 评估)。第二,batch size:梯度是许多样本梯度的平均,batch 增大到 4 倍,梯度噪声的标准差减半。
定理的前提也要记住。独立和方差有限任何一个不满足,结论都可能失效。网络延迟、文件大小这类重尾分布,少数极端值主导平均,均值收敛得非常慢;这时看中位数和 p99 比看均值更有意义。
用 1 万次模拟估计一个概率,标准误差约为 0.005。想把标准误差降到 0.001,大约需要多少次模拟?
标准误差与样本数的平方根成反比,误差要缩小到五分之一,样本数要乘以 25,即 25 万次。
5 万次是把误差和样本数当成线性关系。10 万次和 100 万次分别对应缩小约 3.2 倍和 10 倍。
从概率到估计:最大似然
到这里,我们一直在”已知分布,算概率”。机器学习面对的是反方向:已知数据,猜分布的参数。
扔一枚硬币 10 次,7 次正面。 该估计为多少?最大似然估计(MLE)的想法是:选择让观测数据最可能出现的参数。似然函数
取对数(乘积变求和,单调性不变),求导令其为 0:,得 。和直觉一致,就是频率。
import numpy as np
p = np.linspace(0.01, 0.99, 99)
loglik = 7 * np.log(p) + 3 * np.log(1 - p)
print("argmax p =", p[np.argmax(loglik)].round(2))
# 正态数据的 MLE:均值就是样本均值,方差是除以 n 的样本方差
rng = np.random.default_rng(5)
x = rng.normal(loc=3.0, scale=2.0, size=10_000)
print("mu_hat =", x.mean().round(3), " sigma2_hat =", x.var().round(3))输出:
argmax p = 0.7
mu_hat = 3.043 sigma2_hat = 4.047这件事和机器学习的关系比看上去紧密得多。对独立样本,最大化 等价于最小化负对数似然。若假设 服从以模型输出为中心的正态分布,负对数似然就是均方误差;若假设 是类别分布、模型输出 softmax 概率,负对数似然就是交叉熵。语言模型预训练最小化的正是下一个 token 的负对数似然。这条推导在 从线性回归到神经网络 里展开。
MLE 只看数据。数据很少时它会给出极端估计:扔 3 次全是正面,MLE 说 ,即下次一定正面,这显然过于自信。贝叶斯的做法是给参数一个先验,再用数据更新。选后验最大的点,叫 MAP 估计。对硬币,如果先验相当于”事先已经看过 1 次正面 1 次反面”,3 次正面后的 MAP 估计是 而不是 1。对线性模型,参数的正态先验在 MAP 里变成了 L2 正则项,也就是 weight decay。正则化可以理解成先验。
关于最大似然估计,哪些说法正确?
交叉熵就是类别分布的负对数似然;正态分布的负对数似然展开后,与参数有关的部分就是平方误差;正态先验取对数是参数的平方,加进目标里就是 L2 项。
MLE 不保证无偏(正态方差的 MLE 除以 n 就是有偏的),而且小样本时会给出全正面就估计 p 等于 1 这类极端结论。
记住这三件事
- 条件概率要先问”信息是怎么来的”;先验很小时,即使证据很准,后验也可能很低。
- 期望是线性的,不需要独立;把复杂计数拆成指示变量,很多问题一行可解。
- 样本均值的误差按 缩小且近似正态;最大似然把”拟合数据”变成”最小化负对数似然”,交叉熵和 MSE 都由此而来。
延伸阅读
- Harvard Stat 110(Joe Blitzstein)课程主页,讲课视频在 YouTube
- Blitzstein & Hwang,《Introduction to Probability》(免费 PDF)
- Seeing Theory:布朗大学的可视化概率入门
- MIT 6.041 / 6.431《Probabilistic Systems Analysis》(Bertsekas & Tsitsiklis 教材)
- Christopher Bishop,《Pattern Recognition and Machine Learning》第 1–2 章(MLE、MAP 与 ML 的联系)