DDPM原理详解:从前向加噪到反向去噪的完整数学推导
直接看公式推导的时候大部分人的第一反应都是“这写的什么”。DDPM那篇论文里一串串期望、KL散度、变分下界每个符号都认识连起来就彻底懵了。我自己当初啃的时候也是反复推了好几遍才把整条逻辑链理顺。这篇文章就把DDPM的完整推导过程拆开揉碎讲一遍从“它到底在干嘛”到“每一行公式为什么长这样”全部摊开说清楚。内容会偏数学一些但我尽量用大白话把每一步的动机和来龙去脉讲透适合正在看论文、准备复现实验、或者被各种博客绕晕了想彻底搞懂原理的朋友。1. 先把DDPM到底在做什么搞清楚1.1 核心直觉拆解一张图的“生成”过程DDPM的全称是Denoising Diffusion Probabilistic Models翻译过来就是“去噪扩散概率模型”。它做的事情说起来特别朴素拿到一张干净图片我们不断往上加噪声加一步、加两步、加几百步图片越来越花最后变成一张纯噪声这个过程叫“前向过程forward process”。然后我们训练一个神经网络让它学会“倒着走”也就是给定一个带噪声的图片它能预测出“刚才加进去的噪声是什么”一步步把噪声去掉最终还原出干净图片这叫“反向过程reverse process”。你可能会觉得奇怪既然要生成图片为什么不直接训练网络从噪声映射到图片非得绕这么一大圈答案在于“分步走”这件事本身。直接学一个从纯噪声到干净图片的映射是个极其复杂的任务网络根本不知道从哪里下手。但如果你让它只负责“去掉一小步噪声”这个任务就简单得多——它只需要学会“把当前这张稍微有点糊的图稍微修干净一点点”。几百上千步的“小修小补”叠加起来就能完成从纯噪声到高清图像的神奇转变。这就像画画你不会从一张空白画布一笔画出蒙娜丽莎但你可以先画个大概轮廓再一遍遍细化五官、光影、细节。每次只做一点微调但重复足够多次之后整幅画就完成了。扩散模型的核心哲学就是“把困难的大任务拆解成大量简单的小任务”。1.2 前向过程和反向过程的符号约定后续所有推导都在围绕这两个过程展开先把会用到的符号约定好不然看公式很容易迷路x₀原始干净图片也就是我们最终想生成的图片q前向过程对应的分布往图片里加噪声用的是这个分布pₜₕₑₜₐ反向过程对应的分布即神经网络建模的去噪分布βₜ第t步的噪声方差系数控制每一步加多少噪声αₜ 1 - βₜ第t步保留原始信息的比例ᾱₜ α₁α₂...αₜ前t步的累积保留比例ε标准高斯噪声ε ~ N(0, I)εₜₕₑₜₐ神经网络预测的噪声μₜₕₑₜₐ反向过程预测的均值在DDPM的设置里前向过程的步数T通常取1000。βₜ的取值方案有很多原始论文里用的是线性调度从β₁10⁻⁴线性增长到β_T0.02。也就是说前面每一步加很少的噪声后面逐渐加大。这么设计的目的后面会细说先记住βₜ整体很小就可以了。1.3 关键错觉前向过程并不是“随机乱加”前向过程看起来像在往图片上撒噪声但它的每一步都是有精确数学定义的q(xₜ|xₜ₋₁) N(xₜ; √(1-βₜ)·xₜ₋₁, βₜ·I)意思是给定上一时刻的图片xₜ₋₁当前时刻xₜ就是“把xₜ₋₁缩放√(1-βₜ)倍再加上一个方差为βₜ的高斯噪声”。这个公式使用了重参数化技巧reparameterization trick即把所有随机性都集中在噪声ε上xₜ √(1-βₜ)·xₜ₋₁ √(βₜ)·ε为什么不用 xₜ xₜ₋₁ 噪声因为如果只加噪声不缩放步数多了之后图片的数值会无限增大方差会爆炸。乘上√(1-βₜ)这个缩放因子之后每一步图像的能量可以通俗理解为像素值的尺度都能维持在一个基本稳定的范围内不会发散也不会塌缩。这个缩放因子的设计是整个推导中第一个关键点很多初看论文的人容易忽略它但它直接关系到后续一系列公式的走向。2. 前向过程一步步污染一张图2.1 单步加噪的数学定义前向过程本质上是一个马尔可夫链每一步只依赖上一步的结果。这个马氏性很重要它让我们能把连续多步的噪声过程拆成单步来看。每一步的转移核就是上面写的q(xₜ|xₜ₋₁) N(xₜ; √(αₜ)·xₜ₋₁, (1-αₜ)·I)为了记号方便大部分论文用αₜ1-βₜ来替换βₜ。写成带α的形式更好看xₜ √(αₜ)·xₜ₋₁ √(1-αₜ)·εₜ其中εₜ是标准高斯噪声。往前走一步图片被缩放一下、加一点噪声。但如果我们要从xₜ₋₁一直走到xₜ中间要迭代T步每一步都要重新采样噪声效率太低了。而且如果我们要训练神经网络预测噪声总得有个“标准答案”吧要是在前向过程中每一步都要实时采样训练时得先跑完整个前向链才能拿到带噪图片这个开销没法接受。所以DDPM设计了一个很巧妙的东西因为它每步加的是高斯噪声而高斯噪声有个性质——多个独立高斯变量之和仍然是高斯变量。利用这一点我们可以直接从x₀跳到任意第t步的xₜ而不用一步一步迭代。2.2 任意时刻的闭式解推导现在我们要推导出从x₀直接到xₜ的表达式。先把xₜ按照定义展开xₜ √(αₜ)·xₜ₋₁ √(1-αₜ)·εₜ再把xₜ₋₁用xₜ₋₂表示xₜ √(αₜ)·(√(αₜ₋₁)·xₜ₋₂ √(1-αₜ₋₁)·εₜ₋₁) √(1-αₜ)·εₜ √(αₜαₜ₋₁)·xₜ₋₂ √(αₜ(1-αₜ₋₁))·εₜ₋₁ √(1-αₜ)·εₜ注意后面两项的系数分别是√(αₜ(1-αₜ₋₁))和√(1-αₜ)。两个标准高斯随机变量的线性组合如果系数平方和为1那它们合并之后仍然是标准高斯。我们来验证一下αₜ(1-αₜ₋₁) (1-αₜ) αₜ - αₜαₜ₋₁ 1 - αₜ 1 - αₜαₜ₋₁完美合并之后系数平方和正好是1-αₜαₜ₋₁。于是xₜ √(αₜαₜ₋₁)·xₜ₋₂ √(1-αₜαₜ₋₁)·ε其中ε是一个新的标准高斯噪声。这个模式可以一直往下套每次“吞并”一层噪声。套到x₀之后把所有α乘起来得到著名的累积乘积公式xₜ √(ᾱₜ)·x₀ √(1-ᾱₜ)·ε其中ᾱₜ ∏ᵢ₌₁ᵗ αᵢ。这个公式的意义非常大。它告诉我们三件事第一任意时刻t的xₜ都可以直接由x₀和一个高斯噪声ε生成第二ᾱₜ反映了x₀保留了多少信息当t增大时ᾱₜ递减所以x₀的成分越来越少、噪声成分越来越多第三反向过程要做的就是给定xₜ和t把那个混进去的ε给“抠”出来。同时这个公式也解释了为什么βₜ要从小到大设计一开始αₜ接近1ᾱₜ衰减得慢图像还能看清大体轮廓后期βₜ变大ᾱₜ快速衰减图像才逐渐被噪声淹没。如果全程都用很小的βₜ那么到第1000步时噪声成分可能还不够大如果全程都很大前面几步图像就被毁了信息丢失太快网络根本学不到去噪规律。2.3 前向过程没有可学习参数注意一个细节整个前向过程从头到尾没有任何需要学习的参数。βₜ是人为设定的调度方案scheduleᾱₜ是βₜ的确定性函数。这意味着前向过程是“固定的”“已知的”我们不需要训练它。真正需要训练的只有反向过程里的那个神经网络。这个设定大大简化了问题——前向过程给定了所有时刻的真实分布反向过程只需要逼近这些分布的“逆过程”即可。3. 反向过程让神经网络学会倒放3.1 反向过程的参数化设定前向过程是“每步加已知噪声”反向过程则是“每步预测被加的噪声”并把它去除。如果q(xₜ|xₜ₋₁)确切知道的话理论上可以用贝叶斯公式算出精确的反向分布q(xₜ₋₁|xₜ)但这个分布涉及整个数据分布没法直接计算。所以我们用一个参数化的高斯分布来近似它pₜₕₑₜₐ(xₜ₋₁|xₜ) N(xₜ₋₁; μₜₕₑₜₐ(xₜ, t), Σₜₕₑₜₐ(xₜ, t))这里的μₜₕₑₜₐ和Σₜₕₑₜₐ都来自于神经网络。DDPM论文里做了一步简化方差Σₜₕₑₜₐ不需要学习直接设置成固定值通常是βₜ或者它的某种变形。后面很多改进工作比如iDDPM才开始学习方差但那不是基础DDPM的事情。核心要学的只有均值μₜₕₑₜₐ(xₜ, t)它表示“给定当前带噪图片xₜ和时刻t预测xₜ₋₁应该长什么样子”。注意这里时刻t也要作为网络输入——因为不同时刻的噪声水平不同网络需要知道当前处于去噪过程的哪个阶段才能做出正确的预测。这也是为什么DDPM的U-Net里要加入时间嵌入time embedding你不告诉它“现在走到第几步了”它根本没法干这个活。3.2 为什么反向分布也可以用高斯来建模这个点很多人会困惑x₀是真实图片它的分布极其复杂为什么xₜ₋₁|xₜ就可以用高斯分布来近似关键在于βₜ设置得足够小。每一步加的噪声都很微弱所以反向的每一步只需要做“微小的修正”。一个微小的修正在局部区域看分布就是平滑的、接近高斯的。打个比方你看一副1000块的拼图如果让你直接从一堆碎片拼出全貌那太难了分布极其复杂但如果已经拼好了999块只剩最后一块没放那么下一块该放哪里这是一个非常局部的、规律的判断。扩散模型也是一样当噪声加得足够小时反向分布高度集中在一个很小的区域里高斯近似就非常有效。另外还有一个数学上的保证如果前向过程的每步噪声都是高斯分布且步数足够多那么反向过程的真实分布也会趋近于高斯分布。这是扩散模型“步数必须足够多”的另一个原因。3.3 网络到底在学习什么训练反向过程的时候我们希望pₜₕₑₜₐ(xₜ₋₁|xₜ)能够尽可能接近真实但不可计算的后验分布q(xₜ₋₁|xₜ, x₀)——注意这个分布是可以用贝叶斯公式算出来的因为x₀已知。这个后验分布的长相正是推导训练目标的关键桥梁。网络最终学到的能力可以形象地理解为给定一张有噪声的图片它判断“刚才加进来的噪声大概是哪个方向”然后把那个方向的噪声分量减掉。到了采样阶段初始点x_T采一个纯高斯噪声然后重复T次“预测噪声并去除”最后就得到一张干净的图。4. 训练目标变分下界那串公式是怎么推出来的4.1 从极大似然到ELBO训练生成模型最朴素的目标是最大化对数似然log p(x₀)。也就是让模型给真实图片分配尽可能高的概率。但p(x₀)本身是一个积分式需要把所有可能的x₁到x_T路径都积分起来这个积分完全不可解。于是我们用变分推断的思想引入前向过程q作为变分分布推它的下界ELBOEvidence Lower Bound。标准的推导是log p(x₀) ≥ E_q[log p(x₀:T) - log q(x₁:T|x₀)]这个式子中E_q表示对q(x₁:T|x₀)取期望。因为期望不好直接算我们把它拆成每一项的期望之和。DDPM的整个训练目标就是从分解这个ELBO开始的。展开一下log p(x₀:T) log p(x_T) Σₜ₌₁ᵀ log pₜₕₑₜₐ(xₜ₋₁|xₜ)log q(x₁:T|x₀) Σₜ₌₁ᵀ log q(xₜ|xₜ₋₁)所以ELBO E_q[log p(x_T) Σₜ₌₁ᵀ log pₜₕₑₜₐ(xₜ₋₁|xₜ) - Σₜ₌₁ᵀ log q(xₜ|xₜ₋₁)]4.2 逐项重写把KL散度结构挖出来上面这个式子虽然形式上已经分解了但每一项仍然很乱没法直接优化。处理技巧是通过贝叶斯定理把q(xₜ|xₜ₋₁)替换成能跟pₜₕₑₜₐ配对的表达式。技巧在于q(xₜ|xₜ₋₁) q(xₜ₋₁|xₜ, x₀)·q(xₜ|x₀) / q(xₜ₋₁|x₀)这个替换的精妙之处在于当我们把L项按时间对齐时很多项会互相抵消。整理之后ELBO变成ELBO E_q[log p(x_T) - log q(x_T|x₀)] Σₜ₌₂ᵀ E_q[log pₜₕₑₜₐ(xₜ₋₁|xₜ) - log q(xₜ₋₁|xₜ, x₀)] E_q[log pₜₕₑₜₐ(x₀|x₁)]写成KL散度就很漂亮了L L_T Σₜ₌₂ᵀ Lₜ₋₁ L₀其中L_T KL(q(x_T|x₀) || p(x_T))这是先验项。因为x_T在极限情况下就是纯高斯噪声它几乎不携带x₀的信息所以这一项在训练中趋近于0可以忽略。Lₜ₋₁ KL(q(xₜ₋₁|xₜ, x₀) || pₜₕₑₜₐ(xₜ₋₁|xₜ))这是去噪匹配项也是训练中最核心的一环。它要求神经网络预测的反向分布尽量接近真实后验分布。L₀ log pₜₕₑₜₐ(x₀|x₁)这是重构项理论上存在但因为最后一步的噪声极小对训练影响不大通常也不作为重点。很多博客写到这一步就停了但实际上后面还有一个关键操作把Lₜ₋₁这个KL散度实际算出来得到一个可以落地的MSE损失。这是整个推导中最有技术含量的部分。4.3 关键的一跃算出真实后验q(xₜ₋₁|xₜ, x₀)我们现在要处理Lₜ₋₁中的第一项q(xₜ₋₁|xₜ, x₀)。它由两个已知的高斯分布通过贝叶斯公式合成q(xₜ₋₁|xₜ, x₀) ∝ q(xₜ|xₜ₋₁)·q(xₜ₋₁|x₀)这里有两个关键事实用到q(xₜ|xₜ₋₁) N(xₜ; √αₜ·xₜ₋₁, (1-αₜ)I)这是前向的定义。q(xₜ₋₁|x₀) N(xₜ₋₁; √ᾱₜ₋₁·x₀, (1-ᾱₜ₋₁)I)这是由2.2节得到的闭式解。两个高斯分布相乘结果仍然是一个高斯分布未归一化。我们可以通过“配方法”写出这个高斯分布的均值和方差。指数部分展开log q(xₜ|xₜ₋₁) -1/(2(1-αₜ))·||xₜ - √αₜ·xₜ₋₁||²log q(xₜ₋₁|x₀) -1/(2(1-ᾱₜ₋₁))·||xₜ₋₁ - √ᾱₜ₋₁·x₀||²两者相加之后关于xₜ₋₁的部分可以整理成一个二次型。二次项系数决定方差一次项系数决定均值。具体地第一项里关于xₜ₋₁的项为(√αₜ·xₜ₋₁)²的系数是αₜ/(2(1-αₜ))交叉项xₜ·xₜ₋₁的系数是-√αₜ/(1-αₜ)。第二项里关于xₜ₋₁的项为xₜ₋₁²的系数是1/(2(1-ᾱₜ₋₁))交叉项xₜ₋₁·x₀的系数是-√ᾱₜ₋₁/(1-ᾱₜ₋₁)。所以xₜ₋₁平方项的系数为c₂ 1/(2(1-αₜ))·αₜ 1/(2(1-ᾱₜ₋₁))为了化简我们来算一个有代表性的共同分母(1-αₜ)(1-ᾱₜ₋₁)。分子为αₜ(1-ᾱₜ₋₁)(1-αₜ)。展开αₜ - αₜᾱₜ₋₁ 1 - αₜ 1 - αₜᾱₜ₋₁ 1 - ᾱₜ。所以c₂ (1-ᾱₜ) / [2(1-αₜ)(1-ᾱₜ₋₁)]那么后验方差精确地说是协方差矩阵这里按标量推导就是σ̃ₜ² 1/(2c₂) (1-αₜ)(1-ᾱₜ₋₁) / (1-ᾱₜ)再看一次项系数。为了凑后验均值μ̃ₜ我们把所有含xₜ₋₁的一次项提出来第一项中的-√αₜ/(1-αₜ)·xₜ注意这里包含的是变量xₜ₋₁与固定量xₜ的交叉项所以对应均值中的系数地位要换一下第二项中的-√ᾱₜ₋₁/(1-ᾱₜ₋₁)·x₀。但这里有个细节第一项里xₜ和xₜ₋₁的交叉项在展开时其实是-2·√αₜ·xₜ₋₁·xₜ/(2(1-αₜ))整理成关于xₜ₋₁的一次项系数就是-√αₜ·xₜ/(1-αₜ)。设后验分布形式为N(μ̃ₜ, σ̃ₜ²)那么它对应的指数二次型为-1/(2σ̃ₜ²)·(xₜ₋₁² - 2μ̃ₜxₜ₋₁ μ̃ₜ²)。所以一次项系数与均值的关系是μ̃ₜ/σ̃ₜ² 上述一次项系数的相反数注意二次型里是2μ̃ₜxₜ₋₁/(2σ̃ₜ²)的形式。一次项总系数为c₁ √αₜ·xₜ/(1-αₜ) √ᾱₜ₋₁·x₀/(1-ᾱₜ₋₁)这里我把负号都整理到均值里具体正负号在推导时要非常小心。这是DOUBLE CHECK的常见坑。于是μ̃ₜ σ̃ₜ²·c₁代入σ̃ₜ² (1-αₜ)(1-ᾱₜ₋₁)/(1-ᾱₜ)μ̃ₜ [(1-αₜ)(1-ᾱₜ₋₁)/(1-ᾱₜ)] · [√αₜ·xₜ/(1-αₜ) √ᾱₜ₋₁·x₀/(1-ᾱₜ₋₁)] (1-ᾱₜ₋₁)/(1-ᾱₜ)·√αₜ·xₜ (1-αₜ)/(1-ᾱₜ)·√ᾱₜ₋₁·x₀把第一项系数整理一下也许更常用μ̃ₜ √ᾱₜ₋₁·(1-αₜ)/(1-ᾱₜ)·x₀ √αₜ·(1-ᾱₜ₋₁)/(1-ᾱₜ)·xₜ这个公式非常重要它就是“当我知道x₀时从xₜ回退一步的最优均值”。它同时也是很多后续改进工作的出发点。4.4 用x₀和ε重写均值让网络目标变得可操作上面的μ̃ₜ表达式里同时出现了x₀和xₜ但x₀在采样时不可用。我们利用2.2节的闭式解把x₀替换掉即x₀ (xₜ - √(1-ᾱₜ)·ε)/√ᾱₜ。代入后经过合并整理这个过程比较代数化强烈建议自己在纸上推一遍可以化简为μ̃ₜ 1/√αₜ·(xₜ - (1-αₜ)/√(1-ᾱₜ)·ε)注意这个式子非常漂亮它只依赖于xₜ、ε和已知的系数。它告诉我们给定xₜ去预测xₜ₋₁本质上等价于“预测出混入其中的噪声ε”然后从xₜ中减去它的影响再除以√αₜ做缩放。所以如果神经网络预测的均值μₜₕₑₜₐ也写成类似形式μₜₕₑₜₐ 1/√αₜ·(xₜ - (1-αₜ)/√(1-ᾱₜ)·εₜₕₑₜₐ(xₜ, t))那么匹配这两个均值就等价于匹配ε和εₜₕₑₜₐ。这是整个DDPM思路最核心的转折点把拟合图像的问题转化成了拟合噪声的问题。5. 损失函数化简与完整的训练采样流程5.1 两个高斯分布之间的KL散度怎么算现在Lₜ₋₁里的两项就都是高斯分布了一项是q(xₜ₋₁|xₜ, x₀) N(xₜ₋₁; μ̃ₜ, σ̃ₜ²I)另一项是pₜₕₑₜₐ(xₜ₋₁|xₜ) N(xₜ₋₁; μₜₕₑₜₐ, σₜ²I)。两个多元高斯分布的KL散度有现成公式KL(N(μ₁, Σ₁) || N(μ₂, Σ₂)) 1/2·[log(detΣ₂/detΣ₁) tr(Σ₂⁻¹Σ₁) (μ₂-μ₁)ᵀΣ₂⁻¹(μ₂-μ₁) - d]如果两个协方差矩阵相同DDPM简化设置中令Σₜₕₑₜₐ等于σ̃ₜ²I那么前两项刚好抵消只剩下一个二次型KL 1/(2σ̃ₜ²)·||μ̃ₜ - μₜₕₑₜₐ||²这一步把所有复杂的概率分布匹配问题直接化成了均值向量的MSE。神经网络的目标就是让预测均值与真实后验均值尽可能一致。5.2 代入重参数化形式后的最终损失把μ̃ₜ和μₜₕₑₜₐ都按4.4节的形式代入||μ̃ₜ - μₜₕₑₜₐ||² ||1/√αₜ·(xₜ - (1-αₜ)/√(1-ᾱₜ)·ε) - 1/√αₜ·(xₜ - (1-αₜ)/√(1-ᾱₜ)·εₜₕₑₜₐ)||² (1-αₜ)²/(αₜ(1-ᾱₜ))·||ε - εₜₕₑₜₐ(xₜ, t)||²前面的系数依赖于t不同的t值这个权重不同。DDPM论文发现去掉这个系数、直接对所有t用统一的权重训练效果反而更好。于是最终的简化损失是L_simple E_{t, x₀, ε}[||ε - εₜₕₑₜₐ(√ᾱₜ·x₀ √(1-ᾱₜ)·ε, t)||²]这个式子看起来极其简洁随机取时刻t、随机采样一张真实图片x₀、随机采样一个高斯噪声ε、构造带噪图片xₜ、让神经网络预测噪声、算MSE。就这么简单。没有复杂的概率计算就是一次回归任务。5.3 训练与采样算法伪代码梳理训练阶段的完整流程是从训练集采样一张干净图片x₀。从1到T随机均匀采样一个时刻t。从标准高斯分布采样噪声ε。计算xₜ √ᾱₜ·x₀ √(1-ᾱₜ)·ε。把xₜ和t送入神经网络得到预测噪声εₜₕₑₜₐ。计算损失||ε - εₜₕₑₜₐ||²反向传播更新参数。采样生成阶段的完整流程是从标准高斯分布采样x_T。对t从T到1用神经网络预测噪声εₜₕₑₜₐ(xₜ, t)。计算xₜ₋₁ 1/√αₜ·(xₜ - (1-αₜ)/√(1-ᾱₜ)·εₜₕₑₜₐ) σₜ·z。当t1时z取0最后一步不加噪声。输出x₀。注意采样时在均值后面还要加上一个随机噪声σₜ·z。这是为了保证生成结果的多样性——如果每一步都只输出均值结果会退化成确定性的均值图像丢失多样性。最后一步不加噪声是为了让输出更锐利、减少随机抖动。这里有个细节很多人会忽略采样公式中的σₜ到底取什么值DDPM论文里做了实验对比σₜ取βₜ或者取σ̃ₜ²都行两者结果接近说明这个选择不是特别敏感。关键是“要加噪声”这个行为本身是必要的。6. 推导笔记中的易错点与常见问题排查6.1 我自己推导时踩过的几个坑第一个大坑是贝叶斯替换那步。很多初学者在看ELBO分解的时候试图直接展开log q(xₜ|xₜ₋₁)结果一堆log项堆在一起怎么都消不掉。正确的思路必须是通过贝叶斯公式引入q(xₜ₋₁|xₜ, x₀)让相邻时间步的项能配对抵消。这个技巧想通之后整个ELBO的结构就清晰了。第二个坑是配方法算后验均值的正负号。两个高斯相乘整理成二次型的时候一不留神就会把x₀和xₜ前面的系数搞混。我的经验是每次整理完都做一个数值验证取一组具体的αₜ0.9、ᾱₜ₋₁0.8、x₀1、xₜ0.5代入看看μ̃ₜ的数值是否合理比如t较小时x₀的权重应该大一些t较大时xₜ的权重更大。这种方式在数学推导中非常有效。第三个坑是x₀替换进μ̃ₜ的那一步代数化简。从“μ̃ₜ关于x₀和xₜ的表达式”转化到“关于xₜ和ε的表达式”中间涉及大量分数合并。這一步硬算也容易错建议先把xₜ √ᾱₜ·x₀ √(1-ᾱₜ)·ε代入然后用符号计算工具辅助验证一遍。多验几次会出现一种“数字上的美感”——所有系数最终都收缩到1/√αₜ和(1-αₜ)/√(1-ᾱₜ)这两个简洁的组合上。6.2 常见问题速查表问题原因排查思路训练损失不下降学习率过大或时间嵌入未生效检查t是否正确参与了网络输入尝试降低学习率到1e-4量级生成的图像模糊采样步数太少训练用1000步采样可以用DDIM等加速方法但直接用DDPM采样时别把T减到低于100最后一步不加噪声时效果差实现里把z0的条件写错了确认t1时不采样随机噪声而其他时刻都要加输出有奇怪噪点且不收敛前向过程ᾱₜ计算错误用累乘方式计算检查ᾱₜ是否单调递减到接近0用闭式公式验证xₜ方差为1训练和采样的噪声方向不一致网络预测噪声有符号错误检查损失中是不是写成6.3 从推导到看懂论文我的读论文路径手推一遍DDPM之后会对“为什么现在的扩散模型长这样”有更深的理解。最直接的收获是看后续改进文章时不再怕公式了。比如DDIM就是把采样过程从马尔可夫链改成非马尔可夫的跳跃过程理解了基本推导后再看那些改进就觉得顺理成章。另外一点体会是很多人纠结“为什么简化版损失去掉系数之后效果反而更好”我的理解是原来权重小的t接近0的时刻在总损失里被压制去掉系数后所有时刻的噪声预测任务被平等对待网络被迫在每个时间尺度上都能做好去噪反而让它学得更全面。后来不少关于扩散模型损失权重设计的论文也在讨论类似现象但DDPM当年这一手简化确实给后续大批工作打开了研究空间。如果你正在啃这篇论文我的建议是不要按论文顺序从头读到尾先看懂第2节的前向过程和闭式解再跳到第3节看训练算法伪代码最后再回头啃第3节末尾的变分下界推导。等这三个部分的逻辑都通了整篇文章就基本拿下了。最后再分享一个小技巧推导过程中准备一个足够大的草稿本把从x₀经x₁、x₂一直展开到xₜ的完整链条写一遍再在右边对照闭式解的各个系数你会发现扩散模型的“扩散”两个字真的只是信息逐渐被噪声稀释的过程数学上的一切都只是为了精确描述这个稀释速率以及让神经网络学会逆向追踪它。理解了这一条DDPM就再也不是天书了。