如果你在过去两年里关注过 AI 领域,你绝对无法避开“扩散模型”这个词。从 Midjourney 的惊艳出圈,到 Stable Diffusion 的开源生态大爆发,再到如今 Sora 在视频生成领域的降维打击,这些魔法般的背后,核心驱动力全都是扩散模型。
然而,当你试图深入理解扩散模型的原理时,往往会遇到满篇的马尔可夫链、变分推断(ELBO)、朗之万动力学等吓人的数学名词。为了让更多开发者跨过这道门槛,本文将用最通俗的语言 + 最核心的数学推导 + 实际的代码片段,带你从最基础的 DDPM 一路通关到 Stable Diffusion 的核心架构。
准备好了吗?我们开始“炼丹”!
一、直觉先行:扩散模型到底在干什么?
在理解复杂的数学之前,我们先建立一个直觉。如果说生成对抗网络(GAN)是“左撇子造假钞,右撇子验钞,两者在对抗中共同进步”,那么扩散模型的核心思想则可以用四个字概括:破坏与重建。
扩散模型包含两个主要过程:
- 前向过程(Forward Process / 扩散):给一张清晰的图片不断撒马赛克(加高斯噪声),直到它变成一张纯粹的雪花屏(纯高斯噪声)。
- 逆向过程(Reverse Process / 去噪):训练一个神经网络,让它学会如何一步步把雪花屏上的噪声抹去,最终还原出一张清晰的图片。
这就是所谓的“去噪扩散概率模型”。
二、奠基之作:DDPM 详解
2020 年,Jonathan Ho 等人发表了 DDPM(Denoising Diffusion Probabilistic Models),正式奠定了现代扩散模型的基础。
1. 前向过程(加噪)
前向过程是一个马尔可夫链。假设我们有一张原始图片 x0,我们定义一个方差调度表 β1,β2,...,βT。在每一步 t,我们向图片中加入微小的噪声:
q(xt∣xt−1)=N(xt;1−βtxt−1,βtI)
这里的 N 表示高斯分布。意思是说,时刻 t 的图片,只和时刻 t−1 的图片有关。在机器学习中,我们通常希望在训练时能一步到位地获取任意时刻 t 的加噪图片。利用重参数化技巧,我们可以推导出一个极其重要的公式:
定义 αt=1−βt,且 αˉt=∏i=1tαi。我们可以直接从 x0 生成 xt:
q(xt∣x0)=N(xt;αˉtx0,(1−αˉt)I)
用代码表示就是:
1 2
| x_t = sqrt(alpha_bar[t]) * x_0 + sqrt(1 - alpha_bar[t]) * noise
|
划重点:这个公式意味着,我们在训练时根本不需要一步步去加噪,只要有了 x0 和标准噪声,我们可以瞬间算出第 t 步的加噪结果。
2. 逆向过程(去噪)
逆向过程的目标是 q(xt−1∣xt),即已知当前的噪声图,如何推出上一时刻的图。可惜,这个真实的后验分布是无法直接计算的。
DDPM 的核心神来之笔:用一个神经网络 pθ 来近似这个逆过程。
pθ(xt−1∣xt)=N(xt−1;μθ(xt,t),Σθ(xt,t))
这里有个绝妙的数学结论:经过复杂的变分下界(ELBO)推导,我们发现,神经网络不需要直接预测图片,它只需要预测我们当初撒上去的那张“噪声图”就可以了!
3. 损失函数
简化后的 DDPM 损失函数极其优雅:
Lsimple=Et,x0,ϵ[∥ϵ−ϵθ(xt,t)∥2]
其中:
- ϵ 是我们在前向过程中实际添加的真实噪声。
- ϵθ(xt,t) 是我们的神经网络(通常是 U-Net)根据带噪图片 xt 和时间步 t 预测出来的噪声。
- 损失函数就是两者的 MSE(均方误差)。
4. 采样过程(生成图片)
训练好网络后,如何生成图片?
- 从标准正态分布中随机采样一个纯噪声 xT。
- 利用训练好的网络预测噪声 ϵθ(xt,t)。
- 用以下公式计算出 xt−1:
xt−1=αt1(xt−1−αˉt1−αtϵθ(xt,t))+σtz
(其中 z 是随机噪声,仅在 t>1 时添加)
- 循环 T 次,最终得到生成的图片 x0。
三、速度的革命:从 DDPM 到 DDIM
DDPM 虽然效果惊艳,但致命缺点是太慢了。生成一张图片需要 1000 次循环(即 T=1000),这在实际应用中是无法接受的。
2020 年底,Jiaming Song 等人提出了 DDIM (Denoising Diffusion Implicit Models)。它的核心发现是:前向过程其实不一定要是马尔可夫链。
DDIM 证明了,只要满足特定的边缘分布,我们可以跳过很多中间步骤,直接从 xt 跳到 xt−Δ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×3≈786,432。U-Net 在这么大的维度上计算耗时且吃内存。
1. 核心思想:潜空间扩散
Stable Diffusion 的神来之笔在于:不要在像素空间里玩,去潜空间里玩!
SD 引入了一个预训练的 变分自编码器(VAE)。
- Encoder(编码器):将 512×512×3 的像素图片压缩成 64×64×4 的潜空间特征图。维度缩小了 48 倍!
- Decoder(解码器):将 64×64×4 的潜空间特征图放大还原回 512×512×3 的像素图片。
有了 VAE,SD 的扩散过程完全在 64×64×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) 技术。
在训练时,模型有一定概率会“无视”文本条件进行无条件训练。在推理(生成)时,模型会同时进行两次预测:
- 不带文本预测的噪声 ϵuncond
- 带文本预测的噪声 ϵcond
最终的预测噪声会被外力“拉扯”:
ϵfinal=ϵuncond+s⋅(ϵcond−ϵuncond)
这里的 s 就是我们在 WebUI 中经常调整的 CFG Scale。s 越大,生成的图片越契合提示词,但也可能变得过于饱和或怪异。
五、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)
def q_sample(x_0, t, noise=None): if noise is None: noise = torch.randn_like(x_0) 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) 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 = Unet(dim=64, dim_mults=(1, 2, 4, 8)) optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)
for x_0 in dataloader: optimizer.zero_grad() t = torch.randint(0, T, (x_0.shape[0],), device=x_0.device).long() noise = torch.randn_like(x_0) x_t = q_sample(x_0=x_0, t=t, noise=noise) predicted_noise = model(x_t, t) 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) img = torch.randn(shape)
for i in reversed(range(T)): img = p_sample(model, img, torch.full((1,), i, dtype=torch.long), i)
|
六、总结与展望
从最底层的数学原理来看,扩散模型的演进脉络非常清晰:
- DDPM 解决了能不能的问题:利用加噪/去噪的范式和简单的 MSE 损失函数,证明了生成模型可以达到超越 GAN 的画质。
- DDIM 解决了快不快的问题:打破了马尔可夫链的限制,让扩散模型从生成一张图耗时分钟级,降低到了秒级。
- Stable Diffusion (LDM) 解决了贵不贵的问题:引入 VAE 进入潜空间,将计算量压缩了近 50 倍;引入 Cross-Attention,赋予了模型语言控制的能力。
未来的路在何方?
如今的扩散模型早已不再局限于 2D 图像生成。结合 Transformer 架构,Sora 证明了扩散模型在视频生成长度、物理一致性上的巨大潜力。Consistency Models(一致性模型)正在尝试完全摆脱多步迭代的限制,迈向真正的实时生成(一步生成)。此外,在 3D 资产生成(结合 NeRF/Gaussian Splatting)、音频生成、甚至分子药物设计领域,扩散模型都在大放异彩。
理解扩散模型,不仅是理解当下的 AI 绘画工具,更是拿到了通往下一代生成式 AI 船票的钥匙。希望这篇文章能帮你拨开数学公式的迷雾,真正掌握这项强大的“炼丹”神技!