多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

能量模型(EBM)全解析:从玻尔兹曼分布到训练实战

能量模型(EBM)全解析:从玻尔兹曼分布到训练实战 我第一次系统性接触 Energy Based ModelEBM是在读 Hinton 那批玻尔兹曼机论文的时候当时整个人是懵的概率模型为什么非要叫“能量模型”统计力学里的配分函数、自由能这些东西跟机器学习里的损失函数、归一化常数有什么关系后来把统计力学的框架当作一个翻译器很多概念才真正顺起来。这篇是“概率模型的统计力学理论”系列的第一篇先只讲地基能量函数到底在算什么、EBM 这个家族有哪些成员、训练时为什么配分函数会成为拦路虎、以及四种主流训练方法的直觉和取舍。最后我会用一个 PyTorch 的 RBM 实例把整套流程串起来再附上我实际操作中踩过的坑。适合两类人一类是学生成模型但被 EBM 数学劝退的朋友另一类是物理背景想转机器学习、想搞清楚“能量”“玻尔兹曼分布”在模型里到底怎么落地的朋友。1. 从统计力学到概率模型能量函数到底在计算什么1.1 玻尔兹曼分布把概率理解成“地势”EBM 的核心思想只有一句话每个状态比如一张图片的概率由它的能量决定。能量越低概率越高。这个关系写成公式就是玻尔兹曼分布$$p(x) \frac{e^{-E(x)}}{Z}, \quad Z \int e^{-E(x)} dx$$这个式子来自统计力学原本是描述物理系统在热平衡下处于某个微观状态的概率。机器学习的先辈们直接借用了这套语言把“一个系统有多少种排列方式”换成了“一个样本有多合理”。能量的绝对值不重要重要的是相对高低。这就像看地形我们不关心海拔到底是多少米只关心哪里是盆地、哪里是山脊。概率高的样本天然就落在能量函数的地势低洼处。我试过用重力势能来类比效果很好一个球在山上滚总是更倾向于待在谷底。EBM 就是给数据空间画了一张“山势图”然后让真实数据全部滚进低谷里。生成新样本的过程就相当于往这张图上撒豆子让豆子在概率高的地方聚集。这个视角和判别模型完全不同。分类器关心的是边界给定输入输出标签EBM 关心的是整个分布的形状必须对任意输入都能给出一个能量值哪怕这个输入从来没出现过。所以 EBM 本质上是生成模型而且是“无向图模型”那一支。它不要求显式写出条件依赖关系定义非常自由。1.2 配分函数一个又美又麻烦的归一化常数能量函数好定义但概率密度必须归一化于是 Z 出现了。Z 在统计力学里叫配分函数在概率论里就是归一化常数$$Z(\theta) \int e^{-E_\theta(x)} dx$$这个积分看起来干净实际上几乎永远算不出来。图像数据集动辄几万维你想遍历所有可能像素组合去求积分是天文数字。更麻烦的是Z 还依赖模型参数 \theta每次参数更新它都会变训练过程中根本没法精确追踪。我刚开始研究时一直有一个误解以为配分函数只是个可有可无的“常数项”MLE 求导的时候反正会被消掉。确实对 x 求导消掉很容易但对参数 \theta 求导时 Z 还会以期望的形式出现这就是后面会讲到的“负相”问题。可以说整个 EBM 训练史就是一部“如何绕开或近似配分函数”的历史。所以说EBM 的美感在于它用一套极简的物理语言统一了模型表达而它的痛苦也在于 Z 这个幽灵一直挂在训练过程的头顶上。1.3 为什么用能量定义概率而不是直接拟合概率人们可能会问既然这么麻烦为什么不用神经网络直接输出概率核心原因有两个。第一概率要满足非负且总和为 1 的约束直接在输出层施加这些约束很不方便能量函数只需要输出一个实数约束少网络结构可以非常灵活。第二很多物理直觉能直接搬过来低能量区域对应高概率系统可以通过“退火”慢慢进入稳定态比如在采样时控制温度让马尔可夫链更容易探索到低谷。另外能量定义天然支持“组合”和“对比”。比如判别式 EBM 可以把“分类”实现为每种类别一组能量选择能量最小的类别结构化 EBM 可以把约束条件写进能量项比如给不出合理分割的图像加惩罚。这种自由是直接拟合概率很难做到的。2. 一张图认识 EBM 家族从玻尔兹曼机到扩散模型2.1 古典时期RBM、DBM、DBN 的分工提起 EBM绕不开的第一站是玻尔兹曼机BM由 Hinton 和 Sejnowski 在 1980 年代提出。原版玻尔兹曼机允许可见单元和隐藏单元之间、甚至隐藏单元内部都有全连接训练时要用缓慢的模拟退火实用价值很低。后来 Hinton 把连接做限制提出了受限玻尔兹曼机RBM可见层和隐藏层各自内部没有连接只有层间连接这样条件概率就能独立分解吉布斯采样变得非常高效。RBM 最常见的理解方式有两种一种是能量模型一种是从编码器-解码器的角度理解隐藏单元相当于对输入做特征抽取。但真正让我觉得“物理视角有用”的是它的训练过程——对比散度里“用数据初始化马尔可夫链”这一步。这就像给一个热力学系统注入一个真实样本作为初始条件然后观察它如何演变。RBM 堆叠起来就是深度置信网络DBN和深度玻尔兹曼机DBM。前者更像逐层贪心预训练得到的多个 RBM 的堆叠后者则把所有隐藏层放进一个联合能量函数里进行联合训练。它们当年是深度学习爆发前的重要预训练手段后来被端到端的反向传播替代但 RBM 本身依然是一个干净到可以用来学习 EBM 训练的教科书模型。2.2 文艺复兴Score-based 模型与现代 Hopfield 网络近几年 EBM 的“文艺复兴”来自两个信号。第一个是 score-based 生成模型也就是扩散模型的主流理论框架。得分函数定义为对数概率密度对输入的梯度对于 EBM 来说$$\nabla_x \log p(x) -\nabla_x E(x) 0 -\nabla_x E(x)$$因为配分函数对 x 求导会消失所以我们对“分数”的建模本质上就是对能量函数梯度的建模。扩散模型里的 U-Net 就是去拟合这个梯度采样时用郎之万动力学迭代等价于在能量函数上做随机梯度下降加噪声。从这个角度理解扩散模型可以看作一个特殊参数化、特殊训练的 EBM。只不过它的能量函数不直接输出标量而是通过输出得分间接定义能量。第二个信号是现代 Hopfield 网络。经典 Hopfield 网络就是能量模型用 Hebbian 规则存记忆2020 年提出的现代 Hopfield 网络把能量函数改成带 log-sum-exp 的形式更新规则推导出来竟然就是 Transformer 里的 attention softmax 机制。这件事非常漂亮注意力机制可以被解释成在能量空间中检索记忆。EBM 的容器远比我们想象的大。2.3 和 VAE、GAN、Flow 摆在一起看区别学生成模型时最怕的是每个模型学一套独立逻辑学完就忘。放到能量视角下看会清晰很多模型如何定义概率核心难点和 EBM 的关系VAE隐变量 显式似然下界近似后验分布、重参数化把能量换成负对数似然归一化天然好算GAN不直接定义概率对抗训练不稳定判别器可以理解为能量函数的一种代理Normalizing Flow显式可逆变换精确似然网络结构受限限制极多能精确算 ZEBM能量 配分函数Z 不可计算最通用什么结构都能塞进能量函数这么一比就明白EBM 是“最省约束”的模型它把所有难度都集中到了训练算法上。真正实用化的关键不是模型结构而是能不能发明出有效的近似配分函数方法。3. 训练 EBM 的那堵墙还有四面开墙的锤子3.1 最大似然梯度里的正相与负相训练 EBM 的常规目标是最大似然估计MLE。对参数 \theta 求对数似然梯度会得到一个非常经典的两项形式$$\nabla_\theta \log p_\theta(x) -\nabla_\theta E_\theta(x) \mathbb{E}{p\theta}[\nabla_\theta E_\theta(x)]$$前一项叫正相positive phase作用是把训练数据点的能量梯度拉低让数据落入低能区后一项叫负相negative phase作用是从当前模型分布中采样一批“负样本”把它们的能量梯度推高避免模型把所有区域都压成低能量。没有负相模型会崩溃成把所有输入统一给低能量的平凡解。这个概念和物理里的“温度与粒子数守恒”很像正相是让模型记住数据负相是让模型理解数据空间里哪些地方不应该有高概率。两个 force 相互对抗最终达到平衡。理解这一项后面对比散度、PCD 什么都很好讲。问题在于负相需要从 p_\theta 中采样而 p_\theta 本身带有不可计算的 Z没法直接采样。于是所有训练算法的本质都在问同一个问题怎么得到一个尽量贴近当前模型分布的样本或者完全避开对分布的依赖。3.2 对比散度 CD-k用数据给马尔可夫链点火Hinton 在 2002 年提出的对比散度算法是 EBM 训练最实用的一招原理特别颠覆做吉布斯采样时不要把马尔可夫链跑到收敛而是直接从训练样本出发只跑 k 步一般 k1 就够了。为什么 k1 就够因为负相的目的不是精确采样 p_\theta而是提供“数据区域旁边的对照样本”。数据点已经靠近低能量区跑一步吉布斯采样得到的样本就在数据点附近它能告诉模型“这个邻域内的能量低了得推回去”。一开始跑很多步反而浪费时间训练早期模型分布很差链跑到哪都不知道。CD-k 的梯度更新可以写成$$\Delta W \approx \langle v h^T \rangle_{data} - \langle v h^T \rangle_{recon}$$其中第一项来自真实数据第二项来自“重建样本”。我在实践中的经验是CD-1 对大多数任务足够但训练后期如果发现能量值一直在爬可以试试 CD-3 甚至 CD-10也可以切换到 PCD持续对比散度让每个 batch 的马尔可夫链在上一个 batch 的状态上继续演化相当于维护一组持续刷新状态的“负样本池”。3.3 得分匹配不再碰配分函数直接对齐梯度既然配分函数是恶魔那有没有办法根本不需要它有得分匹配就是一个。它的思路是不直接比较分布而是比较对数密度对输入 x 的导数也就是“分数”。对于 EBM分数函数就是负的能量梯度$$s_\theta(x) \nabla_x \log p_\theta(x) -\nabla_x E_\theta(x)$$配分函数对 x 求导是零所以 Z 彻底消失。目标变成让模型的分数函数尽量接近真实数据分布的分数函数。真实分数我们不知道但 Hyvärinen 在 2005 年证明在适当边界条件下这个目标可以等价转化为$$J(\theta) \mathbb{E}{p{data}} \left[ \frac{1}{2} | s_\theta(x) |^2 \text{tr}(\nabla_x s_\theta(x)) \right]$$这个式子里的迹需要算模型对输入的二阶梯度高维时很昂贵。后来的切片得分匹配、降噪得分匹配解决了这个问题尤其是降噪得分匹配把目标变成了“预测噪声”这就是扩散模型训练能稳定跑起来的核心原因。所以你想扩散模型之所以大杀四方很大程度上是因为它找到了一个工程上极友好的 EBM 训练方案。3.4 噪声对比估计把归一化常数变成判别器任务第四种常用流派是噪声对比估计NCE。它的思路比较“偷懒”我不直接算 Z而是把 Z 当成一个可学习的标量参数然后训练一个二分类器问“这个样本是真的来自数据还是来自我们设计的噪声分布”。贝叶斯推理下来模型会学到能量函数的形状Z 会收敛到真实值的附近。NCE 和 GAN 的判别器有一点神似但理论来源完全不同。我在调 NCE 时最常踩的坑是噪声分布选得不好。噪声分布和真实分布重叠太少分类器太容易分对梯度信号很小重叠太多模型会花力量去区分那些没意义的样本。一般推荐用真实数据的某种扰动或者混合多个尺度的高斯噪声。3.5 四种算法的适用场景对比算法是否依赖采样实现难度典型场景最大似然高需要精确采样高理论分析、小规模问题CD-k / PCD中短链采样低RBM、DBM 等经典 EBMScore Matching低不采样中扩散模型、连续数据密度估计NCE低噪声采样中自然语言模型、配分函数温和的场景实际操作中我一般这样选如果模型结构简单、采样便宜用 CD-k 最省事如果做图像等连续高维数据直接走得分匹配路线如果主要目的是表征学习而非精确密度估计NCE 非常稳。没有万能钥匙关键是知道每把锤子到底在敲哪颗钉子。4. 从零写一个能跑的 EBMPyTorch 训练 RBM 实录4.1 模型定义与能量表达理论说了半天不跑一遍等于没学。我挑 RBM 作为演练对象因为它结构最简单能量函数能手写采样也能精确实现。我使用的是高斯-伯努利 RBM可见层是连续像素隐藏层是二值神经元。能量函数写成$$E(v,h) \frac{1}{2}|v - b|^2 - c^T h - v^T W h$$对应的条件概率非常干净$$p(h_j1 \mid v) \sigma(c_j W_j \cdot v)$$$$v \mid h \sim \mathcal{N}(b W h, I)$$代码骨架如下import torch import torch.nn.functional as F from torch.utils.data import DataLoader from torchvision import datasets, transforms class GaussianBernoulliRBM: def __init__(self, n_vis28*28, n_hid512): self.W torch.randn(n_vis, n_hid) * 0.01 self.b torch.zeros(n_vis) self.c torch.zeros(n_hid) def sample_h(self, v): # p(h1|v) sigmoid(v W c) p_h torch.sigmoid(v self.W self.c) h torch.bernoulli(p_h) return h, p_h def sample_v(self, h, sigma1.0): # v | h ~ N(b Wh, sigma^2 I) mean self.b h self.W.t() v mean sigma * torch.randn_like(mean) return v def free_energy(self, v): # F(v) -log sum_h exp(-E(v,h)) bias_term 0.5 * torch.sum(v**2, dim1) - torch.sum(v * self.b, dim1) hidden_term torch.sum(F.softplus(v self.W self.c), dim1) return torch.mean(bias_term - hidden_term)4.2 CD-1 训练循环与参数选择训练循环就是“数据采样、一步吉布斯、用样本差更新参数”三段式def train_cd1(model, train_loader, epochs30, lr0.1, momentum0.9, weight_decay1e-4): params [model.W, model.b, model.c] velocity [torch.zeros_like(p) for p in params] for epoch in range(epochs): epoch_loss 0.0 for v0, _ in train_loader: v0 v0.view(v0.size(0), -1) h0, _ model.sample_h(v0) v1 model.sample_v(h0) h1, _ model.sample_h(v1) # 对比散度梯度 positive v0.t() h0 / v0.size(0) negative v1.t() h1 / v1.size(0) delta_w positive - negative delta_b torch.mean(v0 - v1, dim0) delta_c torch.mean(h0 - h1, dim0) # 手写动量更新避免框架 autograd 干扰理解 for param, vel, delta in zip(params, velocity, [delta_w, delta_b, delta_c]): delta delta - weight_decay * param vel.mul_(momentum).add_(lr * delta) param.sub_(vel) epoch_loss F.mse_loss(v1, v0).item() if (epoch 1) % 5 0: print(fepoch {epoch1:02d}, recon_loss{epoch_loss/len(train_loader):.4f}) # 数据MNIST 像素归一化到 [0,1] transform transforms.Compose([ transforms.ToTensor(), lambda t: t.view(-1) ]) train_set datasets.MNIST(root./data, trainTrue, downloadTrue, transformtransform) train_loader DataLoader(train_set, batch_size128, shuffleTrue) model GaussianBernoulliRBM() train_cd1(model, train_loader, epochs30)超参方面我的推荐起点是学习率 0.1 到 0.2动量 0.9权重衰减 1e-4隐藏单元数量 256 到 512batch size 128。千万别上来就把学习率设成 1e-3 这种“深度学习默认值”RBM 的训练节奏和普通神经网络不一样大学习率配合动量才是常态。4.3 怎么判断训练有没有成功RBM 没有现成的“loss”可以单调下降我判断训练好坏一般看三样东西。第一个是重建误差就是代码里直接打印的 MSE。训练早期它应该快速下降说明模型开始捕捉数据的基本结构。但它降到一定程度会波动这是正常的不用焦虑。第二个是可视化权重模板。把 W 的每一列 reshape 成 28x28 的图像如果训练成功你能看到各种笔画形状和局部特征像一堆“幽灵数字部件”。如果模板是纯噪声说明训练崩了或者学习率过小如果所有模板几乎一模一样说明模型容量不够或正则太强。第三个是能量对比。用模型分别计算真实图像和随机图像的自由能理想情况下真实图像的能量明显更低。这个指标比重建误差更贴近 EBM 的本体目标。我在调试时经常画自由能的直方图把它当作训练信号来用。5. 踩坑实录与调参笔记我替你们趟过这些坑5.1 最常见的七个问题与对策实际操作 RBM 和 EBM 时会遇到一些让人抓狂的状况。我把最常遇到的整理成一个速查表症状可能原因对策重建误差不降学习率太小、隐藏单元太多调大 lr 到 0.1检查数据归一化权重模板是白噪声梯度爆炸训练发散降低 lr增大 weight_decay检查数据 batch 大小模板“糊成一团”模型容量不足或训练不充分增加隐藏单元延长 epoch提高动量能量一直震荡上升CD 步数太少负相采样质量差试试 CD-5 或 PCD检查 lr 是否过大采样图像全是模糊过度高斯可见层 sigma 太大把 sigma 调到 0.3 到 0.5或用伯努利 RBM训练到后期几乎不动马尔可夫链状态停滞切换 PCD增加负相样本数量显存不够对比散度需要同时保存多组样本减小 batch size或者用更低分辨率输入这里面最隐蔽的一个问题是“假收敛”模型自由能在下降但采样质量越来越差。我遇到过一次后来才发现是 CD 链跑太久负样本和正样本高度相关模型只顾着局部微调全局地形完全没学对。这种时候把吉布斯步数调回 1或者增加负样本池的多样性通常能救回来。5.2 容易被忽略的细节与经验有几个细节论文里通常不会强调但我每次手写 EBM 都会先检查第一权重初始化不要用大数字。RBM 的权重初始化最好控制在正负 0.01 左右否则早期梯度会直接把 sigmoid 推到饱和整个训练卡死。第二动量是 RBM 训练的定海神针。我一开始不懂为什么 CD 梯度明明是个随机量还要配那么高的动量后来才意识到CD 的梯度噪声非常大不靠动量平滑参数路径完全没法收敛。第三权重衰减要加但不要加太多。它在这里不只是防止过拟合更重要的是防止负相能量被“推爆”。权重太大时部分样本会被孤立到极高的能量上梯度计算会不稳定。还有一个经常被忽略的是温度。训练 EBM 其实是在找一个室温T1下的平衡分布但实际模型可能有多峰结构。采样时如果感觉样本总在少数几个模式之间跳来跳去可以试试先高温采样、再慢慢退火这就是模拟退火的思想。它虽然不直接提升训练但能让测试时的生成质量明显更均匀。5.3 什么情况下该换更重的工具如果你的数据不是 MNIST 这种小图而是真实高分辨率图像直接用朴素 RBM 会非常痛苦因为吉布斯采样在高维连续空间里很难快速混合。这时候不要恋战直接换成扩散模型那一套用 U-Net 作为得分网络训练目标用降噪得分匹配推理用郎之万采样。它本质上还是 EBM 的地形换的是更强大的能量函数无参形式和更高效的训练算法。反过来如果数据是二值型的、维度不高、需要可解释特征那 RBM 依然是很棒的选择它训练快、易控制、可视化强。说到底技术选型是在“模型表达能力”和“训练可得性”之间取舍EBM 家族的成员是在这条光谱上的不同取点。6. 这套物理视角还在扩张我的个人体会6.1 统计力学不是数学化是翻译器我学 EBM 最大收获是把很多零散概念连成了网。比如“互信息”可以和“自由能”对应“退火”可以有效帮助采样“相变”能解释模型在训练中的突然崩塌。以前我看到能量函数只知道它是个损失函数现在会去想它画出来的“地形”长什么样——哪里有盆地、哪里是峭壁。这个视角一旦建立再看扩散模型、现代 Hopfield 网络都不会觉得它们是全新事物只是同一座山的不同坡面。6.2 后面我打算接着写什么这一篇只写了“如何用能量定义概率”和“如何训练这种概率模型”但 EBM 的另一个重要分支是推断给定观测怎么算出隐变量的后验那里会用到平均场理论、变分自由能还有更复杂的结构化能量模型。下一篇我想把“自由能最小化”讲透顺便聊聊它对理解现代深度学习的意义。如果你们对哪个部分更感兴趣也可以按着评论区的方向来调整后面几篇的侧重。
返回列表