做生成模型研究的朋友迟早会撞上这样一个概念只要给每一种可能性打一个“能量”分再通过玻尔兹曼公式转成概率就能把统计力学那套优雅的数学搬到机器学习里。这正是 Energy Based ModelEBM也是无数现代生成模型框架的精神源头。早年玻尔兹曼机、受限玻尔兹曼机RBM这么干Hopfield 网络这么干最近火起来的扩散模型本质上也可以看成一种带特殊结构的 EBM。我研究生阶段第一次见到“基于能量的模型”这个说法时整个人是蒙的——后来又花了很多时间踩坑、推公式、跑实验才慢慢把这条线索理清楚。这篇文章就记录我对概率模型统计力学视角的理解重点讲 EBM 的核心思路、训练难点和实操细节希望能帮那些刚接触这个方向、或者想从根本上理解生成模型底层逻辑的读者少走点弯路。1. 整体设计为什么“能量”能定义概率1.1 玻尔兹曼分布统计力学给机器学习的最大礼物我们先从物理背景说起。统计力学研究大量微观粒子的宏观行为核心结论之一就是系统处于某个微观状态 x 的概率正比于 exp(-E(x)/T)其中 T 是温度。温度在高的时候高能量状态也有机会出现温度趋近于零时系统几乎只待在最稳的低能量状态。机器学习一般把温度吸收到能量函数里写成更简洁的形式p(x) exp(-E(x)) / Z这里 Z ∫ exp(-E(x)) dx称为配分函数。一句话解释能量越低的状态出现的概率越大配分函数负责把右边归一化让所有状态的概率加起来等于 1。我第一次看到这个公式时觉得它平平无奇不过就是一个指数族分布。但后来意识到它的威力恰恰在于“能量函数”不用直接输出概率。你随便定义一个打分函数往指数里一放它就自动变成了一个合法的概率分布。这意味着我们不需要提前指定什么独立性假设也不用烦恼“概率值必须落在 0 到 1 之间”这种约束只要能量函数能对样本打分就行。那“能定义概率”解决的是什么实际问题回想一下生成模型的本质给一堆训练数据我们希望模型能从中学习到数据分布然后生成新的、看起来像真实数据的样本。自回归模型需要你一点点去预测下一个 token 或者像素这其实是强行对联合概率做链式分解VAE 需要搭一个显式的隐变量编码器和解码器。而 EBM 走的是另一条路——不分解联合分布只定义一个标量函数让所有状态通过指数映射自然形成概率分布。这种自由度是它最大的优势也是它最难伺候的地方。1.2 配分函数高维空间里那个看不见的“分母”配分函数 Z 虽然在公式里只是起了归一化的作用但它在高维空间里是几乎不可能精确计算的。原因很简单假设我们要建模一张 32×32 的灰度图像每个像素有 256 个取值所有可能状态的数目是 256 的 1024 次方这个数字大到宇宙都装不下。而 Z 是对所有可能状态求和或积分你根本别想穷举。你可能想问那怎么办不计算 Z 就不能得到概率没有概率又怎么做最大似然训练这就是 EBM 训练中最核心的矛盾。解决思路主要有两个方向第一虽然 Z 算不出来但在许多实际场景下我们不需要绝对概率。比如用能量函数给样本排序、“哪个样本更像是数据”只需要相对大小即可。第二在训练模型时对数似然的梯度可以拆成两项其中一项需要用模型自己分布下的样本去估计另一项可以直接由数据样本算出来。于是问题从“精确求归一化常数”变成了“从模型分布采样”。这个思路是整个现代 EBM 训练方法的地基。1.3 为什么不用显式密度反而要绕一个弯子直接定义一个归一化的概率密度 p(x) 不是更省事吗理论上如此但实践中很难。你先要决定这个密度函数长什么样。常见的做法是假设某种独立性结构比如把 x 的各维度用自回归的方式相乘起来p(x) ∏ p(x_i | x_1...x_{i-1})这样虽然归一化了但表达能力受限而且采样通常必须按固定顺序进行。EBM 的优雅之处在于它用一个未归一化的打分函数表达“亲和度”。就好比选房子你要评估一套房子适不适合住可以直接看地段、面积、采光这些指标而不需要先把全城所有房子的分数归一化成百分比再比较。需要真正归一化的时候你可以通过采样来间接逼近。另外很多实际问题中变量的结构很复杂可能是图像像素、图结构、组合对象。EBM 只需要你定义 E(x) 能对任意候选结构返回一个能量值结构本身什么类型并不限制。这给了模型设计者极大的自由度。2. 核心细节解析能量函数、自由能与似然梯度2.1 能量函数怎么设计既然一切信息都被压缩在能量函数里那 E(x) 的选择就直接决定了模型的能力边界。实践中常见三种设计方式第一种是物理启发式。比如经典的 RBM能量函数定义成可见层 x 和隐藏层 h 之间的二次交互E(x, h) -a^T x - b^T h - x^T W h其中 a、b 是偏置W 是连接权重。这种设计让 p(x|h) 和 p(h|x) 都能写成因子化的形式条件采样极其简单这也是 RBM 能高效用吉布斯采样训练的原因。第二种是距离/惩罚式。像早期的 Hopfield 网络或者一些连续型 EBM能量函数定义为样本 x 与某个原型/记忆模式之间的距离。能量越低说明 x 越接近某个存储的干净模式。这种设计在联想记忆和去噪任务里很好用。第三种是深度神经网络式。直接用神经网络把输入 x 映射成一个标量让网络自己学习 E(x) 的“地形”。比如现代基于能量的模型在图像生成上通常用一个卷积网络打分输入图像输出能量值。它的表达能力很强但也更容易过拟合和难采样。设计时要记住一个基本原则能量函数必须是“有界”的或者说不能允许能量在数据分布之外无限降低。否则模型会把概率无限集中到某个单点然后配分函数爆炸。这也是我后来处理 EBM 项目时最先检查的点——能量下界和上界是否可控。常见做法是给能量函数加一个 Lipschitz 约束或者让最后几层输出经过 tanh 缩放到某个区间。2.2 隐变量与自由能在很多 EBM 中除了可见变量 x还有隐变量 h。比如 RBM、深度玻尔兹曼机。这时模型分布其实是 p(x, h) exp(-E(x, h)) / Z而我们需要的是边缘分布 p(x) ∫ p(x, h) dh。你如果直接算这个积分又会碰到复杂的归一化问题。有一个很好的中间量叫自由能Free Energy定义是F(x) -log ∫ exp(-E(x, h)) dh于是 p(x) exp(-F(x)) / Z。这里 Z 还是那个配分函数但归一化的是关于 x 的状态空间。引入自由能的好处是它直接把隐变量带来的复杂性打包成了一个关于 x 的函数。在训练 RBM 时我们实际算的梯度里就包含自由能对参数的导数。自由能越低说明在给定这个可见样本的情况下模型能找到合适的隐变量组合来“解释”它。这个视角在工程上也很有用。比如你想分析模型在测试集上的表现不能用绝对概率因为 Z 未知但可以比较两个样本的相对自由能差异。差值大的地方就是模型认为“更像数据”的地方。2.3 对数似然梯度的正负博弈EBM 的训练目标通常是最大化观测数据的对数似然。对参数 θ 求梯度你能得到一个简洁但含义深刻的公式∂log p(x)/∂θ -∂E(x)/∂θ E_p(x’)[∂E(x’)/∂θ]注意最后一项的期望是对模型分布取的拆开看第一项是“把数据样本的能量往下拉”也就是让训练数据在能量地形中变得更低第二项是“把模型生成样本的能量往上推”也就是让那些模型自己认为重要的状态不要扎堆在低能量区。换句话说训练过程就是在数据样本和模型样本之间做一场对抗式的对比数据要更“低”模型样本要更“高”。理想平衡点就是模型分布和数据分布完全一致。但这个公式里最麻烦的地方是第二项的期望。它要求你从当前模型分布中采样而模型分布本身由能量函数决定。这个采样问题是整个 EBM 训练里最核心、最消耗计算量的地方。另一个视角是如果不做第二项只不断降低数据样本的能量会发生什么那模型会把所有概率质量集中到训练集副本附近生成出来的东西就是死记硬背的训练样本。用术语说就是分布坍缩。正负两项的博弈防止了这种坍缩也让分布有一定的熵。2.4 对比散度用一个短链糊弄过去既然要从模型分布采样最直接的做法是跑吉布斯采样或者朗之万动力学直到马尔可夫链收敛再收集样本作为负样本。但“收敛”在超高维空间往往意味着天文数字的运行步数现实中完全行不通。Hinton 提出的对比散度Contrastive DivergenceCD是个非常巧妙也有点“流氓”的近似每次训练从训练样本 x 出发只做 k 步吉布斯采样k 通常取 1然后把采到的样本 x_k 当作负样本。要点在于初始化不是随机的而是从真实数据点开始所以即使只走了 k 步也能得到一个和当前数据局部相关的负样本。这减少了负样本的方差让训练过程快速推进。从统计角度讲CD 已经不是真正的最大似然梯度了它是一个有偏估计。但实验证明用它训练 RBM 依然能够得到相当不错的特征表示。这里有一个我自己的体会CD-k 里的 k 不是越大越好。k 越大负样本越接近真实模型分布理论偏差越小但方差也会上升计算量线性增加。实际调参时往往 k1 就够了真正影响效果的反而是学习率和动量项。与之相对的是持续对比散度Persistent Contrastive DivergencePCD它维护一组跨训练步的持续马尔可夫链每一轮都接着上一轮的位置继续往前走。因为链长期保持负样本更可能覆盖模型的真实支撑区域缓解了 CD 对数据初始化的依赖。我在稍微复杂一点的 EBM 任务上都会优先选择 PCD 而不是 CD。3. 实操过程从理论到可以跑起来的训练管线3.1 一个最小可复现的 RBM 训练流程怕读者被抽象概念绕晕我先给一个非常具体的例子用 RBM 处理 0-1 二值数据比如手写数字的阈值化版本。能量函数是 E(x,h) -sum(a_i x_i) - sum(b_j h_j) - sum_{i,j} x_i W_{ij} h_j。这个模型的漂亮之处在于条件概率非常好算p(h_j1 | x) sigmoid(b_j sum_i x_i W_{ij})p(x_i1 | h) sigmoid(a_i sum_j W_{ij} h_j)用 Python 写训练主循环伪代码如下def train_rbm(X, hidden_size, lr0.01, epochs50, k1): n_visible X.shape[1] W np.random.normal(0, 0.01, (n_visible, hidden_size)) a np.zeros(n_visible) b np.zeros(hidden_size) for epoch in range(epochs): for batch in chunks(X, batch_size128): # 正相根据数据样本计算隐层后验 h_pos_prob sigmoid(b batch W) h_pos_sample (h_pos_prob np.random.rand(*h_pos_prob.shape)).astype(float) # 负相从数据样本出发做 k 步吉布斯采样 x_neg batch.copy() for step in range(k): h_neg_prob sigmoid(b x_neg W) h_neg_sample (h_neg_prob np.random.rand(*h_neg_prob.shape)).astype(float) x_neg_prob sigmoid(a h_neg_sample W.T) x_neg (x_neg_prob np.random.rand(*x_neg_prob.shape)).astype(float) # 梯度更新 positive_grad batch.T h_pos_prob negative_grad x_neg.T h_neg_prob W lr * (positive_grad - negative_grad) / batch_size a lr * np.mean(batch - x_neg, axis0) b lr * np.mean(h_pos_prob - h_neg_prob, axis0)这里需要注意几个细节梯度被 batch_size 归一化了但学习率也可以按照参数维度做缩放二值采样用随机数比较的方式在 numpy 里很快不过真正训练时价格挺高可以考虑用伯努利采样X 最好归一化到 0 到 1 之间避免数值不稳定。这段代码跑起来刚开始可能会有一种“能量在下什么降”的错觉。你可以打印每轮的平均自由能会发现真实数据和负样本的自由能差逐渐拉开模型开始学会区分真假样本了。3.2 连续型 EBM朗之万动力学的更新法则如果数据是连续向量比如图像像素、分子构象那么用离散的吉布斯采样就不太自然了。一个更通用的采样工具是朗之万动力学。它的更新公式很简洁x_{t1} x_t (ε/2) * ∇_x log p(x_t) sqrt(ε) * η其中 η 是标准高斯噪声。因为 log p(x) -E(x) - log Z对 x 求梯度时 log Z 是常数所以只需要计算 -∇_x E(x)。这个性质太关键了采样根本不需要知道配分函数。用随机梯度朗之万动力学SGLD从 EBM 采样时你甚至不需要完全收敛。每一步相当于“沿着能量下降方向走一小步再注入一点噪声”噪声防止系统卡死在局部能量极小值。我在实践中的体会是指定 ε 的时候要注意量级。如果 ε 太大采样轨迹直接跳飞能量地形摸不着如果 ε 太小链走半天还在原地。对于普通的连续数据ε 通常在 0.001 到 0.01 之间而且随着训练推进逐渐衰减。最稳妥的办法是像 Adam 那样给每个维度设置自适应步长或者用更高级的哈密顿蒙特卡洛HMC但 HMC 每步需要额外的介质动力学计算开销也更大。3.3 训练时的监控指标别只盯着损失曲线很少有教程告诉你EBM 训练时不能只看训练损失因为你几乎永远算不出精确的负似然值。我在实验中通常监控三个指标第一个是重建误差Reconstruction Error。对于 RBM给定数据 x你能采样出隐变量 h再用 h 重建出 x’然后算 x 和 x’ 之间的差异。但这个指标不是越接近 0 越好它反映的是模型对训练数据的压缩和重构能力只能侧面说明特征提取是否有效。第二个是自由能差距Free Energy Gap。定义数据样本的平均自由能与负样本的平均自由能之差。这个差距越大说明模型把真实数据和模型生成数据分得越开。如果这个差距在训练初期快速增大后面稳定在一个水平说明训练正常推进如果差距持续暴涨则很可能是过拟合或者能量函数失控要小心。第三个是样本可视化质量。定期从模型中采样把图像样本展示出来直接看生成结果的真实度。这很“土”但对生成模型来说比任何指标都直观。我经常见到数值指标不错但采样结果一团糟的情况最终还是要靠眼睛来判断。3.4 参数选型与常见配置参考很多初学者问我要默认参数。下面是我经过多次尝试后觉得比较稳的起步配置大家可以根据任务规模做缩放项目RBM / 小规模数据深度 EBM / 图像数据学习率0.01可衰减0.0001 ~ 0.001批量大小12864 或 32CD 步数 k1不用 CD通常用 PCD 100 步朗之万采样初始位置数据点随机噪声 一小步注入数据优化器SGD 动量Adam能量函数约束隐含单元数量适中加谱归一化或梯度惩罚刚上手时不要追求大型模型。RBM 在 MNIST 这种中等规模数据上隐藏层 500 个单元已经能学到很清晰的笔画特征。深度 EBM 则需要更多的工程细节比如动量、梯度裁剪、回放缓冲区Replay Buffer。回放缓冲区是个很好用的东西它把之前采样得到的负样本存起来下一轮训练时混入一些旧样本这样负样本的分布平稳很多训练不容易抖动。4. 常见问题与排查技巧实录4.1 能量值越训越高或者越训越低正常吗这不一定是 bug但需要分情形。如果你监测到模型样本负样本的能量持续上升同时训练数据能量也在上升那很可能出现数值溢出或优化器发散。此时第一步是降低学习率第二步是把能量输出再缩放一下比如在能量函数的最终输出前除以一个固定标度让梯度的量级更温和。反过来如果数据样本能量迅速下降负样本能量却跟不上损失变成一个很小的数字那就提示模型在尖峰化概率分布变得越来越集中于有限几个模式。这通常发生在能量函数表达力过强的时候。对策是减少负样本更新频率、增加噪声强度或者在能量函数中加入正则项鼓励它不要太“尖”。4.2 负样本采样质量很差全是模糊图像这是 EBM 训练中我最常遇到的问题。原因通常不在模型本身而在采样器步骤数太少或者步长不合适。我做过一个实验把朗之万动力学步数从 50 调到 1000生成图像的清晰度上升非常明显但训练时间也成倍增加。一个妥协方案是训练初期用短链、后期用长链或者逐渐增加采样步数。另外要小心噪声强度。朗之万中的噪声项本质上是随机探索没有它系统会直接跑到局部能量最低点但噪声太强又会让样本偏离低能量区域。你可以根据能量地形的实际尺度比如能量差的范围来调节噪声系数而不是机械地使用 sqrt(2ε)。4.3 离散数据的吉布斯采样怎么加速如果你处理的 RBM 或二值模型需要独立地采样很多二元变量矩阵运算虽然快但每次都要生成巨大的随机矩阵。大于 1 万维时可以用二项分布替代逐元素随机比较或者用 PyTorch 的 multinomial 抽样。更激进的做法是跳到二值潜在空间的 Langevin 变体比如对概率做松弛化处理。不过最实用的还是减少批量大小或者用 GPU 矩阵运算并行化。值得一提的是采样过程中的随机性会对梯度产生很大噪声。你可以通过增大采样批次来平均噪声或者对采样样本做中心化处理。踩过几次坑之后我现在一般会把负样本的采样批量设成训练批量的两倍效果比单纯调大学习率稳定得多。4.4 配分函数实在估计不出来有没有近似方案在很多生成样式的应用里你确实不需要估计 Z。但如果要做模型比较、计算近似的对数似然或者做退火重要性采样AISZ 就绕不过去了。退火重要性采样的原理是从一个容易计算 Z0 的初始分布出发逐渐插值到目标分布每一步用重要性权重累计最后得到目标配分函数的估计。它比直接蒙特卡洛抽样稳定得多但计算开销很大。我在做模型评估时默认用 AIS 估计 RBM 的配分函数然后给出一个“近似对数似然”。这个步骤几乎不可能在训练过程中实时进行只能作为实验结束后的最终评估。你也要注意 AIS 的插值路径选择路径节点越多估计偏差越小但稳定性和速度的取舍需要反复试。4.5 模型生成样本多样性太低是不是 mode collapse是的。虽然 mode collapse 常被提起的是 GAN但 EBM 只要采样器混合得慢同样会表现出生成样本单一的问题。对策有三个一是用 PCD 并配合回放缓冲区让负样本覆盖更广二是增加兰德初值采样的次数让采样链从多起点出发三是调整温度参数让采样时的分布更软增加模式的探索。如果在生成时允许你手动调“采样温度”那就可以稍微降低能量函数的缩放系数使分布更均匀。不过温度太高会让生成样本失去细节你需要找一个临界点。每个模型的最优温度不同我通常做一套小范围 grid search比如在 0.8 到 1.2 之间扫几档。5. 实战心得从 RBM 一路跑到现代 EBM我在自己项目中实践过几条路线。最早用 EBM 做无监督特征学习搭建一个 RBM在 MNIST 上训练然后用学到的表示去分类。后来尝试把深度卷积 EBM 用在图像生成上体验就和 RBM 完全不同——采样难、训练缓、调参烦但最终生成的图像细节确实比简单的 RBM 好很多。如果让我给刚入门的朋友一个建议先把 RBM 的 CD 训练彻底搞懂包括正负相的含义、条件采样的推导、监控指标怎么看。理解 RBM你就能理解 90% 的 EBM 思想。然后再加上朗之万动力学把连续性问题解决掉。至于更高级的分数匹配Score Matching和扩散模型它们本质上也是能量函数的另一种变体——学习的不再是 E(x) 本身而是 E(x) 关于 x 的梯度。名字虽然复杂内核却是同一套让数据点尽量低让模型样本尽量高只不过“高低”的判断方式换成了梯度的方向而已。最后再分享一个小技巧调试 EBM 时先写一个简单得不可能出错的能量函数比如 E(x) λ * ||x - c||^2用已知正态分布数据去训练。如果模型能够成功恢复出 c 的位置和数据的方差说明整个训练管线是通的。再去换成神经网络能量函数。这种“从简单到复杂”的调试顺序帮我节省了大量排查时间。大家在复现代码时也建议这样做先把框架跑顺再去追求模型效果。