DDPM入门
- 前言
- [1 什么是DDPM](#1 什么是DDPM)
- [2 正向过程:把数据逐渐变成噪声](#2 正向过程:把数据逐渐变成噪声)
- [2. 训练:让神经网络预测加入的噪声](#2. 训练:让神经网络预测加入的噪声)
- [3. 生成:从随机噪声出发,逐步形成新样本](#3. 生成:从随机噪声出发,逐步形成新样本)
前言
DDPM乍一看非常复杂各种推导公式,变分推断等。但是直接从本质理解并不是很麻烦,先不考虑背后的复杂原理和数学基础,单纯从代码角度理解是非常容易的。DDPM就是两个过程,第一个过程是数据加噪在这个过程中涉及到加噪公式的推导。第二个是数据训练也就是噪声预测,这个过程是让模型学会如何预测噪声即学会去噪。当了解这个过程之后,就明白了什么是经典DDPM,还有就是基于这个过程的各种改进。
1 什么是DDPM
DDPM(Denoising Diffusion Probabilistic Models)是一种生成模型,通过逐步添加噪声将数据(如图像)破坏成纯噪声,再训练模型逆向过程,从噪声中恢复出原始数据。核心思想是:先用随机噪声逐步"破坏"一张图片,再训练一个神经网络学会"去噪",一步步还原出清晰图像。整个过程像一场"反向的破坏游戏"------模型学习如何从混乱中重建秩序。适用于图像生成、修复等任务,生成质量高但推理速度较慢。
2 正向过程:把数据逐渐变成噪声
假设一张清晰图片是 x 0 x_0 x0。我们连续加入少量高斯噪声,得到 x 1 , x 2 , ... , x T x_1, x_2, \dots, x_T x1,x2,...,xT。随着步数增加,图片越来越模糊;当噪声足够多时, x T x_T xT 就近似纯高斯噪声。
经典 DDPM 中,每一步可以写成:
x t = 1 − β t x t − 1 + β t ϵ , ϵ ∼ N ( 0 , I ) x_t = \sqrt{1-\beta_t} x_{t-1} + \sqrt{\beta_t} \epsilon, \quad \epsilon \sim \mathcal{N}(0, \mathbf{I}) xt=1−βt xt−1+βt ϵ,ϵ∼N(0,I)
这里:
- t t t:当前加噪步数。
- β t \beta_t βt:这一小步的噪声强度。
- ϵ \epsilon ϵ:随机采样的高斯噪声。
这个过程由我们事先设定,不需要神经网络学习。
上面这个公式就是经常见到的,但是看起来还是比较抽象,下面具体看如何推导。这个公式是通过马尔可夫链的重参数化技巧(Reparameterization Trick)和正态分布的可加性一步步推导出来的。
第一步:单步加噪的定义在扩散模型中,从 t − 1 t-1 t−1 时刻到 t t t 时刻加入微小噪声的过程定义为:
x t = α t x t − 1 + 1 − α t z t − 1 x_t = \sqrt{\alpha_t} x_{t-1} + \sqrt{1 - \alpha_t} z_{t-1} xt=αt xt−1+1−αt zt−1
其中 z t − 1 ∼ N ( 0 , I ) z_{t-1} \sim \mathcal{N}(0, I) zt−1∼N(0,I) 是标准正态分布的噪声。
第二步:递归展开为了寻找 x t x_t xt 与初始状态 x 0 x_0 x0 的直接关系,我们可以把 x t − 1 x_{t-1} xt−1 也用同样的单步公式展开: x t − 1 = α t − 1 x t − 2 + 1 − α t − 1 z t − 2 x_{t-1} = \sqrt{\alpha_{t-1}} x_{t-2} + \sqrt{1 - \alpha_{t-1}} z_{t-2} xt−1=αt−1 xt−2+1−αt−1 zt−2将其代入第一步的式子中: x t = α t ( α t − 1 x t − 2 + 1 − α t − 1 z t − 2 ) + 1 − α t z t − 1 x_t = \sqrt{\alpha_t} (\sqrt{\alpha_{t-1}} x_{t-2} + \sqrt{1 - \alpha_{t-1}} z_{t-2}) + \sqrt{1 - \alpha_t} z_{t-1} xt=αt (αt−1 xt−2+1−αt−1 zt−2)+1−αt zt−1展开后得到: x t = α t α t − 1 x t − 2 + α t ( 1 − α t − 1 ) z t − 2 + 1 − α t z t − 1 x_t = \sqrt{\alpha_t \alpha_{t-1}} x_{t-2} + \sqrt{\alpha_t(1 - \alpha_{t-1})} z_{t-2} + \sqrt{1 - \alpha_t} z_{t-1} xt=αtαt−1 xt−2+αt(1−αt−1) zt−2+1−αt zt−1
第三步:合并独立正态分布利用正态分布的两个关键性质:
- 如果两个相互独立的随机变量 X ∼ N ( 0 , σ 1 2 ) X \sim \mathcal{N}(0, \sigma_1^2) X∼N(0,σ12) 和 Y ∼ N ( 0 , σ 2 2 ) Y \sim \mathcal{N}(0, \sigma_2^2) Y∼N(0,σ22),那么它们的和 X + Y ∼ N ( 0 , σ 1 2 + σ 2 2 ) X + Y \sim \mathcal{N}(0, \sigma_1^2 + \sigma_2^2) X+Y∼N(0,σ12+σ22)。
- 如果一个随机变量 Z ∼ N ( 0 , 1 ) Z \sim \mathcal{N}(0, 1) Z∼N(0,1),并且有一个常数 c c c,那么 c Z cZ cZ 仍然服从正态分布,但其方差会变成常数的平方: c Z ∼ N ( 0 , c 2 ) cZ \sim \mathcal{N}(0, c^2) cZ∼N(0,c2)
上面展开式后面的两个噪声项可以合并成一个新的标准正态分布噪声。我们计算合并后的方差: Variance = α t ( 1 − α t − 1 ) + ( 1 − α t ) \text{Variance} = \alpha_t(1 - \alpha_{t-1}) + (1 - \alpha_t) Variance=αt(1−αt−1)+(1−αt) Variance = α t − α t α t − 1 + 1 − α t = 1 − α t α t − 1 \text{Variance} = \alpha_t - \alpha_t\alpha_{t-1} + 1 - \alpha_t = 1 - \alpha_t\alpha_{t-1} Variance=αt−αtαt−1+1−αt=1−αtαt−1
因此,式子可以简化为(其中 z ˉ t − 2 \bar{z}{t-2} zˉt−2 是合并后的新标准噪声): x t = α t α t − 1 x t − 2 + 1 − α t α t − 1 z ˉ t − 2 x_t = \sqrt{\alpha_t \alpha{t-1}} x_{t-2} + \sqrt{1 - \alpha_t \alpha_{t-1}} \bar{z}_{t-2} xt=αtαt−1 xt−2+1−αtαt−1 zˉt−2
注意:合并两个独立的高斯噪声项时,本质上是将两个正态分布叠加成一个新的正态分布。由于前两项合并后的总方差是 1 − α t α t − 1 1 - \alpha_t\alpha_{t-1} 1−αtαt−1,为了在公式结构上保持"标准差 × \times × 标准正态分布"的统一形式,我们需要把这个总方差开根号作为系数提出来。提完系数后,剩下的部分自然就是一个均值为 0、方差为 1 的纯标准正态分布。
第四步:不断递归直到第 0 步如果我们重复这个展开和合并噪声的过程,一直倒推到 x 0 x_0 x0,结果中的信号系数就会变成所有 α \alpha α 的乘积,而噪声系数的平方(方差)加上信号系数的平方始终等于 1: x t = α t α t − 1 ... α 1 x 0 + 1 − α t α t − 1 ... α 1 ϵ x_t = \sqrt{\alpha_t \alpha_{t-1} \dots \alpha_1} x_0 + \sqrt{1 - \alpha_t \alpha_{t-1} \dots \alpha_1} \epsilon xt=αtαt−1...α1 x0+1−αtαt−1...α1 ϵ第五步:引入累积乘积符号用 α ˉ t = ∏ i = 1 t α i \bar{\alpha}t = \prod{i=1}^t \alpha_i αˉt=∏i=1tαi 替换连乘部分,就得到了最终公式: x t = α ˉ t x 0 + 1 − α ˉ t ϵ x_t = \sqrt{\bar{\alpha}_t}x_0 + \sqrt{1 - \bar{\alpha}_t}\epsilon xt=αˉt x0+1−αˉt ϵ
这里多递归几次就会发现每递归一次信号项前面的系数 会多乘上一个 α i \alpha_i αi,也就是所有经历过的 α \alpha α 的连乘的规律。
注意
- 在数学推导中,符号 z z z 和 ϵ \epsilon ϵ 虽然都代表服从标准正态分布 N ( 0 , I ) \mathcal{N}(0, \mathbf{I}) N(0,I) 的随机噪声,但它们被用来严格区分独立单步噪声与等效累积噪声。
z t − 1 z_{t-1} zt−1 代表独立采样的单步噪声:在一步一步的扩散过程中,每一次加噪引入的都是全新的、互不相关的随机变量。我们必须用 z t − 1 z_{t-1} zt−1、 z t − 2 z_{t-2} zt−2 这样的下标来强调它们是彼此独立的。如果整个公式都只用一个符号(比如全写成 z z z),在数学表达式上就意味着你每一步都在添加一模一样的那个噪声矩阵,这违背了扩散模型的随机性设定。 - ϵ \epsilon ϵ 代表合并后的等效总噪声:推导过程的核心是把多个独立的单步噪声( z t − 1 , z t − 2 ... z_{t-1}, z_{t-2} \dots zt−1,zt−2...)通过"正态分布的可加性"合并成一个单一的正态分布。这个合并出来的新随机变量依然服从 N ( 0 , I ) \mathcal{N}(0, \mathbf{I}) N(0,I)。引入一个全新的符号 ϵ \epsilon ϵ(有些论文中也会写成 z ˉ \bar{z} zˉ),是为了明确表示它是由历史上所有单步噪声线性组合而成的一个全新代表,而不是某一个具体的 z i z_i zi。简而言之, z z z 强调的是"过程"中每一步产生的独立微小扰动,而 ϵ \epsilon ϵ 强调的是"结果"中那个能让我们直接从 x 0 x_0 x0 一步到位计算出 x t x_t xt 的整体等效噪声。
到这个地方公式就基本推断完成了。但是还有一个疑问,如果说从 x 0 x_{0} x0加噪那么直接 x t = x 0 + ϵ x_t=x_{0} + \epsilon xt=x0+ϵ不就得了嘛,为什么搞得这么麻烦前面还得乘以系数。
- 防止方差爆炸(数值越界与网络宕机):若直接累加噪声,根据方差可加性 V a r ( x T ) = V a r ( x 0 ) + T Var(x_T) = Var(x_0) + T Var(xT)=Var(x0)+T。在1000步后,数据总方差破千,原本 − 1 , 1 -1, 1 −1,1 的像素值会膨胀几十倍。由于神经网络(如归一化层和激活函数)底层依赖"输入方差约为1"的假设,喂入尺度失控的数据会导致激活值飙升,梯度反向传播时呈指数级放大,瞬间击穿浮点数上限,导致 Loss 变为 NaN,训练直接崩溃。
- 强制方差守恒:系数设计满足 ( α ˉ t ) 2 + ( 1 − α ˉ t ) 2 = 1 (\sqrt{\bar{\alpha}_t})^2 + (\sqrt{1-\bar{\alpha}_t})^2 = 1 (αˉt )2+(1−αˉt )2=1。这保证了无论叠加多少次噪声,新数据的整体方差始终被按比例压缩并稳定在 1 附近,为模型提供极其稳定的训练基准线。
- -平滑过渡到纯噪声:随着 t t t 增加,信号系数 α ˉ t \bar{\alpha}_t αˉt 逐渐趋近于 0。原图信息被按比例平滑"褪色清零",噪声平滑占据主导,确保最终状态 x T x_T xT 完美收敛于标准正态分布 N ( 0 , I ) \mathcal{N}(0, \mathbf{I}) N(0,I),为逆向去噪提供确定的数学起点。
2. 训练:让神经网络预测加入的噪声
我们希望模型看到带噪数据 x t x_t xt 和时间步 t t t,就能预测其中的噪声:
ϵ ^ = ϵ θ ( x t , t ) \hat{\epsilon} = \epsilon_\theta(x_t, t) ϵ^=ϵθ(xt,t)
其中, ϵ θ \epsilon_\theta ϵθ 是神经网络。输入 t t t 是为了告诉它:当前数据大概被破坏到了什么程度。
一次训练的基本流程是:
- 从训练集取出一个真实样本 x 0 x_0 x0。
- 随机选一个时间步 t t t,再随机采样噪声 ϵ \epsilon ϵ。
- 按照该时间步对应的噪声强度,构造 x t x_t xt。
- 让网络预测噪声,并与实际使用的噪声比较。
常用的训练损失就是均方误差:
L = E x 0 , t , ϵ ∥ ϵ − ϵ θ ( x t , t ) ∥ 2 \mathcal{L} = \mathbb{E}_{x_0, t, \epsilon} \left \\left\\\| \\epsilon - \\epsilon_\\theta(x_t, t) \\right\\\|\^2 \\right L=Ex0,t,ϵ∥ϵ−ϵθ(xt,t)∥2
因为噪声是我们自己添加的,所以训练目标天然已知,不需要额外人工标注。而且可以直接从 x 0 x_0 x0 构造任意时间步的 x t x_t xt,训练时不必真的逐步加噪。
这个步骤比较明确,就是训练,损失函数也没有什么特别的。就是预测噪声跟真实的噪声进行比较训练网络。也就是说经典的DDPM模型不直接去预测清晰的原图 x 0 x_0 x0,而是根据输入的带噪图像 x t x_t xt 和时间步 t t t,估算出里面掺杂的噪声成分。一旦准确预测出了这部分噪声,在逆向生成阶段,我们就可以在数学上将其从 x t x_t xt 中"剥离"出来,一步步倒推还原出清晰的图像。接下来就是比较重要的一个小节生成。
3. 生成:从随机噪声出发,逐步形成新样本
训练完成后,我们不再输入真实样本,而是先随机生成:
x T ∼ N ( 0 , I ) x_T \sim \mathcal{N}(0, \mathbf{I}) xT∼N(0,I)
然后从 t = T t = T t=T 开始,反复调用网络,根据预测的噪声计算去噪更新,逐渐得到 x T − 1 , x T − 2 , ... , x 0 x_{T-1}, x_{T-2}, \dots, x_0 xT−1,xT−2,...,x0。
这里有两个关键点:
- 模型并不是在找回某个指定的训练样本,而是在生成一个符合所学分布的新样本。
- 去噪并非简单地"减掉全部预测噪声";具体更新包含与时间步有关的系数,在经典 DDPM 中,中间步骤通常还会加入一定的随机噪声。
这个步骤看着还是有点抽象,模型训练的只是预测噪声的,那么如何生成呢,随机从 t = T t=T t=T采样的也是噪声,拿噪声预测噪声非常奇怪,我们在下一个小节继续展开。