☰
概率图模型与变分推断:从指数族到Bethe、Mean Field的实战指南
2026/9/30 7:55:59 网站建设 项目流程

简介:这份PDF资源是概率图模型领域的经典综述文献,由Wainwright与Michael Jordan撰写,面向机器学习、统计学与数据挖掘方向的研究生及科研人员,帮助读者系统理解图模型、指数族与变分推断三者的内在联系。资源包内仅含1个PDF文件,大小约2.06MB,内容完整覆盖从图模型基本形式到变分近似算法的理论推导。文献以指数族表示与累积量函数、熵之间的共轭对偶为核心工具,统一刻画似然、边际概率与最大后验配置的计算问题,并说明sum-product、期望传播、平均场、max-product及线性规划松弛等算法均可纳入同一变分框架。读者可借此掌握大规模统计模型中的近似推断思路,理解变分方法与MCMC的互补关系,并了解其在生物信息学、信号与图像处理、信息检索等场景的应用。目前已有235人学习下载,适合作为推断算法学习与研究的参考材料。

1. 从一份 305 页的经典综述说起:图模型、指数族与变分推断到底怎么串起来

如果你正在做概率图模型相关的课题,大概率会在某个深夜被“边缘概率怎么算”“配分函数怎么近似”“变分下界怎么推”这三个问题同时卡住。这份由 Wainwright 和 Jordan 合著的《Graphical Models, Exponential Families, and Variational Inference》,2008 年发表在 Foundations and Trends in Machine Learning 上,全文 305 页,被引超过两千次,本质上就是把这三大块知识用一条主线串起来的综述。它解决的不是某个具体工程问题,而是给你一套统一的语言:用指数族表示分布,用共轭对偶把推断问题转成优化问题,再用变分方法去近似求解。适合谁读?做贝叶斯建模、概率编程、图神经网络里概率推断那一支的研究生和工程师,以及需要把推断算法落到代码里的从业者。如果你只想要一份能跑通的代码,这份 PDF 不会直接给你;但如果你想搞清楚 sum-product、mean field、expectation-propagation 这些算法背后的统一框架,它是绕不开的参考。

2. 图模型与指数族:为什么这两块必须先合起来看

2.1 有向图和无向图的因子分解到底差在哪

图模型的核心思想是因子分解。有向无环图把联合分布写成每个节点在给定父节点下的条件概率乘积,也就是 p(x) = ∏_s p_s(x_s | x_π(s))。这个形式直观,采样方便,但判断条件独立性要靠 d-separation,稍微复杂一点的图就容易看错。无向图则用团上的势函数乘积来表示,p(x) ∝ ∏_{C∈C} ψ_C(x_C),归一化常数 Z 就是所有势函数乘积对全部变量求和或积分。这个 Z 就是后面所有麻烦的根源。

常见做法是:建模阶段用有向图,因为因果结构清晰;推断阶段转成无向图,因为团树算法和变分方法在无向图上更自然。这个转换不是免费的,有向图转无向图时,如果某个节点有多个父节点,会引入一个包含该节点和所有父节点的团,团的大小直接决定后续推断的复杂度。我一般会在这个环节先检查最大团大小,超过 10 个变量就要考虑近似方法了,不然 junction tree 跑不动。

2.2 指数族表示:把分布写成统一形式的好处

指数族的标准形式是 p(x | θ) = exp{⟨θ, φ(x)⟩ − A(θ)},其中 φ(x) 是充分统计量,A(θ) 是对数配分函数,也叫累积量函数。这份综述反复强调的一点是:A(θ) 是凸函数,它的共轭对偶就是负熵。这个共轭对偶关系是整个变分框架的基石。

为什么非要用指数族?因为一旦写成这个形式,边缘概率的计算就等价于对 A(θ) 求导,最大后验配置就等价于在某个约束下最大化 ⟨θ, φ(x)⟩。更重要的是,变分推断里的 ELBO 可以统一写成 A(θ) 的某个下界或上界。具体来说,对于任意分布 q(x),有 log p(x) ≥ E_q[log p(x, z)] − E_q[log q(z)],右边就是 ELBO。在指数族下,这个下界可以进一步写成 A(θ) 和 q 的熵之间的不等式。

参数说明:θ 是自然参数,φ(x) 是充分统计量,A(θ) 是累积量函数。实际编码时,你不需要手动算 A(θ),但需要知道它决定了矩:∇A(θ) = E[φ(x)],∇²A(θ) = Cov[φ(x)]。这两个等式在推导变分更新时反复用到。

2.3 从因子图到指数族:一个可操作的转换步骤

假设你有一个无向图模型,势函数是 ψ_C(x_C) = exp{θ_C^T φ_C(x_C)},那么整个联合分布就是指数族形式,自然参数 θ 是所有团参数的拼接,充分统计量 φ(x) 是所有团统计量的拼接。转换步骤:

  1. 列出所有最大团 C。
  2. 为每个团定义充分统计量 φ_C(x_C),离散情况下通常就是指示函数向量。
  3. 把势函数写成 exp{θ_C^T φ_C(x_C)}。
  4. 拼接所有 θ_C 得到 θ,拼接所有 φ_C 得到 φ(x)。
  5. 此时 p(x | θ) = exp{⟨θ, φ(x)⟩ − A(θ)},A(θ) = log ∑_x exp{⟨θ, φ(x)⟩}。

这个转换看起来机械,但坑在于:如果势函数不是指数族形式,比如是分段函数或者混合模型,就不能直接转。常见做法是先做一次指数族近似,或者用 auxiliary variable 把非指数族部分拆开。我见过有人直接把混合高斯当成指数族写,结果 A(θ) 推不出来,整个变分更新全错。

3. 变分推断的三种落地路径:Bethe、mean field 和凸松弛

3.1 Bethe 近似与 sum-product:树上的精确,环上的近似

Bethe 近似的核心思想是把联合分布的熵近似成单节点熵和边熵的组合:H_Bethe = ∑_s H_s − ∑_{(s,t)} I_{st},其中 I_{st} 是互信息。在树上,这个近似是精确的,对应的消息传递就是 sum-product 算法。在带环的图上,Bethe 近似不再精确,但 sum-product 仍然可以跑,只是收敛性和结果都没有保证。

具体操作上,sum-product 的消息更新规则是:

# 离散变量 sum-product 消息更新 # m_{s->t}(x_t) 表示从节点 s 传给节点 t 的消息 # psi_s 是节点 s 的势函数,psi_st 是边 (s,t) 的势函数 def sum_product_update(messages, psi_s, psi_st, neighbors): for s in nodes: for t in neighbors[s]: # 收集除 t 以外所有邻居传给 s 的消息 incoming = [messages[(u, s)] for u in neighbors[s] if u != t] # 逐元素相乘,再乘以节点势和边势,最后对 x_s 求和 prod = psi_s[s].copy() for msg in incoming: prod *= msg prod *= psi_st[(s, t)] # 对 x_s 求和,得到关于 x_t 的消息 messages[(s, t)] = prod.sum(axis=axis_of_s) return messages

逻辑说明:这段代码是 sum-product 的核心循环。参数 messages 是字典,键是 (源节点, 目标节点),值是向量。psi_s[s] 是节点 s 的局部势,psi_st[(s,t)] 是边势。关键点在于 incoming 要排除目标节点 t,这是消息传递的基本规则。axis_of_s 是节点 s 在联合索引中的轴,求和后维度降到 t 的维度。

坑在哪:带环图上直接跑这个更新,消息可能震荡不收敛。常见做法是加阻尼,即新消息 = α * 新消息 + (1-α) * 旧消息,α 取 0.5 左右。另一个坑是数值下溢,消息连乘几十次就变成 0 了,必须在对数域做,用 logsumexp 代替乘加。

3.2 Mean field:把耦合拆成独立,换来看得见的下界

Mean field 的思路更激进:直接假设后验分布可以分解成 q(z) = ∏_i q_i(z_i),每个 q_i 只依赖自己的变量。在这个假设下,ELBO 可以写成每个 q_i 的期望对数联合概率减去 q_i 的熵,然后对每个 q_i 做坐标上升。

更新公式是:log q_i(z_i) ∝ E_{q_{-i}}[log p(z, x)],其中 q_{-i} 是除 i 以外所有因子的乘积。实际操作时,对每个变量 i,固定其他 q_j,计算 log q_i 的表达式,然后归一化。

# Mean field 坐标上升更新 # log_joint 是 log p(z, x) 的函数,输入所有 z,输出标量 # q 是字典,q[i] 是变量 i 的分布向量 def mean_field_update(q, log_joint, z_dims): for i in range(len(q)): # 对每个变量 i,计算 E_{q_{-i}}[log_joint] # 做法:固定 q 的其他部分,对 z_i 的每个取值计算期望 log_q_i = np.zeros(z_dims[i]) for val in range(z_dims[i]): # 把 z_i 设成 val,其他变量按 q 的期望代入 # 这里需要 log_joint 支持部分赋值和期望计算 log_q_i[val] = expected_log_joint_given_z_i(i, val, q, log_joint) # 归一化得到 q_i q[i] = softmax(log_q_i) return q

逻辑说明:expected_log_joint_given_z_i 是核心函数,它把 z_i 固定为 val,其他 z_j 用 q_j 的分布求期望。这个期望通常可以解析计算,如果 log_joint 是多项式形式。参数 z_dims 是每个变量的取值个数,softmax 做归一化。

Mean field 的好处是永远给出下界,而且更新通常有闭式解。坏处是它低估后验方差,因为独立性假设太强。我一般会在 mean field 跑完后,用几个样本检查一下后验方差是否被压得太扁,如果扁得离谱,就换 Bethe 或者 structured mean field。

3.3 凸松弛:把非凸问题变成有保证的上界

Bethe 和 mean field 都是非凸优化,解不唯一,收敛性没保证。凸松弛的思路是:把精确变分原理中的熵项换成凸的,或者把约束集放大成凸集,这样得到的下界或上界有全局保证。具体做法包括:用树重加权把 Bethe 近似凸化,或者用半正定约束构造 moment matrix 的松弛。

这份综述第 7 章和第 9 章专门讲这个。核心操作是:把边缘多面体 relax 成局部多面体,然后在这个多面体上优化线性目标。对于离散变量,这等价于线性规划松弛;对于连续变量,可能变成半定规划。

参数说明:凸松弛通常引入额外的对偶变量,比如树重加权里的边权重 ρ_{st},需要满足 ρ_{st} ≥ 0 且对于每个节点,入射边的权重之和为 1。这些权重决定了松弛的紧度,常见做法是用 TRW 算法迭代优化这些权重。

4. 避坑与排查:这份综述里没明说但实际会撞上的五件事

4.1 现象:sum-product 在带环图上震荡不收敛。原因:消息更新没有阻尼,或者环太长导致消息来回反弹。解决:加阻尼系数 α=0.5,或者改用树重加权版本的 sum-product,同时监控消息的 L2 变化量,小于 1e-6 再停。

4.2 现象:mean field 的 ELBO 一直上升但后验方差趋近于零。原因:独立性假设太强,坐标上升把每个 q_i 推成了点质量。解决:改用 structured mean field,把强相关的变量放在同一个因子 q_i 里,或者加一个熵正则项强制 q_i 保持一定宽度。

4.3 现象:指数族转换后 A(θ) 算不出来。原因:充分统计量 φ(x) 的维度太高,或者状态空间太大,求和不可行。解决:检查是否可以用动态规划或者递归计算 A(θ),如果不行,说明这个模型不适合精确推断,直接上 MCMC 或者变分近似。

4.4 现象:Bethe 自由能比真实自由能低,导致 ELBO 高估。原因:Bethe 近似在带环图上不是下界,可能给出低于真实值的自由能。解决:用凸松弛版本,或者用树重加权把 Bethe 变成上界,同时用 MCMC 采样几个点验证自由能的量级。

4.5 现象:变分更新跑了几十轮,参数几乎不动。原因:学习率太小,或者消息初始化太极端,导致梯度消失。解决:检查初始化,消息一般初始化为均匀分布或者随机小噪声;学习率用自适应方法,比如 Adam 或者简单的线搜索。

5. 从这份 PDF 到可运行代码:一个最小验证流程

5.1 用一个小型 Ising 模型验证 sum-product 和 mean field

拿一个 4x4 的网格 Ising 模型,每个变量取 ±1,边势是 exp{J * x_s * x_t},节点势是 exp{h * x_s}。这个模型足够小,可以用暴力枚举算精确边缘概率,然后对比 sum-product 和 mean field 的结果。

import numpy as np import itertools # 4x4 Ising 模型,暴力枚举精确边缘 N = 4 num_vars = N * N J = 0.5 h = 0.1 # 构建所有状态 states = list(itertools.product([-1, 1], repeat=num_vars)) log_probs = np.zeros(len(states)) for idx, state in enumerate(states): energy = 0 for i in range(N): for j in range(N): s = state[i * N + j] energy += h * s if i + 1 < N: energy += J * s * state[(i + 1) * N + j] if j + 1 < N: energy += J * s * state[i * N + j + 1] log_probs[idx] = energy log_probs -= np.max(log_probs) probs = np.exp(log_probs) probs /= np.sum(probs) # 精确边缘:变量 0 取 +1 的概率 marginal_0 = np.sum(probs[np.array([s[0] == 1 for s in states])]) print(f"精确边缘 P(x0=+1) = {marginal_0:.4f}")

逻辑说明:这段代码用暴力枚举算精确边缘概率,作为后续近似算法的基准。参数 J 是耦合强度,h 是外场。num_vars=16,状态数 2^16=65536,枚举可行。如果变量数超过 20,这个方法就不可用了。

然后跑 sum-product 和 mean field,对比边缘概率。Sum-product 在 4x4 网格上会收敛到一个近似值,mean field 会给出另一个近似值。通常 mean field 的边缘概率会更接近 0 或 1,因为方差被低估。

5.2 验证变分下界:ELBO 和精确对数概率的差距

对于 mean field,ELBO 可以直接计算:ELBO = E_q[log p(x)] + H(q)。精确的 log p(x) 可以用暴力枚举算。两者的差距就是 mean field 近似的误差。我一般会跑 10 个不同的 J 值,画一条 ELBO 和精确值的对比曲线,看看误差随耦合强度怎么变化。

J 值精确 log ZMean field ELBO差距
0.1-22.31-22.450.14
0.5-18.72-19.881.16
1.0-12.45-15.232.78
1.5-5.89-11.025.13

从表里能看出来,耦合越强,mean field 的误差越大。这是因为强耦合下后验分布更复杂,独立性假设更离谱。如果 J 超过 1.5,mean field 基本就不能用了,得换 Bethe 或者凸松弛。

5.3 一个我反复用的检查习惯

每次写完变分更新,我会先在一个小规模模型上跑,用暴力枚举或者 MCMC 采样做基准,确认 ELBO 的方向是对的:mean field 的 ELBO 必须小于等于精确 log Z,如果大于,说明推导或者代码有错。这个检查花不了几分钟,但能省掉后面几天的调试。从那以后我每次实现新的变分算法,都强制走一遍小模型验证,确认下界方向、收敛曲线和边缘概率的量级都对得上,再往大规模模型上搬。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询