扩散模型(Diffusion Models)详解
扩散模型(Diffusion Models)在近几年成为了生成对抗网络(GAN)之外的又一高质量生成模型的主要方向。它的中心理念可以概括为:「先把一张清晰的图逐渐加噪,最后变成纯随机噪声;然后训练一个神经网络去逆向地一步步清除噪声,最终还原到清晰的原图」。本篇将用直观的方式,并结合详细数学公式,来解释扩散模型的运作原理与实现流程。
一、为什么叫“扩散”?
在物理上,“扩散”常指例如一滴墨汁在水中慢慢扩散成均匀分布的过程。对于数据而言,则可以想象:把一张图像视为起始状态,每一步添上一点高斯噪声——图像就越来越模糊,最终完全变成像水中“均匀分布的墨汁”一样的纯噪声。 接下来要做的,就是 “逆向扩散”:假设我们手中只有一张纯随机噪声的图像,通过网络一步步“去噪”,让它回到那幅清晰图像。
在训练阶段,我们知道真实图像和各步噪声图像的对应关系;网络学到如何在每个阶段把噪声减去一点点,从而逐步逼近真实图像分布。
二、数学原理
1. 正向扩散(Forward Diffusion)
令数据点
x
0
∈
R
d
\mathbf{x}_0 \in \mathbb{R}^d
x0∈Rd 表示原始样本(如图像像素),我们定义一个“加噪”马尔可夫链:
x
0
→
x
1
→
…
→
x
T
\mathbf{x}_0 \;\to\; \mathbf{x}_1 \;\to\; \dots \;\to\; \mathbf{x}_T
x0→x1→…→xT
其中每一步遵循
q
(
x
t
∣
x
t
−
1
)
=
N
(
x
t
∣
1
−
β
t
x
t
−
1
,
β
t
I
)
,
q(\mathbf{x}_t \mid \mathbf{x}_{t-1}) =\; \mathcal{N}\!\Bigl(\mathbf{x}_t \mid \sqrt{1-\beta_t}\,\mathbf{x}_{t-1},\;\beta_t \mathbf{I}\Bigr),
q(xt∣xt−1)=N(xt∣1−βtxt−1,βtI),
β
t
\beta_t
βt是第
t
t
t步的噪声强度(
0
<
β
1
<
⋯
<
β
T
<
1
0<\beta_1<\dots<\beta_T<1
0<β1<⋯<βT<1)。
直觉:在第
t
t
t步,我们把上一步
x
t
−
1
\mathbf{x}_{t-1}
xt−1保留
1
−
β
t
\sqrt{1-\beta_t}
1−βt那部分,另外
β
t
\beta_t
βt的部分全改为随机高斯噪声,让图像变得比上一时刻更模糊。
通俗解释:想象有人在一幅画上撒了一点灰尘(噪声),然后下一步再撒更多灰尘,直到画面完全看不清。这便是“正向扩散”。
累积加噪公式
若我们一步步加噪到第
t
t
t步,可写
q
(
x
t
∣
x
0
)
=
N
(
x
t
∣
α
ˉ
t
x
0
,
(
1
−
α
ˉ
t
)
I
)
,
q(\mathbf{x}_t \mid \mathbf{x}_0) =\; \mathcal{N}\!\Bigl(\mathbf{x}_t \;\big|\; \sqrt{\bar{\alpha}_t}\,\mathbf{x}_0,\;\bigl(1-\bar{\alpha}_t\bigr)\mathbf{I}\Bigr),
q(xt∣x0)=N(xt
αˉtx0,(1−αˉt)I),
其中
α
t
=
1
−
β
t
\alpha_t = 1-\beta_t
αt=1−βt 且
α
ˉ
t
=
∏
s
=
1
t
α
s
\bar{\alpha}_t = \prod_{s=1}^t \alpha_s
αˉt=∏s=1tαs。
这表示:只要知道第0步的图像
x
0
\mathbf{x}_0
x0,就能一次性地生成第
t
t
t步的带噪图像,少了迭代操作。
2. 逆向去噪(Reverse Diffusion)
在推理(采样)阶段,我们希望从纯随机噪声
x
T
\mathbf{x}_T
xT开始,反向地一步步去噪,得到
x
0
\mathbf{x}_0
x0。
若定义了
p
θ
(
x
t
−
1
∣
x
t
)
,
t
=
T
,
…
,
1
p_\theta(\mathbf{x}_{t-1} \mid \mathbf{x}_t), \quad t=T,\dots,1
pθ(xt−1∣xt),t=T,…,1
则可采样出一个“逆向马尔可夫链”。如何确定这个条件分布?由于正向过程是高斯加噪,我们可以用贝叶斯法得到
q
(
x
t
−
1
∣
x
t
,
x
0
)
=
N
(
x
t
−
1
∣
μ
~
t
(
x
t
,
x
0
)
,
σ
~
t
2
I
)
,
q(\mathbf{x}_{t-1}\mid \mathbf{x}_t,\mathbf{x}_0) \;=\; \mathcal{N}\Bigl(\mathbf{x}_{t-1} \;\big|\; \tilde{\mu}_t(\mathbf{x}_t,\mathbf{x}_0),\; \tilde{\sigma}^2_t \mathbf{I}\Bigr),
q(xt−1∣xt,x0)=N(xt−1
μ~t(xt,x0),σ~t2I),
这个是真实分布,但它依赖
x
0
\mathbf{x}_0
x0,在实际生成时未知。
因此,我们用神经网络
θ
\theta
θ来近似
p
θ
(
x
t
−
1
∣
x
t
)
≈
q
(
x
t
−
1
∣
x
t
,
x
0
)
,
p_\theta(\mathbf{x}_{t-1}\mid \mathbf{x}_t) \approx q(\mathbf{x}_{t-1}\mid \mathbf{x}_t,\mathbf{x}_0),
pθ(xt−1∣xt)≈q(xt−1∣xt,x0),
并把它再写成
p θ ( x t − 1 ∣ x t ) = N ( x t − 1 ∣ μ θ ( x t , t ) , Σ θ ( x t , t ) ) . p_\theta(\mathbf{x}_{t-1}\mid \mathbf{x}_t) =\mathcal{N}\!\Bigl(\mathbf{x}_{t-1} \mid \mu_\theta(\mathbf{x}_t, t),\,\Sigma_\theta(\mathbf{x}_t, t)\Bigr). pθ(xt−1∣xt)=N(xt−1∣μθ(xt,t),Σθ(xt,t)).
通俗解释:要逆向从一堆灰尘变回清晰图,就要知道“这一点灰尘来自何处”?这是未知的。我们只好用网络学习在第 t t t步时“如何去掉部分灰尘”来逼近真实无噪状态。
3. 训练目标
我们要让网络学会在每个
t
t
t步去噪的方式,使得
x
t
−
1
\mathbf{x}_{t-1}
xt−1逼近真实
x
t
−
1
\mathbf{x}_{t-1}
xt−1。
等价地,我们最小化
L
=
E
q
(
x
0
,
…
,
x
T
)
[
∑
t
=
1
T
D
K
L
(
q
(
x
t
−
1
∣
x
t
,
x
0
)
∥
p
θ
(
x
t
−
1
∣
x
t
)
)
]
.
\mathcal{L}= \mathbb{E}_{\,q(\mathbf{x}_0,\dots,\mathbf{x}_T)}\Bigl[ \sum_{t=1}^T D_{\mathrm{KL}}\!\bigl( q(\mathbf{x}_{t-1}\mid \mathbf{x}_t,\mathbf{x}_0) \,\|\, p_\theta(\mathbf{x}_{t-1}\mid \mathbf{x}_t) \bigr) \Bigr].
L=Eq(x0,…,xT)[t=1∑TDKL(q(xt−1∣xt,x0)∥pθ(xt−1∣xt))].
为方便,常写成噪声预测的MSE形式:
L
=
E
t
,
x
0
,
ϵ
[
∥
ϵ
−
ϵ
θ
(
x
t
,
t
)
∥
2
]
,
\mathcal{L}= \mathbb{E}_{t,\mathbf{x}_0,\epsilon}\,\Bigl[ \|\epsilon - \epsilon_\theta(\mathbf{x}_t,\,t)\|^2 \Bigr],
L=Et,x0,ϵ[∥ϵ−ϵθ(xt,t)∥2],
其中
x
t
=
α
ˉ
t
x
0
+
1
−
α
ˉ
t
ϵ
\mathbf{x}_t = \sqrt{\bar{\alpha}_t}\,\mathbf{x}_0 + \sqrt{1-\bar{\alpha}_t}\,\epsilon
xt=αˉtx0+1−αˉtϵ,
ϵ
∼
N
(
0
,
I
)
\epsilon\sim \mathcal{N}(0,\mathbf{I})
ϵ∼N(0,I)。
用大白话:网络只要学会:给定“带噪的图像
x
t
\mathbf{x}_t
xt”和时间步
t
t
t,输出对应的噪声
ϵ
\epsilon
ϵ”就好;然后我们就能“减去”这个噪声。
三、流程概述
-
训练
- 采样一幅真实图像 x 0 \mathbf{x}_0 x0;
- 选随机 t t t,合成 x t \mathbf{x}_t xt;
- 令网络 ϵ θ \epsilon_\theta ϵθ输出噪声预测,与真实 ϵ \epsilon ϵ比对MSE。
- 更新参数,反复迭代。
-
生成
- 采样纯噪声 x T \mathbf{x}_T xT;
- 对 t = T t=T t=T到1顺序:用网络 ϵ θ ( x t , t ) \epsilon_\theta(\mathbf{x}_t,t) ϵθ(xt,t)去掉部分噪声,得到 x t − 1 \mathbf{x}_{t-1} xt−1。
- 最终得 x 0 \mathbf{x}_0 x0便是合成数据。
四、扩散模型的主要类型
- DDPM: Ho等人提出的Denoising Diffusion Probabilistic Models,是最基础的范式;
- LDM: Latent Diffusion Models,将扩散过程放到低维潜在空间,极大减少计算量;
- DPM-Solver等:专注于加速生成的算法,减少迭代步数;
- 条件扩散:在网络输入额外条件,如文本、类标签等,实现可控生成(如Stable Diffusion)。
五、与GAN/VAE相比
特征 | 扩散模型(DM) | GAN | VAE |
---|---|---|---|
训练稳定性 | 高,相对无模式崩溃 | 低,易不稳定,模式崩溃 | 中等,基于变分推断 |
生成质量 | 高,细节丰富 | 高,但易出现模式缺失或崩溃 | 一般较清晰度略逊 |
显式概率解释 | 有,基于似然、KL散度 | 无显式似然 | 有(ELBO) |
推断速度 | 慢,多步迭代 | 快,单次前向传递 | 快,同上 |
多样性 | 避免模式崩溃,样本多样 | 可能模式崩溃 | 尚可 |
六、应用与案例
- 文本到图像:如Stable Diffusion,在文本提示下生成多样且细节丰富的图像;
- 图像修复、超分辨率:可用逆向扩散去除局部噪声/模糊并恢复细节;
- 医学成像:MRI、CT重建;减少扫描时间;
- 音频/语音:Text-to-Speech或音频去噪,多步过程显著提升合成质量;
- 多模态生成:联合文本、图像、音频等信息进行跨模态合成。
七、优缺点
优点
- 质量高:生成图像细节逼真,不易出现GAN那样的模式崩溃;
- 训练稳定:只需回归噪声,不需要对抗式两网络博弈;
- 概率解释:具备显式似然、易分析理论性质;
- 多样性:生成的样本丰富多彩。
缺点
- 生成速度慢:需多步迭代去噪;
- 计算/显存需求大:在高分辨率或大步数下负担沉重;
- 网络结构复杂:如UNet通常较大;噪声调度策略等超参数多;
- 难应用于离散数据:如文本场景需特殊设计。
八、示例代码(PyTorch)
以下给出一个简化版扩散模型,用于MNIST数据的去噪训练与采样示例。
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
# ====== 1. 简易 UNet-like 网络 ======
class SimpleUNet(nn.Module):
def __init__(self, in_channels=1, out_channels=1):
super().__init__()
self.down = nn.Sequential(
nn.Conv2d(in_channels, 64, kernel_size=3, padding=1),
nn.ReLU(),
nn.Conv2d(64, 128, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2)
)
self.up = nn.Sequential(
nn.ConvTranspose2d(128, 64, kernel_size=2, stride=2),
nn.ReLU(),
nn.Conv2d(64, out_channels, kernel_size=3, padding=1)
)
def forward(self, x):
x_enc = self.down(x)
x_dec = self.up(x_enc)
return x_dec
# ====== 2. 正向扩散过程 ======
def forward_diffusion(x0, t, beta):
"""
在给定时间步t下,为x0添加噪声,得到 x_t,并返回噪声本身。
x0: (B,1,28,28)
t: (B,)
beta: 长度T的1D张量
"""
noise = torch.randn_like(x0)
sqrt_alpha = torch.sqrt(1 - beta[t]).view(-1,1,1,1)
sqrt_beta = torch.sqrt(beta[t]).view(-1,1,1,1)
x_t = sqrt_alpha * x0 + sqrt_beta * noise
return x_t, noise
# ====== 3. 逆向生成过程 ======
@torch.no_grad()
def reverse_diffusion(model, xT, beta):
model.eval()
T = len(beta)
x = xT
for t in reversed(range(T)):
# 预测噪声
noise_pred = model(x)
# 简化逆向更新 (仅演示)
alpha_t = 1 - beta[t]
x = (x - torch.sqrt(1-alpha_t)*noise_pred) / (torch.sqrt(alpha_t)+1e-7)
return x
# ====== 4. 数据加载与训练 ======
device = "cuda" if torch.cuda.is_available() else "cpu"
T = 200
beta_start, beta_end = 0.0001, 0.02
beta = torch.linspace(beta_start, beta_end, T).to(device)
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5,),(0.5,))
])
dataset = datasets.MNIST(root='data', train=True, download=True, transform=transform)
loader = DataLoader(dataset, batch_size=64, shuffle=True)
model = SimpleUNet().to(device)
optimizer = optim.Adam(model.parameters(), lr=1e-3)
criterion = nn.MSELoss()
epochs = 3
for epoch in range(epochs):
total_loss = 0
model.train()
for x, _ in loader:
x = x.to(device)
# 随机步数
t = torch.randint(0, T, (x.size(0),), device=device)
x_t, noise_true = forward_diffusion(x, t, beta)
noise_pred = model(x_t)
loss = criterion(noise_pred, noise_true)
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
print(f"Epoch [{epoch+1}/{epochs}], Loss = {total_loss/len(loader):.4f}")
# ====== 5. 推理:从噪声生成样本 ======
with torch.no_grad():
x_init = torch.randn(1,1,28,28, device=device)
x_gen = reverse_diffusion(model, x_init, beta)
# 显示结果
plt.imshow(x_gen.squeeze().cpu().numpy(), cmap='gray')
plt.title("Generated Sample via Simplified Diffusion")
plt.axis('off')
plt.show()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
代码流程说明
-
正向加噪(
forward_diffusion
)
训练阶段每次会随机抽一个时间步 t t t,在真实图像上添加噪声,用于监督网络学习“如何去除这一步的噪声”。这样,网络就知道在各个时刻该减掉多少噪声、如何恢复真图。 -
逆向去噪(
reverse_diffusion
)
采样阶段从纯噪声开始,逆序地一步步用网络预测并消除噪声,直到恢复出最终图像。该过程模拟了一个“反扩散马尔可夫链”。 -
网络结构
这里用的是简化版 UNet:先卷积提取特征并下采样,再反卷积上采样还原,用于输出对噪声的估计。 -
训练数据与流程
- MNIST 数据:简单灰度手写数字(28×28),做标准化后加载。
- 每个batch:选随机步数 t t t,合成带噪图像 x t x_t xt;网络输出噪声 ϵ ^ \hat{\epsilon} ϵ^,与真噪声比对做MSE。
- 训练若干轮后,模型学会“在任意步 t t t”去除对应程度的噪声。
-
采样
给随机噪声 x T \mathbf{x}_T xT,逐步逆向 x t → x t − 1 \mathbf{x}_t \to \mathbf{x}_{t-1} xt→xt−1,多步迭代后得到 x 0 \mathbf{x}_0 x0。这就是生成出的数字图像。
九、常见Q&A
-
训练为什么不会像GAN那样不稳定?
因为扩散模型只需要回归噪声(MSE目标),而不需对抗博弈,不会出现模式崩溃或梯度平衡困难。 -
如何提升生成速度?
- 使用加速采样(如 DDIM、DPM-Solver),减少迭代步数;
- 使用潜在扩散(LDM)在低维空间进行扩散,少运算量。
-
为什么要加噪这么多步?
- 步数越多,逆向去噪越细致,生成质量越高;
- 但也拖慢生成速度,需要在质量与速度间权衡。
-
可否用于文本生成?
- 是的,但文字是离散数据,需要在embedding或潜在表示上进行扩散,或结合特殊离散扩散策略。
-
UNet是必须的吗?
- 并非必须,但它在多尺度特征提取和还原上表现良好,是去噪任务中比较理想的结构。
-
如何评价生成效果?
- 常用FID、IS等指标衡量分布与多样性;或可视化观察生成质量。
-
超参有哪些关键?
- 噪声调度( β t \beta_t βt策略)、网络大小、训练步数、batch大小等都可能影响效果。
-
扩散模型是否只适合图像?
- 并不局限,音频、3D形状、视频、医学数据等都可应用,只要能定义合适的加噪-去噪过程。
-
稳定扩散(Stable Diffusion)是啥?
- 一种潜在扩散模型(LDM),并结合文本条件。其关键在潜在空间扩散、裁剪后记分和文生图等技巧。
十、结语
扩散模型透过“正向加噪—逆向去噪”的可解释马尔可夫链实现了高品质的数据生成和明确的概率表征。尽管多次迭代导致生成速度略慢,但在稳定性、多样性与细节保真度方面表现优异,也避免了GAN中的训练不稳定和模式崩溃问题。随着加速采样、潜在扩散和条件扩散等技术兴起,扩散模型愈发适合各类高维数据(图像、音频、文本、医学等),正成为深度生成学习不可或缺的重要分支。