资讯动态

得分匹配:绕过配分函数,训练能量模型与扩散模型的核心技术

发布时间:2026/9/16 3:47:30 来源:尧图企业网站定制
做生成模型的朋友应该都对一道坎不陌生你花心思设计了一个漂亮的概率分布可一到训练就卡在**配分函数partition function**上。它像是一只看不见的手拽着所有梯度计算让你要么退回去做马尔可夫链蒙特卡洛采样要么提着灯笼找各种近似估计。而Score Matching得分匹配这条技术路线恰恰绕过了这个坎——它不拟合概率本身改去拟合对数概率密度的梯度。这条路安静了好多年近两年因为扩散模型大热又重回舞台中央。这篇内容我会从得分匹配要解决的核心问题讲起把目标函数推导背后的直觉讲透对比三种主流实现变体原始版、切片版、去噪版再给出一份可以跑的 PyTorch 实操代码最后聊聊我在实验中踩过的坑。适合正在学能量模型、扩散模型或者想给非归一化概率模型找一条稳定训练路子的朋友参考。1. 得分匹配到底在解决什么问题1.1 配分函数——生成模型的“房间里的大象”先回忆一下最常见的概率建模套路。假设我们对数据分布感兴趣建立一个参数化模型[ p_\theta(x) \frac{1}{Z(\theta)} \exp(-E_\theta(x)) ]这里的 (E_\theta(x)) 叫能量函数模型的形状和表达能力全在它身上。问题在于为了让 (p_\theta(x)) 积分等于 1需要计算[ Z(\theta) \int \exp(-E_\theta(x)) dx ]这个积分在绝大多数情况下没有解析解而且随着 (x) 的维度升高数值积分根本不可行。这就是配分函数问题。传统上用最大似然估计训练这样的模型需要对 (Z) 求导这又把问题逼向了 MCMC 采样。MCMC 本身不是不能用但它在高维空间里存在“混合慢”“诊断难”“方差大”一系列毛病。用我自己的话说你花了三分之一的时间在调试采样器三分之一的时间在等它收敛剩下三分之一才真正在调模型。1.2 换个思路不拟合概率拟合它的梯度Score Matching 的核心思想在第 2 节推导之前先给大家一个直觉。我们把目标从“让模型分布接近真实分布”换成“让模型分布的得分函数接近真实分布的得分函数”。得分函数定义为对数概率密度关于输入的梯度[ s(x) \nabla_x \log p(x) ]为什么是梯度因为在对数概率密度上求梯度时配分函数作为常数项直接消失了[ \nabla_x \log p_\theta(x) -\nabla_x E_\theta(x) - \nabla_x \log Z(\theta) -\nabla_x E_\theta(x) ]这一下子就捅破了窗户纸——我们完全可以定义一个目标函数让它去匹配 ( \nabla_x \log p_{data}(x) ) 和 ( \nabla_x \log p_\theta(x) )而操作过程中根本不需要计算 ( Z )。这是 Score Matching 的第一层价值梯度匹配绕开了配分函数。1.3 适用场景与边界条件Score Matching 最适合什么场景我个人的经验是你手里有一个自定义能量函数不想限制网络结构也不需要保证输出是合法的概率值你只需要模型能给出“哪个方向修改样本能提高概率密度”的梯度信息你想在连续型数据上训练生成模型且数据维度不算太低你已经决定走扩散模型路线score 网络本来就是你需要的组件。它也有明显的边界条件。第一数据必须在连续空间上离散数据要应用得先做连续化处理。第二真实分布的支撑集必须覆盖整个空间实际中几乎不成立这是后面加噪方法的动机来源。第三它对数据流形的低维性很敏感——真实图片只有高维空间里的一块低维流形上概率非零在这块流形之外得分根本没有定义或者趋于无穷大。这个问题我们在第 5 节还要展开。2. 核心原理与目标函数推导2.1 从 Fisher 散度到可计算的目标Score Matching 的出发点是一种叫做Fisher 散度的度量[ J(\theta) \frac{1}{2} \int p_{data}(x) \left| \nabla_x \log p_{data}(x) - \nabla_x \log p_\theta(x) \right|^2 dx ]直觉上这个式子在做的事是在每个数据点 (x) 处比较真实分布的“梯度箭头”和模型分布的“梯度箭头”有多不一样用真实数据密度做加权平均。问题在于这个目标函数里仍然含有 ( \nabla_x \log p_{data}(x) )我们并不知道真实分布只知道它的样本。Hyvärinen 在 2005 年那篇经典论文里做了一件很漂亮的事通过分部积分把这个未知项消掉。我们考虑一维情况做示例。展开平方项丢掉与参数无关的常数项包含 (|\nabla_x \log p_{data}|^2) 的部分剩下的关键项是[\int p_{data}(x) \left\langle \nabla_x \log p_{data}(x), \nabla_x \log p_\theta(x) \right\rangle dx ]对向量内积中的第 (i) 个分量做分部积分[ \int p_{data}(x) \frac{\partial \log p_{data}(x)}{\partial x_i} \cdot \frac{\partial \log p_\theta(x)}{\partial x_i} dx ]利用 ( \frac{\partial \log p_{data}}{\partial x_i} \frac{1}{p_{data}(x)} \frac{\partial p_{data}}{\partial x_i} )以及 (p_{data}(x)) 在边界处趋于 0 的假设分部积分后得到[ \int p_{data}(x) \left( \frac{\partial^2 \log p_\theta(x)}{\partial x_i^2} \frac{1}{2} \left( \frac{\partial \log p_\theta(x)}{\partial x_i} \right)^2 \right) dx ]把每个维度加起来再把 1/2 系数配平就能得到显式 Score MatchingESM目标[ J_{ESM}(\theta) \mathbb{E}{x \sim p{data}} \left[ \sum_{i1}^{D} \left( \frac{\partial^2 \log p_\theta(x)}{\partial x_i^2} \frac{1}{2} \left( \frac{\partial \log p_\theta(x)}{\partial x_i} \right)^2 \right) \right] ]注意看这个目标函数里只剩对 (p_\theta) 的求导项完全不需要知道真实分布的密度。用样本均值就能估计期望训练时只需要对网络输出求一阶导和二阶导。2.2 为什么梯度匹配就能唯一确定分布还有一个关键问题值得想清楚两个分布如果处处梯度相等它们就真的是同一个分布吗答案是在连通支撑集上是的最多差一个常数。因为梯度相等意味着对数密度差函数的梯度处处为零从而这个差值在连通区域上是常数。而概率密度积分必须等于 1这个归一化条件会把常数锁死。也就是说得分函数蕴含了分布的全部信息只要支撑集是连通的。这件事保证了 Score Matching 的训练目标是良定义的不会出现“梯度完全一样、分布却不同”的歧义情况。从这里也能自然推出一个重要的推论如果我们想让模型分布的支撑集和数据分布一致最稳妥的办法就是对数据加噪声使支撑集铺满全空间。这也是扩散模型会把“加噪”作为核心操作的根本原因之一。2.3 显式目标在实践中的两个痛点理论推导很漂亮但真要把 (J_{ESM}) 用到深度网络里立刻会撞上两个问题。第一计算二阶导数。对高维数据每个样本都要计算 Hessian 矩阵的对角线元素。比如 256x256 的图片维度是 65536虽然可以只算对角线但反向传播的计算量仍然非常可观显存也会很紧张。第二数据流形支撑问题。真实数据往往集中在一个低维流形上流形之外几乎不会采样到数据点。如果模型在流形外的预测得分是乱猜的这些地方的误差根本不会被 Fisher 散度惩罚到训练就会不稳定。这两个痛点是 Score Matching 后续演化的核心驱动力。怎么解决一个方案是引入随机投影降低二阶导的维度另一个方案是加噪声把流形“撑开”。这就是接下来要讲的两条路线。3. 三种主流变体与选型思路3.1 切片得分匹配Sliced Score Matching, SSM切片得分匹配的思想非常直接既然在高维空间里直接比较两个向量场很贵那就把它们投影到一维直线上再比较。一维的 Fisher 散度是有解析形式的而且只需要计算一个方向上的二阶方向导数不再是整个 Hessian 对角线。具体来说对每个样本 (x)采样一个随机方向向量 (v)通常取标准正态分布或 Rademacher 分布目标函数变成[ J_{SSM}(\theta) \mathbb{E}{x, v} \left[ v^\top \nabla_x^2 \log p\theta(x) v \frac{1}{2} \left( v^\top \nabla_x \log p_\theta(x) \right)^2 \right] ]其中 ( v^\top \nabla_x^2 \log p_\theta(x) v ) 是 Hessian 沿方向 (v) 的二阶方向导数可以用两次反向传播高效算出。实际计算时可以先算 (g \nabla_x \log p_\theta(x))然后计算 (u \nabla_x (g^\top v))再与 (v) 做内积得到的就是方向导数项。从我实测经验来看SSM 比 ESM 在高维场景下实用得多。它的额外开销主要来自每次要对随机方向 (v) 再做一次反向传播大概比普通前向慢 1.5 到 2 倍但相比 ESM 的完整 Hessian已经算很划算了。3.2 去噪得分匹配Denoising Score Matching, DSM去噪得分匹配走的是另一条路把带噪数据分布当成新的目标分布。思路很简单。假设干净数据分布是 (p_{data})我们给它加上高斯噪声[ q_\sigma(\tilde{x}) \int p_{data}(x) \mathcal{N}(\tilde{x}; x, \sigma^2 I) dx ]然后去匹配带噪数据的得分函数 (\nabla_{\tilde{x}} \log q_\sigma(\tilde{x}))。Vincent 在 2011 年证明了一个非常实用的结论匹配带噪数据的得分等价于从带噪样本里回归出原始干净数据沿着噪声方向偏移的量[ J_{DSM}(\theta) \mathbb{E}{x \sim p{data}, \epsilon \sim \mathcal{N}(0, I)} \left[ \left| s_\theta(x \sigma \epsilon) \frac{x \sigma \epsilon - x}{\sigma^2} \right|^2 \right] ]这里面 (s_\theta(\tilde{x})) 是得分网络而 (-\frac{\tilde{x} - x}{\sigma^2}) 是加噪分布得分函数的解析形式。换句话说我们只需要让网络从带噪样本中把“噪声方向”估计出来完全不需要对网络输出做任何求导操作。用通俗的话讲你拿到了一张加了噪声的图片让网络学“怎么把噪声的方向指出来”这个方向自然指向了更可能包含真实数据点的区域。DSM 的优势非常明显不需要计算 Hessian不需要额外反向传播训练极其稳定几乎成了扩散模型训练的默认方案天然对抗了流形支撑问题——加了噪声支撑集自然铺满全空间。它的代价也很直接你学到的是带噪分布的得分不是干净分布的得分。但实际操作中我们可以把噪声尺度 (\sigma) 设得很小或者在采样时配合退火策略逐渐减小噪声这个误差是可以接受的。现代扩散模型本质上就是把多个不同 (\sigma) 下的得分函数打包在一起学习再按噪声从大到小的顺序依次去噪。3.3 三种变体怎么选我把三种变体的特点整理成一张表方便大家快速选型方法需要二阶导需额外反向传播是否需要加噪高维适用性训练稳定性显式 Score Matching (ESM)是完整 Hessian 对角线否否差高维成本高中等切片 Score Matching (SSM)方向二阶导约等于 2 次反向传播是每个随机方向一次可选较好中等偏上去噪 Score Matching (DSM)否否必须加噪好非常稳定我个人的建议是如果你的任务只是搭一个能用的能量模型做密度估计首选 DSM它简单、稳、代码好写如果在某些场景下不希望引入噪声扰动、希望逼近干净分布的得分再考虑 SSMESM 则更多是理论上的起点适合低维 toy example 和教学演示。4. 用 PyTorch 实现一个可运行的得分匹配模型4.1 实验设定二维环形数据与小型能量模型为了把原理落到实处我搭了一个直观的实验用二维环形数据作为真实分布训练一个能量模型然后用朗之万动力学从模型分布中采样观察效果。先定义数据。环形分布可以由两个高斯混合生成在半径 (R2) 的圆上均匀采样角度 (\theta)然后在半径上加一点噪声同时给笛卡尔坐标加少量高斯噪声。这样生成的数据天然落在二维平面上便于可视化。核心代码如下import torch import torch.nn as nn import numpy as np import matplotlib.pyplot as plt torch.manual_seed(42) def generate_ring_data(n_samples4096, R2.0, noise0.1): theta torch.rand(n_samples) * 2 * np.pi r R torch.randn(n_samples) * noise x r * torch.cos(theta) y r * torch.sin(theta) return torch.stack([x, y], dim1)模型方面我设计了一个非常简单的 MLP 作为得分网络输入二维向量输出二维得分向量。注意一点最终输出层不要接激活函数因为得分函数的取值范围是整个实数空间它需要能自由地从负到正变化。class ScoreNet(nn.Module): def __init__(self, hidden_dim128): super().__init__() self.net nn.Sequential( nn.Linear(2, hidden_dim), nn.SiLU(), nn.Linear(hidden_dim, hidden_dim), nn.SiLU(), nn.Linear(hidden_dim, hidden_dim), nn.SiLU(), nn.Linear(hidden_dim, 2) ) def forward(self, x): return self.net(x)这里我特意选了 SiLU 激活函数而不是 ReLU。原因在于得分网络需要输出光滑的梯度场ReLU 在零点附近的不光滑性会让二阶导在反向传播时出现异常而 SiLU 处处光滑训练更稳。4.2 训练过程DSM 损失与超参数选择训练部分用 DSM 目标。对每个 batch我们做三步采样高斯噪声 (\epsilon \sim \mathcal{N}(0, I))构造带噪样本 (\tilde{x} x \sigma \epsilon)计算带噪分布的解析得分目标 (-\frac{\epsilon}{\sigma})注意这里有个等价的简化形式因为 (\tilde{x} - x \sigma \epsilon)所以得分目标可以写成 (-\frac{\tilde{x} - x}{\sigma^2} -\frac{\epsilon}{\sigma})。def train_dsm(model, dataloader, sigma0.3, epochs200): optimizer torch.optim.Adam(model.parameters(), lr1e-3) for epoch in range(epochs): total_loss 0.0 for x in dataloader: x x[0] eps torch.randn_like(x) x_tilde x sigma * eps # 网络输出是 s_theta(x_tilde) score_pred model(x_tilde) # 目标得分是 -eps / sigma score_target -eps / sigma loss torch.mean(torch.sum((score_pred - score_target) ** 2, dim1)) optimizer.zero_grad() loss.backward() optimizer.step() total_loss loss.item() if epoch % 20 0: print(fEpoch {epoch}, Loss: {total_loss / len(dataloader):.6f}) sigma max(0.05, sigma * 0.98) # 噪声退火这里有一个值得展开的参数噪声尺度 sigma。sigma 太大得分函数变得过于平滑学到的分布细节少采样结果模糊sigma 太小数据流形外的区域没有足够的梯度信息训练会不稳定。我在这套二维环形数据上测试下来初始 sigma 取 0.3然后每轮乘以 0.98 做退火效果最好。退火的好处在于前期用较大的噪声快速铺满全空间、稳定优化后期用小噪声逼近真实的得分。4.3 朗之万采样从得分场还原分布训练完成后怎么验证模型学到了正确的分布最直接的办法就是采样。我们采用朗之万动力学Langevin Dynamics它只依赖得分函数迭代更新样本[ x_{t1} x_t \frac{\epsilon}{2} s_\theta(x_t) \sqrt{\epsilon} z_t ]更新公式的第一项是沿着得分方向爬升相当于往高概率区域移动第二项是高斯随机扰动保证不会收敛到一个点上而是在分布中探索。采样代码如下def langevin_sample(model, n_samples1000, n_steps200, step_size0.1): model.eval() x torch.randn(n_samples, 2) * 3.0 # 初始点覆盖范围大一些 with torch.no_grad(): for _ in range(n_steps): score model(x) noise torch.randn_like(x) x x 0.5 * step_size * score torch.sqrt(torch.tensor(step_size)) * noise return x初始点我特意采样范围比较大的高斯噪声让各区域都有起点。步长 step_size 和噪声项系数 (\sqrt{\epsilon}) 需要匹配——如果 step_size 太大采样过程发散太小则收敛极慢。200 步在这个问题上足够让样本混合到分布主体区域。我跑完训练后把原始数据和采样数据画在同一张图上能看到采样点基本沿着环形分布展开圈状结构清晰。虽然有一小部分点还在环内较远区域游走但这些点是朗之万采样早期的“残留”增加步数会进一步收敛。也可以做一个更直观的验证把二维网格上的每个点输入给得分网络画出得分向量场。得分向量应该从四周指向环形高密度区域二维可视化下能看到一个明显的“环形磁场”。这个图形检查是我每次训练能量模型后第一时间画的它能快速暴露模型没学好的问题。5. 现代视角Score Matching 如何支撑扩散模型5.1 扩散模型的本质是同时估计多个噪声尺度的得分很多人第一次接触扩散模型时会被前向加噪、反向去噪、U-Net、时间步嵌入这些概念绕晕。但如果你先理解了 Score Matching再回头看扩散模型会发现它的骨架非常清晰。扩散模型的前向过程其实就是在逐步给数据加噪声。从干净分布到带噪分布我们可以理解为一连串不同噪声水平 (\sigma_1 \sigma_2 \dots \sigma_T) 下的数据分布(p_{\sigma_1}(x), p_{\sigma_2}(x), \dots, p_{\sigma_T}(x))。几乎趋于纯噪声。反向过程生成数据就是从最大的噪声分布开始不断朝着低噪声方向走。而“从当前噪声水平往更干净方向走”需要的核心指引恰恰就是当前噪声分布下的得分函数 (\nabla_x \log p_{\sigma_t}(x))。所以扩散模型训练的到底是什么一个以噪声水平为条件输入的得分网络 (s_\theta(x, \sigma))。它同时为所有噪声水平提供得分估计。这正好解释了为什么现代扩散模型的实现里几乎都能看到 DSM 损失的影子——因为对每个噪声水平我们都用“加噪样本”对“得分方向”做回归。5.2 噪声退火与朗之万采样的配合既然扩散模型相当于一组不同噪声水平下的得分函数生成过程就不会只是跑一步朗之万采样而是要跑一个由粗到细的退火过程从大噪声开始用当前得分做若干步朗之万更新然后减小噪声继续做若干步更新循环往复直到噪声接近零。每个阶段里朗之万采样负责在对应噪声尺度的分布上探索而退火负责逐步把探索范围从模糊的全局结构缩小到清晰的细节。这就是为什么说 Score Matching 是扩散模型的理论地基。理解了这一层很多问题就豁然开朗了。比如为什么扩散模型的输入要做时间步嵌入因为网络需要知道“当前处在哪个噪声水平”才能输出对应的得分为什么推理时的步数不能太少因为每次退火后朗之万采样需要足够步数在中间分布上“站稳”为什么加噪方式都是高斯型因为高斯噪声的扰动分布有解析的得分目标计算可以直接闭环。5.3 从能量模型到基于得分的生成模型顺着这个思路再往前一步就能自然地理解近年来“基于得分的生成模型”Score-based Generative Models为什么能与扩散模型合并为同一套框架。它们本质上都是估计数据分布的得分函数再通过朗之万采样或 SDE 求解器生成样本。对我个人来说学习 Score Matching 最有价值的一点是它提供了一个统一视角能量模型、基于得分的模型、扩散模型三者只是看待同一件事的不同切面。能量模型关心 (p(x) \propto \exp(-E(x))) 里的 (E)基于得分的模型直接关心 (\nabla_x \log p(x))扩散模型则把这条技术路线工程化、规模化到了图像视频生成的水平。训练目标共享同一套数学原理只是工程表现形式不同。6. 常见问题与排查技巧实录6.1 训练不收敛或损失爆炸这是我在跑 ESM 时最先遇到的问题。原因通常是两个一是数据没有归一化原始数值范围太大直接让梯度爆炸二是学习率设置过高二阶导项对学习率尤其敏感。我的排查顺序是先把数据标准化到均值为 0、方差为 1 的范围学习率从 1e-3 开始如果损失抖动剧烈就降到 1e-4对 ESM 在输出层加梯度裁剪限制最大梯度的范数。DSM 在这方面的表现会好很多因为它本质上是一个回归任务预测噪声方向没有高阶导放大误差的问题。如果你用 DSM 仍然爆炸先检查是不是 sigma 设得太小了导致得分目标的值巨大因为目标 (-\epsilon / \sigma) 的范数与 1/sigma 成正比。常规做法是在 loss 上乘 sigma^2 做缩放或者用预测噪声向量而非得分向量作为网络输出来平衡量纲。6.2 得分估计在低维流形上失效这个问题的本质我前面提到过真实数据分布只占高维空间里的一块低维流形流形以外几乎没有训练样本得分函数在那里就是无方向可言的。你画出来的得分场在数据区域附近可能很漂亮一到远处就开始随机乱指。这在图像数据上特别明显。干净图片在像素空间里只是极低维的流形直接对干净图片做 Score Matching 几乎注定失败。扩散模型解决这个问题靠的就是加噪。加噪的本质是把低维流形上的概率质量“扩散”到全空间让每个地方都能获得梯度信号。如果在自己的任务上遇到这个问题先检查训练数据里每个样本是否都偏离其他样本很远。更直接的做法在 DSM 的框架下观察不同 sigma 下 loss 的变化。如果最小的 sigma 下 loss 仍然很大说明网络还没有能力把低噪声尺度的细节拟合好可以增加小噪声尺度的采样权重。6.3 朗之万采样结果不理想采样结果差很多人的第一反应是“是不是训练没学好”但实际上采样参数的影响往往被低估。我列几个常见现象样本发散到无穷远步长太大或者步数太少。步长太大时更新公式里的确定性推进项过猛样本直接飞出去了。可以把步长缩小到原来的 1/10同时增加步数测试。样本聚成一团多样性不足步长太小朗之万采样的探索不足样本被局限在初始点附近的模式里。增大步长或增加噪声项系数可以缓解。混合速度慢如果分布是多模态的朗之万采样很难跨过低概率区域在两个模式之间迁移。最好用“多个初始点并行采样”每个模式都会被覆盖到。我在二维环形数据上的经验是步长取 0.1 到 0.2 之间步数 100 到 300 步初始点覆盖范围要大于数据分布的范围这样采样才能从“外圈”往里收。6.4 如何快速可视化诊断模型学得好不好最后分享一个我每次训练后必做的可视化检查。把二维平面画成网格对每个网格点输入得分网络用 quiver 图画出得分向量。一个训练良好的得分网络向量场应该在数据分布区域内向量指向高密度中心在远离数据区域的地方向量指向数据流形方向且大小随距离增大而增大在数据流形附近向量幅值最小因为已经接近概率密度的峰值。如果看到向量场在某些区域出现“漩涡”或者方向突变说明网络在这些区域的拟合有问题。这种定性检查比只看 loss 曲线可靠得多因为 loss 是平均量掩盖了局部失败。我踩过一次很典型的坑训练早期看到一个 loss 下降正常的曲线矢量图却出现了一段方向的“分叉”看起来像能量面上多了一个假峰。排查后发现是网络容量不够隐藏层只有 32 个神经元。把宽度提到 128 之后假峰消失向量场变得平滑有序。这种问题只看曲线是发现不了的。实操总结与个人体会这套实验完整跑下来我的体感很明确如果你是第一次接触 Score Matching不要从 ESM 开始死磕推导里的每个积分细节先跑通一个 DSM 的 toy example把“得分场长什么样”“朗之万采样怎么从噪声里生成数据”这两件事印在脑子里再回头看理论推导会觉得顺畅得多。从工程角度看DSM 是我目前实际项目里最常用的训练方式。它稳定、简单、可扩展加了随机噪声退火之后几乎不需要怎么调参。SSM 在不想引入噪声扰动、需要逼近真实得分函数的场景下依然有价值比如某些科学计算里需要精确估计数据流形的局部几何结构。ESM 更多的意义在于理论完整性日常建模里我不太会直接用它。扩散模型的整套技术体系虽然看起来宏大但核心基建之一确实是 Score Matching 这条思想脉络。如果你已经能熟练地训练一个得分网络再去看扩散模型的代码实现会发现很多东西是相通的噪声调度、条件输入、损失形式、采样过程都能在 Score Matching 的框架里找到对应。最后再提一个实用小技巧训练得分网络时给网络输入加上噪声水平的嵌入哪怕是一个简单的一次函数比训练多个独立网络更高效。共享权重让不同噪声尺度互相促进这在扩散模型里已经是标配做法但即使在小规模 toy model 上也能明显加快收敛速度。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价