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}xt1xtPredictedNoise(实际公式包含系数和方差项)。

2.3 训练目标

非常简单!就是让网络预测的噪声 ϵθ(xt,t)\epsilon_\theta(x_t, t)ϵθ(xt,t) 和真实加入的噪声 ϵ\epsilonϵ 越像越好。
Loss=∣∣ϵ−ϵθ(xt,t)∣∣2 Loss = ||\epsilon - \epsilon_\theta(x_t, t)||^2 Loss=∣∣ϵϵθ(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

它由三个部分组成:

  1. VAE (变分自编码器)
    • 作用:把巨大的“像素空间”压缩到微小的“潜在空间 (Latent Space)”。
    • 例如:把 512x512x3 的图片压缩成 64x64x4 的 Latent 向量。数据量减少了 48 倍!
    • 所有的扩散、去噪过程,都在这个 64x64 的小图上进行。
  2. U-Net (噪声预测器)
    • 在 Latent 空间里干活,预测噪声。
    • 引入了 Cross-Attention (交叉注意力) 机制,为了听懂提示词。
  3. CLIP Text Encoder (提示词理解)
    • 把用户的 Prompt(如 “A cute cat”)变成向量,通过 Cross-Attention 注入到 U-Net 中。
    • 这让 Diffusion 变成了可控生成的 Text-to-Image 模型。

4.2 生成流程

  1. 用户输入 “A cyberpunk city”。
  2. CLIP 把它变成 Text Embeddings。
  3. 随机生成一个 64x64 的纯噪声 Latent。
  4. U-Net 在 Text Embeddings 的指引下,对 Latent 逐步去噪(比如 50 步)。
  5. VAE Decoder 把去噪后的 Latent 放大回 512x512 的高清大图。

5. 代码实践:DDPM 核心逻辑

这里展示最核心的训练 Loss 计算(伪代码)。

import torch
import torch.nn as nn

class DDPM(nn.Module):
    def __init__(self, unet, timesteps=1000):
        super().__init__()
        self.unet = unet
        self.timesteps = timesteps
        # 预计算 alpha, beta 等参数...

    def forward(self, x0):
        # 1. 随机采样一个时间步 t
        t = torch.randint(0, self.timesteps, (x0.shape[0],), device=x0.device)
        
        # 2. 生成随机噪声 epsilon
        noise = torch.randn_like(x0)
        
        # 3. 计算加噪后的图片 xt (基于重参数化技巧,直接一步算出)
        # xt = sqrt(alpha_bar) * x0 + sqrt(1 - alpha_bar) * noise
        xt = 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)
        return loss

    def sample(self, n_samples):
        # 逆向采样过程
        xt = torch.randn(n_samples, c, h, w) # 从纯噪声开始
        for t in reversed(range(self.timesteps)):
            predicted_noise = self.unet(xt, t)
            # xt_prev = (xt - coeff * predicted_noise) / sqrt(alpha) + sigma * z
            xt = self.p_sample(xt, t, predicted_noise)
        return xt

6. 总结

  • DDPM:用慢工出细活的“去噪”思路,换来了生成质量的巅峰。
  • DDIM:让扩散模型从“这东西没法用”变成了“稍微等等就能用”。
  • Stable Diffusion:通过 Latent SpaceCLIP 的引入,真正把 AI 绘画带入了寻常百姓家(消费级显卡也能跑)。

思考:为什么 Stable Diffusion 生成手指总是容易画崩?

  • 一方面是因为训练数据(Laion-5B)里手部细节往往不清晰或占比太小。
  • 另一方面,VAE 的压缩过程是有损的,手部这种高频细节信息可能在压缩到 64x64 时丢失了。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐