简介:《Graphical Models, Exponential Families, and Variational Inference》是一份面向机器学习、统计学研究者和进阶学习者的经典综述性 PDF 文献,系统梳理概率图模型、指数族与变分推断三者之间的内在联系,并给出似然计算、边际概率与最大后验配置的统一变分视角,适合用于构建理论框架与算法推导背景。资源仅有 1 个 PDF 文件,压缩包约 2.06MB,为 Wainwright 与 Jordan 发表于 Foundations and Trends in Machine Learning 的全文,便于直接阅读、标注与归档。目前已有 235 人浏览学习,适用于对图模型基础、指数族共轭对偶性以及 sum-product、mean field、max-product 等算法统一理解有需求的研究人员。读者可从这份资料中获取完整的数学推导、算法归类与大规模统计模型推断的近似方法概述;除概率图模型与指数族的系统论述外,还覆盖 sum-product、聚类变分法、期望传播、平均场、max-product 与线性规划松弛等算法的统一理解,是一份高引用、高密度的理论参考资料。
1. 一份2008年的综述,如何把图模型、指数族和变分推断捆成同一根绳
这篇2008年的综述把三个看上去各管一摊的概念——图模型、指数族、变分推断——统一进了同一个数学框架。它的核心结论一句话能说清:先利用指数族中配分函数与熵的共轭对偶关系,把图模型上的边际推断、似然计算、众数求解全部改写成优化问题;然后让和积算法、平均场、最大积、线性松弛这些经典方法,都作为同一优化问题的不同近似程度来理解和比较。对我这种做贝叶斯推断应用的人来说,这个视角的实战价值不在于多背几个公式,而在于拿到一个新推断算法时能立刻判断“它到底近似了什么、误差方向有没有保证”。适合对概率图模型有基础,但知识还没串成体系的从业者和研究者。
2. 有向与无向图模型:先看分解方式,再看参数化
2.1 有向图模型:DAG分解与祖先次序
有向图模型用一个有向无环图加上一组局部条件分布定义联合分布:p(x) = ∏ₛ p(xₛ | x_π(s)),其中π(s)是节点s的父节点集合。由于图无环,可以对节点做拓扑排序,从祖先到后代逐层展开分解。链式结构X1 → X2 → X3直接写成p(x1)p(x2|x1)p(x3|x2),朴素贝叶斯则写成p(c)∏p(x_i|c)。
使用要点:图结构直接决定参数个数。节点s的父节点越多,本地条件分布的维度越高;而高维父节点带来的连带问题是,子树之间的条件独立性更复杂,精确推断时联结树的构建也更困难。综述里用祖先关系定义偏序,这不仅是形式上的优雅,也是实现采样、做因子图转换时的操作依据——你在把有向图转成无向图时,必须先把父节点之间补齐边(道德化),再三角化,否则后面没法跑联结树算法。
我自己的经验是:有向模型在建模因果或时序依赖时顺手,但做推断时常常要先对图结构做变换,才能套用通用的精确推断算法。这一步变换的成败,直接影响后边的计算复杂度。如果你是从零开始建模,先问自己一个问题:这个任务的依赖关系是天然有方向的,还是只强调“互相约束”?前者选有向,后者选无向,不要混着用。
2.2 无向图模型:团势能函数与配分函数
无向模型把分布写成 p(x) = (1/Z) ∏_c ψ_c(x_c),每个c是图的一个团(一般取极大团),ψ_c(x_c)是非负的势能函数,Z = Σ_x ∏_c ψ_c(x_c)称为配分函数。势能函数的数值只代表“这个配置有多受偏好”,它本身不是概率,也不要求归一化;真正让乘积落成一个合法分布的,是分母上的Z。
论文在这里给出了一个极其重要的提醒:Z通常很难算。对离散变量,Z是所有可能状态组合的求和,状态数随变量维度指数增长。一个20×20的网格马尔可夫随机场,如果硬算Z,状态数是2的400次方,即使利用图的结构做变量消除,团规模也在百万量级甚至更高,对多维势能存表并不现实。这就是后面所有近似方法的共同出发点:绕开对Z的直接求和。
条件独立性的判据在无向图上比较直观:给定变量集合A之后,B与C独立,等价于所有连接B和C的路径都经过A。这个图论判据让马尔可夫随机场特别适合图像、通信这类有局部交互结构的任务,因为局部势能函数天然对应“相邻像素应该相似”这类约束。反过来,这也意味着你在设计势能函数时,不能随便给任意两个变量加上一条边——每条边都会改变条件独立结构,进而改变整个推断问题的难度。
2.3 指数族参数化:凸性才是真正的宝藏
指数族的一般写法是 p(x; θ) = exp(⟨θ, φ(x)⟩ - A(θ)),其中θ是自然参数,φ(x)是充分统计量,A(θ) = log Σ_x exp(⟨θ, φ(x)⟩) 是 log 配分函数。大量我们熟悉的分布——高斯、多项式、泊松——都是指数族成员,有向模型里的逻辑回归条件分布、无向模型里的势能函数,也都能统一进这个写法。
A(θ)的凸性,是整篇综述真正的支点。凸函数可以做共轭对偶运算,把A(θ)等价地表示成另一个函数在某种意义下的最大值;而均值参数空间上的那个共轭函数A*(μ),恰好与熵相联系。于是计算log Z、算边际、求众数这三个经典难题,全部落入同一个数学结构:一类优化问题的不同侧面。
实用层面,指数族框架也方便统一处理有向与无向模型。有向模型的局部条件分布常常是逻辑回归这类指数族成员;无向模型的势能函数在指数族视角下,不过是对若干“充分统计量的线性组合”做指数运算。中间的差别,用一张表能看得更清楚:
| 维度 | 有向图模型 | 无向图模型 | 指数族视角 |
|---|---|---|---|
| 基本对象 | 条件分布 p(x_s | x_π(s)) | 团势能 ψ_c(x_c) |
| 归一化方式 | 局部自动归一化 | 全局配分函数 Z | 配分函数 A(θ) |
| 条件独立性 | 由 d-分离 判定 | 由图分离判定 | 由统计量的线性结构决定 |
| 精确推断难度 | 取决于树宽 | 取决于树宽 | 与同构图一致 |
| 典型应用 | 因果建模、时序 | 图像、空间统计 | 理论统一与算法推导 |
指数族视角带来的最大改变,是让你不再把“概率计算”和“优化”看成两门课。后面第3章要做的,就是把这句话落到实处。
3. 变分推断的统一视角:推断即优化
3.1 变分问题:log配分函数写成上确界
论文中最核心的一个公式是:log Z(θ) = sup_μ { ⟨θ, μ⟩ - A*(μ) }。这里的μ遍历所有可以由某个分布诱导出的边际向量,A*(μ)是A的共轭对偶,代表着负熵在均值参数空间里的延续。表面看,这只是一个数学恒等式;实际上,它的策略意义是把“求和/积分”问题转成“优化”问题。
求配分函数、算似然、算边际,这三个任务在变分表示下都被统一成“在某个约束集上优化目标函数”。这个变换对图模型特别重要:直接在图上枚举所有状态是阶乘级复杂度,但优化问题可以用迭代算法逼近。一旦写成优化,就可以开始谈松弛——放松约束集合、替换目标函数、或者同时改两者。松弛方式不同,得到的算法族也不一样。
这也解释了为什么同一篇综述里能同时装下和积、平均场、期望传播、最大积、线性规划松弛这么多看起来风格迥异的方法:它们不是各自独立的发明,而是同一个精确变分问题在“近似熵项”和“放松约束集合”两个方向上的不同投影。
3.2 和积算法与Bethe近似:树上的精确,环上的近似
把精确变分表示做第一个粗糙的近似:把自由能中的熵项换成Bethe近似的熵。它假设每个节点单独贡献熵,然后在相邻边上扣除重复计算的熵。在这个近似下,最优性条件正好导出和积算法的消息更新方程。
这里有一个非常容易踩的判定点:在树结构图上,Bethe近似恰好等于真实自由能,因此和积算法是精确算法;在带环图上,Bethe近似不等于真实值,和积就变成了近似算法,而且误差没有统一的先验保证。很多网上代码默认“跑置信传播就是精确推断”,这个默认在树之外不成立。带环图上的和积算法,收敛行为完全是另一回事,我们在第5章会专门展开。
需要另外记住的是,和积算法不只用于算边际。把消息更新里的求和换成取最大值,就得到最大积算法,用来算众数。同一个消息传递框架,稍改一句就换了目标函数,这正是变分统一视角的好处。
3.3 平均场:以独立分布做变分近似
平均场走的是另一条路:把后验分布限制成全因子化的形式 q(x) = ∏ᵢ qᵢ(xᵢ),再用最小KL散度去拟合真实后验。这个约束比较强,近似分布在“不确定性估计”上会系统性偏低——q把所有变量都当成独立,自然丢失了变量间的相关性。
在变分推断的术语里,平均场得到的是证据下界(ELBO),也就是log似然的下界。它给估计值一个“只低不高”的保证,在需要保守估计的应用场景里很关键;而Bethe近似没有这类方向保证。反过来看,平均场因为假设太强,局部极小的问题也很突出,初始化不同,结果差异可能极大。这一点,在后面的避坑章节里会用一条专门记录来说。
3.4 不同松弛方式下生成的算法家族对比
| 算法/家族 | 松弛方式 | 误差方向 | 凸性 | 最常踩的坑 |
|---|---|---|---|---|
| 和积 / Bethe | 对熵项做图分解近似 | 无保证 | 非凸 | 环上不收敛,陷入振荡 |
| 平均场 | 对后验分布做独立因子化 | 恒为下界 | 非凸 | 局部极小、低估不确定性 |
| LP 松弛 | 放松边际约束集合 | 恒为上界 | 凸 | 可能返回不自洽的边际估计 |
| 锥规划 / SDP | 用半定约束放松矩条件 | 上界 | 凸 | 计算开销大,实现复杂 |
这张表是整个第3章的“速查卡”。当你在论文里看到一个新的变分算法,第一步不是看它的更新公式,而是找它对应表格里的哪一行:松弛在哪里、误差方向有没有保证。这个习惯,比记住任何一个具体算法的推导都更值钱。
4. 在模拟项目X上复现三个推断算法:从公式到收敛特征
4.1 模拟项目X的设置:一个8×8网格二值MRF
验证一套理论资源,不能一上来就铺开大数据集,先造一个能快速跑出结论的合成场景:8×8网格,每个节点是二值变量,观测数据由“真实标签加随机翻转”生成。一元势能从观测得出:观测为0时,节点取0的势能更高;成对势能则鼓励相邻节点取值一致。
参数选择上,成对耦合强度J从0.5到2.0之间变化,观测翻转率固定为10%。设计这个参数区间是有意为之:J越大,图上的后验越“双峰化”,三个推断算法的行为差异就越明显。J很小时所有算法都接近真值,看不出区别;J超过某个阈值后,近似算法的性格才会暴露出来。跑实验的人应该先扫一遍J的取值范围,找到真正有诊断价值的区间,再开始正式对比。
4.2 和积算法的log域实现:循环置信传播
下面这段代码不是生产实现,只用来展示消息更新公式的基本形态,也就是带环图上的标准循环置信传播写法:
import numpy as np def sum_product_loop(adj, psi, K, damping=0.5, max_iter=50): # adj: 邻接表, adj[i] 是节点 i 的邻居列表 # psi: 一元势能表, psi[i] 是节点 i 的负log观测能量, shape=(n, 2) # K: 成对势能矩阵, K[a,b] 表示相邻节点取值 (a,b) 的负log n = len(adj) # 构造双向边表 undirected_edges = [(i, j) for i in range(n) for j in adj[i] if i < j] edges = undirected_edges + [(j, i) for (i, j) in undirected_edges] # 每条边两个方向各存一条消息, 初始为全零 log 消息 messages = {e: np.zeros(2) for e in edges} for _ in range(max_iter): new_messages = {} for (i, j) in edges: # 聚合 i 的所有除 j 之外的邻接消息 incoming = np.zeros(2) for k in adj[i]: if k != j: incoming += messages[(k, i)] # 消息更新: m_{i->j}(x_j) = log Σ_{x_i} exp(-E_i(x_i) + 入边消息 + K(x_i, x_j)) tmp = -psi[i] - incoming log_m = np.logaddexp( tmp[0] + K[0, :], # x_i = 0 的贡献 tmp[1] + K[1, :] # x_i = 1 的贡献 ) log_m -= np.max(log_m) # 减最大值, 防止绝对值膨胀 new_messages[(i, j)] = log_m # 阻尼更新, 抑制环上的振荡 for e in edges: messages[e] = damping * new_messages[e] + (1 - damping) * messages[e] return messages逻辑说明:消息更新里的incoming先聚合除目标节点之外的所有邻居消息;np.logaddexp 完成 log 域的累加,避免在极端概率上出现下溢;每次更新后减掉最大值,是 log 域消息传递里最基础的归一化手段。阻尼系数的作用,是把这一轮新消息与上一轮消息做加权平均,不让消息在环上跳得太猛。
参数说明:damping 是阻尼系数,可以试 0.3 到 0.7,遇到振荡就调低;max_iter 是迭代上限。对于带环图,阻尼太小或迭代轮数太大都有可能让消息在接近极值的区间来回震荡,最终得到一个“看起来像固定点、其实没有收敛”的伪稳定状态。判断收敛不能只看最大迭代次数,要画消息变化量的曲线。
4.3 平均场一行更新:实现简单,行为完全不同
平均场的更新要比和积简单很多:分布被强制独立,不需要传消息,每个节点只对自己的局部熵和能量做梯度上升。同样一张网格,平均场的边缘概率会收敛到一个固定点,但固定点往往不是全局最优:
import numpy as np def mean_field(grid_size, data, J_coupling, h_field, max_iter=50): # data: 二值观测, shape=(grid_size, grid_size) # J_coupling: 邻居耦合强度; h_field: 外部场强度 q = np.random.rand(grid_size, grid_size) for _ in range(max_iter): # 四邻居聚合: 上下左右 neighbor_sum = ( np.roll(q, 1, axis=0) + np.roll(q, -1, axis=0) + np.roll(q, 1, axis=1) + np.roll(q, -1, axis=1) ) # 平均场更新: q ∝ sigmoid(2 * (外部场 + 耦合 * 邻居均值)) new_q = 1.0 / (1.0 + np.exp(-2.0 * (h_field + J_coupling * neighbor_sum))) q = new_q return q逻辑说明:np.roll 实现四个方向的邻居聚合;更新公式是无向网格上平均场的标准形式,等价于对每个节点做一次 logistic 回归式的变分参数更新。注意这里是同步更新,所有节点一起动;在耦合较强时同步更新容易振荡,实际中更稳的做法是随机选点异步更新。
参数说明:J_coupling 越大,平均场越容易收敛到“所有节点取一致标签”的解,也就是所谓过度自信;h_field 是外部场,用于把估计值拉向真实观测。跑实验时,J_coupling 从 0.5 扫到 2.0,你会看到边缘概率的方差系统性地低于后面 MCMC 基准给出的方差值。
4.4 对比结果的三条曲线:什么时候变分方法会“骗你”
同一组网格数据,把三个方法的结果画在一张图上,三条曲线:
- J=0.5 时,三者几乎重合,变分近似和采样结果差别很小;
- J=1.0 时,和积可能出现小幅振荡,平均场的边缘方差开始偏低,MCMC 需要采样步数明显变多;
- J=2.0 时,平均场给出接近 0 或 1 的极端边际,和积振荡幅度加大,MCMC 要经过长时间预热才能混合。
我的习惯做法:先在合成数据上用这种双对数曲线检查算法行为,再决定上不上生产环境。这个步骤至少能提前暴露一半的收敛问题。如果你跳过了这一步,直接把变分算法接到真实数据上,遇到结果不合理时,你根本分不清是数据的问题、模型的问题,还是推断算法自身性质带来的偏差。“用合成数据先确认算法行为,再换真实数据”,这是一条成本最低的排查路径。
5. 变分推断的常见问题排查:四个高频踩坑现场
从原理到实现,变分推断类算法最麻烦的地方不在公式,而在“跑出来的结果到底能不能信”。下面四条,基本覆盖了我实际使用这些算法时最常出问题的位置。
5.1 循环置信传播不收敛
现象:同一批数据上跑循环置信传播,每隔几百轮,消息矩阵就在最大值和最小值之间来回横跳,误差曲线不下降;有时看起来收敛了,换个随机种子或者换种初始化,又收敛到另一个完全不同的固定点。
原因:带环图上的 Bethe 自由能是非凸的,消息更新不再具有树结构里的单调压缩性质。初始化和阻尼选择会直接影响收敛点,甚至把迭代推到极限环上。
解决:先加阻尼,从 0.5 开始,往 0.3 方向调;把同步更新改成异步更新,每次只刷新一条边;如果依然不收敛,改用树重加权(TRW)这类凸化变体。如果只是取边缘概率而不追求最优性,阻尼调到 0.5 以下通常能压住振荡。
5.2 平均场低估方差:固定点与过度自信
现象:平均场给出的边缘概率几乎不是 0 就是 1,后验方差明显小于 MCMC 基准值;在真实后验接近 0.5 的位置,平均场也会给出极端值。
原因:独立因子化假设剥离了变量间的相关性,使得近似分布过窄。这是平均场的结构性偏差,不是代码 bug。变量之间相关性越强,这个偏差越明显。
解决:多做几组不同初始化,观察结果稳定性;如果结果对初始化非常敏感,说明非凸性影响很大,考虑换成结构化平均场,把强相关变量保留在同一个团里再因子化。另外,不要只看边缘概率的均值,要同时报告方差,并把方差和采样基准对比。
5.3 log-sum-exp 里出现 NaN
现象:在 log 域写消息传递,跑几轮后消息里突然出现 nan 或 inf,往往集中在高置信观测对应的节点上,而且一旦出现就再也回不去。
原因:在 log 域里,某个局部消息的绝对值不断膨胀,减最大值时两个接近的浮点数相减,直接产生 inf - inf = nan。大多数情况是某一步提前把 log 消息转成了概率(取了 exp),导致极小的概率在下一次 log 时变成 -inf。
解决:所有消息更新必须全程留在 log 域;每个更新后面做一次“减最大值”的归一化;调试阶段在每一轮循环里检查消息是否为有限值,定位到出错的边。切记:不要在消息更新过程中来回切换 log 域与概率域,每切换一次,就等于给数值误差留了一次引爆机会。
5.4 树宽预评估缺失导致精确推断“内存爆炸”
现象:一个看起来不大的图,比如 60 个节点,跑联结树算法 5 分钟还没出结果,内存先溢出了。
原因:没有先估计最小树宽就贸然启用精确推断。图看似只有几十个节点,但某些局部子图的团大小可能接近节点总数,联结树的团规模随树宽指数增长,内存自然扛不住。
解决:先用最小度启发式排序估计树宽上界。经验阈值:当问题规模在几十个节点时,树宽超过 8 到 10,精确推断基本不可行,直接转向变分法或采样法。树宽预估算不了太准,但能帮你避免在最不该试的地方浪费半天算力。
提示:树宽预评估的代码网上很多,关键是选一个支持返回消除顺序的实现;不要只拿一个图数据库试,要对自己真实的图结构做一遍消去顺序模拟。
这些坑记下来,不是为了说明变分方法不行。恰恰相反,正因为这些算法的固定点行为复杂、数值实现容易触发边界问题,才更需要一套检查流程去确保结果可信。把“结果验证”和数据本身分开对待,是排查变分推断问题的第一原则。
6. 一条可复现的研读路线:把综述变成算法字典
前面五章把理论、实现和常见坑都过了一遍,剩下的事情是如何把这份 305 页的综述真正消化成自己的知识。我的做法是:分两天读,而不是拖两周。
第一天只读第 1 到第 3 节,目标是把“变分问题 = 对 log 配分函数做优化”这个等式写在自己的笔记本上,顺手用一个 8×8 网格 MRF 手动展开一遍 Bethe 自由能的形式。第二天跳读第 4 到第 9 节,对每个算法各写三行笔记:它近似了什么函数、它在什么图上精确、它的误差方向有没有保证。这个“三行笔记”模式,后来成了我面对任何新推断算法时的固定动作。
实际练习方面,可以给自己布置三个作业:
- 用 10 行代码跑通网格 MRF 上的一次循环置信传播,改变阻尼系数,画出消息变化量随迭代次数的曲线;
- 对比同一张网格上平均场和 MCMC 给出的边缘方差,画出标准差随耦合强度的变化;
- 把自己用过的一个深度生成模型的 ELBO 重写成“指数族配分函数减共轭对偶”的形式,在纸上算一遍,不回翻任何源码。
这三个作业里,第 3 个如果能在不打开 IDE 的情况下完成,基本可以确认你真正吸收了综述的核心视角。判断标准不是推导多完整,而是你能不能脱离源代码,独立地写出“这个目标函数的松弛点在哪里”。
如果只给一条建议,我会说:一看到新论文里出现 variational、mean-field、relaxation 这些词,先问三个问题——它的目标函数是什么,在哪个集合上做松弛,近似方向是上界还是下界。三个问题答完,这篇新论文在你的知识地图里就不再是一个陌生黑匣子。
说实话,这份综述我一开始也是跳着读的,结果就是对着“消息传递也叫和积算法”这类表面概念感动,完全错过了它真正值钱的内核。后来遇到一个新的结构化平均场算法,翻开源码才发现“把这个近似分布放回变分目标后,代价函数根本不是作者声称的那一个”,白白浪费了三天时间。从那以后,我每次接触新的推断算法,都强制自己按上面三行笔记的模式先写一遍底层变分目标,再去看代码。这个习惯帮我避开不少“看起来对、跑起来错”的尴尬。希望帮到你。
本文还有配套的精品资源,点击获取