炼丹师的终极指南:从 DDPM 到 Stable Diffusion,彻底搞懂扩散模型原理

如果你在过去两年里关注过 AI 领域,你绝对无法避开“扩散模型”这个词。从 Midjourney 的惊艳出圈,到 Stable Diffusion 的开源生态大爆发,再到如今 Sora 在视频生成领域的降维打击,这些魔法般的背后,核心驱动力全都是扩散模型。

然而,当你试图深入理解扩散模型的原理时,往往会遇到满篇的马尔可夫链、变分推断(ELBO)、朗之万动力学等吓人的数学名词。为了让更多开发者跨过这道门槛,本文将用最通俗的语言 + 最核心的数学推导 + 实际的代码片段,带你从最基础的 DDPM 一路通关到 Stable Diffusion 的核心架构。

准备好了吗?我们开始“炼丹”!


一、直觉先行:扩散模型到底在干什么?

在理解复杂的数学之前,我们先建立一个直觉。如果说生成对抗网络(GAN)是“左撇子造假钞,右撇子验钞,两者在对抗中共同进步”,那么扩散模型的核心思想则可以用四个字概括:破坏与重建

扩散模型包含两个主要过程:

  1. 前向过程(Forward Process / 扩散):给一张清晰的图片不断撒马赛克(加高斯噪声),直到它变成一张纯粹的雪花屏(纯高斯噪声)。
  2. 逆向过程(Reverse Process / 去噪):训练一个神经网络,让它学会如何一步步把雪花屏上的噪声抹去,最终还原出一张清晰的图片。

这就是所谓的“去噪扩散概率模型”。


二、奠基之作:DDPM 详解

2020 年,Jonathan Ho 等人发表了 DDPM(Denoising Diffusion Probabilistic Models),正式奠定了现代扩散模型的基础。

1. 前向过程(加噪)

前向过程是一个马尔可夫链。假设我们有一张原始图片 x0x_0,我们定义一个方差调度表 β1,β2,...,βT\beta_1, \beta_2, ..., \beta_T。在每一步 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 \mathbf{I})

这里的 N\mathcal{N} 表示高斯分布。意思是说,时刻 tt 的图片,只和时刻 t1t-1 的图片有关。在机器学习中,我们通常希望在训练时能一步到位地获取任意时刻 tt 的加噪图片。利用重参数化技巧,我们可以推导出一个极其重要的公式:

定义 αt=1βt\alpha_t = 1 - \beta_t,且 αˉt=i=1tαi\bar{\alpha}_t = \prod_{i=1}^t \alpha_i。我们可以直接从 x0x_0 生成 xtx_t

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)\mathbf{I})

用代码表示就是:

1
2
# x_0: 原始图片, noise: 随机高斯噪声, t: 当前时间步
x_t = sqrt(alpha_bar[t]) * x_0 + sqrt(1 - alpha_bar[t]) * noise

划重点:这个公式意味着,我们在训练时根本不需要一步步去加噪,只要有了 x0x_0 和标准噪声,我们可以瞬间算出第 tt 步的加噪结果。

2. 逆向过程(去噪)

逆向过程的目标是 q(xt1xt)q(x_{t-1} | x_t),即已知当前的噪声图,如何推出上一时刻的图。可惜,这个真实的后验分布是无法直接计算的。

DDPM 的核心神来之笔:用一个神经网络 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))

这里有个绝妙的数学结论:经过复杂的变分下界(ELBO)推导,我们发现,神经网络不需要直接预测图片,它只需要预测我们当初撒上去的那张“噪声图”就可以了!

3. 损失函数

简化后的 DDPM 损失函数极其优雅:

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

其中:

  • ϵ\epsilon 是我们在前向过程中实际添加的真实噪声。
  • ϵθ(xt,t)\epsilon_\theta(x_t, t) 是我们的神经网络(通常是 U-Net)根据带噪图片 xtx_t 和时间步 tt 预测出来的噪声。
  • 损失函数就是两者的 MSE(均方误差)

4. 采样过程(生成图片)

训练好网络后,如何生成图片?

  1. 从标准正态分布中随机采样一个纯噪声 xTx_T
  2. 利用训练好的网络预测噪声 ϵθ(xt,t)\epsilon_\theta(x_t, t)
  3. 用以下公式计算出 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 时添加)
  4. 循环 TT 次,最终得到生成的图片 x0x_0

三、速度的革命:从 DDPM 到 DDIM

DDPM 虽然效果惊艳,但致命缺点是太慢了。生成一张图片需要 1000 次循环(即 T=1000T=1000),这在实际应用中是无法接受的。

2020 年底,Jiaming Song 等人提出了 DDIM (Denoising Diffusion Implicit Models)。它的核心发现是:前向过程其实不一定要是马尔可夫链。

DDIM 证明了,只要满足特定的边缘分布,我们可以跳过很多中间步骤,直接从 xtx_t 跳到 xtΔtx_{t-\Delta t}。这使得我们可以在仅仅 20 到 50 步的情况下,生成质量与 1000 步相媲美的图片。这一改进,为后来的 Stable Diffusion 在消费级显卡上的普及铺平了道路。


四、工程奇迹:Stable Diffusion (LDM) 架构剖析

到了 2021 年底,慕尼黑大学 Runway 等团队发表了 High-Resolution Image Synthesis with Latent Diffusion Models,也就是后来大名鼎鼎的 Stable Diffusion (SD)

DDPM 有一个痛点:它直接在像素空间进行扩散。如果是 512x512 分辨率的 RGB 图片,向量的维度高达 512×512×3786,432512 \times 512 \times 3 \approx 786,432。U-Net 在这么大的维度上计算耗时且吃内存。

1. 核心思想:潜空间扩散

Stable Diffusion 的神来之笔在于:不要在像素空间里玩,去潜空间里玩!

SD 引入了一个预训练的 变分自编码器(VAE)

  • Encoder(编码器):将 512×512×3512 \times 512 \times 3 的像素图片压缩成 64×64×464 \times 64 \times 4 的潜空间特征图。维度缩小了 48 倍!
  • Decoder(解码器):将 64×64×464 \times 64 \times 4 的潜空间特征图放大还原回 512×512×3512 \times 512 \times 3 的像素图片。

有了 VAE,SD 的扩散过程完全在 64×64×464 \times 64 \times 4 的低维空间中进行。计算量瞬间暴跌,这也为什么我们能在普通的 RTX 3060 显卡上流畅运行 SD 的根本原因。

2. 灵魂注入:文本条件控制

只会随机生成图片的模型是没有商业价值的,我们需要让模型听懂人类的指令,比如“画一只戴着墨镜的猫”。

在 SD 中,文本提示词是通过 CLIP (Contrastive Language-Image Pre-training) 模型转化为特征向量的。但问题来了:如何让 U-Net 在去噪时参考这些文本特征?

答案在于 Cross-Attention(交叉注意力机制)

在 U-Net 的网络结构中,除了常规的卷积层,还插入了很多 Cross-Attention 块:

  • Query (Q):来自于图像特征(潜空间中的噪声图)。
  • Key (K) 和 Value (V):来自于文本提示词的特征向量。

通过这种机制,U-Net 在预测噪声时,会不断地“看一眼”文本提示词,知道哪些区域应该保留,哪些区域应该去掉噪声。

3. CFG:无分类器指导

为了进一步提升图像与提示词的契合度,SD 采用了 CFG (Classifier-Free Guidance) 技术。

在训练时,模型有一定概率会“无视”文本条件进行无条件训练。在推理(生成)时,模型会同时进行两次预测:

  1. 不带文本预测的噪声 ϵuncond\epsilon_{uncond}
  2. 带文本预测的噪声 ϵcond\epsilon_{cond}

最终的预测噪声会被外力“拉扯”:

ϵfinal=ϵuncond+s(ϵcondϵuncond)\epsilon_{final} = \epsilon_{uncond} + s \cdot (\epsilon_{cond} - \epsilon_{uncond})

这里的 ss 就是我们在 WebUI 中经常调整的 CFG Scaless 越大,生成的图片越契合提示词,但也可能变得过于饱和或怪异。


五、PyTorch 伪代码实战:手写一个极简扩散模型

为了让大家有更直观的体会,这里提供一段高度简化的 PyTorch 风格伪代码,展示 DDPM 训练和采样的核心逻辑。

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

def linear_beta_schedule(timesteps, beta_start=0.0001, beta_end=0.02):
return torch.linspace(beta_start, beta_end, timesteps)

T = 1000
betas = linear_beta_schedule(T)
alphas = 1. - betas
alphas_cumprod = torch.cumprod(alphas, axis=0)
sqrt_alphas_cumprod = torch.sqrt(alphas_cumprod)
sqrt_one_minus_alphas_cumprod = torch.sqrt(1. - alphas_cumprod)

# 前向加噪一步到位 (提取 x_t)
def q_sample(x_0, t, noise=None):
if noise is None:
noise = torch.randn_like(x_0)

# 根据时间步 t 获取对应的系数
sqrt_alpha_t = sqrt_alphas_cumprod[t].view(-1, 1, 1, 1)
sqrt_one_minus_alpha_t = sqrt_one_minus_alphas_cumprod[t].view(-1, 1, 1, 1)

# 公式: x_t = sqrt(alpha_bar_t) * x_0 + sqrt(1 - alpha_bar_t) * noise
return sqrt_alpha_t * x_0 + sqrt_one_minus_alpha_t * noise

2. 训练步骤 (Training)

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
# 假设 model 是一个类似 U-Net 的神经网络
# 输入为带噪图像 x_t 和时间步 t,输出为预测的噪声
model = Unet(dim=64, dim_mults=(1, 2, 4, 8))
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)

for x_0 in dataloader: # x_0 是真实的清晰图像
optimizer.zero_grad()

# 1. 随机采样时间步 t
t = torch.randint(0, T, (x_0.shape[0],), device=x_0.device).long()

# 2. 采样真实噪声
noise = torch.randn_like(x_0)

# 3. 获取加噪后的图片 x_t
x_t = q_sample(x_0=x_0, t=t, noise=noise)

# 4. 利用神经网络预测噪声
predicted_noise = model(x_t, t)

# 5. 计算 MSE Loss
loss = nn.MSELoss()(noise, predicted_noise)

loss.backward()
optimizer.step()

3. 采样步骤 (Sampling / 生成)

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
@torch.no_grad()
def p_sample(model, x_t, t, t_index):
# 预测噪声
betas_t = betas[t].view(-1, 1, 1, 1)
sqrt_one_minus_alphas_cumprod_t = sqrt_one_minus_alphas_cumprod[t].view(-1, 1, 1, 1)
sqrt_recip_alphas_t = (1. / alphas[t]).sqrt().view(-1, 1, 1, 1)

eps_theta = model(x_t, t)

# 计算均值
model_mean = sqrt_recip_alphas_t * (x_t - betas_t * eps_theta / sqrt_one_minus_alphas_cumprod_t)

if t_index == 0:
return model_mean
else:
# 加入随机噪声 (除了最后一步)
posterior_variance_t = betas_t
noise = torch.randn_like(x_t)
return model_mean + torch.sqrt(posterior_variance_t) * noise

# 完整的采样循环
shape = (1, 3, 64, 64) # Batch, Channels, Height, Width
img = torch.randn(shape) # 从纯噪声 x_T 开始

for i in reversed(range(T)):
img = p_sample(model, img, torch.full((1,), i, dtype=torch.long), i)

# img 就是最终生成的图片

六、总结与展望

从最底层的数学原理来看,扩散模型的演进脉络非常清晰:

  1. DDPM 解决了能不能的问题:利用加噪/去噪的范式和简单的 MSE 损失函数,证明了生成模型可以达到超越 GAN 的画质。
  2. DDIM 解决了快不快的问题:打破了马尔可夫链的限制,让扩散模型从生成一张图耗时分钟级,降低到了秒级。
  3. Stable Diffusion (LDM) 解决了贵不贵的问题:引入 VAE 进入潜空间,将计算量压缩了近 50 倍;引入 Cross-Attention,赋予了模型语言控制的能力。

未来的路在何方?
如今的扩散模型早已不再局限于 2D 图像生成。结合 Transformer 架构,Sora 证明了扩散模型在视频生成长度、物理一致性上的巨大潜力。Consistency Models(一致性模型)正在尝试完全摆脱多步迭代的限制,迈向真正的实时生成(一步生成)。此外,在 3D 资产生成(结合 NeRF/Gaussian Splatting)、音频生成、甚至分子药物设计领域,扩散模型都在大放异彩。

理解扩散模型,不仅是理解当下的 AI 绘画工具,更是拿到了通往下一代生成式 AI 船票的钥匙。希望这篇文章能帮你拨开数学公式的迷雾,真正掌握这项强大的“炼丹”神技!