
简介这份PDF是机器学习与统计学领域的经典综述《图模型、指数族与变分推断》由Martin Wainwright与Michael Jordan两位学者合著面向对概率模型与近似推断有基础的研究生、工程师和科研人员。资源共1个文件为PDF格式文档压缩包大小约2.06MB内容包含论文全文适合作为系统学习图模型理论的原版教材。目前已有235人浏览学习。文中从概率图模型的基本形式出发系统梳理了它与指数族分布之间的共轭对偶关系并将边际概率计算、最大后验推断等问题统一到变分表示的框架下详细介绍了和积算法、聚类变分法、期望传播、平均场方法、最大积算法以及线性规划松弛等众多算法的内在联系。阅读这份材料读者可以深入理解大规模统计模型中的近似推断原理掌握从变分角度推导和比较不同算法的能力是概率图模型方向值得精读的参考资料。1. 变分推断落地为什么总在“玄学”和“可复现”之间摇摆做隐变量模型的人大概率都经历过这种时刻模型结构画得明明白白EM 公式推得头头是道一上真实数据,ELBO 要么发散要么收敛到明显不对的局部解。查了半天最后发现是对“指数族”这个前提理解不到位。这份标题为 Graphical_Models_Exponential_Families_and_Variatio.pdf 的讲义恰好把三件事焊在了一条线上概率图模型定义了“依赖关系长什么样”指数族给出了“参数化用什么语言”变分推断回答了“近似后验怎么优化”。它不是教你调某个库而是让你在调库之前先看清黑匣子内部的齿轮。适合两类人一类是把变分推断当黑盒、想搞懂收敛性和参数含义的工程开发者另一类是已经跑通简单模型、想在主题模型或时序模型里自己写更新公式的研究型从业者。读完你会得到一个关键判断力什么时候闭式更新成立什么时候必须上梯度估计。2. 图模型与指数族为什么这两件事必须放在一起读2.1 从因子分解到条件独立先把图的“骨架”拆出来概率图模型的核心不是画图而是用图结构表达联合分布的因子分解。一个有向无环图对应的联合分布可以写成所有节点的条件概率乘积p(x₁, x₂, …, x_N) ∏ p(x_i | x_{pa(i)})这里的 pa(i) 是节点 i 的父节点集合。图结构一旦定下来因子分解就定下来了因子分解一旦定下来条件独立关系也就定下来了。比如某个模拟项目 X 里我做一个简单的隐马尔可夫模型观测 x_t 只依赖于当前状态 z_t状态 z_t 只依赖于 z_{t-1}。写成因子就是 p(z₁)∏p(z_t|z_{t-1})∏p(x_t|z_t)这比直接写一个高维联合分布要清晰得多。这个“骨架”直接决定了后续变分推断的计算图。因为后面做平均场近似时每个变分因子的更新公式里会出现“该节点在给定父节点和子节点条件下的对数条件概率”的期望而这个期望的依赖范围由图的局部结构决定。换句话说图结构越稀疏坐标上升每一步需要计算的量就越小。这也是为什么图模型不是“画着好看”而是直接关系到大模型能不能在有限算力下跑起来。2.2 指数族的标准形自然参数、充分统计量与对数配分函数指数族分布是图模型里最常见的节点分布类型。它的标准形可以写成p(x | θ) h(x) · exp(θᵀ t(x) − A(θ))其中 θ 是自然参数t(x) 是充分统计量A(θ) 是对数配分函数h(x) 是基础测度项。大多数常见分布都能写成这种形式只是 θ 和 t(x) 的取值不同。分布自然参数 θ充分统计量 t(x)对数配分函数 A(θ)伯努利log(π/(1−π))xlog(1 exp(θ))正态方差已知μ/σ²xθ²σ²/2多项分布概率比值向量独热向量log(Σ exp(θ))伽马形状与速率比值(log x, x)log Γ(θ₁) − θ₁ log θ₂把分布改写成标准形有两个意义。第一充分统计量的期望 E[t(x)] 是后面变分更新里要反复匹配的量第二同一个指数族内先验与似然如果共轭后验就在同一个族里变分更新可以直接写成自然参数相加的形式。这两个性质是整篇讲义里变分推断能“闭式更新”的地基。2.3 当图节点取指数族分布时联合分布发生了什么顺着因子分解继续往前走如果每个条件概率 p(x_i | x_{pa(i)}) 都是指数族成员把因子乘起来再取对数会得到一堆自然参数的线性项与 A(θ) 的累加。理想情况下整个联合分布也属于指数族。但这里有一个关键边界必须说清楚只有当图为弦图且因子分解中的每个团都是完整的时联合分布才严格保持指数族形式。树结构是最典型的例子更一般的有向图局部因子乘积之后归一化常数未必还有闭式形式。这个边界直接决定了变分推断的必要性。如果联合分布严格属于指数族后验本身就有闭式解不需要近似正因为一般图上联合分布超出了指数族范围我们才需要引入变分分布 q 去近似后验。讲义里的处理方式是不要求整体可解而是假设每个局部变分因子都属于指数族然后把整体推断问题转化为“找到一组自然参数使 q 尽可能接近真实后验”。这个假设在实际工程里非常常见比如主题模型里每个局部因子取多项分布或狄利克雷分布整体后验虽然不可解但局部更新依然有闭式形式。3. 变分推断的目标函数与坐标上升更新3.1 ELBO 拆解把对数边际似然切成“证据”与“误差”变分推断的目标是找 q(z) 去近似真实后验 p(z|x)。直接最小化 KL(q ∥ p(z|x)) 的问题是KL 中包含 log p(z|x)它的归一化常数 log p(x) 正是我们算不出来的量。所以常规做法是最大化另一个等价目标——ELBOELBO(q) E_q[log p(x, z)] − E_q[log q(z)]对 ELBO 做简单的代数变形log p(x) ELBO(q) KL(q ∥ p(z|x))因为 KL 非负所以 ELBO 是对数边际似然的下界。最大化 ELBO 等价于同时做到两件事让 q 尽量解释数据第一项变大同时让 q 不要偏离先验太远第二项防止熵过小。这个分解特别重要因为很多工程上的“过拟合”信号其实可以从 ELBO 的两项变化里看出来如果 E_q[log p(x,z)] 一直涨但第二项也涨得很凶说明 q 正在变得过度自信。3.2 平均场假设下最优变分因子长什么样平均场近似假设变分分布完全因子分解q(z) ∏ q_i(z_i)。这个假设看起来很强但它把“求一个高维分布”变成“交替求若干个一维分布”。坐标上升的核心结论是固定其他因子最优的 q_j(z_j) 满足log q_j(z_j) E_{q_{-j}}[log p(z_j | z_{-j}, x)] const注意右边是“在 q 对其他变量的边际分布下对 log 条件后验取期望”而不是直接把真实后验拿来用。这意味着每一步更新只需要计算目标节点在马尔可夫毯内的期望量图模型的局部结构在这里第二次发挥作用。这个公式也是后面写代码的骨架先算每个变量在 q 下的期望充分统计量再把这些期望代入条件后验的对数形式得到新的变分参数。3.3 一个能跑的最小实现高斯混合模型的变分坐标上升下面给一个最小但完整的例子两组件高斯混合方差已知为 1均值 μ₀ 和 μ₁ 的先验为 N(0, prior_var)。隐变量 z 指示样本来自哪个组件。我们用平均场变分推断同时近似 q(z) 和 q(μ₀), q(μ₁)。import numpy as np rng np.random.default_rng(42) N 400 true_mu np.array([-2.0, 2.0]) true_pi 0.5 z_true rng.binomial(1, true_pi, sizeN) x rng.normal(true_mu[z_true], 1.0) # 变分参数初始化 logit_phi np.zeros(N) # q(z_i1) 的 logit m np.array([0.0, 0.0]) # q(mu_k) 的均值 s2 np.ones(2) # q(mu_k) 的方差 prior_var 1.0 # 先验 N(0, prior_var) max_iter 200 tol 1e-4 def q_mu_logpdf(x_val, m_val, s2_val): E_{q(mu)}[log N(x | mu, 1)] -0.5*log(2pi) - 0.5*((x-m)^2 s2) return -0.5 * np.log(2 * np.pi) - 0.5 * ((x_val - m_val) ** 2 s2_val) def compute_elbo(x_val, logit_phi, m_val, s2_val): phi 1.0 / (1.0 np.exp(-logit_phi)) # E_q[log p(x | z, mu)] e_log_lik ( phi * q_mu_logpdf(x_val, m_val[1], s2_val[1]) (1 - phi) * q_mu_logpdf(x_val, m_val[0], s2_val[0]) ).sum() # E_q[log p(z)]先验为 Bernoulli(0.5) e_log_pz np.log(0.5) * N # E_q[log p(mu)]先验 N(0, prior_var) e_log_pmu ( -0.5 * np.log(2 * np.pi * prior_var) - (m_val ** 2 s2_val) / (2 * prior_var) ).sum() # E_q[log q(z)]用 logaddexp 保持数值稳定 lq1 -np.logaddexp(0.0, -logit_phi) # log sigma(logit) lq0 -np.logaddexp(0.0, logit_phi) # log (1 - sigma(logit)) e_log_qz (phi * lq1 (1 - phi) * lq0).sum() # E_q[log q(mu)] e_log_qmu (-0.5 * np.log(2 * np.pi * s2_val) - 0.5).sum() return e_log_lik e_log_pz e_log_pmu - e_log_qz - e_log_qmu prev_elbo compute_elbo(x, logit_phi, m, s2) for it in range(max_iter): # E 步更新 q(z)利用 logit_phi E_q[log p(x|z1,...)] - E_q[log p(x|z0,...)] logit_phi q_mu_logpdf(x, m[1], s2[1]) - q_mu_logpdf(x, m[0], s2[0]) phi 1.0 / (1.0 np.exp(-logit_phi)) # M 步更新 q(mu_k)后验精度 先验精度 数据精度 * 责任度之和 prec 1.0 / prior_var np.array([(1 - phi).sum(), phi.sum()]) m np.array([ ((1 - phi) * x).sum() / prec[0], (phi * x).sum() / prec[1] ]) s2 1.0 / prec cur_elbo compute_elbo(x, logit_phi, m, s2) if abs(cur_elbo - prev_elbo) tol: print(fiter {it}: ELBO converged at {cur_elbo:.4f}) break prev_elbo cur_elbo print(近似后验均值:, m) print(近似后验方差:, s2)这段代码里有几个关键点要说明。E 步的 logit_phi 更新本质是在做“两个组件对当前样本的期望对数似然之差”。q_mu_logpdf 函数里把方差 s2 也加进去了这是因为 E[μ²] m² s2如果忽略这一项等于默认 q(μ) 的方差为零会得到过度自信的 false 责任度分配。M 步的精度更新是“先验精度 数据精度 × 责任度之和”这对应指数族的自然参数相加先验的自然参数精度加上数据贡献的自然参数结果就是后验变分参数。收敛判断用的是 ELBO 绝对变化量小于 1e-4比看参数变化更可靠。4. 指数族视角下的通用更新模式充分统计量匹配4.1 积函数变加法自然参数为什么能直接相加回到指数族标准形如果一个因子是 exp(θ₁ᵀ t(x) − A(θ₁))另一个因子是 exp(θ₂ᵀ t(x) − A(θ₂))两者相乘的结果是 exp((θ₁θ₂)ᵀ t(x) − A(θ₁) − A(θ₂))。虽然联合分布的归一化常数不再是单个 A(θ)但自然参数可以直接相加这一点是变分更新能写成“累加充分统计量”的根本原因。实际实现时这个性质体现在两个层面。先验与似然共轭时变分后验的自然参数等于先验自然参数 数据期望充分统计量多个节点相互依赖时某个变分因子收到的“消息”是邻居节点传入的自然参数之和。我们可以把整个坐标上升过程看成每个节点从邻居收集期望充分统计量加上自己的先验得到新的自然参数再重新广播出去。这比逐项推导每个模型的具体更新公式要通用得多。4.2 从变分参数到自然参数一张参数对应表把上一节的代码映射成自然参数语言会看得更清楚。这里的变分参数m, s2, φ本质上都是自然参数的某种函数而更新规则全部是自然参数的相加。变量变分分布变分参数自然参数形式更新规则z_iBernoulliφ_ilogit(φ_i)两个组件的期望对数似然之差μ_kNormalm_k, s2_k精度 λ_k s2_k⁻¹λ_k·m_k先验自然参数 责任度加权数据之和观测 x_iNormal(μ, 1)—精度 1均值 x_i作为固定输入这张表的工程含义是你不需要为每个新模型从头推导坐标上升公式只需要确定三件事——每个节点的变分分布属于哪个指数族、它的自然参数对应什么、邻居节点传入的期望充分统计量是什么。剩下的就是“加”和“广播”两件事。4.3 扩展到文档-主题模型局部参数与全局参数的更新骨架把第 3 章的思路推广到文档-主题模型常见缩写即 LDA更新骨架会变成两层循环。局部参数是每个文档 d 中第 n 个词属于主题 k 的责任度 φ[d][n][k]全局参数是主题 k 在词表上的分布 β[k] 和文档-主题分布 θ[d]。# 局部更新文档 d 内词 n 的主题责任度 # log phi[d][n][k] ∝ digamma(gamma[d][k]) - digamma(sum(gamma[d])) # E_q[log beta[k][word_n]] # 其中 gamma[d][k] 是 q(theta[d]) 的变分参数 # 全局更新主题 k 在词表上的变分参数 # lambda[k][v] eta[v] sum_d sum_n phi[d][n][k] * onehot(x[d][n])[v] # 这里的 eta 是先验的自然参数后面累加的是期望充分统计量注意全局更新的 lambda[k] 有两部分组成先验贡献 eta[v]和数据贡献。数据贡献部分就是把所有文档中分配给主题 k 的词频统计累加起来。这正是 4.1 节“自然参数相加”的直接应用。实现时可以先跑局部更新若干轮再刷新全局参数再回到局部更新也可以逐文档交替。收敛速度取决于更新的同步方式异步更新收敛快但容易震荡同步更新稳定但慢这个度要自己在项目里调。5. 变分推断落地常见的 5 个翻车点与排查路径5.1 ELBO 先升后降更新顺序与条件期望用错了现象坐标上升过程中ELBO 前几轮正常上升然后突然掉头向下甚至比初始值还低。这不是正常的“数值波动”而是更新公式有 bug。原因最常见的问题是更新某个变分因子时用到了自身当前轮的参数。比如在更新 q(z_i) 时把 q(μ) 的新参数和旧参数混在一起用或者在更新 q(μ) 时把 q(z) 的新旧责任度各用了一半。坐标上升要求“固定其他因子更新当前因子”如果某个因子的期望充分统计量来自自己刚更新的参数就不再满足收敛条件。解决严格按“先算全部期望充分统计量再一次性地更新所有因子”的同步顺序做。如果为了加速用异步更新也要保证更新当前因子时使用的邻居参数来自同一轮迭代不能让新旧参数交叉出现在同一个期望里。排查方法是把每轮的 ELBO 和参数一起打印出来看哪个参数在 ELBO 下降前被更新过。5.2 log-sum-exp 溢出指数族更新里的 NaN 陷阱现象跑第 3 章的代码时迭代到中途 logit_phi 出现 infELBO 变成 NaN。在主题模型里更常见log phi 直接爆掉。原因指数族更新的核心操作是算 log p(x|θ) 的期望里面通常有 exp 或 log 嵌套。比如多项分布里要算 log(Σ exp(θ))θ 稍微大一点exp(θ) 直接溢出结果就是 inf − inf NaN。很多新手误以为是学习率太大或初始化不好实际上只是缺了一个 log-sum-exp 稳定化。解决把所有“先 exp 再求和再取 log”的地方统一改成减最大值的形式logsumexp(x) max(x) log(Σ exp(x − max(x)))。Python 里直接用 scipy.special.logsumexp不需要自己实现。另一个细节是 logit_phi 本身也要稳定化计算 φ 的 log 时用 np.logaddexp(0, -logit_phi)不要先算 exp(logit_phi) 再用 log。我在第 3 章的代码里就是这样处理的看似多写了几行实际省掉很多排查时间。5.3 收敛判断失真只看参数变化量会早停现象用参数变化量小于 1e-3 作为停止条件结果训练日志显示参数早就“不动了”但下游任务效果还在缓慢变好要么反过来参数还在大幅变化时 ELBO 已经平台期程序迟迟不结束。原因参数变化量和目标函数变化量不是一回事。尤其在平均场变分推断里变分参数可能已经接近不动点但 ELBO 因为 KL 项的原因还在缓慢爬升也可能参数更新的步长很小累积起来对目标函数影响很大。只看参数变化量这两种情况都会误判。解决收敛判断应该以 ELBO 增量为主、参数变化量为辅。我的习惯是设置两个阈值ELBO 相对变化小于 1e-4 且参数变化小于 1e-3两个条件都满足才停止。而且 ELBO 的计算本身要数值稳定否则噪声会掩盖真实的收敛信号。把 ELBO 和参数变化量同时打进日志每次迭代都看不要只输出最终结果。5.4 离散隐变量上硬套重参数化ELBO 估计方差爆炸现象对离散隐变量用重参数化梯度比如 Gumbel-softmax去优化 ELBO结果训练不稳定ELBO 的估计方差巨大几轮之后就崩了。原因重参数化梯度适用于连续变量因为它把随机性转移到参数化的噪声项上梯度可以通过确定性路径回传。离散变量没有自然的连续噪声表示Gumbel-softmax 本质上是把离散分布松弛成连续近似当温度参数没退好时松弛分布和原分布差别很大ELBO 的梯度估计就偏了。解决离散隐变量的坐标上升更新本来就是闭式的不需要梯度直接用第 3 章的责任度更新就好。如果模型复杂到责任度更新不可解优先考虑评分函数估计量score function estimator它虽然方差大但无偏实在要用 Gumbel-softmax就把温度从高到低调好退火曲线温度太高会偏离原分布太低梯度方差又大。这个点是很多初学变分推断的人最容易踩的坑绕开它的关键就是“先想清楚这个变量是离散的还是连续的再选估计器”。5.5 低估后验方差平均场的“过自信”是结构性的现象变分推断得到的不确定性区间明显比 MCMC 采样的窄后验均值倒是差不多但方差可能差了 2 到 3 倍。原因平均场假设变量完全独立忽略了变量之间的相关性。当真实后验里变量强相关时平均场逼近只能用独立的边际分布去覆盖后验的支撑集结果必然是方差被低估。这不是实现 bug而是模型假设带来的结构性问题怎么调参都躲不掉。解决如果下游任务只关心点估计这个问题可以忽略如果要做不确定性量化建议做三件事。第一改用结构化变分把强相关的变量放在同一个变分因子里第二用平均场结果做诊断性检查——把每个变量的后验预测分布和真实数据的波动对比如果预测过于集中说明近似太紧第三和 MCMC 做矩对比这个做法我在第 6 章会展开。别把平均场的方差直接当置信区间交付给业务方这是血泪经验。6. 把讲义公式变成实验规范三个值得养成的验证习惯6.1 合成数据上的 ELBO 单测拿到一个新模型的变分更新公式先不要上真实数据构造一个简单到“后验近似可知”的合成数据把 ELBO 的解析解和代码算出来的值对比。比如在第 3 章的模型里我可以把 true_mu 设为 0 和 2样本量调到 2000这时平均场近似应该接近真实后验。如果 ELBO 差超过 0.1就说明更新公式或数值实现有问题。这个习惯能帮你把“推导错误”和“数值问题”分开。6.2 与 MCMC 做矩对比当更新公式跑通后用小规模数据、简单模型跑一遍 MCMC比如用 PyMC 或自己写一个和 MH得到后验样本的均值和方差再和变分推断的变分参数对比。均值差在 0.1 以内、方差差在 20% 以内基本可以认为实现正确。如果方差差太多就是平均场假设的结构性局限而不是代码 bug。这个对比同时能回答“值不值得换更复杂的近似”这个问题。6.3 训练日志里必须有的三个量每次迭代的日志里除了 ELBO至少要记录三个量ELBO 增量、变分参数的最大变化量、E_q[log p(x,z)] 与熵项各自的值。第一个判断收敛第二个判断参数是否震荡第三个判断模型是在“拟合数据”还是“在熵上做文章”。我习惯把它们排成一行打出来用脚本自动检测 ELBO 增量连续 20 轮为负的情况——一旦触发立刻停止并检查更新公式。有一次正是因为看到第三项“涨得快于第一项”才发现某个变分因子的温度参数设得不对这种细节光看 ELBO 总量是发现不了的。这套验证习惯我已经沿用了好几个项目每次换新模型都能在一小时内定位到底是推导问题、实现问题还是假设问题。希望帮到你。本文还有配套的精品资源点击获取