共计 2824 个字符,预计需要花费 8 分钟才能阅读完成。
理解扩散模型的数学基础
扩散模型的核心思想其实很有趣——想象一杯清水滴入墨水,墨水会逐渐扩散直到整杯水变得均匀。这个过程反过来,就是从混沌中恢复出清晰图像的神奇能力。让我们拆解两个关键概念:

-
马尔可夫链假设:扩散模型认为每一步的噪声添加只依赖前一步的状态,就像多米诺骨牌,当前状态只被前一块影响。数学表达为:
$$q(x_t|x_{t-1}) = \mathcal{N}(x_t; \sqrt{1-\beta_t}x_{t-1}, \beta_t\mathbf{I})$$
其中 $\beta_t$ 是预设的噪声计划表(noise schedule)。 -
变分下界(ELBO):模型通过最大化这个下界来学习逆向过程。简单说,就是让模型预测的 ” 去噪路径 ” 尽可能接近真实的扩散路径:
$$\text{ELBO} = \mathbb{E}q[\log p\theta(x_0|T)] – \sum_{t=1}^T D_{KL}(q(x_{t-1}|x_t,x_0)||p_\theta(x_{t-1}|x_t))$$
DDPM 与 DDIM 的关键对比
- DDPM:
- 严格遵循马尔可夫过程
- 需要完整 T 步采样(通常 T =1000)
-
训练稳定但采样慢
-
DDIM:
- 非马尔可夫过程,允许跳步采样
- 采样步数可缩减至 20-50 步
- 需要更精细的超参调节
选择建议:优先用 DDPM 打基础,理解原理后再尝试 DDIM 加速。
PyTorch 实战实现
1. 高斯扩散可视化
import matplotlib.pyplot as plt
import torch
def visualize_diffusion(image, betas, steps=5):
alphas = 1 - betas
alpha_bars = torch.cumprod(alphas, dim=0)
plt.figure(figsize=(15,3))
for t in range(0, len(betas), len(betas)//steps):
noise = torch.randn_like(image)
noisy_img = torch.sqrt(alpha_bars[t])*image + torch.sqrt(1-alpha_bars[t])*noise
plt.subplot(1, steps+1, t//(len(betas)//steps)+1)
plt.imshow(noisy_img.squeeze().cpu().numpy(), cmap='gray')
2. 带 EMA 的 U -Net
class UNetBlock(nn.Module):
def __init__(self, in_c, out_c, time_emb_dim):
super().__init__()
self.time_mlp = nn.Sequential(nn.Linear(time_emb_dim, out_c),
nn.SiLU(),
nn.Linear(out_c, out_c)
)
self.conv = nn.Sequential(nn.Conv2d(in_c, out_c, 3, padding=1),
nn.BatchNorm2d(out_c),
nn.SiLU())
def forward(self, x, t):
h = self.conv(x)
time_emb = self.time_mlp(t)[:,:,None,None]
return h + time_emb
# 使用 EMA 包装器
class EMA:
def __init__(self, beta=0.9999):
self.beta = beta
self.shadow = {}
def register(self, module):
for name, param in module.named_parameters():
self.shadow[name] = param.data.clone()
def update(self, module):
for name, param in module.named_parameters():
self.shadow[name] = self.beta * self.shadow[name] + (1-self.beta) * param.data
3. 余弦噪声调度器
def cosine_beta_schedule(timesteps, s=0.008):
"""https://arxiv.org/abs/2102.09672"""
steps = timesteps + 1
x = torch.linspace(0, timesteps, steps)
alphas_cumprod = torch.cos(((x / timesteps) + 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, 0.999)
训练优化与问题解决
典型问题 1:梯度消失
- 现象:深层网络参数更新幅度趋近于 0
- 解决方案:
- 使用残差连接(如 U -Net 的 skip connection)
- 采用梯度裁剪(
torch.nn.utils.clip_grad_norm_) - 混合精度训练(AMP):
scaler = torch.cuda.amp.GradScaler() with torch.cuda.amp.autocast(): loss = model(x, t) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()
典型问题 2:采样速度慢
- 加速方案:
- 知识蒸馏:训练轻量学生模型模仿大模型行为
- DDIM 采样:将 1000 步压缩到 50 步
- 渐进式蒸馏:迭代式压缩模型步数
避坑指南
- 数值稳定性:
- 对预测噪声进行
torch.clamp(noise, -clip_val, clip_val) -
使用
torch.where(t > 0, ..., ...)处理 t = 0 边界条件 -
超参调节:
- 余弦调度器的
s参数建议 0.01-0.02 -
初始 $\beta_1$ 建议 1e-4,最终 $\beta_T$ 建议 0.02-0.05
-
资源预估:
- 256×256 图像训练需 16GB+ 显存
- 批次大小与分辨率平方成反比(如 256²→batch=8,512²→batch=2)
开放思考:Latent Diffusion 的可能性
传统扩散直接在像素空间操作,计算代价高昂。Latent Diffusion 先在 VAE 的隐空间进行扩散,最后解码到像素空间:
1. 先用 VAE 压缩图像到隐空间(如 256×256→32x32x4)
2. 在低维空间执行扩散过程
3. 最终通过 VAE 解码器生成高清图像
关键优势:
– 计算量减少约 16 倍
– 可结合 CLIP 等跨模态模型
– 更易控制生成语义特征
完整实现代码见:https://github.com/example/diffusion-tutorial(符合 PEP8 规范,含详细维度注释)
