在扩散模型的发展史上,DDIM 的出现是一次绝妙的“逆向工程”。它不仅将采样速度提升了数十倍,更在理论上架起了离散马尔可夫链与连续常微分方程(ODE)之间的桥梁。

推导

一、锁定边缘分布,白嫖预训练模型

DDPM 的训练目标(损失函数 L s i m p l e L_{simple} Lsimple)有一个极其重要的特性:它只依赖于前向过程的边缘分布 q ( x t ∣ x 0 ) q(x_t | x_0) q(xtx0),而完全不关心状态是如何从 x 0 x_0 x0 一步步走到 x t x_t xt 的(即不关心联合分布 q ( x 1 : T ∣ x 0 ) q(x_{1:T} | x_0) q(x1:Tx0))。详情见上一节的 loss 计算。

DDPM 的边缘分布被严格定义为:

q ( x t ∣ x 0 ) = N ( x t ; α ˉ t x 0 , ( 1 − α ˉ t ) I ) q(x_t | x_0) = \mathcal{N}(x_t; \sqrt{\bar{\alpha}_t}x_0, (1-\bar{\alpha}_t)I) q(xtx0)=N(xt;αˉt x0,(1αˉt)I)

既然 Loss 只认这个边缘分布,那我是不是可以随便捏造一个非马尔可夫的前向过程,只要保证它在任意时刻 t t t 的边缘分布 q ( x t ∣ x 0 ) q(x_t | x_0) q(xtx0) 和 DDPM 一模一样,我就可以直接白嫖 DDPM 训练好的网络?

二、构造非马尔可夫的反向过程

为了打破只能“一步走一环”的马尔可夫链,我们直接去构造一个依赖于 x 0 x_0 x0 x t x_t xt 的反向条件分布 q σ ( x t − 1 ∣ x t , x 0 ) q_\sigma(x_{t-1} | x_t, x_0) qσ(xt1xt,x0)

我们大胆假设 x t − 1 x_{t-1} xt1 的生成机制为:

x t − 1 = a x 0 + b x t + σ t ϵ x_{t-1} = a x_0 + b x_t + \sigma_t \epsilon xt1=ax0+bxt+σtϵ

为什么可以假设它是线性高斯组合?

因为高斯分布具有极其优美的封闭性(Closure Property)。已知的 q ( x t ∣ x 0 ) q(x_t | x_0) q(xtx0) 和我们期望得到的 q ( x t − 1 ∣ x 0 ) q(x_{t-1} | x_0) q(xt1x0) 都是高斯分布。如果对高斯变量进行线性组合,其结果依然是高斯分布。因此,为了让边缘分布保持为高斯, x t − 1 x_{t-1} xt1 最自然且唯一的解空间,就是 x 0 x_0 x0 x t x_t xt 以及纯高斯噪声 ϵ \epsilon ϵ 的线性组合。

其中, ϵ ∼ N ( 0 , I ) \epsilon \sim \mathcal{N}(0, I) ϵN(0,I) 引入了方差为 σ t 2 \sigma_t^2 σt2 的随机噪声。现在的任务就是:利用待定系数法,求解出系数 a 和 b。

三、边缘一致性方程

无论我们怎么构造中间过程,它必须满足一个铁律(边缘化一致性):给定 x 0 x_0 x0 的前提下, x t − 1 x_{t-1} xt1 的分布必须严格等于 DDPM 规定的边缘分布 N ( α ˉ t − 1 x 0 , ( 1 − α ˉ t − 1 ) I ) \mathcal{N}(\sqrt{\bar{\alpha}_{t-1}}x_0, (1-\bar{\alpha}_{t-1})I) N(αˉt1 x0,(1αˉt1)I)

我们对假设的方程 x t − 1 = a x 0 + b x t + σ t ϵ x_{t-1} = a x_0 + b x_t + \sigma_t \epsilon xt1=ax0+bxt+σtϵ 求关于 x 0 x_0 x0 的条件期望和条件方差:

1. 匹配均值(一阶矩):

左边的期望必须是 α ˉ t − 1 x 0 \sqrt{\bar{\alpha}_{t-1}}x_0 αˉt1 x0

右边求期望: E [ a x 0 + b x t + σ t ϵ ∣ x 0 ] = a x 0 + b E [ x t ∣ x 0 ] = a x 0 + b α ˉ t x 0 \mathbb{E}[a x_0 + b x_t + \sigma_t \epsilon | x_0] = a x_0 + b \mathbb{E}[x_t | x_0] = a x_0 + b \sqrt{\bar{\alpha}_t} x_0 E[ax0+bxt+σtϵx0]=ax0+bE[xtx0]=ax0+bαˉt x0

令两边相等:

α ˉ t − 1 = a + b α ˉ t    ⟹    a = α ˉ t − 1 − b α ˉ t \sqrt{\bar{\alpha}_{t-1}} = a + b \sqrt{\bar{\alpha}_t} \quad \implies \quad a = \sqrt{\bar{\alpha}_{t-1}} - b \sqrt{\bar{\alpha}_t} αˉt1 =a+bαˉt a=αˉt1 bαˉt

2. 匹配方差(二阶矩):

左边的方差必须是 1 − α ˉ t − 1 1-\bar{\alpha}_{t-1} 1αˉt1

右边求方差: x 0 x_0 x0 是已知条件(方差为0), x t x_t xt 带来的条件方差是 1 − α ˉ t 1-\bar{\alpha}_t 1αˉt ϵ \epsilon ϵ 的方差是 1。由于二者独立,方差直接相加:

Var ( a x 0 + b x t + σ t ϵ ∣ x 0 ) = b 2 ( 1 − α ˉ t ) + σ t 2 \text{Var}(a x_0 + b x_t + \sigma_t \epsilon | x_0) = b^2 (1-\bar{\alpha}_t) + \sigma_t^2 Var(ax0+bxt+σtϵx0)=b2(1αˉt)+σt2

令两边相等,得到:

1 − α ˉ t − 1 = b 2 ( 1 − α ˉ t ) + σ t 2    ⟹    b = 1 − α ˉ t − 1 − σ t 2 1 − α ˉ t 1-\bar{\alpha}_{t-1} = b^2 (1-\bar{\alpha}_t) + \sigma_t^2 \quad \implies \quad b = \frac{\sqrt{1-\bar{\alpha}_{t-1}-\sigma_t^2}}{\sqrt{1-\bar{\alpha}_t}} 1αˉt1=b2(1αˉt)+σt2b=1αˉt 1αˉt1σt2

3. 组装理论公式:

将解出的 b 代回 a,然后将 a, b 一起代回我们最初的线性假设中,合并同类项,便得到了受边缘分布严格约束的理论生成公式:

x t − 1 = α ˉ t − 1 x 0 + 1 − α ˉ t − 1 − σ t 2 ⋅ x t − α ˉ t x 0 1 − α ˉ t + σ t ϵ x_{t-1} = \sqrt{\bar{\alpha}_{t-1}} x_0 + \sqrt{1 - \bar{\alpha}_{t-1} - \sigma_t^2} \cdot \frac{x_t - \sqrt{\bar{\alpha}_t}x_0}{\sqrt{1 - \bar{\alpha}_t}} + \sigma_t \epsilon xt1=αˉt1 x0+1αˉt1σt2 1αˉt xtαˉt x0+σtϵ

四、自举估算

理论公式很完美,但存在一个致命的工程悖论:反向采样时,我们是从纯噪声 x T x_T xT 开始的,根本不知道真实的 x 0 x_0 x0 是什么。

为了打破悖论,我们请出训练好的 U-Net ϵ θ ( x t , t ) \epsilon_\theta(x_t, t) ϵθ(xt,t)

根据前向加噪公式 x t = α ˉ t x 0 + 1 − α ˉ t ϵ t x_t = \sqrt{\bar{\alpha}_t}x_0 + \sqrt{1-\bar{\alpha}_t}\epsilon_t xt=αˉt x0+1αˉt ϵt,如果网络能够预测出当前的噪声,我们就可以把未知的 x 0 x_0 x0 反解(估算)出来:

x ^ 0 = x t − 1 − α ˉ t ϵ θ ( x t , t ) α ˉ t \hat{x}_0 = \frac{x_t - \sqrt{1-\bar{\alpha}_t}\epsilon_\theta(x_t, t)}{\sqrt{\bar{\alpha}_t}} x^0=αˉt xt1αˉt ϵθ(xt,t)

五、狸猫换太子

我们将估算的 x ^ 0 \hat{x}_0 x^0 强行代入第三步理论公式中所有出现真实 x 0 x_0 x0 的地方。

代入并整理后,我们得到了最终的 DDIM 采样核心表达式:

x t − 1 = α ˉ t − 1 ( x t − 1 − α ˉ t ϵ θ ( x t , t ) α ˉ t ) ⏟ 指向预测的  x ^ 0 + 1 − α ˉ t − 1 − σ t 2 ⋅ ϵ θ ( x t , t ) ⏟ 指向  x t  的方向 + σ t ϵ ⏟ 随机噪声 x_{t-1} = \underbrace{\sqrt{\bar{\alpha}_{t-1}} \left( \frac{x_t - \sqrt{1 - \bar{\alpha}_t} \epsilon_\theta(x_t, t)}{\sqrt{\bar{\alpha}_t}} \right)}_{\text{指向预测的 } \hat{x}_0} + \underbrace{\sqrt{1 - \bar{\alpha}_{t-1} - \sigma_t^2} \cdot \epsilon_\theta(x_t, t)}_{\text{指向 } x_t \text{ 的方向}} + \underbrace{\sigma_t \epsilon}_{\text{随机噪声}} xt1=指向预测的 x^0 αˉt1 (αˉt xt1αˉt ϵθ(xt,t))+指向 xt 的方向 1αˉt1σt2 ϵθ(xt,t)+随机噪声 σtϵ

这个等式展示了扩散模型采样时的“微观动力学”:

  1. 第一项(锚点):模型在当前 t t t 时刻,极力猜测最终的干净图片长什么样。它构成了去噪的主基调。
  2. 第二项(方向):因为模型知道自己的猜测可能不准,所以它不敢直接跳到 x ^ 0 \hat{x}_0 x^0,而是保留了一部分指向当前状态 x t x_t xt 的特征,作为平滑过渡。
  3. 第三项(随机性与控制) σ t \sigma_t σt 是人为引入的方差控制参数。
    • 如果令 σ t \sigma_t σt = 0,随机项消失,整个过程变成纯确定性的常微分方程 (ODE) 的一阶欧拉步。这不仅使得采样轨迹唯一可逆,更允许我们使用极大的步长 Δ t \Delta t Δt 进行跳步(加速)采样

讨论

一、关于方差

在 DDIM 的推导中, σ t \sigma_t σt 是最具魔法色彩的一个参数。

x t − 1 = (指向预测的  x ^ 0  的项) + (指向  x t  的项) + σ t ϵ x_{t-1} = \text{(指向预测的 } \hat{x}_0 \text{ 的项)} + \text{(指向 } x_t \text{ 的项)} + \sigma_t \epsilon xt1=(指向预测的 x^0 的项)+(指向 xt 的项)+σtϵ

其中, ϵ ∼ N ( 0 , I ) \epsilon \sim \mathcal{N}(0, I) ϵN(0,I)

1. σ t \sigma_t σt 到底是什么?(随机性旋钮)

在数学上, σ t \sigma_t σt 是我们在人为构造反向分布 q σ ( x t − 1 ∣ x t , x 0 ) q_\sigma(x_{t-1} | x_t, x_0) qσ(xt1xt,x0) 时,赋予每一次去噪迭代的“额外噪声的标准差”。

它代表了模型在生成过程中的随机性(Stochasticity)程度。

根据 DDIM 的理论证明,只要 σ t \sigma_t σt 的取值在合法的范围内(最大不能超过 DDPM 的方差),无论你取什么值,最终模型生成的图像分布都能逼近真实的训练数据分布。

  • 极值 A(方差拉满):如果让 σ t = 1 − α ˉ t − 1 1 − α ˉ t 1 − α ˉ t α ˉ t − 1 \sigma_t = \sqrt{\frac{1-\bar{\alpha}_{t-1}}{1-\bar{\alpha}_t}} \sqrt{1 - \frac{\bar{\alpha}_t}{\bar{\alpha}_{t-1}}} σt=1αˉt1αˉt1 1αˉt1αˉt ,这个公式就精确退化成了 DDPM。每走一步都伴随着强烈的随机扰动,生成过程是一条曲折的马尔可夫随机游走路径。
  • 极值 B(方差归零):如果我们直接令 σ t = 0 \sigma_t = 0 σt=0,也就是 纯正的 DDIM。

2. 为什么要设置为 0

最直接的数学后果就是:公式最后一项 σ t ϵ \sigma_t \epsilon σtϵ 彻底消失了!

一旦去掉了这个唯一的随机变量,整个反向采样过程就发生了质变:

它从一个随机微分方程 (SDE, Stochastic Differential Equation) 坍缩成了一个常微分方程 (ODE, Ordinary Differential Equation)。

在有随机噪声( σ t > 0 \sigma_t > 0 σt>0)的情况下,为了抵消噪声带来的剧烈抖动,你必须走极其细碎的步子(比如严格走完 1000 步),否则系统就会崩溃。

而一旦系统变成了确定的 ODE( σ t = 0 \sigma_t = 0 σt=0),变量在解空间中的轨迹就变得极其平滑和有规律。对于平滑的曲线,我们完全可以用更大的步长去近似它。我们可以直接在时间轴上“跳远”。比如只取 [ 1000 , 950 , 900 , … , 0 ] [1000, 950, 900, \dots, 0] [1000,950,900,,0] 这 20 个节点,直接用网络预测当前的梯度方向,然后大步迈过去。这种方法将采样时间缩短了 10 倍到 50 倍,同时依然能保持极高的生成质量。

同时,既然 σ t = 0 \sigma_t = 0 σt=0 让过程变成了确定性的 ODE,那么这条轨迹就是双向可逆的

  • 正向求解:给一个特定的噪声 x T x_T xT,模型永远只会生成同一张唯一确定的图片 x 0 x_0 x0

  • 反向求解(Inversion):如果你丢给模型一张真实的猫的图片 x 0 x_0 x0,你可以沿着 ODE 的轨迹倒推,完美求出能够生成这只猫的那个特定的初始噪声 x T x_T xT

    这在 DDPM 里是绝对不可能的(因为每步加的随机噪声丢了就找不回了)。有了 Inversion,我们就可以提取一张真实照片的“潜在噪声底稿”,然后修改 Prompt(比如改成“一只赛博朋克风格的猫”),再顺着 ODE 生成回来。这样不仅改变了风格,还能完美保留原图的构图和细节!

最后,DDPM 依赖随机噪声来“探索”并保持图像的高频细节,防止结果坍缩为模糊的均值;而 DDIM 通过重构概率流(ODE),在数学上保证了确定性路径也能完美匹配真实数据分布,因此不再需要随机性来“兜底”。

二、关于 ODE

我们开始将 DDIM 的离散步长推向极限,从差分方程过渡到常微分方程(ODE)。

我们回到纯确定性( σ t = 0 \sigma_t = 0 σt=0)的 DDIM 迭代公式。为了体现时间的连续性,我们把 t − 1 t-1 t1 写成 t − Δ t t-\Delta t tΔt

x t − Δ t = α ˉ t − Δ t ( x t − 1 − α ˉ t ϵ θ ( x t , t ) α ˉ t ) + 1 − α ˉ t − Δ t ϵ θ ( x t , t ) x_{t-\Delta t} = \sqrt{\bar{\alpha}_{t-\Delta t}} \left( \frac{x_t - \sqrt{1 - \bar{\alpha}_t} \epsilon_\theta(x_t, t)}{\sqrt{\bar{\alpha}_t}} \right) + \sqrt{1 - \bar{\alpha}_{t-\Delta t}} \epsilon_\theta(x_t, t) xtΔt=αˉtΔt (αˉt xt1αˉt ϵθ(xt,t))+1αˉtΔt ϵθ(xt,t)

这个公式看起来非常复杂,但是我们加以变形,寻找同构,就会变成这样:

x t − Δ t α ˉ t − Δ t = x t α ˉ t − 1 − α ˉ t α ˉ t ϵ θ ( x t , t ) + 1 − α ˉ t − Δ t α ˉ t − Δ t ϵ θ ( x t , t ) \frac{x_{t-\Delta t}}{\sqrt{\bar{\alpha}_{t-\Delta t}}} = \frac{x_t}{\sqrt{\bar{\alpha}_t}} - \frac{\sqrt{1 - \bar{\alpha}_t}}{\sqrt{\bar{\alpha}_t}} \epsilon_\theta(x_t, t) + \frac{\sqrt{1 - \bar{\alpha}_{t-\Delta t}}}{\sqrt{\bar{\alpha}_{t-\Delta t}}} \epsilon_\theta(x_t, t) αˉtΔt xtΔt=αˉt xtαˉt 1αˉt ϵθ(xt,t)+αˉtΔt 1αˉtΔt ϵθ(xt,t)

观察上面的等式,只有两种结构在变化,我们为它们定义新的符号:

  1. 归一化信号 X t X_t Xt:令 X t = x t α ˉ t X_t = \frac{x_t}{\sqrt{\bar{\alpha}_t}} Xt=αˉt xt。(物理意义:剥离了信号衰减系数后的纯图像特征)。
  2. 噪声-信号比 λ t \lambda_t λt:令 λ t = 1 − α ˉ t α ˉ t \lambda_t = \frac{\sqrt{1-\bar{\alpha}_t}}{\sqrt{\bar{\alpha}_t}} λt=αˉt 1αˉt 。(物理意义:当前时刻噪声与真实信号的比例)。

原本臃肿的公式瞬间坍缩为:

X t − Δ t = X t − λ t ϵ θ ( x t , t ) + λ t − Δ t ϵ θ ( x t , t ) X_{t-\Delta t} = X_t - \lambda_t \epsilon_\theta(x_t, t) + \lambda_{t-\Delta t} \epsilon_\theta(x_t, t) XtΔt=Xtλtϵθ(xt,t)+λtΔtϵθ(xt,t)

移项合并,得到增量形式:

X t − Δ t − X t = ( λ t − Δ t − λ t ) ϵ θ ( x t , t ) X_{t-\Delta t} - X_t = (\lambda_{t-\Delta t} - \lambda_t) \epsilon_\theta(x_t, t) XtΔtXt=(λtΔtλt)ϵθ(xt,t)

写成大家最熟悉的 Δ \Delta Δ 形式:

Δ X = ϵ θ ( x t , t ) ⋅ Δ λ \Delta X = \epsilon_\theta(x_t, t) \cdot \Delta \lambda ΔX=ϵθ(xt,t)Δλ

现在,我们让时间步长 Δ t → 0 \Delta t \to 0 Δt0(即扩散步数 T → ∞ T \to \infty T)。离散的马尔可夫链彻底变成了一个常微分方程 (ODE):

d X = ϵ θ ( x t , t ) d λ    ⟹    d X d λ = ϵ θ ( x t , t ) dX = \epsilon_\theta(x_t, t) d\lambda \quad \implies \quad \frac{dX}{d\lambda} = \epsilon_\theta(x_t, t) dX=ϵθ(xt,t)dλdλdX=ϵθ(xt,t)

这就是概率流常微分方程 (Probability Flow ODE) 的最极简形态。

思考 1:神经网络的物理身份被重新定义(U-Net = 向量场/导数)

在离散 DDPM 时期,我们一直认为 U-Net 是一个“去噪滤镜”。

但在 ODE 视角下,U-Net 本质上是在拟合一个连续空间中的“向量场(Vector Field)”或“导数”。

网络吃进去一个坐标 x_t,输出的是在这个坐标点上,数据流向真实图像分布的“斜率”或“速度方向”。生成图片的过程,就是在解空间里顺着这个向量场做积分,把一堆无序的粒子(高斯噪声)平滑地“吹”成有序的形状(真实图像)。

思考 2:“模型”与“求解器”的彻底解耦(DPM-Solver 的诞生)

训练 U-Net 只是为了“获得微分方程的解析表达式(即求导数)”。一旦网络训练好了,这个微分方程就固定了。

至于怎么求解这个微分方程?那是数值计算领域的事情。数值分析里发展了上百年的 ODE Solver 都可以直接拿来用:

  • Euler Method(一阶):这就是最基础的 DDIM,误差大,需要 50 步。
  • Heun’s Method / 预测-校正(二阶):K-Diffusion 采样器,能降到 20-30 步。
  • 高阶 Runge-Kutta / 精确解析积分(DPM-Solver, UniPC):利用泰勒展开精确计算积分项,直接把步数压缩到了惊人的 10 到 15 步

思考 3:通向 Flow Matching 和 Rectified Flow 的启蒙

既然扩散模型本质上是在学习一条从噪声到数据的 ODE 轨迹,算法研究员们很快就意识到了一个问题:DDPM 规定的这条高斯加噪轨迹,是“最优”的吗?

答案是:不。高斯加噪的轨迹是弯曲的,求解起来会有截断误差。

这就直接启发了目前最前沿的生成架构(如 Stable Diffusion 3 和 Flux 使用的底层架构):Flow Matching (流匹配) 和 Rectified Flow (校正流)。

既然都是学向量场,为什么我们不直接让噪声和真实图像之间连一条直线?沿直线的 ODE 导数是个常数,求解起来极其简单,甚至 1 步就能出图!这是 ODE 视角给整个生成式 AI 带来的下一次范式转移。

代码

学完了底层的物理直觉和数学本质后,代码真的就只是顺理成章的“翻译”工作了

import torch
import tqdm

@torch.no_grad()
def ddim_sample_loop(model, image_shape, T=1000, ddim_steps=50, eta=0.0):
    """
    DDIM 完整的跳步采样循环
    - model: 训练好的 U-Net (完全复用 DDPM 的模型)
    - T: 训练时的总步数 (如 1000)
    - ddim_steps: 我们想要的加速采样步数 (如 50)
    - eta: 随机性控制 (eta=0 就是纯 ODE 确定性采样)
    """
    device = next(model.parameters()).device
    b = image_shape[0]

    # 1. 生成确定的初始纯高斯噪声 x_T
    x = torch.randn(image_shape, device=device)

    # 2. 构造跳步的时间轴 (例如从 1000 步中均匀抽出 50 步)
    # 比如: [980, 960, 940, ..., 20, 0]
    step_ratio = T // ddim_steps
    timesteps = (torch.arange(0, ddim_steps) * step_ratio).round().long().to(device)
    timesteps = torch.flip(timesteps, dims=[0]) # 倒序排,从大到小

    # 3. 顺着微分方程的轨道,开始平滑滑行
    for i, t in enumerate(tqdm.tqdm(timesteps)):
        t_batch = torch.full((b,), t, device=device, dtype=torch.long)
        
        # 获取上一个时间步 t_prev (即走向的时间步,注意越走越小)
        t_prev = timesteps[i + 1] if i < len(timesteps) - 1 else torch.tensor(-1).to(device)
        
        # ---------- 开始执行一阶 ODE 欧拉步 (DDIM Step) ----------
        
        # A. 提取对应的 \bar{\alpha} 参数
        alpha_bar_t = extract(alphas_cumprod, t_batch, x.shape)
        if t_prev >= 0:
            alpha_bar_t_prev = extract(alphas_cumprod, torch.full((b,), t_prev, device=device), x.shape)
        else:
            alpha_bar_t_prev = torch.ones_like(alpha_bar_t) # 最后一步 t_prev=-1 时,alpha_bar 为 1

        # B. 呼叫 U-Net:求出当前位置的“梯度/向量场方向”
        pred_noise = model(x, t_batch)

        # C. 核心方程 1:估算最佳的 x_0 (锚点)
        pred_x0 = (x - torch.sqrt(1 - alpha_bar_t) * pred_noise) / torch.sqrt(alpha_bar_t)
        
        # D. 核心方程 2:计算 sigma_t
        # 当 eta=0 时,sigma_t 彻底为 0,完全没有随机性!
        sigma_t = eta * torch.sqrt((1 - alpha_bar_t_prev) / (1 - alpha_bar_t) * (1 - alpha_bar_t / alpha_bar_t_prev))
        
        # E. 核心方程 3:计算指向 x_t 的过渡方向
        dir_xt = torch.sqrt(1 - alpha_bar_t_prev - sigma_t**2) * pred_noise
        
        # F. 更新状态 (ODE 迈出一步)
        noise = torch.randn_like(x) if (t_prev >= 0 and eta > 0) else 0.0
        x = torch.sqrt(alpha_bar_t_prev) * pred_x0 + dir_xt + sigma_t * noise

    return x
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐