扩散模型完全指南:从零开始理解生成式AI的新范式

📚 专为初学者打造的扩散模型深度教程

🎯 目标:用最通俗的语言,讲清楚最前沿的图像生成技术

🎨 扩散模型是什么?它是当今AI图像生成领域最强大的技术,支撑着Stable Diffusion、DALL-E 2、Midjourney等顶级AI绘画工具


📋 目录


1. 为什么要学习扩散模型?

1.1 扩散模型改变了AI创作

如果你关注过最近的AI艺术创作,你会发现几乎所有惊艳的AI绘画工具都基于扩散模型:

应用 基于的技术 发布时间 特点
Stable Diffusion Latent Diffusion Model 2022年8月 开源、可本地运行
DALL-E 2 CLIP + Diffusion 2022年4月 文本到图像生成
Midjourney Diffusion Model 2022年 艺术风格强
Imagen Diffusion Model 2022年5月 Google出品

1.2 为什么扩散模型这么强?

在扩散模型出现之前(2020年前),图像生成主要用GAN(生成对抗网络):

GAN的问题

  • 训练不稳定(模式崩溃)
  • 生成多样性差
  • 难以控制生成内容
  • 质量不够高

扩散模型的优势

  • 训练稳定(简单的去噪任务)
  • 生成质量极高(超越GAN)
  • 多样性好(随机采样)
  • 可控性强(条件生成)
  • 可扩展性强(模型越大效果越好)

1.3 学完这个教程你能做什么?

  • 🔍 理解原理:彻底搞懂扩散模型的前向和反向过程
  • 📐 掌握数学:理解DDPM、DDIM等核心算法
  • 💻 动手实践:用PyTorch从零实现扩散模型
  • 🎨 应用开发:为使用Stable Diffusion等工具打下基础
  • 🚀 研究创新:理解最新的扩散模型变体

2. 扩散模型的核心思想

2.1 一个生活中的类比

想象你在海滩上画了一幅沙画

【第1天】清晰的画
    🎨 细节丰富
    
【第2天】被海浪冲刷
    🌊 变模糊了
    
【第3天】再次冲刷
    🌊 更模糊了
    
【第30天】完全消失
    🌫️ 只剩下均匀的沙子

扩散模型做的事情

  1. 前向过程(加噪声):像海浪冲刷沙画,逐渐添加噪声,直到图像变成纯噪声
  2. 反向过程(去噪声):学习"时光倒流",从纯噪声逐步恢复出清晰图像

2.2 核心思想的可视化

前向扩散(加噪):图像 → 噪声
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

t=0          t=250        t=500        t=750        t=1000
清晰图像  →  轻微噪声  →  中等噪声  →  严重噪声  →  纯噪声
  🖼️         🖼️ ▒        🖼️ ▒▒       ▒▒▒▒       ░░░░
完全清晰    还能看出     很模糊      几乎看不出   完全随机


反向去噪(生成):噪声 → 图像
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

t=1000       t=750        t=500        t=250        t=0
纯噪声    →  去噪一点  →  轮廓出现  →  细节清晰  →  完美图像
░░░░         ▒▒▒▒        🖼️ ▒▒       🖼️ ▒        🖼️
随机噪声    略有形状     看出大概     基本清晰    完全生成

2.3 为什么这样设计有效?

关键洞察:

1. 加噪声很简单
   - 只需要在图像上加高斯噪声
   - 数学上有明确的公式
   
2. 去噪声可以学习
   - 训练神经网络预测噪声
   - 一步步去噪,最终生成图像
   
3. 渐进式生成更稳定
   - 不是一步生成(GAN的做法)
   - 而是逐步细化(更容易学习)

类比:
GAN = 要求画家一笔画出完美作品(很难!)
扩散模型 = 先画草稿,再逐步细化(更容易!)

2.4 扩散模型 vs GAN vs VAE

┌─────────────────────────────────────────────────────────┐
│              三大生成模型对比                            │
├─────────────────────────────────────────────────────────┤
│                                                          │
│  GAN(生成对抗网络):                                   │
│    生成器 vs 判别器(对抗训练)                          │
│    优点:生成快速                                        │
│    缺点:训练不稳定、模式崩溃                            │
│                                                          │
│  VAE(变分自编码器):                                   │
│    编码器 + 解码器(重建+正则化)                        │
│    优点:训练稳定、有潜在空间                            │
│    缺点:生成质量一般(模糊)                            │
│                                                          │
│  扩散模型(Diffusion Model):                           │
│    前向加噪 + 反向去噪(逐步生成)                       │
│    优点:生成质量最高、训练稳定                          │
│    缺点:生成慢(需要多步)                              │
│                                                          │
└─────────────────────────────────────────────────────────┘

3. 前向扩散过程详解

3.1 什么是前向扩散?

前向扩散过程就是逐步给图像加噪声的过程。

数学定义:

给定原始图像 x_0

第 t 步的图像:x_t = √(α_t) · x_0 + √(1-α_t) · ε

其中:
- α_t:噪声调度参数(随t递减)
- ε ~ N(0, I):标准高斯噪声
- t ∈ [0, T]:时间步(通常T=1000)

3.2 噪声调度(Noise Schedule)

什么是噪声调度?

噪声调度决定了每一步加多少噪声

常用调度:线性调度(Linear Schedule)

β_t = β_min + (β_max - β_min) · t/T

参数设置:
β_min = 0.0001(开始加很少的噪声)
β_max = 0.02  (结束加很多噪声)

关系:
α_t = 1 - β_t
ᾱ_t = ∏(1 to t) α_i  (累积乘积)

可视化:
α_t 随时间递减
  1.0 │╲
      │ ╲
  0.5 │  ╲___
      │      ╲___
  0.0 └──────────╲_____→ t
      0   250   500   750  1000
      
前期变化慢(保留更多信息)
后期变化快(快速变成噪声)

3.3 前向过程的性质

性质1:马尔可夫性质

q(x_t | x_{t-1}, x_{t-2}, ..., x_0) = q(x_t | x_{t-1})

意思:
当前时刻只依赖前一时刻,不依赖更早的历史

类比:
就像多米诺骨牌,每块牌只推倒下一块

性质2:一步直达公式

重要性质:可以直接从 x_0 计算任意 x_t

x_t = √(ᾱ_t) · x_0 + √(1-ᾱ_t) · ε

不需要逐步计算!

意义:
训练时可以随机采样任意时间步
不需要顺序计算所有步骤

3.4 前向过程的代码实现

import torch
import numpy as np

class ForwardDiffusion:
    def __init__(self, T=1000):
        """
        初始化前向扩散过程
        
        Args:
            T: 总时间步数
        """
        self.T = T
        
        # 定义 β 调度(线性)
        self.betas = torch.linspace(0.0001, 0.02, T)
        
        # 计算 α 和 ᾱ
        self.alphas = 1 - self.betas
        self.alphas_cumprod = torch.cumprod(self.alphas, dim=0)
        
        # 计算相关系数(用于后续计算)
        self.sqrt_alphas_cumprod = torch.sqrt(self.alphas_cumprod)
        self.sqrt_one_minus_alphas_cumprod = torch.sqrt(1 - self.alphas_cumprod)
    
    def forward(self, x_0, t):
        """
        前向扩散:从 x_0 生成 x_t
        
        Args:
            x_0: 原始图像 [batch, C, H, W]
            t: 时间步 [batch]
        
        Returns:
            x_t: 加噪后的图像
            noise: 添加的噪声(用于训练)
        """
        # 生成随机噪声
        noise = torch.randn_like(x_0)
        
        # 获取对应时间步的系数
        sqrt_alpha = self.sqrt_alphas_cumprod[t].reshape(-1, 1, 1, 1)
        sqrt_one_minus_alpha = self.sqrt_one_minus_alphas_cumprod[t].reshape(-1, 1, 1, 1)
        
        # 应用公式: x_t = √ᾱ_t · x_0 + √(1-ᾱ_t) · ε
        x_t = sqrt_alpha * x_0 + sqrt_one_minus_alpha * noise
        
        return x_t, noise


# 使用示例
if __name__ == "__main__":
    # 创建前向扩散
    diffusion = ForwardDiffusion(T=1000)
    
    # 假设有一张图像
    x_0 = torch.randn(4, 3, 64, 64)  # [batch=4, channels=3, H=64, W=64]
    
    # 在不同时间步加噪
    for t in [0, 250, 500, 750, 999]:
        t_tensor = torch.tensor([t] * 4)  # 批量处理
        x_t, noise = diffusion.forward(x_0, t_tensor)
        
        print(f"t={t:4d}, 信号强度: {x_t.mean():.4f}, "
              f"噪声强度: {noise.std():.4f}")

3.5 前向过程的可视化

【示例:一张猫的图片的扩散过程】

t=0       清晰的猫
          🐱 完全清晰
          
t=100     轻微噪声
          🐱▒ 基本清晰,稍有颗粒感
          
t=250     可见噪声
          🐱▒▒ 能看出是猫,但有明显噪声
          
t=500     严重噪声
          ▒▒▒▒ 勉强能看出轮廓
          
t=750     几乎消失
          ▒▒▒░ 很难辨认
          
t=1000    纯噪声
          ░░░░ 完全看不出原图

数学角度:
信号(原图)的贡献:√ᾱ_t → 0
噪声的贡献:√(1-ᾱ_t) → 1

4. 反向去噪过程详解

4.1 反向过程的目标

反向过程的目标:学习如何从噪声逐步恢复图像

目标:学习反向转移概率

p_θ(x_{t-1} | x_t)

意思:
给定 t 时刻的噪声图像 x_t
预测 t-1 时刻的图像 x_{t-1}

类比:
就像学习"时光倒流"
每一步都让图像更清晰一点

4.2 去噪的数学建模

核心假设:反向过程也是高斯分布

p_θ(x_{t-1} | x_t) = N(x_{t-1}; μ_θ(x_t, t), Σ_θ(x_t, t))

简化版本(DDPM):方差固定,只预测均值

μ_θ(x_t, t) = (1/√α_t) · (x_t - (β_t/√(1-ᾱ_t)) · ε_θ(x_t, t))

其中:
- ε_θ(x_t, t):神经网络预测的噪声
- μ_θ:预测的均值(去噪后的图像)

4.3 神经网络的作用

神经网络要做什么?

输入:
  x_t:当前噪声图像
  t:  当前时间步

输出:
  ε_θ:预测的噪声

为什么预测噪声而不是预测图像?

答:数学上等价,但预测噪声更稳定!

推导:
已知:x_t = √ᾱ_t · x_0 + √(1-ᾱ_t) · ε
则:  x_0 = (x_t - √(1-ᾱ_t) · ε) / √ᾱ_t

所以:
预测噪声 ε → 可以恢复原图 x_0

4.4 去噪步骤详解

单步去噪算法:

输入:x_t, t
输出:x_{t-1}

Step 1: 用神经网络预测噪声
  ε_pred = ε_θ(x_t, t)

Step 2: 估计原始图像
  x_0_pred = (x_t - √(1-ᾱ_t) · ε_pred) / √ᾱ_t

Step 3: 计算前一时刻的均值
  μ = (√α_t · (1-ᾱ_{t-1}) · x_t + √ᾱ_{t-1} · β_t · x_0_pred) / (1-ᾱ_t)
  
Step 4: 添加少量噪声(除了最后一步)
  if t > 1:
      z = N(0, I)
      x_{t-1} = μ + √σ_t · z
  else:
      x_{t-1} = μ

返回:x_{t-1}

4.5 完整去噪代码

import torch
import torch.nn as nn

class ReverseDiffusion:
    def __init__(self, model, forward_diffusion):
        """
        反向去噪过程
        
        Args:
            model: 预测噪声的神经网络
            forward_diffusion: 前向扩散对象
        """
        self.model = model
        self.forward_diffusion = forward_diffusion
        self.T = forward_diffusion.T
        
        # 复制必要的参数
        self.betas = forward_diffusion.betas
        self.alphas = forward_diffusion.alphas
        self.alphas_cumprod = forward_diffusion.alphas_cumprod
    
    @torch.no_grad()
    def denoise_step(self, x_t, t):
        """
        单步去噪
        
        Args:
            x_t: 当前噪声图像 [batch, C, H, W]
            t: 当前时间步(标量)
        
        Returns:
            x_{t-1}: 去噪后的图像
        """
        batch_size = x_t.shape[0]
        t_batch = torch.full((batch_size,), t, dtype=torch.long)
        
        # Step 1: 预测噪声
        noise_pred = self.model(x_t, t_batch)
        
        # 获取参数
        alpha_t = self.alphas[t]
        alpha_cumprod_t = self.alphas_cumprod[t]
        beta_t = self.betas[t]
        
        if t > 0:
            alpha_cumprod_t_prev = self.alphas_cumprod[t-1]
        else:
            alpha_cumprod_t_prev = torch.tensor(1.0)
        
        # Step 2: 计算 x_0 的估计
        sqrt_recip_alpha_cumprod = 1 / torch.sqrt(alpha_cumprod_t)
        sqrt_recip_minus_one = torch.sqrt(1 / alpha_cumprod_t - 1)
        
        x_0_pred = sqrt_recip_alpha_cumprod * x_t - sqrt_recip_minus_one * noise_pred
        
        # Step 3: 计算 x_{t-1} 的均值
        coef1 = beta_t * torch.sqrt(alpha_cumprod_t_prev) / (1 - alpha_cumprod_t)
        coef2 = (1 - alpha_cumprod_t_prev) * torch.sqrt(alpha_t) / (1 - alpha_cumprod_t)
        
        mu = coef1 * x_0_pred + coef2 * x_t
        
        # Step 4: 添加噪声(除了最后一步)
        if t > 0:
            # 计算方差
            sigma_t = torch.sqrt(beta_t * (1 - alpha_cumprod_t_prev) / (1 - alpha_cumprod_t))
            # 添加噪声
            noise = torch.randn_like(x_t)
            x_t_minus_1 = mu + sigma_t * noise
        else:
            x_t_minus_1 = mu
        
        return x_t_minus_1
    
    @torch.no_grad()
    def sample(self, shape, device='cpu'):
        """
        完整采样过程:从纯噪声生成图像
        
        Args:
            shape: 生成图像的形状 [batch, C, H, W]
            device: 设备
        
        Returns:
            生成的图像
        """
        # 从纯噪声开始
        x_t = torch.randn(shape, device=device)
        
        # 逐步去噪
        for t in reversed(range(self.T)):
            x_t = self.denoise_step(x_t, t)
            
            if t % 100 == 0:
                print(f"去噪进度: t={t}/{self.T}")
        
        return x_t


# 使用示例
if __name__ == "__main__":
    # 假设我们有一个训练好的模型
    from model import UNet  # 假设的UNet模型
    
    model = UNet(channels=3, time_emb_dim=256)
    model.eval()
    
    forward_diffusion = ForwardDiffusion(T=1000)
    reverse_diffusion = ReverseDiffusion(model, forward_diffusion)
    
    # 生成图像
    generated_images = reverse_diffusion.sample(shape=(4, 3, 64, 64))
    
    print(f"生成图像形状: {generated_images.shape}")
    print(f"像素值范围: [{generated_images.min():.2f}, {generated_images.max():.2f}]")

5. 数学原理深入分析

5.1 DDPM的损失函数

训练目标:让神经网络学会预测噪声

简化损失函数:

L_simple = E_t,x_0,ε [ ||ε - ε_θ(x_t, t)||² ]

展开说明:
- E:期望(平均)
- t:随机采样的时间步
- x_0:原始图像
- ε:真实添加的噪声
- ε_θ(x_t, t):网络预测的噪声

意思:
让预测的噪声尽可能接近真实噪声
这是一个简单的均方误差(MSE)!

5.2 为什么这个损失函数有效?

从变分下界(ELBO)推导:

完整目标:最大化 log p_θ(x_0)

变分下界:
log p_θ(x_0) ≥ E_q [ log p_θ(x_0|x_1) - Σ D_KL(q(x_t|x_{t-1}) || p_θ(x_{t-1}|x_t)) ]

其中 D_KL 是KL散度(衡量两个分布的差异)

关键发现(DDPM论文):
最小化 KL 散度 ≈ 最小化噪声预测误差

因此简化为:
L = ||ε - ε_θ(x_t, t)||²

这就是为什么我们用简单的MSE损失!

5.3 不同时间步的加权

完整损失函数:

L = E_t [ w_t · ||ε - ε_θ(x_t, t)||² ]

其中 w_t 是权重

DDPM 的选择:
w_t = 1(所有时间步权重相同)

Improved DDPM 的选择:
w_t = 1 / (1 - ᾱ_t)
(后期时间步权重更大)

权重的意义:
- 平衡不同时间步的学习
- 避免某些时间步主导训练

5.4 扩散过程的数学性质

性质1:前向过程是确定的

给定 x_0 和噪声调度
x_t 完全确定(添加高斯噪声)

数学表达:
q(x_t | x_0) = N(x_t; √ᾱ_t · x_0, (1-ᾱ_t)I)

意义:
训练时可以直接计算任意 x_t
不需要逐步forward

性质2:反向过程的后验分布

理论上的最优反向步骤:

q(x_{t-1} | x_t, x_0) = N(x_{t-1}; μ̃_t, σ̃_t²I)

其中:
μ̃_t = (√ᾱ_{t-1} · β_t · x_0 + √α_t · (1-ᾱ_{t-1}) · x_t) / (1-ᾱ_t)

问题:
需要知道 x_0(未知!)

解决:
用神经网络预测,估计 x_0

性质3:扩散极限(t→∞)

当 t 足够大:
x_t → N(0, I)(标准高斯分布)

意思:
最终图像完全变成噪声
与原图无关

这就是为什么生成时从纯噪声开始!

6. 训练过程完全解析

6.1 训练算法伪代码

DDPM 训练算法:

输入:
  数据集 D = {x_0^(1), x_0^(2), ..., x_0^(N)}
  噪声调度 β_1, ..., β_T
  神经网络 ε_θ

训练循环:
  repeat until convergence:
    1. 从数据集采样:x_0 ~ D
    
    2. 随机采样时间步:t ~ Uniform(1, T)
    
    3. 采样噪声:ε ~ N(0, I)
    
    4. 计算 x_t:
       x_t = √ᾱ_t · x_0 + √(1-ᾱ_t) · ε
    
    5. 预测噪声:
       ε_pred = ε_θ(x_t, t)
    
    6. 计算损失:
       L = ||ε - ε_pred||²
    
    7. 反向传播更新参数:
       θ ← θ - lr · ∇_θ L

6.2 完整训练代码

import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader
from tqdm import tqdm

class DDPMTrainer:
    def __init__(self, model, forward_diffusion, device='cpu'):
        """
        DDPM 训练器
        
        Args:
            model: 噪声预测网络
            forward_diffusion: 前向扩散对象
            device: 训练设备
        """
        self.model = model.to(device)
        self.forward_diffusion = forward_diffusion
        self.device = device
        self.T = forward_diffusion.T
    
    def train_step(self, x_0):
        """
        单步训练
        
        Args:
            x_0: 原始图像 [batch, C, H, W]
        
        Returns:
            loss: 损失值
        """
        batch_size = x_0.shape[0]
        x_0 = x_0.to(self.device)
        
        # 1. 随机采样时间步
        t = torch.randint(0, self.T, (batch_size,), device=self.device)
        
        # 2. 前向扩散(加噪)
        x_t, noise = self.forward_diffusion.forward(x_0, t)
        
        # 3. 预测噪声
        noise_pred = self.model(x_t, t)
        
        # 4. 计算损失(MSE)
        loss = F.mse_loss(noise_pred, noise)
        
        return loss
    
    def train(self, dataloader, epochs, optimizer, save_path='model.pt'):
        """
        完整训练循环
        
        Args:
            dataloader: 数据加载器
            epochs: 训练轮数
            optimizer: 优化器
            save_path: 模型保存路径
        """
        self.model.train()
        
        for epoch in range(epochs):
            epoch_loss = 0.0
            progress_bar = tqdm(dataloader, desc=f'Epoch {epoch+1}/{epochs}')
            
            for batch_idx, (images, _) in enumerate(progress_bar):
                # 训练步骤
                optimizer.zero_grad()
                loss = self.train_step(images)
                loss.backward()
                optimizer.step()
                
                # 记录损失
                epoch_loss += loss.item()
                progress_bar.set_postfix({'loss': loss.item()})
            
            # 打印每个epoch的平均损失
            avg_loss = epoch_loss / len(dataloader)
            print(f'Epoch {epoch+1}, Average Loss: {avg_loss:.4f}')
            
            # 定期保存模型
            if (epoch + 1) % 10 == 0:
                torch.save(self.model.state_dict(), save_path)
                print(f'Model saved to {save_path}')


# 使用示例
if __name__ == "__main__":
    from torchvision import datasets, transforms
    from model import UNet
    
    # 1. 准备数据
    transform = transforms.Compose([
        transforms.Resize(64),
        transforms.CenterCrop(64),
        transforms.ToTensor(),
        transforms.Normalize([0.5], [0.5])  # 归一化到[-1, 1]
    ])
    
    dataset = datasets.CIFAR10(
        root='./data',
        train=True,
        transform=transform,
        download=True
    )
    
    dataloader = DataLoader(
        dataset,
        batch_size=128,
        shuffle=True,
        num_workers=4
    )
    
    # 2. 创建模型
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    model = UNet(channels=3, time_emb_dim=256)
    forward_diffusion = ForwardDiffusion(T=1000)
    
    # 3. 创建训练器
    trainer = DDPMTrainer(model, forward_diffusion, device=device)
    
    # 4. 训练
    optimizer = torch.optim.Adam(model.parameters(), lr=2e-4)
    trainer.train(dataloader, epochs=100, optimizer=optimizer)

6.3 训练技巧

技巧1:学习率调度

使用 Warmup + Cosine Decay:
- 前10%步数:线性增加学习率(warmup)
- 后90%步数:余弦衰减

from torch.optim.lr_scheduler import CosineAnnealingLR

scheduler = CosineAnnealingLR(optimizer, T_max=epochs)


技巧2:EMA(指数移动平均)

维护模型参数的移动平均:
θ_ema = momentum · θ_ema + (1-momentum) · θ

生成时使用EMA参数(效果更好)

class EMA:
    def __init__(self, model, decay=0.9999):
        self.model = model
        self.decay = decay
        self.shadow = {}
        for name, param in model.named_parameters():
            self.shadow[name] = param.data.clone()
    
    def update(self):
        for name, param in self.model.named_parameters():
            self.shadow[name] = (
                self.decay * self.shadow[name] +
                (1 - self.decay) * param.data
            )


技巧3:梯度裁剪

防止梯度爆炸:
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)


技巧4:混合精度训练

使用 FP16 加速训练:
from torch.cuda.amp import autocast, GradScaler

scaler = GradScaler()

with autocast():
    loss = train_step(images)
    
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()

6.4 训练监控

监控指标:

1. 训练损失(Loss)
   - 应该持续下降
   - 收敛到较小值(<0.1)

2. 采样质量
   - 每N个epoch生成样本
   - 目视检查质量

3. FID分数(Fréchet Inception Distance)
   - 衡量生成图像与真实图像的相似度
   - 越低越好

4. IS分数(Inception Score)
   - 衡量生成图像的质量和多样性
   - 越高越好

代码示例:

import matplotlib.pyplot as plt

def visualize_training(trainer, reverse_diffusion, epoch):
    # 生成样本
    samples = reverse_diffusion.sample((16, 3, 64, 64))
    
    # 可视化
    fig, axes = plt.subplots(4, 4, figsize=(12, 12))
    for i, ax in enumerate(axes.flat):
        img = samples[i].permute(1, 2, 0).cpu().numpy()
        img = (img + 1) / 2  # 反归一化
        ax.imshow(img)
        ax.axis('off')
    
    plt.savefig(f'samples_epoch_{epoch}.png')
    plt.close()

7. 采样生成过程

7.1 DDPM采样算法

DDPM 采样算法(完整版):

输入:
  噪声调度 β_1, ..., β_T
  训练好的模型 ε_θ

采样过程:
  1. 初始化:x_T ~ N(0, I)
  
  2. for t = T, T-1, ..., 1:
       a. 预测噪声:
          ε = ε_θ(x_t, t)
       
       b. 估计 x_0:
          x̂_0 = (x_t - √(1-ᾱ_t) · ε) / √ᾱ_t
       
       c. 计算均值:
          μ_t = (√ᾱ_{t-1} · β_t · x̂_0 + √α_t · (1-ᾱ_{t-1}) · x_t) / (1-ᾱ_t)
       
       d. 计算方差:
          σ_t = √(β_t · (1-ᾱ_{t-1}) / (1-ᾱ_t))
       
       e. 采样:
          if t > 1:
              z ~ N(0, I)
              x_{t-1} = μ_t + σ_t · z
          else:
              x_{t-1} = μ_t
  
  3. 返回:x_0

7.2 DDIM采样(加速版)

DDIM(Denoising Diffusion Implicit Models):

关键思想:
DDPM需要1000步采样(慢!)
DDIM可以跳过很多步(快!)

DDIM采样公式:

x_{t-Δt} = √ᾱ_{t-Δt} · x̂_0 + √(1-ᾱ_{t-Δt}) · ε

其中:
- Δt:步长(可以>1)
- x̂_0:估计的原始图像

优势:
- 可以用50步甚至20步生成
- 质量几乎不损失
- 速度提升20-50倍!

确定性vs随机性:
- DDPM:随机采样(每次生成不同)
- DDIM:确定性(相同噪声→相同图像)

7.3 DDIM代码实现

class DDIMSampler:
    def __init__(self, model, forward_diffusion, ddim_steps=50):
        """
        DDIM 采样器
        
        Args:
            model: 训练好的噪声预测网络
            forward_diffusion: 前向扩散对象
            ddim_steps: DDIM采样步数(远小于训练步数)
        """
        self.model = model
        self.forward_diffusion = forward_diffusion
        self.ddim_steps = ddim_steps
        self.T = forward_diffusion.T
        
        # 选择采样的时间步(均匀间隔)
        self.timesteps = torch.linspace(0, self.T-1, ddim_steps, dtype=torch.long)
    
    @torch.no_grad()
    def sample(self, shape, device='cpu', eta=0.0):
        """
        DDIM 采样
        
        Args:
            shape: 生成图像形状 [batch, C, H, W]
            device: 设备
            eta: 随机性参数(0=完全确定性,1=DDPM随机性)
        
        Returns:
            生成的图像
        """
        # 从纯噪声开始
        x_t = torch.randn(shape, device=device)
        
        # 逆序遍历时间步
        timesteps = reversed(self.timesteps)
        
        for i, t in enumerate(timesteps):
            # 当前和前一个时间步
            t_current = t
            t_prev = self.timesteps[-(i+2)] if i < len(self.timesteps)-1 else torch.tensor(0)
            
            # 预测噪声
            t_batch = t_current.repeat(shape[0]).to(device)
            noise_pred = self.model(x_t, t_batch)
            
            # 获取alpha值
            alpha_t = self.forward_diffusion.alphas_cumprod[t_current]
            alpha_t_prev = self.forward_diffusion.alphas_cumprod[t_prev] if t_prev >= 0 else torch.tensor(1.0)
            
            # 估计x_0
            sqrt_one_minus_alpha_t = torch.sqrt(1 - alpha_t)
            x_0_pred = (x_t - sqrt_one_minus_alpha_t * noise_pred) / torch.sqrt(alpha_t)
            
            # 计算方向指向x_t
            dir_xt = torch.sqrt(1 - alpha_t_prev - eta**2 * (1-alpha_t_prev)/(1-alpha_t) * (1-alpha_t/alpha_t_prev)) * noise_pred
            
            # DDIM更新
            x_t = torch.sqrt(alpha_t_prev) * x_0_pred + dir_xt
            
            # 可选:添加随机性(eta > 0)
            if eta > 0 and t_prev > 0:
                sigma_t = eta * torch.sqrt((1-alpha_t_prev)/(1-alpha_t)) * torch.sqrt(1-alpha_t/alpha_t_prev)
                noise = torch.randn_like(x_t)
                x_t += sigma_t * noise
        
        return x_t


# 使用示例
if __name__ == "__main__":
    model = UNet(channels=3, time_emb_dim=256)
    model.load_state_dict(torch.load('model.pt'))
    model.eval()
    
    forward_diffusion = ForwardDiffusion(T=1000)
    
    # DDIM采样(50步)
    ddim_sampler = DDIMSampler(model, forward_diffusion, ddim_steps=50)
    samples = ddim_sampler.sample((4, 3, 64, 64), device='cuda')
    
    print(f"生成完成!形状: {samples.shape}")
    
    # 对比:DDPM需要1000步,DDIM只需要50步
    # 速度提升 20倍!

7.4 采样加速技术对比

┌──────────────────────────────────────────────────────────┐
│              采样方法对比                                 │
├──────────────────────────────────────────────────────────┤
│                                                           │
│  方法       │ 步数  │ 生成时间 │ 质量    │ 确定性       │
│  ──────────┼──────┼─────────┼────────┼──────────────│
│  DDPM      │ 1000 │ ~100秒  │ 最高    │ 随机          │
│  DDIM-50   │  50  │ ~5秒    │ 很高    │ 可控          │
│  DDIM-20   │  20  │ ~2秒    │ 高      │ 可控          │
│  DPM-Solver│  20  │ ~2秒    │ 很高    │ 可控          │
│  PNDM      │  50  │ ~5秒    │ 很高    │ 可控          │
│                                                           │
└──────────────────────────────────────────────────────────┘

推荐:
- 质量优先:DDPM(1000步)
- 平衡:DDIM-50
- 速度优先:DDIM-20 或 DPM-Solver

8. 扩散模型的变体

8.1 Latent Diffusion Model(LDM)

核心思想:
不在像素空间做扩散,而在潜在空间(Latent Space)

架构:
原图 → 编码器 → 潜在表示 → 扩散过程 → 解码器 → 生成图

优势:
✅ 降低计算成本(潜在空间维度更小)
✅ 加快训练和采样速度
✅ 保持高质量

Stable Diffusion 就是LDM!

可视化:
【像素空间扩散】
512×512×3 = 786,432 维
↓ 计算量巨大

【潜在空间扩散】
512×512×3 → 编码器 → 64×64×4 = 16,384 维
↓ 计算量减少 48倍!

8.2 Conditional Diffusion(条件扩散)

问题:
DDPM只能随机生成
如何控制生成内容?

解决:Conditional Diffusion

方法1:Classifier Guidance(分类器引导)
  训练一个分类器 p(y|x_t)
  采样时沿着梯度方向移动
  
  x_{t-1} = μ + σ · (ε - s·∇log p(y|x_t))
  
  s:引导强度(越大越符合条件)

方法2:Classifier-Free Guidance(无分类器引导)
  同时训练条件模型和无条件模型
  
  ε_guided = ε_uncond + w · (ε_cond - ε_uncond)
  
  w:引导权重
  
  优势:
  - 不需要额外训练分类器
  - 效果更好
  - Stable Diffusion 使用此方法

条件类型:
- 类别标签("猫"、"狗")
- 文本描述("一只在草地上的猫")
- 图像(图像到图像)
- 分割图(语义控制)

8.3 Score-Based Models

核心思想:
学习数据分布的分数(梯度)

分数函数:
s_θ(x_t, t) = ∇_x log p_t(x_t)

关系:
分数函数 ≈ 噪声预测(差一个负号和缩放)

s_θ(x_t, t) ≈ -ε_θ(x_t, t) / √(1-ᾱ_t)

统一框架:
Score-based Models 和 Diffusion Models 本质相同
只是不同的数学视角

9. 条件生成与引导

9.1 文本到图像生成(Text-to-Image)

目标:根据文本描述生成图像

输入:"一只戴墨镜的猫在海滩上"
输出:对应的图像 🐱🕶️🏖️

关键技术:CLIP

CLIP(Contrastive Language-Image Pre-training):
- OpenAI 训练的模型
- 将文本和图像映射到同一空间
- 衡量文本-图像匹配度

架构:
文本 → CLIP文本编码器 → 文本embedding
                ↓
        交叉注意力机制
                ↓
噪声图 → UNet → 预测噪声

9.2 无分类器引导代码

class ClassifierFreeGuidance:
    def __init__(self, model, text_encoder, p_uncond=0.1):
        """
        无分类器引导
        
        Args:
            model: 条件扩散模型
            text_encoder: 文本编码器(如CLIP)
            p_uncond: 训练时无条件的概率
        """
        self.model = model
        self.text_encoder = text_encoder
        self.p_uncond = p_uncond
    
    def train_step(self, x_0, text):
        """
        训练步骤
        
        Args:
            x_0: 原始图像
            text: 文本描述
        
        Returns:
            loss: 损失
        """
        # 随机决定是否使用条件
        use_condition = torch.rand(1).item() > self.p_uncond
        
        # 编码文本
        if use_condition:
            text_emb = self.text_encoder(text)
        else:
            text_emb = None  # 无条件
        
        # 前向扩散
        t = torch.randint(0, self.T, (x_0.shape[0],))
        x_t, noise = self.forward_diffusion(x_0, t)
        
        # 预测噪声
        noise_pred = self.model(x_t, t, text_emb)
        
        # 计算损失
        loss = F.mse_loss(noise_pred, noise)
        
        return loss
    
    @torch.no_grad()
    def sample_with_guidance(self, text, guidance_scale=7.5):
        """
        使用引导采样
        
        Args:
            text: 文本描述
            guidance_scale: 引导强度(典型值:7.5)
        
        Returns:
            生成的图像
        """
        # 编码文本
        text_emb = self.text_encoder(text)
        
        # 初始化噪声
        x_t = torch.randn(1, 3, 64, 64)
        
        for t in reversed(range(self.T)):
            t_tensor = torch.tensor([t])
            
            # 条件预测
            noise_cond = self.model(x_t, t_tensor, text_emb)
            
            # 无条件预测
            noise_uncond = self.model(x_t, t_tensor, None)
            
            # 引导
            noise_pred = noise_uncond + guidance_scale * (noise_cond - noise_uncond)
            
            # 去噪一步
            x_t = self.denoise_step(x_t, t, noise_pred)
        
        return x_t


# 使用示例
if __name__ == "__main__":
    model = ConditionalUNet()
    text_encoder = CLIPTextEncoder()
    
    cfg = ClassifierFreeGuidance(model, text_encoder)
    
    # 生成图像
    prompt = "一只戴墨镜的猫在海滩上"
    image = cfg.sample_with_guidance(prompt, guidance_scale=7.5)
    
    # 保存
    save_image(image, 'generated.png')

9.3 ControlNet(精确控制)

ControlNet:
在Stable Diffusion基础上添加精确控制

控制类型:
1. 边缘图(Canny Edge)
2. 深度图(Depth Map)
3. 姿态图(Pose Skeleton)
4. 分割图(Segmentation Map)
5. 涂鸦(Scribble)

原理:
添加额外的编码器
复制UNet的参数
通过零卷积连接

优势:
- 精确控制生成内容
- 不影响原模型质量
- 可以组合多个控制

示例:
输入:姿态图 + 文本"一个穿红裙子的女孩"
输出:完全符合姿态的女孩图像

10. 实战代码实现

10.1 UNet架构实现

import torch
import torch.nn as nn
import math

class SinusoidalPositionEmbedding(nn.Module):
    """时间步的正弦位置编码"""
    def __init__(self, dim):
        super().__init__()
        self.dim = dim
    
    def forward(self, time):
        device = time.device
        half_dim = self.dim // 2
        embeddings = math.log(10000) / (half_dim - 1)
        embeddings = torch.exp(torch.arange(half_dim, device=device) * -embeddings)
        embeddings = time[:, None] * embeddings[None, :]
        embeddings = torch.cat((embeddings.sin(), embeddings.cos()), dim=-1)
        return embeddings


class ResidualBlock(nn.Module):
    """残差块"""
    def __init__(self, in_channels, out_channels, time_emb_dim):
        super().__init__()
        
        self.conv1 = nn.Conv2d(in_channels, out_channels, 3, padding=1)
        self.conv2 = nn.Conv2d(out_channels, out_channels, 3, padding=1)
        
        self.time_mlp = nn.Linear(time_emb_dim, out_channels)
        
        self.norm1 = nn.GroupNorm(8, out_channels)
        self.norm2 = nn.GroupNorm(8, out_channels)
        
        if in_channels != out_channels:
            self.shortcut = nn.Conv2d(in_channels, out_channels, 1)
        else:
            self.shortcut = nn.Identity()
    
    def forward(self, x, t):
        h = self.conv1(x)
        h = self.norm1(h)
        h = h + self.time_mlp(t)[:, :, None, None]
        h = torch.relu(h)
        
        h = self.conv2(h)
        h = self.norm2(h)
        h = torch.relu(h)
        
        return h + self.shortcut(x)


class AttentionBlock(nn.Module):
    """自注意力块"""
    def __init__(self, channels):
        super().__init__()
        self.channels = channels
        
        self.norm = nn.GroupNorm(8, channels)
        self.qkv = nn.Conv2d(channels, channels * 3, 1)
        self.proj = nn.Conv2d(channels, channels, 1)
    
    def forward(self, x):
        B, C, H, W = x.shape
        
        h = self.norm(x)
        qkv = self.qkv(h)
        q, k, v = torch.chunk(qkv, 3, dim=1)
        
        # 重塑为注意力格式
        q = q.reshape(B, C, H*W).permute(0, 2, 1)
        k = k.reshape(B, C, H*W)
        v = v.reshape(B, C, H*W).permute(0, 2, 1)
        
        # 计算注意力
        attn = torch.bmm(q, k) / math.sqrt(C)
        attn = torch.softmax(attn, dim=-1)
        
        # 应用注意力
        out = torch.bmm(attn, v)
        out = out.permute(0, 2, 1).reshape(B, C, H, W)
        
        out = self.proj(out)
        
        return x + out


class UNet(nn.Module):
    """完整的UNet架构"""
    def __init__(self, in_channels=3, out_channels=3, time_emb_dim=256):
        super().__init__()
        
        # 时间嵌入
        self.time_mlp = nn.Sequential(
            SinusoidalPositionEmbedding(time_emb_dim),
            nn.Linear(time_emb_dim, time_emb_dim * 4),
            nn.GELU(),
            nn.Linear(time_emb_dim * 4, time_emb_dim),
        )
        
        # 下采样
        self.down1 = ResidualBlock(in_channels, 64, time_emb_dim)
        self.down2 = ResidualBlock(64, 128, time_emb_dim)
        self.down3 = ResidualBlock(128, 256, time_emb_dim)
        self.down4 = ResidualBlock(256, 512, time_emb_dim)
        
        self.pool = nn.MaxPool2d(2)
        
        # 瓶颈
        self.bottleneck = nn.Sequential(
            ResidualBlock(512, 512, time_emb_dim),
            AttentionBlock(512),
            ResidualBlock(512, 512, time_emb_dim),
        )
        
        # 上采样
        self.up1 = ResidualBlock(512 + 512, 256, time_emb_dim)
        self.up2 = ResidualBlock(256 + 256, 128, time_emb_dim)
        self.up3 = ResidualBlock(128 + 128, 64, time_emb_dim)
        self.up4 = ResidualBlock(64 + 64, 64, time_emb_dim)
        
        self.upsample = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True)
        
        # 输出
        self.out = nn.Conv2d(64, out_channels, 1)
    
    def forward(self, x, time):
        # 时间嵌入
        t = self.time_mlp(time)
        
        # 下采样
        d1 = self.down1(x, t)
        d2 = self.down2(self.pool(d1), t)
        d3 = self.down3(self.pool(d2), t)
        d4 = self.down4(self.pool(d3), t)
        
        # 瓶颈
        b = self.bottleneck[0](self.pool(d4), t)
        b = self.bottleneck[1](b)
        b = self.bottleneck[2](b, t)
        
        # 上采样
        u1 = self.up1(torch.cat([self.upsample(b), d4], dim=1), t)
        u2 = self.up2(torch.cat([self.upsample(u1), d3], dim=1), t)
        u3 = self.up3(torch.cat([self.upsample(u2), d2], dim=1), t)
        u4 = self.up4(torch.cat([self.upsample(u3), d1], dim=1), t)
        
        return self.out(u4)


# 测试模型
if __name__ == "__main__":
    model = UNet(in_channels=3, out_channels=3)
    
    x = torch.randn(2, 3, 64, 64)
    t = torch.randint(0, 1000, (2,))
    
    out = model(x, t)
    
    print(f"输入形状: {x.shape}")
    print(f"输出形状: {out.shape}")
    print(f"参数量: {sum(p.numel() for p in model.parameters()):,}")

11. 实际应用场景

11.1 图像生成

应用:AI艺术创作

工具:
- Stable Diffusion
- Midjourney
- DALL-E 2

流程:
文本提示 → 扩散模型 → 生成图像

示例:
输入:"赛博朋克风格的城市夜景"
输出:高质量的未来城市图像

优势:
✅ 创作效率提升100倍
✅ 风格多样化
✅ 概念艺术、插画、设计

11.2 图像修复

应用:自动修复照片

技术:Inpainting(图像修补)

原理:
1. 遮罩需要修复的区域
2. 扩散模型根据周围内容填充

应用场景:
- 去除照片中的人或物体
- 扩展图像边界
- 修复老照片

示例:
输入:有水印的照片
处理:遮罩水印区域
输出:自然去除水印的照片

11.3 图像编辑

应用:智能图像编辑

技术:
1. Text-guided Editing(文本引导编辑)
2. Image-to-Image Translation

功能:
- 改变物体属性
- 替换背景
- 风格迁移

示例:
原图:白天的街道照片
文本:将其变为夜晚
输出:同一场景的夜景图

工具:
- Instruct-Pix2Pix
- DiffEdit
- ControlNet

11.4 医学图像

应用:医学影像增强和生成

用途:
1. 数据增强(生成训练数据)
2. 图像超分辨率
3. 跨模态生成(CT → MRI)

优势:
- 保护隐私(生成合成数据)
- 降低标注成本
- 辅助诊断

示例:
输入:低分辨率CT扫描
输出:高分辨率增强图像

11.5 视频生成

应用:文本到视频生成

技术:
- Temporal扩散模型
- 3D卷积UNet

挑战:
- 时间一致性
- 计算成本高
- 运动平滑性

工具:
- Runway Gen-2
- Pika Labs
- Stable Video Diffusion

示例:
输入:"一只猫在追逐激光笔"
输出:流畅的3秒视频

12. 常见问题解答

Q1: 扩散模型生成为什么这么慢?

答:因为需要多步采样

DDPM:1000步
每步:前向传播UNet

加速方法:
1. DDIM:减少到50步
2. Latent Diffusion:在低维空间
3. 蒸馏:训练单步生成模型
4. 更快的采样器(DPM-Solver)

对比:
GAN:一步生成(快!但质量差)
扩散模型:多步生成(慢,但质量高)

Q2: 扩散模型能做实时生成吗?

答:目前还很难,但有进展

当前状态:
- Stable Diffusion:3-5秒/张(GPU)
- LCM(Latent Consistency Model):<1秒
- Turbo模型:0.2秒

未来方向:
1. 模型蒸馏(单步生成)
2. 硬件优化(TensorRT)
3. 新架构(DiT - Diffusion Transformer)

实时应用:
目前主要用于非实时场景
实时视频还需要突破

Q3: 如何控制生成的内容?

答:多种控制方法

1. 文本提示(Text Prompts)
   详细描述 → 精确控制
   
2. Negative Prompts(负提示)
   避免不想要的内容
   
3. Classifier-Free Guidance
   调节引导强度
   
4. ControlNet
   结构级精确控制
   
5. LoRA(Low-Rank Adaptation)
   微调特定风格/角色

技巧:
- 提示词工程很重要
- 组合多种控制方法
- 调整采样参数

Q4: 扩散模型的计算成本如何?

答:取决于分辨率和步数

训练成本:
- 数据集:数百万张图像
- 硬件:8×A100 GPU
- 时间:数周到数月
- 成本:数万到数十万美元

推理成本:
- 单张图像:3-5秒(512×512)
- 显存:4-8GB
- 硬件:RTX 3060+

降低成本:
1. Latent Diffusion(降低48倍)
2. DDIM加速(降低20倍)
3. 量化(INT8)
4. 剪枝

Q5: 扩散模型vs GAN,哪个更好?

答:各有优劣

扩散模型优势:
✅ 训练稳定
✅ 生成质量高
✅ 多样性好
✅ 可扩展性强

扩散模型劣势:
❌ 生成慢
❌ 训练成本高
❌ 推理成本高

GAN优势:
✅ 生成快(一步)
✅ 推理成本低
✅ 实时应用

GAN劣势:
❌ 训练不稳定
❌ 模式崩溃
❌ 多样性差

选择:
- 质量优先:扩散模型
- 速度优先:GAN
- 大规模生成:扩散模型
- 实时应用:GAN

趋势:
扩散模型正在逐步取代GAN
但GAN在某些场景仍有优势

Q6: 如何提高生成质量?

答:多方面优化

1. 模型方面:
   - 增大模型(更多参数)
   - 更深的UNet
   - 注意力机制

2. 训练方面:
   - 更多数据
   - 更长训练时间
   - EMA(指数移动平均)

3. 采样方面:
   - 更多采样步数
   - 调整引导强度
   - 使用DDIM等高级采样器

4. 提示词:
   - 详细描述
   - 使用高质量提示词
   - Negative prompts

5. 后处理:
   - 超分辨率
   - 颜色校正
   - 细节增强

📚 总结与展望

核心要点回顾

✅ 扩散模型的三大核心:
1. 前向扩散(逐步加噪声)
2. 反向去噪(逐步恢复)
3. 神经网络(学习去噪)

✅ 数学原理:
- 马尔可夫链
- 高斯分布
- 变分推断

✅ 关键技术:
- DDPM(基础算法)
- DDIM(加速采样)
- Latent Diffusion(降维)
- Conditional Generation(条件控制)

✅ 实际应用:
- 图像生成(Stable Diffusion)
- 图像编辑(ControlNet)
- 视频生成
- 3D生成

学习路径建议

第1阶段:理论基础(1-2周)
├─ 理解前向和反向过程
├─ 掌握DDPM数学原理
└─ 阅读原论文

第2阶段:代码实践(2-3周)
├─ 实现简单的DDPM
├─ 训练小规模模型
└─ 实现DDIM采样

第3阶段:应用开发(4-8周)
├─ 使用Stable Diffusion
├─ Fine-tune模型
├─ LoRA训练
└─ ControlNet应用

第4阶段:深入研究(持续)
├─ 阅读最新论文
├─ 实现新变体
└─ 参与开源项目

推荐资源

经典论文:
1. DDPM (2020) - "Denoising Diffusion Probabilistic Models"
2. DDIM (2021) - "Denoising Diffusion Implicit Models"
3. Latent Diffusion (2022) - "High-Resolution Image Synthesis"
4. Classifier-Free Guidance (2022)

代码库:
1. Hugging Face Diffusers - 官方库
2. Stable Diffusion - 开源实现
3. CompVis/stable-diffusion - 原始仓库

教程:
1. Lilian Weng的博客
2. Yang Song的博客
3. Hugging Face教程

工具:
1. Stable Diffusion WebUI
2. ComfyUI
3. InvokeAI

未来发展方向

技术前沿:
1. 更快的采样(<10步)
2. 更高分辨率(8K+)
3. 视频生成(长视频)
4. 3D生成
5. 多模态(文本+图像+音频)

研究热点:
1. 扩散Transformer(DiT)
2. 一致性模型(Consistency Models)
3. Flow Matching
4. 连续时间扩散
5. 离散扩散(文本生成)

应用前沿:
1. 实时生成
2. 个性化定制
3. 专业工具集成
4. 移动端部署
5. 边缘计算

🎉祝你天天开心,我将更新更多有意思的内容,欢迎关注!

最后更新:2025年11月
作者:Echo

Logo

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

更多推荐