30秒快速回答: 扩散模型的核心思想极其优雅——「先破坏,再重建」。训练时,给一张清晰图片逐步加噪声直到变成纯噪点;推理时,从纯噪点开始逐步去噪恢复到清晰图片。AI 在这个过程中学会了「把噪声变成图片」,这就是 Stable Diffusion 能根据文字生成画面的本质。
扩散模型的核心直觉
训练阶段(加噪声):
清晰图 → 轻度雾化 → 中度模糊 → 重度噪点 → 纯噪点
[注: 这是特意设计的破坏过程,教 AI 「坏掉的图长什么样」]
推理阶段(去噪声):
纯噪点 → 猜下一帧 → 猜下一帧 → ... → 清晰图
[AI 在做的事:每步预测一步噪声并减去,逐步恢复图像]
这个过程的数学本质是「学习从噪声分布到数据分布的转换」。
扩散模型的数学原理(极简版)
前向扩散(Forward Diffusion)
图像 x₀ 经过 T 步加噪,变成近似白噪声 x_T:
x_t = √(α_t) × x_{t-1} + √(1 - α_t) × ε
其中 ε 是高斯噪声,α_t 是随时间递减的系数
反向去噪(Reverse Denoising)
从噪声 x_T 开始,逐步去噪恢复图像:
核心任务:预测每一步的噪声 ε_t
loss = ||ε - ε_pred||² (预测噪声与真实噪声的平方差)
训练一个 U-Net 网络来做这件事:
输入:(加噪后的图像 x_t, 时间步 t)
输出:预测的噪声 ε_pred
为什么预测「噪声」而不是「图像」?
这是一个巧妙的设计选择——预测噪声比直接预测清晰图像容易得多:
预测清晰图:从完全模糊重建,信息极度不足
预测噪声:每一步只预测「这一步加的噪声是什么」
→ 任务简化,训练更稳定
扩散模型的架构(U-Net)
输入: x_t (加噪图) + t (时间步)
↓
┌──────────────────────────────────┐
│ U-Net │
│ │
│ 下采样(编码): │
│ 输入 → Conv → Down → ... → 瓶颈 │
│ │
│ 上采样(解码): │
│ 瓶颈 → Up → Conv → ... → 输出 │
│ │
│ 跳跃连接(skip-connection): │
│ 每层下采样 → 对应层上采样 │
└──────────────────────────────────┘
↓
输出: ε_pred (预测的噪声)
实战:从零实现一个简单扩散模型
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
# ==========================================
# 1. 扩散调度器:定义加噪过程
# ==========================================
class DiffusionScheduler:
def __init__(self, timesteps=1000, beta_start=1e-4, beta_end=0.02):
self.timesteps = timesteps
self.betas = torch.linspace(beta_start, beta_end, timesteps)
self.alphas = 1.0 - self.betas
self.alpha_bars = torch.cumprod(self.alphas, dim=0)
def add_noise(self, x_0, t):
"""给干净图像加噪"""
sqrt_alpha_bar = self.alpha_bars[t].sqrt().view(-1,1,1,1)
sqrt_one_minus_alpha_bar = (1 - self.alpha_bars[t]).sqrt().view(-1,1,1,1)
epsilon = torch.randn_like(x_0)
x_t = sqrt_alpha_bar * x_0 + sqrt_one_minus_alpha_bar * epsilon
return x_t, epsilon
# ==========================================
# 2. 简化的 U-Net 去噪网络
# ==========================================
class SimpleUNet(nn.Module):
def __init__(self, in_channels=1, time_emb_dim=32):
super().__init__()
# 时间嵌入(告诉网络当前是第几步)
self.time_mlp = nn.Sequential(
nn.Linear(1, time_emb_dim),
nn.ReLU(),
nn.Linear(time_emb_dim, time_emb_dim)
)
# 编码器
self.conv1 = nn.Conv2d(in_channels, 64, 3, padding=1)
self.conv2 = nn.Conv2d(64, 128, 3, padding=1)
# 时间调节
self.time_proj1 = nn.Linear(time_emb_dim, 64)
self.time_proj2 = nn.Linear(time_emb_dim, 128)
# 解码器
self.deconv1 = nn.ConvTranspose2d(128, 64, 3, padding=1)
self.deconv2 = nn.ConvTranspose2d(64, in_channels, 3, padding=1)
def forward(self, x, t):
# 时间嵌入
t_emb = self.time_mlp(t.float().unsqueeze(-1))
# 编码
h1 = F.relu(self.conv1(x) + self.time_proj1(t_emb).unsqueeze(-1).unsqueeze(-1))
h2 = F.relu(self.conv2(h1) + self.time_proj2(t_emb).unsqueeze(-1).unsqueeze(-1))
# 解码
h3 = F.relu(self.deconv1(h2))
out = self.deconv2(h3)
return out # 预测的噪声
# ==========================================
# 3. 训练扩散模型
# ==========================================
device = "cuda" if torch.cuda.is_available() else "cpu"
model = SimpleUNet().to(device)
scheduler = DiffusionScheduler(timesteps=1000)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
for epoch in range(100):
for x_0 in dataloader: # x_0: 真实图像
x_0 = x_0.to(device)
# 随机采样时间步
t = torch.randint(0, 1000, (x_0.shape[0],)).to(device)
# 加噪
x_t, true_noise = scheduler.add_noise(x_0, t)
# 预测噪声
pred_noise = model(x_t, t)
# 噪声预测损失
loss = F.mse_loss(pred_noise, true_noise)
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(f"Epoch {epoch+1}: loss = {loss.item():.4f}")
print("训练完成!")
# ==========================================
# 4. 采样生成新图像
# ==========================================
@torch.no_grad()
def sample(model, scheduler, img_size=(1, 28, 28)):
"""从纯噪声逐步去噪生成图像"""
model.eval()
x = torch.randn((1, *img_size)).to(device) # 纯噪声
for t in reversed(range(scheduler.timesteps)):
t_batch = torch.full((1,), t, device=device)
pred_noise = model(x, t_batch)
# DDIM 采样公式
alpha = scheduler.alphas[t]
alpha_bar = scheduler.alpha_bars[t]
beta = scheduler.betas[t]
if t > 0:
noise = torch.randn_like(x)
else:
noise = 0
x = (1 / alpha.sqrt()) * (x - (beta / (1 - alpha_bar).sqrt()) * pred_noise) + beta.sqrt() * noise
return x.clamp(0, 1)
generated_img = sample(model, scheduler)
主流扩散模型产品
| 模型 | 类型 | 参数 | 特点 |
|---|---|---|---|
| Stable Diffusion 3 | 图像生成 | 8B | 开源,社区生态最丰富 |
| DALL-E 3 | 图像生成 | 未公开 | 文字理解强,与 ChatGPT 集成 |
| Midjourney v6 | 图像生成 | 未公开 | 美学质量最高 |
| FLUX.1 | 图像生成 | 12B | 原 SD 团队作品,提示跟随极强 |
| Sora | 视频生成 | 未公开 | 基于 Diffusion Transformer (DiT) |
| Kling 2 | 视频生成 | 未公开 | 中文提示理解好 |
扩散模型 vs 其他生成模型
| 维度 | Diffusion | GAN | VAE |
|---|---|---|---|
| 生成质量 | 最高 | 中高 | 中 |
| 训练稳定性 | 稳定 | 不稳定 | 稳定 |
| 推理速度 | 慢(多步) | 快(一步) | 快 |
| 多样性 | 高 | 低(模式坍缩) | 中 |
| 可控性 | 高 | 低 | 中 |
常见问题
Q: 扩散模型为什么需要这么多步推理?
因为每一步只做「微小去噪」,需要逐步迭代。这是扩散模型质量高的代价。2025-2026 年的蒸馏技术(如 SDXL-Turbo、LCM)已将步数从 50 降到 1-4 步。
Q: Diffusion Transformer (DiT) 是什么?
用 Transformer 替代 U-Net 的扩散架构。Sora 就基于 DiT——Transformer 的 scaling 能力更强,适合视频这种高维数据。
Q: 扩散模型和 Stable Diffusion 有什么关系?
Stable Diffusion 是「潜在空间扩散模型」——不在像素空间扩散,而是先用 VAE 压缩到小得多的潜在空间再扩散。这大幅降低了计算量。
下一步建议: 用 diffusers 库加载 stable-diffusion-v1-5,尝试用不同 text prompt 生成图片,体会「文字控制扩散方向」的魔法。