查看全课大纲(第 33 / 40 节)
- 1.从规则到学习:深度学习解决什么问题
- 2.表示学习与端到端训练
- 3.张量、计算图与自动微分
- 4.训练三要素:数据、模型、目标函数
- 5.从感知机到多层感知机
- 6.反向传播:手推一个两层网络
- 7.激活函数与初始化
- 8.优化器:SGD → Momentum → Adam
- 9.正则化:权重衰减、Dropout、BN/LN
- 10.卷积的直觉与参数共享
- 11.池化、感受野与下采样
- 12.经典网络演进:LeNet → AlexNet → VGG → ResNet
- 13.残差连接与归一化为什么有效
- 14.迁移学习与特征可视化
- 15.RNN 与 BPTT
- 16.LSTM/GRU:门控如何缓解梯度问题
- 17.Seq2Seq 与编码器-解码器
- 18.注意力前史:Bahdanau 加性注意力
- 19.Self-Attention:QKV 与缩放点积
- 20.多头注意力:并行的多种关系
- 21.位置编码:正弦与可学习
- 22.残差、LayerNorm 与前馈层
- 23.从零实现一个最小 Transformer block
- 24.训练稳定性、复杂度与常见误区
- 25.自监督与语言建模目标
- 26.BERT:掩码语言模型与双向编码
- 27.GPT:自回归生成与缩放定律
- 28.指令微调与对齐:SFT → RLHF/DPO
- 29.参数高效微调:LoRA 与适配器
- 30.自编码器与 VAE
- 31.GAN:对抗训练与模式崩塌
- 32.扩散模型:前向加噪与反向去噪
- 33.扩散模型的应用与采样加速
- 34.对比学习与 CLIP
- 35.ViT:把图像当序列
- 36.多模态架构:对齐、融合与生成
- 37.能力边界、幻觉与评测
- 38.PyTorch 2.x 训练循环与混合精度
- 39.实验管理、评测与复现
- 40.推理部署与成本:量化/蒸馏概览
扩散模型的应用与采样加速
小象实战讲义 · 深度学习基础:从神经网络到 Transformer
学完本节,你将能回答:扩散模型在生成高分辨率图像时为何计算成本高昂?Latent Diffusion Model(LDM)如何通过引入潜在空间大幅降低计算量?DDIM 等采样加速方法背后的原理是什么?本节是第 7 章《生成模型》的最后一节。先修:扩散模型的基本前向与反向过程(本章第 3 节)。后续:第 8 章《多模态与前沿》会把视觉与语言放进同一个表征空间,讲 CLIP 与 ViT。
💡 核心导读
- 像素空间扩散的瓶颈:原始扩散模型直接在像素空间操作,每一步去噪都需要在高维空间(如 256×256×3)进行神经网络前向传播,导致训练和采样极其耗时。
- 潜在扩散模型(LDM)的突破:通过一个预训练的变分自编码器(VAE),将图像压缩到一个更低维的潜在空间,扩散过程在此空间进行,极大减少了计算量。
- 采样加速技术:DDIM 等方法通过将随机扩散过程转化为确定性过程,或减少采样步数,在不显著损失质量的前提下,将采样速度提升数十倍。
- 灵活的条件生成:通过在扩散模型的 U-Net 中引入交叉注意力(Cross-Attention)层,LDM 能够接受文本、语义图等多种条件输入,实现可控生成。
🧭 前置知识
- 扩散模型的基本框架:前向加噪过程 与反向去噪过程 。
- 变分自编码器(VAE)的基本思想:编码器将输入映射到潜在变量 ,解码器从 重建输入。
- 注意力机制,特别是交叉注意力(Query 来自一种模态,Key/Value 来自另一种模态)。
- 矩阵乘法与张量形状的基本操作。
像素空间扩散的计算挑战
在 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)。原因在于:
- 维度灾难:一张 256×256×3 的图像有 196,608 维,每一步去噪都要处理这个高维张量。
- 序列评估:采样需要连续进行 步(通常 )的去噪操作,无法并行。
- 细节建模:如 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 的核心思想是 “两步走”:
- 感知压缩:训练一个自编码器(包括编码器 和解码器 ),学习一个在感知上等价于图像空间的、低维的潜在空间。编码器将图像 映射到潜在表示 ,其中 。
- 潜在空间生成:在潜在空间 上训练一个扩散模型。生成时,先由扩散模型在潜在空间中生成 ,再通过解码器 映射回像素空间 。
为何 LDM 能取得更好的权衡?
早期方法(如 VQGAN 这类向量量化自编码器)用了较大的下采样因子(如 ),空间信息丢失严重、重建保真度低。LDM 论文指出,由于扩散模型本身对空间数据有优秀的归纳偏置,我们不需要如此激进的空间下采样。可以选择一个更温和的压缩率(如 或 ),在减少计算量的同时保留足够的细节。论文的训练消融(Fig.6)也印证这点: 训练推进慢, 样本质量受限,– 是折中区间。
条件生成:交叉注意力机制
LDM 的另一个关键创新是引入了 基于交叉注意力的条件机制,使其能够处理文本、类别标签、语义图等多种条件输入。
具体实现是,在扩散模型 U-Net 的每一层,将条件信息 (通过一个专门的编码器 投影)作为 Key 和 Value,将 U-Net 的中间特征图作为 Query,进行交叉注意力计算:
其中 , , , 是 U-Net 第 层的中间表示。
下面的代码块模拟了在潜在扩散的 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 采样是一个从 到 的马尔可夫链,需要完整走完 步。Denoising Diffusion Implicit Models(Song et al., 2020,arXiv:2010.02502)提出了一种非马尔可夫的推导,允许使用更少的步数进行采样。
其核心是推导出一个确定性的反向过程:给定 与去噪网络对 的预测 ,可直接算出 :
式中 是标准高斯噪声(与潜在变量 同名不同义)。当 时整个反向过程变为确定性,称为 DDIM:可定义一个比训练步数 短得多的子序列 (),按其跳跃采样即可大幅加速。
下面代码对比两种子序列选择策略:
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 步,加速数十倍。
📝 动手练一练
假设我们有一个在 步上训练的扩散模型,现在想用 DDIM 方法进行 步的采样。请设计一个子序列,要求从 开始,到 结束,并且步长逐渐减小(即前期步长大,后期步长小),以更好地匹配扩散过程噪声从大到小的变化规律。
👉 点击查看参考答案
一种常见的策略是使用余弦调度的逆函数来生成间隔。这里我们采用一个简单的二次函数来模拟这种“先粗后细”的间隔。
思路:我们希望索引序列 满足 、,并且相邻间隔 随 增大而减小(先粗后细)。一个简单构造是先取 (,步长随 递增),再把整个列表反转,从 走向 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——前期步子大、后期小,符合“先粗后细”的去噪直觉。
常见错误
| 错误理解或写法 | 现象或报错 | 原因 | 正确做法 |
|---|---|---|---|
| 在潜在空间训练扩散模型时,使用与像素空间相同的噪声调度 。 | 训练不稳定,生成质量差。 | 潜在空间的数值分布与像素空间(通常归一化到 [-1, 1])不同,其方差可能更小或更大。直接套用像素空间的噪声调度会导致信噪比不匹配。 | 根据潜在空间数据的经验方差,重新调整或设计噪声调度 。LDM 论文中提到了对潜在变量进行缩放以调节信噪比。 |
| 在实现交叉注意力时,将条件编码直接与 U-Net 特征拼接(Concatenate)。 | 模型难以训练,条件控制效果弱。 | 简单的拼接无法建立特征图每个位置与条件序列元素间的灵活关系。交叉注意力通过可学习的相似度计算,能动态地让每个视觉特征关注条件中最相关的部分。 | 使用标准的交叉注意力层,将 U-Net 特征作为 Query,条件编码作为 Key 和 Value。 |
| 使用 DDIM 采样时,子序列选择完全均匀间隔。 | 在步数很少时(如 S<20),生成图像质量明显下降,细节模糊。 | 均匀间隔没有考虑扩散过程早期噪声大、变化快,后期噪声小、变化慢的特性。在关键的变化阶段采样点不足。 | 使用非均匀间隔,如平方根间隔、余弦间隔等,在噪声大的区域(对应早期)设置更稀疏的采样点,在噪声小的区域(对应后期)设置更密集的采样点。 |
本章小结
- 原始像素空间扩散模型的主要瓶颈在于高维数据的序列化计算,导致训练和采样成本极高。
- 潜在扩散模型(LDM)通过引入一个预训练的感知压缩模型(VAE),将扩散过程转移到低维潜在空间,实现了计算复杂度和生成质量的最佳权衡。
- LDM 通过在其 U-Net 骨干网络中集成交叉注意力层,成为了一个强大且灵活的多条件生成器,支持文本、布局等丰富条件输入。
- DDIM 等采样加速方法通过将反向过程推导为非马尔可夫链,允许使用更少的确定性步骤进行采样,在不显著牺牲质量的前提下大幅提升生成速度。
📋 行动清单
- 尝试修改潜在空间压缩率
f的模拟代码,计算f=8和f=16时的数据缩减比例,直观理解压缩率与计算量的关系。 - 在交叉注意力模拟代码中,将
d_head改为 32,观察num_heads如何变化,并思考这对模型容量有何影响。 - 设计一个 S=7 步的 DDIM 采样子序列,要求使用不同的函数(如指数函数)来生成非均匀间隔,并与参考答案的二次函数间隔进行对比。
延伸阅读
- 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)
- Denoising Diffusion Implicit Models(Song et al., 2020,arXiv:2010.02502)
—— 小象教研组