【扩散模型】从零开始理解生成式AI的新范式,万字解析,细节拉满!!(保姆级教学)
扩散模型完全指南:从零理解生成式AI新范式 本文系统讲解当前最先进的图像生成技术——扩散模型。从基础概念到数学原理,再到代码实现,帮助读者全面掌握这一AI生成领域的关键技术。 核心要点: 扩散模型通过"加噪-去噪"的渐进过程生成高质量图像,相比GAN更稳定且效果更好 前向扩散过程逐步添加噪声,反向过程则学习去噪以重建图像 数学上基于马尔可夫链和变分推断,通过噪声调度控制扩散节奏
·
扩散模型完全指南:从零开始理解生成式AI的新范式
📚 专为初学者打造的扩散模型深度教程
🎯 目标:用最通俗的语言,讲清楚最前沿的图像生成技术
🎨 扩散模型是什么?它是当今AI图像生成领域最强大的技术,支撑着Stable Diffusion、DALL-E 2、Midjourney等顶级AI绘画工具
📋 目录
- 1. 为什么要学习扩散模型?
- 2. 扩散模型的核心思想
- 3. 前向扩散过程详解
- 4. 反向去噪过程详解
- 5. 数学原理深入分析
- 6. 训练过程完全解析
- 7. 采样生成过程
- 8. 扩散模型的变体
- 9. 条件生成与引导
- 10. 实战代码实现
- 11. 实际应用场景
- 12. 常见问题解答
1. 为什么要学习扩散模型?
1.1 扩散模型改变了AI创作
如果你关注过最近的AI艺术创作,你会发现几乎所有惊艳的AI绘画工具都基于扩散模型:
| 应用 | 基于的技术 | 发布时间 | 特点 |
|---|---|---|---|
| Stable Diffusion | Latent Diffusion Model | 2022年8月 | 开源、可本地运行 |
| DALL-E 2 | CLIP + Diffusion | 2022年4月 | 文本到图像生成 |
| Midjourney | Diffusion Model | 2022年 | 艺术风格强 |
| Imagen | Diffusion Model | 2022年5月 | Google出品 |
1.2 为什么扩散模型这么强?
在扩散模型出现之前(2020年前),图像生成主要用GAN(生成对抗网络):
❌ GAN的问题:
- 训练不稳定(模式崩溃)
- 生成多样性差
- 难以控制生成内容
- 质量不够高
✅ 扩散模型的优势:
- 训练稳定(简单的去噪任务)
- 生成质量极高(超越GAN)
- 多样性好(随机采样)
- 可控性强(条件生成)
- 可扩展性强(模型越大效果越好)
1.3 学完这个教程你能做什么?
- 🔍 理解原理:彻底搞懂扩散模型的前向和反向过程
- 📐 掌握数学:理解DDPM、DDIM等核心算法
- 💻 动手实践:用PyTorch从零实现扩散模型
- 🎨 应用开发:为使用Stable Diffusion等工具打下基础
- 🚀 研究创新:理解最新的扩散模型变体
2. 扩散模型的核心思想
2.1 一个生活中的类比
想象你在海滩上画了一幅沙画:
【第1天】清晰的画
🎨 细节丰富
【第2天】被海浪冲刷
🌊 变模糊了
【第3天】再次冲刷
🌊 更模糊了
【第30天】完全消失
🌫️ 只剩下均匀的沙子
扩散模型做的事情:
- 前向过程(加噪声):像海浪冲刷沙画,逐渐添加噪声,直到图像变成纯噪声
- 反向过程(去噪声):学习"时光倒流",从纯噪声逐步恢复出清晰图像
2.2 核心思想的可视化
前向扩散(加噪):图像 → 噪声
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
t=0 t=250 t=500 t=750 t=1000
清晰图像 → 轻微噪声 → 中等噪声 → 严重噪声 → 纯噪声
🖼️ 🖼️ ▒ 🖼️ ▒▒ ▒▒▒▒ ░░░░
完全清晰 还能看出 很模糊 几乎看不出 完全随机
反向去噪(生成):噪声 → 图像
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
t=1000 t=750 t=500 t=250 t=0
纯噪声 → 去噪一点 → 轮廓出现 → 细节清晰 → 完美图像
░░░░ ▒▒▒▒ 🖼️ ▒▒ 🖼️ ▒ 🖼️
随机噪声 略有形状 看出大概 基本清晰 完全生成
2.3 为什么这样设计有效?
关键洞察:
1. 加噪声很简单
- 只需要在图像上加高斯噪声
- 数学上有明确的公式
2. 去噪声可以学习
- 训练神经网络预测噪声
- 一步步去噪,最终生成图像
3. 渐进式生成更稳定
- 不是一步生成(GAN的做法)
- 而是逐步细化(更容易学习)
类比:
GAN = 要求画家一笔画出完美作品(很难!)
扩散模型 = 先画草稿,再逐步细化(更容易!)
2.4 扩散模型 vs GAN vs VAE
┌─────────────────────────────────────────────────────────┐
│ 三大生成模型对比 │
├─────────────────────────────────────────────────────────┤
│ │
│ GAN(生成对抗网络): │
│ 生成器 vs 判别器(对抗训练) │
│ 优点:生成快速 │
│ 缺点:训练不稳定、模式崩溃 │
│ │
│ VAE(变分自编码器): │
│ 编码器 + 解码器(重建+正则化) │
│ 优点:训练稳定、有潜在空间 │
│ 缺点:生成质量一般(模糊) │
│ │
│ 扩散模型(Diffusion Model): │
│ 前向加噪 + 反向去噪(逐步生成) │
│ 优点:生成质量最高、训练稳定 │
│ 缺点:生成慢(需要多步) │
│ │
└─────────────────────────────────────────────────────────┘
3. 前向扩散过程详解
3.1 什么是前向扩散?
前向扩散过程就是逐步给图像加噪声的过程。
数学定义:
给定原始图像 x_0
第 t 步的图像:x_t = √(α_t) · x_0 + √(1-α_t) · ε
其中:
- α_t:噪声调度参数(随t递减)
- ε ~ N(0, I):标准高斯噪声
- t ∈ [0, T]:时间步(通常T=1000)
3.2 噪声调度(Noise Schedule)
什么是噪声调度?
噪声调度决定了每一步加多少噪声。
常用调度:线性调度(Linear Schedule)
β_t = β_min + (β_max - β_min) · t/T
参数设置:
β_min = 0.0001(开始加很少的噪声)
β_max = 0.02 (结束加很多噪声)
关系:
α_t = 1 - β_t
ᾱ_t = ∏(1 to t) α_i (累积乘积)
可视化:
α_t 随时间递减
1.0 │╲
│ ╲
0.5 │ ╲___
│ ╲___
0.0 └──────────╲_____→ t
0 250 500 750 1000
前期变化慢(保留更多信息)
后期变化快(快速变成噪声)
3.3 前向过程的性质
性质1:马尔可夫性质
q(x_t | x_{t-1}, x_{t-2}, ..., x_0) = q(x_t | x_{t-1})
意思:
当前时刻只依赖前一时刻,不依赖更早的历史
类比:
就像多米诺骨牌,每块牌只推倒下一块
性质2:一步直达公式
重要性质:可以直接从 x_0 计算任意 x_t
x_t = √(ᾱ_t) · x_0 + √(1-ᾱ_t) · ε
不需要逐步计算!
意义:
训练时可以随机采样任意时间步
不需要顺序计算所有步骤
3.4 前向过程的代码实现
import torch
import numpy as np
class ForwardDiffusion:
def __init__(self, T=1000):
"""
初始化前向扩散过程
Args:
T: 总时间步数
"""
self.T = T
# 定义 β 调度(线性)
self.betas = torch.linspace(0.0001, 0.02, T)
# 计算 α 和 ᾱ
self.alphas = 1 - self.betas
self.alphas_cumprod = torch.cumprod(self.alphas, dim=0)
# 计算相关系数(用于后续计算)
self.sqrt_alphas_cumprod = torch.sqrt(self.alphas_cumprod)
self.sqrt_one_minus_alphas_cumprod = torch.sqrt(1 - self.alphas_cumprod)
def forward(self, x_0, t):
"""
前向扩散:从 x_0 生成 x_t
Args:
x_0: 原始图像 [batch, C, H, W]
t: 时间步 [batch]
Returns:
x_t: 加噪后的图像
noise: 添加的噪声(用于训练)
"""
# 生成随机噪声
noise = torch.randn_like(x_0)
# 获取对应时间步的系数
sqrt_alpha = self.sqrt_alphas_cumprod[t].reshape(-1, 1, 1, 1)
sqrt_one_minus_alpha = self.sqrt_one_minus_alphas_cumprod[t].reshape(-1, 1, 1, 1)
# 应用公式: x_t = √ᾱ_t · x_0 + √(1-ᾱ_t) · ε
x_t = sqrt_alpha * x_0 + sqrt_one_minus_alpha * noise
return x_t, noise
# 使用示例
if __name__ == "__main__":
# 创建前向扩散
diffusion = ForwardDiffusion(T=1000)
# 假设有一张图像
x_0 = torch.randn(4, 3, 64, 64) # [batch=4, channels=3, H=64, W=64]
# 在不同时间步加噪
for t in [0, 250, 500, 750, 999]:
t_tensor = torch.tensor([t] * 4) # 批量处理
x_t, noise = diffusion.forward(x_0, t_tensor)
print(f"t={t:4d}, 信号强度: {x_t.mean():.4f}, "
f"噪声强度: {noise.std():.4f}")
3.5 前向过程的可视化
【示例:一张猫的图片的扩散过程】
t=0 清晰的猫
🐱 完全清晰
t=100 轻微噪声
🐱▒ 基本清晰,稍有颗粒感
t=250 可见噪声
🐱▒▒ 能看出是猫,但有明显噪声
t=500 严重噪声
▒▒▒▒ 勉强能看出轮廓
t=750 几乎消失
▒▒▒░ 很难辨认
t=1000 纯噪声
░░░░ 完全看不出原图
数学角度:
信号(原图)的贡献:√ᾱ_t → 0
噪声的贡献:√(1-ᾱ_t) → 1
4. 反向去噪过程详解
4.1 反向过程的目标
反向过程的目标:学习如何从噪声逐步恢复图像。
目标:学习反向转移概率
p_θ(x_{t-1} | x_t)
意思:
给定 t 时刻的噪声图像 x_t
预测 t-1 时刻的图像 x_{t-1}
类比:
就像学习"时光倒流"
每一步都让图像更清晰一点
4.2 去噪的数学建模
核心假设:反向过程也是高斯分布
p_θ(x_{t-1} | x_t) = N(x_{t-1}; μ_θ(x_t, t), Σ_θ(x_t, t))
简化版本(DDPM):方差固定,只预测均值
μ_θ(x_t, t) = (1/√α_t) · (x_t - (β_t/√(1-ᾱ_t)) · ε_θ(x_t, t))
其中:
- ε_θ(x_t, t):神经网络预测的噪声
- μ_θ:预测的均值(去噪后的图像)
4.3 神经网络的作用
神经网络要做什么?
输入:
x_t:当前噪声图像
t: 当前时间步
输出:
ε_θ:预测的噪声
为什么预测噪声而不是预测图像?
答:数学上等价,但预测噪声更稳定!
推导:
已知:x_t = √ᾱ_t · x_0 + √(1-ᾱ_t) · ε
则: x_0 = (x_t - √(1-ᾱ_t) · ε) / √ᾱ_t
所以:
预测噪声 ε → 可以恢复原图 x_0
4.4 去噪步骤详解
单步去噪算法:
输入:x_t, t
输出:x_{t-1}
Step 1: 用神经网络预测噪声
ε_pred = ε_θ(x_t, t)
Step 2: 估计原始图像
x_0_pred = (x_t - √(1-ᾱ_t) · ε_pred) / √ᾱ_t
Step 3: 计算前一时刻的均值
μ = (√α_t · (1-ᾱ_{t-1}) · x_t + √ᾱ_{t-1} · β_t · x_0_pred) / (1-ᾱ_t)
Step 4: 添加少量噪声(除了最后一步)
if t > 1:
z = N(0, I)
x_{t-1} = μ + √σ_t · z
else:
x_{t-1} = μ
返回:x_{t-1}
4.5 完整去噪代码
import torch
import torch.nn as nn
class ReverseDiffusion:
def __init__(self, model, forward_diffusion):
"""
反向去噪过程
Args:
model: 预测噪声的神经网络
forward_diffusion: 前向扩散对象
"""
self.model = model
self.forward_diffusion = forward_diffusion
self.T = forward_diffusion.T
# 复制必要的参数
self.betas = forward_diffusion.betas
self.alphas = forward_diffusion.alphas
self.alphas_cumprod = forward_diffusion.alphas_cumprod
@torch.no_grad()
def denoise_step(self, x_t, t):
"""
单步去噪
Args:
x_t: 当前噪声图像 [batch, C, H, W]
t: 当前时间步(标量)
Returns:
x_{t-1}: 去噪后的图像
"""
batch_size = x_t.shape[0]
t_batch = torch.full((batch_size,), t, dtype=torch.long)
# Step 1: 预测噪声
noise_pred = self.model(x_t, t_batch)
# 获取参数
alpha_t = self.alphas[t]
alpha_cumprod_t = self.alphas_cumprod[t]
beta_t = self.betas[t]
if t > 0:
alpha_cumprod_t_prev = self.alphas_cumprod[t-1]
else:
alpha_cumprod_t_prev = torch.tensor(1.0)
# Step 2: 计算 x_0 的估计
sqrt_recip_alpha_cumprod = 1 / torch.sqrt(alpha_cumprod_t)
sqrt_recip_minus_one = torch.sqrt(1 / alpha_cumprod_t - 1)
x_0_pred = sqrt_recip_alpha_cumprod * x_t - sqrt_recip_minus_one * noise_pred
# Step 3: 计算 x_{t-1} 的均值
coef1 = beta_t * torch.sqrt(alpha_cumprod_t_prev) / (1 - alpha_cumprod_t)
coef2 = (1 - alpha_cumprod_t_prev) * torch.sqrt(alpha_t) / (1 - alpha_cumprod_t)
mu = coef1 * x_0_pred + coef2 * x_t
# Step 4: 添加噪声(除了最后一步)
if t > 0:
# 计算方差
sigma_t = torch.sqrt(beta_t * (1 - alpha_cumprod_t_prev) / (1 - alpha_cumprod_t))
# 添加噪声
noise = torch.randn_like(x_t)
x_t_minus_1 = mu + sigma_t * noise
else:
x_t_minus_1 = mu
return x_t_minus_1
@torch.no_grad()
def sample(self, shape, device='cpu'):
"""
完整采样过程:从纯噪声生成图像
Args:
shape: 生成图像的形状 [batch, C, H, W]
device: 设备
Returns:
生成的图像
"""
# 从纯噪声开始
x_t = torch.randn(shape, device=device)
# 逐步去噪
for t in reversed(range(self.T)):
x_t = self.denoise_step(x_t, t)
if t % 100 == 0:
print(f"去噪进度: t={t}/{self.T}")
return x_t
# 使用示例
if __name__ == "__main__":
# 假设我们有一个训练好的模型
from model import UNet # 假设的UNet模型
model = UNet(channels=3, time_emb_dim=256)
model.eval()
forward_diffusion = ForwardDiffusion(T=1000)
reverse_diffusion = ReverseDiffusion(model, forward_diffusion)
# 生成图像
generated_images = reverse_diffusion.sample(shape=(4, 3, 64, 64))
print(f"生成图像形状: {generated_images.shape}")
print(f"像素值范围: [{generated_images.min():.2f}, {generated_images.max():.2f}]")
5. 数学原理深入分析
5.1 DDPM的损失函数
训练目标:让神经网络学会预测噪声
简化损失函数:
L_simple = E_t,x_0,ε [ ||ε - ε_θ(x_t, t)||² ]
展开说明:
- E:期望(平均)
- t:随机采样的时间步
- x_0:原始图像
- ε:真实添加的噪声
- ε_θ(x_t, t):网络预测的噪声
意思:
让预测的噪声尽可能接近真实噪声
这是一个简单的均方误差(MSE)!
5.2 为什么这个损失函数有效?
从变分下界(ELBO)推导:
完整目标:最大化 log p_θ(x_0)
变分下界:
log p_θ(x_0) ≥ E_q [ log p_θ(x_0|x_1) - Σ D_KL(q(x_t|x_{t-1}) || p_θ(x_{t-1}|x_t)) ]
其中 D_KL 是KL散度(衡量两个分布的差异)
关键发现(DDPM论文):
最小化 KL 散度 ≈ 最小化噪声预测误差
因此简化为:
L = ||ε - ε_θ(x_t, t)||²
这就是为什么我们用简单的MSE损失!
5.3 不同时间步的加权
完整损失函数:
L = E_t [ w_t · ||ε - ε_θ(x_t, t)||² ]
其中 w_t 是权重
DDPM 的选择:
w_t = 1(所有时间步权重相同)
Improved DDPM 的选择:
w_t = 1 / (1 - ᾱ_t)
(后期时间步权重更大)
权重的意义:
- 平衡不同时间步的学习
- 避免某些时间步主导训练
5.4 扩散过程的数学性质
性质1:前向过程是确定的
给定 x_0 和噪声调度
x_t 完全确定(添加高斯噪声)
数学表达:
q(x_t | x_0) = N(x_t; √ᾱ_t · x_0, (1-ᾱ_t)I)
意义:
训练时可以直接计算任意 x_t
不需要逐步forward
性质2:反向过程的后验分布
理论上的最优反向步骤:
q(x_{t-1} | x_t, x_0) = N(x_{t-1}; μ̃_t, σ̃_t²I)
其中:
μ̃_t = (√ᾱ_{t-1} · β_t · x_0 + √α_t · (1-ᾱ_{t-1}) · x_t) / (1-ᾱ_t)
问题:
需要知道 x_0(未知!)
解决:
用神经网络预测,估计 x_0
性质3:扩散极限(t→∞)
当 t 足够大:
x_t → N(0, I)(标准高斯分布)
意思:
最终图像完全变成噪声
与原图无关
这就是为什么生成时从纯噪声开始!
6. 训练过程完全解析
6.1 训练算法伪代码
DDPM 训练算法:
输入:
数据集 D = {x_0^(1), x_0^(2), ..., x_0^(N)}
噪声调度 β_1, ..., β_T
神经网络 ε_θ
训练循环:
repeat until convergence:
1. 从数据集采样:x_0 ~ D
2. 随机采样时间步:t ~ Uniform(1, T)
3. 采样噪声:ε ~ N(0, I)
4. 计算 x_t:
x_t = √ᾱ_t · x_0 + √(1-ᾱ_t) · ε
5. 预测噪声:
ε_pred = ε_θ(x_t, t)
6. 计算损失:
L = ||ε - ε_pred||²
7. 反向传播更新参数:
θ ← θ - lr · ∇_θ L
6.2 完整训练代码
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader
from tqdm import tqdm
class DDPMTrainer:
def __init__(self, model, forward_diffusion, device='cpu'):
"""
DDPM 训练器
Args:
model: 噪声预测网络
forward_diffusion: 前向扩散对象
device: 训练设备
"""
self.model = model.to(device)
self.forward_diffusion = forward_diffusion
self.device = device
self.T = forward_diffusion.T
def train_step(self, x_0):
"""
单步训练
Args:
x_0: 原始图像 [batch, C, H, W]
Returns:
loss: 损失值
"""
batch_size = x_0.shape[0]
x_0 = x_0.to(self.device)
# 1. 随机采样时间步
t = torch.randint(0, self.T, (batch_size,), device=self.device)
# 2. 前向扩散(加噪)
x_t, noise = self.forward_diffusion.forward(x_0, t)
# 3. 预测噪声
noise_pred = self.model(x_t, t)
# 4. 计算损失(MSE)
loss = F.mse_loss(noise_pred, noise)
return loss
def train(self, dataloader, epochs, optimizer, save_path='model.pt'):
"""
完整训练循环
Args:
dataloader: 数据加载器
epochs: 训练轮数
optimizer: 优化器
save_path: 模型保存路径
"""
self.model.train()
for epoch in range(epochs):
epoch_loss = 0.0
progress_bar = tqdm(dataloader, desc=f'Epoch {epoch+1}/{epochs}')
for batch_idx, (images, _) in enumerate(progress_bar):
# 训练步骤
optimizer.zero_grad()
loss = self.train_step(images)
loss.backward()
optimizer.step()
# 记录损失
epoch_loss += loss.item()
progress_bar.set_postfix({'loss': loss.item()})
# 打印每个epoch的平均损失
avg_loss = epoch_loss / len(dataloader)
print(f'Epoch {epoch+1}, Average Loss: {avg_loss:.4f}')
# 定期保存模型
if (epoch + 1) % 10 == 0:
torch.save(self.model.state_dict(), save_path)
print(f'Model saved to {save_path}')
# 使用示例
if __name__ == "__main__":
from torchvision import datasets, transforms
from model import UNet
# 1. 准备数据
transform = transforms.Compose([
transforms.Resize(64),
transforms.CenterCrop(64),
transforms.ToTensor(),
transforms.Normalize([0.5], [0.5]) # 归一化到[-1, 1]
])
dataset = datasets.CIFAR10(
root='./data',
train=True,
transform=transform,
download=True
)
dataloader = DataLoader(
dataset,
batch_size=128,
shuffle=True,
num_workers=4
)
# 2. 创建模型
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = UNet(channels=3, time_emb_dim=256)
forward_diffusion = ForwardDiffusion(T=1000)
# 3. 创建训练器
trainer = DDPMTrainer(model, forward_diffusion, device=device)
# 4. 训练
optimizer = torch.optim.Adam(model.parameters(), lr=2e-4)
trainer.train(dataloader, epochs=100, optimizer=optimizer)
6.3 训练技巧
技巧1:学习率调度
使用 Warmup + Cosine Decay:
- 前10%步数:线性增加学习率(warmup)
- 后90%步数:余弦衰减
from torch.optim.lr_scheduler import CosineAnnealingLR
scheduler = CosineAnnealingLR(optimizer, T_max=epochs)
技巧2:EMA(指数移动平均)
维护模型参数的移动平均:
θ_ema = momentum · θ_ema + (1-momentum) · θ
生成时使用EMA参数(效果更好)
class EMA:
def __init__(self, model, decay=0.9999):
self.model = model
self.decay = decay
self.shadow = {}
for name, param in model.named_parameters():
self.shadow[name] = param.data.clone()
def update(self):
for name, param in self.model.named_parameters():
self.shadow[name] = (
self.decay * self.shadow[name] +
(1 - self.decay) * param.data
)
技巧3:梯度裁剪
防止梯度爆炸:
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
技巧4:混合精度训练
使用 FP16 加速训练:
from torch.cuda.amp import autocast, GradScaler
scaler = GradScaler()
with autocast():
loss = train_step(images)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
6.4 训练监控
监控指标:
1. 训练损失(Loss)
- 应该持续下降
- 收敛到较小值(<0.1)
2. 采样质量
- 每N个epoch生成样本
- 目视检查质量
3. FID分数(Fréchet Inception Distance)
- 衡量生成图像与真实图像的相似度
- 越低越好
4. IS分数(Inception Score)
- 衡量生成图像的质量和多样性
- 越高越好
代码示例:
import matplotlib.pyplot as plt
def visualize_training(trainer, reverse_diffusion, epoch):
# 生成样本
samples = reverse_diffusion.sample((16, 3, 64, 64))
# 可视化
fig, axes = plt.subplots(4, 4, figsize=(12, 12))
for i, ax in enumerate(axes.flat):
img = samples[i].permute(1, 2, 0).cpu().numpy()
img = (img + 1) / 2 # 反归一化
ax.imshow(img)
ax.axis('off')
plt.savefig(f'samples_epoch_{epoch}.png')
plt.close()
7. 采样生成过程
7.1 DDPM采样算法
DDPM 采样算法(完整版):
输入:
噪声调度 β_1, ..., β_T
训练好的模型 ε_θ
采样过程:
1. 初始化:x_T ~ N(0, I)
2. for t = T, T-1, ..., 1:
a. 预测噪声:
ε = ε_θ(x_t, t)
b. 估计 x_0:
x̂_0 = (x_t - √(1-ᾱ_t) · ε) / √ᾱ_t
c. 计算均值:
μ_t = (√ᾱ_{t-1} · β_t · x̂_0 + √α_t · (1-ᾱ_{t-1}) · x_t) / (1-ᾱ_t)
d. 计算方差:
σ_t = √(β_t · (1-ᾱ_{t-1}) / (1-ᾱ_t))
e. 采样:
if t > 1:
z ~ N(0, I)
x_{t-1} = μ_t + σ_t · z
else:
x_{t-1} = μ_t
3. 返回:x_0
7.2 DDIM采样(加速版)
DDIM(Denoising Diffusion Implicit Models):
关键思想:
DDPM需要1000步采样(慢!)
DDIM可以跳过很多步(快!)
DDIM采样公式:
x_{t-Δt} = √ᾱ_{t-Δt} · x̂_0 + √(1-ᾱ_{t-Δt}) · ε
其中:
- Δt:步长(可以>1)
- x̂_0:估计的原始图像
优势:
- 可以用50步甚至20步生成
- 质量几乎不损失
- 速度提升20-50倍!
确定性vs随机性:
- DDPM:随机采样(每次生成不同)
- DDIM:确定性(相同噪声→相同图像)
7.3 DDIM代码实现
class DDIMSampler:
def __init__(self, model, forward_diffusion, ddim_steps=50):
"""
DDIM 采样器
Args:
model: 训练好的噪声预测网络
forward_diffusion: 前向扩散对象
ddim_steps: DDIM采样步数(远小于训练步数)
"""
self.model = model
self.forward_diffusion = forward_diffusion
self.ddim_steps = ddim_steps
self.T = forward_diffusion.T
# 选择采样的时间步(均匀间隔)
self.timesteps = torch.linspace(0, self.T-1, ddim_steps, dtype=torch.long)
@torch.no_grad()
def sample(self, shape, device='cpu', eta=0.0):
"""
DDIM 采样
Args:
shape: 生成图像形状 [batch, C, H, W]
device: 设备
eta: 随机性参数(0=完全确定性,1=DDPM随机性)
Returns:
生成的图像
"""
# 从纯噪声开始
x_t = torch.randn(shape, device=device)
# 逆序遍历时间步
timesteps = reversed(self.timesteps)
for i, t in enumerate(timesteps):
# 当前和前一个时间步
t_current = t
t_prev = self.timesteps[-(i+2)] if i < len(self.timesteps)-1 else torch.tensor(0)
# 预测噪声
t_batch = t_current.repeat(shape[0]).to(device)
noise_pred = self.model(x_t, t_batch)
# 获取alpha值
alpha_t = self.forward_diffusion.alphas_cumprod[t_current]
alpha_t_prev = self.forward_diffusion.alphas_cumprod[t_prev] if t_prev >= 0 else torch.tensor(1.0)
# 估计x_0
sqrt_one_minus_alpha_t = torch.sqrt(1 - alpha_t)
x_0_pred = (x_t - sqrt_one_minus_alpha_t * noise_pred) / torch.sqrt(alpha_t)
# 计算方向指向x_t
dir_xt = torch.sqrt(1 - alpha_t_prev - eta**2 * (1-alpha_t_prev)/(1-alpha_t) * (1-alpha_t/alpha_t_prev)) * noise_pred
# DDIM更新
x_t = torch.sqrt(alpha_t_prev) * x_0_pred + dir_xt
# 可选:添加随机性(eta > 0)
if eta > 0 and t_prev > 0:
sigma_t = eta * torch.sqrt((1-alpha_t_prev)/(1-alpha_t)) * torch.sqrt(1-alpha_t/alpha_t_prev)
noise = torch.randn_like(x_t)
x_t += sigma_t * noise
return x_t
# 使用示例
if __name__ == "__main__":
model = UNet(channels=3, time_emb_dim=256)
model.load_state_dict(torch.load('model.pt'))
model.eval()
forward_diffusion = ForwardDiffusion(T=1000)
# DDIM采样(50步)
ddim_sampler = DDIMSampler(model, forward_diffusion, ddim_steps=50)
samples = ddim_sampler.sample((4, 3, 64, 64), device='cuda')
print(f"生成完成!形状: {samples.shape}")
# 对比:DDPM需要1000步,DDIM只需要50步
# 速度提升 20倍!
7.4 采样加速技术对比
┌──────────────────────────────────────────────────────────┐
│ 采样方法对比 │
├──────────────────────────────────────────────────────────┤
│ │
│ 方法 │ 步数 │ 生成时间 │ 质量 │ 确定性 │
│ ──────────┼──────┼─────────┼────────┼──────────────│
│ DDPM │ 1000 │ ~100秒 │ 最高 │ 随机 │
│ DDIM-50 │ 50 │ ~5秒 │ 很高 │ 可控 │
│ DDIM-20 │ 20 │ ~2秒 │ 高 │ 可控 │
│ DPM-Solver│ 20 │ ~2秒 │ 很高 │ 可控 │
│ PNDM │ 50 │ ~5秒 │ 很高 │ 可控 │
│ │
└──────────────────────────────────────────────────────────┘
推荐:
- 质量优先:DDPM(1000步)
- 平衡:DDIM-50
- 速度优先:DDIM-20 或 DPM-Solver
8. 扩散模型的变体
8.1 Latent Diffusion Model(LDM)
核心思想:
不在像素空间做扩散,而在潜在空间(Latent Space)
架构:
原图 → 编码器 → 潜在表示 → 扩散过程 → 解码器 → 生成图
优势:
✅ 降低计算成本(潜在空间维度更小)
✅ 加快训练和采样速度
✅ 保持高质量
Stable Diffusion 就是LDM!
可视化:
【像素空间扩散】
512×512×3 = 786,432 维
↓ 计算量巨大
【潜在空间扩散】
512×512×3 → 编码器 → 64×64×4 = 16,384 维
↓ 计算量减少 48倍!
8.2 Conditional Diffusion(条件扩散)
问题:
DDPM只能随机生成
如何控制生成内容?
解决:Conditional Diffusion
方法1:Classifier Guidance(分类器引导)
训练一个分类器 p(y|x_t)
采样时沿着梯度方向移动
x_{t-1} = μ + σ · (ε - s·∇log p(y|x_t))
s:引导强度(越大越符合条件)
方法2:Classifier-Free Guidance(无分类器引导)
同时训练条件模型和无条件模型
ε_guided = ε_uncond + w · (ε_cond - ε_uncond)
w:引导权重
优势:
- 不需要额外训练分类器
- 效果更好
- Stable Diffusion 使用此方法
条件类型:
- 类别标签("猫"、"狗")
- 文本描述("一只在草地上的猫")
- 图像(图像到图像)
- 分割图(语义控制)
8.3 Score-Based Models
核心思想:
学习数据分布的分数(梯度)
分数函数:
s_θ(x_t, t) = ∇_x log p_t(x_t)
关系:
分数函数 ≈ 噪声预测(差一个负号和缩放)
s_θ(x_t, t) ≈ -ε_θ(x_t, t) / √(1-ᾱ_t)
统一框架:
Score-based Models 和 Diffusion Models 本质相同
只是不同的数学视角
9. 条件生成与引导
9.1 文本到图像生成(Text-to-Image)
目标:根据文本描述生成图像
输入:"一只戴墨镜的猫在海滩上"
输出:对应的图像 🐱🕶️🏖️
关键技术:CLIP
CLIP(Contrastive Language-Image Pre-training):
- OpenAI 训练的模型
- 将文本和图像映射到同一空间
- 衡量文本-图像匹配度
架构:
文本 → CLIP文本编码器 → 文本embedding
↓
交叉注意力机制
↓
噪声图 → UNet → 预测噪声
9.2 无分类器引导代码
class ClassifierFreeGuidance:
def __init__(self, model, text_encoder, p_uncond=0.1):
"""
无分类器引导
Args:
model: 条件扩散模型
text_encoder: 文本编码器(如CLIP)
p_uncond: 训练时无条件的概率
"""
self.model = model
self.text_encoder = text_encoder
self.p_uncond = p_uncond
def train_step(self, x_0, text):
"""
训练步骤
Args:
x_0: 原始图像
text: 文本描述
Returns:
loss: 损失
"""
# 随机决定是否使用条件
use_condition = torch.rand(1).item() > self.p_uncond
# 编码文本
if use_condition:
text_emb = self.text_encoder(text)
else:
text_emb = None # 无条件
# 前向扩散
t = torch.randint(0, self.T, (x_0.shape[0],))
x_t, noise = self.forward_diffusion(x_0, t)
# 预测噪声
noise_pred = self.model(x_t, t, text_emb)
# 计算损失
loss = F.mse_loss(noise_pred, noise)
return loss
@torch.no_grad()
def sample_with_guidance(self, text, guidance_scale=7.5):
"""
使用引导采样
Args:
text: 文本描述
guidance_scale: 引导强度(典型值:7.5)
Returns:
生成的图像
"""
# 编码文本
text_emb = self.text_encoder(text)
# 初始化噪声
x_t = torch.randn(1, 3, 64, 64)
for t in reversed(range(self.T)):
t_tensor = torch.tensor([t])
# 条件预测
noise_cond = self.model(x_t, t_tensor, text_emb)
# 无条件预测
noise_uncond = self.model(x_t, t_tensor, None)
# 引导
noise_pred = noise_uncond + guidance_scale * (noise_cond - noise_uncond)
# 去噪一步
x_t = self.denoise_step(x_t, t, noise_pred)
return x_t
# 使用示例
if __name__ == "__main__":
model = ConditionalUNet()
text_encoder = CLIPTextEncoder()
cfg = ClassifierFreeGuidance(model, text_encoder)
# 生成图像
prompt = "一只戴墨镜的猫在海滩上"
image = cfg.sample_with_guidance(prompt, guidance_scale=7.5)
# 保存
save_image(image, 'generated.png')
9.3 ControlNet(精确控制)
ControlNet:
在Stable Diffusion基础上添加精确控制
控制类型:
1. 边缘图(Canny Edge)
2. 深度图(Depth Map)
3. 姿态图(Pose Skeleton)
4. 分割图(Segmentation Map)
5. 涂鸦(Scribble)
原理:
添加额外的编码器
复制UNet的参数
通过零卷积连接
优势:
- 精确控制生成内容
- 不影响原模型质量
- 可以组合多个控制
示例:
输入:姿态图 + 文本"一个穿红裙子的女孩"
输出:完全符合姿态的女孩图像
10. 实战代码实现
10.1 UNet架构实现
import torch
import torch.nn as nn
import math
class SinusoidalPositionEmbedding(nn.Module):
"""时间步的正弦位置编码"""
def __init__(self, dim):
super().__init__()
self.dim = dim
def forward(self, time):
device = time.device
half_dim = self.dim // 2
embeddings = math.log(10000) / (half_dim - 1)
embeddings = torch.exp(torch.arange(half_dim, device=device) * -embeddings)
embeddings = time[:, None] * embeddings[None, :]
embeddings = torch.cat((embeddings.sin(), embeddings.cos()), dim=-1)
return embeddings
class ResidualBlock(nn.Module):
"""残差块"""
def __init__(self, in_channels, out_channels, time_emb_dim):
super().__init__()
self.conv1 = nn.Conv2d(in_channels, out_channels, 3, padding=1)
self.conv2 = nn.Conv2d(out_channels, out_channels, 3, padding=1)
self.time_mlp = nn.Linear(time_emb_dim, out_channels)
self.norm1 = nn.GroupNorm(8, out_channels)
self.norm2 = nn.GroupNorm(8, out_channels)
if in_channels != out_channels:
self.shortcut = nn.Conv2d(in_channels, out_channels, 1)
else:
self.shortcut = nn.Identity()
def forward(self, x, t):
h = self.conv1(x)
h = self.norm1(h)
h = h + self.time_mlp(t)[:, :, None, None]
h = torch.relu(h)
h = self.conv2(h)
h = self.norm2(h)
h = torch.relu(h)
return h + self.shortcut(x)
class AttentionBlock(nn.Module):
"""自注意力块"""
def __init__(self, channels):
super().__init__()
self.channels = channels
self.norm = nn.GroupNorm(8, channels)
self.qkv = nn.Conv2d(channels, channels * 3, 1)
self.proj = nn.Conv2d(channels, channels, 1)
def forward(self, x):
B, C, H, W = x.shape
h = self.norm(x)
qkv = self.qkv(h)
q, k, v = torch.chunk(qkv, 3, dim=1)
# 重塑为注意力格式
q = q.reshape(B, C, H*W).permute(0, 2, 1)
k = k.reshape(B, C, H*W)
v = v.reshape(B, C, H*W).permute(0, 2, 1)
# 计算注意力
attn = torch.bmm(q, k) / math.sqrt(C)
attn = torch.softmax(attn, dim=-1)
# 应用注意力
out = torch.bmm(attn, v)
out = out.permute(0, 2, 1).reshape(B, C, H, W)
out = self.proj(out)
return x + out
class UNet(nn.Module):
"""完整的UNet架构"""
def __init__(self, in_channels=3, out_channels=3, time_emb_dim=256):
super().__init__()
# 时间嵌入
self.time_mlp = nn.Sequential(
SinusoidalPositionEmbedding(time_emb_dim),
nn.Linear(time_emb_dim, time_emb_dim * 4),
nn.GELU(),
nn.Linear(time_emb_dim * 4, time_emb_dim),
)
# 下采样
self.down1 = ResidualBlock(in_channels, 64, time_emb_dim)
self.down2 = ResidualBlock(64, 128, time_emb_dim)
self.down3 = ResidualBlock(128, 256, time_emb_dim)
self.down4 = ResidualBlock(256, 512, time_emb_dim)
self.pool = nn.MaxPool2d(2)
# 瓶颈
self.bottleneck = nn.Sequential(
ResidualBlock(512, 512, time_emb_dim),
AttentionBlock(512),
ResidualBlock(512, 512, time_emb_dim),
)
# 上采样
self.up1 = ResidualBlock(512 + 512, 256, time_emb_dim)
self.up2 = ResidualBlock(256 + 256, 128, time_emb_dim)
self.up3 = ResidualBlock(128 + 128, 64, time_emb_dim)
self.up4 = ResidualBlock(64 + 64, 64, time_emb_dim)
self.upsample = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True)
# 输出
self.out = nn.Conv2d(64, out_channels, 1)
def forward(self, x, time):
# 时间嵌入
t = self.time_mlp(time)
# 下采样
d1 = self.down1(x, t)
d2 = self.down2(self.pool(d1), t)
d3 = self.down3(self.pool(d2), t)
d4 = self.down4(self.pool(d3), t)
# 瓶颈
b = self.bottleneck[0](self.pool(d4), t)
b = self.bottleneck[1](b)
b = self.bottleneck[2](b, t)
# 上采样
u1 = self.up1(torch.cat([self.upsample(b), d4], dim=1), t)
u2 = self.up2(torch.cat([self.upsample(u1), d3], dim=1), t)
u3 = self.up3(torch.cat([self.upsample(u2), d2], dim=1), t)
u4 = self.up4(torch.cat([self.upsample(u3), d1], dim=1), t)
return self.out(u4)
# 测试模型
if __name__ == "__main__":
model = UNet(in_channels=3, out_channels=3)
x = torch.randn(2, 3, 64, 64)
t = torch.randint(0, 1000, (2,))
out = model(x, t)
print(f"输入形状: {x.shape}")
print(f"输出形状: {out.shape}")
print(f"参数量: {sum(p.numel() for p in model.parameters()):,}")
11. 实际应用场景
11.1 图像生成
应用:AI艺术创作
工具:
- Stable Diffusion
- Midjourney
- DALL-E 2
流程:
文本提示 → 扩散模型 → 生成图像
示例:
输入:"赛博朋克风格的城市夜景"
输出:高质量的未来城市图像
优势:
✅ 创作效率提升100倍
✅ 风格多样化
✅ 概念艺术、插画、设计
11.2 图像修复
应用:自动修复照片
技术:Inpainting(图像修补)
原理:
1. 遮罩需要修复的区域
2. 扩散模型根据周围内容填充
应用场景:
- 去除照片中的人或物体
- 扩展图像边界
- 修复老照片
示例:
输入:有水印的照片
处理:遮罩水印区域
输出:自然去除水印的照片
11.3 图像编辑
应用:智能图像编辑
技术:
1. Text-guided Editing(文本引导编辑)
2. Image-to-Image Translation
功能:
- 改变物体属性
- 替换背景
- 风格迁移
示例:
原图:白天的街道照片
文本:将其变为夜晚
输出:同一场景的夜景图
工具:
- Instruct-Pix2Pix
- DiffEdit
- ControlNet
11.4 医学图像
应用:医学影像增强和生成
用途:
1. 数据增强(生成训练数据)
2. 图像超分辨率
3. 跨模态生成(CT → MRI)
优势:
- 保护隐私(生成合成数据)
- 降低标注成本
- 辅助诊断
示例:
输入:低分辨率CT扫描
输出:高分辨率增强图像
11.5 视频生成
应用:文本到视频生成
技术:
- Temporal扩散模型
- 3D卷积UNet
挑战:
- 时间一致性
- 计算成本高
- 运动平滑性
工具:
- Runway Gen-2
- Pika Labs
- Stable Video Diffusion
示例:
输入:"一只猫在追逐激光笔"
输出:流畅的3秒视频
12. 常见问题解答
Q1: 扩散模型生成为什么这么慢?
答:因为需要多步采样
DDPM:1000步
每步:前向传播UNet
加速方法:
1. DDIM:减少到50步
2. Latent Diffusion:在低维空间
3. 蒸馏:训练单步生成模型
4. 更快的采样器(DPM-Solver)
对比:
GAN:一步生成(快!但质量差)
扩散模型:多步生成(慢,但质量高)
Q2: 扩散模型能做实时生成吗?
答:目前还很难,但有进展
当前状态:
- Stable Diffusion:3-5秒/张(GPU)
- LCM(Latent Consistency Model):<1秒
- Turbo模型:0.2秒
未来方向:
1. 模型蒸馏(单步生成)
2. 硬件优化(TensorRT)
3. 新架构(DiT - Diffusion Transformer)
实时应用:
目前主要用于非实时场景
实时视频还需要突破
Q3: 如何控制生成的内容?
答:多种控制方法
1. 文本提示(Text Prompts)
详细描述 → 精确控制
2. Negative Prompts(负提示)
避免不想要的内容
3. Classifier-Free Guidance
调节引导强度
4. ControlNet
结构级精确控制
5. LoRA(Low-Rank Adaptation)
微调特定风格/角色
技巧:
- 提示词工程很重要
- 组合多种控制方法
- 调整采样参数
Q4: 扩散模型的计算成本如何?
答:取决于分辨率和步数
训练成本:
- 数据集:数百万张图像
- 硬件:8×A100 GPU
- 时间:数周到数月
- 成本:数万到数十万美元
推理成本:
- 单张图像:3-5秒(512×512)
- 显存:4-8GB
- 硬件:RTX 3060+
降低成本:
1. Latent Diffusion(降低48倍)
2. DDIM加速(降低20倍)
3. 量化(INT8)
4. 剪枝
Q5: 扩散模型vs GAN,哪个更好?
答:各有优劣
扩散模型优势:
✅ 训练稳定
✅ 生成质量高
✅ 多样性好
✅ 可扩展性强
扩散模型劣势:
❌ 生成慢
❌ 训练成本高
❌ 推理成本高
GAN优势:
✅ 生成快(一步)
✅ 推理成本低
✅ 实时应用
GAN劣势:
❌ 训练不稳定
❌ 模式崩溃
❌ 多样性差
选择:
- 质量优先:扩散模型
- 速度优先:GAN
- 大规模生成:扩散模型
- 实时应用:GAN
趋势:
扩散模型正在逐步取代GAN
但GAN在某些场景仍有优势
Q6: 如何提高生成质量?
答:多方面优化
1. 模型方面:
- 增大模型(更多参数)
- 更深的UNet
- 注意力机制
2. 训练方面:
- 更多数据
- 更长训练时间
- EMA(指数移动平均)
3. 采样方面:
- 更多采样步数
- 调整引导强度
- 使用DDIM等高级采样器
4. 提示词:
- 详细描述
- 使用高质量提示词
- Negative prompts
5. 后处理:
- 超分辨率
- 颜色校正
- 细节增强
📚 总结与展望
核心要点回顾
✅ 扩散模型的三大核心:
1. 前向扩散(逐步加噪声)
2. 反向去噪(逐步恢复)
3. 神经网络(学习去噪)
✅ 数学原理:
- 马尔可夫链
- 高斯分布
- 变分推断
✅ 关键技术:
- DDPM(基础算法)
- DDIM(加速采样)
- Latent Diffusion(降维)
- Conditional Generation(条件控制)
✅ 实际应用:
- 图像生成(Stable Diffusion)
- 图像编辑(ControlNet)
- 视频生成
- 3D生成
学习路径建议
第1阶段:理论基础(1-2周)
├─ 理解前向和反向过程
├─ 掌握DDPM数学原理
└─ 阅读原论文
第2阶段:代码实践(2-3周)
├─ 实现简单的DDPM
├─ 训练小规模模型
└─ 实现DDIM采样
第3阶段:应用开发(4-8周)
├─ 使用Stable Diffusion
├─ Fine-tune模型
├─ LoRA训练
└─ ControlNet应用
第4阶段:深入研究(持续)
├─ 阅读最新论文
├─ 实现新变体
└─ 参与开源项目
推荐资源
经典论文:
1. DDPM (2020) - "Denoising Diffusion Probabilistic Models"
2. DDIM (2021) - "Denoising Diffusion Implicit Models"
3. Latent Diffusion (2022) - "High-Resolution Image Synthesis"
4. Classifier-Free Guidance (2022)
代码库:
1. Hugging Face Diffusers - 官方库
2. Stable Diffusion - 开源实现
3. CompVis/stable-diffusion - 原始仓库
教程:
1. Lilian Weng的博客
2. Yang Song的博客
3. Hugging Face教程
工具:
1. Stable Diffusion WebUI
2. ComfyUI
3. InvokeAI
未来发展方向
技术前沿:
1. 更快的采样(<10步)
2. 更高分辨率(8K+)
3. 视频生成(长视频)
4. 3D生成
5. 多模态(文本+图像+音频)
研究热点:
1. 扩散Transformer(DiT)
2. 一致性模型(Consistency Models)
3. Flow Matching
4. 连续时间扩散
5. 离散扩散(文本生成)
应用前沿:
1. 实时生成
2. 个性化定制
3. 专业工具集成
4. 移动端部署
5. 边缘计算
🎉祝你天天开心,我将更新更多有意思的内容,欢迎关注!
最后更新:2025年11月
作者:Echo
更多推荐

所有评论(0)