DDIM 深度剖析:从随机马尔可夫链到确定性隐式模型

5088 字
25 分钟
DDIM 深度剖析:从随机马尔可夫链到确定性隐式模型

1. DDPM 的致命弱点:为什么需要 DDIM?#

1.1 从 DDPM 到 DDIM 的动机#

回顾 DDPM 文章:DDPM 用一个 T=1000T = 1000 步的马尔可夫链逆向去噪,每一步都加随机噪声。生成一张 512×512 图像需要:

DDPM 生成时间 ≈ 1000 步 × 每步 ~100ms ≈ 100 秒 (GPU)

这对实际应用来说是不可接受的。核心矛盾

问题DDPM 的选择后果
逆向过程是随机的xt1N(μ,σ2)x_{t-1} \sim \mathcal{N}(\mu, \sigma^2)每步独立随机噪声,无法利用前面的信息
马尔可夫假设p(xt1xt)p(x_{t-1}\|x_t) 只依赖 xtx_t无法”跳跃”——必须一步步走
生成轨迹高度发散(随机游走)步数少时质量急剧下降

1.2 DDIM 的核心洞察#

放弃马尔可夫假设,换取确定性和速度。

Song et al. 2021 提出的 DDIM(Denoising Diffusion Implicit Models)发现:

DDPM 的核心假设"逆向过程必须是马尔可夫的"是人为加的。
如果只要求:
1. 逆向过程是"隐式概率模型"(Implicit Probabilistic Model)
2. 边际分布 q(x_t) 不变(训练数据分布不变)
那么逆向过程可以完全自由设计——包括非马尔可夫的确定性过程!

这就是”Implicit Models”(隐式模型)名称的由来:不再显式定义 p(xt1xt)p(x_{t-1}|x_t) 的概率密度,而是定义一个确定性映射 xt1=fθ(xt,t)x_{t-1} = f_\theta(x_t, t),通过 ODE 积分来生成样本。

1.3 一句话概括 DDIM#

DDIM 通过放弃”逆向过程必须是马尔可夫链”的约束,把 DDPM 的随机走路径变成确定性的直线路径——从而用 10-50 步生成与 1000 步 DDPM 相当甚至更好的图像质量。

1.4 论文信息#

论文: "Denoising Diffusion Implicit Models" (DDIM)
作者: Jiaming Song, Chenlin Meng, Stefano Ermon
单位: Stanford University
发表于: ICLR 2021
引用: > 5,000 次 (截至 2024)
开源: https://github.com/ermongroup/ddim

2. 数学框架:非马尔可夫逆向过程#

2.1 隐式概率模型的定义#

DDPM 定义了一个生成过程(马尔可夫链):

pθ(x0:T)=p(xT)t=1Tpθ(t)(xt1xt)p_\theta(x_{0:T}) = p(x_T) \prod_{t=1}^T p_\theta^{(t)}(x_{t-1} | x_t)

其中 pθ(t)p_\theta^{(t)} 是神经网络近似的逆向分布。

DDIM 则定义了一个更一般的生成过程,称为非马尔可夫链

pθ(x0:T)=p(xT)t=1Tp~θ(t)(xt1xt,x0)p_\theta(x_{0:T}) = p(x_T) \prod_{t=1}^T \tilde{p}_\theta^{(t)}(x_{t-1} | x_t, x_0)

注意:p~θ(t)\tilde{p}_\theta^{(t)} 依赖 x0x_0(不只是 xtx_t)——这打破了马尔可夫假设。

关键约束:边际分布 q(xt)q(x_t) 必须保持不变(即与前向过程定义的分布一致),否则训练目标会失效。

2.2 边际分布约束#

q(x1:Tx0)q(x_{1:T}|x_0) 是前向过程定义的联合分布。DDIM 要求生成模型的边际分布 pθ(xt)p_\theta(x_t)q(xt)q(x_t) 匹配:

pθ(xt)=q(xt)tp_\theta(x_t) = q(x_t) \quad \forall t

这是一致性约束:不管用马尔可夫还是非马尔可夫方式生成,边际分布必须一致。

q(x1:Tx0)q(x_{1:T}|x_0) 的马尔可夫链结构出发,可以推导出 q(xt1xt,x0)q(x_{t-1}|x_t, x_0) 的解析形式(贝叶斯定理):

q(xt1xt,x0)=N(xt1;μ~t(xt,x0),σ~t2I)q(x_{t-1} | x_t, x_0) = \mathcal{N}\left(x_{t-1}; \tilde{\mu}_t(x_t, x_0), \tilde{\sigma}_t^2 I\right)

其中均值和方差与 DDPM 中的逆向条件分布完全相同(来自 DDPM 文章的推导)。

2.3 DDIM 逆向过程的定义#

σt[0,1]\sigma_t \in [0, 1] 是自由参数(DDPM 中固定为 βt1/2\beta_t^{1/2})。DDIM 定义逆向过程为:

xt1=αˉt1x0+1αˉt1σt2ϵθ(xt,t)+σtϵx_{t-1} = \sqrt{\bar\alpha_{t-1}} x_0 + \sqrt{1 - \bar\alpha_{t-1} - \sigma_t^2} \cdot \epsilon_\theta(x_t, t) + \sigma_t \cdot \epsilon

其中:

  • ϵθ(xt,t)\epsilon_\theta(x_t, t) 是神经网络预测的噪声(与 DDPM 相同)
  • ϵN(0,I)\epsilon \sim \mathcal{N}(0, I) 是可选的随机噪声
  • σt=0\sigma_t = 0 时,过程完全确定性——这就是 DDIM 的核心

2.4 三种特殊情形#

情形σt\sigma_t 取值过程类型采样步数影响
DDPM(完全随机)σt=1αˉt1\sigma_t = \sqrt{1 - \bar\alpha_{t-1}}随机马尔可夫必须多步,质量急剧下降
DDIM(完全确定)σt=0\sigma_t = 0确定性非马尔可夫步数减少时质量下降最慢
插值(部分随机)0<σt<1αˉt10 < \sigma_t < \sqrt{1 - \bar\alpha_{t-1}}随机非马尔可夫介于两者之间
def ddim_reverse_step(xt, eps_theta, t, t_prev, sigma_t):
"""
DDIM 单步逆向过程。
参数:
xt: 当前位置 x_t
eps_theta: 神经网络预测的噪声
t: 当前时间步 (整数)
t_prev: 上一个时间步 (整数)
sigma_t: 随机性参数 [0, 1]
"""
# 从 xt 估计 x0
alpha_bar_t = alphas_cumprod[t]
sqrt_ab = torch.sqrt(alpha_bar_t)
sqrt_1m = torch.sqrt(1 - alpha_bar_t)
x0_pred = (xt - sqrt_1m * eps_theta) / sqrt_ab
# 计算 x_{t-1}
alpha_bar_prev = alphas_cumprod[t_prev]
sqrt_ab_prev = torch.sqrt(alpha_bar_prev)
sqrt_1m_prev = torch.sqrt(1 - alpha_bar_prev)
# 确定性方向(始终存在)
pred_eps = eps_theta
x_prev_deterministic = sqrt_ab_prev * x0_pred + sqrt_1m_prev * pred_eps
# 随机性方向(sigma_t > 0 时加入)
if sigma_t > 0:
noise = torch.randn_like(xt)
x_prev = x_prev_deterministic + sigma_t * noise
else:
x_prev = x_prev_deterministic
return x_prev

3. 轨迹视图:从 SDE 到 ODE#

3.1 把 DDPM 看作 SDE#

连续时间下,DDPM 的前向过程是一个随机微分方程(SDE)

dx=f(x,t)dt+g(t)dwdx = f(x, t) dt + g(t) dw

其中 dwdw 是维纳过程(Wiener process),引入随机性。

DDPM 的逆向过程(在连续时间下)对应 SDE 的反向:

dxˉ=[f(xˉ,t)g(t)2xlogpt(xˉ)]dt+g(t)dwˉd\bar{x} = \left[ f(\bar{x}, t) - g(t)^2 \nabla_x \log p_t(\bar{x}) \right] dt + g(t) d\bar{w}

3.2 把 DDIM 看作 ODE#

σt=0\sigma_t = 0 时,随机项消失,DDIM 变成常微分方程(ODE)

dxdt=[f(x,t)12g(t)2xlogpt(x)]\frac{dx}{dt} = \left[ f(x, t) - \frac{1}{2} g(t)^2 \nabla_x \log p_t(x) \right]

ODE 的好处

SDE (DDPM): 随机轨迹,每步独立,无法加速
ODE (DDIM): 确定性轨迹,可以用 ODE 求解器加速
- 自适应步长
- 高阶积分(Heun, RK45)
- 逆向轨迹固定,步数减少时误差可预测

3.3 ODE 求解器与采样#

@torch.no_grad()
def ddim_ode_sampler(model, num_steps=50, eta=0.0):
"""
DDIM ODE 采样器(确定性)。
eta = 0 → 完全确定性 DDIM
eta = 1 → 等效 DDPM(随机)
"""
T = len(alphas_cumprod) - 1
shape = (batch_size, channels, height, width)
# 从纯噪声开始
x = torch.randn(*shape, device=device)
# 生成时间步序列(均匀或非线性)
# 非线性调度: t 越接近 0,变化越慢
ts = torch.linspace(1.0, 0.0, num_steps + 1)
for i in range(num_steps):
t = int(ts[i] * T) # 当前时间步
t_prev = int(ts[i + 1] * T) # 上一个时间步
eps_theta = model(x, t)
# sigma_t: 由 eta 控制
# eta = 0 → 完全确定
# eta = 1 → 随机性等于 DDPM
sigma_t = eta * ((1 - alphas_cumprod[t_prev] / alphas_cumprod[t]) ** 0.5) \
* ((1 - alphas_cumprod[t] / alphas_cumprod[t_prev]) ** 0.5)
alpha_bar_t = alphas_cumprod[t]
alpha_bar_prev = alphas_cumprod[t_prev]
sqrt_ab = alpha_bar_t ** 0.5
sqrt_1m = (1 - alpha_bar_t) ** 0.5
sqrt_ab_prev = alpha_bar_prev ** 0.5
sqrt_1m_prev = (1 - alpha_bar_prev) ** 0.5
# 估计 x_0
x0_pred = (x - sqrt_1m * eps_theta) / sqrt_ab
# 预测 x_{t-1}
x_pred = sqrt_ab_prev * x0_pred + sqrt_1m_prev * eps_theta
# 加入随机性(仅当 eta > 0)
if sigma_t > 0:
std = sigma_t * ((1 - alpha_bar_prev) ** 0.5)
x_pred = x_pred + std * torch.randn_like(x)
x = x_pred
return x

3.4 自适应步长求解器#

from scipy.integrate import solve_ivp
def ddim_neural_ode_sampler(model, rtol=1e-5, atol=1e-6):
"""
用 scipy 的自适应 ODE 求解器(精度驱动的采样)。
与固定步数相比:
- 在低噪声区域(t→0)自动用更小步长
- 在高噪声区域(t→T)自动用更大步长
- 总步数可能更少,精度更高
"""
dim = channels * height * width
def ode_function(t_float, x_flat):
"""
t_float ∈ [0, 1]: 连续时间(0=数据,1=噪声)
这里用反向时间,t=0 是噪声,t=1 是数据
"""
t_int = int((1.0 - t_float) * (T - 1))
t_int = max(0, min(T - 1, t_int))
x = torch.tensor(x_flat.reshape(1, channels, height, width),
device=device, dtype=torch.float32)
eps = model(x, t_int)
alpha_bar = alphas_cumprod[t_int]
sqrt_ab = alpha_bar ** 0.5
sqrt_1m = (1 - alpha_bar) ** 0.5
x0_pred = (x - sqrt_1m * eps) / sqrt_ab
# dx/dt = -(预测方向) / T
# 近似: 速度场方向
dx_dt = -(eps - x0_pred).cpu().numpy().flatten() / T
return dx_dt
x0 = torch.randn(1, channels, height, width).cpu().numpy().flatten()
sol = solve_ivp(
ode_function,
t_span=(0.0, 1.0),
y0=x0,
method='RK45',
rtol=rtol, atol=atol,
dense_output=True,
)
final = sol.y[:, -1].reshape(channels, height, width)
return torch.from_numpy(final).to(device).unsqueeze(0)

4. 与 DDPM 的关系:严格的数学联系#

4.1 训练目标完全相同#

最重要的事实:DDIM 的训练目标与 DDPM 完全相同

L=Et,x0,ϵϵϵθ(xt,t)2\mathcal{L} = \mathbb{E}_{t, x_0, \epsilon} \left\| \epsilon - \epsilon_\theta(x_t, t) \right\|^2

这意味着:

  • 不需要重新训练 DDPM 模型——已有的 DDPM checkpoint 可以直接用于 DDIM
  • DDIM 只是不同的采样策略,不是新的训练方法

4.2 采样分布的数学联系#

qσ(xt1xt,x0)q_\sigma(x_{t-1}|x_t, x_0) 是 DDIM 定义的逆向分布,q(xt1xt,x0)q(x_{t-1}|x_t, x_0) 是 DDPM 的真实逆向分布。

定理:对任意 σt\sigma_t,DDIM 的边际分布与 DDPM 的边际分布相同:

qσ(xt1xt,x0)q(xtx0)dx0=q(xt1x0)\int q_\sigma(x_{t-1}|x_t, x_0) q(x_t|x_0) dx_0 = q(x_{t-1}|x_0)

这就是 DDIM 的”隐式”特性——不显式定义概率密度函数,但边际分布自动保持一致

4.3 采样轨迹的几何对比#

采样轨迹对比 (x_0 → x_T 方向为正向)
DDPM (随机马尔可夫):
x_0 ──→ x_1 ──→ x_2 ──→ ... ──→ x_T
↑ ↑ ↑
随机 随机 随机
发散 发散 发散
→ 每步独立随机偏移
→ 步数少时轨迹高度发散
DDIM (确定性非马尔可夫):
x_0 ────────→ x_{S_1}
↑ ↑ 跳步
x_{S_2} ←───────────────
↑ 跳步
x_{S_3} ←───────────────
...
x_T = x_{S_k}
→ 确定性轨迹,每步连续
→ 跳步后仍沿同一条光滑曲线
→ 步数减少时轨迹仍光滑

4.4 为什么确定性更好?#

直觉解释:

DDPM 每步: x_{t-1} = μ + 随机噪声
↑ 每个时间步的随机偏移是独立的
→ 走 N 步后,总偏移 ~ √N × 单步偏移(随机游走)
→ 步数减半,偏移翻倍,误差爆炸
DDIM 每步: x_{t-1} = F(x_t) (确定性函数)
↑ 跳步仍沿同一光滑 ODE 曲线
→ 走 N 步 = 直接求 ODE 的 N 个点
→ 步数减半,只是不采样中间点,不影响精度
→ 误差可预测(由 ODE 求解器控制)

5. 采样调度策略#

5.1 均匀时间步调度#

最简单的策略:将 [0,T][0, T] 均匀分割:

def uniform_timestep_schedule(num_steps, T=1000):
"""均匀调度。"""
return torch.linspace(T - 1, 0, num_steps).long().tolist()

5.2 DDIM 论文推荐的调度#

论文发现,噪声强度越大(tt 越接近 TT),ODE 变化越剧烈。因此建议用非线性调度,在高噪声区域用更多步:

def ddim_timestep_schedule(num_steps, T=1000, eta=0.0):
"""
DDIM 论文推荐的时间步调度。
核心思想:
- 高噪声区域 (t > 100): 变化剧烈,多放步
- 低噪声区域 (t < 100): 变化平缓,少放步
"""
# 从 [1, T] 均匀选 num_steps 个
steps = torch.arange(0, T, T // num_steps).flip(0)
# 如果步数不够 T,调整最后一步
if len(steps) < num_steps:
steps = torch.cat([steps, torch.tensor([0])])
return steps[:num_steps].long().tolist()

5.3 重要性采样:跳过 vs 插值#

DDIM 支持两种子序列策略:

策略方法效果
跳步 (skip)跳过中间时间步,只采样子序列更直接,减少步数
插值 (interpolate)在相邻时间步之间插值平滑过渡,更稳定
def get_ddim_schedule(num_steps, T=1000, method="skip"):
"""
生成 DDIM 子序列。
skip: [999, 949, 899, ..., 49, 0]
interpolate: [999, 997, 995, ..., 5, 3, 1]
"""
if method == "skip":
# 每 T/num_steps 步跳一次
step_size = T // num_steps
schedule = list(range(T - 1, -1, -step_size))
elif method == "interpolate":
# 均匀分布 + 线性插值
uniform = torch.linspace(T - 1, 0, num_steps)
schedule = uniform.round().long().tolist()
return schedule

5.4 噪声调度的敏感性#

def sensitivity_analysis():
"""
不同 eta 和 num_steps 的 FID 表现(经验值)。
来自 DDIM 论文实验。
"""
# num_steps=100 时
configs = [
(100, 0.0, "DDIM (完全确定)"), # FID ~5.8
(100, 0.5, "DDIM (部分随机)"), # FID ~6.2
(100, 1.0, "DDPM (完全随机)"), # FID ~20.0+
]
# num_steps=20 时
configs_20 = [
(20, 0.0, "DDIM (完全确定)"), # FID ~7.5
(20, 0.5, "DDIM (部分随机)"), # FID ~12.0
(20, 1.0, "DDPM (完全随机)"), # FID ~100+ (几乎无法用)
]
return configs, configs_20

关键发现η=0\eta = 0(完全确定)在所有步数下都优于 η=1\eta = 1(完全随机)。这是 DDIM 最重要的实践结论。

6. 轨迹坍缩与多样性#

6.1 轨迹坍缩现象#

DDIM 的 η=0\eta = 0(完全确定性)有一个副作用:多样性降低

多样性 vs 确定性 trade-off:
高多样性 (η=1, DDPM):
提示: "a cat on a sofa"
生成: [猫1], [猫2], [猫3], ... [猫N]
每张猫都不同(颜色、姿势、背景)
低多样性 (η=0, DDIM):
提示: "a cat on a sofa"
生成: [猫A], [猫A], [猫A], ... [猫A]
几乎完全相同(只有微小随机性来自初始噪声)
原因: 确定性映射 f_θ: x_t → x_{t-1} 是固定的
相同的初始噪声 x_T → 相同的生成结果
初始噪声的多样性 = 生成的多样性

6.2 数学解释#

随机 DDPM 的多样性来源

每一步逆向过程都加随机噪声:

xt1=μt+σtϵtx_{t-1} = \mu_t + \sigma_t \cdot \epsilon_t

这意味着 xt1x_{t-1} 的分布是”展宽”的(convolution),引入额外随机性。

确定性 DDIM 的多样性来源

无随机噪声:

xt1=μtdetx_{t-1} = \mu_t^{\text{det}}

只有初始噪声 xTN(0,I)x_T \sim \mathcal{N}(0, I) 引入多样性。

6.3 解决方案:调整 η\eta#

def trade_off_eta():
"""
eta 对多样性-质量 trade-off 的影响。
η = 0.0: 质量最高,多样性最低(轨迹坍缩)
η = 0.3: 质量高,多样性适中(推荐默认值)
η = 0.5: 质量中等,多样性中等
η = 1.0: 质量低(步数少时),多样性最高
"""
pass

6.4 不同任务的最佳 η\eta#

任务推荐 η\eta理由
高保真生成0.00.0质量优先,不在乎多样性
多样化生成0.30.50.3 \sim 0.5平衡质量和多样性
图像编辑0.00.0确定性保证编辑一致性
Inpainting0.00.0需要精确控制
视频生成0.00.20.0 \sim 0.2时间一致性优先

7. 图像生成实验结果#

7.1 步数 vs 质量#

def cifar10_results():
"""
CIFAR-10 32x32 上的 FID vs 采样步数。
"""
results = {
# (num_steps, eta): FID
(1000, 1.0): 3.85, # DDPM baseline
(100, 0.0): 4.20, # DDIM 100步确定
(50, 0.0): 4.60, # DDIM 50步确定
(20, 0.0): 5.90, # DDIM 20步确定
(10, 0.0): 8.10, # DDIM 10步确定
(5, 0.0): 15.20, # DDIM 5步确定
(100, 1.0): 25.50, # DDPM 100步随机(很差!)
(50, 1.0): 55.00, # DDPM 50步随机(几乎失败)
}
return results
def imagenet_results():
"""
ImageNet 256x256 上的 FID vs 采样步数。
"""
results = {
(1000, 1.0, "ADM-Improved"): 10.4,
(100, 0.0, "DDIM"): 11.3,
(50, 0.0, "DDIM"): 12.8,
(20, 0.0, "DDIM"): 17.2,
}
return results

核心数据:DDIM 50 步(η=0\eta=0)的 FID 与 DDPM 1000 步相当,而速度快 20 倍。

7.2 感知质量分析#

def perceptual_quality():
"""
DDIM 的感知质量特点。
"""
observations = {
"优点": [
"细节更锐利(确定性路径避免随机模糊)",
"颜色更一致(无每步随机偏移累积)",
"构图更稳定(适合编辑/控制任务)",
],
"缺点": [
"多样性降低(相同初始噪声产生相同结果)",
"部分图像出现"超锐化"伪影(尤其 10 步以内)",
"某些类别容易陷入局部最优",
]
}
return observations

8. 加速技术的演进#

8.1 DDIM 的后续加速方法#

DDIM (2021): 非马尔可夫, 20-50步, 确定性
├── LDM (2022): Latent Diffusion → 压缩到隐空间,速度再次提升
├── Consistency Models (2023): Song et al.
│ 一致性蒸馏 → 1-4 步
├── LCM (Latent Consistency Models) (2023): Luo et al.
│ 蒸馏 + 隐空间 → 2-4 步 ★ (SD + LCM)
├── SDXL-Turbo / SDXL-Lightning (2024)
│ 步蒸馏 → 1-2 步
└── Rectified Flow (2023): Liu et al.
最优传输 → 4-10 步
(见 [Flow Matching 文章](/posts/flow-matching/))

8.2 为什么 DDIM 之后还需要新方法?#

维度DDIM新方法(LCM/RF)
采样步数10-50 步1-4 步
训练方式同一训练目标需要额外蒸馏
模型兼容性复用 DDPM 模型需要重新训练
生成质量10步 FID ~84步 FID ~6

8.3 DDIM 的持续重要性#

尽管有更快的方法,DDIM 仍被广泛使用:

# Stable Diffusion 推理管线中的 DDIM
from diffusers import DDIMScheduler, StableDiffusionPipeline
pipeline = StableDiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5")
pipeline.scheduler = DDIMScheduler.from_config(pipeline.scheduler.config)
# 使用 DDIM 调度器
image = pipeline(
prompt="a cat on a sofa",
num_inference_steps=20, # DDIM: 20步即可
eta=0.0, # 完全确定性
).images[0]

9. 图像编辑与 DDIM#

9.1 DDIM 的编辑优势#

确定性是编辑任务的关键

编辑任务: 把 [原图] 中的 [对象] 替换为 [新对象]
DDPM 方法:
原图 → 加噪到某步 → 编辑条件 → 去噪
问题: 加噪和去噪都是随机的
同一张图多次处理会得到不同结果
DDIM 方法:
原图 → 加噪到某步(确定性反向 ODE)→ 编辑条件 → 去噪
优点: 加噪路径是确定性的
同一张图→同一条轨迹→可逆
编辑结果可复现

9.2 DDIM Inpainting#

def ddim_inpainting():
"""
DDIM Inpainting 流程。
1. 原图 x_0 → 加噪到 t=T-edit(如 T=500)
(确定性反向 ODE, 可以精确恢复)
2. 在隐空间中修改对应区域(inpaint mask)
3. 从 x_t=500 用 DDIM 去噪到 x_0
4. 合并编辑区域和原始区域
"""
pass

9.3 引导编辑#

DDIM 与 CFG(无分类器引导)完美结合:

ϵ^=(1+w)ϵθ(xt,t,c)wϵθ(xt,t,)\hat{\epsilon} = (1 + w) \cdot \epsilon_\theta(x_t, t, c) - w \cdot \epsilon_\theta(x_t, t, \emptyset)

CFG 的引导强度 ww 与 DDIM 的 η\eta 可以独立调节:

ww效果典型值
w=1w = 1无引导默认
w=7w = 7中度引导文本对齐好
w=15w = 15强度引导高保真

10. 完整实现#

import torch
import torch.nn.functional as F
class DDIMScheduler:
"""
DDIM 调度器实现。
完全兼容 Diffusers 库的接口。
"""
def __init__(self, num_train_timesteps=1000, beta_start=1e-4, beta_end=0.02,
beta_schedule="linear", clip_sample=False, set_alpha_to_one=False):
self.num_train_timesteps = num_train_timesteps
self.clip_sample = clip_sample
self.final_alpha_cumprod = 1.0 if set_alpha_to_one else 0.0
# 噪声调度
if beta_schedule == "linear":
betas = torch.linspace(beta_start, beta_end, num_train_timesteps)
elif beta_schedule == "cosine":
betas = self._cosine_beta_schedule(num_train_timesteps)
else:
raise ValueError(f"Unknown beta_schedule: {beta_schedule}")
alphas = 1.0 - betas
alphas_cumprod = torch.cumprod(alphas, dim=0)
self.alphas_cumprod = alphas_cumprod
def _cosine_beta_schedule(self, timesteps, s=0.008):
"""余弦噪声调度(更平滑)。"""
steps = timesteps + 1
x = torch.linspace(0, timesteps, steps)
alphas_cumprod = torch.cos((x / timesteps + s) / (1 + s) * torch.pi * 0.5) ** 2
alphas_cumprod = alphas_cumprod / alphas_cumprod[0]
betas = 1 - alphas_cumprod[1:] / alphas_cumprod[:-1]
return torch.clamp(betas, 0.0001, 0.9999)
def set_timesteps(self, num_inference_steps, offset=0):
"""
设置推理时间步。
参数:
num_inference_steps: DDIM 采样步数(如 50)
offset: 偏移量
"""
self.num_inference_steps = num_inference_steps
step_ratio = self.num_train_timesteps // num_inference_steps
# 生成从 T-1 到 0 的时间步序列
timesteps = (torch.arange(0, num_inference_steps) * step_ratio
+ step_ratio // 2).flip(0)
self.timesteps = timesteps.long()
@torch.no_grad()
def step(self, model_output, timestep, sample, eta=0.0):
"""
DDIM 单步采样。
参数:
model_output: 模型预测的噪声 ε_θ(x_t, t)
timestep: 当前时间步 t
sample: 当前状态 x_t
eta: 随机性参数 (0=确定, 1=随机)
返回:
prev_sample: 下一状态 x_{t-1}
"""
t = timestep
prev_t = timestep - self.num_train_timesteps // self.num_inference_steps
# 当前和上一步的 alpha_cumprod
alpha_prod_t = self.alphas_cumprod[t]
alpha_prod_t_prev = self.alphas_cumprod[prev_t] if prev_t >= 0 else self.final_alpha_cumprod
# 预测 x_0
pred_original_sample = (sample - (1 - alpha_prod_t).sqrt() * model_output) \
/ alpha_prod_t.sqrt()
if self.clip_sample:
pred_original_sample = torch.clamp(pred_original_sample, -1, 1)
# 计算方差
# DDIM 方差 (公式来自论文)
var = (1 - alpha_prod_t_prev) / (1 - alpha_prod_t) * (1 - alpha_prod_t / alpha_prod_t_prev)
std = (var ** 0.5) * eta
# 预测 x_{t-1} 的方向
pred_sample_direction = (1 - alpha_prod_t_prev - std ** 2).sqrt() * model_output
# 确定性部分
prev_sample = alpha_prod_t_prev.sqrt() * pred_original_sample + pred_sample_direction
# 随机噪声(仅当 eta > 0)
if eta > 0:
noise = torch.randn_like(sample)
variance_noise = std * noise
prev_sample = prev_sample + variance_noise
return prev_sample
def add_noise(self, original_samples, timesteps, noise=None):
"""前向加噪(用于重建目标)。"""
if noise is None:
noise = torch.randn_like(original_samples)
sqrt_alpha_prod = self.alphas_cumprod[timesteps] ** 0.5
sqrt_one_minus_alpha = (1 - self.alphas_cumprod[timesteps]) ** 0.5
noisy_samples = sqrt_alpha_prod.view(-1, 1, 1, 1) * original_samples \
+ sqrt_one_minus_alpha.view(-1, 1, 1, 1) * noise
return noisy_samples

11. 与 Flow Matching 的理论联系#

11.1 DDIM 的 ODE 就是 Flow Matching#

回想 Flow Matching 文章的核心:

Flow Matching 把从噪声到数据的路径建模为 ODE dx/dt=vθ(x,t)dx/dt = v_\theta(x, t),训练目标是 MSE(预测速度, 真实速度)。

DDIM 的 σt=0\sigma_t = 0 情形正是 Flow Matching 的一个特例:

DDIM (η=0) 的 ODE:
x_{t-1} = √ᾱ_{t-1} · x̂_0 + √(1-ᾱ_{t-1}) · ε_θ(x_t, t)
将 t-1 写成 dt,并整理成 dx/dt 的形式:
dx/dt = (ᾱ_{t-1} - ᾱ_t) / dt · x̂_0 + ...
= v_θ(x_t, t)
其中 v_θ(x_t, t) = ε_θ(x_t, t) - x̂_0
而 x̂_0 = (x_t - √(1-ᾱ_t) · ε_θ) / √ᾱ_t
→ v_θ(x_t, t) = ε_θ - (x_t - √(1-ᾱ_t) · ε_θ) / √ᾱ_t
= (1 - 1/√ᾱ_t) · ε_θ - x_t/√ᾱ_t
这恰好是 RF 直线路径的向量场!

11.2 三种路径的理论统一#

所有扩散类生成模型 = Flow Matching 的特例
路径设计空间:
├── Rectified Flow: 直线 x_t = (1-t)x_0 + tε → v = ε - x_0
├── DDPM (VP-SDE): 弧线 x_t = √ᾱ_t x_0 + √(1-ᾱ_t)ε
└── DDIM (η=0): 与 DDPM 相同的路径,但 ODE 求解
采样策略:
├── DDPM: SDE → 1000步随机马尔可夫
├── DDIM: ODE → 10-50步确定性非马尔可夫
└── RF: ODE → 10-50步确定性直线

12. 总结#

12.1 核心要点#

维度关键要点
核心洞察逆向过程不需要是马尔可夫的,边际分布不变即可
数学框架非马尔可夫链 + 边际分布约束
训练目标与 DDPM 完全相同(不需要重新训练)
采样σt=0\sigma_t = 0 时为确定性 ODE 采样
质量50 步 DDIM ≈ 1000 步 DDPM(FID 相当)
速度20 倍加速(1000 步 → 50 步)
权衡η=0\eta = 0 质量最高但多样性最低
编辑优势确定性使加噪/去噪可逆,完美支持图像编辑
与 FMDDIM η=0\eta=0 的 ODE 路径是 Flow Matching 的特例

12.2 一句话总结#

DDIM 通过放弃”逆向必须是马尔可夫链”的人为约束,把 DDPM 的随机走路径变成确定性的 ODE 轨迹——不改变任何训练目标,只改变采样方式,就实现了 20 倍加速,同时保持了生成质量。这是非马尔可夫隐式概率模型在生成建模中的首次成功应用,也是 Rectified Flow 和一致性模型的理论先驱。

12.3 推荐资源#

论文:
- DDIM (Song et al., 2021): "Denoising Diffusion Implicit Models"
- Consistency Models (Song et al., 2023): "Consistency Models"
- Flow Matching (Lipman et al., 2022): "Flow Matching for Causal Inference"
代码:
- ermongroup/ddim (官方实现)
- huggingface/diffusers (DDIMScheduler)
- CompVis/stable-diffusion (集成 DDIM)
教程:
- Lilian Weng 博客: "Diffusion Models as a Kind of ODE"
- Hugging Face 博客: "Speed up inference with DDIM"

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

DDIM 深度剖析:从随机马尔可夫链到确定性隐式模型
https://aiattnstudio.link/posts/ddim/
作者
Federico
发布于
2026-07-16
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author

Federico

AI Research Lab

Hello, I'm Federico.

关于实验室 / About
公告

欢迎来到Federico的个人博客

分类
标签
站点统计
57文章
7分类
404标签