查看全课大纲(第 33 / 40 节)
  1. 1.从规则到学习:深度学习解决什么问题
  2. 2.表示学习与端到端训练
  3. 3.张量、计算图与自动微分
  4. 4.训练三要素:数据、模型、目标函数
  5. 5.从感知机到多层感知机
  6. 6.反向传播:手推一个两层网络
  7. 7.激活函数与初始化
  8. 8.优化器:SGD → Momentum → Adam
  9. 9.正则化:权重衰减、Dropout、BN/LN
  10. 10.卷积的直觉与参数共享
  11. 11.池化、感受野与下采样
  12. 12.经典网络演进:LeNet → AlexNet → VGG → ResNet
  13. 13.残差连接与归一化为什么有效
  14. 14.迁移学习与特征可视化
  15. 15.RNN 与 BPTT
  16. 16.LSTM/GRU:门控如何缓解梯度问题
  17. 17.Seq2Seq 与编码器-解码器
  18. 18.注意力前史:Bahdanau 加性注意力
  19. 19.Self-Attention:QKV 与缩放点积
  20. 20.多头注意力:并行的多种关系
  21. 21.位置编码:正弦与可学习
  22. 22.残差、LayerNorm 与前馈层
  23. 23.从零实现一个最小 Transformer block
  24. 24.训练稳定性、复杂度与常见误区
  25. 25.自监督与语言建模目标
  26. 26.BERT:掩码语言模型与双向编码
  27. 27.GPT:自回归生成与缩放定律
  28. 28.指令微调与对齐:SFT → RLHF/DPO
  29. 29.参数高效微调:LoRA 与适配器
  30. 30.自编码器与 VAE
  31. 31.GAN:对抗训练与模式崩塌
  32. 32.扩散模型:前向加噪与反向去噪
  33. 33.扩散模型的应用与采样加速
  34. 34.对比学习与 CLIP
  35. 35.ViT:把图像当序列
  36. 36.多模态架构:对齐、融合与生成
  37. 37.能力边界、幻觉与评测
  38. 38.PyTorch 2.x 训练循环与混合精度
  39. 39.实验管理、评测与复现
  40. 40.推理部署与成本:量化/蒸馏概览

扩散模型的应用与采样加速

小象实战讲义 · 深度学习基础:从神经网络到 Transformer

学完本节,你将能回答:扩散模型在生成高分辨率图像时为何计算成本高昂?Latent Diffusion Model(LDM)如何通过引入潜在空间大幅降低计算量?DDIM 等采样加速方法背后的原理是什么?本节是第 7 章《生成模型》的最后一节。先修:扩散模型的基本前向与反向过程(本章第 3 节)。后续:第 8 章《多模态与前沿》会把视觉与语言放进同一个表征空间,讲 CLIP 与 ViT。

💡 核心导读

  1. 像素空间扩散的瓶颈:原始扩散模型直接在像素空间操作,每一步去噪都需要在高维空间(如 256×256×3)进行神经网络前向传播,导致训练和采样极其耗时。
  2. 潜在扩散模型(LDM)的突破:通过一个预训练的变分自编码器(VAE),将图像压缩到一个更低维的潜在空间,扩散过程在此空间进行,极大减少了计算量。
  3. 采样加速技术:DDIM 等方法通过将随机扩散过程转化为确定性过程,或减少采样步数,在不显著损失质量的前提下,将采样速度提升数十倍。
  4. 灵活的条件生成:通过在扩散模型的 U-Net 中引入交叉注意力(Cross-Attention)层,LDM 能够接受文本、语义图等多种条件输入,实现可控生成。

🧭 前置知识

  1. 扩散模型的基本框架:前向加噪过程 q(xt∣xt−1)q(\mathbf{x}_t | \mathbf{x}_{t-1}) 与反向去噪过程 pθ(xt−1∣xt)p_\theta(\mathbf{x}_{t-1} | \mathbf{x}_t)。
  2. 变分自编码器(VAE)的基本思想:编码器将输入映射到潜在变量 z\mathbf{z},解码器从 z\mathbf{z} 重建输入。
  3. 注意力机制,特别是交叉注意力(Query 来自一种模态,Key/Value 来自另一种模态)。
  4. 矩阵乘法与张量形状的基本操作。

像素空间扩散的计算挑战

在 Denoising Diffusion Probabilistic Models(Ho et al., 2020,arXiv:2006.11239)中,扩散模型在像素空间取得了惊艳的生成效果。但代价同样惊人:正如 High-Resolution Image Synthesis with Latent Diffusion Models(Rombach et al., 2021,arXiv:2112.10752)在动机部分所总结的,在 256×256 分辨率上训练这类模型往往需要数百个 GPU 天(论文举例 150–1000 个 V100 天),生成 5 万张样本约需 5 天(单张 A100)。原因在于:

  1. 维度灾难:一张 256×256×3 的图像有 196,608 维,每一步去噪都要处理这个高维张量。
  2. 序列评估:采样需要连续进行 TT 步(通常 T=1000T=1000)的去噪操作,无法并行。
  3. 细节建模:如 LDM 论文所分析,像素含大量人眼难感知的高频细节;扩散模型作为似然模型,会花大量算力去建模这些“不重要”信息。

下面代码对比像素空间与潜在空间的数据量:

import torch
torch.manual_seed(0)

# 为控制示例规模用 64x64,缩减比例与 256x256 完全一致
batch_size = 2
height = 64
width = 64
channels = 3
pixel_image = torch.randn(batch_size, channels, height, width)

# 模拟一个压缩率为 f=4 的 VAE 编码器输出
# 即空间尺寸缩小 4 倍,通道数可能增加
f = 4
latent_channels = 4  # 潜在空间通道数
latent_height = height // f
latent_width = width // f
latent_z = torch.randn(batch_size, latent_channels, latent_height, latent_width)

print(f"像素图像张量形状: {tuple(pixel_image.shape)}")
print(f"元素总数: {pixel_image.numel()}")
print(f"潜在编码张量形状: {tuple(latent_z.shape)}")
print(f"元素总数: {latent_z.numel()}")
print(f"数据量缩减比例: {latent_z.numel() / pixel_image.numel():.4f}")
像素图像张量形状: (2, 3, 64, 64)
元素总数: 24576
潜在编码张量形状: (2, 4, 16, 16)
元素总数: 2048
数据量缩减比例: 0.0833

可以看出,潜在表示只有像素表示的 8.3%,在潜在空间做扩散要处理的数据量整整少了一个数量级。

潜在扩散模型(LDM)的核心机制

LDM 的核心思想是 “两步走”:

  1. 感知压缩:训练一个自编码器(包括编码器 E\mathcal{E} 和解码器 D\mathcal{D}),学习一个在感知上等价于图像空间的、低维的潜在空间。编码器将图像 x∈RH×W×3\mathbf{x} \in \mathbb{R}^{H \times W \times 3} 映射到潜在表示 z=E(x)∈Rh×w×c\mathbf{z} = \mathcal{E}(\mathbf{x}) \in \mathbb{R}^{h \times w \times c},其中 h=H/f,w=W/fh = H/f, w = W/f。
  2. 潜在空间生成:在潜在空间 z\mathbf{z} 上训练一个扩散模型。生成时,先由扩散模型在潜在空间中生成 z\mathbf{z},再通过解码器 D\mathcal{D} 映射回像素空间 x=D(z)\mathbf{x} = \mathcal{D}(\mathbf{z})。

为何 LDM 能取得更好的权衡?

早期方法(如 VQGAN 这类向量量化自编码器)用了较大的下采样因子(如 f=16f=16),空间信息丢失严重、重建保真度低。LDM 论文指出,由于扩散模型本身对空间数据有优秀的归纳偏置,我们不需要如此激进的空间下采样。可以选择一个更温和的压缩率(如 f=4f=4 或 f=8f=8),在减少计算量的同时保留足够的细节。论文的训练消融(Fig.6)也印证这点:f=1f=1 训练推进慢,f=32f=32 样本质量受限,f=4f=4–1616 是折中区间。

条件生成:交叉注意力机制

LDM 的另一个关键创新是引入了 基于交叉注意力的条件机制,使其能够处理文本、类别标签、语义图等多种条件输入。

具体实现是,在扩散模型 U-Net 的每一层,将条件信息 y\mathbf{y}(通过一个专门的编码器 τθ\tau_\theta 投影)作为 Key 和 Value,将 U-Net 的中间特征图作为 Query,进行交叉注意力计算:

Attention(Q,K,V)=softmax(QKTd)V\text{Attention}(\mathbf{Q}, \mathbf{K}, \mathbf{V}) = \text{softmax}\left(\frac{\mathbf{Q}\mathbf{K}^T}{\sqrt{d}}\right) \mathbf{V}

其中 Q=WQ⋅φi(zt)\mathbf{Q} = \mathbf{W}_Q \cdot \varphi_i(\mathbf{z}_t), K=WK⋅τθ(y)\mathbf{K} = \mathbf{W}_K \cdot \tau_\theta(\mathbf{y}), V=WV⋅τθ(y)\mathbf{V} = \mathbf{W}_V \cdot \tau_\theta(\mathbf{y}),φi(zt)\varphi_i(\mathbf{z}_t) 是 U-Net 第 ii 层的中间表示。

下面的代码块模拟了在潜在扩散的 U-Net 中,一个交叉注意力层的前向计算过程:

import torch
import torch.nn as nn
import torch.nn.functional as F
torch.manual_seed(0)

# 模拟参数(玩具规模便于 CPU 演示;以 Stable Diffusion 为例:潜空间特征图
# 64x64=4096 个 token,文本条件为 CLIP 编码的 77 个 token、768 维)
batch = 2
latent_seq_len = 4 * 4  # 将空间特征图展平
cond_seq_len = 12      # 条件序列长度(如文本 token 数)
d_model = 64           # U-Net 特征维度
d_cond = 48            # 条件编码维度
d_head = 16            # 注意力头维度
num_heads = d_model // d_head  # 4 个头

# 1. 模拟 U-Net 中间特征 (作为 Query)
# 形状: (batch, latent_seq_len, d_model)
unet_feat = torch.randn(batch, latent_seq_len, d_model)
# 2. 模拟条件编码 (作为 Key 和 Value)
# 形状: (batch, cond_seq_len, d_cond)
cond_emb = torch.randn(batch, cond_seq_len, d_cond)

# 3. 定义投影层
W_q = nn.Linear(d_model, d_model)  # 将 U-Net 特征投影到 Query
W_k = nn.Linear(d_cond, d_model)   # 将条件编码投影到 Key
W_v = nn.Linear(d_cond, d_model)   # 将条件编码投影到 Value
# 初始化权重(仅为演示)
for layer in [W_q, W_k, W_v]:
    nn.init.xavier_uniform_(layer.weight)
    nn.init.zeros_(layer.bias)

# 4. 计算 Query, Key, Value
Q = W_q(unet_feat)  # (batch, latent_seq_len, d_model)
K = W_k(cond_emb)   # (batch, cond_seq_len, d_model)
V = W_v(cond_emb)   # (batch, cond_seq_len, d_model)

# 5. 多头注意力拆分、计算、合并(简化版,使用 PyTorch 内置函数)
# 重塑为 (batch, seq_len, num_heads, d_head) 然后转置以进行批量矩阵乘法
Q = Q.view(batch, latent_seq_len, num_heads, d_head).transpose(1, 2)
K = K.view(batch, cond_seq_len, num_heads, d_head).transpose(1, 2)
V = V.view(batch, cond_seq_len, num_heads, d_head).transpose(1, 2)

# 缩放点积注意力
attn_scores = torch.matmul(Q, K.transpose(-2, -1)) / (d_head ** 0.5)  # (batch, num_heads, latent_seq_len, cond_seq_len)
attn_weights = F.softmax(attn_scores, dim=-1)
attn_output = torch.matmul(attn_weights, V)  # (batch, num_heads, latent_seq_len, d_head)

# 6. 合并多头输出
attn_output = attn_output.transpose(1, 2).contiguous().view(batch, latent_seq_len, d_model)  # (batch, latent_seq_len, d_model)

# 7. 输出投影(可选,这里省略)
print(f"U-Net 特征形状: {tuple(unet_feat.shape)}")
print(f"条件编码形状: {tuple(cond_emb.shape)}")
print(f"注意力权重形状: {tuple(attn_weights.shape)}")
print(f"交叉注意力输出形状: {tuple(attn_output.shape)}")
# 检查注意力权重的分布(对第一个样本,第一个头)
print(f"注意力权重[0,0] 行和: {[round(x, 4) for x in attn_weights[0,0].sum(dim=-1).tolist()][:5]}... (前5个Query)")
U-Net 特征形状: (2, 16, 64)
条件编码形状: (2, 12, 48)
注意力权重形状: (2, 4, 16, 12)
交叉注意力输出形状: (2, 16, 64)
注意力权重[0,0] 行和: [1.0, 1.0, 1.0, 1.0, 1.0]... (前5个Query)

通过交叉注意力,U-Net 在去噪的每一步都能“看到”条件信息,从而生成与条件相符的图像。

采样加速:从 DDPM 到 DDIM

原始 DDPM 采样是一个从 t=Tt=T 到 t=0t=0 的马尔可夫链,需要完整走完 TT 步。Denoising Diffusion Implicit Models(Song et al., 2020,arXiv:2010.02502)提出了一种非马尔可夫的推导,允许使用更少的步数进行采样。

其核心是推导出一个确定性的反向过程:给定 xt\mathbf{x}_t 与去噪网络对 x0\mathbf{x}_0 的预测 x^0\hat{\mathbf{x}}_0,可直接算出 xt−1\mathbf{x}_{t-1}:

xt−1=αˉt−1x^0+1−αˉt−1−σt2⋅ϵθ(xt,t)+σtz\mathbf{x}_{t-1} = \sqrt{\bar{\alpha}_{t-1}} \hat{\mathbf{x}}_0 + \sqrt{1 - \bar{\alpha}_{t-1} - \sigma_t^2} \cdot \epsilon_\theta(\mathbf{x}_t, t) + \sigma_t \mathbf{z}

式中 z∼N(0,I)\mathbf{z} \sim \mathcal{N}(\mathbf{0}, \mathbf{I}) 是标准高斯噪声(与潜在变量 z\mathbf{z} 同名不同义)。当 σt=0\sigma_t = 0 时整个反向过程变为确定性,称为 DDIM:可定义一个比训练步数 TT 短得多的子序列 {τ1,...,τS}\lbrace \tau_1, ..., \tau_S \rbrace(S≪TS \ll T),按其跳跃采样即可大幅加速。

下面代码对比两种子序列选择策略:

import numpy as np

T = 1000  # 训练总步数
S = 10    # 采样步数

def to_indices(arr):
    """转成 python int 列表:去重、降到 [0, T-1]、以 T-1 开头、以 0 结尾"""
    vals = sorted({int(min(max(round(v), 0), T - 1)) for v in arr}, reverse=True)
    if vals[0] != T - 1:
        vals = [T - 1] + vals
    if vals[-1] != 0:
        vals = vals + [0]
    return vals

# 方法1: 均匀间隔(从 T-1 到 0 等距)
uniform_indices = to_indices(np.linspace(T - 1, 0, S, dtype=int))

# 方法2: 平方根间隔(t 大的一侧稀疏,t 小的一侧密集)
sqrt_indices = to_indices(np.linspace(0, np.sqrt(T - 1), S) ** 2)

print(f"均匀间隔子序列: {uniform_indices}")
print(f"平方根间隔子序列: {sqrt_indices}")
均匀间隔子序列: [999, 888, 777, 666, 555, 444, 333, 222, 111, 0]
平方根间隔子序列: [999, 789, 604, 444, 308, 197, 111, 49, 12, 0]

实际 DDIM 采样就按这个子序列做确定性“跳跃式”去噪,把步数从 1000 降到 10–50 步,加速数十倍。

📝 动手练一练

假设我们有一个在 T=1000T=1000 步上训练的扩散模型,现在想用 DDIM 方法进行 S=5S=5 步的采样。请设计一个子序列,要求从 t=999t=999 开始,到 t=0t=0 结束,并且步长逐渐减小(即前期步长大,后期步长小),以更好地匹配扩散过程噪声从大到小的变化规律。

👉 点击查看参考答案

一种常见的策略是使用余弦调度的逆函数来生成间隔。这里我们采用一个简单的二次函数来模拟这种“先粗后细”的间隔。

思路:我们希望索引序列 [τ0,τ1,...,τS−1][\tau_0, \tau_1, ..., \tau_{S-1}] 满足 τ0=T−1\tau_0 = T-1、τS−1=0\tau_{S-1}=0,并且相邻间隔 τi−τi+1\tau_i - \tau_{i+1} 随 ii 增大而减小(先粗后细)。一个简单构造是先取 sj=⌊(T−1)×(j/(S−1))2⌋s_j = \lfloor (T-1) \times (j/(S-1))^2 \rfloor(j=0,…,S−1j=0,\dots,S-1,步长随 jj 递增),再把整个列表反转,从 T−1T-1 走向 0 时步长就变成了递减。

import numpy as np
T = 1000
S = 5
indices = []
for j in range(S):
    # 二次函数:j 增大时步长增大,反转后就是“先粗后细”
    frac = (j / (S - 1)) ** 2
    t = int((T - 1) * frac)
    indices.append(t)
indices = indices[::-1]
# 确保从大到小排序且包含首尾
indices = sorted(list(dict.fromkeys(indices)), reverse=True)
if indices[-1] != 0:
    indices.append(0)
print(f"设计的子序列: {indices}")
设计的子序列: [999, 561, 249, 62, 0]

从 999 到 561 跨了 438,而 62 到 0 只跨 62——前期步子大、后期小,符合“先粗后细”的去噪直觉。

常见错误

错误理解或写法现象或报错原因正确做法
在潜在空间训练扩散模型时,使用与像素空间相同的噪声调度 βt\beta_t。训练不稳定,生成质量差。潜在空间的数值分布与像素空间(通常归一化到 [-1, 1])不同,其方差可能更小或更大。直接套用像素空间的噪声调度会导致信噪比不匹配。根据潜在空间数据的经验方差,重新调整或设计噪声调度 βt\beta_t。LDM 论文中提到了对潜在变量进行缩放以调节信噪比。
在实现交叉注意力时,将条件编码直接与 U-Net 特征拼接(Concatenate)。模型难以训练,条件控制效果弱。简单的拼接无法建立特征图每个位置与条件序列元素间的灵活关系。交叉注意力通过可学习的相似度计算,能动态地让每个视觉特征关注条件中最相关的部分。使用标准的交叉注意力层,将 U-Net 特征作为 Query,条件编码作为 Key 和 Value。
使用 DDIM 采样时,子序列选择完全均匀间隔。在步数很少时(如 S<20),生成图像质量明显下降,细节模糊。均匀间隔没有考虑扩散过程早期噪声大、变化快,后期噪声小、变化慢的特性。在关键的变化阶段采样点不足。使用非均匀间隔,如平方根间隔、余弦间隔等,在噪声大的区域(对应早期)设置更稀疏的采样点,在噪声小的区域(对应后期)设置更密集的采样点。

本章小结

  1. 原始像素空间扩散模型的主要瓶颈在于高维数据的序列化计算,导致训练和采样成本极高。
  2. 潜在扩散模型(LDM)通过引入一个预训练的感知压缩模型(VAE),将扩散过程转移到低维潜在空间,实现了计算复杂度和生成质量的最佳权衡。
  3. LDM 通过在其 U-Net 骨干网络中集成交叉注意力层,成为了一个强大且灵活的多条件生成器,支持文本、布局等丰富条件输入。
  4. DDIM 等采样加速方法通过将反向过程推导为非马尔可夫链,允许使用更少的确定性步骤进行采样,在不显著牺牲质量的前提下大幅提升生成速度。

📋 行动清单

  • 尝试修改潜在空间压缩率 f 的模拟代码,计算 f=8 和 f=16 时的数据缩减比例,直观理解压缩率与计算量的关系。
  • 在交叉注意力模拟代码中,将 d_head 改为 32,观察 num_heads 如何变化,并思考这对模型容量有何影响。
  • 设计一个 S=7 步的 DDIM 采样子序列,要求使用不同的函数(如指数函数)来生成非均匀间隔,并与参考答案的二次函数间隔进行对比。

延伸阅读

  1. Denoising Diffusion Probabilistic Models(Ho et al., 2020,arXiv:2006.11239)
  2. High-Resolution Image Synthesis with Latent Diffusion Models(Rombach et al., 2021,arXiv:2112.10752)
  3. Denoising Diffusion Implicit Models(Song et al., 2020,arXiv:2010.02502)

—— 小象教研组