从噪点到艺术:深度解析扩散模型原理(从 DDPM 到 Stable Diffusion)

在过去的一两年里,无论是 AI 绘画领域的 Midjourney、Stable Diffusion,还是震惊世界的视频生成模型 Sora,其背后的核心引擎都指向了同一个技术:扩散模型

如果说 GAN(生成对抗网络)开启了 AI 生成的大门,那么扩散模型则彻底引爆了这场 AI 创作的文艺复兴。然而,相比于 GAN 直观的“左右互搏”思想,扩散模型的数学推导显得更为晦涩。

本文将从直观的物理现象出发,带你拨开扩散模型的数学迷雾,详细拆解 DDPM(去噪扩散概率模型)的核心原理,并一步步推演它是如何进化为今天强大的 Stable Diffusion 的。无论你是刚入门的 AI 爱好者,还是准备深入底层的算法工程师,这篇文章都将为你提供清晰的脉络。


1. 核心直觉:扩散模型到底在干什么?

要理解扩散模型,我们可以想象一滴墨水滴入清水中的过程。

  • 前向过程(加噪):墨水刚滴入水中时,它的形状是非常清晰的。随着时间的推移,墨水不断在水中扩散,最终变成一杯均匀的浅色水。这是一个不可逆的熵增过程,信息不断丢失,最终变成纯粹的随机噪声。
  • 反向过程(去噪):扩散模型的终极目标,就是学会逆转时间。它要训练一个神经网络,看着那杯充满噪声的水,一步步把墨水“浓缩”回去,最终还原出最初那滴清晰的墨水。

简而言之,扩散模型的核心思想就是:毁坏数据,然后再学习如何恢复数据。


2. 揭开 DDPM 的神秘面纱

DDPM(Denoising Diffusion Probabilistic Models)是由 Jonathan Ho 等人在 2020 年提出的经典论文,它为现代扩散模型奠定了坚实的数学和工程基础。

2.1 前向过程:毁灭的艺术

在前向过程中,我们不是一次性把图像变成噪声,而是分步进行。我们给图像一点点添加高斯噪声,经过 TT 步之后,图像就变成了一张纯高斯噪声图。

假设原图为 x0x_0,每一步添加的噪声服从正态分布 N(0,βtI)\mathcal{N}(0, \beta_t I)。这里的 βt\beta_t 是预先设定的方差表(通常是一个从 0 逐渐增大的线性或余弦序列)。

数学上,第 tt 步的图像 xtx_t 完全取决于上一步的图像 xt1x_{t-1}

q(xtxt1)=N(xt;1βtxt1,βtI)q(x_t | x_{t-1}) = \mathcal{N}(x_t; \sqrt{1 - \beta_t}x_{t-1}, \beta_t I)

重参数化技巧(数学魔法):
如果在训练时真的要一步步计算 xtx_t,那太慢了。DDPM 的一个伟大贡献是利用了高斯分布的特性,推导出了一个直接从 x0x_0 生成 xtx_t 的公式:

xt=αˉtx0+1αˉtϵx_t = \sqrt{\bar{\alpha}_t}x_0 + \sqrt{1 - \bar{\alpha}_t}\epsilon

其中,αt=1βt\alpha_t = 1 - \beta_tαˉt=s=1tαs\bar{\alpha}_t = \prod_{s=1}^t \alpha_sϵ\epsilon 是一个标准的纯高斯噪声。

这个公式极其重要!它意味着我们在训练时,可以直接在原图 x0x_0 上加噪,瞬间得到第 tt 步的加噪图 xtx_t

2.2 反向过程:重建的艺术

反向过程的目标是从纯噪声 xTx_T 开始,一步步去噪,最终恢复出 x0x_0。由于真实的后验分布 q(xt1xt)q(x_{t-1} | x_t) 无法直接计算,我们需要训练一个神经网络 pθp_\theta 来近似它:

pθ(xt1xt)=N(xt1;μθ(xt,t),Σθ(xt,t))p_\theta(x_{t-1} | x_t) = \mathcal{N}(x_{t-1}; \mu_\theta(x_t, t), \Sigma_\theta(x_t, t))

巧妙的设计:预测噪声而不是预测图像
DDPM 论文发现,与其让神经网络直接预测去噪后的图像 μθ\mu_\theta,不如让神经网络预测我们在前向过程中添加的那个噪声 ϵ\epsilon

也就是说,我们设计一个模型(通常是 U-Net),输入加噪后的图像 xtx_t 和时间步 tt,让它输出图像中的噪声 ϵθ(xt,t)\epsilon_\theta(x_t, t)。然后通过以下公式计算出 xt1x_{t-1}

xt1=1αt(xt1αt1αˉtϵθ(xt,t))+σtzx_{t-1} = \frac{1}{\sqrt{\alpha_t}} \left( x_t - \frac{1 - \alpha_t}{\sqrt{1 - \bar{\alpha}_t}} \epsilon_\theta(x_t, t) \right) + \sigma_t z

(其中 zz 是额外的随机噪声,除了最后一步 t=1t=1 时不加)

2.3 损失函数:大道至简

既然模型要预测噪声,那么训练目标就变得极其简单:最小化真实添加的噪声 ϵ\epsilon 与模型预测的噪声 ϵθ\epsilon_\theta 之间的均方误差(MSE)

L=Et,x0,ϵ[ϵϵθ(xt,t)2]\mathcal{L} = \mathbb{E}_{t, x_0, \epsilon} \left[ || \epsilon - \epsilon_\theta(x_t, t) ||^2 \right]

这就是 DDPM 的训练过程!没有 GAN 中复杂的对抗损失,也没有令人头疼的模式崩溃,极其稳定。


3. 代码实战:教你用 PyTorch 手搓一个简易 DDPM

为了让大家更直观地理解,我们用 PyTorch 写一个极其简化的 DDPM 核心代码。

3.1 定义扩散过程

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
import torch
import torch.nn as nn
import numpy as np

class SimpleDDPM:
def __init__(self, num_timesteps=1000, beta_start=1e-4, beta_end=0.02, device='cuda'):
self.num_timesteps = num_timesteps
self.device = device

# 线性 beta 调度器
self.betas = torch.linspace(beta_start, beta_end, num_timesteps).to(device)
self.alphas = 1.0 - self.betas
# 计算累积乘积 alpha_bar
self.alpha_cumprod = torch.cumprod(self.alphas, axis=0)

# 提前计算好的常数,用于反向采样
self.sqrt_alpha_inv = torch.sqrt(1.0 / self.alphas)
self.sqrt_one_minus_alpha_cumprod = torch.sqrt(1.0 - self.alpha_cumprod)

def add_noise(self, x_0, t):
"""前向过程:直接给 x_0 加噪得到 x_t"""
epsilon = torch.randn_like(x_0).to(self.device) # 纯随机噪声

# 根据公式提取对应的常数
sqrt_alpha_cumprod_t = self.alpha_cumprod[t][:, None, None, None]
sqrt_one_minus_alpha_cumprod_t = self.sqrt_one_minus_alpha_cumprod[t][:, None, None, None]

# x_t = sqrt(alpha_bar_t) * x_0 + sqrt(1 - alpha_bar_t) * epsilon
x_t = sqrt_alpha_cumprod_t * x_0 + sqrt_one_minus_alpha_cumprod_t * epsilon
return x_t, epsilon

def sample_step(self, model, x_t, t):
"""反向过程:单步去噪"""
# 模型预测噪声
predicted_noise = model(x_t, t)

# 提取常数
sqrt_alpha_inv_t = self.sqrt_alpha_inv[t][:, None, None, None]
beta_t = self.betas[t][:, None, None, None]
sqrt_one_minus_alpha_cumprod_t = self.sqrt_one_minus_alpha_cumprod[t][:, None, None, None]

# 计算均值
# mu = (1/sqrt(alpha_t)) * (x_t - (beta_t / sqrt(1 - alpha_bar_t)) * predicted_noise)
mean = sqrt_alpha_inv_t * (x_t - (beta_t / sqrt_one_minus_alpha_cumprod_t) * predicted_noise)

if t[0] > 0:
# 保持随机性(除了最后一步)
noise = torch.randn_like(x_t)
sigma_t = torch.sqrt(beta_t)
x_t_minus_1 = mean + sigma_t * noise
else:
x_t_minus_1 = mean

return x_t_minus_1

3.2 训练循环

在实际训练中,代码异常简单:

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
# 假设 model 是一个类似 U-Net 的网络,接收图像和时间步 t
# optimizer 是优化器
# dataloader 是你的图像数据集

ddpm = SimpleDDPM()
model = UNet() # 伪代码
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)

for epoch in range(epochs):
for batch in dataloader:
optimizer.zero_grad()

x_0 = batch.to('cuda') # 真实图像
batch_size = x_0.shape[0]

# 1. 随机采样时间步 t
t = torch.randint(0, ddpm.num_timesteps, (batch_size,), device='cuda').long()

# 2. 前向加噪,获取加噪图像 x_t 和真实噪声 epsilon
x_t, epsilon = ddpm.add_noise(x_0, t)

# 3. 模型预测噪声
pred_epsilon = model(x_t, t)

# 4. 计算 MSE 损失
loss = nn.MSELoss()(pred_epsilon, epsilon)

# 5. 反向传播更新权重
loss.backward()
optimizer.step()

4. 突破像素限制:Stable Diffusion 的降维打击

DDPM 虽然效果惊艳,但它有一个致命的缺点:在像素空间直接做扩散太慢、太耗显存了!

生成一张 512×512512 \times 512 的 RGB 图片,需要处理 512×512×3=786,432512 \times 512 \times 3 = 786,432 个维度。U-Net 在这么大的分辨率上跑成百上千步去噪,普通人的显卡根本吃不消。

为了解决这个问题,Stable Diffusion(基于 Latent Diffusion Models 论文)引入了一个极其天才的设计:潜在扩散

4.1 压缩空间:变分自编码器 (VAE)

Stable Diffusion 没有在像素空间做扩散,而是引入了一个预训练好的 VAE。

  • 编码器:将高分辨率的像素图像(H×W×3H \times W \times 3)压缩到一个低维度的“潜在空间”。例如,512×512×3512 \times 512 \times 3 的图像会被压缩为 64×64×464 \times 64 \times 4 的特征图。维度缩小了接近 48 倍!
  • 解码器:将低维度的潜在特征还原回高分辨率图像。

核心改变:扩散过程不再发生在像素图上,而是发生在 64×64×464 \times 64 \times 4 的潜在特征图上!这让计算量直线下降,普通消费级显卡也能跑得动。

4.2 文本控制:文本编码器与交叉注意力

如果只是能生成随机的风景图,那 AI 绘画就不会这么火。Stable Diffusion 的强大之处在于它理解人类的语言(Prompt)。

  • CLIP 文本编码器:SD 使用 CLIP 模型将用户输入的文字(比如 “a cute cat”)转换为一系列的特征向量。
  • U-Net 中的交叉注意力:在潜在空间进行去噪的 U-Net 中,不仅有处理图像特征的卷积层,还加入了注意力层。这些注意力层负责将“文本特征”和“图像特征”进行融合。模型在去噪时,必须时刻参考文本的指令,确保去噪出来的图像符合文字描述。

4.3 SD 的完整工作流

现在我们来看 Stable Diffusion 生成图像的完整生命周期:

  1. 文本输入:用户输入提示词,CLIP 将其转化为文本向量。
  2. 生成初始噪声:在潜在空间生成一个完全随机的 64×64×464 \times 64 \times 4 的纯噪声张量。
  3. 迭代去噪(核心):U-Net 接收纯噪声和时间步 tt,结合文本向量,预测出噪声。不断循环 TT 次,将纯噪声转化为有意义的潜在特征图。
  4. 图像解码:将去噪完成的潜在特征图输入到 VAE 的解码器中,放大并还原成一张 512×512512 \times 512 的精美图像。

5. 不止于文生图:扩散模型的无限可能

DDPM 和 Stable Diffusion 打开了生成式 AI 的新纪元。在此基础上,整个技术生态正在疯狂迭代:

  • ControlNet:让扩散模型不仅仅听文字,还能根据用户提供的草图、人体姿态骨架、深度图进行精准控制,真正做到了“指哪打哪”。
  • LoRA (Low-Rank Adaptation):通过冻结大模型的权重,只训练极少量的附加参数,让普通人可以用几张照片训练专属的 AI 模型(比如生成特定风格的画作或特定人物的脸)。
  • Sora 与视频扩散:将扩散模型的思想引入时间维度,不仅预测空间上的噪声,还预测时间序列上的动态变化,开启了物理世界模拟器的大门。

6. 总结

从 DDPM 的严谨数学推导,利用 U-Net 在像素空间中学习逆转熵增;到 Stable Diffusion 引入 VAE 将计算转移到潜在空间,结合 CLIP 实现文本到图像的精准控制。扩散模型的演进,是深度学习中“工程智慧”与“数学美感”完美结合的典范。

理解这些底层原理,不仅有助于我们更好地调整 Prompt 和使用各类参数,更为我们探索未来的 AI 技术架构打下了坚实的基础。在 AI 技术日新月异的今天,扩散模型不会是终点,但它所蕴含的“去噪与重建”的思想,必将在更广阔的技术领域大放异彩。