从VAE到DDPM:图解扩散模型如何革新图像生成

在图像生成的演进长河中,我们见证了从变分自编码器(VAE)的隐变量探索,到生成对抗网络(GAN)的对抗博弈,再到如今以去噪扩散概率模型(DDPM)为代表的扩散模型引领的范式转移。对于许多算法工程师而言,理解这一技术脉络,不仅仅是掌握一个新工具,更是洞察生成式AI底层逻辑如何被重塑的关键。想象一下,在团队技术分享会上,如何向同事清晰地解释,为什么一个看似“笨拙”的、需要成百上千步迭代去噪的过程,最终却能生成比GAN更稳定、更多样、细节更丰富的图像?这背后,是概率建模思想的一次深刻回归与超越。

传统生成模型往往致力于学习一个从简单分布(如高斯噪声)到复杂数据分布(如图像)的直接映射。VAE通过编码-解码结构引入隐空间,追求重建与正则化的平衡;GAN则通过判别器与生成器的博弈,试图直接逼近数据分布。然而,它们各有掣肘:VAE生成图像可能模糊,GAN则饱受模式崩溃和训练不稳定的困扰。扩散模型另辟蹊径,它不追求“一步登天”,而是将生成过程分解为一系列微小的、可逆的步骤,模拟一个物理上合理的扩散与逆扩散过程。这种“渐进式精修”的思想,不仅带来了惊人的生成质量,更在数学上具备了优雅的可解释性和训练稳定性。

本文旨在为你拆解这一技术跃迁的核心。我们将抛开复杂的公式堆砌,通过信息图式的思维,对比VAE、GAN与DDPM在架构哲学上的根本差异,深入剖析DDPM中马尔可夫链的设计、噪声调度策略的奥秘,以及它如何通过学习“去噪”这一看似简单的任务,最终实现“无中生有”的创造性生成。无论你是希望将扩散模型应用于实际项目,还是单纯想理清其技术脉络,这篇文章都将为你提供一个清晰、深入且操作性强的视角。

1. 传统生成模型的局限与扩散模型的哲学转向

在深入DDPM的细节之前,我们有必要回顾一下它所试图超越的范式。理解旧框架的“天花板”,才能更好地欣赏新范式的“突破口”。

变分自编码器(VAE) 的核心思想是压缩与重建。它将输入图像编码到一个低维的、连续的隐空间(通常假设服从标准高斯分布),再从这个隐变量解码回图像。其优化目标是一个折衷:一方面要最小化重建误差,让输出尽可能像输入;另一方面要约束隐变量分布接近标准正态(通过KL散度),以保证隐空间的规整性和生成能力。

注意:VAE的生成过程是一次性的映射。从隐变量z到图像x,通过解码器网络一步完成。这导致它难以建模非常复杂的数据分布,生成结果常常丢失高频细节,显得平滑甚至模糊。

下面是一个简化的VAE训练损失函数的核心部分,它清晰地体现了这种权衡:

# 伪代码示意VAE损失
reconstruction_loss = F.mse_loss(decoder(z), x)  # 重建损失
kl_divergence = -0.5 * torch.sum(1 + log_var - mu.pow(2) - log_var.exp()) # KL散度
total_loss = reconstruction_loss + beta * kl_divergence  # β-VAE,β是权衡系数

生成对抗网络(GAN) 则采用了完全不同的博弈论思路。它引入一个判别器(D)来区分真实图像和生成图像,而生成器(G)的目标是生成足以“欺骗”判别器的图像。理想状态下,这个博弈会达到纳什均衡,生成器学到的分布无限接近真实数据分布。

特性VAEGAN (原始)
训练稳定性高,有明确的损失函数低,容易模式崩溃或训练发散
生成清晰度较低,倾向于模糊高,能产生锐利细节
隐空间性质连续、结构化,易于插值可能不连续,插值结果不可控
多样性较好,但可能牺牲保真度可能陷入模式崩溃,多样性差

尽管GAN在图像清晰度上取得了突破,但其训练如同走钢丝。判别器和生成器的能力必须精心平衡,任何一方的过强都会导致训练崩溃。此外,GAN缺乏一个显式的、易于评估的概率模型,其生成过程更像一个“黑箱”。

扩散模型的哲学正是在此背景下诞生。它放弃了“直接生成”的执念,转而模拟一个渐进式的物理过程:如何将一张清晰的图像,通过逐步添加噪声,变成纯粹的无结构噪声(前向过程);再学习如何将这个过程逆向进行,从噪声中一步步恢复出图像(反向过程)。这个想法的精妙之处在于:

  • 将复杂问题分解:学习一个复杂的生成分布,被转化为学习一系列相对简单的去噪分布。
  • 训练目标稳定:每一步的反向过程都被构造成一个预测噪声的回归任务,损失函数是平滑的均方误差(MSE),避免了GAN的对抗性不稳定。
  • 理论基础坚实:基于马尔可夫链和变分推断,整个框架有严格的概率论解释。

2. DDPM核心机制图解:前向扩散与反向生成

DDPM的整个过程可以比喻为一部电影的倒放。正向播放时(前向扩散),一幅画被不断泼上墨点,直到完全变成一团随机墨渍。而我们需要训练的模型,就是学会如何将这部电影倒放(反向生成),从一团墨渍中,一步步还原出最初的画作。

2.1 前向扩散过程:确定性的加噪

前向过程是一个固定的、无需学习的马尔可夫链。在每一步t,我们向当前数据x_{t-1}添加一小部分高斯噪声,得到x_t。这个过程可以用一个简单的公式定义:

x_t = √(α_t) * x_{t-1} + √(1 - α_t) * ε_t,其中 ε_t ~ N(0, I)

这里,α_t是一个介于0和1之间的值,称为噪声调度系数。它决定了在步骤t保留多少原始信号,以及添加多少新噪声。√(α_t)是保留信号的权重,√(1 - α_t)是添加噪声的权重。关键设计在于,这些权重的平方和为1,这保证了每一步输出x_t的方差保持稳定(假设输入已标准化)。

由于每一步都是线性的高斯加噪,并且是马尔可夫过程(下一步只依赖于当前步),我们可以通过数学推导,将任意步骤tx_t直接表示为原始图像x_0和一系列噪声的线性组合:

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

其中 ᾱ_t = ∏_{i=1}^{t} α_iε ~ N(0, I)。这个性质极其重要!它意味着我们不需要真的迭代1000步来模拟第500步的状态,而是可以直接通过x_0和一个随机噪声ε计算出来。这大大加快了训练时的数据采样效率。

在典型的DDPM设置中,α_t被安排为从接近1(如0.9999)缓慢衰减到接近0(如0.98)的序列。经过足够多的步数T(通常是1000步),ᾱ_T会趋近于0,此时x_T ≈ ε,几乎完全变成了标准高斯噪声。下图概括了这一过程:

[原始图像 x_0] --(加噪 step 1)--> [x_1] --(加噪 step 2)--> [x_2] --> ... --> [x_T ≈ 纯噪声]
      |(√ᾱ_1衰减) + 噪声        |(√ᾱ_2衰减) + 更多噪声                              |(ᾱ_T ≈ 0)

2.2 反向生成过程:学习去噪

如果前向过程是已知的、确定性的“破坏”,那么反向过程就是我们需要学习的“重建”。从纯噪声x_T开始,我们希望能一步步“猜”出前一步稍微干净一点的图像x_{T-1},并最终得到x_0

理论上,反向过程的每一步p(x_{t-1} | x_t)也是一个高斯分布,但其均值和方差依赖于x_t以及我们想要求得的x_0。这形成了一个“先有鸡还是先有蛋”的循环:为了从x_t得到x_{t-1},我们需要知道x_0;但x_0正是我们最终想生成的。

DDPM的巧妙解决方案是:不直接预测x_0x_{t-1},而是预测在前向过程中添加到x_{t-1}上,从而得到x_t的那个噪声ε。为什么可行?回顾前向过程的直接计算公式: x_t = √(ᾱ_t) * x_0 + √(1 - ᾱ_t) * ε

如果我们有一个模型ε_θ(x_t, t)能够预测出这个ε,那么我们就可以解出x_0的估计值: 估计的 x_0 = (x_t - √(1 - ᾱ_t) * ε_θ(x_t, t)) / √(ᾱ_t)

一旦有了x_0的估计,再结合x_t和已知的噪声调度参数α_t, ᾱ_t,就可以计算出反向高斯分布的均值和方差,进而采样得到x_{t-1}。这个过程可以逐步迭代,从t=T一直进行到t=1

因此,DDPM的训练目标变得异常简洁和稳定:训练一个神经网络(通常是U-Net),输入是任意噪声程度t下的噪声图像x_t,输出是预测的噪声ε_θ,使其与真实用于生成x_t的噪声ε的均方误差最小。

# DDPM训练步骤的简化伪代码
def train_step(model, x_0, noise_scheduler):
    # 1. 随机采样一个时间步t
    t = torch.randint(0, T, (batch_size,))
    
    # 2. 根据公式,直接从x_0生成加噪后的x_t
    sqrt_alpha_bar_t = noise_scheduler.get_sqrt_alpha_bar(t) # √ᾱ_t
    sqrt_one_minus_alpha_bar_t = noise_scheduler.get_sqrt_one_minus_alpha_bar(t) # √(1-ᾱ_t)
    noise = torch.randn_like(x_0) # 真实噪声 ε
    x_t = sqrt_alpha_bar_t * x_0 + sqrt_one_minus_alpha_bar_t * noise
    
    # 3. 模型预测噪声
    predicted_noise = model(x_t, t) # 模型输入:噪声图x_t和时间步t
    
    # 4. 计算简单的MSE损失
    loss = F.mse_loss(predicted_noise, noise)
    return loss

这种将复杂的生成问题,转化为预测一个简单高斯噪声的任务,是DDPM成功的关键。它避免了GAN中判别器与生成器的动态博弈,也避免了VAE中需要平衡重建与正则化的难题。

3. 架构差异深度对比:DDPM为何效果更胜一筹?

理解了基本流程后,我们可以从多个维度,将DDPM与VAE、GAN进行更深入的对比,从而揭示其性能优势的根源。

3.1 目标函数的稳定性与梯度质量

  • VAE:优化的是证据下界(ELBO),包含重建损失和KL散度。两者需要权衡(如β-VAE),调整不当会导致生成质量下降。梯度来源于明确的损失函数,稳定但可能陷入模糊解的局部最优。
  • GAN:优化的是一个极小极大博弈目标。梯度来源于判别器提供的“批评”,这种对抗性梯度可能非常不稳定、稀疏甚至消失,导致训练震荡和模式崩溃。
  • DDPM:优化的是一个简单的去噪分数匹配目标(预测噪声的MSE)。梯度直接、平滑且信息丰富。模型在每一个噪声级别t上都接受训练,相当于在数据空间的许多不同噪声版本上进行了平滑,这有助于学习到更稳健的数据流形。

3.2 生成过程的可控性与可解释性

  • VAE/GAN的一次性生成:从隐变量z到图像x是单步非线性变换。我们很难精确控制z空间中某个方向对应图像中的何种变化(尽管VAE的隐空间相对可控)。生成过程像一个“跳跃”,缺乏中间状态。
  • DDPM的渐进式生成:生成是从x_Tx_0多步迭代过程。这带来了几个巨大优势:
    1. 中间可视化:我们可以观察图像从噪声逐渐清晰化的全过程,这本身具有很好的可解释性和调试价值。
    2. 条件控制:可以在任何中间步骤t引入额外的条件信息(如类别标签、文本描述),引导生成方向。著名的Stable Diffusion就是在潜在空间进行扩散,并结合了文本编码器的条件输入。
    3. 编辑能力:基于扩散的图像编辑技术(如SDEdit、Prompt-to-Prompt)通过混合不同步骤的噪声或注意力图,实现了对生成内容的精细操控,这是单步生成模型难以做到的。

3.3 对数据分布的覆盖与模式多样性

  • GAN的模式崩溃:判别器可能会“封杀”某些生成模式,导致生成器只产生少数几种相似的样本,多样性严重不足。
  • VAE的模糊平均:为了最小化重建损失,解码器可能倾向于输出所有可能输出的“平均”,导致图像模糊,丢失细节和多样性。
  • DDPM的似然建模:DDPM本质上是在建模数据的概率密度函数。其训练目标(分数匹配)与最大化数据似然密切相关。这意味着它更倾向于覆盖所有训练数据模式,而不是像GAN那样可能丢弃一些“难生成”的模式。因此,扩散模型通常表现出更好的多样性和分布覆盖度

为了更直观地对比,我们用一个表格总结三者在关键特性上的差异:

特性维度VAEGANDDPM
训练稳定性
生成清晰度低(模糊)极高
模式覆盖/多样性较好差(易崩溃)极好
可解释性中等(隐空间)(渐进过程)
条件生成便利性容易中等(需cGAN)非常容易
计算成本低(单步)中等(单步)(多步迭代)
推理速度

从表格可以看出,DDPM几乎在所有生成质量相关的指标上都占优,其代价是高昂的计算成本和慢速的推理(需要多次网络前向传播)。这也正是当前扩散模型研究的一个核心优化方向。

4. 噪声调度与采样器:影响生成质量与速度的关键杠杆

在DDPM框架中,除了U-Net模型本身,噪声调度采样算法是另外两个至关重要的组件,它们直接决定了生成图像的质量、多样性和速度。

4.1 噪声调度:设计加噪的“节奏”

噪声调度定义了前向过程中α_t(或等价的β_t = 1 - α_t)如何随时间t变化。它控制了信息被破坏的速率。

  • 线性调度:原始DDPM论文使用的方案,β_t从很小的值(如1e-4)线性增长到较大的值(如0.02)。这意味着早期加噪很慢,保留大部分信息;后期加噪快,迅速走向纯噪声。
  • 余弦调度:后续改进(如Improved DDPM)提出使用基于余弦函数的调度。它在过程开始和结束时变化平缓,在中间阶段变化更快。这种调度通常能产生更好的生成效果,因为它更平滑地处理了信号与噪声的权衡。

不同的调度策略,会导致ᾱ_t(累计乘积)曲线形状不同,从而影响模型在不同噪声水平下的学习难度和最终生成效果。选择或设计调度策略,是调优扩散模型的第一步。

# 两种常见噪声调度函数的示例(伪代码)
def linear_beta_schedule(timesteps, start=1e-4, end=0.02):
    return torch.linspace(start, end, timesteps)

def cosine_beta_schedule(timesteps, s=0.008):
    steps = timesteps + 1
    x = torch.linspace(0, timesteps, steps)
    alphas_cumprod = torch.cos(((x / timesteps) + s) / (1 + s) * math.pi * 0.5) ** 2
    alphas_cumprod = alphas_cumprod / alphas_cumprod[0]
    betas = 1 - (alphas_cumprod[1:] / alphas_cumprod[:-1])
    return torch.clip(betas, 0, 0.999)

4.2 采样器:加速反向生成的艺术

原始DDPM采样需要完整迭代T步(如1000步),这非常缓慢。研究人员提出了多种采样器来加速这一过程,其核心思想是:用更少的步数,近似模拟多步反向过程

  • DDIM (Denoising Diffusion Implicit Models):这是最重要的加速采样方法之一。它发现DDPM的生成过程具有确定性的一面。通过将随机噪声ε的影响固定(或部分固定),DDIM可以将采样过程转化为一个确定性映射。这意味着:
    • 可以用远少于T的步数(如50步、20步)进行采样。
    • 隐变量具备了良好的插值特性,因为过程是确定性的,从一个隐码x_T出发,总是生成相同的图像。这为图像编辑和插值提供了便利。
    # DDIM采样步骤的核心思想(简化)
    # 已知当前步x_t和预测的噪声ε_θ,直接跳步估计x_{t-δt}
    pred_x0 = (x_t - sqrt_one_minus_alpha_bar_t * ε_θ) / sqrt_alpha_bar_t
    # 使用一个公式,结合pred_x0和x_t,直接计算x_{t-δt},跳过了中间若干步
    x_prev = sqrt_alpha_bar_prev * pred_x0 + sqrt_one_minus_alpha_bar_prev * ε_θ
    
  • PLMS (Pseudo Linear Multi-step method)DPM-Solver等:这些是更先进的数值求解器,将扩散方程视为一个微分方程,利用高阶方法(如龙格-库塔法)来求解,能以极少的步数(10-20步)获得高质量样本。

选择采样器时,需要在速度质量确定性之间做权衡。DDIM在速度和质量间取得了很好的平衡,并且是确定性的;而一些更快的采样器(如DPM-Solver++)可能在某些步骤数下质量略有波动。

4.3 引导技术:控制生成内容

基础的DDPM从纯噪声生成图像,结果是随机的。为了控制生成内容(如根据文本“一只戴着礼帽的猫”生成图像),需要引入引导

  • 分类器引导:训练一个额外的分类器p(y|x_t, t),在采样时,用分类器关于x_t的梯度来调整反向过程的均值,使其朝向指定类别y移动。这种方法效果好但需要额外训练分类器。
  • 无分类器引导:目前的主流方法。在训练时,随机以一定概率将条件信息(如文本)置为空(null)。在采样时,计算有条件预测和无条件预测的差值,将这个差值放大一定倍数(引导尺度)后,用来调整噪声预测。
    # 无分类器引导采样伪代码
    # model(x_t, t, cond) 是条件预测,model(x_t, t, null) 是无条件预测
    eps_cond = model(x_t, t, text_embedding) # 有条件噪声预测
    eps_uncond = model(x_t, t, empty_embedding) # 无条件噪声预测
    # 用引导尺度(guidance_scale)放大条件与无条件预测的差异
    eps = eps_uncond + guidance_scale * (eps_cond - eps_uncond)
    # 使用调整后的eps进行下一步采样
    
    引导尺度是一个超参数,越大则生成图像越贴合条件,但可能牺牲多样性和图像自然度。找到合适的尺度是获得理想结果的关键。

5. 从理论到实践:构建与训练一个简易DDPM

理解了所有原理后,让我们动手勾勒一个简易DDPM的实现框架,将理论落地。这里我们聚焦于核心组件,省略一些工程细节。

5.1 构建噪声调度器

首先,我们需要一个模块来管理所有与时间步t相关的噪声参数。

import torch
import math

class NoiseScheduler:
    def __init__(self, timesteps=1000, beta_start=1e-4, beta_end=0.02, schedule_type='linear'):
        self.timesteps = timesteps
        if schedule_type == 'linear':
            self.betas = torch.linspace(beta_start, beta_end, timesteps)
        elif schedule_type == 'cosine':
            # 使用简化版的余弦调度
            steps = timesteps + 1
            x = torch.linspace(0, timesteps, steps)
            alphas_cumprod = torch.cos((x / timesteps + 0.008) / 1.008 * math.pi * 0.5) ** 2
            alphas_cumprod = alphas_cumprod / alphas_cumprod[0]
            betas = 1 - (alphas_cumprod[1:] / alphas_cumprod[:-1])
            self.betas = torch.clip(betas, 0, 0.999)
        else:
            raise ValueError(f"Unsupported schedule type: {schedule_type}")
        
        self.alphas = 1. - self.betas
        self.alphas_cumprod = torch.cumprod(self.alphas, dim=0) # ᾱ_t
        self.sqrt_alphas_cumprod = torch.sqrt(self.alphas_cumprod) # √ᾱ_t
        self.sqrt_one_minus_alphas_cumprod = torch.sqrt(1. - self.alphas_cumprod) # √(1-ᾱ_t)
        
        # 为反向过程计算一些后验参数(用于DDPM采样)
        alphas_cumprod_prev = torch.cat([torch.tensor([1.0]), self.alphas_cumprod[:-1]])
        self.posterior_variance = self.betas * (1. - alphas_cumprod_prev) / (1. - self.alphas_cumprod)
        self.posterior_mean_coef1 = self.betas * torch.sqrt(alphas_cumprod_prev) / (1. - self.alphas_cumprod)
        self.posterior_mean_coef2 = (1. - alphas_cumprod_prev) * torch.sqrt(self.alphas) / (1. - self.alphas_cumprod)
    
    def add_noise(self, x_start, timesteps, noise):
        """根据公式直接计算加噪后的x_t"""
        sqrt_alpha_prod = self.sqrt_alphas_cumprod[timesteps].reshape(-1, 1, 1, 1)
        sqrt_one_minus_alpha_prod = self.sqrt_one_minus_alphas_cumprod[timesteps].reshape(-1, 1, 1, 1)
        return sqrt_alpha_prod * x_start + sqrt_one_minus_alpha_prod * noise

5.2 设计U-Net模型

U-Net是DDPM预测噪声ε_θ的核心网络。它需要接收两个输入:噪声图像x_t和时间步t的嵌入。

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

class SinusoidalPositionEmbeddings(nn.Module):
    """将时间步t转换为向量表示"""
    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

# 这里仅示意一个极简的Block,实际U-Net包含下采样、上采样、残差连接和注意力层等
class SimpleUNet(nn.Module):
    def __init__(self, in_channels=3, time_emb_dim=128):
        super().__init__()
        self.time_mlp = nn.Sequential(
            SinusoidalPositionEmbeddings(time_emb_dim),
            nn.Linear(time_emb_dim, time_emb_dim),
            nn.ReLU()
        )
        # 假设有一个初始卷积层和一个最终输出层
        self.init_conv = nn.Conv2d(in_channels + time_emb_dim, 64, kernel_size=3, padding=1)
        self.out_conv = nn.Conv2d(64, in_channels, kernel_size=3, padding=1)
        # ... 中间应包含完整的U-Net下采样和上采样结构
        
    def forward(self, x, timesteps):
        # x: 噪声图像 [B, C, H, W]
        # timesteps: 时间步 [B, ]
        t_emb = self.time_mlp(timesteps) # [B, time_emb_dim]
        # 将时间嵌入扩展到空间维度并拼接到输入
        t_emb = t_emb[:, :, None, None].repeat(1, 1, x.shape[2], x.shape[3])
        x = torch.cat([x, t_emb], dim=1)
        x = self.init_conv(x)
        # ... 经过U-Net主干网络
        x = self.out_conv(x) # 预测的噪声 [B, C, H, W]
        return x

5.3 训练循环与采样循环

有了调度器和模型,训练和采样的逻辑就非常清晰了。

def train_loop(model, dataloader, noise_scheduler, optimizer, device, epochs):
    model.train()
    for epoch in range(epochs):
        for batch in dataloader:
            x_0 = batch.to(device) # 干净图像
            optimizer.zero_grad()
            
            # 1. 随机采样时间步和噪声
            t = torch.randint(0, noise_scheduler.timesteps, (x_0.shape[0],), device=device).long()
            noise = torch.randn_like(x_0)
            
            # 2. 根据时间步t,为x_0加噪,得到x_t
            x_t = noise_scheduler.add_noise(x_0, t, noise)
            
            # 3. 模型预测噪声
            predicted_noise = model(x_t, t)
            
            # 4. 计算损失并反向传播
            loss = F.mse_loss(predicted_noise, noise)
            loss.backward()
            optimizer.step()
            
        print(f"Epoch {epoch}, Loss: {loss.item():.4f}")

def ddpm_sample(model, noise_scheduler, image_size, batch_size=1, device='cuda'):
    """使用原始DDPM算法采样"""
    model.eval()
    with torch.no_grad():
        # 从纯噪声开始
        x_t = torch.randn((batch_size, 3, image_size, image_size), device=device)
        
        for i in reversed(range(noise_scheduler.timesteps)):
            t = torch.full((batch_size,), i, device=device, dtype=torch.long)
            
            # 预测噪声
            eps_theta = model(x_t, t)
            
            # 计算后验均值和方差
            sqrt_alpha_t = torch.sqrt(noise_scheduler.alphas[t])[:, None, None, None]
            sqrt_one_minus_alpha_bar_t = noise_scheduler.sqrt_one_minus_alphas_cumprod[t][:, None, None, None]
            sqrt_recip_alpha_t = 1. / sqrt_alpha_t
            # 估计x_0
            pred_x0 = sqrt_recip_alpha_t * (x_t - sqrt_one_minus_alpha_bar_t * eps_theta)
            
            # 计算后验方差和均值系数
            posterior_variance = noise_scheduler.posterior_variance[t][:, None, None, None]
            coef1 = noise_scheduler.posterior_mean_coef1[t][:, None, None, None]
            coef2 = noise_scheduler.posterior_mean_coef2[t][:, None, None, None]
            
            # 计算均值
            mean = coef1 * pred_x0 + coef2 * x_t
            
            if i > 0:
                noise = torch.randn_like(x_t)
            else:
                noise = 0
                
            # 采样x_{t-1}
            x_t = mean + torch.sqrt(posterior_variance) * noise
            
        # 最终x_t即为生成的图像x_0
        generated_images = torch.clip(x_t, -1, 1)
        return generated_images

在实际项目中,你会使用更成熟的U-Net实现(如来自diffusers库),并集成DDIM等更快的采样器。但上述代码骨架清晰地揭示了DDPM从训练到推理的完整逻辑闭环:通过预测噪声来学习逆扩散过程

从VAE的概率隐空间到GAN的对抗博弈,再到DDPM的渐进式去噪,图像生成技术的发展脉络体现着对“如何建模复杂数据分布”这一核心问题的不断深化理解。扩散模型的成功并非偶然,它将一个高难度的生成问题,分解为数百个可管理的去噪子问题,用稳定的回归损失替代了对抗性博弈,用明确的概率推导支撑了整个框架。虽然其多步迭代的特性带来了计算开销,但随之而来的却是前所未有的生成质量、稳定性和可控性。如今,结合潜在空间扩散、蒸馏加速、无分类器引导等技术的现代扩散模型(如Stable Diffusion、DALL-E 3),正在不断突破效率瓶颈,拓展应用边界。对于算法工程师而言,掌握扩散模型不仅意味着多了一件强大的生成工具,更意味着理解了一种新的、基于概率和迭代的生成范式,这种范式正在重新定义AIGC的可能性。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐