简介:《Graphical Models, Exponential Families, and Variational Inference》是一份面向概率图模型与近似推断学习者的经典综述型 PDF,适合机器学习、统计学方向的研究生及需要补齐理论框架的算法工程师。内容以指数族表示和累积量函数与熵的共轭对偶为主线,系统梳理似然、边际概率与最可能配置的变分表达,并将 sum-product、簇变分、期望传播、mean field、max-product 及线性规划、锥规划松弛纳入统一视角,同时触及生物信息学、信号与图像处理、信息检索等应用场景。压缩包仅含 1 个 PDF 文件,约 2.06MB,轻量便于检索、批注与反复查阅。已有 235 人学习,在图模型与变分推断主题上具备一定参考热度。读者可从中获得从问题建模、变分表示到算法归类的完整脉络,并理解图模型、指数族与变分推断之间的结构性联系。
1. 一屋子互相影响的人:图模型到底在算什么
想象一个屋子里坐着十来个人,每个人今晚出不出门,取决于旁边几个人出不出门。你想知道某一个人出门的概率,就得把所有 2 的 10 次方种组合逐一算一遍;人再多一点,这个数字立刻变成天文数字。
图模型(Graphical Models)要做的事,就是先画出「谁影响谁」这张关系图,再顺着连线把那个巨大的联合概率拆成一堆小块,让计算从「根本不可能」退回到「勉强能算」。这个概念值得搞懂,因为今天你听到的消息传递、信念传播、变分推断、采样近似,全都是围着这张图在转;搞不清图在表达什么,后面那些算法就只是个黑匣子。
2. 图模型是什么:把一张天文数字的概率表,拆成一张关系网
2.1 有向图:把联合概率写成一串条件概率的乘积
第一种写法是「有向」:边上带箭头,箭头表示「谁直接影响谁」。如果箭头永远不绕回起点,这张图就叫有向无环图。在这个约定下,每个节点只需要关心自己的父节点,也就是那些直接指向它的节点。
于是联合概率可以写成一串条件概率连乘:每个节点的概率,只以它自己的父节点为条件。这句话看着平淡,威力在于它顺手给出了一堆条件独立:一个节点在给定父节点之后,就和图上的其他节点都没关系了。
举个四节点的例子,节点 1、2、3 都指向节点 4。如果不看图,写联合概率要枚举 2 的 4 次方共 16 种组合;看了图,只要存三张边缘小表和一张「三父一子」的条件表,一共 3×2 + 8×2 = 22 个数,而且每个数都有明确含义,改一个不影响别的。
import itertools import numpy as np # 某模拟项目里的四节点有向图:1->4, 2->4, 3->4,变量均取 0/1 # cpt[节点][父节点取值元组] = [P(x=0), P(x=1)] cpt = { "1": {(): np.array([0.6, 0.4])}, "2": {(): np.array([0.3, 0.7])}, "3": {(): np.array([0.5, 0.5])}, # 节点 4 有三个父节点,条件表共 8 行;这里用一个简单规则填充 "4": {(a, b, c): (np.array([0.9, 0.1]) if a + b + c == 0 else np.array([0.2, 0.8])) for a, b, c in itertools.product([0, 1], repeat=3)}, } parents = {"1": (), "2": (), "3": (), "4": ("1", "2", "3")} def joint(x): """给定一次完整赋值 x=(x1,x2,x3,x4),按图结构连乘得到联合概率""" p = 1.0 for node, pa in parents.items(): key = tuple(x[int(p) - 1] for p in pa) if pa else () p *= cpt[node][key][x[int(node) - 1]] return p print(sum(joint(x) for x in itertools.product([0, 1], repeat=4))) # 应等于 1.0这段代码里最关键的不是循环,而是parents这张表:它决定了每个条件项只依赖谁。key那一行把父节点的取值打包成一个元组去查表,这正是「按图分解」在代码层面的样子。如果把parents["4"]改成空元组,就等于宣称节点 4 和谁都不相干,模型立刻退化成四个独立变量——图变了,模型就变了,这是图模型最直观的一点。
2.2 无向图:团、势函数和那个算不出来的归一化常数
第二种写法是「无向」:边没有箭头,只表示「这两个变量之间有约束」。这时候没法再谈父子,只能谈团——图上两两相连的一组节点。联合概率写成每个团上一个非负函数(叫势函数)的乘积,再除以一个归一化常数 Z。
无向图更适合表达「互相迁就」而不是「谁导致谁」。比如一张黑白噪点图,每个像素和上下左右四个邻居倾向于同色,这种关系是对称的,硬要画箭头反而别扭。反过来,如果你要表达「先有病因再有症状」,有向图更自然。
无向图还带来一套漂亮的图判据:两个变量如果在图上被「切断」——把它们的邻居都拿掉之后彼此不连通——那它们在给定这些邻居时条件独立。这套判据让「独立性」这种抽象概念变成了拿眼睛看图就能判断的事。
代价出在 Z 上。有向图的连乘天然归一化,每一项都是条件概率;无向图的势函数只是「打分的相对大小」,必须除以所有可能取值的总和才能变成概率。这个总和就是配分函数,节点一多,求和项数指数级爆炸。后面所有的近似推断方法,本质上都是在跟这个 Z 较劲。
2.3 为什么非拆不可:联结树算法和树宽这道坎
拆完之后能不能算,取决于一个叫树宽的图指标。有一种经典算法能把图上的求和整理成一棵「团树」,在树上做一轮消息传递,就能精确算出所有边缘概率。它的开销大致是「取值数」的「树宽加一次方」。
这句话翻译过来就是:树宽小的图,比如链、树、稀疏网格,算起来很轻松;树宽大的图,比如几乎人人都和人人相连的稠密图,开销直接翻车。而现实中的模型往往不稀疏——一份文档里词与词的关系、一张图里区域与区域的关系,稍不留神就连成一片。
提示:判断一个图模型能不能硬算,第一步不是看节点多少,而是估一下树宽。节点一千但结构像条链,可能比节点五十但稠密相连还好算。
这就是整份资料要解决的核心矛盾:精确算法在图不够稀疏时失效,于是需要一套系统的近似方法。采样是一条路,变分推断是另一条路,也是这份资料重点铺开的那条。
3. 指数族为什么是万能底座:凸性、对偶和那个算不出来的 Z
3.1 一句话定义和三个零件
指数族是一大类概率分布的统称,形式统一:概率等于「一个基准函数」乘上「自然参数的线性组合取指数」,再除以一个归一化项。这个归一化项通常记作 A(θ),叫对数配分函数。
它有三个零件:自然参数 θ,决定分布长什么样;充分统计量 φ(x),是从数据里能算出来的那组数;还有 A(θ),负责把结果拉回概率。伯努利、泊松、高斯、多项分布,全都是这个形式的特例,只是零件不同。
为什么图模型几乎都长在指数族上?因为图模型的每个团因子本身就是「打分」形式,取个对数就变成了参数的线性组合,天然落进指数族的框里。换句话说,指数族不是硬套上去的帽子,而是图模型分解结构的自然语言。
3.2 共轭对偶:把「求和」翻译成「求最优」
指数族最值钱的性质是凸性。A(θ) 是凸函数,它的导数就是充分统计量的期望,这个映射还是可逆的。更重要的是,A(θ) 有一个共轭对偶函数 A*(μ),本质上是负熵。
这组对偶关系说的是:你可以用参数 θ 描述一个分布,也可以用期望 μ 描述同一个分布,两种描述一一对应,中间靠一个凸函数连接起来。参数空间里的一次「求导」,对应期望空间里的一次「求值」。
这一步是整份资料的地基。因为一旦把「算概率」翻译成「算一个凸函数的对偶」,原本的积分求和问题就变成了一个优化问题,而优化问题是有成熟工具、有下界、有收敛性可谈的。
3.3 变分表示:log Z 的两种身份
把对偶关系代进去,会得到一个非常关键的等式:对数配分函数等于「参数和期望的内积减去负熵」在所有合法期望上的上确界。左边是一个算不动的积分,右边是一个优化问题。
import numpy as np # 伯努利指数族:phi(x)=x,A(theta)=log(1+exp(theta)) def A(theta): return np.log1p(np.exp(theta)) def A_star(mu): """共轭对偶函数 = 负熵;mu 必须在 (0,1) 内""" return mu * np.log(mu) + (1 - mu) * np.log(1 - mu) for theta in [-1.5, 0.0, 2.0]: mu = 1 / (1 + np.exp(-theta)) # mu = A'(theta),参数到期望的映射 dual = theta * mu - A_star(mu) # 变分表示右边的取值 print(f"theta={theta:5.2f} mu={mu:.4f} A={A(theta):.4f} dual={dual:.4f}")跑一下会发现两组数完全一致:A(theta)和dual每一行都相等。这不是巧合,而是说在最优的那个 μ 上,右侧取到最大值,正好等于左侧。这个等式就是「变分表示」四个字的全部含义:把一个难算的量,写成一族好算的量的最大值。
难点在于「合法的期望」这个集合。精确的集合叫边缘多面体,它由大量线性不等式刻画,规模随变量数指数增长。所以真正的做法是:把这个集合换成更大或更小的近似集合,得到原问题的一个上界或下界。整套变分推断,就是围绕「换哪个集合、换来的是上界还是下界、优化起来难不难」这三个问题展开的。
4. 变分推断怎么落地:把难算的积分,换成一个能优化的目标
4.1 Bethe 近似与和积算法:把大问题拆成一堆小问题
最经典的一种换法叫 Bethe 近似。它把精确表示里的熵项,换成一堆「单点熵减去边熵」的组合。这个组合在树上是严格正确的,所以树上的消息传递算法能给出精确答案;图上一旦出现环,这个近似就只是近似,而且不保证是上界还是下界。
对应的算法就是和积算法,也就是常说的信念传播。每个节点往邻居发一条消息,消息的内容是「结合我自己的势函数、以及其他邻居告诉我的信息,我认为你应该是什么样」。所有消息反复迭代到不再变化,再用收敛后的消息算出每个变量的边缘概率。
import numpy as np # 一条四节点链上的和积算法:1 - 2 - 3 - 4,二值变量 pairwise = { # 相邻节点之间的 2x2 势函数(兼容性打分) (1, 2): np.array([[3.0, 0.5], [0.5, 3.0]]), (2, 3): np.array([[2.0, 0.8], [0.8, 2.0]]), (3, 4): np.array([[2.5, 0.6], [0.6, 2.5]]), } unary = {i: np.array([1.0, 1.0]) for i in range(1, 5)} # 单点势函数,先设为均匀 def message(i, j): """节点 i 发给节点 j 的消息:把自己的单点势和除 j 外所有邻居消息乘起来再求和""" m = unary[i].copy() for k in (i - 1, i + 1): if k in unary and (min(i, k), max(i, k)) in pairwise and k != j: m *= incoming[(k, i)] psi = pairwise[(min(i, j), max(i, j))] return psi @ m # 对 i 的取值求和,得到关于 j 取值的向量 incoming = {} for (i, j) in pairwise: incoming[(i, j)] = np.ones(2) incoming[(j, i)] = np.ones(2) for _ in range(50): # 链上没有环,几轮就收敛;有环的图需要更多轮且未必收敛 new = {k: message(k[0], k[1]) for k in incoming} if max(np.abs(new[k] - incoming[k]).max() for k in incoming) < 1e-9: break incoming = new for i in range(1, 5): # 用收敛的消息拼出每个节点的边缘(未归一化) b = unary[i].copy() for k in (i - 1, i + 1): if k in unary and (min(i, k), max(i, k)) in pairwise: b *= incoming[(k, i)] print(i, b / b.sum())这段代码有两个容易忽略的细节。第一,message里先用min/max把边的顺序统一,否则 (2,1) 和 (1,2) 会被当成不同的边,查到不存在的键上直接报错。第二,链上迭代几十轮就稳定,但在有环的图上,消息可能来回震荡永不收敛——这不是代码写错了,而是 Bethe 近似本身的性质。工程上常见的做法是加阻尼,或者干脆限制迭代次数后取最后一次结果,但那样就没有任何保证可言了。
4.2 平均场:砍断耦合,换一个可靠的下界
另一种思路更粗放:既然变量之间互相纠缠难算,那就干脆假设它们互相独立。用一个可分解的分布去逼近真实后验,然后最小化两者之间的 KL 散度。
这个假设明显是错的,但它错得有方向——因为 KL 散度这个量本身有个符号保证,这么逼近得到的目标值一定是真实对数配分函数的下界。也就是说,答案不精确,但你知道真实值在它上面,不会算着算着自己骗自己。
优化方式是坐标上升:固定其他所有变量的分布,单独更新某一个,更新公式是「在其余变量的当前分布下,对联合对数概率取期望」。一轮一轮刷,目标值单调不降,直到稳定。缺点是容易停在局部最优,而且因为强行假设独立,往往会低估不确定性,给出过于自信的结论。
Bethe 和平均场都建立在非凸的优化问题上,都可能有多解。这正是下一节要说的凸松弛的动机:既然非凸让人提心吊胆,那就找一个凸的近似。
4.3 凸松弛:换一个更大的集合,换一个上界
回到那个「在所有合法期望上取上确界」的表示。如果精确集合太难刻画,就把它放宽成一个更大的集合,比如只用「每个变量各自的取值非负且求和为一」这类线性约束去包住它。集合变大了,上确界只会变大,于是结果成了真实值的一个上界。
这类做法叫线性规划松弛,它和最大积算法有很深的联系:最大积算法在树上精确,在有环时其实隐式地解了一个线性规划,而松弛做得越细,结果越接近真实。
再往外还有锥规划松弛,用半正定的矩矩阵来施加更紧的约束。代价是每一层松弛都更贵,收益是界更紧。实际选择时,通常先跑便宜的松弛看看界松不松,如果本来就接近精确,就没必要上重的工具。
5. 最容易搞混的五个地方:图模型、指数族、变分推断的边界在哪
5.1 误区一:把变分推断和采样当成同一类东西
这两者经常被并列成「两种近似推断」,于是很多人以为它们可以互换。混淆的原因在于它们解决的确实是同一个问题:算不动的边缘概率和配分函数。
区别在路径上。采样是从分布里抽样本,用样本频率去估计期望,样本够多时结果会收敛到真值,但收敛速度和「多久算够」没有硬保证,跑一晚上也未必知道差多少。变分推断是把推断变成一个优化问题,得到的是确定性的解,速度快,但解本身有系统性偏差,而且这个偏差不会因为多跑几轮就消失。
区分的口诀是:要一个能明确说出「误差往哪个方向偏」的答案,走变分;要一个长期看不会偏、且能承受等待的答案,走采样。两者的混合也常见:用变分给出一个初始近似,再用采样去修正。
5.2 误区二:把图模型、贝叶斯网络、马尔可夫随机场当成三个并列的东西
初学者常把这三个名词轮流使用,以为差不多。混淆的原因在于它们的名字都很「概率」,而且经常同时出现在一篇文档里。
真实关系是包含关系:图模型是总称,贝叶斯网络是有向无环图那一支,马尔可夫随机场是无向图那一支。判据很简单:看边有没有箭头、允不允许成环。有箭头且无环,贝叶斯网络;无箭头,马尔可夫随机场。同一份问题甚至可以用两种图分别表示,只是分解方式和独立性读法不同。
5.3 误区三:把「边缘概率」和「最可能配置」混为一谈
这两个任务都要面对那个算不出来的归一化常数,看起来像是同一件事的两种说法。混淆的原因在于,很多人第一次接触时都是在一棵树上跑算法,两种算法长得几乎一样。
区别在于问的问题不同:边缘概率问的是「单个变量取某个值的概率是多少」,最可能配置问的是「所有变量一起取哪组值,联合概率最大」。前者用和积算法,后者用最大积算法,中间就是把「求和」换成「取最大」。
关键点在于:最可能配置不能由各变量的边缘概率拼出来。每个变量各自最可能的取值凑在一起,可能恰恰是一组联合概率很低的组合。这在离散变量的问题里尤其常见,用错了会得到一个自相矛盾的结论。
5.4 误区四:以为算法跑出数来就是对的
树上跑消息传递是精确的,这一点很多人记住了,然后不小心推广成「消息传递是精确的」。混淆的原因在于代码完全一样,换个图结构照样能跑出数,不报错。
区分要看图上有没有环,以及环有多长。无环时精确;有环时不保证,而且可能来回震荡。工程上的常见补救是加阻尼、改成异步更新、或者用更贵的凸松弛换一个能说清方向的界。
注意:有环图上的信念传播,结果既可能是上界也可能是下界,甚至可能因为没收敛而毫无意义。看到收敛曲线还在跳,就别急着用那个数。
5.5 误区五:以为 Bethe 近似和平均场差不多
两者都是近似,都做优化,都不保证全局最优,看起来像一对兄弟。混淆的原因在于它们在简单例子上经常给出接近的结果。
差别在界的方向和熵的写法。Bethe 保留了一部分变量之间的相关性,在树上严格正确,有环时方向不明。平均场直接砍掉所有相关性,换来一个严格的下界,代价是低估方差、结果过于自信。如果你需要一个能对外声明的保证——比如说「真实值不会低于这个数」——那只能用平均场或凸松弛这一侧的工具。
6. 什么时候用、什么时候别用:一份判断清单
先给一个具体的对照。假设手上有一个模型,要算某几个变量的后验。第一步不是选算法,而是估结构:
| 情况 | 特征 | 建议路线 | 风险点 |
|---|---|---|---|
| 图是一棵树或链 | 树宽小,无环 | 和积算法直接算,精确 | 几乎无,注意数值下溢,转对数域 |
| 图有环但稀疏 | 环长,局部连接 | 信念传播 + 阻尼,或用 Bethe 近似 | 不保证收敛,结果无界的方向保证 |
| 需要能对外声明的界 | 下游要做决策 | 平均场拿下界,线性规划松弛拿上界 | 两边都松,界可能宽到没用 |
| 变量多且耦合强 | 稠密,树宽大 | 采样或变分,看能等多久 | 采样难判断收敛,变分有系统偏差 |
| 只要最可能配置 | 离散变量,组合优化味道 | 最大积 / 线性规划松弛 | 不能用边缘概率拼,会自相矛盾 |
几个明确的「别用」场景。第一,别在有环图上用信念传播,然后把结果当成精确后验写进报告,这是最容易翻车的一步。第二,别用平均场去估计不确定性区间,它的方差估计天生偏小,拿它做风险度量等于自己给自己吃后悔药。第三,别在变量强耦合的问题上硬套可分解的平均场,独立性假设和现实差太远,优化会卡在一个明显不对的解上。第四,别在树宽已经很小的图上上锥规划松弛,那是拿大炮打蚊子,还容易因为数值问题引入新的误差。
反过来,什么时候这套东西值得投入?当你需要反复对同一个模型做推断、且对速度有要求时,变分方法的优势就出来了:优化一次,后续查询几乎免费;采样则每次都要重跑。当你需要把推断嵌进一个更大的训练循环里时,变分还能顺带把参数估计一起做掉,这是它相比采样更顺手的地方。
我第一次认真读这类材料时,是从「变分」这个词开始懵的,后来才明白它不过是在说「把算不出来的积分写成一族可以优化的量的极值」。从那以后我每次拿到一个新模型,都强制走一遍「先看树宽、再看有没有环、最后问自己需不需要界」这三步,哪怕结论是「这个图太小,直接硬算」。这份三百多页的综述把图模型、指数族、变分推断三条线拧成了一股绳,从因子分解讲到凸松弛,中间每一步都有例子和推导支撑,需要的可以按文件名取这份资料,对着自己手上的模型翻对应的章节,比从头读到尾省力得多。希望帮到你。
本文还有配套的精品资源,点击获取