本文深入解析 DDPM(扩散概率模型) 和 DDIM(去噪扩散隐式模型) 的核心原理与推导过程。从DDPM的前向扩散与反向生成出发,逐步过渡到DDIM的确定性采样方法,详细推导其数学公式,并对比两者的差异与优势。
从DDPM到DDIM
[TOC]
✅ 一、DDPM回顾:前向扩散 & 反向生成
我们从 DDPM 的基本构建出发,它定义了两个过程:
1. 前向过程(加噪声):
我们从真实图像 $x_0 \sim q(x_0)$,在每个时间步 $t$ 向其添加高斯噪声: \(q(x_t | x_{t-1}) = \mathcal{N}(x_t; \sqrt{1 - \beta_t} x_{t-1}, \beta_t \mathbf{I})\) 可以一口气写出: \(q(x_t | x_0) = \mathcal{N}(x_t; \sqrt{\bar{\alpha}_t} x_0, (1 - \bar{\alpha}_t) \mathbf{I})\) 其中:
- $\alpha_t = 1 - \beta_t$
- $\bar{\alpha_t}=\prod_{s=1}^{t}\alpha_s$
2. 反向过程(去噪):
DDPM 训练一个模型预测噪声 $\epsilon_\theta(x_t, t)$,然后使用如下方式一步一步生成图像: \(p_\theta(x_{t-1} | x_t) = \mathcal{N}(x_{t-1}; \mu_\theta(x_t, t), \sigma_t^2 \mathbf{I})\) 其中 $\mu_\theta$ 是从预测噪声和 $x_t$ 计算出的均值。
✅ 二、DDIM 的提出:从随机 → 确定性
DDPM 的采样过程是随机的,需要采样高斯噪声。DDIM 提出一种 确定性 的反向过程(无需每步采样噪声),并可以“跳步采样”,提高效率。
✅ 三、DDIM的推导过程
DDIM 的采样不再用随机变量,而是设计一个新的确定性过程: \(x_{t-1} = \sqrt{\bar{\alpha}_{t-1}} x_0 + \sqrt{1 - \bar{\alpha}_{t-1}} \cdot \eta\) 其中 $\eta$ 是控制采样轨迹的变量。关键在于我们使用网络预测的噪声 $\hat{\epsilon} = \epsilon_\theta(x_t, t)$ 来估计 $x_0$:
Step 1:从预测噪声恢复 $x_0$
根据前向过程的解析式: \(x_t = \sqrt{\bar{\alpha}_t} x_0 + \sqrt{1 - \bar{\alpha}_t} \epsilon \Rightarrow x_0 = \frac{1}{\sqrt{\bar{\alpha}_t}} \left( x_t - \sqrt{1 - \bar{\alpha}_t} \cdot \epsilon \right)\)
Step 2:重构 DDIM 的采样公式
DDIM 构造了如下形式的采样路径(确定性): \(x_{t-1} = \sqrt{\bar{\alpha}_{t-1}} x_0 + \sqrt{1 - \bar{\alpha}_{t-1}} \cdot \epsilon_{\text{ddim}}\)
然后引入一个 采样控制参数 $\eta \in [0, 1]$,表示采样的“温度”或“随机程度”。当 $\eta = 0$ 时是纯确定性采样。
最终,DDIM的采样公式如下: \(x_{t-1} = \sqrt{\bar{\alpha}_{t-1}} x_0 + \sqrt{1 - \bar{\alpha}_{t-1} - \eta^2 \sigma_t^2} \cdot \hat{\epsilon} + \eta \cdot \sigma_t \cdot z\) 其中:
- $x_0$ 是从 $x_t$ 和 $\hat{\epsilon}$ 反推出来的;
- $\hat{\epsilon} = \epsilon_\theta(x_t, t)$ 是模型预测的噪声;
- $\sigma_t^2$ 是 DDPM 的原始噪声;
- $z \sim \mathcal{N}(0, I)$,当 $\eta = 0$ 时不加入噪声,就是确定性采样。
⚠️ 你可以理解为:DDIM 相当于控制噪声加入比例,让采样可以变得快、稳,还可控。
✅ 四、$\eta$ 特殊情况
$\eta$ 值 | 效果 |
---|---|
$\eta = 1$ | 就是原始的 DDPM 采样(随机) |
$\eta = 0$ | 完全确定性采样(DDIM 核心) |
$0 < \eta < 1$ | 半随机,可调控采样路径 |
✅ 五、DDIM 的优势
- 采样速度快:可以只用 25~50 步就生成不错的图像(而不是 DDPM 的 1000 步);
- 更可控:由于是确定性的采样路径,可以做 interpolation、图像编辑等;
- 可逆路径:DDIM 的采样路径是可逆的,有利于更稳定的图像生成。
✅ 六、总结
DDIM 是在保持生成质量的前提下,把 DDPM 的随机反向采样变成了可控、可逆的确定性采样,提升了效率与稳定性。
🧠 DDIM 重构采样公式推导笔记
一、背景概念
- Markov 假设(标准 DDPM),扩散过程满足马尔可夫性质:
但在采样重构时,我们关注的是:
$p(x_s | x_k, x_0)$
目标是构造一个非马尔可夫的采样过程,在保持边缘分布不变的同时,实现更高效的采样。
二、基本公式构造
我们希望通过公式构造出: \(p(x_s | x_k, x_0) \sim \mathcal{N}(\mu, \Sigma)\) 由公式展开: \(p(x_s | x_k, x_0) = \frac{p(x_k | x_s, x_0) \cdot p(x_s | x_0)}{p(x_k | x_0)}\) 重点在于构造 $x_s$ 的显式表达式。
三、推导核心部分
假设中间噪声采样: \(x_k = \sqrt{\bar{\alpha}_k} x_0 + \sqrt{1 - \bar{\alpha}_k} \epsilon', \quad \epsilon' \sim \mathcal{N}(0, I)\) 我们定义: \(x_s = k x_0 + m x_k + \delta \epsilon\)
将 $x_k$ 带入上式: \(x_s = k x_0 + m (\sqrt{\bar{\alpha}_k} x_0 + \sqrt{1 - \bar{\alpha}_k} \epsilon') + \delta \epsilon\) 整理得到: \(x_s = (k + m\sqrt{\bar{\alpha}_k}) x_0 + m \sqrt{1 - \bar{\alpha}_k} \epsilon' + \delta \epsilon\) 由于 $\epsilon’ \sim \mathcal{N}(0, I), \epsilon \sim \mathcal{N}(0, I)$,合并两项噪声,得到总方差: \(x_s = \underbrace{(k + m\sqrt{\bar{\alpha}_k})}_{\sqrt{\bar{\alpha}_s}} x_0 + \underbrace{\sqrt{m^2(1 - \bar{\alpha}_k) + \delta^2}}_{\sqrt{1 - \bar{\alpha}_s}} \epsilon\)
定义采样中用到的中间变量 $m$ 和 $k$:
\[m = \frac{\sqrt{1 - \bar{\alpha}_s} - \sigma^2}{\sqrt{1 - \bar{\alpha}_k}}, \quad k = \frac{\sqrt{\bar{\alpha}_s} - \sqrt{1 - \bar{\alpha}_s - \sigma^2}}{\sqrt{1 - \bar{\alpha}_k}} \sqrt{\bar{\alpha}_k}\]采样分布为高斯分布,其均值部分为:
\(\mu = m x_k + k x_0\) 将 $m, k$ 展开整理后,可以写为:
\[\mu = \frac{\sqrt{1 - \bar{\alpha}_s - \sigma^2}}{\sqrt{1 - \bar{\alpha}_k}} x_k + \sqrt{\bar{\alpha}_s} x_0 - \frac{\sqrt{1 - \bar{\alpha}_s - \sigma^2}}{\sqrt{1 - \bar{\alpha}_k}} \sqrt{\bar{\alpha}_k} x_0\]将上式合并后,得到最终形式:
\[x_s \sim \mathcal{N} \left( \sqrt{\bar{\alpha}_s} x_0 + \frac{\sqrt{1 - \bar{\alpha}_s - \sigma^2}}{\sqrt{1 - \bar{\alpha}_k}} (x_k - \sqrt{\bar{\alpha}_k} x_0), \sigma^2 I \right)\]这是 DDIM 非马尔可夫跳跃采样的核心采样公式。
- 当 $s = k - 1$ 时,退化为普通的马尔可夫采样(DDPM)。
- 当 $s < k - 1$ 时,即为跳跃式非马尔可夫采样路径(DDIM)。
- 当 $\sigma = 0$ 时,表示确定性采样(no noise)。
- 参数 $\bar{\alpha_s}$,$\bar{\alpha_k}$ 是累积噪声系数:
$\bar{\alpha_t} = \prod_{i=1}^t\alpha_i$
💡 该公式广泛用于 DDIM/PLMS/DPM 等推理加速方法中,是所有加速型扩散模型的核心公式之一。
🔥 DDPM与DDIM对比
“DDPM 也预测了从 $x_t$ 到 $x_0$ 的噪声(ε),那不也算是用到了 $x_0$ 吗?那为什么它还是马尔可夫过程?”
虽然 DDPM 的模型也预测了 $\epsilon_\theta(x_t, t)$(可以还原出 $x_0$), 但 DDPM 的采样步骤 并 没有用到预测出来的 $x_0$ —— 它只是用预测的噪声来生成下一步的 $x_{t-1}$,而不是先还原出 $x_0$ 再插值回去的!
✅ DDPM 的采样过程(马尔可夫)
从 $x_t$ 到 $x_{t-1}$ 的采样是: \(x_{t-1} = \mu_\theta(x_t, t) + \sigma_t \cdot z,\quad z \sim \mathcal{N}(0, I)\) 其中: \(\mu_\theta(x_t, t) = \frac{1}{\sqrt{\alpha_t}} \left( x_t - \frac{1 - \alpha_t}{\sqrt{1 - \bar{\alpha}_t}} \cdot \epsilon_\theta(x_t, t) \right)\)
-
是从一个**高斯分布 $p(x_{t-1} x_t)$** 采样出来的 - 只依赖 $x_t$ 和 $\epsilon_\theta(x_t, t)$
- 虽然预测了 ε,其目的只是为了构造这个高斯分布的均值而已
- 你并不会明确地计算 $x_0$,也不会拿它去插值
所以整个过程还是“你现在在哪、下一步往哪走” —— 纯粹的马尔可夫!
❌ 而 DDIM 是这样的
你直接算: \(x_{t-1} = \sqrt{\bar{\alpha}_{t-1}} \cdot x_0 + \sqrt{1 - \bar{\alpha}_{t-1}} \cdot \epsilon_\theta(x_t, t)\) 其中: \(x_0 = \frac{1}{\sqrt{\bar{\alpha}_t}} \left( x_t - \sqrt{1 - \bar{\alpha}_t} \cdot \epsilon_\theta(x_t, t) \right)\)
$x_t \xrightarrow{\epsilon_\theta}\hat{x}0\xrightarrow{\text{插值}}x{t-1}$ ➡️ 明显构造了一个“先预测目标,再回退路径”的结构,破坏了马尔可夫性
🔄 总结
DDPM 的 ε 是为了构造局部高斯转移分布, 而 DDIM 的 ε 是为了还原目标 $x_0$,再用于全局插值退回路径
即使两者都预测了 ε,但它们的用途和结构依赖关系完全不同:
特性 | DDPM | DDIM |
---|---|---|
是否预测 ε | ✅ 是 | ✅ 是 |
ε 的用途 | 构造 $p(x_{t-1}|x_t)$的均值 | 还原 $x_0$ |
是否显式用 $x_0$ | ❌ 否 | ✅ 是 |
是否马尔可夫过程 | ✅ 是 | ❌ 否 |
本质对应 | 随机过程(SDE) | 确定性过程(ODE) |
🔥核心代码
DDPM和DDIM只是在采样时不同,训练的时候是完全一样的!!
import torch
from forward_noising import (
get_index_from_list,
sqrt_one_minus_alphas_cumprod,
betas,
posterior_variance,
sqrt_recip_alphas,
alphas_cumprod,
alphas_cumprod_prev
)
import matplotlib
matplotlib.use('Agg') # 使用非交互式后端
import matplotlib.pyplot as plt
from dataloader import show_tensor_image
from unet import SimpleUnet
from forward_noising import T
from training_model import epochs
import numpy as np
@torch.no_grad()
def sample_timestep_ddim(model, x, t, eta=0.0):
"""
DDIM deterministic sampling step.
"""
alpha_cumprod_t = get_index_from_list(alphas_cumprod, t, x.shape)
alpha_cumprod_t_prev = get_index_from_list(alphas_cumprod_prev, t, x.shape)
sqrt_alpha_cumprod_t = torch.sqrt(alpha_cumprod_t)
sqrt_alpha_cumprod_t_prev = torch.sqrt(alpha_cumprod_t_prev)
sqrt_one_minus_alpha_cumprod_t = torch.sqrt(1 - alpha_cumprod_t)
# Predict noise
eps_theta = model(x, t)
# Predict x0
x0_pred = (x - sqrt_one_minus_alpha_cumprod_t * eps_theta) / sqrt_alpha_cumprod_t
x0_pred = torch.clamp(x0_pred, -1.0, 1.0)
# Compute sigma and noise
sigma = eta * torch.sqrt(
(1 - alpha_cumprod_t_prev) / (1 - alpha_cumprod_t) *
(1 - alpha_cumprod_t / alpha_cumprod_t_prev)
)
noise = torch.randn_like(x) if eta > 0 else 0
# Compute direction pointing to x_t
dir_xt = torch.sqrt(1 - alpha_cumprod_t_prev - sigma**2) * eps_theta
x_prev = sqrt_alpha_cumprod_t_prev * x0_pred + dir_xt + sigma * noise
return x_prev
@torch.no_grad()
def sample_plot_image_ddim(model, device, img_size, ddim_timesteps, eta=0.0):
img = torch.randn((1, 3, img_size, img_size), device=device)
plt.figure(figsize=(15, 15))
plt.axis("off")
num_images = 10
stepsize = max(1, len(ddim_timesteps) // num_images)
for i, step in enumerate(reversed(ddim_timesteps)):
t = torch.tensor([step], device=device, dtype=torch.long)
img = sample_timestep_ddim(model, img, t, eta=eta)
img = torch.clamp(img, -1.0, 1.0)
if i % stepsize == 0:
plt.subplot(1, num_images, i // stepsize + 1)
show_tensor_image(img.detach().cpu())
plt.savefig("sample_ddim_steps=%d_eta=%.1f.png" % (len(ddim_timesteps), eta))
if __name__ == "__main__":
img_size = 64
model = SimpleUnet()
device = "cuda:1" if torch.cuda.is_available() else "cpu"
print(f"Using device: {device}")
save_path = "./trained_models/ddpm_mse_epochs_%d_T_%d.pth" % (epochs, T)
model.load_state_dict(torch.load(save_path))
model.to(device)
# 设置采样步数
ddim_steps = 50
ddim_timesteps = np.linspace(0, T - 1, ddim_steps, dtype=int)
# 开始采样
sample_plot_image_ddim(model=model, device=device, img_size=img_size,
ddim_timesteps=ddim_timesteps, eta=0)