1. 从0开始理解DDPM它到底在解决什么问题扩散模型Diffusion ModelDDPM 全称 Denoising Diffusion Probabilistic Model这几年几乎是生成模型领域的顶流图像生成的落地效果已经能和很多传统生成对抗网络掰手腕。你如果平时关注技术社区大概率见过那些“从噪声里长出一张图”的演示——输入一堆随机高斯噪声几十步迭代之后输出一张清晰人像或风景图。今天这篇我带着完整手写推导笔记的思路一步步解开 DDPM 的原理和推导过程。先明确一下这篇内容能解决什么问题很多人看过公式、代码但始终没弄明白为什么扩散模型的训练目标被简化为“预测噪声”以及那个看起来像一堆高斯分布叠加的损失函数到底怎么来的。这篇的核心目标就是把这根从“变分推断”到“KL散度”再到“MSE损失”的链条捋顺让数学公式和代码实现真正对上。适合谁来学你已经会操作深度学习框架知道反向传播、随机梯度下降但对生成模型还停留在“看别人代码跑通”的阶段。你对贝叶斯公式和高斯分布有一定基础但不想看教材式的冗长推导。这类读者这篇文章就是为你准备的。如果你完全没有概率论基础建议先补一补高斯分布和 KL 散度的基本概念再回来顺着推导走体验会好很多。另外我想先给一个总览DDPM 的策略是设计一条“逐步加噪”的破坏链再训练一条“逐步去噪”的修复链。这个思路在数学上非常像把“低维隐变量”变成一个固定时间长度的马尔可夫链再用变分下界ELBO来训练反向生成链。下面我会先拆解前向过程再进入最优难懂的变分推导最后落回到一个极其简单的训练目标。2. 前向扩散过程为什么噪声要一点一点加2.1 破坏过程的数学定义DDPM 的前向过程很简单把一张干净图像 x0在 T 步之内逐步加入高斯噪声最终得到一个接近纯噪声的图像 xT。每一步定义是q(xt | x(t-1)) N(xt; sqrt(1 - βt) * x(t-1), βt * I)其中 βt 是每一步的噪声尺度超参数随着时间步逐渐增大常见做法是从 0.0001 线性增到 0.02。这里的含义是每一步只新增一点点噪声保持图像结构慢慢被侵蚀而不是一步就毁掉全部信息。为什么必须“逐步”加噪而不是一步到位有两个关键原因。第一一步加噪会导致信息瞬间散失反向过程无法学会“逐步恢复”因为一步去噪的逆向映射过于复杂网络根本拟合不了。第二逐步加噪可以构造出每步都近似高斯的条件分布这正好方便我们用高斯分布去建模和学习反向过程。2.2 重参数化与任意时刻的噪声状态前向过程是一个马尔可夫链。我们可以用重参数化技巧把每一步的采样写成xt sqrt(1 - βt) * x(t-1) sqrt(βt) * εt其中 εt ~ N(0, I)这和写分布公式完全等价但好处是便于采样、便于求导也是后面推导损失函数的重要工具。更有用的一个结论是给定初始图 x0我们可以算出任意时间步 t 的边际分布不需要真的迭代 t 次。定义αt 1 - βt αbar_t Π(s1到t) αs通过累乘可以得到xt sqrt(αbar_t) * x0 sqrt(1 - αbar_t) * ε其中 ε 是标准高斯噪声。这条公式是 DDPM 训练环节的关键意味着你可以随机取一个时间步 t从任意干净图生成对应的加噪图然后让网络去预测混入的噪声而不用真的跑一遍 T 步加噪过程。从信息论角度理解随着 t 增大αbar_t 逐渐趋近于 0此时信号部分 sqrt(αbar_t) * x0 越来越小噪声部分主导整张图。当 t 接近 T 时xt 近似服从标准高斯分布前向过程就完成了“数据分布逐渐变形为标准高斯分布”的任务。这个特性直接决定了反向过程从纯噪声出发就可以采样。实操中βt 的调度选择会直接影响训练稳定性。常用的线性调度、基于余弦函数的调度本质都是平衡“前几步信息保留”和“后几步噪声纯度”之间的矛盾。如果噪声加得太快反向过程前面几步压力极大模型会倾向于输出模糊图像如果加得太慢前面几十步变化过于微小模型会浪费学习容量。提示理解 αbar_t 的核心公式几乎等于理解了整个 DDPM 的训练加速逻辑。后面第三节做变分推导时这个式子会反复出现。3. 变分下界推导从最大似然出发的“退一步”策略3.1 为什么做最大化似然却要绕道 ELBO生成模型的最终目标是让学到的分布 pθ(x0) 逼近真实数据分布 q(x0)。最直接的想法是最大化真实样本在模型下的对数似然log pθ(x0)但是有个现实问题DDPM 的反向过程是一个从 xT 到 x0 的马尔可夫链计算 log pθ(x0) 需要对所有中间隐变量积分这个积分基本无法解析求解直接优化不可行。于是我们套用变分推断里经典的“退一步”策略不直接优化对数似然而是优化它的一个下界。把隐变量 x1 到 xT 全部显式写进联合分布引出 ELBOEvidence Lower Boundlog pθ(x0) ≥ E_q [ log pθ(x0:T) / q(x1:T | x0) ]这里的期望是针对前向过程 q(x1:T | x0) 取的。整条公式的本质是因为我们算不出精确的积分就找一个通过采样可估计的下界来优化当这个下界被提高真正的对数似然也会跟着提高。3.2 把联合分布拆开去配对好每一项接下来我们要对 ELBO 做拆解。把模型的联合分布 pθ(x0:T) 按马尔可夫链展开成三个部分第一个部分是 pθ(xT)即生成链出发点的分布。通常直接设定为标准高斯分布 N(0, I)因为前向过程终点已经接近标准高斯反向过程的起点也应该是一个简单分布。第二个部分是反向过程中的每一跳 pθ(x(t-1) | xt)这一步由神经网络参数化是本模型真正的可学习部分。第三个部分是最后的解码 pθ(x0 | x1)因为图像像素有范围限制这一步要单独处理通常拆成多个独立的高斯分布或者连续分布去拟合采样值。把这些带进 ELBO并且利用条件概率的链式法则经过整理可以得到三项相加的形式ELBO E_q[ log pθ(xT) ] - Σ(t2到T) KL[q(xt | x(t-1)) ‖ pθ(x(t-1) | xt)] E_q[ log pθ(x0 | x1) ]这里每一项都有直观的含义第一项是结束状态和标准高斯的匹配程度中间一大串是前向破坏过程中的每一步原始噪声转移与反向去噪分布之间的 KL 散度求和最后一项是最后一步生成质量。3.3 重点来了前向重构项的折中很多人推导到这里就开始头大因为 ELBO 中间项用的是 q(xt | x(t-1))可我们实际想让网络学的是反向转移这一项不太容易直接优化。这时引入一个关键技巧把 KL 散度中的 q(xt | x(t-1)) 再“加一层条件”变成 q(xt | x(t-1), x0)。因为整个马尔可夫过程以 x0 为源头条件中多一个 x0 不会破坏分布的一致性。同时利用贝叶斯公式可以证明 q(xt | x(t-1), x0) 等于 q(x(t-1) | xt, x0) * q(xt | x0) / q(x(t-1) | x0)。不要小看这个转换它的价值在于q(x(t-1) | xt, x0) 是一个可以用高斯解析计算的分布给定 x0 和 xt确切的反向前向条件而且我们可以直接算出它的均值和方差根本不依赖任何网络。这一项被称作“可解释的后验分布”它告诉反向网络每一时间步最理想的“去噪结果”长什么样。3.4 三项重组之后ELBO 变成了另一个形式经过这一份“条件化”的折中处理ELBO 被重新整理成如下四项组合第一项是重构项 E_q[ log pθ(x0 | x1) ]决定恢复质量。第二项是做一致性项 KL[q(x1 | x0) ‖ pθ(x1 | x0)]它约束这个边界步的匹配程度因为已经开始显得噪声很大实际训练中可以忽略因为 q(x1 | x0) 已经是一个非常近似高斯的小噪声转移。第三项是最关键的扩散过程一致性项Σ(t2到T) KL[q(x(t-1) | xt, x0) ‖ pθ(x(t-1) | xt)]这项要求网络学到的反向转移分布尽量匹配一个在“已知原始干净图 x0”条件下的高斯分布。理论上我们用 x0 的信息可以直接计算最优的分布而实际模型看不到 x0只能通过 xt 预测。训练目标就是想尽办法让预测结果逼近那个以 x0 为条件的理想去噪均值。第四项就是先验匹配项 E_q[ log pθ(xT) ]因为 pθ(xT) 设成标准高斯而前向过程中当 αbar_T 足够小时 q(xT) 也接近标准高斯这一项可以视为约等于常数对训练贡献有限。这个整理后的 ELBO 就干净多了每一项都是高斯分布对高斯分布的 KL 散度或者重建项都有了明确的计算路径。4. 从KL散度到MSE损失函数被“简化到极致”4.1 高斯KL散度的闭式解现在关键问题变成如何计算 KL[q(x(t-1) | xt, x0) ‖ pθ(x(t-1) | xt)]好消息是两边都是高斯分布。假设前向噪声方差 βt 已知那么条件后验 q(x(t-1) | xt, x0) 的高斯参数可以解析写出。均值是某个关于 x0 和 xt 的线性组合方差是一个确定的标量乘单位阵具体写出来就是一个由 αbar_t、αt、βt 和 xt、x0 决定的表达式不需要训练。反向分布 pθ(x(t-1) | xt) 我们也设成高斯均值由神经网络预测方差固定为和前向后验相同的标量。对于两个同方差高斯分布KL散度有一个非常简单的闭式解等于两倍方差分之一乘以均值的平方差再加上常数。把后面那个常数挪到训练目标之外剩下的其实就是“两个均值之间的平方欧氏距离”。4.2 均值预测等价于噪声预测上面已经得到理想反向均值 μθ(xt, t) 应该逼近 q(x(t-1) | xt, x0) 的均值。如果我把网络设计成直接输出“均值”那么损失可以是一个均方差L_t || μθ(xt, t) - 理想均值 ||²但是原文做了更巧妙的换元。因为前向过程是 xt sqrt(αbar_t) * x0 sqrt(1 - αbar_t) * ε可以通过 x0 把理想均值写成 xt 和一个“干净信号”的线性组合。此时如果我们让网络去预测那个被混入的核心噪声 ε整条推导链条会变得异常简洁。本来公式里又乱又长的系数会全部消掉训练目标最终变成L_simple || εθ(xt, t) - ε ||²其中 ε 是在前向过程中采样的标准高斯噪声εθ(xt, t) 是网络从带噪图 xt 和时间步 t 预测出的噪声。很多人第一次看到这个结果都想不到如此繁复的变分推导最后竟然只是让网络“预测噪声”。但推导到这里你会明白这个简化不是凭空拍的脑袋是自然化简的结果而且预测噪声的形式还天然附带一个好处在采样阶段我们可以用预测的噪声构造每步去噪公式完全符合反向模型的行为。4.3 为什么噪声预测损失比均值预测更稳定从实现角度看均值预测需要网络在每一时刻学会解一个和当前噪声水平相关的线性反问题网络输入分布随 t 变化剧烈而噪声预测本质上把任务解耦成“判断哪部分信息是噪声哪部分是真实信号”对不同 t 的输入变化更平滑。另外在采样时我们使用重参数化方式从 pθ(x(t-1) | xt) 中采样先让网络预测噪声再据此算出这个高斯分布的均值加上一个随机采样的小噪声。这个过程相当于“估计要移除的噪声再加上必要的随机性”能让采样结果更加多样而不是直接回归到一个确定图像。注意训练和采样的噪声尺度需要保持一致。很多新手在这里踩坑——训练时用的 βt 调度和采样时用的 βt 不一致导致采样结果模糊甚至发散。后文会专门讲这个问题。5. 训练与采样算法从公式到可运行的套路5.1 训练算法的核心循环训练算法的流程并不复杂核心就是一个循环从训练集中采样一张真实图像 x0随机采样一个时间步 t范围从 1 到 T采样标准高斯噪声 ε根据累计乘积公式生成带噪图像 xt把 xt 和时间步 t 一起输入网络预测噪声计算预测噪声与真实噪声之间的均方误差反向传播更新网络整个过程直观上很像“让网络学会辨别图像中被叠加的噪声”。由于时间步 t 是随机的网络被迫要处理不同噪声强度的输入从而学会一个连续的“去噪时间函数”。5.2 采样算法反向马尔可夫链训练完之后采样就简单了从标准高斯分布采样一个初始噪声 xT从 t T 一路迭代到 t 1用网络预测噪声算出均值除最后一步外加上一个标准高斯缩放噪声输出 x0这里每步加噪声的规模必须参考前向过程。最后一步不加噪声是为了得到尽可能贴近原始像素分布的输出并且避免输出出现不必要的随机模糊。采样过程中还有个常见技巧叫“采样加速”例如 DDIM 把随机采样过程提纯为近确定性过程酒几步就能出不错的结果。它是把反向过程改造成一个非马尔可夫的确定映射这也是基于DDPM后处理思路的优化核心依然是采样公式的重参数化本文以基础 DDPM 为主暂不展开。5.3 时间步信息怎么喂给网络很多人看代码时会困惑xt 是图像那时间步 t 是整数怎么输进网络常见的方案是把 t 做正弦位置编码变成向量然后在网络的中间层以相加或自适应方式融入特征图。这和 Transformer 的位置编码思路一致目的是让网络知道当前输入处在扩散链的哪个阶段进而调节去噪强度。实践上拥有较小 t 的输入几乎还是清晰图像网络应该预测轻微噪声拥有较大 t 的输入是高度噪化的图像网络应该预测强噪声。不把时间信息显式编码进去模型非常难学到这种连续变化的行为。6. 手写推导笔记整理一条主线串起所有公式6.1 前向过程速记定义 xt sqrt(1 - βt) * x(t-1) sqrt(βt) * εε ~ N(0, I)重复累乘得到任意时刻公式 xt sqrt(αbar_t) * x0 sqrt(1 - αbar_t) * ε推导要点αbar_t 是 β 的补数累积决定信号衰减和噪声放大的权重。6.2 变分下界速记log pθ(x0) ≥ ELBO拆解链式条件概率合并 KL 散度项经过条件化后处理得到 L 重建项 边界一致性项 Σ KL 反向匹配项 先验匹配项实际训练时可以忽略先验匹配和边界一致性因为它们要么不 contribute 梯度要么约等于常数。这样真正驱动学习的就只有重建项和中间一堆 KL 项。6.3 从KL转到MSE的速记在固定方差的高斯假设下KL散度转换为均值的欧氏距离 KL 散度 ∝ ||μθ - μ后验||²把 μ后验 展开利用前向加噪公式反解 x0得到等价形式 L_simple E_t || εθ(xt, t) - ε ||²这就是你在所有开源代码里看到的损失函数没有指数符号、没有积分就是一行均方差。这一整条推导线从“最大化似然”到“ELBO”从“三项拆解”到“条件后验”最后到“预测噪声”其实是生成模型里非常优雅的一次转化。每次有新同学问我“为什么 loss 这么简单”我都会带着他走一遍这条线走完之后他们往往有一种“原来如此”的通透感。7. 实践中的常见问题与排查实录7.1 常见问题速查表问题现象可能原因排查与修复方向采样结果整体模糊βt 调度范围过大或采样步数不足降低最大 βt增加采样步数尝试余弦调度采样前几步图像剧烈变化反向过程噪声缩放不匹配检查采样时使用的 βt 是否与训练一一对应损失值不下降时间步编码有问题检查正弦位置编码是否正确时间步是否进入网络图像带有明显条纹训练样本未归一化把像素缩放到 -1 到 1采样后再映射回 0 到 255早期样例像彩色噪声网络容量不足或训练不充分增加模型通道数延长训练轮数7.2 几个我踩过的坑第一个坑是数据归一化。图像如果不缩放到对称区间网络预测的噪声始终带一个偏移训练损失能降但采样图发灰。我习惯把图像预处理为 -1 到 1并在采样结尾用 tanh 或 clamp 截断。第二个坑是训练时的时间步随机策略。如果均匀随机采样时间步模型会被迫同时面对非常轻微和非常严重的噪声训练后期容易不稳定。我在实践中发现对时间步做一定程度的加权采样例如按 sqrt 或二次权重偏向中间步会让训练曲线更平滑尤其在小数据集上明显。第三个坑是验证损失与生成质量的关系。验证损失下降不一定代表生成图像变好因为分布匹配质量不是单纯由 MSE 刻画。我建议每隔固定轮次就生成一批样例图肉眼对比以样例图为准调整超参而不是只看 loss 曲线。第四个坑是反向采样时使用最后一步不加噪声。如果你每一步都加噪声最后的输出会被混入一个标准差为最小 β 的噪声肉眼可见地发花。要记得在最后一步去掉随机项。提示如果你想快速验证自己的理解先在小数据集例如小尺寸人像上完整跑通训练与采样再把模型规模扩大。直接上大模型调参调试成本会高得离谱。7.3 关于稳定复现的一些心得要复现 DDPM最需要盯住的三个超参数βt 调度的起始与结束时间步总数 T以及采样步数。这三个变量决定训练难度和采样质量之间的平衡。T 太小会导致加噪不够渐进反向过程难学T 太大训练成本线性上升。常见选择是 T1000个人实践在一般图像任务上效果不错。如果你想节省训练时间也可以把 T 降到 500配合改进型采样器视觉差别可能在可接受范围内。另外一个容易忽略的经验反向网络最好采用类 U-Net 结构不同时间步的特征对应不同分辨率表达U-Net 天然的跳接能够同时保留全局结构和局部细节这对去噪任务非常有帮助。如果网络太浅采样结果会丢失纹理细节。8. 实用工具箱配置与扩展方向8.1 最小复现环境清单想要把 DDPM 跑起来一个中等配额的 GPU个人实践集显 8GB 就能跑小尺寸图片加一套主流框架就够了。依赖库主要是 PyTorch 或同类深度学习框架配合 NumPy 做数值计算、Matplotlib 画图观察中间状态图像读取用常见的图像库即可。不需要任何环境包裹做好模型定义、前向加噪、噪声预测、采样四件事整个项目就能跑通。建议先固定随机种子特别是采样阶段方便复查问题。8.2 扩散模型方向的几个有意思的扩展DDPM 是很多高阶扩散模型的基石。你可以往几个方向探索条件生成在训练时把类别标签嵌入网络实现指定类别采样图像超分辨率把低分辨率图作为条件输入用扩散模型生成高分辨率细节文生图模型引入文本编码器作为条件配合更大的 U-Net 和更长训练就能走向多模态生成还有医学影像、三维点云、音频生成等应用场景原理一脉相承只是数据形态变了。从我个人的长期实践经验来看先把 DDPM 的标准推导彻底搞懂再去看这些扩展会发现它们都是在“条件依赖”和“生成空间”上做文章核心的变分推导和噪声预测损失基本不变。这也是为什么我建议初学者啃下这一篇文章的推导内容它是整个扩散模型知识树的主根。最后分享一个自己的习惯每次调参前我会把前向加噪过程可视化一遍看看不同时间步的带噪图像大概长什么样这样对 βt 调度和时间步网络输入区间能有一种直觉。调参调多了你会发现扩散模型虽然公式看起来绕但实际训练中往往比很多对抗训练更稳定只要你理解了它的“每一步都往后退一点”的本质出了问题排查起来其实很有方向。