如果你在概率模型里见过“一种带指数的归一化式子”反复出现那大概率是遇到基于能量的模型Energy Based Model, EBM了。这类模型的核心长这样(p(x) \propto \exp(-E(x)))能量越低的地方概率越高能量越高的地方概率越低。第一次接触的人通常会有两个反应它为什么不写成标准的概率密度那个归一化常数去哪了这两个问题背后是整整一门从统计力学里长出来的概率建模思想。这篇是“概率模型的统计力学理论”系列的第一篇聚焦EBM最核心的部分玻尔兹曼分布和配分函数如何进入机器学习、能量函数怎么设计、极大似然训练为什么需要采样、对比散度CD到底在优化什么还有现代扩散模型和EBM之间的联系。适合正在学概率图模型或生成模型的读者也适合知道RBM但一直没搞懂它为什么难训练的从业者。我会把从物理到算法的完整链条拆开讲尽量不堆公式但关键推导会保留。1. 从统计力学到机器学习的桥1.1 玻尔兹曼分布能量低的系统更常见统计力学里最著名的一个结果就是玻尔兹曼分布当一个系统处于热平衡时处在某个能量状态 (E) 的概率正比于 (\exp(-E/k_B T))其中 (k_B) 是玻尔兹曼常数(T) 是绝对温度。机器学习里通常不写温度直接把 (k_B T) 吸收进能量函数于是就有了 (p(x) \propto \exp(-E(x))) 这个形式。这个式子的物理直觉非常好理解。想象一个山丘上不断被热扰动推动的小球它处于低势能位置的次数一定比高势能位置多。能量函数在这里扮演的就是“势能”角色它给每个状态打分得分越低的状态越容易被系统访问。温度则控制着这种倾向的强弱温度超高的时候系统几乎不在乎能量差异所有状态都差不多温度趋近零的时候系统只会停留在全局能量最低的地方。这个“低能量高概率”的原则就是EBM全部理论的起点。从物理搬到机器学习我们只需要做一步替换把“系统的微观状态”换成“模型要建模的数据点”。比如图片数据每一张图片就是一个状态能量函数负责判断这张图片在模型眼里“自然不自然”。自然的数据能量低不自然的数据能量高。这样图片分布就被一个能量函数间接定义出来了。1.2 温度与配分函数EBM的两个灵魂参数公式 (p(x) \propto \exp(-E(x))) 里的“正比于”三个字意味着还有一个比例常数没写出来。要把这个式子变成真正的概率分布需要在分母上除以所有可能状态的指数能量之和[ p(x) \frac{\exp(-E(x))}{Z}, \quad Z \sum_x \exp(-E(x)) ]这个 (Z) 就是配分函数。对连续数据求和要换成积分。统计力学里所有的热力学量都从配分函数导出机器学习里EBM所有的训练困难也都从配分函数开始。关键是这个 (Z) 几乎从来算不出来。对于一张 (32\times32) 的三通道图片可能的像素取值组合是一个天文数字你不可能把所有状态的指数加一遍。高维连续空间的积分更没希望。所以EBM和普通概率模型最大的区别在于它只定义了一个“未归一化的概率形式”归一化常数永远留在那。正因如此EBM又被叫做无归一化模型unnormalized model。这种模型虽然形式灵活但带来的代价就是训练和评估都要绕着 (Z) 走。可以说理解了配分函数就理解了EBM的一半。注意EBM里说的“能量”并不是物理上的动能加势能它只是在数学形式上借用了统计力学的语言。你完全可以叫它“代价函数”或“打分函数”思想是一样的。2. 能量函数的设计不是随便一个函数都能当“能量”2.1 一个最简单的例子从能量到高斯分布先用一维例子建立直觉。设 (E(x) (x-\mu)^2 / (2\sigma^2))那么 (p(x) \propto \exp\left(-(x-\mu)^2 / (2\sigma^2)\right))。调整配分函数之后这恰好就是均值 (\mu)、方差 (\sigma^2) 的高斯分布。这个例子说明一个关键点能量函数本身决定了分布的形状而配分函数只是确保所有概率加起来等于1。高斯例子里的配分函数可以手算出来(Z \sigma\sqrt{2\pi})。但一旦把 (x) 换成高维向量把 (E(x)) 换成神经网络配分函数立刻就失控了。这也解释了为什么早期机器学习社区很少用EBM能量好定义归一化搞不定。直到后来MCMC采样方法成熟以及对比散度这类近似训练方法出现EBM才真正落地。从设计的角度看能量函数几乎可以是任意函数只要它能输出一个实数。但问题在于这个函数会直接控制最终分布的性质。如果能量函数关于某些输入根本不分比如恒为常数那最终的分布就是均匀分布什么也没学到。所以能量函数必须足够有表达力去刻画数据中真正重要的结构。2.2 能量函数的常见套路与坑实际建模里能量函数通常分解成几部分。最典型的模式是“单体项交互项”比如限制玻尔兹曼机里的能量[ E(v,h) -\sum_i a_i v_i - \sum_j b_j h_j - \sum_{i,j} v_i W_{ij} h_j ]其中 (v) 是可见变量你的数据(h) 是隐藏变量模型内部的潜变量。第一项只依赖单个可见单元第二项只依赖单个隐藏单元第三项是可见单元和隐藏单元之间的成对交互。这个分解的意义在于当给定其中一层时另一层的条件分布会变得非常简单可以逐节点独立采样。这对后面的训练算法至关重要。设计能量函数时有一个很实际的大坑能量函数不能出现无界的低能区域。如果你设计的能量函数在某个方向上趋向负无穷那模型会在那个方向上疯狂积累概率质量配分函数直接发散训练时会看到loss变成NaN。比如能量写成 (E(x) (x^2-1)^2 - cx^2) 这种四次项和二次项“拔河”的形式(c) 稍大一点就可能创造两个无界下行的方向。解决方法是让能量函数的主体部分在无穷远处保持正的增长趋势例如二次项或带约束的参数化。实操心得我最早写一个自定义EBM时能量函数用的是纯MLP输出没有对无穷远处的行为做任何约束。结果训练到第12个epochloss直接变成NaN排查了很久才发现是能量函数在某个方向上无界导致的。后来我学会了在网络的最后一层输出的基础上加一个 (L_2) 范数惩罚相当于给远距离状态一个能量上推训练就稳了。3. 训练EBM极大似然背后是一场“拔河比赛”3.1 对数似然梯度正相、负相、配分函数幽灵给定训练数据 ({x_1,...,x_N})EBM的极大似然目标是对数似然最大化[ \mathcal{L}(\theta) \frac{1}{N}\sum_{n1}^N \log p(x_n) \frac{1}{N}\sum_{n1}^N \left[-E(x_n) - \log Z(\theta)\right] ]求梯度时配分函数也会参与进来。对单个样本 (x) 有[ \frac{\partial \log p(x)}{\partial \theta} -\frac{\partial E(x)}{\partial \theta} \mathbb{E}_{p(x)}\left[\frac{\partial E(x)}{\partial \theta}\right] ]这个式子里有两项。第一项叫正相来自当前训练样本的贡献作用是把观测到的数据点对应的能量往下压。第二项叫负相来自模型自身分布 (p(x)) 的期望作用是把模型当前认为的高概率区域的能量往上抬。一压一抬最终模型会在数据附近形成低能量的“谷”在其他地方形成高能量的“山”。这里的核心问题是负相需要从当前模型分布 (p(x)) 中采样。但 (p(x)) 正是我们要学的东西而且它的归一化常数未知无法直接采样。所以EBM训练绕不开一条老路——MCMC马尔可夫链蒙特卡洛。你必须在每个训练步里运行一个采样过程从近似稳态的马尔可夫链里拿到负相样本。下面这张表可以直观理解正相和负相的区别阶段采样来源梯度作用通俗解释正相训练数据降低数据点的能量让真实例子越来越“顺眼”负相模型分布MCMC采样升高模型样本的能量让模型编出来的假例子越来越“别扭”3.2 采样方法吉布斯、朗之万、SGLDMCMC采样是EBM训练真正的瓶颈。最常用的两类方法各有适用场景。第一类是块吉布斯采样block Gibbs sampling特别适合能量函数具有“条件分布可分解”结构的情形。比如RBM给定可见层时隐藏层的各个神经元条件独立可以一次性并行采样所有隐藏单元反过来也一样。这样交替采样几步就能得到一个近似服从模型分布的样本。块吉布斯采样的优点是简单、稳定不需要计算梯度缺点是要求模型结构必须允许这种条件分解否则一步采样要跑很长的嵌套循环速度无法接受。第二类是朗之万动力学Langevin dynamics适合连续状态空间。它的更新公式是[ x_{t1} x_t - \frac{\varepsilon}{2}\nabla_x E(x_t) \sqrt{\varepsilon}, z_t, \quad z_t \sim \mathcal{N}(0, I) ]这个更新有两部分一部分是沿着能量梯度下降的方向走减少能量另一部分是注入高斯噪声让采样过程能够探索状态空间。理论上当步长 (\varepsilon) 足够小、迭代次数足够多时(x_t) 的分布会收敛到 (p(x) \propto \exp(-E(x)))。实际使用时人们更喜欢用随机梯度朗之万动力学SGLD就是只对一小批数据计算梯度来更新采样链效率高很多在采样过程中保持收敛性质。经验上朗之万动力学在连续空间的EBM训练里更常用因为现代EBM的能量函数往往是神经网络没有自然的条件分解结构。但朗之万动力学对步长非常敏感步长太大链子容易发散步长太小混合又太慢。新手最常犯的错误是把采样步长和学习率设成同一个值两个概念差别很大学习率决定参数更新幅度采样步长决定模拟随机过程的离散化细粒度。3.3 对比散度CDRBM时代最实用的近似理论上负相采样需要让马尔可夫链从随机状态出发跑很久才能达到稳态。实践中根本耗不起。1999年前后Hinton提出的对比散度算法给出一个极其大胆的近似直接从训练数据点出发只跑几步吉布斯采样就用这个“半生不熟”的样本当作负相估计。为什么这真的work核心直觉是训练数据已经落在真实分布的高概率区域附近所以数据点的邻居也大概率是真实分布中的合法样本。虽然这时候链子还没完全混合但对于估计负相梯度来说与其从全局随机状态开始等它慢慢爬到数据附近不如直接从数据附近开始把最必要的那点“推开”信息拿过来。CD-k算法里的 (k) 就是采样步数通常取1就够。训练流程可以概括为从一个训练数据点 (v^{(0)}) 出发。交替吉布斯采样 (k) 步得到模型样本 (v^{(k)})。用正相统计量和负相统计量的差值近似梯度更新参数。RBM训练时经典的更新规则如下[ \Delta W_{ij} \eta \left( \langle v_i h_j \rangle_{\text{data}} - \langle v_i h_j \rangle_{\text{recon}} \right) ]其中角括号表示期望第一项在给定训练数据的条件下求隐藏单元期望第二项在重建样本下求期望。这个形式其实就是极大似然梯度中正相减负相的展开。CD算法的代价是引入了偏差但换来的是训练速度的大幅提升。后续研究者又提出了持续对比散度PCDPersistent Contrastive Divergence即维护一组始终在更新的采样链不再从训练数据重启而是让链子在训练过程中持续演变。PCD在训练早期更稳定尤其在学习率较大时能比CD得到更好的结果。注意CD算法虽然便宜但不是免费的。采样步数太少时负相估计严重偏差模型会把训练数据周围的局部区域都当成低能区导致生成样本模糊甚至退化成数据的小扰动。如果发现生成的样本看起来只是训练集的轻微噪声版本试试把CD步数从1增加到5或者换成PCD。4. 手把手实现RBM理论落到代码4.1 RBM的能量与条件分布受限玻尔兹曼机RBM是EBM里结构最简单、应用最广的代表。它是一个二部图模型可见层 (v) 和隐藏层 (h)层内没有连接层间全连接。每个变量取二值通常为0或1。能量函数写成[ E(v,h) -\sum_i a_i v_i - \sum_j b_j h_j - \sum_{i,j} v_i W_{ij} h_j ]省去推导过程的细节由这个能量函数可以得到两个关键结论。给定可见层 (v) 时第 (j) 个隐藏单元取1的条件概率是[ P(h_j1 \mid v) \sigma\left(b_j \sum_i v_i W_{ij}\right) ]其中 (\sigma) 是sigmoid函数。反过来给定隐藏层 (h) 时[ P(v_i1 \mid h) \sigma\left(a_i \sum_j W_{ij} h_j\right) ]这两条式子简单到让人怀疑是不是漏了什么。但它们的意义非常大条件分布是因子化的所以采样时可以一次性并行更新整层。这正是RBM能被高效训练的根本原因。把隐藏层积分掉还能导出关于可见层的自由能[ F(v) -\sum_i a_i v_i - \sum_j \log\left(1 \exp(b_j \sum_i v_i W_{ij})\right) ]这个自由能可以直接作为可见层“有多不自然”的打分做异常检测时特别好用。4.2 CD训练的核心代码骨架下面用PyTorch写一个最简RBM训练骨架只保留核心逻辑。import torch import torch.nn as nn import torch.nn.functional as F class RBM(nn.Module): def __init__(self, n_vis, n_hid): super().__init__() self.W nn.Parameter(torch.randn(n_vis, n_hid) * 0.01) self.a nn.Parameter(torch.zeros(n_vis)) self.b nn.Parameter(torch.zeros(n_hid)) def sample_h(self, v): p_h torch.sigmoid(self.b v self.W) return p_h # 训练时一般用概率做统计量 def sample_v(self, h): p_v torch.sigmoid(self.a h self.W.t()) return p_v def free_energy(self, v): term1 -v self.a term2 -F.softplus(self.b v self.W).sum(dim1) return term1 term2 def cd_step(self, v0, k1): v_k v0 with torch.no_grad(): for _ in range(k): h_k torch.bernoulli(self.sample_h(v_k)) v_k torch.bernoulli(self.sample_v(h_k)) # 正相自由能 负相自由能 pos_energy self.free_energy(v0).mean() neg_energy self.free_energy(v_k).mean() return pos_energy - neg_energy训练循环里只需要做三件事算CD损失、反向传播、用优化器更新参数。以大小为128的批次为例model RBM(n_vis784, n_hid256) optimizer torch.optim.SGD(model.parameters(), lr0.01, momentum0.9) for batch in dataloader: batch batch.view(batch.size(0), -1) loss model.cd_step(batch, k1) optimizer.zero_grad() loss.backward() optimizer.step()注意这里并没有显式计算正相和负相统计量再手动组合梯度而是把自由能的差值直接当作损失。原理在第3章的推导里已经提过对单个样本 (x)(-\log p(x) F(x) \log Z)CD近似把 (\log Z) 的梯度用负相自由能代替于是 (F(v_0) - F(v_k)) 就是负对数似然梯度的代理。这样写代码最干净也不容易出错。4.3 训练要点与超参数选择RBM训练看起来简单实际中超参数却非常敏感。我按踩坑频率排序说几个重点。第一是学习率。动量SGD情况下0.01是一个比较安全的起点。学习率过大时训练loss会上下剧烈震荡模型参数在两次更新之间跳来跳去采样链完全跟不上参数的变化最终训练失败。如果做对比你会发现CD训练里学习率需要的量级通常比普通分类网络小一个数量级因为负相采样带噪声梯度本身的方差天然就大。第二是可见层激活函数。上面代码用的是伯努利采样适合二值数据。如果处理灰度图像这种连续数据建议改成正态可见单元能量函数中的可见项换成 (\sum_i (v_i-a_i)^2 / (2\sigma_i^2))条件分布 (P(v_i|h)) 变成均值为 (a_i \sigma_i \sum_j W_{ij}h_j) 的高斯分布。此时还需要对输入数据做标准化否则能量函数的尺度差异会导致训练非常不稳定。第三是初始化和权衰。权重矩阵用均值为0、标准差0.01的正态初始化即可不要用xavier初始化。因为RBM训练早期最怕某些隐藏单元在正相统计量里被“永远激活”xavier初始化会让初始权重偏大容易造成隐藏单元饱和。如果觉得重建误差收敛太慢可以加一个非常小的 (L_2) 权重衰减1e-4量级配合L1稀疏惩罚效果更明显。我建议训练时每隔几个epoch计算一次重构误差reconstruction error即 (v_0) 经过一次采样-重建后的平均距离。重构误差虽然不等于负对数似然但它是一个非常廉价有效的早期停止指标。如果重构误差下降后开始反弹通常意味着过拟合了此时应该考虑增大训练数据量或降低模型容量。5. 常见问题与排查技巧实录5.1 模型崩到“什么都生成不了”症状训练完成后从模型采样得到的图像要么全是噪声要么全是同一个模糊图案。原因通常有两种。第一种是负相样本太差采样链没有混合。解决办法是增大CD的 (k) 步数或者改用PCD维护持续链。第二种是能量函数表达能力不足模型无法在数据不同模态之间都形成低能谷。解决办法是增加隐藏单元数或引入卷积结构比如ConvRBM。我遇到过最迷惑的一次是训练MNIST看起来loss一直在降但生成的样本却变成了十种数字的“叠加平均值”。后来发现是对输入数据做了中心化但没有压缩到0到1之间负值输入让伯努利可见单元的sigmoid输出全部推向0.5附近重建样本自然成了灰色斑点。数据标准化这个看似不起眼的步骤对EBM的影响比对普通神经网络大得多。5.2 训练loss变成NaN几乎所有EBM新手都会遇到NaN问题。主要原因有两个都可以提前预防。第一是能量函数无界导致配分函数发散前面已经提到过。第二是负相采样时链子发散尤其使用朗之万动力学时步长过大。排查方法是先固定随机种子把采样步长缩小10倍看看loss是否恢复稳定。如果是加一个梯度裁剪到1.0很多情况下就能救回来。需要特别提醒EBM的loss曲线不像分类任务的交叉熵那样平滑。因为负相采样本身是随机过程loss在训练过程中会有肉眼可见的波动这是正常的。判定训练是否健康的标志不是loss单调下降而是重构误差和大批量采样质量是否在逐步改善。5.3 能量的平移和缩放温度到底怎么调温度在EBM里的作用是隐式的。把能量函数整体乘以一个常数等价于改变温度(p(x) \propto \exp(-E(x)/T))。温度高则分布平坦温度低则分布尖锐。生产环境中很少显式调温度但有一个常见场景必须懂训练完成后做生成时可以通过缩放能量来调整样本多样性。想要更清晰但多样性略低的样本可以用 (T1)相当于把能量整体放大让模型更倾向于落在低能量的谷底。想要多样性能探索更多模式用 (T1)分布变得更平坦。这个技巧在RBM做图像生成时非常有用训练时看不到的罕见模式适当升温后经常能被采样出来。实操心得有一次我做数据增强用RBM生成一些训练样本的变体直接采样出来的图像模式比较单一。把采样过程的朗之万温度调到1.2之后模式多样性肉眼可见地提升代价是生成的个体质量稍有下降。对于数据增强这种容错率高的场景这个取舍非常划算。5.4 模式坍塌与多峰分布EBM存在和GAN类似的模式坍塌问题只是表现方式略有不同。EBM训练不足时模型可能只在某个模式附近分配过高概率其他模式完全没有被覆盖。检查办法是训练后用模型大量采样并统计类别分布和训练数据的类别分布做对比。缓解手段实践下来最有效的是三种一是用PCD替代CD因为持续链更容易跨越模式间的能量壁垒二是训练数据里随机做数据增强逼迫模型看到更多变化形态三是显式添加一个熵正则项鼓励隐藏层激活分布分散间接提升采样多样性。面对多模态数据时EBM的表达力很强但训练动力学对模式覆盖并不友好这是模型本身的结构特性不是简单的调参能根治的。6. 从EBM到扩散模型6.1 Score Matching绕开配分函数的另一条路极大似然之所以痛苦全是因为配分函数。那有没有办法完全躲开它有Score Matching就是其中一条非常漂亮的路。注意到EBM的对数概率梯度是[ \nabla_x \log p(x) -\nabla_x E(x) ]这个量叫得分函数它只依赖能量函数的梯度完全不需要配分函数。Score Matching的训练目标就是让一个参数化的得分函数 (s(x)) 尽量接近真实数据的得分函数。可惜真实得分函数没法直接观测但理论推导表明可以用含噪数据构造一个可行的替代目标——去噪得分匹配Denoising Score Matching。去噪得分匹配的做法是给每个数据点加高斯噪声得到一个噪声扰动后的分布 (q(\tilde{x}))。可以证明训练 (s(x)) 去预测噪声扰动数据条件分布的得分等价于学习原始数据分布的真实得分。等号右边的条件分布 (q(\tilde{x}|x)) 是高斯的它的得分有解析形式训练直接可解。这正是NCSN和DDPM这类模型在能量视角下的基本原理。6.2 扩散模型一条经过平滑处理的EBM训练路径扩散模型Diffusion Model可以看作是EBM思想在现代条件下的延续。它没有直接学一个 (E(x))而是学一族时间依赖的得分函数[ s_\theta(x_t, t) \approx \nabla_{x_t} \log q(x_t) ]其中 (x_t) 是原始数据经过 (t) 步加噪后的版本。加噪过程本质上做了一个很巧妙的事情把原来复杂无比、到处都是尖锐峰值的能量景观通过逐步噪声扰动变得平滑。注意这和回火采样殊途同归——回火是采样时升温让链子更容易跨峰扩散模型是把步进式的“升温降温”直接编码进训练目标里。生成阶段扩散模型从噪声开始用学习到的得分函数做朗之万动力学或等价的逆过程一步步“去噪”回到数据分布。对照第3章的框架扩散模型训练用的就是去噪得分匹配替代极大似然采样过程用的就是基于梯度的朗之万采样替代吉布斯采样。理解了EBM的配分函数难题再看扩散模型你会觉得它的一切设计都像是在给一个旧问题做新解法——这层联系是这两年最值得理解的部分。6.3 EBM的现实应用场景虽然后续有扩散模型等更强大的生成模型但EBM在几个特定场景下依然有不可替代的优势。一是异常检测。EBM天然自带一个打分功能能量低是正常样本能量高是异常样本。训练一个EBM只需要正常数据推理时直接算自由能或能量值就行。相比其他生成模型还要算重构误差那些间接指标EBM的打分是直接的、理论完备的。二是组合优化和采样问题。EBM的能量函数可以编码约束条件和目标函数通过采样直接寻找低能构型。这在一些组合优化问题、约束满足问题上表现得非常自然。三是作为更复杂模型的基础组件。比如能量模型可以被嵌入深度架构中或者和VAE类模型结合用来建模潜变量空间的先验分布。很多后续理论分析也都以线性EBM作为基石先理解这一篇的内容后面看变分推断、平均场论、扩散模型那一大串东西都会轻松很多。最后分享一点个人体会。刚开始接触EBM时我最大的困惑是一个算不出归一化常数的模型凭什么能当概率模型用后来在实做里把采样、正负相位、配分函数梯度的逻辑一步步走通这个疑问才消失。EBM确实难训采样慢调参玄学但它的价值恰恰在于逼迫你把概率模型的核心难点——采样和归一化——想透一次。现在去看扩散模型那些intricate的细节很多都能在EBM的训练困境里找到影子。后面这个系列我会继续拆解RBM与深度玻尔兹曼机的关系、变分推断与平均场方法以及EBM与对比学习方法之间的内在联系。如果你正在学概率模型建议这一篇里的正相/负相梯度推导和CD算法亲自动手推一遍值得花这个时间。