前言

作为生成式 AI 的基石之一,扩散模型(Diffusion Model)早已不是什么新鲜概念。但相信很多人和我一样,第一次接触 DDPM(Denoising Diffusion Probabilistic Models)论文时,都会被一堆看似复杂的公式和跳步的推导劝退:

本文将严格遵循 DDPM 原文的逻辑,从人为定义的马尔可夫链出发,完整推导所有关键公式,重点解决上述所有困惑。我会尽量避免不必要的数学炫技,用最直白的语言讲透每一个设计背后的动机和原理,确保你看完不仅能懂 "是什么",更能懂 "为什么"。

DDPM论文:https://arxiv.org/pdf/2006.11239

一、前向扩散:我们人为定义的 "涂鸦过程"

DDPM 的核心思想非常朴素:先教 AI"擦涂鸦",再让 AI 从一张全灰的纸开始,一步步擦出一张全新的画。而前向扩散过程,就是我们作为 "老师",给图片 "涂鸦" 的过程 ——所有规则都是我们提前写死的,没有任何 AI 参与

1. 第一步:定义前向扩散是马尔可夫链

论文第 2 章开篇就明确了前向扩散的本质:

The approximate posterior q(x1:T​∣x0​), called the forward process or diffusion process, is fixed to a Markov chain that gradually adds Gaussian noise to the data according to a variance schedule β1​,...,βT​.

翻译成人话:前向扩散是一个固定的、无学习参数的马尔可夫链,它会按照我们提前定好的噪声强度表,逐步向图片中添加高斯噪声。

为什么是马尔可夫链?因为它有一个最适合我们的性质:下一步只和当前步有关,和更早的步无关。这让整个过程的数学表达变得极其简单,整个前向过程的联合分布可以直接写成每一步条件分布的乘积:q(x1:T​∣x0​)=t=1∏T​q(xt​∣xt−1​)(1)

2. 第二步:人为设定单步加噪规则

这是整个 DDPM 中唯一的人为设定,所有后续的推导都建立在这个规则之上。论文公式 (2) 给出了单步加噪的定义:q(xt​∣xt−1​):=N(xt​;1−βt​​xt−1​,βt​I)(2)

我来逐句拆解这个规则:

  1. 我们规定:第 t 步的图 xt​ 只由第 t-1 步的图 xt−1​ 决定
  2. 我们规定:xt​ 服从一个高斯分布(也就是我们常说的 "颗粒感" 噪声)
  3. 这个高斯分布的均值​:把上一步的图稍微变暗一点
  4. 这个高斯分布的方差:给每个像素加独立的、强度为 βt​ 的高斯噪声

为什么要这么设计? 这里有两个极其巧妙的考量:

  • 乘以:抵消噪声带来的方差增加,让每一步的图的方差都保持在 1 左右,数值范围稳定,模型训练更稳定
  • 加高斯噪声:利用高斯分布独一无二的性质 ——多个独立高斯噪声加在一起,结果还是高斯噪声。这是我们能推导出任意步加噪公式的根本前提

3. 第三步:完整推导任意步加噪公式(代码中真正用的)

如果真的要给每张图跑 1000 步加噪,训练速度会慢到无法接受。但多亏了高斯分布的可加性,我们可以直接算出任意第 t 步的加噪结果,不用从 x₀一步步算到 xₜ。

推导过程(从 t=2 推广到任意 t)

我们先从最简单的 t=2 开始推导,看懂了两步,任意 t 步就都懂了。

根据单步加噪规则 (2),我们可以把高斯分布写成 "均值 + 噪声" 的形式:

把 x₁代入 x₂的表达式:

现在,我们需要合并后面两个独立的高斯噪声项。这里用到了高斯分布的可加性(整个 DDPM 唯一用到的概率论知识):

在我们的例子里,,所以:

我们来化简括号里的方差:

为了简化公式,论文定义了两个辅助符号(论文第 2 页):(αˉt​ 就是 α₁到 αₜ的累积乘积)

把 (4) 和 (5) 代入 (3),我们得到:

其中(合并后的总噪声)

用数学归纳法可以很容易地证明,对于任意 t≥1,这个结论都成立。因此,我们得到了任意步加噪的闭式解(论文公式 4):

把高斯分布写成 "均值 + 噪声" 的形式,就得到了代码中 100% 会用到的加噪公式

其中 是前 t 步加的总噪声。

这里澄清一个最常见的概念混淆:q(xt​∣x0​) 不是一个概率值,而是一个条件分布。它描述的是:给定同一张原图 x₀,每次加 t 步噪声得到的 xₜ的取值规律。因为我们加的是高斯噪声,所以这个规律自然也是高斯分布。

二、训练过程:教 AI"预测我涂了多少灰色"

有了任意步的加噪公式,训练过程就变得异常简单了。我们不需要让 AI 直接 "画一张图",只需要让它完成一个最简单的回归任务:给定一张加了 t 步噪声的图 xₜ,预测我在这 t 步里总共加了多少噪声 εₜ

1. 为什么不直接预测原图 x₀?

如果我们让 AI 直接从 xₜ预测 x₀,这是一个非常难的任务 —— 就像给你一张被涂了一半的画,让你猜原来画的是什么。而预测噪声则简单得多:噪声是随机的、无结构的,模型只需要学习 "哪些像素是我加的灰色" 就行。

2. 训练的完整流程

论文 Algorithm 1 给出了训练算法,我把它翻译成大白话:

  1. 随机拿一张干净的原图 x₀
  2. 随机选一个步数 t(1 到 T 之间随便挑)
  3. 随机生成一个总噪声 εₜ
  4. 用公式 (7),直接算出 xₜ
  5. 把 xₜ和 t 输入模型,让模型预测噪声
  6. 用 MSE 损失计算,更新模型参数

重复几百万次,AI 就学会了:不管给我一张被涂了多少步的图,我都能准确说出这一步加了多少噪声

三、反向采样:AI 自己 "擦涂鸦" 的过程(含完整推导)

训练完成后,AI 已经是一个顶级的 "擦涂鸦大师" 了。现在我们让它反过来,从一张全灰的噪声图开始,一步步擦出一张新画。这部分是 DDPM 最核心、也是最容易被误解的部分,我会把所有推导过程完整写出来。

1. 最致命的误区:为什么不能一步还原 x₀?

很多人都会问:既然 ,那我直接变形得到 ,一步就能还原原图,为什么还要走 1000 步?

答案非常简单:模型预测的噪声不是 100% 准确的,有误差。如果一步还原,这个误差会被放大 倍。当 t=1000 时,,误差会被放大无穷大,得到的 x₀会完全是噪声。

我们来做一个简单的误差分析:假设模型预测的噪声有一个小误差δ,也就是,其中。代入一步还原的公式:

所以,x^0​的误差是:

当 t=1000 时,,这个误差会趋近于无穷大。

2. 完整推导:上帝视角的完美去噪分布

那我们应该怎么去噪呢?论文告诉我们:如果我们知道真实的 x₀,那么从 xₜ还原到 xₜ₋₁的分布是可以用贝叶斯公式精确计算出来的。

贝叶斯公式的应用

对于任意两个随机变量 A 和 B,贝叶斯公式为:

我们要求的是q(xt−1​∣xt​,x0​),把 A 换成xt−1​,B 换成xt​,条件是x0​,得到:

为什么能用贝叶斯公式直接算出来? 因为前向扩散的所有规则都是我们人为定死的,没有任何未知参数。右边的每一项,都是我们已经知道的:

代入计算,得到结果

把这三个高斯分布代入 (8),经过合并指数项、配方等代数运算,就会发现结果还是一个高斯分布(论文公式 6):

其中:

整个过程没有任何近似,是精确的数学推导。这个分布就是 "上帝视角" 的完美去噪分布 —— 如果我们知道真实的 x₀,就能用它 100% 准确地从 xₜ还原出 xₜ₋₁。

3. 完整推导:实际能用的采样公式

当然,采样的时候我们不知道真实的 x₀。但我们可以用模型预测的噪声ϵ^t​来估计 x₀。

从公式 (7) 变形,我们可以得到 x₀的估计值:

现在,我们把这个估计的x^0​代入上帝视角的均值公式 (10),就得到了我们实际能用的反向过程均值μθ​(xt​,t):

这个公式看起来很复杂,但我们可以把它化简成一个非常简洁的形式。注意到αˉt​=αt​αˉt−1​,我们来一步步化简:

首先,把 (13) 拆成两项:

合并 x_t 的系数:

所以 x_t 的系数化简为:

把两个系数代入 (13),我们就得到了论文公式 (11),也就是最终的采样均值公式

太神奇了! 这么复杂的公式,化简后居然这么简单。这就是 DDPM 采样过程的核心公式。

4. 最终的采样公式

论文里说,反向过程的方差σt2​I可以固定为常数,​都可以,效果差不多。

所以,从 xₜ采样得到 xₜ₋₁的最终公式就是:

其中:

  • z∼N(0,I) 是一个随机的高斯噪声
  • 当 t=1 时,z=0(最后一步不加噪声,得到确定的原图)

5. 为什么采样的时候还要加随机噪声 z?

这是另一个常见的困惑:我们好不容易把噪声擦掉了,为什么还要再加回去一点?

答案是:为了生成的多样性。反向过程是一个高斯分布,不是一个确定的点。如果我们只取均值,那么每次用同一个 x_T 生成的图都是一模一样的。加一点点随机噪声 z,就是让我们从这个高斯分布里随机采样一个点,这样每次生成的图都会有细微的差别,更自然。

6. 为什么分步去噪的误差很小?

我们之前说过,直接一步还原 x₀的误差会被指数级放大,但用误差大的估计 xₜ₋₁,误差却很小。我们来证明这一点:

假设模型预测的噪声有误差δ,即,那么x^0​的误差是:

代入μθ​的误差:

所以,μθ​的误差是:

当 t=1000 时,β1000​=0.02,所以误差只被放大了0.02​≈0.14倍!哪怕x^0​的误差是无穷大,μθ​的误差也只有原来噪声误差的 0.14 倍。

这就是 DDPM 最核心的设计智慧:它没有让模型直接预测难的 x₀,而是让模型预测一个简单的 ε,然后用这个 ε 去修正 x_t,得到 xₜ₋₁。每一步只修正一点点,误差就不会被放大。

7. 完整的采样算法

论文 Algorithm 2 给出了完整的采样算法,我逐行翻译:

  1. 生成一张完全随机的纯噪声图 x_T(生成的起点)
  2. 从 t=T 倒着循环到 t=1
  3. 生成随机噪声 z,如果是最后一步 t=1,z=0
  4. 用公式 (14) 计算均值μθ​
  5. 用公式 (15) 得到 xₜ₋₁
  6. 循环结束,返回 x₀

四、代码实现:极简 DDPM 的核心逻辑

下面我用 PyTorch 写出 DDPM 最核心的加噪和采样函数,你会发现理论和代码是完全一一对应的。

1. 初始化 β 和 α

import torch
import torch.nn as nn

# DDPM超参数(和论文完全一致)
T = 1000
beta_start = 1e-4
beta_end = 0.02

# 线性β调度(论文默认)
beta = torch.linspace(beta_start, beta_end, T)
alpha = 1. - beta
alpha_bar = torch.cumprod(alpha, dim=0)  # 累积乘积,就是ᾱ_t

2. 加噪函数(训练时用)

def add_noise(x0, t):
    """
    给干净图x0加t步噪声,返回加噪后的xt和真实噪声epsilon
    对应论文公式(7)
    """
    noise = torch.randn_like(x0)
    # 把t对应的alpha_bar广播到和x0相同的形状
    sqrt_alpha_bar = torch.sqrt(alpha_bar[t]).view(-1, 1, 1, 1)
    sqrt_one_minus_alpha_bar = torch.sqrt(1 - alpha_bar[t]).view(-1, 1, 1, 1)
    xt = sqrt_alpha_bar * x0 + sqrt_one_minus_alpha_bar * noise
    return xt, noise

3. 采样函数(生成时用)

def sample(model, batch_size=1, image_size=32):
    """
    从纯噪声开始,采样生成batch_size张图片
    对应论文Algorithm 2
    """
    x = torch.randn(batch_size, 3, image_size, image_size)
    
    for t in reversed(range(T)):
        t_tensor = torch.tensor([t] * batch_size)
        z = torch.randn_like(x) if t > 0 else torch.zeros_like(x)
        
        # 模型预测噪声
        epsilon_hat = model(x, t_tensor)
        
        # 计算均值(对应论文公式14)
        sqrt_alpha = torch.sqrt(alpha[t]).view(-1, 1, 1, 1)
        beta_t = beta[t].view(-1, 1, 1, 1)
        sqrt_one_minus_alpha_bar = torch.sqrt(1 - alpha_bar[t]).view(-1, 1, 1, 1)
        mu = (x - beta_t / sqrt_one_minus_alpha_bar * epsilon_hat) / sqrt_alpha
        
        # 计算方差(论文说固定为sqrt(beta_t)即可)
        sigma_t = torch.sqrt(beta[t]).view(-1, 1, 1, 1)
        
        # 得到x_{t-1}(对应论文公式15)
        x = mu + sigma_t * z
    
    return x

五、总结:DDPM 的核心设计智慧

最后,我们用一句话总结 DDPM 的整个逻辑链:

我们人为定义了一个逐步加高斯噪声的马尔可夫链,利用高斯分布的可加性推导出任意步加噪公式,把生成任务转化为最简单的噪声预测回归任务;采样时利用贝叶斯公式推导出分步去噪公式,每一步只去掉一点点噪声,避免误差被放大,最终生成高质量的图片。

DDPM 看起来很 "笨",要走 1000 步才能生成一张图,但正是这种 "笨办法",解决了 GAN 长期存在的模式崩溃问题,也让生成模型的训练变得前所未有的稳定。这也正是它能成为现在所有主流生成模型(Stable Diffusion、Sora 等)基础的原因。

希望这篇文章能帮你彻底搞懂 DDPM 的核心原理。如果还有什么疑问,欢迎在评论区留言交流。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐