上一节能用公式,是因为先验和似然是「一对」。换个模型就没这好事:后验等于「先验乘似然, 再除以一个积分」,而这个积分绝大多数时候算不出来。 MCMC 的答案是:别求公式,去抽样本。只要能从后验里抽出一堆数,均值、区间、 概率全能估——这比推导一个积分容易得多。
这一节是第 6 章的技术核心,也是全书唯一一节「连答案都算不出来」的内容。前面每一节都在算, 这一节在「绕」。 它跟第 5 章的搜索也有血缘关系。搜索是「在一个图上走」, MCMC 也是「走」—— 只不过走的规则换成了「按概率决定往哪走」,于是走出来的轨迹,恰好就是目标分布的形状。
一、为什么会走到这一步
贝叶斯那套公式本身没问题: 后验 ∝ 先验 × 似然。问题出在后面那一步——要把右边除一个常数,才能让它积成 1。 那个常数就是「所有可能情况的概率之和」,也就是一个积分。 共轭的时候它能用公式直接写出来(上一节那个 Beta(9, 5) 就是); 可一旦模型稍微复杂一点,这个积分就没有解析解。 最典型的例子是「多层」的模型:数据的参数依赖另一层参数,一层套一层,积分变成高维的, 维数一高,网格法也算不动(十维每个方向取一百个点,就是一亿亿个格子)。 MCMC 换了个思路:不要公式,要样本。有了样本,均值、分位数、区间全都能算; 而且它只需要「能算出某个位置的概率密度」,不需要「能算出归一化常数」—— 因为那个常数在「比值」里会约掉。这一点是整节的关键:它只用密度的比值,不用密度本身。
二、Metropolis-Hastings:提议、接受、或者原地不动
规则只有三行,Demo 里就是这么写的:
| 步骤 | 做什么 |
|---|---|
| 提议 | 从当前位置附近随便提一个新位置 |
| 判断 | 算「新位置比当前位置好多少」,比值就是接受概率 |
| 记账 | 接受就挪过去,不接受就原地复制一个样本 |
| 第三行最反直觉:不挪的时候也要记一个样本。因为「留在原地」本身就是这个分布的一部分—— | |
| 密度低的地方,链更容易被拒、待得更久,待的时长自动就变成了概率。漏记这些「没动」的样本, | |
| 等于把分布的低处剪掉,整体形状就歪了。 | |
| Demo 从 Beta(2, 5) 里抽了四万个样本(丢掉前两千步),分成十档和理论值比: | |
| 每一档都落在抽样误差里,样本均值 0.2910,真值 0.2857。 |
| 区间 | 样本占比 | 理论值 |
|---|---|---|
| 0.0 到 0.1 | 0.1137 | 0.1222 |
| 0.2 到 0.3 | 0.2300 | 0.2373 |
| 0.4 到 0.5 | 0.1304 | 0.1235 |
| 0.6 到 0.7 | 0.0319 | 0.0293 |
| 0.9 到 1.0 | 0.0001 | 0.0000 |
| 注意这里从头到尾没算过那个积分。每一次判断只用「两个位置密度的比值」, | ||
| 常数在比值的上下同时出现,直接约掉。这就是 MCMC 能在「密度已知、常数未知」的情况下工作的原因。 |
三、步长:走太小浪费时间,走太大原地打转
唯一要调的旋钮是步长(每次提议挪多远)。扫一遍就看见两个陷阱:
| 步长 | 接受率 | 一步自相关 | 有效样本量 |
|---|---|---|---|
| 0.01 | 97.9% | 0.998 | 39 |
| 0.05 | 90.1% | 0.962 | 726 |
| 0.20 | 64.9% | 0.720 | 6182 |
| 0.50 | 35.6% | 0.687 | 7061 |
| 2.00 | 9.8% | 0.899 | 2017 |
| 步长 0.01 那一行是个陷阱:接受率高达 97.9%,看着很「成功」。可一步自相关 0.998, | |||
| 四万个样本只顶得上 39 个有效的——它在原地小小地抖,等于没动。 | |||
| 步长 2.0 那一行是另一个陷阱:接受率只剩 9.8%。提议大半被拒,链卡在原地的时间太长。 | |||
| 有效样本量 2017,也比 0.5 那一行差。 | |||
| 接受率高不是好事,这件事跟直觉完全相反。真正该看的是「有效样本量」, | |||
| 而它的峰在中间——这里落在步长 0.5、接受率 35.6%。教科书里那句「接受率两成到四成最好」, | |||
| 量出来就是这个位置。 |
四、预热和自相关,两个必须交代的事
预热(burn-in):链的起点是随手挑的。Demo 里故意从 0.99 出发(离主峰很远),看不同段的均值:
| 取哪一段 | 均值 | 真值 |
|---|---|---|
| 第 0 到 50 步 | 0.4906 | 0.2857 |
| 第 0 到 200 步 | 0.3076 | 0.2857 |
| 第 0 到 2000 步 | 0.2710 | 0.2857 |
| 第 2000 到 40000 步 | 0.2872 | 0.2857 |
| 前五十步的均值 0.4906,离真值差了七成。因为链刚从 0.99 那头慢慢趟过来。 | ||
| 这段「还没忘记起点」的样本必须丢掉,否则整条链都被起点污染。 | ||
| 自相关是另一件事:相邻样本不是独立的。所以「四万个样本」不等于「四万个独立信息」, | ||
| 比值就是有效样本量。步长 0.01 那一行自相关 0.998,四万个样本只值 39 个——这就是它的真相。 | ||
| 这两件事加起来说明一句很重要的话:MCMC 的结果不是「一个数」,是一段带诊断的汇报。跑了多久、 | ||
| 丢了多少、几条链、有效样本量多少,少一个都不算交代清楚。 |
五、Gibbs 采样:一次只动一个坐标
Metropolis 有个偷懒的版本叫 Gibbs:一次只更新一个变量,而且直接从「它的条件分布」里抽。 Demo 用一个二维正态(相关系数 0.8)演示。 它的条件分布也是正态:固定 y 时,x 的均值是 0.8y,方差是 1−0.8²。 于是只需要「轮流抽」,连接受率那一步都省了。
| 量 | 采样算出来 | 理论值 |
|---|---|---|
| x 的均值 | 0.0133 | 0.0000 |
| x 的标准差 | 0.9989 | 1.0000 |
| x 与 y 的相关 | 0.7997 | 0.8000 |
| 三个数都对。注意 Gibbs 的提议永远被接受—— | ||
| 因为它抽的就是真正的条件分布,不存在「提得不好」这回事。 | ||
| 代价在两个变量相关很强的时候才显现:一次只动一个坐标,动完 x,y 的最优位置又变了, | ||
| 链只能在一条窄斜带里碎步挪。 | ||
| 相关越强,挪得越慢——这也是为什么高维模型里常常要把变量「解耦」再抽。 |
六、它给你什么,拿不走什么
它给的是样本,不是公式。你得不到「后验是某个分布」这句话,只能得到一堆数, 然后从这堆数里算均值、分位数、区间。这已经够用了——绝大多数决策要的正是这些数。 它拿不走的是「链到底收敛了没有」这个判断。Demo 里每一步都知道真值所以能对答案, 真实问题里没有真值可对。 常规做法是同时跑几条链,看它们是不是走到同一个地方。 一致不代表对,但不一致一定有问题。这是 MCMC 实践里最重要的一条经验, 它比任何收敛指标都可靠,因为指标本身也会骗人。 最后一句话总结这一节:它把「算一个积分」换成了「走一段路」, 代价是要为「走了多久、走够没有」负责。这也是为什么它属于「方法」而不属于「公式」。
七、动手 Demo:算不出后验,就去「抽」它
核心就是 mh 那十几行:提一个位置、按比值决定接不接受、记一个样本。
注意 out[i] = x 在 if 外面——不接受也要记,这一句漏了分布就歪了。
第二段那个分档对比是这一节的「对答案」。因为 Beta(2, 5) 的密度是已知的,所以能拿理论值来验;
真实问题里没有这一步,只能靠多跑几条链互相印证。
def logpdf(x): # Beta(2,5) 的对数密度
if x <= 0.0 or x >= 1.0:
return -np.inf
return (A - 1) * np.log(x) + (B - 1) * np.log(1 - x)
def mh(step, n, seed, x0=0.5):
rg = np.random.default_rng(seed)
x = x0 # 起点随手挑,所以要做预热
out = np.empty(n)
acc = 0
for i in range(n):
y = x + rg.normal(0.0, step) # 1. 在附近提一个新位置
ly, lx = logpdf(y), logpdf(x)
if np.log(rg.random()) < ly - lx: # 2. 按「好多少」决定接不接受
x = y
acc += 1
out[i] = x # 3. 不接受也要记一个样本
return out, acc / float(n)
# 有效样本量:相邻样本不独立,用一步自相关把「折扣」算出来
r = ((s - s.mean())[:-1] * (s - s.mean())[1:]).sum() / ((s - s.mean()) ** 2).sum()
ess = len(s) * (1 - r) / (1 + r)
实跑输出:
--------------------------------------------------------------------
算不出后验,就去「抽」它
--------------------------------------------------------------------
==============================================================
[1] 为什么会走到这一步
上一节能用公式,是因为 Beta 配二项是共轭的——**参数一加就完事。**
换个模型就没这好事了:**后验等于「先验乘似然,再除以一个积分」,**
**而这个积分绝大多数时候没有解析解。**
这时候的办法是:**别求公式,去抽样本。**
只要能从后验里抽出一堆样本,**均值、区间、概率,全都能用样本估出来**——
这比推导一个积分容易得多。
目标就取 Beta(2, 5):它的密度是已知的(真值均值 0.2857),这样才有标准答案对。
**实际用的时候没有标准答案,这也正是「必须同时跑几条链」的原因。**
[2] Metropolis-Hastings:提议、接受、或者原地不动
规则只有三行:
1. 从当前位置附近随便提一个新位置(当前位置 + 一个随机小步)
2. 算「新位置比当前位置好多少」,比值就是这个新位置该被接受的概率
3. 接受就挪过去,不接受就**原地复制一个样本**
**第三步最反直觉:不挪的时候也要记一个样本。**因为「留在原地」本身就是这个分布的一部分——
密度低的地方,链就待得久,**待的时长自动就变成了概率。**
走了 40000 步,接受了 80.6%,丢掉前 2000 步(预热)。
位置区间 样本落进去的比例 理论值
------------------------------------------------------
0.0 到 0.1 0.1137 0.1222
0.1 到 0.2 0.2183 0.2349
0.2 到 0.3 0.2300 0.2373
0.3 到 0.4 0.1903 0.1874
0.4 到 0.5 0.1304 0.1235
0.5 到 0.6 0.0752 0.0677
0.6 到 0.7 0.0319 0.0293
0.7 到 0.8 0.0086 0.0088
0.8 到 0.9 0.0017 0.0013
0.9 到 1.0 0.0001 0.0000
每一档都落在抽样误差里。**样本的均值 0.2910,真值 0.2857。**
**注意:这里从来没算过那个积分。**只是不停地「挪或者不挪」,样本自己就排成了该有的形状。
[3] 步长:走太小浪费时间,走太大原地打转
步长 接受率 样本均值 一步自相关 有效样本量
----------------------------------------------------------------
0.01 97.9% 0.2663 0.998 39
0.05 90.1% 0.2840 0.962 726
0.20 64.9% 0.2861 0.720 6182
0.50 35.6% 0.2852 0.687 7061
2.00 9.8% 0.2877 0.899 2017
**步长 0.01 那一行:接受了九成七,可样本之间几乎一模一样**(自相关 0.998),
四万个样本只顶得上 39 个有效的。**它在原地小小地抖,等于没动。**
步长 2.0 那一行反过来:接受率只剩 9.8%,**链卡在原地不动的时间太长。**
有效样本量最高的是步长 0.5 那一行(7061 个,接受率 35.6%)。
**教科书里那句「接受率两成到四成最好」,量出来就落在这个位置。**
[4] 预热和自相关,是两个必须交代的事
**预热(burn-in)**:链的起点是随手挑的(这里故意从 0.99 出发,离主峰很远),
前若干步还没「忘记起点」,那段样本不能用。看前两百步的均值:
取哪一段 均值 真值
------------------------------------------------
第 0 到 50 步 0.4906 0.2857
第 0 到 200 步 0.3076 0.2857
第 0 到 2000 步 0.2710 0.2857
第 2000 到 40000 步 0.2872 0.2857
**前五十步的均值明显偏高**,因为链刚从 0.99 那头慢慢趟过来。
到两千步就稳了——**这段「还没忘记起点」的样本必须丢掉。**
**自相关**是另一件事:相邻样本不是独立的,所以「四万个样本」
**不等于「四万个独立信息」。**比值就是有效样本量。
上面那张表的最后一列,步长 0.01 时只有 39 个——**这就是它的真相。**
[5] Gibbs 采样:一次只动一个坐标
二维正态,两个变量相关系数 0.8。**Gibbs 的做法是轮流更新:**
固定 y 抽 x,再固定 x 抽 y,来回滚。**关键是要知道「条件分布」长什么样**——
二维正态的条件分布也是正态,均值 0.8 倍对方,方差 1−0.8²。
量 采样算出来 理论值 差
----------------------------------------------------
x 的均值 0.0133 0.0000 +0.0133
x 的标准差 0.9989 1.0000 -0.0011
x 与 y 的相关 0.7997 0.8000 -0.0003
**三个数都对。**注意 Gibbs 一次只动一个坐标,**提议永远被接受**——
因为它是从真正的条件分布里抽的,不需要「接受率」这一步。
代价是:**两个变量相关越强,链走得越慢**(一个坐标动完,另一个又被拉回去一点)。
[6] 它给你的东西和拿不走的东西
**它给的是样本,不是公式。**你得不到「后验是某个分布」这句话,
只能得到一堆数,**然后从这堆数里算均值、分位数、区间。**
这已经够用了——**绝大多数实际决策要的正是这些数,不是公式。**
**它拿不走的是「链到底收敛了没有」这个判断。**
上面每一步我都知道真值,所以能对答案;**真实问题里没有真值可对。**
常规做法是**同时跑几条链,看它们是不是走到同一个地方**——
**一致不代表对,但不一致一定有问题。**
这也是为什么 MCMC 的结果总要连带一串「跑了多久、丢了多少、几条链、有效样本量多少」一起报。
**只报一个均值,等于什么也没说。**
第一段那句话是理解整节的关键:它只用密度的比值,不用密度本身。 所以那个算不出来的归一化常数根本不重要—— 它在分子分母同时出现,直接约掉。这也是为什么 MCMC 能处理「只知道相对概率」的问题。 第三段那张步长表,最该盯着看的是第一行。接受率 97.9%, 看着是「几乎每步都被接受」的好事,实际是灾难——四万个样本只值 39 个。 这个坑很多第一次用 MCMC 的人都会踩,因为「接受率高」听起来太像好事了。 第四段那个 0.4906 值得多看一眼。它是一段很短的链(五十步)的均值, 偏差大到七成。这说明一件事: 预热不是「保险起见丢一点」,而是「必须丢」——起点的影响可以大到让结论完全反过来。 第五段 Gibbs 那三个数都对,但它有个隐藏的代价要记住:相关越强走得越慢。 这个性质在高维里会变成大麻烦:几十个变量两两相关,Gibbs 可能几万步都走不出一个角落, 这时候就得换算法(比如 HMC)。 最后一段那句「一致不代表对,但不一致一定有问题」是全节的落点。多条链走到同一个地方, 只能说明「它们都没走丢」,不能说明「那个地方就是对的」——如果模型本身写错了, 几条链会一起错到同一个地方。这是 MCMC 最容易被忽略的一层风险。
30 秒实验
- 把
mh里的step从 0.1 改成 0.001,跑四万步。 分档表会明显歪——因为链几乎没动,样本挤在起点附近。 这就是「接受率高但没探索」的直观样子。 - 把目标分布从 Beta(2, 5) 换成 Beta(0.5, 0.5)(双峰,两头翘)。 Metropolis 会卡在其中一个峰里出不来,分档表只覆盖一半。 这是 MCMC 在多峰问题上的经典失败,也是「同时跑几条链」的另一个理由。
- 在 Gibbs 那段把相关系数从 0.8 改成 0.99,重跑。 三个数的均值还准,但一步自相关会大幅上升——相关越强,链挪得越慢。这时候要么增加步数, 要么换成联合更新。
常见误区
- 以为 MCMC 会「算出」后验分布。它不给你分布,只给你样本。 「后验是 Beta(9,5)」这句话是共轭给的,不是 MCMC 给的。 MCMC 给的是「一堆落在该去的地方的数」。
- 以为接受率越高越好。Demo 里接受率 97.9% 的那一行是全场最差的一行。 该看的是有效样本量,不是接受率。
- 以为跑得越久越准。跑得久只增加样本,不修复「链没走对地方」这个错误。预热不够、 步长太小、多峰卡住,这些毛病跑十万步也还是那个毛病。
- 以为「几条链结果一致」就证明对了。一致只能说明「都没走丢」, 模型写错的时候几条链会一起错。所以还要做后验预测检验: 拿拟合出来的参数反过来生成数据,看像不像原来那批。
自测三问(点开对答案)
1. 为什么 MCMC 不需要算出那个归一化常数? —— 因为**它每一步只用「两个位置的密度比值」**, 而那个常数在比值里**分子分母同时出现,直接约掉。**所以只要「相对高低」算得出来,链就能走。 **这也是它比「直接积分」强的地方:积分要绝对值,走路只要相对值。** 2. 为什么接受率太高反而是坏事? —— 因为**接受率高说明每次提议都只挪了一小步**(提议太保守,几乎总在概率高的地方打转)。 **样本之间高度相似,自相关接近 1,有效样本量极低。**Demo 里步长 0.01 时接受率 97.9%, 四万个样本只值 39 个——**看着最「成功」的一行,实际最没用。** 3. 预热为什么必须做?丢掉多少合适? —— 因为**链的起点是随手挑的,前若干步还没「忘记起点」。**Demo 里从 0.99 出发, 前五十步的均值 0.4906,离真值 0.2857 差了一大截。 **丢掉多少没有统一答案**,常规做法是把前 10% 到 50% 丢掉, **然后对比丢与不丢的结果**——如果差别很大,说明预热还不够。上一节管「参数不确定」,这一节管「参数算不出来」。 下一节换一种不确定:状态本身就是隐藏的,你只能看见它留下的痕迹。 那就是 HMM 和贝叶斯网络要做的事。
- ☐ (本节完,下一节 6.4 HMM 和贝叶斯网络)