1. 从统计力学到机器学习:能量模型的前世今生
做概率模型的人,迟早会遇到 Energy Based Model 这个名字。我第一次认真研究 EBM,其实是带着一个挺朴素的问题:为什么物理学家研究气体分子运动的那套数学,会被原封不动搬到机器学习里来?后来才明白,这两件事本质上都在回答同一个问题——给定一堆可能的配置,每个配置出现的概率到底是多少?
在统计力学里,一个微观状态出现的概率由玻尔兹曼分布决定:概率正比于 exp(-E/T),其中 E 是能量,T 是温度。能量越低的状态,出现概率越高;能量越高的状态,出现概率指数级下降。这个形式简洁到近乎优雅,而且给了我们一个非常直观的世界观:系统倾向于朝着能量低的方向走。
EBM 就是把这套世界观直接搬进了机器学习。我们不再直接定义 P(x),而是先定义一个能量函数 E(x)(以及 E(x,y)),然后通过玻尔兹曼映射把能量变成概率。这样做的第一好处是形式极度统一——任何你能用能量函数刻画的关联规则,都可以纳入这个框架;第二好处是物理直觉清楚——训练一个 EBM,本质上就是在"凿"一个能量地形,让真实数据的能量低、非真实数据的能量高,或者说得更技术一点,让模型分布去贴合数据分布。
这篇文章主要适合两类读者:一类是已经接触过一些深度生成模型(比如 VAE、GAN),想换个视角重新理解概率建模的人;另一类是本来做统计物理或计算物理,想看看自己的老本行在机器学习里怎么发光发热的人。我会尽量把数学推导控制在"够用"的范围内,但一些关键的公式变换必须写出来,因为 EBM 所有的训练难点都藏在公式里。
2. EBM 的核心框架:能量、配分函数与概率分布
2.1 能量函数与玻尔兹曼分布
EBM 的定义其实非常简单。给定输入数据 x(可以是图片、序列、状态向量等),我们定义一个能量函数 E_θ(x),带参数 θ。这个函数输出一个实数标量,表示这个配置(configuration)的"不合适程度"。能量越低,说明这个配置越"自然"或越"合理"。
然后通过玻尔兹曼分布把能量转换为概率密度:
P_θ(x) = exp(-E_θ(x)) / Z(θ)
这里的 Z(θ) 是配分函数,计算公式是:
Z(θ) = ∫ exp(-E_θ(x)) dx
在离散情况下就是求和:
Z(θ) = Σ_x exp(-E_θ(x))
你看,就这么两步,一个概率模型就建立起来了。这个框架的巧妙之处在于:它没有强迫我们直接给出一个归一化的概率表达式,而是允许我们先随便定义一个打分函数(能量函数),最后再统一归一化。
2.2 配分函数:EBM 一切苦难的根源
配分函数这个量,是 EBM 与普通判别模型最大的区别,也是所有训练难题的根源。为什么难?因为你要对全空间所有可能的 x 求和/积分。在图像任务里,x 是 256×256×3 维的向量,全空间的大小是天文数字,精确求和是彻底不可能的。
更麻烦的是,配分函数本身还在参数 θ 的控制下不断变化。每更新一步参数,Z 都跟着变,而我们甚至很难估计它当前的值。这就导致 EBM 没法直接用标准的极大似然估计来做梯度下降——因为似然函数里面带着一个无法计算的归一化常数。
我记得刚学到这里的时候特别困惑:既然配分函数这么难算,为什么还要用这个框架?为什么不直接定义一个归一化的网络输出?答案有两个层面:
第一,能量函数的形式可以非常灵活,不受归一化约束。这意味着你可以轻易地融入各种结构约束、物理规律、对称性,这在很多科学问题里价值巨大。
第二,虽然精确计算配分函数不可能,但梯度的估计是可行的。绕开配分函数直接估计模型梯度,这就是对比散度(Contrastive Divergence)等一系列算法的出发点。
2.3 对数似然梯度:核心公式推导
极大似然估计的目标是最大化对数似然:
L(θ) = E_{x~P_data}[log P_θ(x)]
把 P_θ(x) = exp(-E_θ(x)) / Z(θ) 代进去,得到:
log P_θ(x) = -E_θ(x) - log Z(θ)
对 θ 求梯度:
∇_θ log P_θ(x) = -∇_θ E_θ(x) - ∇_θ log Z(θ)
关键在于第二项 ∇_θ log Z(θ)。做一个简单的变换:
∇_θ log Z(θ) = (1/Z(θ)) ∇_θ Z(θ) = (1/Z(θ)) ∇_θ ∫ exp(-E_θ(x)) dx = (1/Z(θ)) ∫ exp(-E_θ(x)) (-∇_θ E_θ(x)) dx = ∫ [exp(-E_θ(x)) / Z(θ)] (-∇_θ E_θ(x)) dx = -E_{x~P_θ} [∇_θ E_θ(x)]
把这一项代回原式:
∇_θ log P_θ(x) = -∇_θ E_θ(x) + E_{x~P_θ} [∇_θ E_θ(x)]
写成更对称的形式:
∇_θ L(θ) = E_{x~P_data}[-∇_θ E_θ(x)] - E_{x~P_θ}[∇_θ E_θ(x)]
这个公式是 EBM 训练的基石。它告诉我们两件事:
第一项是"把真实数据点的能量拉低",这是正相(positive phase); 第二项是"把模型采样出来的点的能量拉高",这是负相(negative phase)。
整个训练过程就是在玩一个拔河游戏:真实数据的能量往下压,模型幻想出来的数据的能量往上抬。最后平衡的时候,模型分布就等于数据分布。
从物理角度理解,第一项相当于让系统更倾向于停留在数据所在的低能区域,第二项相当于对系统的其他区域施加"排斥力",防止模型把概率质量摊得到处都是。
2.4 从物理视角看模型行为
统计力学的语言在这里非常好用。把 EBM 训练好的模型想象成一个能量地形图,数据点集中在若干"能量盆地"里,盆地之间的"山脊"能量很高。采样的时候,模型在热噪声的驱动下在地形图上漫游——它更愿意待在低能量的盆地,偶尔也会翻越山脊跑到另一个盆地。
这个画面比"神经网络输出一个概率"生动得多,而且在很多场景下更有解释力。比如在分子构象生成任务里,能量盆地对应的就是一个一个稳定构象;在图像生成里,能量盆地对应的是不同类别的图像流形。
实操中我特别喜欢用一个类比向别人解释 EBM:想象一个弹性势能场,数据点在底部安营扎寨,负相采样相当于往这个势能场里随机扔小球,看它们最终滚到哪里。训练的目标就是不断重塑这个地形,让小球最终总爱往数据点附近滚。
3. 能量函数的设计:从受限玻尔兹曼机到深度能量网络
3.1 经典选择:受限玻尔兹曼机(RBM)
说到 EBM 的历史,绕不开受限玻尔兹曼机(Restricted Boltzmann Machine, RBM)。RBM 是一个二部图结构:可见层 v 和隐藏层 h,层内无连接,层间全连接。它的能量函数定义为:
E(v,h) = -Σ_i a_i v_i - Σ_j b_j h_j - Σ_{i,j} v_i W_{ij} h_j
这里的 a、b 是偏置项,W 是可见层和隐藏层之间的权重矩阵。因为层内无连接,所以给定一个层,另一个层的条件分布是独立的,这给采样带来了极大的方便。
RBM 在 2006 年深度学习复兴的时候扮演过关键角色——Hinton 用它做逐层预训练,训练深度信念网络。我自己也从头写过 RBM 的代码,说实话,训练 RBM 比想象中要难,难在负相采样的质量。经典的做法是用对比散度(CD-k),即从训练数据出发做 k 步吉布斯采样来近似负相。
3.2 深度能量网络与现代架构
RBM 的线性结构表达能力有限,现代的 EBM 基本都用深度神经网络直接做能量函数。也就是 E_θ(x) = Net(x),输入 x,输出一个标量。网络内部可以是任意结构——卷积网络、Transformer、残差网络都可以。
但直接让网络输出一个标量,自由度太大了,很容易出现训练不稳定的情况。实践中常见的设计策略有:
第一种是去噪自编码器风格的能量。输入被加入噪声后,网络的目标是尽量输出一个能量值,让干净数据的能量低、加噪数据的能量高。这本质上是在让能量函数学会"分辨"干净信号和噪声。
第二种是基于得分(score)的视角。我们其实不关心能量的绝对值,只关心能量对输入的梯度 ∇_x E_θ(x),这个梯度叫得分函数。在很多生成方法(如 Langevin 采样)里,我们只需要这个梯度就足够完成采样,根本不需要算配分函数。这给了我们极大的设计自由——甚至可以让网络直接输出得分而不是标量能量。
第三种是对比学习式的思路。把能量函数设计成一种度量:正样本对的能量低,负样本对的能量高。这在一些度量学习和检索任务里非常好用。
3.3 能量函数的选择原则
这里分享一些我踩过坑之后总结出的经验:
能量函数不是越复杂越好。网络容量越大,能量地形越崎岖,负相采样就越容易陷入局部模式。如果你的生成任务不是特别复杂,一个中等规模的网络往往比一个超大网络效果更好。我试过一个 6 层的 MLP 做 MNIST 上的 EBM,效果竟然不比 ResNet 差太多,但训练稳定得多。
能量函数对输入的依赖方式很重要。如果你希望模型有平移不变性(比如图像任务),能量函数应该用卷积结构来构建,而不是把图像拉平后丢进全连接层。否则你需要海量数据来让模型自己学会平移不变性,这在能量模型里尤其难学。
注意能量函数的尺度。能量值的绝对大小会影响采样步长和温度参数的选择。我习惯在能量网络的最后一层加一个 tanh 或者把输出尺度限制在一定范围,这样采样超参数更容易调节。
3.4 一个简单的 EBM 网络实现
以 PyTorch 为例,一个用于图像的最小 EBM 可以这样定义:
import torch import torch.nn as nn class SimpleEBM(nn.Module): def __init__(self, input_dim=784, hidden_dim=256): super().__init__() self.net = nn.Sequential( nn.Linear(input_dim, hidden_dim), nn.Softplus(), nn.Linear(hidden_dim, hidden_dim), nn.Softplus(), nn.Linear(hidden_dim, 1) ) def forward(self, x): # 输入形状: (batch, input_dim),输出形状: (batch, 1) return self.net(x).squeeze(-1)注意这里激活函数用了 Softplus 而不是 ReLU。原因是 Softplus 处处可导且导数连续,对基于梯度的 Langevin 采样更友好。ReLU 在负半轴的梯度为 0,采样到这些区域时得分会失效,导致粒子原地卡住。
4. 训练 EBM 的核心难点:配分函数与对比散度
4.1 为什么不能直接做最大似然
我们已经看到,最大似然梯度的表达式非常干净:
∇_θ L(θ) = E_{P_data}[-∇_θ E_θ(x)] - E_{P_θ}[∇_θ E_θ(x)]
漂亮是漂亮,但第二项包含对模型分布 P_θ 的期望。要算这个期望,就得从当前模型采样——而这恰恰是需要配分函数的,因果循环了。
如果你硬算呢?把配分函数的数值估计出来,然后代入公式计算梯度。在低维空间里可以这么做,比如二维的高斯混合模型,用数值积分算 Z 没问题。但一旦维度升上去(图像就成千上万维),数值积分直接爆炸。所以我们必须创造一种避开精确配分函数的方法。
4.2 对比散度(CD):一个天才的近似
Hinton 在 2002 年提出的对比散度(Contrastive Divergence, CD)是历史上最成功的近似方案。
CD 的出发点是一个很实际的观察:我们不需要精确地从 P_θ 采样,只需要一个"方向大致正确"的负相样本来计算梯度。既然精确采样难,那我们从训练数据出发,做 k 步吉布斯采样(或 Langevin 采样),把得到的样本当作 P_θ 的近似样本。
关键在于初始化的选择:正相采样的起点是训练数据 x,而不是随机噪声。这意味着采样器只需要从数据点开始"漂移"一小段距离,就能反映出模型分布与数据分布在局部上的偏差方向。
CD-k 算法的流程是:
- 从训练集取一个 batch 的真实样本 x;
- 从 x 出发,执行 k 步采样(RBM 里是块吉布斯采样,连续 EBM 里是 Langevin 采样),得到负相样本 x';
- 计算正相梯度 ∇_θ E_θ(x) 和负相梯度 ∇_θ E_θ(x'),两者相减得到近似梯度;
- 用这个梯度更新参数 θ。
CD 的一个"细节魔鬼"是:从数据点初始化意味着采样永远倾向于停留在数据分布附近,对远端的高概率区域探索不足。这在实践中会导致模型学到的分布比真实分布"更尖锐"——它把概率集中在了训练数据的小邻域内,而牺牲了对整个流形的覆盖。
4.3 持久对比散度(PCD):把链子养起来
为了解决 CD 探索不足的问题,Tieleman 提出了持久对比散度(Persistent Contrastive Divergence, PCD)。思路也很妙:不要每次从数据点重新初始化采样链,而是维护一组持续运行的采样链(称为持久链),在每一步训练中让这些链继续采样几步,用它们得到的样本作为负相样本。
这样做的好处是:采样链有充分的时间在模型分布中游走,能探索到更远的区域;坏处是:当模型参数快速变化时,采样链可能跟不上参数的更新,导致负相样本"过时",梯度不准确。
PCD 在训练初期效果很好,但后期会遇到一个常见问题:采样链会逐渐收敛到某个模式附近,丢失多样性。这就是所谓的模式坍缩(mode collapse)现象在 EBM 训练中的体现。
我在实践中对 PCD 做过一个小改进:给持久链加入周期性重置。比如每 500 步训练,随机选取一部分持久链的状态,用随机噪声或某个训练样本重新初始化。这样可以避免链子陷入单一模式而长期无法摆脱的情况。
4.4 对比学习与 score matching:另辟蹊径
除了 CD 家族,还有两大类训练 EBM 的方法,各有各的适用场景。
Score Matching(得分匹配)的思路是:不直接优化数据的 log 似然,而是让模型的得分函数 ∇_x log P_θ(x) 尽量接近真实数据分布的得分函数 ∇_x log P_data(x)。因为真实分布的得分是未知的,要把它用可计算的形式替换掉。Score Matching 有一个漂亮的结论——目标函数可以化为一个只依赖模型得分梯度(Hessian)和模型得分值的期望式,配分函数完全消掉了。
这个方法的优点是稳定,不需要采样;缺点是 Hessian 计算代价高,而且它对分布的支持域假设比较严格。后来 Hyvärinen 又提出了 Sliced Score Matching,用随机投影近似 Hessian,大大缓解了计算压力。
噪声对比估计(NCE)的思路则是:既然配分函数难算,那就把配分函数当作一个额外参数来学。引入一个辅助噪声分布 p_noise(x),把问题变成二分类——判断一个样本来自数据还是噪声。这样配分函数被隐性吸收进了判别器的偏置项里。
NCE 的缺点是:噪声分布的选择对效果影响巨大。如果噪声分布离数据分布太远,判别任务太简单,学到的东西会很粗糙;如果太近,又难以区分。实践里常用的策略是用数据的扰动版本作为噪声分布,配合学习率退火,效果不错。
4.5 各种训练方法对比
为了帮你快速决策不同场景下选哪种方法,这里整理一个基于我实际经验对照表。
| 方法 | 核心思想 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| CD-k | 从数据点出发采样 k 步 | 简单、收敛快 | 对分布覆盖不足,容易出现模式坍缩 | 中小规模数据、RBM、快速原型验证 |
| PCD | 维护持久采样链 | 负相质量高、探索充分 | 采样链可能滞后或坍缩、超参数多 | 中等规模连续数据、图像 |
| Score Matching | 匹配模型与数据的得分函数 | 无需采样、稳定 | Hessian 代价高、对分布形式有要求 | 低维到中维连续数据 |
| Sliced SM | 随机投影近似 Hessian | 计算可控、可扩展 | 实现复杂 | 高维连续数据 |
| NCE | 与噪声分布做判别 | 无采样、思想简单 | 依赖噪声分布选择 | 分布有明确先验的场景 |
5. Langevin 采样:让能量变成样本
5.1 从能量到样本的物理过程
训练 EBM 不是终点,最终目的是从模型中采样生成新样本。由于配分函数未知,我们不能像其他归一化模型那样直接算概率,而是要用马尔可夫链蒙特卡洛(MCMC)的方法。
最常用的采样工具是 Langevin 动力学。它的更新规则是:
x_{t+1} = x_t - (ε/2) ∇_x E_θ(x_t) + √ε · z_t
其中 z_t ~ N(0, I) 是高斯噪声,ε 是步长。这个公式的物理含义非常清楚:第一项让粒子沿着能量下降的方向滑动(漂移项),第二项加入热噪声让粒子能够翻越能量壁垒(扩散项)。平衡状态下,粒子的分布恰好是玻尔兹曼分布 exp(-E/τ)。
注意:严格来说 Langevin 采样给出的分布是 exp(-2E/τ) 的形式,所以实际使用时步长和温度的关系需要小心处理。我在代码里通常会这样写:
def langevin_step(x, energy_fn, step_size=0.1, noise_scale=1.0): x.requires_grad_(True) energy = energy_fn(x).sum() grad = torch.autograd.grad(energy, x)[0] x = x.detach() - step_size * grad + noise_scale * torch.randn_like(x) return x5.2 采样步数与步长的博弈
Langevin 采样有一个核心矛盾:步长太大,动力学不稳定,粒子容易发散;步长太小,需要很多步才能从初始点走到高概率区域。而且 EBM 的能量地形通常很不均匀——有的区域平坦、有的区域陡峭,单一的学习率很难在所有区域都表现良好。
实践中我常用的策略是一个两步走的方案。第一步用较大的步长(比如 0.5)做 10-20 步"预热"采样,让粒子快速靠近低能量区域;第二步改用较小的步长(比如 0.05)做 30-50 步"精炼"采样,让粒子在高概率区域内部充分混合。这样可以兼顾效率和精度。
还有一个特别容易踩的坑:Langevin 采样对能量的绝对尺度非常敏感。如果能量函数输出的数值范围很大(比如几百到几千),那么梯度也会很大,即使步长很小也会一步跑飞。解决方法是给能量输出做归一化处理,或者使用自适应步长。我在自己的框架里用了一个简单的技巧:记录最近若干步的梯度均方根,如果过大就减小步长,过小就增大步长,效果相当不错。
5.3 从 RBM 到连续 EBM 的采样对比
RBM 因为层内无连接的结构,可以使用块吉布斯采样:先固定可见层 v,从 P(h|v) 采样隐藏层;再固定隐藏层 h,从 P(v|h) 采样可见层。这两步都只需要做独立采样,非常高效:
# RBM 条件采样伪代码 def gibbs_step(v, W, a, b): # 采样隐藏层 h_prob = torch.sigmoid(b + v @ W) h = torch.bernoulli(h_prob) # 采样可见层 v_prob = torch.sigmoid(a + h @ W.t()) v = torch.bernoulli(v_prob) return v连续 EBM 就没有这种便利,只能走 Langevin 路线。但和 RBM 的块吉布斯相比,Langevin 采样的优势在于可以处理连续变量,而且不需要设计条件分布,适用面广得多。
6. 实际应用场景与案例:EBM 在图像、科学计算与决策问题中的实战
6.1 图像生成与异常检测
EBM 在图像任务上最经典的应用之一,是把它当作一个可学习的能量地形,然后用 Langevin 采样生成图像。早期的深度 EBM 工作(比如 Yann LeCun 团队的论文)展示了在 CIFAR-10 和 MNIST 上可以生成合理的样本。
不过老实说,纯 EBM 的图像生成质量在相当一段时间内都不如 GAN 或扩散模型。它的优势更多体现在其他方面——尤其是异常检测。因为 EBM 天然地给每个输入打了一个"能量分",训练时正常样本的能量被压低,异常样本(模型没见过的类型)通常落在高能量区域。你不需要专门训练一个分类头,直接把能量当作异常分数就行。我做过一个工业质检的小实验:用正常产品图片训练 EBM,到了测试阶段,把有缺陷的产品图输入网络,能量值会明显偏高。这个方案在只有正样本、没有负样本的场景下特别好用,传统监督学习很难处理这种问题。
6.2 分子构象生成与科学计算
EBM 在科学计算领域有一个根正苗红的优势:很多物理系统本身就是能量模型。分子力场就是典型的能量函数,蛋白质折叠问题里用的也是能量地形。用 EBM 去学习分子数据,能量函数不仅是一个生成模型,还能被解释为一种"可学习的物理势能"。
我接触过的一个方向是分子构象生成:给定一个分子的化学式,生成它在不同温度下可能出现的三维构象。用 EBM 建模时,能量函数的输入是原子的三维坐标,输出是构象的能量。得益于 EBM 的物理可解释性,采样得到的构象天然符合玻尔兹曼分布,不同构象的出现频率大致正比于 exp(-E/kT),这在药物设计中特别有价值。
另一个让我觉得有潜力的方向是蛋白质设计。AlphaFold 预测的是结构,但怎样在序列空间中搜索能折叠成目标结构的序列,本质上是一个在能量地形上采样的过程。EBM 的框架在这里能自然地结合物理约束和实验数据。
6.3 与强化学习的结合:能量视角的策略表示
EBM 在强化学习里有一个不太为人知但潜力很大的用法——把策略(policy)表示为能量模型。具体来说,给定状态 s,动作 a 的条件能量是 E_θ(s, a),策略就是:
π(a|s) = exp(-E_θ(s, a)) / Z(s)
这样做的理由很实际:某些场景下最优策略是多模态的。比如一个机器人走到分岔路口,往左和往右都可以到达目的地,但政策梯度方法通常只能学到一个模式的分布(因为高斯分布是单峰的)。EBM 可以天然地表示多模态策略——能量地形上有几个盆地,就对应几个策略模式。
这类方法在模仿学习里也有应用,比如 Energy-Based Imitation Learning。把专家轨迹映射到能量地形上,专家动作落在低能量区域,非专家动作能量高,这比直接行为克隆更鲁棒,特别是在专家数据不完美的情况下。
6.4 一个最小可运行的训练循环
为了让你能快速上手,我提供一个完整的训练循环骨架。这里用最简单的 CD-1 思路:
import torch import torch.nn as nn import torchvision.datasets as datasets import torchvision.transforms as transforms # 数据加载 mnist = datasets.MNIST('./data', train=True, download=True, transform=transforms.ToTensor()) loader = torch.utils.data.DataLoader(mnist, batch_size=128, shuffle=True) # 初始化模型与优化器 model = SimpleEBM(input_dim=784, hidden_dim=256) optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) def langevin_sample(x, k=30, step_size=0.05): """从输入 x 出发做 k 步 Langevin 采样""" x = x.clone().detach().requires_grad_(True) for _ in range(k): energy = model(x).sum() grad = torch.autograd.grad(energy, x)[0] x = x.detach() + step_size * (-grad) + (2 * step_size) ** 0.5 * torch.randn_like(x) x = x.clamp(0, 1) # 像素值约束 return x.detach() # 训练循环 for epoch in range(20): for batch_idx, (data, _) in enumerate(loader): x_real = data.view(-1, 784) # (batch, 784) # 负相采样:从数据点出发做 Langevin 采样 x_fake = langevin_sample(x_real, k=10, step_size=0.1) # 计算正相和负相能量 e_real = model(x_real).mean() e_fake = model(x_fake).mean() # CD 损失:正相能量 - 负相能量 loss = e_real - e_fake optimizer.zero_grad() loss.backward() optimizer.step() if batch_idx % 200 == 0: print(f'Epoch {epoch} | Batch {batch_idx} | Loss: {loss.item():.4f}')注意上面langevin_sample里步长和噪声项的系数:我用了(2 * step_size) ** 0.5,这是因为 Langevin 更新的标准形式里噪声标准差是 sqrt(2ε)。不同论文里的系数有差异,你自己实现时务必保持一致性。
6.5 实战配置速查
| 任务类型 | 能量网络规模 | 采样步长 | 采样步数 | 训练方法 |
|---|---|---|---|---|
| MNIST 级别 | 2-3 层 MLP | 0.05-0.1 | 10-30 | CD-1 或 CD-5 |
| CIFAR 级别 | 4-6 层 CNN | 0.01-0.03 | 20-60 | PCD 或 Sliced SM |
| 分子构象 | 图神经网络 | 0.01-0.02 | 50-100 | Score Matching |
| 高分辨率图像 | 深度 ResNet | 0.005-0.01 | 100+ | 扩散模型式得分训练 |
7. 常见问题与排错实录:我在 EBM 实操中踩过的 8 个坑
7.1 训练发散:能量值一路冲上天
现象:训练过程中 loss 持续增大,能量值跑到几千几万,模型完全崩掉。
原因分析:负相采样的步长太大或步数太少,导致负相样本停留在高能量区域,梯度方向混乱;也可能能量网络的输出没有约束,数值尺度失控。
解决办法:
- 把能量网络的输出层换成 tanh 激活,把能量限制在 [-1, 1];
- 调小 Langevin 步长,增加采样步数;
- 检查数据预处理是否做了归一化(图片像素应放缩到 [0,1] 而不是 [0,255])。
这个坑我最初踩过好几次,后来养成了一个习惯:每次训练前先固定几个测试样本跑一次 Langevin 采样可视化,看能量值是否合理、采样结果是否像样,再做正式训练。
7.2 模式坍缩:生成结果永远只有一种
现象:采样生成的样本全都长得差不多,多样性极差。
原因分析:负相采样不够充分,或者持久链收敛到了一个能量盆地无法逃出。
解决办法:
- 使用 PCD 并周期性重置部分链子;
- 在负相采样的初始点中加入噪声甚至随机噪声,增加探索性;
- 增大 Langevin 采样的噪声系数(等价于提高采样温度);
- 调整模型容量,如果容量太大,能量地形过拟合到训练样本的尖峰上,很容易坍缩。
7.3 采样质量差:生成图像模糊或有伪影
现象:生成的图像整体能看,但细节模糊或出现奇怪伪影。
原因分析:能量函数对局部模式的刻画不够精准;负相采样步长太大导致粒子只能到达盆地的大致区域,无法细化到高概率的核心区。
解决办法:
- 采样时先用大步长预热、再小步长精炼;
- 在能量网络上增加跳跃连接(ResNet 结构)以保留更多原始输入信息;
- 检查是否有数据预处理环节引入的噪声。
7.4 训练很慢:Langevin 采样是瓶颈
现象:每个 batch 的训练都要做几十步 Langevin 采样,自动微分算梯度代价极高,训练速度比同等规模的 GAN 慢 10 倍以上。
原因分析:这是 EBM 方法的固有代价,无解。但可以优化:
- 减少 Langevin 步数,用 CD-1 代替 CD-10(效果下降但速度快很多);
- 用得分匹配类方法替代采样类方法(如果场景允许);
- 对能量函数的梯度做近似计算,比如每隔几步才重新计算梯度、中间用常梯度外推;
- 在 GPU 上并行处理多个采样链,充分利用批处理能力。
7.5 负相采样体感像随机噪声:能量地形没成型
现象:Langevin 采样出来的负相样本看起来完全不像数据,像纯噪声。
原因分析:模型还没有学到任何有意义的结构,能量地形还是平的,粒子的运动完全是随机游走。这通常是训练初期的正常现象,但如果持续很多 epoch 还是这样,就要检查学习率是否过大或能量网络是否太浅。
7.6 CD 与真实梯度偏差太大
现象:用 CD 训练收敛的结果,直接用真实梯度(用 bootstrap 近似)验证,发现分布差异很大。
原因分析:CD-k 在 k 很小时偏差显著,尤其是数据分布和模型分布差别大的时候。Hinton 的解释是 CD 在优化一个不同的目标函数(对比散度而不是负对数似然),不过实践中这个偏差未必有害。如果实在担心,建议用 PCD 或 Score Matching 交叉验证一下。
7.7 训练后期 loss 一直在震荡
现象:loss 在训练后期无法收敛,曲线像心脏跳动一样上下震荡。
原因分析:负相采样链的噪声太大了。Langevin 采样中的随机噪声在训练后期应该逐渐减小(模拟退火的效果)。
解决办法:让噪声系数随训练进度线性衰减,比如从 1.0 降到 0.1。这样做相当于逐渐降低采样温度,让模型在后期更精准地贴合数据分布。
7.8 MNIST 上经典 EBM 效果对照
这里附一份我自己复现实验时的典型结果对照(训练 20 epoch,batch size 128,Adam 优化器):
| 方法 | FID 分数(越低越好) | 训练耗时 | 备注 |
|---|---|---|---|
| CD-1 + MLP | 65-85 | 约 10 分钟 | 快速原型验证首选 |
| CD-10 + MLP | 45-60 | 约 20 分钟 | 质量与速度折中 |
| PCD + CNN | 30-40 | 约 35 分钟 | 稳定的中等质量 |
| Sliced SM + CNN | 25-35 | 约 40 分钟 | 无采样过程,但实现复杂 |
7.9 排查步骤速查表
| 症状 | 第一步检查 | 第二步检查 | 第三步尝试 |
|---|---|---|---|
| loss 爆炸 | 能量值尺度 | 采样步长 | 减小学习率 |
| 生成模糊 | 采样步数 | 网络深度 | 预热+精炼方案 |
| 模式坍缩 | 采样链多样性 | 温度系数 | PCD + 链重置 |
| 训练太慢 | 采样步数 | 网络规模 | Score Matching |
| 不收敛 | 学习率 | 数据归一化 | 换优化器(AdamW) |
8. EBM 背后的数学:为什么对偶、几何与拓扑视角如此重要
8.1 能量模型与指数族分布的关系
从统计学的角度看,EBM 本质上是在定义一个指数族分布:
P_θ(x) = exp(-E_θ(x)) / Z(θ)
不过 EBM 与经典指数族分布有一个关键区别:经典指数族分布的能量函数是线性参数化的(比如高斯的能量是二次型),而 EBM 的能量函数是非线性、深层参数化的。这使得 EBM 的表达能力远超经典指数族,但也让配分函数彻底失去了闭式解。
一个有趣的理论结果表明,EBM 的本质是一个"学习过的物理势能场"——它对数据的建模方式不是直接给概率密度一个解析表达式,而是搭建了一个经典粒子在其中运动的势能地形。这个视角让 EBM 区别于其他生成模型的地方一目了然。
8.2 能量面视角下的学习动态
我特别想强调一点:用能量地形来思考 EBM 的学习过程,比盯着 loss 曲线要高效得多。
当训练开始的时候,能量地形基本是平的,所有点的能量都差不多。随着训练的推进,真实数据点附近开始出现盆地,负相采样点慢慢被推上高能量区。一个有经验的研究者应该关注的是:盆地之间的间隔是否清晰、每个盆地的"宽度"是否合适。
盆地太窄(能量只在极小邻域内低),说明模型过拟合了训练样本,缺乏泛化能力;盆地太宽(大片区域能量都低),说明模型区分度不够。
我通常用一个简单的可视化技巧来判断:把测试集的样本输入模型,看它们的能量分布。正常的模型,测试集样本的能量应和训练集样本的能量分布接近;如果测试集样本能量明显偏高,说明模型把训练样本"背下来"了,泛化性差。
8.3 EBM 与扩散模型的亲缘关系
近几年大热的扩散模型(Diffusion Model),实际上和 EBM 有着千丝万缕的联系。扩散模型在训练时拟合的是"得分函数"(即能量函数的负梯度),采样时走的也是 Langevin 动力学式的逐步去噪过程。从这个角度看,扩散模型可以理解为一种"动态 EBM"——它不再学习一个固定的能量函数,而是学习一系列从噪声到数据的得分函数。
这种亲缘关系让我对 EBM 的未来比较乐观:扩散模型的成功证明了"得分 → 采样"这条技术路线是可行的、可以扩展到高维和大数据的。EBM 作为一个更广义的框架,在能量函数可解释性和任务适应性上还有不少潜力可挖。
9. 写在最后的实践建议
9.1 从哪个玩具任务入手
如果你刚接触 EBM,我建议不要一上来就挑战 CIFAR-10 或高分辨率图像。先在 MNIST 或 Fashion-MNIST 上把完整流程跑通——数据加载、能量网络、Langevin 采样、CD 训练、生成可视化——然后逐步增加难度。
我这里有一个推荐的递进路线:
- 二维高斯混合数据(可视化能量地形和采样轨迹,直观理解算法行为);
- MNIST(验证生成质量和模型基本能力);
- Fashion-MNIST 或 CIFAR-10(挑战更复杂的分布,换用 CNN 结构);
- 特定领域数据(比如自己的业务数据集,此时你已经有足够经验做定制化调整)。
9.2 关键参数速查
| 参数 | 推荐范围 | 影响 |
|---|---|---|
| 学习率 | 1e-4 ~ 1e-3 | 太大发散,太小收敛慢 |
| Langevin 步长 | 0.01 ~ 0.1 | 太大不稳定,太小采样不足 |
| 采样步数 | 10 ~ 100 | 越多负相越准但越慢 |
| 噪声系数 | 与步长相关,约 sqrt(2ε) | 决定探索能力 |
| 批大小 | 64 ~ 256 | 负相梯度方差与计算速度的权衡 |
| 能量网络宽度 | 256 ~ 1024 | 表达能力与过拟合风险的权衡 |
需要注意,这些参数不是独立的。步长、噪声系数、温度三者紧密耦合,改动一个通常需要同步调整其他几个。
9.3 面向工业应用的建议
如果你的目标是工业落地,我的建议是优先试试 EBM 的异常检测应用,而不是纯生成任务。原因很简单:异常检测只需要能量值,不需要在高维空间采样,绕过了最困难的配分函数问题,训练稳定性和产出价值都很可观。
另一个比较现实的落地方向是与现有模型做组合。比如把 EBM 的能量分数作为其他系统的特征或约束项,而不是独立使用。我做过一个推荐系统的实验:用 EBM 给用户-物品对打分,虽然单独用效果一般,但把能量分数和协同过滤的特征拼接后,效果有明显的提升。
9.4 最后一点经验
我个人在实际操作中的体会是:EBM 的门槛不在理论上,而在工程调试。你可能会花很多时间调整采样步长、噪声系数、链的数量,却感觉效果始终差一口气。这是正常的。EBM 对超参数敏感程度远高于 VAE 或 GAN,但一旦你把整套调试流程捋顺,你会发现它在处理分布复杂、模态多样的数据时有不可替代的优势。
顺着这个方向继续扩展,后面可以写概率模型的统计力学理论第二篇——比如基于流的模型与最优传输的视角,或者从变分推断到平均场理论的对照。每一个主题挖下去,都能挖出不少有意思的东西。