DDPM 深度剖析:变分推断视角下的去噪扩散概率模型
1. DDPM 的诞生:为什么是 2020 年?
1.1 在 DDPM 之前:两段独立的历史
扩散概率模型并非凭空出现——它由两条独立发展的理论脉络交汇而成。
历史脉络 A:变分推断
2002 VAE (Kingma & Welling) - 变分下界 (ELBO) - 重参数化技巧 - 重构 + KL 正则化
2019 Sohl-Dickstein et al. - 扩散概率模型 (Deep Unsupervised Learning using Nonequilibrium Thermodynamics) - 马尔可夫前向 + 逆向 - 但太慢,生成需 ~1000 步历史脉络 B:Score Matching
2011 Score Matching (Hyvarinen) - 学 ∇_x log p(x) — 不需要归一化常数
2019 Song et al. — Noise Conditional Score Network (NCSN) - 用 Score Matching 学噪声尺度下的对数密度梯度 - 朗之万采样生成
2021 Song et al. — Score SDE - 把 NCSN 统一到 SDE 框架2020 年,Ho et al. 把两条脉络合二为一:
DDPM = Sohl-Dickstein 扩散的工程化 + Score Matching 的数学优雅 + 简单的 MSE 训练目标
1.2 一句话概括 DDPM
DDPM 是一个分两步走的生成模型:① 预设一个将数据逐步破坏成纯噪声的前向过程(马尔可夫链);② 训练一个神经网络去学习逆向过程——从噪声一步步恢复出原始数据。用变分推断证明,训练等价于简单的 MSE(预测噪声或 )。
1.3 DDPM 论文信息
论文: "Denoising Diffusion Probabilistic Models" (DDPM)作者: Jonathan Ho, Ajay Jain, Pieter Abbeel单位: Google Brain, UC Berkeley发表于: NeurIPS 2020引用: > 15,000 次 (截至 2024)开源: https://github.com/hojonathanho/diffusion2. 前向过程:预设的加噪马尔可夫链
2.1 马尔可夫链定义
设原始数据为 (真实数据分布)。前向过程定义为一个 步马尔可夫链,每一步添加少量高斯噪声:
其中 是噪声调度(noise schedule),满足 , 随 递增。
def forward_step(xt_minus_1, beta_t): """前向过程的单步: x_{t-1} → x_t""" mean = torch.sqrt(1 - beta_t) * xt_minus_1 std = torch.sqrt(beta_t) noise = torch.randn_like(xt_minus_1) return mean + std * noise2.2 关键性质:闭式分布
不用逐步递推! 通过重参数化技巧,任意时刻 的分布可以直接写出:
其中:
推导:
设 独立,则:
其中 是标准高斯噪声。
def closed_form_forward(x0, t, alphas_cumprod): """ 闭式前向分布: q(x_t | x_0)
参数: x0: (batch_size, ...) 原始数据 t: (batch_size,) 时间步 (整数或浮点) alphas_cumprod: (T,) cumprod 数组 返回: (batch_size, ...) 在 t 时刻加噪后的数据 """ idx = t.long() # 安全取整数索引 sqrt_alpha_bar = torch.sqrt(alphas_cumprod[idx]) sqrt_one_minus = torch.sqrt(1 - alphas_cumprod[idx]) noise = torch.randn_like(x0) B, C, H, W = x0.shape reshape_shape = (B, 1, 1, 1) return sqrt_alpha_bar.view(*reshape_shape) * x0 \ + sqrt_one_minus.view(*reshape_shape) * noise2.3 噪声调度的直观理解
和 随时间步变化:
- :,对应原始数据
- :两者约各占一半,噪声和数据混合
- :,接近纯噪声
| 时刻 | 信号占比 | 噪声占比 | 含义 |
|---|---|---|---|
| 高 | 极低 | 原始数据 | |
| 中等 | 中等 | 噪声和数据各占一半 | |
| 极低 | 高 | 接近纯噪声 |
核心洞察:当 时,,所以 ——前向过程把任意数据分布变成了标准高斯噪声。
2.4 常见噪声调度
class NoiseSchedule: """三种常见噪声调度。"""
@staticmethod def linear(T, beta_start=1e-4, beta_end=0.02): """线性调度 (DDPM 原始论文使用)。""" betas = torch.linspace(beta_start, beta_end, T) return betas
@staticmethod def cosine(T, s=0.008): """余弦调度 (改进版, 噪声衰减更平滑)。""" steps = torch.arange(T + 1) # \bar\alpha_t = cos²( (t/T + s)/(1+s) * π/2 ) alphas_cumprod = torch.cos((steps / T + s) / (1 + s) * (torch.pi / 2)) ** 2 alphas_cumprod = alphas_cumprod / alphas_cumprod[0] # 归一化: \bar\alpha_0 = 1 betas = 1 - alphas_cumprod[1:] / alphas_cumprod[:-1] return betas
@staticmethod def sigmoid(T, beta_start=1e-4, beta_end=0.02): """S 形调度 (介于线性和余弦之间)。""" betas = torch.linspace(-6, 6, T) betas = torch.sigmoid(betas) * (beta_end - beta_start) + beta_start return betasᾱ_t 曲线对比:
1.0 ───────────────────────────────────────── ╲ Linear (更快变噪声) ╲ ╲ Cosine (更平滑) ╲ ╲ ──────────────────────── 0.0 t3. 逆向过程:学习的去噪分布
3.1 逆向分布的必要性
如果能精确知道 ,就可以从纯噪声 精确恢复数据 。
但 不可直接计算(需要对所有 积分),所以用神经网络 来近似:
3.2 逆向过程的参数化
均值参数化(最直觉的写法):
但更常用的是直接预测噪声或 ,而不是直接预测均值——因为这让目标函数成为简单的 MSE。
3.3 均值与噪声的关系
由前向过程的闭式公式,可以反解出均值:
其中 是注入的噪声。由 ,得:
代入均值表达式:
或者,直接用噪声 和 来表达(这是神经网络实际预测的):
网络 预测注入的噪声 ,从而反推均值。
4. 变分推断与 ELBO
4.1 生成模型的对数似然目标
我们想最大化生成数据的对数似然 。但 直接计算困难(需要对所有隐变量积分),所以用变分推断引入隐变量 :
4.2 ELBO 的引入
对任意分布 (建议分布,即前向过程),用 Jensen 不等式得到变分下界 (ELBO):
展开 ,,得到 ELBO 的分项形式:
4.3 ELBO 三项的物理含义
ELBO = -[ L_T + L_{T-1} + ... + L_1 + L_0 ] ↓ ↓ ↓ ↓ 终点 中间步 中间步 重构项 KL项 KL项 KL项 重构损失ELBO 包含三项,每项对应不同的训练阶段:
| 项 | 名称 | 含义 | 训练时是否需要 |
|---|---|---|---|
| 终点先验 KL | 与 的 KL, 足够大时自动满足 | ❌ | |
| 去噪匹配项 | (可解析)与 的 KL | ✅ 核心 | |
| 重构项 | 在 时从 重构 | ✅(可与 合并) |
4.4 关键: 有闭式解
定理:给定 和 ,逆向条件分布 也是高斯分布,可以直接写出均值和方差:
其中:
方差:
def reverse_posterior_mean(x0, xt, t, alphas_cumprod, betas): """ 计算 q(x_{t-1} | x_t, x_0) 的均值 (闭式)。 这是在 ELBO 中,用于计算 L_{t-1} KL 项的 target mean。 """ alpha_bar_t = alphas_cumprod[t] alpha_bar_tm1 = alphas_cumprod[t - 1] alpha_t = 1 - betas[t]
# 从 xt 解出注入的噪声 sqrt_alpha_bar_t = torch.sqrt(alpha_bar_t) sqrt_one_minus = torch.sqrt(1 - alpha_bar_t) eps = (xt - sqrt_alpha_bar_t * x0) / sqrt_one_minus
# 计算均值 (用噪声表示) sqrt_alpha_bar_tm1 = torch.sqrt(alpha_bar_tm1) beta_t = betas[t]
# 方法 1: 用 x0 和 xt 表示 mean = (sqrt_alpha_bar_tm1 * beta_t / (1 - alpha_bar_t)) * x0 \ + (torch.sqrt(alpha_t) * (1 - alpha_bar_tm1) / (1 - alpha_bar_t)) * xt
# 方法 2: 用噪声 ε 表示 (更常用, 神经网络预测 ε) mean_eps = (xt - torch.sqrt(1 - alpha_bar_t) * beta_t / (1 - alpha_bar_t).sqrt() * eps) \ / alpha_t.sqrt()
return mean_eps4.5 ELBO 的简化
在 DDPM 论文中, 简化为:
论文发现,去掉复杂的系数后,简化为:
这意味着:训练 DDPM 等价于训练一个 MSE 损失——预测注入的噪声 !
5. 三种参数化方式
5.1 噪声预测 -prediction
网络输出:注入的噪声 目标:
def loss_eps_prediction(model, x0, t, alphas_cumprod): """DDPM 标准噪声预测损失。""" eps = torch.randn_like(x0) xt = torch.sqrt(alphas_cumprod[t]) * x0 + torch.sqrt(1 - alphas_cumprod[t]) * eps pred_eps = model(xt, t) return F.mse_loss(pred_eps, eps)5.2 预测 (x-prediction)
网络输出:原始数据 目标:
由 :
def loss_x0_prediction(model, x0, t, alphas_cumprod): """x0 预测损失。""" eps = torch.randn_like(x0) xt = torch.sqrt(alphas_cumprod[t]) * x0 + torch.sqrt(1 - alphas_cumprod[t]) * eps pred_x0 = model(xt, t) return F.mse_loss(pred_x0, x0)5.3 速度预测 v-prediction
网络输出:速度向量 目标:
由 ,对 求导得:
在实际中,DDPM++ 论文使用:
def loss_v_prediction(model, x0, t, alphas_cumprod): """速度预测损失 (DDPM++ / ADM 采用)。""" eps = torch.randn_like(x0) sqrt_ab = torch.sqrt(alphas_cumprod[t]) sqrt_1m = torch.sqrt(1 - alphas_cumprod[t]) xt = sqrt_ab * x0 + sqrt_1m * eps
# 真实速度 v = sqrt_ab * eps - sqrt_1m * x0
pred_v = model(xt, t) return F.mse_loss(pred_v, v)5.4 三种参数化对比
| 维度 | 噪声预测 (DDPM) | x0 预测 | 速度预测 (DDPM++) |
|---|---|---|---|
| 网络输出 | |||
| 损失函数 | MSE | MSE | MSE |
| 低噪声时 () | 好 | 最好 | 好 |
| 高噪声时 () | 好 | 差 | 好 |
| 训练稳定性 | 高 | 中 | 高 |
| 典型应用 | DDPM, SD 1/2/xl | — | ADM, DiT, MMDiT |
6. 训练与采样完整流程
6.1 训练算法
def ddpm_train_step(model, x0, alphas_cumprod, betas): """ DDPM 训练步 (Algorithm 1, Ho et al. 2020)。 """ B = x0.shape[0]
# 1) 采样时间步 t ~ Uniform{1, ..., T} t = torch.randint(0, len(alphas_cumprod), (B,), device=x0.device)
# 2) 采样噪声 ε ~ N(0, I) eps = torch.randn_like(x0)
# 3) 前向加噪 (闭式) sqrt_ab = alphas_cumprod[t] ** 0.5 sqrt_1m = (1 - alphas_cumprod[t]) ** 0.5 xt = sqrt_ab.view(-1, *([1] * (x0.dim() - 1))) * x0 \ + sqrt_1m.view(-1, *([1] * (x0.dim() - 1))) * eps
# 4) 计算损失 (噪声预测) eps_theta = model(xt, t) loss = F.mse_loss(eps_theta, eps)
return loss
def train_ddpm(model, dataloader, T, beta_schedule_fn): """完整 DDPM 训练循环。""" betas = beta_schedule_fn(T) alphas = 1 - betas alphas_cumprod = torch.cumprod(alphas, dim=0)
for epoch in range(num_epochs): for batch in dataloader: x0 = batch.to(device) loss = ddpm_train_step(model, x0, alphas_cumprod, betas)
optimizer.zero_grad() loss.backward() optimizer.step()6.2 采样算法(DDPM 原版)
@torch.no_grad()def ddpm_sample(model, alphas_cumprod, betas, shape, T): """ DDPM 采样 (Algorithm 2, Ho et al. 2020)。 从 x_T ~ N(0, I) 开始,T 步逆向去噪。 """ # 1) 从纯噪声开始 xt = torch.randn(*shape, device=device)
# 2) 逐步逆向去噪 (t = T, T-1, ..., 1) for t in reversed(range(T)): t_batch = torch.full((shape[0],), t, device=device)
# 预测噪声 eps_theta = model(xt, t_batch)
# 估计 x_0 sqrt_ab = alphas_cumprod[t] ** 0.5 sqrt_1m = (1 - alphas_cumprod[t]) ** 0.5 x0_pred = (xt - sqrt_1m * eps_theta) / sqrt_ab
# 计算均值 beta_t = betas[t] alpha_t = 1 - beta_t mean = (xt - (beta_t / sqrt_1m) * eps_theta) / alpha_t ** 0.5
# 如果不是最后一步,加随机噪声 if t > 0: std = (1 - alphas_cumprod[t - 1] / alphas_cumprod[t]) ** 0.5 * beta_t ** 0.5 xt = mean + std * torch.randn_like(xt) else: xt = mean
return xt注意:原版 DDPM 需要 1000 步才能生成高质量图像——这太慢了,所以后来有了 DDIM 等加速方法。
6.3 DDIM 加速采样
DDIM(Denoising Diffusion Implicit Models, Song et al., 2021)放弃了马尔可夫假设,用非马尔可夫逆向过程实现确定性且更快的采样:
@torch.no_grad()def ddim_sample(model, alphas_cumprod, betas, shape, num_steps=50, eta=0.0): """ DDIM 采样 (Song et al., 2021)。 num_steps << T (比如 T=1000, num_steps=50)
eta = 0: 完全确定性 (DDIM) eta = 1: 随机马尔可夫 (等效 DDPM) """ T = len(alphas_cumprod) - 1 # 选择子序列 step_list = torch.linspace(0, T - 1, num_steps, dtype=torch.long).tolist() seq = step_list[::-1] # 倒序: [T-1, ..., 0]
# 从纯噪声开始 xt = torch.randn(*shape, device=device)
for i, t in enumerate(seq): t_batch = torch.full((shape[0],), t, device=device)
# 预测噪声 eps_theta = model(xt, t_batch)
# 估计 x_0 sqrt_ab = alphas_cumprod[t] ** 0.5 sqrt_1m = (1 - alphas_cumprod[t]) ** 0.5 x0_pred = (xt - sqrt_1m * eps_theta) / sqrt_ab
# 下一个时间步 t_next = seq[i + 1] if i + 1 < len(seq) else 0 sqrt_ab_next = alphas_cumprod[t_next] ** 0.5 sqrt_1m_next = (1 - alphas_cumprod[t_next]) ** 0.5
# DDIM 确定性方向 pred_eps = eps_theta xt_next = sqrt_ab_next * x0_pred + sqrt_1m_next * pred_eps
# 如果要随机性 (eta > 0) if eta > 0: std = eta * ((1 - alphas_cumprod[t_next]) / (1 - alphas_cumprod[t]) * betas[t]) ** 0.5 xt_next = xt_next + std * torch.randn_like(xt)
xt = xt_next
return xtDDIM 的关键洞察: 的转移不需要是随机的——只要 能从 推出 ,就是合法的逆向过程。
6.4 DDPM vs DDIM vs Flow Matching 对比
| 维度 | DDPM (马尔可夫) | DDIM (非马尔可夫) | Rectified Flow |
|---|---|---|---|
| 逆向过程 | 随机(加噪声) | 确定性(可选随机性) | ODE(确定性) |
| 采样步数 | 1000(慢) | 20-50(快) | 10-50(快) |
| 轨迹形状 | 弧线 | 取决于噪声调度 | 直线(OT 最优) |
| 训练目标 | MSE() | 同 DDPM | MSE() |
| 轨迹可视化 | 发散 | 集中 | 最短 |
7. 与 Score Matching 的理论联系
7.1 Score Matching 的核心
Score Matching 的目标是训练一个得分网络 来估计对数密度梯度:
得分匹配损失(Hutchinson 估计器):
7.2 DDPM 与 Score Matching 的等价性
核心联系:DDPM 预测的噪声 与得分函数成正比:
其中 是噪声标准差。
推导:
- 所以
def score_from_eps(eps_theta, t, alphas_cumprod): """ 从噪声预测网络得到得分函数 ∇_x log p_t(x)。 """ sigma_t = torch.sqrt(1 - alphas_cumprod[t]) score = -eps_theta / sigma_t # s_θ(x_t, t) ≈ -ε / σ return score
def eps_from_score(score, t, alphas_cumprod): """ 从得分函数恢复噪声预测。 """ sigma_t = torch.sqrt(1 - alphas_cumprod[t]) return -sigma_t * score7.3 为什么 DDPM 比纯 Score Matching 更好训练?
| 维度 | 纯 Score Matching | DDPM |
|---|---|---|
| 学习目标 | (得分函数) | (噪声) |
| 额外计算 | 需要 Hessian 或 Hutchinson 估计 | 不需要 |
| 采样方式 | 朗之万动力学(慢,不精确) | 直接从 逆向(快) |
| 训练稳定性 | 差(得分函数在高维空间容易爆炸) | 好(MSE 损失有界) |
DDPM 通过引入预设前向过程和可闭式计算的 KL 目标,绕过了纯 Score Matching 的 Hessian 估计难题——这是它最重要的工程贡献。
8. 方差参数化:固定 vs 学习
8.1 DDPM 的方差设计
原版 DDPM 将逆向方差 固定为前向方差:
论文发现,学习方差并不会带来显著提升,反而增加训练难度。
# DDPM 原版: 方差固定def ddpm_variance(t, betas): """逆向过程的方差(固定为 β_t)。""" return betas[t]
# DDPM++ (改进版): 学习方差class LearnedVarianceUNet(nn.Module): """输出均值 + log 方差。""" def forward(self, xt, t): out = self.backbone(xt, t) mean, log_var = out.chunk(2, dim=1) log_var = torch.clamp(log_var, -10, 10) return mean, log_var
def ddpmpp_sample(model, alphas_cumprod, betas, shape, T): """DDPM++ 采样 (学习方差版本)。""" xt = torch.randn(*shape, device=device)
for t in reversed(range(T)): t_batch = torch.full((shape[0],), t, device=device) mean, log_var = model(xt, t_batch)
# 混合: 固定 + 学习 fixed_var = betas[t] learned_var = log_var.exp() var = (1 - alphas_cumprod[t - 1] / alphas_cumprod[t]) * betas[t]
std = var ** 0.5 xt = mean + std * torch.randn_like(xt)
return xt9. Classifier Guidance(分类器引导)
9.1 为什么需要引导?
无条件 DDPM 生成质量不错,但无法控制类别。Classifier Guidance 用外部分类器注入类别信息:
对应的引导得分:
9.2 引导下的逆向分布
def classifier_guidance(eps_theta, x_t, t, classifier, y, gamma=1.0): """ 分类器引导 (Dhariwal & Nichol, 2021)。
在无类别噪声预测上叠加分类器梯度。 """ # 分类器对 x_t 求梯度 x_t.requires_grad_(True) logits = classifier(x_t, t) log_probs = torch.log_softmax(logits, dim=-1) target_log_prob = log_probs.gather(-1, y.unsqueeze(-1)).squeeze(-1) grad = torch.autograd.grad(target_log_prob.sum(), x_t)[0]
# 引导噪声预测 sigma_t = ... # 噪声标准差 guided_eps = eps_theta - gamma * sigma_t * grad return guided_eps关键:引导强度 控制类别保真度和多样性之间的 trade-off。 是标准引导, 越大,生成越精确但越缺乏多样性。
9.3 CFG vs Classifier Guidance
| 维度 | Classifier Guidance | Classifier-Free Guidance |
|---|---|---|
| 是否需要 | 单独训练分类器 | 无需额外网络 |
| 训练方式 | 分类器单独训,DDPM 单独训 | DDPM 联合训练条件 + 无条件 |
| 质量 | 高(专用信号) | 高(隐式引导) |
| 多样性 | 低(强引导降低) | 可控(由 调节) |
| 典型使用 | ADM, Guided Diffusion | SD 1/2/xl, SD3, FLUX |
**CFG(无分类器引导)**是主流:
def cfg_eps(eps_cond, eps_uncond, w): """无分类器引导 (CFG)。""" return (1 + w) * eps_cond - w * eps_uncond10. DDPM 的完整 PyTorch 实现
import mathimport torchimport torch.nn as nnimport torch.nn.functional as Ffrom torch.utils.data import DataLoader
class SinusoidalTimeEmbedding(nn.Module): """DDPM 标准时间步嵌入。"""
def __init__(self, dim): super().__init__() self.dim = dim
def forward(self, t): device = t.device half = self.dim // 2 embeddings = math.log(10000) / (half - 1) embeddings = torch.exp(torch.arange(half, device=device) * -embeddings) embeddings = t[:, None] * embeddings[None, :] embeddings = torch.cat([embeddings.sin(), embeddings.cos()], dim=-1) return embeddings
class ResidualBlock(nn.Module): """带 GroupNorm + SiLU 的残差块。"""
def __init__(self, in_ch, out_ch, time_emb_dim, dropout=0.1): super().__init__() self.norm1 = nn.GroupNorm(32, in_ch) self.conv1 = nn.Conv2d(in_ch, out_ch, 3, padding=1) self.time_mlp = nn.Sequential( nn.SiLU(), nn.Linear(time_emb_dim, out_ch * 2) ) self.norm2 = nn.GroupNorm(32, out_ch) self.conv2 = nn.Conv2d(out_ch, out_ch, 3, padding=1) self.dropout = nn.Dropout(dropout)
# 残差连接 self.shortcut = nn.Conv2d(in_ch, out_ch, 1) if in_ch != out_ch else nn.Identity()
def forward(self, x, t_emb): h = self.norm1(x).swish() h = self.conv1(h)
# 调制: AdaGN 风格 t = self.time_mlp(t_emb) shift, scale = t.chunk(2, dim=-1) h = h * (1 + scale[:, None, :, :]) + shift[:, None, :, :]
h = self.dropout(h) h = self.norm2(h).swish() h = self.conv2(h)
return h + self.shortcut(x)
class DDPMUNet(nn.Module): """简化版 DDPM U-Net。"""
def __init__(self, in_channels=3, base_channels=128, channel_mults=(1, 2, 4, 8)): super().__init__() self.time_embed_dim = base_channels * 4 self.time_mlp = nn.Sequential( SinusoidalTimeEmbedding(base_channels), nn.Linear(base_channels, self.time_embed_dim), nn.SiLU(), nn.Linear(self.time_embed_dim, self.time_embed_dim), )
# Encoder chs = [base_channels] for mult in channel_mults: chs.append(base_channels * mult)
self.encoder = nn.ModuleList() for i in range(len(chs) - 1): self.encoder.append( ResidualBlock(chs[i], chs[i + 1], self.time_embed_dim) )
# Bottleneck self.bottleneck = ResidualBlock(chs[-1], chs[-1], self.time_embed_dim)
# Decoder self.decoder = nn.ModuleList() for i in reversed(range(len(chs) - 1)): self.decoder.append( ResidualBlock(chs[i + 1], chs[i], self.time_embed_dim) )
# 输出层 self.final = nn.Sequential( nn.GroupNorm(32, base_channels), nn.SiLU(), nn.Conv2d(base_channels, in_channels, 3, padding=1), )
def forward(self, xt, t): """返回噪声预测 ε_θ(x_t, t)。""" t_emb = self.time_mlp(t)
# Encoder hs = [] for block in self.encoder: xt = block(xt, t_emb) hs.append(xt)
# Bottleneck xt = self.bottleneck(xt, t_emb)
# Decoder with skip connections for block in self.decoder: xt = torch.cat([xt, hs.pop()], dim=1) xt = block(xt, t_emb)
return self.final(xt)11. DDPM 的局限与后续发展
11.1 DDPM 的三大局限
| 局限 | 描述 | 影响 |
|---|---|---|
| 推理速度 | 需要 1000 步,生成极慢 | 实际应用受限 |
| 隐空间缺失 | 直接在像素空间加噪,计算量大 | 512×512 以上极慢 |
| 无条件生成 | 文本条件需要额外引导机制 | CFG 引入计算开销 |
11.2 DDPM 之后的演进时间线
2020 DDPM (Ho et al.) — MSE 训练, 1000 步推理 │2021 DDIM (Song et al.) — 非马尔可夫, 20-50 步 │2021 Classifier-free Guidance (Ho & Salimans) │2022 Latent Diffusion (Rombach et al.) — VAE 压缩到隐空间 ★ │2022 Score SDE (Song et al.) — 统一 SDE 框架 │2022 Consistency Models (Song et al.) — 蒸馏到 1 步 │2022 DiT (Peebles & Xie) — Transformer 替换 UNet │2023 SDXL (Rombach et al.) — 级联扩散, 1024px │2024 Rectified Flow (Liu et al.) — 最优传输, 直线路径 │2024 SD3 (Esser et al.) — MMDiT 双流架构 ★ │2024 FLUX (Black Forest Labs) — RF + MMDiT, 12B12. 总结
12.1 核心公式速查
| 名称 | 公式 |
|---|---|
| 前向加噪 | |
| 逆向均值 | |
| 训练目标 | |
| DDIM 采样 | |
| CFG | |
| Score 联系 |
12.2 一句话总结
DDPM 用”预设前向 + 变分推断 + 简单 MSE”的三角组合,绕过了纯 Score Matching 的计算难题,把扩散模型从理论可行的方法变成了实际可训练的生成模型——这是 2020 年最重要的生成建模突破之一,也是后续所有扩散改进(Latent Diffusion、DiT、MMDiT、Rectified Flow、FLUX)的起点。
12.3 推荐资源
论文: - DDPM (Ho et al., 2020): "Denoising Diffusion Probabilistic Models" - DDIM (Song et al., 2021): "Denoising Diffusion Implicit Models" - Score SDE (Song et al., 2021): "Score-Based Generative Modeling through SDEs" - CFG (Ho & Salimans, 2022): "Classifier-Free Diffusion Guidance" - Latent Diffusion (Rombach et al., 2022): "High-Resolution Image Synthesis with Latent Diffusion Models"
代码: - hojonathanho/diffusion (官方实现) - openai/guided-diffusion (ADM) - CompVis/stable-diffusion (LDM) - lucidrains/DDPM-pytorch
数学参考: - Lilian Weng 博客: "What are Diffusion Models?" (最好的技术解读) - Angus Gilmour 博客: "Deriving the DDPM Training Objective"文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!

