跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
PythonAI算法

扩散模型原理与基于 DDPM 的图像生成实战

扩散模型通过前向加噪与反向去噪的马尔可夫链过程实现数据生成,解决了传统 GAN 训练不稳定的问题。详细阐述了 DDPM 的数学原理,包括噪声调度公式与损失函数设计,并提供了基于 PyTorch 的完整代码实现。实战部分展示了如何在 MNIST 数据集上训练模型并进行图像采样,同时介绍了余弦调度、分类引导等优化技巧及 Stable Diffusion 等主流变体的应用场景。

氛围发布于 2026/3/21更新于 2026/10/2184 浏览
扩散模型原理与基于 DDPM 的图像生成实战

导读

扩散模型作为近年来最热门的生成式 AI 技术之一,凭借其稳定的训练过程和高质量的生成效果,正在逐步取代传统的 GAN。本文将深入解析其前向扩散与反向扩散机制,并基于 PyTorch 从零搭建一个 DDPM 模型,完成手写数字图像的生成实战。

扩散模型示意图

核心思想

为什么需要扩散模型

传统的生成对抗网络(GAN)常面临训练不稳定、模式崩溃等挑战。扩散模型则通过概率建模的方式,将复杂的生成问题拆解为多个简单的马尔可夫链步骤。它模仿非平衡热力学的过程,在图像、文本和语音合成等领域展现出了超越传统模型的表现。

基本框架

扩散模型包含两个核心过程:

  1. 前向扩散过程:从真实数据出发,逐步添加高斯噪声。经过 T 步后,数据会变成完全随机的噪声。
  2. 反向扩散过程:从随机噪声出发,训练神经网络逐步去除噪声。经过 T 步后,噪声还原为真实的数据分布。

整个过程遵循马尔可夫链假设,即每一步的状态只与前一步有关。

前向扩散过程详解

前向扩散是一个固定的、无需训练的过程。它的目标是通过逐步添加噪声,将真实图像 $x_0$ 转换为随机噪声 $x_T$。

数学原理

前向扩散过程的每一步,都会按照以下公式向图像中添加噪声:

$$ x_t = \sqrt{\alpha_t} x_{t-1} + \sqrt{1 - \alpha_t} \epsilon_t $$

其中:

  • $x_t$ 表示第 t 步添加噪声后的图像
  • $\alpha_t$ 是一个预先设定的噪声系数,满足 $0 < \alpha_t < 1$
  • $\epsilon_t$ 是服从标准正态分布的高斯噪声

为了计算方便,通常会定义累计乘积系数:

$$ \bar{\alpha}t = \prod{i=1}^t \alpha_i $$

通过累计系数,可以直接从 $x_0$ 计算出任意步的 $x_t$:

$$ x_t = \sqrt{\bar{\alpha}_t} x_0 + \sqrt{1 - \bar{\alpha}_t} \epsilon $$

⚠️ 注意:前向扩散的步数 T 是一个超参数。T 越大,前向扩散越充分,反向扩散的效果越好,但训练和生成的时间也会越长。

代码实现

import torch
import numpy as np
import matplotlib.pyplot as plt

# 定义扩散过程的超参数
T = 1000  # 扩散步数
beta_start = 0.0001  # 初始噪声系数
beta_end = 0.02  # 最终噪声系数

# 生成线性变化的 beta 序列
beta = torch.linspace(beta_start, beta_end, T)
alpha = 1 - beta
alpha_bar = torch.cumprod(alpha, dim=0)  # 累计乘积

# 前向扩散函数:从 x0 生成 xt
def forward_diffusion(x0, t, device):
    """
    x0: 原始图像 (batch_size, channels, height, width)
    t: 扩散步数 (batch_size,)
    """
    # 生成高斯噪声
    eps = torch.randn_like(x0).to(device)
    # 获取累计系数
    alpha_bar_t = alpha_bar[t].reshape(-1, 1, 1, 1).to(device)
    # 计算 xt
    xt = torch.sqrt(alpha_bar_t) * x0 + torch.sqrt(1 - alpha_bar_t) * eps
    return xt, eps

# 测试前向扩散过程
from torchvision.datasets import MNIST
from torchvision.transforms import ToTensor

dataset = MNIST(root='./data', train=True, download=True, transform=ToTensor())
x0, _ = dataset[0]
x0 = x0.unsqueeze(0)  # 增加 batch 维度 (1, 1, 28, 28)

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# 可视化不同步数的扩散效果
plt.figure(figsize=(15, 3))
for i, t in enumerate([0, 100, 200, 500, 800, 999]):
    xt, _ = forward_diffusion(x0, torch.tensor([t]), device)
    xt = xt.squeeze().cpu().detach().numpy()
    plt.subplot(1, 6, i + 1)
    plt.imshow(xt, cmap='gray')
    plt.title(f't={t}')
    plt.axis('off')
plt.show()

反向扩散过程与模型训练

数学原理

反向扩散是前向扩散的逆过程。目标是训练一个神经网络 $\epsilon_\theta$,从 $x_t$ 中预测出添加的噪声 $\epsilon$,然后逐步去除噪声,还原出 $x_0$。

反向扩散的核心公式为:

$$ p_\theta(x_{t-1}|x_t) = \mathcal{N}(x_{t-1}; \mu_\theta(x_t, t), \Sigma_\theta(x_t, t)) $$

其中,均值 $\mu_\theta$ 由神经网络预测的噪声计算得到,方差 $\Sigma_\theta$ 通常设置为固定值。

训练的目标是最小化预测噪声和真实噪声之间的均方误差:

$$ L(\theta) = \mathbb{E}{x_0, \epsilon, t}[|\epsilon - \epsilon\theta(x_t, t)|^2] $$

构建噪声预测网络

噪声预测网络通常采用 U-Net 架构。输入是 $x_t$ 和步数 t 的嵌入,输出是预测的噪声 $\epsilon_\theta$。

import torch.nn as nn
import torch.nn.functional as F

# 定义位置嵌入层:将步数 t 转换为高维向量
class PositionalEncoding(nn.Module):
    def __init__(self, dim):
        super().__init__()
        self.dim = dim

    def forward(self, t):
        # t: (batch_size,)
        device = t.device
        half_dim = self.dim // 2
        emb = np.log(10000) / (half_dim - 1)
        emb = torch.exp(torch.arange(half_dim, device=device) * -emb)
        emb = t[:, None] * emb[None, :]
        emb = torch.cat([torch.sin(emb), torch.cos(emb)], dim=-1)
        return emb

# 定义残差块
class ResidualBlock(nn.Module):
    def __init__(self, in_channels, out_channels, time_dim):
        super().__init__()
        self.conv1 = nn.Conv2d(in_channels, out_channels, 3, padding=1)
        self.bn1 = nn.BatchNorm2d(out_channels)
        self.conv2 = nn.Conv2d(out_channels, out_channels, 3, padding=1)
        self.bn2 = nn.BatchNorm2d(out_channels)
        self.time_mlp = nn.Linear(time_dim, out_channels)
        self.skip = nn.Conv2d(in_channels, out_channels, 1) if in_channels != out_channels else nn.Identity()

    def forward(self, x, t):
        # x: (batch_size, channels, h, w)
        # t: (batch_size, time_dim)
        h = F.relu(self.bn1(self.conv1(x)))
        # 添加时间嵌入
        h += self.time_mlp(t)[:, :, None, None]
        h = F.relu(self.bn2(self.conv2(h)))
        return h + self.skip(x)

# 定义噪声预测网络
class UNet(nn.Module):
    def __init__(self, in_channels=1, out_channels=1, time_dim=256):
        super().__init__()
        self.time_dim = time_dim
        self.pos_encoding = PositionalEncoding(time_dim)

        # 下采样路径
        self.down1 = ResidualBlock(in_channels, 64, time_dim)
        self.down2 = ResidualBlock(64, 128, time_dim)
        self.down3 = ResidualBlock(128, 256, time_dim)
        self.pool = nn.MaxPool2d(2)

        # 瓶颈层
        self.bottleneck = ResidualBlock(256, 256, time_dim)

        # 上采样路径
        self.up1 = nn.ConvTranspose2d(256, 128, 2, stride=2)
        self.res_up1 = ResidualBlock(256, 128, time_dim)
        self.up2 = nn.ConvTranspose2d(128, 64, 2, stride=2)
        self.res_up2 = ResidualBlock(128, 64, time_dim)

        # 输出层
        self.out = nn.Conv2d(64, out_channels, 1)

    def forward(self, x, t):
        # x: (batch_size, 1, 28, 28)
        # t: (batch_size,)
        # 位置编码
        t = self.pos_encoding(t)

        # 下采样
        h1 = self.down1(x, t)
        h2 = self.down2(self.pool(h1), t)
        h3 = self.down3(self.pool(h2), t)

        # 瓶颈层
        bottleneck = self.bottleneck(self.pool(h3), t)

        # 上采样
        up1 = self.up1(bottleneck)
        up1 = torch.cat([up1, h3], dim=1)
        up1 = self.res_up1(up1, t)
        
        up2 = self.up2(up1)
        up2 = torch.cat([up2, h2], dim=1)
        up2 = self.res_up2(up2, t)
        
        up3 = self.up2(up2)  # 修正:原逻辑此处重复调用 up2,保持结构一致
        up3 = torch.cat([up3, h1], dim=1)
        up3 = self.res_up2(up3, t)
        
        return self.out(up3)

# 初始化模型
model = UNet().to(device)
print(model)

模型训练流程

from torch.utils.data import DataLoader
from torch.optim import Adam

# 加载数据集
dataset = MNIST(root='./data', train=True, download=True, transform=ToTensor())
dataloader = DataLoader(dataset, batch_size=128, shuffle=True)

# 定义优化器
optimizer = Adam(model.parameters(), lr=1e-4)
criterion = nn.MSELoss()

# 训练函数
def train_epoch(model, dataloader, optimizer, criterion, device):
    model.train()
    total_loss = 0
    for x0, _ in dataloader:
        x0 = x0.to(device)
        batch_size = x0.shape[0]
        
        # 随机采样步数 t
        t = torch.randint(0, T, (batch_size,), device=device)
        
        # 前向扩散生成 xt 和真实噪声
        xt, eps_true = forward_diffusion(x0, t, device)
        
        # 模型预测噪声
        eps_pred = model(xt, t)
        
        # 计算损失
        loss = criterion(eps_pred, eps_true)
        
        # 反向传播
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        total_loss += loss.item()
    
    return total_loss / len(dataloader)

# 开始训练
epochs = 50
for epoch in range(epochs):
    loss = train_epoch(model, dataloader, optimizer, criterion, device)
    print(f"Epoch [{epoch+1}/{epochs}], Loss: {loss:.4f}")

# 保存模型
torch.save(model.state_dict(), 'ddpm_mnist.pth')

实战:基于 DDPM 的图像生成

反向扩散采样过程

训练完成后,我们可以从随机噪声出发,通过反向扩散过程逐步去除噪声,生成新的图像。

# 反向扩散采样函数
def sample(model, batch_size, device):
    model.eval()
    # 从随机噪声开始
    xt = torch.randn((batch_size, 1, 28, 28)).to(device)
    
    with torch.no_grad():
        for t in reversed(range(T, 0, -1)):  # 从 T 到 1
            # 生成当前步数的 tensor
            t_tensor = torch.tensor([t], device=device).repeat(batch_size)
            
            # 预测噪声
            eps_pred = model(xt, t_tensor)
            
            # 获取系数
            alpha_t = alpha[t].to(device)
            alpha_bar_t = alpha_bar[t].to(device)
            alpha_bar_t_1 = alpha_bar[t-1].to(device)
            beta_t = beta[t].to(device)
            
            # 计算均值
            mean = (1 / torch.sqrt(alpha_t)) * (xt - (beta_t / torch.sqrt(1 - alpha_bar_t)) * eps_pred)
            
            # 计算方差
            if t == 1:
                variance = 0
            else:
                variance = beta_t
            
            # 添加噪声
            z = torch.randn_like(xt).to(device) if t > 1 else torch.zeros_like(xt).to(device)
            xt = mean + torch.sqrt(variance) * z
    
    # 归一化到 [0,1]
    xt = torch.clamp(xt, 0, 1)
    return xt

# 生成图像
model.load_state_dict(torch.load('ddpm_mnist.pth'))
generated_images = sample(model, batch_size=16, device=device)

# 可视化生成结果
plt.figure(figsize=(8, 8))
for i in range(16):
    img = generated_images[i].squeeze().cpu().detach().numpy()
    plt.subplot(4, 4, i + 1)
    plt.imshow(img, cmap='gray')
    plt.axis('off')
plt.show()

模型优化技巧

在实际应用中,还可以尝试以下优化手段来提升生成质量:

  1. 使用余弦噪声调度:将 beta 的变化从线性改为余弦,可以平滑噪声分布,提升生成图像的质量。
  2. 添加分类引导:在生成过程中加入类别信息,实现指定类别的图像生成。
  3. 使用更大的网络架构:如 UNet++、Attention UNet 等,可以提升模型的特征捕捉能力。

扩散模型的发展与应用

经典变体

  • DDIM:Denoising Diffusion Implicit Models,通过简化反向扩散过程,大幅提升生成速度。
  • Stable Diffusion:基于潜在空间的扩散模型,在保持生成质量的同时,降低了计算资源消耗。
  • Latent Diffusion Models:在压缩的潜在空间中进行扩散,适用于高分辨率图像生成。

应用场景

  • 图像生成:生成艺术画作、人像、产品设计图等。
  • 图像修复:修复老照片、去除图像中的水印和瑕疵。
  • 文本到图像生成:根据文本描述生成对应的图像,如 Midjourney、DALL·E。
  • 语音合成:生成自然流畅的语音,提升语音合成的质量。

总结

扩散模型通过前向扩散和反向扩散两个过程,实现了稳定的生成模型训练,有效解决了 GAN 的训练不稳定问题。前向扩散是固定的噪声添加过程,反向扩散通过训练神经网络预测噪声,逐步还原真实数据。基于 DDPM 模型,可以实现手写数字等简单图像的生成,优化噪声调度和网络架构可进一步提升生成效果。目前,扩散模型的变体 Stable Diffusion 等已成为主流的图像生成工具,广泛应用于各类创意设计场景。

目录

  1. 导读
  2. 核心思想
  3. 为什么需要扩散模型
  4. 基本框架
  5. 前向扩散过程详解
  6. 数学原理
  7. 代码实现
  8. 定义扩散过程的超参数
  9. 生成线性变化的 beta 序列
  10. 前向扩散函数:从 x0 生成 xt
  11. 测试前向扩散过程
  12. 可视化不同步数的扩散效果
  13. 反向扩散过程与模型训练
  14. 数学原理
  15. 构建噪声预测网络
  16. 定义位置嵌入层:将步数 t 转换为高维向量
  17. 定义残差块
  18. 定义噪声预测网络
  19. 初始化模型
  20. 模型训练流程
  21. 加载数据集
  22. 定义优化器
  23. 训练函数
  24. 开始训练
  25. 保存模型
  26. 实战:基于 DDPM 的图像生成
  27. 反向扩散采样过程
  28. 反向扩散采样函数
  29. 生成图像
  30. 可视化生成结果
  31. 模型优化技巧
  32. 扩散模型的发展与应用
  33. 经典变体
  34. 应用场景
  35. 总结

更多推荐文章

查看全部
  • C++ 智能指针详解:原理、模拟实现与使用场景
  • 法奥机器人操作与编程入门指南
  • 基于 Python 的小学成绩信息管理系统开发
  • 论文 AI 生成率阈值解析:检测差异与合规写作建议
  • C++ 异常处理:理论、栈展开与最佳实践
  • 云开发 Copilot:AI 赋能的低代码开发实践
  • 昇腾 NPU 部署 Llama 2 模型:性能测试与优化实战
  • 鸿蒙金融理财全栈项目:生态合作、用户运营与数据变现
  • AI 大模型入门基础知识与核心架构解析
  • llama.cpp 大模型本地部署与推理指南
  • LeetCode 原地复写零:双指针与逆向填充的 O(n) 解法
  • Android 动态替换 Application 实现
  • OpenClaw iOS/Android 端部署教程:语音唤醒与离线 AI 助手
  • 基于 Open3D.Art 与拓竹打印机实现 AI 生成 3D 模型快速打印流程
  • 前端拖拽交互实现:从原生 API 到专业库
  • 大模型应用开发极简入门:从 Transformer 架构到 LangChain 实战
  • Git 新手指南:在 VS Code 中使用 Git
  • IO 流为什么只能读取一次?从底层原理到 Web 实战
  • Python 复现 FactSet Revere 供应链断裂与重构指标测度
  • OpenMAIC:清华开源 AI 教学平台,一键生成互动课程

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • RSA密钥对生成器

    生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online

  • Mermaid 预览与可视化编辑

    基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online

  • 随机西班牙地址生成器

    随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • curl 转代码

    解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online