Day 17: 扩散模型 (Diffusion Models)
摘要:2022年被称为 “AIGC 元年”,Midjourney 和 Stable Diffusion 的横空出世彻底改变了创意产业。而这背后的功臣,就是扩散模型。它不再像 GAN 那样搞“左右互搏”,也不像 VAE 那样搞“生硬压缩”,而是模仿物理热力学中的“扩散”过程,慢慢把一张图毁掉,再学会慢慢把它复原。
1. 为什么需要扩散模型?
在 Diffusion 出现之前,生成领域存在一个“不可能三角”:
- GAN:采样快,质量高,但多样性差(Mode Collapse),训练极难。
- VAE:多样性好,训练稳,但质量差(模糊)。
- Flow:数学推导完美,但计算成本极高。
Diffusion Models打破了这个诅咒:它生成质量极高(超越 GAN),多样性极好(覆盖整个分布),训练非常稳定(只是慢了一点)。
2. DDPM 原理:从加噪到去噪
DDPM (Denoising Diffusion Probabilistic Models) 的核心思想非常简单:毁掉一幅画很容易,复原它很难,但如果我们把复原过程拆解成 1000 小步,每一步就变得简单了。
2.1 前向过程 (Forward Process) —— 加噪
这是一个固定的过程(不需要训练)。
- 我们将一张清晰图片x0x_0x0,在TTT步(例如 T=1000)内,每一步都加入一点点高斯噪声。
- 最终xTx_TxT会变成一张纯粹的标准正态分布噪声。
- 数学特性:由于高斯分布的优良性质,我们可以直接算出任意时刻ttt的加噪图片xtx_txt,而不需要一步步循环算。
2.2 逆向过程 (Reverse Process) —— 去噪
这是一个需要训练的过程。
- 如果能从纯噪声xTx_TxT逐步推回x0x_0x0,我们就学会了生成图片。
- 核心任务:训练一个神经网络(通常是 U-Net),让它根据当前的噪点图xtx_txt和时间步ttt,预测这一步加了多少噪声ϵ\epsilonϵ。
- 去噪公式:xt−1≈xt−PredictedNoisex_{t-1} \approx x_t - \text{PredictedNoise}xt−1≈xt−PredictedNoise(实际公式包含系数和方差项)。
2.3 训练目标
非常简单!就是让网络预测的噪声ϵθ(xt,t)\epsilon_\theta(x_t, t)ϵθ(xt,t)和真实加入的噪声ϵ\epsilonϵ越像越好。
Loss=∣∣ϵ−ϵθ(xt,t)∣∣2 Loss = ||\epsilon - \epsilon_\theta(x_t, t)||^2Loss=∣∣ϵ−ϵθ(xt,t)∣∣2
本质上就是一个回归问题:给你一张带噪图,请算出上面的噪点长什么样。
3. 加速采样:DDIM 与 DPM-Solver
DDPM 的最大缺点是慢。生成一张图需要像倒放录像带一样走完 1000 步。
- DDIM (Denoising Diffusion Implicit Models):
- 发现去噪过程其实不需要严格遵循马尔可夫链。
- 通过改变采样公式,可以跳步走。比如从 1000 步跳到 900 步,再跳到 800 步。
- 结果:只需要 50 步甚至更少就能生成高质量图片,速度提升 20 倍。
- DPM-Solver:
- 把扩散过程看作微分方程(ODE)求解。
- 使用高阶数值求解器,能进一步把步数压缩到 10-20 步。
4. Stable Diffusion:潜在扩散模型 (Latent Diffusion)
虽然 DDIM 变快了,但在像素空间(Pixel Space)上直接跑 Diffusion 还是很贵。一张 512x512 的图有 26万个像素,显存遭不住。
Stable Diffusion (LDM)的天才之处在于:降维打击。
4.1 核心架构:VAE + U-Net + CLIP
它由三个部分组成:
- VAE (变分自编码器):
- 作用:把巨大的“像素空间”压缩到微小的“潜在空间 (Latent Space)”。
- 例如:把 512x512x3 的图片压缩成 64x64x4 的 Latent 向量。数据量减少了 48 倍!
- 所有的扩散、去噪过程,都在这个 64x64 的小图上进行。
- U-Net (噪声预测器):
- 在 Latent 空间里干活,预测噪声。
- 引入了Cross-Attention (交叉注意力)机制,为了听懂提示词。
- CLIP Text Encoder (提示词理解):
- 把用户的 Prompt(如 “A cute cat”)变成向量,通过 Cross-Attention 注入到 U-Net 中。
- 这让 Diffusion 变成了可控生成的 Text-to-Image 模型。
4.2 生成流程
- 用户输入 “A cyberpunk city”。
- CLIP 把它变成 Text Embeddings。
- 随机生成一个 64x64 的纯噪声 Latent。
- U-Net 在 Text Embeddings 的指引下,对 Latent 逐步去噪(比如 50 步)。
- VAE Decoder 把去噪后的 Latent 放大回 512x512 的高清大图。
5. 代码实践:DDPM 核心逻辑
这里展示最核心的训练 Loss 计算(伪代码)。
importtorchimporttorch.nnasnnclassDDPM(nn.Module):def__init__(self,unet,timesteps=1000):super().__init__()self.unet=unet self.timesteps=timesteps# 预计算 alpha, beta 等参数...defforward(self,x0):# 1. 随机采样一个时间步 tt=torch.randint(0,self.timesteps,(x0.shape[0],),device=x0.device)# 2. 生成随机噪声 epsilonnoise=torch.randn_like(x0)# 3. 计算加噪后的图片 xt (基于重参数化技巧,直接一步算出)# xt = sqrt(alpha_bar) * x0 + sqrt(1 - alpha_bar) * noisext=self.q_sample(x0,t,noise)# 4. 让 U-Net 预测噪声predicted_noise=self.unet(xt,t)# 5. 计算 Loss (MSE)loss=nn.functional.mse_loss(predicted_noise,noise)returnlossdefsample(self,n_samples):# 逆向采样过程xt=torch.randn(n_samples,c,h,w)# 从纯噪声开始fortinreversed(range(self.timesteps)):predicted_noise=self.unet(xt,t)# xt_prev = (xt - coeff * predicted_noise) / sqrt(alpha) + sigma * zxt=self.p_sample(xt,t,predicted_noise)returnxt6. 总结
- DDPM:用慢工出细活的“去噪”思路,换来了生成质量的巅峰。
- DDIM:让扩散模型从“这东西没法用”变成了“稍微等等就能用”。
- Stable Diffusion:通过Latent Space和CLIP的引入,真正把 AI 绘画带入了寻常百姓家(消费级显卡也能跑)。
思考:为什么 Stable Diffusion 生成手指总是容易画崩?
- 一方面是因为训练数据(Laion-5B)里手部细节往往不清晰或占比太小。
- 另一方面,VAE 的压缩过程是有损的,手部这种高频细节信息可能在压缩到 64x64 时丢失了。