先看视频
【简单易懂Diffusion模型综述 - 基础算法详解】 https://www.bilibili.com/video/BV1TP4y1Q7qJ/?share_source=copy_web&vd_source=0261590b30f63d16142104bb154164d9
【Why Does Diffusion Work Better than Auto-Regression?(为什么扩散比自回归更有效?)】 https://www.bilibili.com/video/BV1zb42187Ec/?share_source=copy_web&vd_source=0261590b30f63d16142104bb154164d9
【大白话01一文理清 Diffusion Model 扩散模型 - 原理图解+公式推导】 https://www.bilibili.com/video/BV1xih7ecEMb/?share_source=copy_web&vd_source=0261590b30f63d16142104bb154164d9
diffusion是什么?
✅ Diffusion 的目标是学习图像分布
我们有很多真实图像样本 $x_0 \sim p_{\text{data}}(x)$,x表示图像,这些图像本身可能来自很复杂的分布,比如:
- 有猫、有狗、有房子
- 有颜色、有纹理、有边缘
- 这些内容在图像像素空间中有 统计相关性
🧠 所以我们想学这个分布 $p_{\text{data}}(x)$
学到了这个分布就能自己生成看上去还不错的图片了。
但是这个分布是我们不知道的,直接建模它太难了,于是 diffusion 做了一件特别巧妙的事:
🧊 1. 构造一个容易的分布:高斯噪声
我们知道一个很简单的分布:标准正态分布 $\mathcal{N}(0, I)$。
它没有任何结构——纯噪声。
🔁 2. 一步步把真实图像“毁掉”,变成高斯
这个过程是我们自己设计出来的,叫做 前向扩散过程(Forward Diffusion):
$x_0 \rightarrow x_1 \rightarrow \cdots \rightarrow x_T \approx \mathcal{N}(0, I)$
每一步我们人为加噪。最终你会把结构丰富的图像变成结构全无的噪声。
这一步是有 closed-form 的(可以直接计算),不需要训练。
🧑🎓 3. 训练一个神经网络做“去噪”
这时候我们要训练一个神经网络来“逆转”这个过程,让它学会:
$x_T \rightarrow x_{T-1} \rightarrow \cdots \rightarrow x_0$
但这个过程本身不是我们有现成数据的,而是我们通过训练让神经网络学会如何一步步还原结构。
为了训练这个网络,大家设计了一个很巧的目标函数,就是让网络预测你加进去的噪声 $\epsilon$,从而间接学会还原图像。
🤖 所以你训练的神经网络其实在做:
- 给它一个 noisy 图像 $x_t$
- 给它一个时间步 $t$
- 它要预测的是这个图像中有多少噪声(也就是你加了多少扰动)
- 这样你就能一步步“反着走”回去,还原出原始图像
✅ 最终目标:生成图像!
一旦你训练好了这个“去噪神经网络”,你就可以:
- 从 $x_T \sim \mathcal{N}(0, I)$ 采一个随机噪声
- 反复应用网络,一步步去噪
- 最终你得到一个新的 $x_0$,它是来自于学到的图像分布 $p_{\text{data}}(x)$。这就完成了图像生成。
📌 所以 diffusion 的核心结构是:
- 设计 Forward 过程(人为加噪,模拟随机性)
- 训练神经网络学会 Reverse(一步步去噪)
- 推理的时候从高斯噪声出发,用神经网络“生成图像”
怎么做呢?
🧠 回顾:我们的目标是学会“反向还原图像”
我们设计了一个前向过程,把真实图像一步步加噪:
$q(x_t \mid x_{t-1}) = \mathcal{N}(x_t; \sqrt{1 - \beta_t} x_{t-1}, \beta_t I)$
最终到第 T 步变成纯高斯噪声 $x_T \sim \mathcal{N}(0, I)$。
我们要训练的神经网络,是学会“从 noisy 图像中逐步去噪”,也就是估计:
$p_\theta(x_{t-1} \mid x_t)$
基本变量定义:
符号 | 含义 |
---|---|
$x_0$ | 原始图像(来自训练集) |
$x_t$ | 第 t 步加噪后的图像(包含部分噪声) |
$T$ | 总共加噪的步数,比如 1000 步 |
$\beta_t$ | 第 t 步加进去的噪声强度(噪声调节因子) |
$\alpha_t = 1 - \beta_t$ | 每一步保留原图的比例 |
$\bar{\alpha}t = \prod{s=1}^t \alpha_s$ | 到第 t 步为止累计保留原图的比例 |
✅ 训练方式一: 预测噪声 $\epsilon$(最主流做法)
最常见的 diffusion(比如 DDPM)是用 噪声预测 的方式来训练网络。
🧪 数据构造(采样训练样本)
对于每一张图像 $x_0 \sim \text{真实图像}$,我们做以下步骤:
- 随机采样一个时间步 $t \sim {1, \dots, T}$
- 采一个随机噪声 $\epsilon \sim \mathcal{N}(0, I)$
- 按照 closed-form 的加噪公式得到 $x_t$:
$x_t = \sqrt{\bar{\alpha}_t} x_0 + \sqrt{1 - \bar{\alpha}_t} \epsilon$
其中 $\bar{\alpha}t = \prod{s=1}^{t}(1 - \beta_s)$ 是累计的保持比例。
- $x_0$:原始图像;
- $\epsilon \sim \mathcal{N}(0, I)$:标准高斯噪声;
- $\sqrt{\bar{\alpha}_t}$:当前保留图像信息的比例;
- $\sqrt{1 - \bar{\alpha}_t}$:当前混入噪声的比例。
这表示:在任意时间步 t,我们都能直接采样对应的 noisy 图像 $x_t$。
🎯 神经网络目标:预测噪声
我们训练一个神经网络 $\epsilon_\theta(x_t, t)$,目标是预测我们加进去的噪声 $\epsilon$。训练目标是:
$L(\theta) = \mathbb{E}{x_0, \epsilon, t} \left[ \left| \epsilon\theta(x_t, t) - \epsilon \right|^2 \right]$
这样网络学会了:给定一个 noisy 图像和时间步,它能告诉你这个图像里藏了多少噪声。
-
$\theta$:网络的可学习参数;
- $x_0$:从数据集中采样的一张真实图像;
- $\epsilon \sim \mathcal{N}(0, I)$:我们人为加进去的随机噪声;
- $t \sim \text{Uniform}(1, T)$:随机选取一个时间步;
- $x_t$:由前向过程公式计算出来的 noisy 图像;
- $\epsilon_\theta(x_t, t)$:模型的预测输出,一个与 $x_t$ 同形状的张量;
- 目标:让模型预测出的噪声尽量接近我们真实加进去的噪声 $\epsilon$。
🤖 网络结构怎么设计?
1. 输入是两个东西
- $x_t$:一张 noisy 图像,形状和原始图像一样(比如 $3 \times 64 \times 64$)
- t:一个整数时间步,表示当前图像中混入了多少噪声
2. 网络结构一般是 U-Net(特别适合图像处理)
- 输入:noisy 图像 $x_t$
- 时间步 t 会被编码成一个时间向量(embedding),加入到每一层
- 使用 skip-connection,保留高分辨率信息
- 输出和输入同样大小,表示每个像素的噪声值(和 $\epsilon$ 同型)
这就是 diffusion 模型的核心网络。
3. 时间步 t 的处理方式
时间是一个整数 $t \in {1, \dots, T}$,不能直接送给网络。一般的做法是:
- 用一个 位置编码(Positional Encoding) 把 t 编成一个向量;
- 加入到网络的每一层(比如用 FiLM 或直接加到激活上);
这样网络就知道当前是哪一步,还原多少结构。
输出:
- 一个和 $x_t$ 同样形状的张量;
- 表示每个像素的噪声预测值;
✅ 推理阶段怎么用这个网络?
训练完网络后,我们可以用它一步步去噪:
- 从 $x_T \sim \mathcal{N}(0, I)$采样
- 用神经网络预测噪声 $\hat{\epsilon}_\theta(x_T, T)$
- 用下面的公式还原 $x_{T-1}$:
$x_{t-1} = \frac{1}{\sqrt{1 - \beta_t}} \left( x_t - \beta_t \cdot \hat{\epsilon}_\theta(x_t, t) / \sqrt{1 - \bar{\alpha}_t} \right) + \text{some noise}$
重复上面的步骤直到 x_0,得到一张新图像。
🔁 总结一下:
步骤 | 内容 |
---|---|
训练目标 | 让神经网络预测图像里的噪声 |
网络结构 | U-Net + 时间步编码 |
输入输出 | 输入 noisy 图像 $x_t$ 和时间步 t,输出预测噪声 |
损失函数 | MSE:预测的噪声 vs 实际加进去的噪声 |
推理过程 | 从噪声出发,逐步去噪恢复图像 |
代码练习
我们就通过一个从头训练 diffusion 模型的完整 PyTorch demo,一步一步走一遍流程。
为了简单清晰,我们使用 MNIST(28x28 灰度图像) 作为训练数据,使用经典的 DDPM(denoising diffusion probabilistic model) 框架。
🧩 整体结构分为 4 步:
- 加噪函数(前向过程)
- 网络结构(一个小 U-Net)
- 损失函数(噪声预测)
- 训练循环
🧪 Step 0: 安装依赖(只需 PyTorch 和 torchvision)
pip install torch torchvision
🧠 Step 1: 前向加噪函数
import torch
import torch.nn.functional as F
import numpy as np
# beta schedule(噪声强度)线性从小到大
T = 300 # 时间步数
beta = torch.linspace(1e-4, 0.02, T)
alpha = 1.0 - beta
alpha_bar = torch.cumprod(alpha, dim=0)
# 加噪函数 q(x_t | x_0)
def q_sample(x_0, t, noise=None):
if noise is None:
noise = torch.randn_like(x_0)
sqrt_alpha_bar_t = torch.sqrt(alpha_bar[t]).view(-1, 1, 1, 1)
sqrt_one_minus_alpha_bar_t = torch.sqrt(1 - alpha_bar[t]).view(-1, 1, 1, 1)
return sqrt_alpha_bar_t * x_0 + sqrt_one_minus_alpha_bar_t * noise
🧱 Step 2: 一个简单的 U-Net 网络结构(适配 MNIST)
import torch.nn as nn
class SimpleUNet(nn.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(
nn.Conv2d(1 + 1, 32, 3, padding=1), # 图像 + 时间通道
nn.ReLU(),
nn.Conv2d(32, 64, 3, padding=1),
nn.ReLU(),
nn.Conv2d(64, 32, 3, padding=1),
nn.ReLU(),
nn.Conv2d(32, 1, 3, padding=1), # 输出与输入同形状
)
def forward(self, x, t):
# 把时间步 t 编码成一张和图像一样大小的通道图
t = t.float() / T
t = t.view(-1, 1, 1, 1).expand(x.shape[0], 1, 28, 28)
x = torch.cat([x, t], dim=1) # 拼接时间通道
return self.net(x)
🎯 Step 3: 训练 loss 函数(预测噪声)
def diffusion_loss(model, x_0, t):
noise = torch.randn_like(x_0)
x_t = q_sample(x_0, t, noise)
pred_noise = model(x_t, t)
return F.mse_loss(pred_noise, noise)
🔁 Step 4: 训练循环
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
device = 'cuda' if torch.cuda.is_available() else 'cpu'
# 加载 MNIST 数据
transform = transforms.Compose([
transforms.ToTensor(),
lambda x: x * 2. - 1. # 归一化到 [-1, 1]
])
dataset = datasets.MNIST(root='./data', train=True, transform=transform, download=True)
dataloader = DataLoader(dataset, batch_size=128, shuffle=True)
# 初始化模型
model = SimpleUNet().to(device)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
# 训练主循环
for epoch in range(10):
for x, _ in dataloader:
x = x.to(device)
t = torch.randint(0, T, (x.shape[0],), device=device).long()
loss = diffusion_loss(model, x, t)
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(f"Epoch {epoch}: loss = {loss.item():.4f}")
🎨 (Bonus) 推理采样函数(生成图像)
@torch.no_grad()
def sample(model, n_samples=16):
model.eval()
x = torch.randn(n_samples, 1, 28, 28).to(device)
for t_ in reversed(range(T)):
t = torch.full((n_samples,), t_, device=device, dtype=torch.long)
noise_pred = model(x, t)
beta_t = beta[t].view(-1, 1, 1, 1).to(device)
alpha_t = alpha[t].view(-1, 1, 1, 1).to(device)
alpha_bar_t = alpha_bar[t].view(-1, 1, 1, 1).to(device)
if t_ > 0:
noise = torch.randn_like(x)
else:
noise = torch.zeros_like(x)
x = 1 / torch.sqrt(alpha_t) * (x - (1 - alpha_t) / torch.sqrt(1 - alpha_bar_t) * noise_pred) + torch.sqrt(beta_t) * noise
return x
✅ 运行 sample 后你可以用 matplotlib 展示生成图像:
import matplotlib.pyplot as plt
samples = sample(model, 16).cpu()
grid = samples.view(4, 4, 28, 28).permute(0, 2, 1, 3).reshape(4*28, 4*28)
plt.imshow(grid, cmap='gray')
plt.axis('off')
plt.show()
📌 总结
你现在有了一个完整的 diffusion 模型训练管线:
- 从真实图像构造 noisy 图像;
- 用神经网络预测噪声;
- 用 MSE loss 训练网络;
- 用网络逐步还原图像。