从噪点到艺术:一文通透扩散模型(DDPM 到 Stable Diffusion)核心原理与代码实战

引言

如果说过去两年 AI 领域有什么技术真正实现了“破圈”,彻底改变了人类创作图像的方式,那无疑是扩散模型。从 Midjourney 的惊艳作画,到 Stable Diffusion 的开源生态,再到如今 Sora 在视频生成领域的震撼,背后的核心引擎全都是扩散模型。

在此之前,生成对抗网络(GAN)曾是图像生成的王者,但其难以训练、容易模式崩溃的痛点始终如影随形。扩散模型则另辟蹊径,以一种看似“笨拙”却极具数学美感的方式——先加噪,再去噪——完成了对 GAN 的超越。

本文将从最经典的 DDPM(去噪扩散概率模型)出发,深入浅出地剖析扩散模型的底层数学原理,并一步步推演到如今大红大紫的潜在扩散模型。本文不仅包含直观的原理图解,还包含底层数学推导与 PyTorch 代码实战,带你真正搞懂扩散模型。


一、直觉理解:扩散模型在做什么?

想象你有一张高清的小猫照片。

  1. 前向过程:你往照片上撒一点雪花噪点。撒了 1 次照片还能看清,撒了 100 次,照片就变成了一张纯高斯噪声的图。
  2. 逆向过程:现在给你这张纯噪声图,你如何一步步把噪点去掉,还原出那只小猫?

扩散模型的核心思想就在于:神经网络不直接生成图像,而是学习“如何从噪点图中剥离出 noise”


二、基石:深入 DDPM 原理

DDPM(Denoising Diffusion Probabilistic Models)由 Jonathan Ho 等人在 2020 年提出,奠定了现代扩散模型的理论基础。

2.1 前向过程:毁灭信息的加噪

前向过程是一个马尔可夫链过程。假设原始图像为 x0x_0,我们定义一个时间步 tt(从 1 到 TT)。在每个时间步,我们向图像中添加一小部分高斯噪声:

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)

其中,βt\beta_t 是预先设定的方差表,控制加噪的速度。

重参数化技巧的妙用:
如果按照上述公式一步步算,计算 xTx_T 会极其缓慢。幸运的是,由于高斯分布的可加性,我们可以直接从 x0x_0 得到任意时刻 tt 的加噪图像 xtx_t

定义 αt=1βt\alpha_t = 1 - \beta_t,且 αˉt=i=1tαi\bar{\alpha}_t = \prod_{i=1}^t \alpha_i。我们可以推导出:

q(xtx0)=N(xt;αˉtx0,(1αˉt)I)q(x_t | x_0) = \mathcal{N}(x_t; \sqrt{\bar{\alpha}_t} x_0, (1 - \bar{\alpha}_t) I)

用代码表示就是:

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

(这里的 ϵN(0,I)\epsilon \sim \mathcal{N}(0, I) 就是纯高斯噪声)。

核心意义: 这意味着在训练时,我们可以直接随机采样一个时间步 tt,并立刻生成对应的 xtx_t,而不需要真的跑 tt 次循环。

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 中,方差 Σθ\Sigma_\theta 通常被固定为常量,神经网络的任务仅仅是预测均值 μθ\mu_\theta

经过一系列复杂的变分推断和数学推导,DDPM 论文得出了一个极度优雅的结论:我们不需要让网络直接预测图像,也不需要让它预测均值,最有效的方法是让网络预测我们在时间步 tt 添加的那个噪声 ϵ\epsilon

我们训练一个网络 ϵθ(xt,t)\epsilon_\theta(x_t, t),它的输入是加噪后的图像 xtx_t 和时间步 tt,输出是预测的噪声。

2.3 损失函数:简单的 MSE

基于上述推导,DDPM 的最终损失函数简单到令人发指,本质上就是一个均方误差(MSE):

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

  • x0x_0:真实的干净图像。
  • ϵ\epsilon:随机采样的真实高斯噪声。
  • xtx_t:根据 x0x_0ϵ\epsilon 直接计算出的加噪图像。
  • ϵθ\epsilon_\theta:神经网络预测出的噪声。

训练算法总结:

  1. 取出干净图像 x0x_0
  2. 随机选择一个时间步 tt
  3. 随机生成一个高斯噪声 ϵ\epsilon
  4. 根据 x0x_0ϵ\epsilon 计算 xtx_t
  5. xtx_ttt 输入神经网络,得到预测噪声 ϵθ\epsilon_\theta
  6. 计算 ϵ\epsilonϵθ\epsilon_\theta 的 MSE 损失,反向传播更新网络。

2.4 PyTorch 简易代码实现:前向加噪与训练

为了更直观地理解,我们来看一段简化版的 PyTorch 代码:

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
import torch
import torch.nn.functional as F

# 假设我们有一个简单的 UNet 模型
model = UNet()

# 定义超参数
T = 1000 # 总时间步
betas = torch.linspace(0.0001, 0.02, T).to('cuda') # 线性 beta 调度
alphas = 1.0 - betas
alphas_cumprod = torch.cumprod(alphas, axis=0) # 计算 alpha_bar

def forward_diffusion(x_0, t):
"""
根据x_0和t,直接计算加噪后的x_t
"""
noise = torch.randn_like(x_0).to('cuda') # 生成标准高斯噪声 epsilon

# 提取对应时间步的 alpha_bar_t
alpha_bar_t = alphas_cumprod[t].view(-1, 1, 1, 1)

# 重参数化公式: x_t = sqrt(alpha_bar_t) * x_0 + sqrt(1 - alpha_bar_t) * noise
x_t = torch.sqrt(alpha_bar_t) * x_0 + torch.sqrt(1 - alpha_bar_t) * noise

return x_t, noise

# --- 训练循环 ---
optimizer = torch.optim.Adam(model.parameters(), lr=2e-5)

for batch_images in dataloader:
optimizer.zero_grad()

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

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

# 2. 进行前向加噪,获取加噪图像 x_t 和真实的噪声 noise
x_t, real_noise = forward_diffusion(x_0, t)

# 3. 将 x_t 和 t 输入神经网络,预测噪声
# UNet 需要知道当前是第几步,通常通过正弦位置编码注入时间步 t
predicted_noise = model(x_t, t)

# 4. 计算 MSE 损失
loss = F.mse_loss(real_noise, predicted_noise)

# 5. 反向传播与优化
loss.backward()
optimizer.step()

三、进阶:从 DDPM 到潜在扩散模型

DDPM 虽然效果惊艳,但存在一个致命缺陷:计算资源消耗极大
图像是在像素空间(Pixel Space)中进行扩散的。以一张 512×512×3512 \times 512 \times 3 的图片为例,特征维度高达近 80 万。UNet 在如此高维的空间中反复进行前向和反向传播,导致训练和推理极其缓慢,这也是早期扩散模型无法普及的原因。

为了解决这个问题,Stable Diffusion 横空出世。它引入了潜在空间 的概念,官方论文名为 High-Resolution Image Synthesis with Latent Diffusion Models (LDM)

3.1 压缩与重建:VAE 的魔法

Stable Diffusion 没有直接在像素空间做扩散,而是先用一个预训练好的变分自编码器 (VAE) 将高维图像压缩到一个低维的潜在空间。

  • Encoder:将 512×512×3512 \times 512 \times 3 的图像下采样为 64×64×464 \times 64 \times 4 的潜在特征(Latent)。维度缩小了 48 倍!
  • Decoder:将生成的 64×64×464 \times 64 \times 4 潜在特征上采样还原回 512×512×3512 \times 512 \times 3 的图像。

核心逻辑: 扩散模型的加噪和去噪过程,全部在这个 64×64×464 \times 64 \times 4 的潜在空间中进行!这极大地降低了计算量,使得在消费级 GPU(如 RTX 3090)上训练和推理成为可能。

3.2 文本控制:文本条件注入

如果扩散模型只能随机生成图像,那它充其量只是个玩具。Stable Diffusion 之所以强大,是因为它能够理解人类的语言(如提示词 “a cute cat”)。

这是如何做到的呢?这就需要引入条件机制。SD 采用了 CLIP (Contrastive Language-Image Pre-training) 模型的文本编码器。

  1. 文本编码:将输入的 Prompt(如 “a cute cat”)通过 CLIP Text Encoder 转化为一系列高维的文本特征向量。
  2. 交叉注意力:在 UNet 的去噪网络中,引入了交叉注意力层。图像特征作为 Query (Q),文本特征作为 Key (K) 和 Value (V)。网络在预测每个像素的噪声时,都会去“询问”文本特征,从而知道哪里该画猫,哪里该画背景。

修改后的损失函数如下:

LLDM=Et,z0,ϵ,c[ϵϵθ(zt,t,c)2]L_{LDM} = \mathbb{E}_{t, z_0, \epsilon, c} \left[ || \epsilon - \epsilon_\theta(z_t, t, c) ||^2 \right]

这里的 ztz_t 是潜在空间中的噪声图,cc 就是文本提示词的条件特征。


四、架构解析:Stable Diffusion 的三大组件

综合上述内容,我们可以将 Stable Diffusion 的完整架构拆解为三大核心模块:

1. 文本编码器

  • 作用:将输入文本转化为机器能理解的语义向量。
  • 实现:基于 OpenAI 的 CLIP 模型(具体为 ViT-L/14)。它将词元映射为 768 维或 1024 维的向量。

2. U-Net (噪声预测器)

  • 作用:在潜在空间中,根据当前的噪声图 ztz_t、当前的时间步 tt 以及文本特征 cc,预测出应该去除的噪声。
  • 架构细节
    • 下采样与上采样:经典的 U 型结构,通过卷积提取多尺度特征。
    • 时间步嵌入:使用正弦位置编码将时间步 tt 映射为向量,注入到残差块中。
    • 交叉注意力:穿插在下采样和上采样模块之间,用于融合文本提示词的信息。

3. VAE (变分自编码器)

  • 作用:在像素空间和潜在空间之间进行转换。
  • 推理阶段:仅使用 Decoder,将 UNet 生成的 64×64×464 \times 64 \times 4 Latent 还原为 512×512×3512 \times 512 \times 3 的高清图像。

五、代码实战:使用 Hugging Face Diffusers 生成图像

理解了底层原理后,我们来看看如何在工程上使用 Stable Diffusion。目前最主流的库是 Hugging Face 的 diffusers。它将复杂的模型封装成了简洁的 API。

以下是一段完整的图像生成代码示例:

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
# 确保已经安装了依赖: pip install diffusers transformers accelerate torch
import torch
from diffusers import StableDiffusionPipeline

# 1. 加载模型和权重 (这里使用 Stable Diffusion v1.5 版本)
# 模型会自动从 Hugging Face Hub 下载
model_id = "runwayml/stable-diffusion-v1-5"
pipe = StableDiffusionPipeline.from_pretrained(
model_id,
torch_dtype=torch.float16 # 使用半精度节省显存
)

# 将模型转移到 GPU
pipe = pipe.to("cuda")

# (可选) 开启注意力切片机制,如果显存不足 (例如 < 8GB) 这个选项非常有用
pipe.enable_attention_slicing()

# 2. 准备提示词
prompt = "a photograph of an astronaut riding a horse on mars, detailed, 8k"
negative_prompt = "ugly, blurry, poor quality" # 负面提示词:告诉模型不要生成什么

# 3. 生成图像
# torch.no_grad() 确保在推理时不计算梯度,进一步节省显存
with torch.no_grad():
image = pipe(
prompt=prompt,
negative_prompt=negative_prompt,
height=512, # 生成的图像高度,必须是 8 的倍数
width=512, # 生成的图像宽度
num_inference_steps=50, # 去噪的推理步数,通常 20-50 之间,越多越精细但也越慢
guidance_scale=7.5 # 分类器自由引导强度 (CFG Scale),控制图像贴合提示词的程度
).images[0]

# 4. 保存图像
image.save("astronaut_rides_horse.png")
print("图像已成功生成!")

参数解析(极其重要):

  • num_inference_steps:逆向去噪的步数。在 DDPM 中需要 1000 步,但 LDM 采用了更高级的采样器(如 PNDM、DPM++),通常只需 20-50 步就能生成高质量图像。
  • guidance_scale:CFG(Classifier-Free Guidance)系数。在训练时,模型会随机丢弃一些文本条件,让它同时学习有条件生成和无条件生成。推理时,模型会将预测结果偏离无条件预测,向有条件预测靠近。guidance_scale 越大,生成的图像越符合提示词,但可能会过度饱和;越小越自由,但容易跑题。

六、总结与展望

从 DDPM 的理论奠基,到 LDM 将计算引入潜在空间的降维打击,再到文本条件的完美融合,Stable Diffusion 带来的不仅仅是一场视觉革命,更是生成式 AI 发展史上的一座里程碑。

扩散模型的优势:

  1. 生成质量极高:在多样性和逼真度上全面超越了 GAN。
  2. 训练稳定:没有对抗网络那种难以平衡的博弈过程,损失函数几乎不会崩塌。
  3. 极强的可控性:通过引入文本、边缘图、深度图等条件,能实现极其精准的控制(如 ControlNet 的诞生)。

未来的发展方向:
虽然扩散模型取得了巨大的成功,但挑战依然存在。例如,推理速度较慢(相比于一步生成的 GAN),即便有 LCM(潜在一致性模型)和 SDXL Turbo 等技术的涌现,如何实现实时的极高质量生成仍是研究热点;此外,架构层面的创新也在继续,如 Sora 证明在扩散过程中引入 DiT (Diffusion Transformer) 架构能够展现出更强大的 Scaling Law(缩放定律)。

理解扩散模型的底层原理,不仅有助于我们更好地调参和使用现有工具,更为我们把握未来 AI 生成技术的脉络打下了坚实的基础。从噪点到艺术,扩散模型的魔法大门才刚刚敞开。