📘 第6章 统计机器学习(概率分布和建模)
📄 一句话概括

上一节能用公式,是因为先验和似然是「一对」。换个模型就没这好事:后验等于「先验乘似然, 再除以一个积分」,而这个积分绝大多数时候算不出来。 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 秒实验

  1. 把 mh 里的 step 从 0.1 改成 0.001,跑四万步。 分档表会明显歪——因为链几乎没动,样本挤在起点附近。 这就是「接受率高但没探索」的直观样子。
  2. 把目标分布从 Beta(2, 5) 换成 Beta(0.5, 0.5)(双峰,两头翘)。 Metropolis 会卡在其中一个峰里出不来,分档表只覆盖一半。 这是 MCMC 在多峰问题上的经典失败,也是「同时跑几条链」的另一个理由。
  3. 在 Gibbs 那段把相关系数从 0.8 改成 0.99,重跑。 三个数的均值还准,但一步自相关会大幅上升——相关越强,链挪得越慢。这时候要么增加步数, 要么换成联合更新。

常见误区

  1. 以为 MCMC 会「算出」后验分布。它不给你分布,只给你样本。 「后验是 Beta(9,5)」这句话是共轭给的,不是 MCMC 给的。 MCMC 给的是「一堆落在该去的地方的数」。
  2. 以为接受率越高越好。Demo 里接受率 97.9% 的那一行是全场最差的一行。 该看的是有效样本量,不是接受率。
  3. 以为跑得越久越准。跑得久只增加样本,不修复「链没走对地方」这个错误。预热不够、 步长太小、多峰卡住,这些毛病跑十万步也还是那个毛病。
  4. 以为「几条链结果一致」就证明对了。一致只能说明「都没走丢」, 模型写错的时候几条链会一起错。所以还要做后验预测检验: 拿拟合出来的参数反过来生成数据,看像不像原来那批。
自测三问(点开对答案) 1. 为什么 MCMC 不需要算出那个归一化常数? —— 因为**它每一步只用「两个位置的密度比值」**, 而那个常数在比值里**分子分母同时出现,直接约掉。**所以只要「相对高低」算得出来,链就能走。 **这也是它比「直接积分」强的地方:积分要绝对值,走路只要相对值。** 2. 为什么接受率太高反而是坏事? —— 因为**接受率高说明每次提议都只挪了一小步**(提议太保守,几乎总在概率高的地方打转)。 **样本之间高度相似,自相关接近 1,有效样本量极低。**Demo 里步长 0.01 时接受率 97.9%, 四万个样本只值 39 个——**看着最「成功」的一行,实际最没用。** 3. 预热为什么必须做?丢掉多少合适? —— 因为**链的起点是随手挑的,前若干步还没「忘记起点」。**Demo 里从 0.99 出发, 前五十步的均值 0.4906,离真值 0.2857 差了一大截。 **丢掉多少没有统一答案**,常规做法是把前 10% 到 50% 丢掉, **然后对比丢与不丢的结果**——如果差别很大,说明预热还不够。

上一节管「参数不确定」,这一节管「参数算不出来」。 下一节换一种不确定:状态本身就是隐藏的,你只能看见它留下的痕迹。 那就是 HMM 和贝叶斯网络要做的事。

相关笔记