在过去的一两年里,无论是 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 前向过程:毁灭的艺术
在前向过程中,我们不是一次性把图像变成噪声,而是分步进行。我们给图像一点点添加高斯噪声,经过 T 步之后,图像就变成了一张纯高斯噪声图。
假设原图为 x0,每一步添加的噪声服从正态分布 N(0,βtI)。这里的 βt 是预先设定的方差表(通常是一个从 0 逐渐增大的线性或余弦序列)。
数学上,第 t 步的图像 xt 完全取决于上一步的图像 xt−1:
q(xt∣xt−1)=N(xt;1−βtxt−1,βtI)
重参数化技巧(数学魔法):
如果在训练时真的要一步步计算 xt,那太慢了。DDPM 的一个伟大贡献是利用了高斯分布的特性,推导出了一个直接从 x0 生成 xt 的公式:
xt=αˉtx0+1−αˉtϵ
其中,αt=1−βt,αˉt=∏s=1tαs,ϵ 是一个标准的纯高斯噪声。
这个公式极其重要!它意味着我们在训练时,可以直接在原图 x0 上加噪,瞬间得到第 t 步的加噪图 xt。
2.2 反向过程:重建的艺术
反向过程的目标是从纯噪声 xT 开始,一步步去噪,最终恢复出 x0。由于真实的后验分布 q(xt−1∣xt) 无法直接计算,我们需要训练一个神经网络 pθ 来近似它:
pθ(xt−1∣xt)=N(xt−1;μθ(xt,t),Σθ(xt,t))
巧妙的设计:预测噪声而不是预测图像
DDPM 论文发现,与其让神经网络直接预测去噪后的图像 μθ,不如让神经网络预测我们在前向过程中添加的那个噪声 ϵ。
也就是说,我们设计一个模型(通常是 U-Net),输入加噪后的图像 xt 和时间步 t,让它输出图像中的噪声 ϵθ(xt,t)。然后通过以下公式计算出 xt−1:
xt−1=αt1(xt−1−αˉt1−αtϵθ(xt,t))+σtz
(其中 z 是额外的随机噪声,除了最后一步 t=1 时不加)
2.3 损失函数:大道至简
既然模型要预测噪声,那么训练目标就变得极其简单:最小化真实添加的噪声 ϵ 与模型预测的噪声 ϵθ 之间的均方误差(MSE)。
L=Et,x0,ϵ[∣∣ϵ−ϵθ(xt,t)∣∣2]
这就是 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 self.betas = torch.linspace(beta_start, beta_end, num_timesteps).to(device) self.alphas = 1.0 - self.betas 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_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] 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
|
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] t = torch.randint(0, ddpm.num_timesteps, (batch_size,), device='cuda').long() x_t, epsilon = ddpm.add_noise(x_0, t) pred_epsilon = model(x_t, t) loss = nn.MSELoss()(pred_epsilon, epsilon) loss.backward() optimizer.step()
|
4. 突破像素限制:Stable Diffusion 的降维打击
DDPM 虽然效果惊艳,但它有一个致命的缺点:在像素空间直接做扩散太慢、太耗显存了!
生成一张 512×512 的 RGB 图片,需要处理 512×512×3=786,432 个维度。U-Net 在这么大的分辨率上跑成百上千步去噪,普通人的显卡根本吃不消。
为了解决这个问题,Stable Diffusion(基于 Latent Diffusion Models 论文)引入了一个极其天才的设计:潜在扩散。
4.1 压缩空间:变分自编码器 (VAE)
Stable Diffusion 没有在像素空间做扩散,而是引入了一个预训练好的 VAE。
- 编码器:将高分辨率的像素图像(H×W×3)压缩到一个低维度的“潜在空间”。例如,512×512×3 的图像会被压缩为 64×64×4 的特征图。维度缩小了接近 48 倍!
- 解码器:将低维度的潜在特征还原回高分辨率图像。
核心改变:扩散过程不再发生在像素图上,而是发生在 64×64×4 的潜在特征图上!这让计算量直线下降,普通消费级显卡也能跑得动。
4.2 文本控制:文本编码器与交叉注意力
如果只是能生成随机的风景图,那 AI 绘画就不会这么火。Stable Diffusion 的强大之处在于它理解人类的语言(Prompt)。
- CLIP 文本编码器:SD 使用 CLIP 模型将用户输入的文字(比如 “a cute cat”)转换为一系列的特征向量。
- U-Net 中的交叉注意力:在潜在空间进行去噪的 U-Net 中,不仅有处理图像特征的卷积层,还加入了注意力层。这些注意力层负责将“文本特征”和“图像特征”进行融合。模型在去噪时,必须时刻参考文本的指令,确保去噪出来的图像符合文字描述。
4.3 SD 的完整工作流
现在我们来看 Stable Diffusion 生成图像的完整生命周期:
- 文本输入:用户输入提示词,CLIP 将其转化为文本向量。
- 生成初始噪声:在潜在空间生成一个完全随机的 64×64×4 的纯噪声张量。
- 迭代去噪(核心):U-Net 接收纯噪声和时间步 t,结合文本向量,预测出噪声。不断循环 T 次,将纯噪声转化为有意义的潜在特征图。
- 图像解码:将去噪完成的潜在特征图输入到 VAE 的解码器中,放大并还原成一张 512×512 的精美图像。
5. 不止于文生图:扩散模型的无限可能
DDPM 和 Stable Diffusion 打开了生成式 AI 的新纪元。在此基础上,整个技术生态正在疯狂迭代:
- ControlNet:让扩散模型不仅仅听文字,还能根据用户提供的草图、人体姿态骨架、深度图进行精准控制,真正做到了“指哪打哪”。
- LoRA (Low-Rank Adaptation):通过冻结大模型的权重,只训练极少量的附加参数,让普通人可以用几张照片训练专属的 AI 模型(比如生成特定风格的画作或特定人物的脸)。
- Sora 与视频扩散:将扩散模型的思想引入时间维度,不仅预测空间上的噪声,还预测时间序列上的动态变化,开启了物理世界模拟器的大门。
6. 总结
从 DDPM 的严谨数学推导,利用 U-Net 在像素空间中学习逆转熵增;到 Stable Diffusion 引入 VAE 将计算转移到潜在空间,结合 CLIP 实现文本到图像的精准控制。扩散模型的演进,是深度学习中“工程智慧”与“数学美感”完美结合的典范。
理解这些底层原理,不仅有助于我们更好地调整 Prompt 和使用各类参数,更为我们探索未来的 AI 技术架构打下了坚实的基础。在 AI 技术日新月异的今天,扩散模型不会是终点,但它所蕴含的“去噪与重建”的思想,必将在更广阔的技术领域大放异彩。