03-扩散模型基础¶
⚠️ 时效性说明:本章涉及前沿模型/价格/榜单等信息,可能随版本快速变化;请以论文原文、官方发布页和 API 文档为准。
📌 定位说明:本章为扩散模型入门级教学,侧重基础原理。深度进阶请参阅 扩散模型学习(含完整数学推导、DDPM从零实现、SD深入、条件生成、视频生成等35+篇专题)
学习时间: 约6-8小时 难度级别: ⭐⭐⭐ 中级 前置知识: 概率论基础、PyTorch基础、GAN/VAE基本概念(参见本目录 01/02) 实践环境: Python 3.10+, PyTorch 2.0+
目录¶
- 1. 概述:扩散模型的革命性地位
- 2. DDPM核心原理
- 2.1 前向扩散过程
- 2.2 逆向去噪过程
- 2.3 噪声调度(Noise Schedule)
- 2.4 训练目标与损失函数
- 3. Score-Based模型与SDE统一框架
- 4. 条件生成:Classifier-Free Guidance
- 5. Latent Diffusion Models与Stable Diffusion
- 6. DiT:Diffusion Transformer架构
- 7. 主要应用领域
- 8. GAN / VAE / Diffusion 对比
- 9. PyTorch实战:简单DDPM训练循环
- 10. 面试必考题
- 11. 学习检查清单
- 12. 参考资料与延伸阅读
1. 概述:扩散模型的革命性地位¶
1.1 什么是扩散模型?¶
扩散模型(Diffusion Models)是一类基于迭代去噪的生成模型。其核心思想借鉴了非平衡热力学:
- 前向过程:逐步向数据添加高斯噪声,直到数据变成纯噪声
- 逆向过程:学习一个神经网络,逐步从噪声中恢复出原始数据
1.2 为什么扩散模型如此重要?¶
扩散模型在2020年后迅速崛起,成为生成式AI的主导范式:
| 里程碑 | 时间 | 意义 |
|---|---|---|
| DDPM(Ho et al.) | 2020.06 | 证明扩散模型可以生成高质量图像 |
| Improved DDPM | 2021.02 | 引入学习的方差调度,提升对数似然 |
| DALL-E 2 | 2022.04 | 基于扩散模型的文生图系统 |
| Stable Diffusion | 2022.08 | 开源Latent Diffusion,引爆社区 |
| SDXL | 2023.07 | 更高分辨率,更优质量 |
| Sora | 2024.02 | 基于DiT的视频生成,震惊学界 |
| SD3/Flux | 2024-2025 | Flow Matching + DiT,新一代架构 |
1.3 扩散模型 vs 其他生成模型¶
传统生成模型的痛点:
- GAN:训练不稳定(模式崩塌、梯度消失),多样性有限
- VAE:生成图像模糊(后验近似的信息损失),ELBO松弛
- Flow:架构约束严格(可逆网络),表达能力受限
扩散模型的优势:
- ✅ 训练稳定(简单的MSE损失)
- ✅ 生成质量极高(超越GAN的FID得分)
- ✅ 覆盖全部模式(不存在模式崩塌)
- ✅ 似然可计算(有理论保证)
- ✅ 架构灵活(UNet、Transformer均可)
代价:
- ❌ 采样速度慢(需要多步迭代去噪)
- ❌ 计算资源需求大
💡 核心洞察:扩散模型本质上将一个复杂的生成任务分解成了许多简单的去噪子任务,每一步只需要去除少量噪声,因此每一步都容易学习。
2. DDPM核心原理¶
DDPM(Denoising Diffusion Probabilistic Models)是扩散模型最经典的形式化框架,由Ho et al. 2020提出。
2.1 前向扩散过程¶
前向过程(Forward Process / Diffusion Process)是一个固定的马尔可夫链,逐步向数据添加高斯噪声:
其中: - \(\mathbf{x}_0\) 是原始数据 - \(\mathbf{x}_t\) 是第 \(t\) 步的含噪数据 - \(\beta_t \in (0, 1)\) 是噪声调度参数,控制每步添加的噪声量 - \(T\) 是总步数(通常取 1000)
关键性质 — 任意时刻的闭式解:
定义 \(\alpha_t = 1 - \beta_t\),\(\bar{\alpha}_t = \prod_{s=1}^{t} \alpha_s\),则:
这意味着可以直接从 \(\mathbf{x}_0\) 跳到任意 \(\mathbf{x}_t\),而不需要逐步执行:
💡 直觉理解:\(\sqrt{\bar{\alpha}_t}\) 控制原始信号的保留比例,\(\sqrt{1-\bar{\alpha}_t}\) 控制噪声的混入比例。随着 \(t\) 增大,信号逐渐消失,噪声逐渐主导。
2.2 逆向去噪过程¶
逆向过程(Reverse Process)是前向过程的"倒放",从噪声中恢复数据:
为什么需要神经网络?
前向过程的逆向条件分布 \(q(\mathbf{x}_{t-1} | \mathbf{x}_t)\) 依赖于整个数据分布,无法直接计算。但如果同时知道 \(\mathbf{x}_0\),则后验是可解析的:
其中:
三种等价的参数化方式:
| 参数化 | 网络预测目标 | 说明 |
|---|---|---|
| 预测噪声 \(\boldsymbol{\epsilon}_\theta(\mathbf{x}_t, t)\) | 添加的噪声 \(\boldsymbol{\epsilon}\) | DDPM原始方式,最常用 |
| 预测 \(\mathbf{x}_0\) | 原始数据 \(\mathbf{x}_0\) | 直接预测去噪结果 |
| 预测 \(\mathbf{v}\) | \(v = \sqrt{\bar\alpha_t}\boldsymbol{\epsilon} - \sqrt{1-\bar\alpha_t}\mathbf{x}_0\) | "v-prediction",数值更稳定 |
DDPM选择预测噪声,将 \(\boldsymbol{\mu}_\theta\) 表示为:
2.3 噪声调度(Noise Schedule)¶
噪声调度定义了 \(\{\beta_t\}_{t=1}^{T}\) 的变化规律:
线性调度(DDPM原始):
典型值:\(\beta_{\min}=10^{-4}\),\(\beta_{\max}=0.02\)
余弦调度(Improved DDPM):
💡 为什么余弦调度更好? 线性调度在靠近 \(T\) 时信号衰减过快,导致大量步骤浪费在纯噪声上。余弦调度使 \(\bar{\alpha}_t\) 更平滑地下降,让每一步都更有效。
import torch
import math
def linear_beta_schedule(T, beta_min=1e-4, beta_max=0.02):
"""线性噪声调度"""
return torch.linspace(beta_min, beta_max, T)
def cosine_beta_schedule(T, s=0.008):
"""余弦噪声调度(Improved DDPM)"""
steps = torch.arange(T + 1, dtype=torch.float64)
alphas_cumprod = torch.cos(((steps / T) + s) / (1 + s) * math.pi * 0.5) ** 2
alphas_cumprod = alphas_cumprod / alphas_cumprod[0]
betas = 1 - (alphas_cumprod[1:] / alphas_cumprod[:-1]) # 切片操作取子序列
return torch.clip(betas, 0.0001, 0.9999).float() # 链式调用,连续执行多个方法
2.4 训练目标与损失函数¶
DDPM的训练目标通过变分推断推导而来。最终简化为一个极其简洁的损失:
训练流程伪代码:
repeat:
1. 采样 x_0 ~ q(x_0) # 从训练集采一张图
2. 采样 t ~ Uniform(1, T) # 随机选一个时间步
3. 采样 ε ~ N(0, I) # 采样随机噪声
4. 计算 x_t = √ᾱₜ · x_0 + √(1-ᾱₜ) · ε # 加噪
5. 计算损失 L = ||ε - ε_θ(x_t, t)||² # 预测噪声的MSE
6. 梯度下降更新 θ
until converged
推理/采样流程:
1. 采样 x_T ~ N(0, I) # 从纯噪声开始
2. for t = T, T-1, ..., 1:
z ~ N(0, I) if t > 1 else z = 0
x_{t-1} = (1/√αₜ)(x_t - βₜ/√(1-ᾱₜ) · ε_θ(x_t, t)) + σₜ · z
3. return x_0
💡 为什么这个损失如此有效? 这实际上是去噪得分匹配(Denoising Score Matching)的一种形式。网络不需要学习整个数据分布,只需要学习"每个时间步的噪声长什么样"——这是一个远比直接生成图像简单得多的任务。
3. Score-Based模型与SDE统一框架¶
3.1 得分函数(Score Function)¶
得分函数定义为对数概率密度的梯度:
得分函数指向数据密度增大的方向。一旦知道得分函数,就可以通过朗之万动力学(Langevin Dynamics)采样:
3.2 Score Matching与NCSN¶
直接估计得分函数需要知道 \(p(\mathbf{x})\)(归一化常数),这不可行。得分匹配(Score Matching)提供了不需要归一化常数的训练方法。
NCSN(Noise Conditional Score Network,Song & Ermon 2019): - 在多个噪声尺度下估计得分函数 - 使用退火朗之万动力学采样
3.3 SDE统一框架¶
Song et al. (2021) 提出了统一视角:扩散过程可以用随机微分方程(SDE)描述:
前向SDE: $\(d\mathbf{x} = \mathbf{f}(\mathbf{x}, t) \, dt + g(t) \, d\mathbf{w}\)$
逆向SDE: $\(d\mathbf{x} = [\mathbf{f}(\mathbf{x}, t) - g(t)^2 \nabla_{\mathbf{x}} \log p_t(\mathbf{x})] \, dt + g(t) \, d\bar{\mathbf{w}}\)$
| 离散模型 | 对应的SDE |
|---|---|
| DDPM | VP-SDE(Variance Preserving) |
| NCSN | VE-SDE(Variance Exploding) |
💡 SDE框架还引出了ODE采样器(Probability Flow ODE),可以用确定性的ODE求解器加速采样,这是DDIM等加速方法的理论基础。
4. 条件生成:Classifier-Free Guidance¶
4.1 条件扩散模型¶
实际应用中,我们通常希望有条件地生成内容(如根据文本描述生成图像)。条件扩散模型建模:
4.2 Classifier Guidance¶
Dhariwal & Nichol (2021) 提出使用预训练分类器引导生成:
缺点:需要额外训练一个在含噪图像上工作的分类器。
4.3 Classifier-Free Guidance(CFG)¶
Ho & Salimans (2022) 提出了更优雅的方案——无分类器引导:
核心思想:同一个网络同时学习有条件和无条件生成,推理时用两者的线性外推:
其中: - \(w\) 是引导强度(guidance scale),典型值 5-15 - \(\varnothing\) 表示空条件(训练时随机丢弃条件实现) - \(w=0\) 等价于不使用引导 - \(w\) 越大,生成结果越忠于条件,但多样性降低
训练技巧:训练时以一定概率(如10%-20%)将条件 \(c\) 替换为 \(\varnothing\)(空字符串embedding),使模型同时学会有条件和无条件生成。
def classifier_free_guidance(model, x_t, t, cond, w=7.5, uncond=None):
"""Classifier-Free Guidance采样"""
# 有条件预测
eps_cond = model(x_t, t, cond)
# 无条件预测
eps_uncond = model(x_t, t, uncond)
# CFG组合
eps_guided = (1 + w) * eps_cond - w * eps_uncond
return eps_guided
💡 为什么CFG如此重要? 几乎所有现代文生图模型(DALL-E ⅔、Stable Diffusion、Midjourney、Imagen)都使用CFG。它是连接"文本理解"和"图像生成"的关键桥梁。
5. Latent Diffusion Models与Stable Diffusion¶
5.1 像素空间的困境¶
直接在像素空间运行扩散模型面临严重的计算瓶颈:
- 一张 512×512×3 的图像有 786,432 维
- 每步去噪都需要完整的UNet前向传播
- 训练和推理成本极高
5.2 LDM核心架构¶
Rombach et al. (2022) 提出Latent Diffusion Models(LDM):在压缩的潜在空间(Latent Space)中运行扩散过程。
┌─────────────────────────────────────────────────┐
│ LDM 架构 │
│ │
│ 像素空间 潜在空间 像素空间 │
│ ┌──────┐ ┌─────────────────┐ ┌──────┐ │
│ │ 图像 │───→│ 编码器(E) │───→│ z_0 │ │
│ │512x512│ │ 下采样 8x │ │64x64 │ │
│ └──────┘ └─────────────────┘ └──┬───┘ │
│ │ │
│ ┌─────────────────┐ ↓ │
│ │ 扩散过程 │ 加噪/去噪 │
│ │ (在z空间) │ (UNet) │
│ └─────────────────┘ │ │
│ ↓ │
│ ┌──────┐ ┌─────────────────┐ ┌──────┐ │
│ │ 图像 │←──│ 解码器(D) │←──│ z_0' │ │
│ │512x512│ │ 上采样 8x │ │64x64 │ │
│ └──────┘ └─────────────────┘ └──────┘ │
│ │
│ 条件输入──→ CLIP Text Encoder ──→ Cross-Attention│
└─────────────────────────────────────────────────┘
| 组件 | 作用 | 关键细节 |
|---|---|---|
| AutoEncoder (VAE) | 图像↔潜在空间 转换 | 压缩比 8x,潜在维度 4 |
| UNet | 逆向去噪网络 | 含 ResNet块 + Self-Attention + Cross-Attention |
| Text Encoder | 文本条件编码 | CLIP ViT-L/14(SD1.x)/ OpenCLIP ViT-bigG(SDXL) |
| Cross-Attention | 文本-图像交互 | \(\text{Attention}(Q_{img}, K_{text}, V_{text})\) |
5.3 Stable Diffusion架构详解¶
Stable Diffusion 是 LDM 的具体实现,由 Stability AI 开源:
SD 1.x / 2.x: - VAE:将 512×512×3 压缩到 64×64×4 - UNet:约860M参数,含ResBlock + SpatialTransformer - 文本编码器:CLIP(SD1.x)/ OpenCLIP(SD2.x) - 采样器:DDIM、Euler、DPM-Solver等
SDXL: - 更大的UNet(2.6B参数),双文本编码器 - 新增尺寸/裁剪条件embedding - 可选的Refiner模型
SD3 / Flux: - 从UNet转向Transformer架构(MM-DiT) - 采用Flow Matching替代传统DDPM噪声调度 - 支持更高分辨率和更好的文本渲染 - MMDiT(Multimodal DiT):将文本和图像视为等价序列,使用联合注意力机制 - Rectified Flow:改进的Flow Matching训练目标,生成质量更高
5.4 为什么在潜在空间工作?¶
| 比较维度 | 像素空间扩散 | 潜在空间扩散 |
|---|---|---|
| 数据维度 | 512×512×3 = 786K | 64×64×4 = 16K |
| 计算量 | 极大 | 降低约 50x |
| 训练时间 | 数周(大集群) | 数天 |
| 生成质量 | 高 | 高(VAE重建损失可忽略) |
| 灵活性 | 低 | 高(VAE可复用) |
6. DiT:Diffusion Transformer架构¶
6.1 从UNet到Transformer¶
传统扩散模型使用UNet作为去噪网络。Peebles & Xie (2023) 提出的DiT(Diffusion Transformer)证明Transformer架构在扩散模型中同样有效甚至更优。
6.2 DiT架构¶
┌─────────────────────────────────┐
│ DiT Architecture │
│ │
│ 输入: z_t (含噪潜在表示) │
│ ↓ │
│ Patchify (切分为patch tokens) │
│ ↓ │
│ + Positional Embedding │
│ ↓ │
│ ┌───────────────────────────┐ │
│ │ DiT Block × N │ │
│ │ ┌─────────────────────┐ │ │
│ │ │ LayerNorm (adaLN) │ │ │
│ │ │ Self-Attention │ │ │
│ │ │ LayerNorm (adaLN) │ │ │
│ │ │ Feed-Forward │ │ │
│ │ └─────────────────────┘ │ │
│ └───────────────────────────┘ │
│ ↓ │
│ Linear + Reshape │
│ ↓ │
│ 输出: 预测的噪声 ε │
└─────────────────────────────────┘
│ │
│ 条件注入: t + class label │
│ → adaLN-Zero (自适应LayerNorm) │
└─────────────────────────────────┘
DiT的关键创新:
- Patchify:将潜在表示切分为non-overlapping patches,类似ViT
- adaLN-Zero:使用自适应Layer Normalization注入条件(时间步、类别)
- Scaling效果好:模型规模增大时性能持续提升
6.3 DiT的影响¶
DiT开启了扩散模型的"Scaling Law"时代:
| 模型 | 基于DiT | 应用 | 关键特性 |
|---|---|---|---|
| Sora(OpenAI) | ✅ Spatial-Temporal DiT | 视频生成 | 长视频生成,时空一致性 |
| SD3(Stability AI) | ✅ MM-DiT | 文生图 | MMDiT+Rectified Flow |
| Flux(Black Forest Labs) | ✅ 改进DiT | 文生图 | 优秀文本渲染 |
| HunyuanVideo(腾讯) | ✅ | 视频生成 | 中文视频生成 |
| CogVideoX(智谱) | ✅ | 视频生成 | 高质量视频 |
| Qwen-Image(阿里) | ✅ | 文生图 | Flow Matching |
💡 趋势:DiT + Flow Matching 正在替代 UNet + DDPM 成为新一代生成模型的标准配置。
6.4 MMDiT架构详解¶
MMDiT(Multimodal Diffusion Transformer)是SD3和Flux的核心创新:
核心思想:将文本和图像作为等价的token序列,在同一个Transformer中联合处理
class JointAttention(nn.Module): # 继承nn.Module定义神经网络层
"""MMDiT的联合注意力机制"""
def __init__(self, d_model, num_heads): # __init__构造方法,创建对象时自动调用
super().__init__() # super()调用父类方法
self.d_model = d_model
self.num_heads = num_heads
self.head_dim = d_model // num_heads
self.to_q = nn.Linear(d_model, d_model)
self.to_k = nn.Linear(d_model, d_model)
self.to_v = nn.Linear(d_model, d_model)
self.scale = self.head_dim ** -0.5
def forward(self, img, txt):
"""
img: (batch, img_tokens, d_model) - 图像tokens
txt: (batch, txt_tokens, d_model) - 文本tokens
"""
# 拼接图像和文本tokens
x = torch.cat([img, txt], dim=1) # (batch, img_tokens + txt_tokens, d_model)
# 计算Q, K, V
q = self.to_q(x)
k = self.to_k(x)
v = self.to_v(x)
# 分割为多头
q = q.view(q.size(0), -1, self.num_heads, self.head_dim).transpose(1, 2)
k = k.view(k.size(0), -1, self.num_heads, self.head_dim).transpose(1, 2)
v = v.view(v.size(0), -1, self.num_heads, self.head_dim).transpose(1, 2)
# 自注意力(所有tokens互相attend)
attn = (q @ k.transpose(-2, -1)) * self.scale
attn = attn.softmax(dim=-1)
out = attn @ v
# 拆分回图像和文本
out = out.transpose(1, 2).contiguous().view(out.size(0), -1, self.d_model)
img_out, txt_out = out.split([img.size(1), txt.size(1)], dim=1)
return img_out, txt_out
MMDiT的优势: 1. 文本-图像对齐更好:双向注意力机制让文本和图像充分交互 2. 架构更简洁:无需Cross-Attention模块 3. 训练更稳定:统一的注意力机制更易优化
6.5 Flow Matching与Rectified Flow¶
Flow Matching是新一代扩散模型的训练范式:
传统DDPM的问题: - 噪声调度复杂(线性/余弦调度) - 采样步数多(通常50-1000步) - 训练目标不够直观
Flow Matching的改进: - Rectified Flow:使用"修正流"替代噪声预测 - 更快的采样:可降至10-25步甚至更少 - 更好的质量:生成图像更清晰,文本渲染更准确
# Flow Matching的核心思想(简化版)
class FlowMatchingLoss(nn.Module):
"""Flow Matching损失函数"""
def forward(self, model, x_0, t):
"""
x_0: 原始数据
t: 时间步
"""
# 1. 采样噪声和中间状态
eps = torch.randn_like(x_0)
x_t = (1 - t) * x_0 + t * eps # 简单线性插值
# 2. 预测速度场(velocity field)
v_pred = model(x_t, t)
# 3. 计算目标速度场
v_target = eps - x_0 # 从x_0到噪声的方向
# 4. MSE损失
loss = F.mse_loss(v_pred, v_target)
return loss
Rectified Flow的关键改进: - 使用更复杂的插值策略(非简单线性) - 引入"修正"项,改善长距离流动 - 结合CFG(Classifier-Free Guidance),生成质量更高
7. 主要应用领域¶
7.1 图像生成¶
| 应用 | 代表模型 | 说明 |
|---|---|---|
| 文生图(T2I) | Stable Diffusion, DALL-E 3, Midjourney | 最成熟的应用 |
| 图像编辑 | InstructPix2Pix, ControlNet | 根据指令编辑图像 |
| 图像修复(Inpainting) | SD Inpainting | 补全图像缺失部分 |
| 超分辨率 | SR3, StableSR | 低分辨率→高分辨率 |
| 风格迁移 | LoRA, DreamBooth | 少样本个性化生成 |
7.2 视频生成¶
- Sora(OpenAI):可生成长达60秒的高质量视频
- Runway Gen-3:商用视频生成平台
- Kling/可灵(快手):国产视频生成模型
- HunyuanVideo(腾讯):开源视频生成
核心挑战:时间一致性、长视频生成、计算成本
7.3 3D生成¶
- DreamFusion:Text-to-3D,使用SDS(Score Distillation Sampling)
- Zero-1-to-3:单张图像生成3D
- Point-E / Shap-E(OpenAI):点云/隐式场3D生成
- Instant3D:快速3D生成
7.4 音频生成¶
- AudioLDM:文本到音频
- MusicLDM:音乐生成
- Bark:语音合成
- Stable Audio:Stability AI的音频生成
7.5 其他应用¶
- 分子设计:药物分子生成(Diffusion for molecule generation)
- 蛋白质设计:RFDiffusion(蛋白质结构生成)
- 机器人控制:Diffusion Policy(扩散策略,用于机器人动作生成)
- 时间序列:TimeGrad(概率时间序列预测)
8. GAN / VAE / Diffusion 对比¶
8.1 综合对比表¶
| 维度 | GAN | VAE | Diffusion Models |
|---|---|---|---|
| 生成质量 | 高(锐利) | 中(偏模糊) | 极高 |
| 训练稳定性 | 差(模式崩塌) | 好 | 好 |
| 模式覆盖 | 差(模式遗漏) | 好 | 极好 |
| 采样速度 | 极快(一步) | 极快(一步) | 慢(多步迭代) |
| 似然评估 | 不可 | 可(ELBO) | 可 |
| 训练目标 | 对抗博弈 | ELBO最大化 | 去噪得分匹配 |
| 核心网络 | 生成器+判别器 | 编码器+解码器 | 去噪网络 |
| 潜在空间 | 无结构 | 有结构(连续) | 隐式 |
| 条件生成 | cGAN | CVAE | CFG |
| 主要缺点 | 训练不稳定 | 图像模糊 | 速度慢 |
| 当前地位 | 特定领域适用 | 作为组件使用 | 主导范式 |
8.2 采样速度优化¶
扩散模型的主要缺点是采样慢。常见加速方法:
| 方法 | 步数 | 核心思想 |
|---|---|---|
| DDIM | 10-50 | 确定性采样,跳步 |
| DPM-Solver | 10-25 | 高阶ODE求解器 |
| Consistency Models | 1-2 | 直接映射噪声到数据 |
| Latent Consistency Model (LCM) | 1-4 | 潜在空间一致性蒸馏 |
| 蒸馏(Distillation) | 1-8 | 教师-学生蒸馏 |
| SDXL Turbo / Lightning | 1-4 | 对抗蒸馏 |
9. PyTorch实战:简单DDPM训练循环¶
以下是一个完整可运行的简单DDPM实现,在MNIST数据集上训练:
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
import math
# ========================================
# 1. 噪声调度器
# ========================================
class NoiseScheduler:
"""管理DDPM的噪声调度参数"""
def __init__(self, T=1000, beta_min=1e-4, beta_max=0.02, device='cpu'):
self.T = T
self.device = device
# 线性beta调度
self.betas = torch.linspace(beta_min, beta_max, T).to(device)
self.alphas = 1.0 - self.betas
self.alphas_cumprod = torch.cumprod(self.alphas, dim=0)
self.alphas_cumprod_prev = F.pad(self.alphas_cumprod[:-1], (1, 0), value=1.0)
# 预计算常用系数
self.sqrt_alphas_cumprod = torch.sqrt(self.alphas_cumprod)
self.sqrt_one_minus_alphas_cumprod = torch.sqrt(1.0 - self.alphas_cumprod)
self.sqrt_recip_alphas = torch.sqrt(1.0 / self.alphas)
# 逆向过程的后验方差
self.posterior_variance = (
self.betas * (1.0 - self.alphas_cumprod_prev) / (1.0 - self.alphas_cumprod)
)
def add_noise(self, x_0, t, noise=None):
"""前向过程:给x_0添加噪声得到x_t"""
if noise is None:
noise = torch.randn_like(x_0)
sqrt_alpha = self.sqrt_alphas_cumprod[t].view(-1, 1, 1, 1) # view重塑张量形状(要求内存连续)
sqrt_one_minus_alpha = self.sqrt_one_minus_alphas_cumprod[t].view(-1, 1, 1, 1)
x_t = sqrt_alpha * x_0 + sqrt_one_minus_alpha * noise
return x_t, noise
@torch.no_grad() # 禁用梯度计算,节省内存
def sample_step(self, model, x_t, t):
"""逆向过程:单步去噪 x_t -> x_{t-1}"""
t_tensor = torch.full((x_t.shape[0],), t, device=self.device, dtype=torch.long)
# 预测噪声
predicted_noise = model(x_t, t_tensor)
# 计算均值
beta_t = self.betas[t]
sqrt_recip_alpha_t = self.sqrt_recip_alphas[t]
sqrt_one_minus_alpha_cumprod_t = self.sqrt_one_minus_alphas_cumprod[t]
mean = sqrt_recip_alpha_t * (
x_t - beta_t / sqrt_one_minus_alpha_cumprod_t * predicted_noise
)
if t > 0:
noise = torch.randn_like(x_t)
variance = torch.sqrt(self.posterior_variance[t])
return mean + variance * noise
else:
return mean
# ========================================
# 2. 时间步嵌入
# ========================================
class SinusoidalPositionEmbedding(nn.Module):
"""正弦位置编码,将时间步t编码为向量"""
def __init__(self, dim):
super().__init__()
self.dim = dim
def forward(self, t):
device = t.device
half_dim = self.dim // 2
emb = math.log(10000) / (half_dim - 1)
emb = torch.exp(torch.arange(half_dim, device=device) * -emb)
emb = t[:, None].float() * emb[None, :]
emb = torch.cat([emb.sin(), emb.cos()], dim=-1)
return emb
# ========================================
# 3. 简单UNet去噪网络
# ========================================
class SimpleUNet(nn.Module):
"""简化版UNet,用于MNIST去噪"""
def __init__(self, in_channels=1, time_dim=256):
super().__init__()
# 时间步嵌入
self.time_mlp = nn.Sequential(
SinusoidalPositionEmbedding(time_dim),
nn.Linear(time_dim, time_dim),
nn.GELU(),
nn.Linear(time_dim, time_dim),
)
# 编码器(下采样)
self.enc1 = self._make_block(in_channels, 64)
self.enc2 = self._make_block(64, 128)
self.pool = nn.MaxPool2d(2)
# 瓶颈层
self.bottleneck = self._make_block(128, 256)
self.time_proj = nn.Linear(time_dim, 256)
# 解码器(上采样)
self.up2 = nn.ConvTranspose2d(256, 128, kernel_size=2, stride=2)
self.dec2 = self._make_block(256, 128) # 256 = 128(up) + 128(skip)
self.up1 = nn.ConvTranspose2d(128, 64, kernel_size=2, stride=2)
self.dec1 = self._make_block(128, 64) # 128 = 64(up) + 64(skip)
# 输出层
self.out = nn.Conv2d(64, in_channels, kernel_size=1)
def _make_block(self, in_ch, out_ch):
return nn.Sequential(
nn.Conv2d(in_ch, out_ch, 3, padding=1),
nn.GroupNorm(8, out_ch),
nn.GELU(),
nn.Conv2d(out_ch, out_ch, 3, padding=1),
nn.GroupNorm(8, out_ch),
nn.GELU(),
)
def forward(self, x, t):
# 时间步嵌入
t_emb = self.time_mlp(t) # (B, time_dim)
# 编码器
e1 = self.enc1(x) # (B, 64, 28, 28)
e2 = self.enc2(self.pool(e1)) # (B, 128, 14, 14)
# 瓶颈层 + 时间条件注入
b = self.bottleneck(self.pool(e2)) # (B, 256, 7, 7)
t_proj = self.time_proj(t_emb)[:, :, None, None]
b = b + t_proj # 时间条件加入
# 解码器 + 跳跃连接
d2 = self.up2(b) # (B, 128, 14, 14)
d2 = self.dec2(torch.cat([d2, e2], dim=1))
d1 = self.up1(d2) # (B, 64, 28, 28)
d1 = self.dec1(torch.cat([d1, e1], dim=1))
return self.out(d1)
# ========================================
# 4. 训练循环
# ========================================
def train_ddpm(
epochs=10,
batch_size=128,
lr=2e-4,
T=1000,
device='cuda' if torch.cuda.is_available() else 'cpu',
):
"""训练DDPM模型"""
print(f"Using device: {device}")
# 数据集
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize([0.5], [0.5]), # 归一化到 [-1, 1]
])
dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True, num_workers=2) # DataLoader批量加载数据,支持shuffle和多进程
# 模型和优化器
model = SimpleUNet(in_channels=1).to(device)
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
scheduler = NoiseScheduler(T=T, device=device)
print(f"Model parameters: {sum(p.numel() for p in model.parameters()):,}")
# 训练
for epoch in range(epochs):
total_loss = 0
for batch_idx, (images, _) in enumerate(dataloader): # enumerate同时获取索引和元素
images = images.to(device)
# 随机采样时间步
t = torch.randint(0, T, (images.shape[0],), device=device)
# 前向加噪
x_t, noise = scheduler.add_noise(images, t)
# 预测噪声
predicted_noise = model(x_t, t)
# MSE损失
loss = F.mse_loss(predicted_noise, noise)
# 反向传播
optimizer.zero_grad()
loss.backward() # 反向传播计算梯度
# 梯度裁剪(稳定训练)
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
optimizer.step() # 根据梯度更新模型参数
total_loss += loss.item() # .item()将单元素张量转为Python数值
avg_loss = total_loss / len(dataloader)
print(f"Epoch [{epoch+1}/{epochs}], Loss: {avg_loss:.4f}")
return model, scheduler
# ========================================
# 5. 采样/生成
# ========================================
@torch.no_grad()
def generate_samples(
model, scheduler, n_samples=16, device='cuda',
image_size=28, channels=1
):
"""从训练好的DDPM生成图像"""
model.eval() # eval()开启评估模式(关闭Dropout等)
# 从纯噪声开始
x = torch.randn(n_samples, channels, image_size, image_size).to(device)
# 逐步去噪 T -> 0
for t in reversed(range(scheduler.T)):
x = scheduler.sample_step(model, x, t)
# 将 [-1, 1] 映射回 [0, 1]
x = (x + 1) / 2
x = x.clamp(0, 1)
return x
# ========================================
# 主入口
# ========================================
if __name__ == '__main__':
# 训练模型
model, scheduler = train_ddpm(epochs=10, batch_size=128)
# 生成样本
device = next(model.parameters()).device
samples = generate_samples(model, scheduler, n_samples=16, device=device)
print(f"Generated {samples.shape[0]} samples, shape: {samples.shape}")
# 可选:可视化(需要matplotlib)
# import matplotlib.pyplot as plt
# fig, axes = plt.subplots(4, 4, figsize=(8, 8))
# for i, ax in enumerate(axes.flat):
# ax.imshow(samples[i, 0].cpu().numpy(), cmap='gray')
# ax.axis('off')
# plt.savefig('ddpm_samples.png')
# plt.show()
代码要点说明:
| 组件 | 说明 |
|---|---|
NoiseScheduler | 管理前向/逆向过程的所有参数 |
SinusoidalPositionEmbedding | 将整数时间步编码为连续向量,与Transformer的位置编码相同 |
SimpleUNet | 简化版UNet,含编码器-瓶颈-解码器+跳跃连接 |
add_noise | 实现闭式前向加噪 \(x_t = \sqrt{\bar\alpha_t} x_0 + \sqrt{1-\bar\alpha_t} \epsilon\) |
sample_step | 实现单步逆向去噪 |
| 损失函数 | 简单的MSE:\(\lVert\epsilon - \epsilon_\theta(x_t, t)\rVert^2\) |
| 梯度裁剪 | clip_grad_norm_ 防止梯度爆炸,稳定训练 |
10. 面试必考题¶
Q1:请解释DDPM的前向过程和逆向过程¶
参考答案:
前向过程是一个固定的马尔可夫链,逐步向数据 \(\mathbf{x}_0\) 添加高斯噪声。在每一步 \(t\),通过 \(q(\mathbf{x}_t | \mathbf{x}_{t-1}) = \mathcal{N}(\sqrt{1-\beta_t}\mathbf{x}_{t-1}, \beta_t\mathbf{I})\) 添加少量噪声。关键性质是可以用闭式解直接计算任意时刻的 \(\mathbf{x}_t\):\(\mathbf{x}_t = \sqrt{\bar\alpha_t}\mathbf{x}_0 + \sqrt{1-\bar\alpha_t}\boldsymbol\epsilon\)。
逆向过程学习一个参数化的马尔可夫链 \(p_\theta(\mathbf{x}_{t-1}|\mathbf{x}_t)\),从纯噪声 \(\mathbf{x}_T \sim \mathcal{N}(0,I)\) 逐步去噪恢复数据。通常参数化为预测噪声 \(\boldsymbol\epsilon_\theta(\mathbf{x}_t, t)\),训练目标是简单的MSE损失。
要点:只有逆向过程需要学习,前向过程是固定的。
Q2:扩散模型与GAN相比有哪些优缺点?¶
参考答案:
优势: 1. 训练稳定——无对抗训练,不存在模式崩塌 2. 生成多样性好——覆盖全部数据分布 3. 似然可计算——有理论保证 4. 架构灵活——不需要对抗训练的精细平衡
劣势: 1. 采样速度慢——需要数百到数千步迭代(GAN只需一步) 2. 计算资源更大——训练和推理成本都高
现状:通过DDIM、DPM-Solver、一致性模型等加速技术,扩散模型的采样速度已大幅提升(可降至1-4步),基本弥补了速度劣势。
Q3:什么是Classifier-Free Guidance?为什么它很重要?¶
参考答案:
CFG是一种无需额外分类器的条件引导方法。核心是在同一个模型中同时学习条件和无条件生成:
训练时随机drop掉条件(如10%概率置为空),推理时用引导强度 \(w\) 控制条件的影响。
重要性:CFG是连接条件(如文本)和生成结果的核心机制,几乎所有现代文生图系统(DALL-E、SD、Midjourney)都依赖CFG实现高质量条件生成。
Q4:Latent Diffusion Models的核心创新是什么?¶
参考答案:
LDM的核心创新是将扩散过程从像素空间转移到低维潜在空间:
- 先用预训练的VAE将图像编码到潜在空间(如512×512→64×64)
- 在潜在空间中运行扩散过程
- 最后用VAE解码器还原为像素图像
优势: - 计算量降低约50倍(维度从786K降至16K) - 扩散模型专注于语义层面的生成(细节由VAE处理) - VAE是预训练的,可以跨任务复用 - 实现了高质量生成的"民主化"——消费级GPU即可运行
Q5:DiT(Diffusion Transformer)相比UNet有什么优势?¶
参考答案:
DiT用Transformer替代传统UNet作为扩散模型的骨干网络:
- Scaling效果更好——遵循Scaling Law,模型越大效果越好,且可预测
- 架构更简洁——去除了UNet的不规则结构(skip connections等),纯Transformer更标准化
- 与NLP生态兼容——可以复用Transformer的训练基础设施和优化技巧
- 时空扩展自然——处理视频时,时间维度可以自然地作为token加入(Sora就是这样做的)
条件注入使用adaLN-Zero(自适应Layer Normalization),将时间步和条件信息注入到每个Transformer Block的归一化层。
Q6:如何加速扩散模型的采样过程?¶
参考答案:
五种主要加速策略:
- 高效求解器:
- DDIM:将随机过程转为确定性ODE,跳步采样(1000步→50步)
-
DPM-Solver:高阶ODE求解器(10-25步即可)
-
一致性蒸馏:
- Consistency Models(CM):学习从任意噪声级别直接映射到 \(\mathbf{x}_0\)(1-2步)
-
Latent Consistency Model(LCM):在潜在空间做一致性蒸馏(1-4步)
-
知识蒸馏:
- Progressive Distillation:教师模型2步→学生模型1步,递归蒸馏
-
SDXL Turbo/Lightning:对抗蒸馏
-
潜在空间压缩:
-
LDM/Stable Diffusion:在低维空间扩散,减少每步计算
-
缓存与硬件优化:
- DeepCache:缓存UNet中间特征
- FlashAttention:加速Attention计算
- 模型量化:INT8/FP16推理
11. 学习检查清单¶
完成本章学习后,请对照以下清单自测:
基础概念¶
- 能用自己的话解释扩散模型的前向过程和逆向过程
- 理解 \(\alpha_t\)、\(\bar\alpha_t\)、\(\beta_t\) 的含义和关系
- 知道为什么可以用闭式解直接从 \(\mathbf{x}_0\) 计算 \(\mathbf{x}_t\)
- 理解DDPM为什么选择预测噪声而不是直接预测图像
数学与损失¶
- 能推导或解释简化损失 \(L_{\text{simple}} = \mathbb{E}\|\boldsymbol\epsilon - \boldsymbol\epsilon_\theta\|^2\)
- 理解线性调度与余弦调度的区别和优劣
- 知道三种等价参数化(预测噪声/预测x_0/预测v)
条件生成¶
- 理解Classifier Guidance和Classifier-Free Guidance的区别
- 能解释CFG的引导强度 \(w\) 对生成结果的影响
- 知道训练时随机drop条件的原因
架构与系统¶
- 理解LDM为什么在潜在空间运行扩散过程
- 能描述Stable Diffusion的三大组件(VAE/UNet/CLIP)
- 了解DiT架构的创新点和adaLN-Zero
- 知道从UNet到Transformer的演进趋势
实践与应用¶
- 能运行和理解本章的DDPM代码
- 知道至少3种扩散模型加速方法
- 了解扩散模型在图像/视频/3D/音频领域的应用
- 能对比GAN、VAE、Diffusion的优缺点
进阶准备¶
- 对Score-Based模型和SDE框架有初步认识
- 了解Flow Matching是新一代的训练范式
- 准备好深入 扩散模型学习 中的进阶专题
12. 参考资料与延伸阅读¶
必读论文¶
- DDPM:Ho et al., "Denoising Diffusion Probabilistic Models" (NeurIPS 2020)
- Improved DDPM:Nichol & Dhariwal, "Improved Denoising Diffusion Probabilistic Models" (ICML 2021)
- Score SDE:Song et al., "Score-Based Generative Modeling through Stochastic Differential Equations" (ICLR 2021)
- CFG:Ho & Salimans, "Classifier-Free Diffusion Guidance" (NeurIPS Workshop 2021)
- LDM:Rombach et al., "High-Resolution Image Synthesis with Latent Diffusion Models" (CVPR 2022)
- DiT:Peebles & Xie, "Scalable Diffusion Models with Transformers" (ICCV 2023)
经典博客与教程¶
- Lilian Weng: "What are Diffusion Models?" — 优秀的技术博客综述
- Calvin Luo: "Understanding Diffusion Models: A Unified Perspective" — 数学统一视角
- Hugging Face Diffusion Models Course — 动手教程
深度进阶¶
- 📚 扩散模型学习 — 本教程配套的进阶专题,含35+篇深度内容:
- 完整数学推导(前向/逆向/ELBO)
- DDPM从零实现(逐行代码)
- Stable Diffusion深度解析
- 条件生成完整指南
- 视频生成(Sora原理分析)
- 加速采样技术汇总
- …更多专题
附录:扩散模型技术演进¶
A.1 架构演进¶
| 时期 | 主流架构 | 代表模型 | 特点 |
|---|---|---|---|
| 2020-2022 | UNet + DDPM | DDPM, Improved DDPM | 像素空间扩散 |
| 2022-2023 | LDM + UNet | Stable Diffusion 1.x/2.x | 潜在空间扩散 |
| 2023-2024 | DiT + DDPM | DiT, Sora | Transformer骨干 |
| 2024-2025 | DiT + Flow Matching | SD3, Flux, Qwen-Image | MMDiT + Rectified Flow |
A.2 训练目标演进¶
| 技术 | 提出时间 | 核心思想 | 优势 |
|---|---|---|---|
| DDPM | 2020 | 预测噪声 | 简单有效 |
| Improved DDPM | 2021 | 学习方差调度 | 更好似然 |
| Flow Matching | 2022 | 预测速度场 | 更快采样 |
| Rectified Flow | 2024 | 修正流匹配 | 更高质量 |
A.3 采样加速技术¶
| 技术 | 步数 | 核心思想 | 代表模型 |
|---|---|---|---|
| DDIM | 10-50 | 确定性ODE采样 | SD 1.x |
| DPM-Solver | 10-25 | 高阶ODE求解器 | SDXL |
| Consistency Models | 1-2 | 一致性蒸馏 | LCM |
| Flow Matching | 10-25 | 速度场预测 | SD3, Flux |
| LCM-LoRA | 1-4 | 潜在一致性蒸馏 | SDXL Turbo |
💡 趋势:DiT + Flow Matching + 快速采样器的组合正在成为新一代生成模型的标准配置。
更新说明: - 最后更新时间:2026年2月 - 新增内容: - MMDiT架构详解和代码实现 - Flow Matching与Rectified Flow的详细说明 - 扩散模型技术演进附录 - 数据来源:相关论文和开源模型文档
📝 下一步学习建议: - 如果对本章内容都已掌握,建议进入 扩散模型学习 进行系统深入学习 - 如果对GAN/VAE还不熟悉,先回顾 01-GAN基础 和 02-VAE基础



