从 0 开始,最小例子驱动的扩散模型 DDPM 数学原理

目标:不跳任何一步,把 DDPM(Denoising Diffusion Probabilistic Models)的推导讲清楚。
方法:先用「1 个像素值的 1 维小例子」手算一遍,再推广到图片、再解释「为什么这样能训练」。
学完应能:不看资料写出 q_sample(一步加噪)公式、写出训练损失、写出反向一步更新。
建议配合阶段 1 的概念直觉一起读。

2.0 先问一个傻问题:我们到底在解决什么问题?

我们手里有海量真实图片 $x_0$(比如一堆猫图)。 我们想造一个函数 $f$,输入「噪声」,输出「一张新猫图」。

难点:从纯噪声直接跳到一张清晰的猫,跨度太大,神经网络学不会。 (类比:让你闭眼凭空画一只猫很难;但给你一张「只糊了一点的猫图」,让你擦掉那点模糊,就容易多了。)

Diffusion 的核心智慧就一句话:

别让模型「一次画猫」,让模型「每次只擦掉一点点噪声」。把一个超难问题,拆成一千个超简单问题。

下面我们用数学把这事严格化。


2.1 最小例子:一个像素值的「消失」

先别想整张图,想象 只有 1 个像素,它的亮度值是一个实数,比如:

$$ x_0 = 5.0 $$

(真实图片里像素在 $[0,255]$ 或 $[-1,1]$,这里用 5.0 方便手算。)

我们定义一个「加噪操作」:每一步,把当前值往「噪声」方向拉一点,同时掺入一点随机高斯噪声。

第 1 步(t=1)

设加噪强度 $\beta_1 = 0.1$(很小)。操作定义成:

$$ x_1 = \sqrt{1-\beta_1}\; x_0 + \sqrt{\beta_1}\; \epsilon_1 $$

其中 $\epsilon_1 \sim \mathcal N(0,1)$(标准高斯随机数,比如这次抽到了 $\epsilon_1 = 0.5$)。 代入:

$$ x_1 = \sqrt{0.9}\times 5.0 + \sqrt{0.1}\times 0.5 = 0.9487\times 5.0 + 0.3162\times 0.5 = 4.7434 + 0.1581 = 4.9015 $$

观察:

  • $\sqrt{1-\beta_1}=0.9487$ 是「保留多少原信号」的系数(约 95%)。
  • $\sqrt{\beta_1}=0.3162$ 是「加多少噪声」的系数。
  • 这一步后,$x_1$ 还是 4.9,和 5.0 很接近——只糊了一点点。
为什么写成 $\sqrt{1-\beta}$ 和 $\sqrt{\beta}$?因为这样设计的方差/均值,能保证 $x_t$ 的分布方差始终是 1(后面 2.3 证明)。这是数学上「恰好好用」的写法,记住这个形式即可。

第 2 步(t=2)

同样,用 $\beta_2=0.1$(简单起见先让每步一样):

$$ x_2 = \sqrt{1-\beta_2}\; x_1 + \sqrt{\beta_2}\; \epsilon_2 = 0.9487\times 4.9015 + 0.3162\times \epsilon_2 $$

假设这次 $\epsilon_2 = -0.3$:

$$ x_2 = 4.6504 - 0.0949 = 4.5555 $$

继续走 1000 步,每步都「保留 95% + 加 5% 噪声」。最终 $x_{1000}$ 会怎样? → 原信号 $x_0=5.0$ 被稀释得几乎没有,剩下全是噪声。可以证明(2.3)它会趋近 $\mathcal N(0,1)$,即纯噪声。

这就是前向过程(forward process):一条从清晰到纯噪声的「渐进消失」链条。


2.2 把一步写成通用公式

把上面两步的操作抽象成对任意步 $t$:

$$ q(x_t \mid x_{t-1}) = \mathcal N\big(x_t;\ \sqrt{1-\beta_t}\,x_{t-1},\ \beta_t \mathbf I\big) \tag{1} $$

读做:「已知上一步 $x_{t-1}$,这一步 $x_t$ 服从高斯分布,均值 = $\sqrt{1-\beta_t}x_{t-1}$,方差 = $\beta_t$。」

  • $\beta_t$ 叫 noise schedule(噪声时间表),通常从 1e-4 慢慢涨到 0.02(前几步几乎不动,后几步狂加噪)。
  • 整个链条是马尔可夫链:每一步只依赖前一步,和历史无关。

关键记号(后面天天用)

定义: $$ \alpha_t = 1 - \beta_t,\qquad \bar\alpha_t = \prod_{s=1}^{t}\alpha_s $$

  • $\alpha_t$:单步「信号保留率」
  • $\bar\alpha_t = \alpha_1\alpha_2\cdots\alpha_t$:累积到第 $t$ 步的「总信号保留率」(一直乘,越乘越小)

在最小例子里 $\beta_t=0.1$ 恒定,则 $\alpha_t=0.9$,$\bar\alpha_1=0.9,\ \bar\alpha_2=0.81,\ \bar\alpha_3=0.729\ldots$ 可以看到 $\bar\alpha_t$ 指数衰减,正是「信号越来越少」的数学表达。


2.3 核心技巧:一步跳到任意 t(封闭形式)

如果每次都从 $x_0$ 一步步走到 $x_t$,训练时要循环 $t$ 次,太慢。 DDPM 最妙的推导是:我们能直接写出 $x_t$ 和 $x_0$ 的关系,跳过中间所有步。

把 (1) 连代入:

$$ \begin{aligned} x_1 &= \sqrt{\alpha_1}\,x_0 + \sqrt{1-\alpha_1}\,\epsilon_1 \\ x_2 &= \sqrt{\alpha_2}\,x_1 + \sqrt{1-\alpha_2}\,\epsilon_2 \\ &= \sqrt{\alpha_2\alpha_1}\,x_0 + \sqrt{\alpha_2(1-\alpha_1)}\,\epsilon_1 + \sqrt{1-\alpha_2}\,\epsilon_2 \end{aligned} $$

由于 $\epsilon_1,\epsilon_2$ 都是独立标准高斯,两个高斯噪声的加权和仍是高斯,且可以合并成一个噪声(方差相加后再开方):

$$ \sqrt{\alpha_2(1-\alpha_1)}\,\epsilon_1 + \sqrt{1-\alpha_2}\,\epsilon_2 \ \sim\ \mathcal N\!\left(0,\ \alpha_2(1-\alpha_1) + (1-\alpha_2)\right) $$

而 $\alpha_2(1-\alpha_1) + (1-\alpha_2) = \alpha_2 - \alpha_1\alpha_2 + 1 - \alpha_2 = 1 - \alpha_1\alpha_2 = 1 - \bar\alpha_2$。 开根号得到 $\sqrt{1-\bar\alpha_2}$。

推到任意 $t$,归纳可得著名的封闭形式(closed form):

$$ \boxed{\,x_t = \sqrt{\bar\alpha_t}\,x_0 + \sqrt{1-\bar\alpha_t}\,\epsilon\,,\qquad \epsilon\sim\mathcal N(0,\mathbf I)\,} \tag{2} $$

并且因此: $$ q(x_t\mid x_0) = \mathcal N\big(x_t;\ \sqrt{\bar\alpha_t}\,x_0,\ (1-\bar\alpha_t)\mathbf I\big) \tag{3} $$

这就是阶段 4 里 q_sample(x0, t, noise) 的数学依据。 它告诉我们:

想给图加「第 t 步的噪声」,根本不用循环,直接拿原图乘 $\sqrt{\bar\alpha_t}$、再加 $\sqrt{1-\bar\alpha_t}$ 倍的随机噪声即可。

用最小例子验证一下

$\bar\alpha_2 = 0.81$,$\sqrt{\bar\alpha_2}=0.9$,$\sqrt{1-\bar\alpha_2}=\sqrt{0.19}=0.4359$。 若直接由 $x_0$ 生成 $x_2$(用一个新噪声 $\epsilon=0.2$): $$ x_2 = 0.9\times 5.0 + 0.4359\times 0.2 = 4.5 + 0.0872 = 4.5872 $$ 而 2.1 里我们一步步算到 $x_2=4.5555$(两步噪声累加效应不同,但分布完全一致)。 分布一致,单次采样数值因噪声不同而有差异——这正是「一步跳到 t」成立的含义。

直观:公式 (2) 里 $\sqrt{\bar\alpha_t}$ 随 $t$ 增大越来越小(信号淡出),$\sqrt{1-\bar\alpha_t}$ 越来越大(噪声淡入)。$t=T$ 时 $\bar\alpha_T\approx 0$,于是 $x_T \approx \epsilon\sim\mathcal N(0,1)$——完美的纯噪声。

2.4 反向过程:我们想学什么?

前向是「已知 $x_{t-1}$ 怎么加噪」(公式 1,固定、无需学习)。 反向是「已知 $x_t$(带噪图),怎么退回去得到 $x_{t-1}$」——这才是神经网络要干的活。

我们假设反向每一步也是高斯(这是 DDPM 的建模假设): $$ p_\theta(x_{t-1}\mid x_t) = \mathcal N\big(x_{t-1};\ \mu_\theta(x_t,t),\ \Sigma_\theta(x_t,t)\big) \tag{4} $$

神经网络 $\epsilon_\theta$(参数 $\theta$)的任务是:给定 $x_t$ 和步数 $t$,预测这一步该往哪退。

一个漂亮的事实:如果「知道原图 $x_0$」,反向一步有解析解

利用贝叶斯,已知 $x_0$ 时,$q(x_{t-1}\mid x_t, x_0)$ 也是高斯,其均值为: $$ \tilde\mu_t(x_t,x_0) = \frac{\sqrt{\alpha_t}(1-\bar\alpha_{t-1})}{1-\bar\alpha_t}\,x_t + \frac{\sqrt{\bar\alpha_{t-1}}\,\beta_t}{1-\bar\alpha_t}\,x_0 \tag{5} $$

但训练时我们不知道 $x_0$。怎么办?用公式 (2) 把 $x_0$ 解出来: $$ x_0 = \frac{x_t - \sqrt{1-\bar\alpha_t}\,\epsilon}{\sqrt{\bar\alpha_t}} \tag{6} $$

把 (6) 代入 (5),整理后神奇地发现:均值可以完全用「噪声 $\epsilon$」表示: $$ \mu_\theta(x_t,t) = \frac{1}{\sqrt{\alpha_t}}\left(x_t - \frac{1-\alpha_t}{\sqrt{1-\bar\alpha_t}}\,\epsilon_\theta(x_t,t)\right) \tag{7} $$

这一行是整个 DDPM 的灵魂。 它说:

反向一步的均值,只需要「当前带噪图 $x_t$」和「网络预测的噪声 $\epsilon_\theta$」就够了。
网络不用直接预测干净图,只要预测「这步加进去的噪声长什么样」,就能退回去。

用最小例子感受反向一步

假设我们已知 $x_t=4.55$(某个中间带噪值),真实噪声 $\epsilon=0.2$,网络完美预测出 $\epsilon_\theta=0.2$。 取 $t=2, \alpha_2=0.9, \bar\alpha_2=0.81, \bar\alpha_1=0.9,\beta_2=0.1$: $$ \mu = \frac{1}{\sqrt{0.9}}\left(4.55 - \frac{0.1}{\sqrt{0.19}}\times 0.2\right) = 1.0541\times(4.55 - 0.2294) = 1.0541\times 4.3206 = 4.555 $$ 而真实的 $x_1$ 应该是约 4.56——几乎吻合!说明:只要噪声预测准,去噪就准。


2.5 训练目标:为什么「预测噪声」就够了?(ELBO 直觉)

现在问题变成:怎么训练 $\epsilon_\theta$ 让它预测准噪声?我们需要一个损失函数。

从「最大化似然」到 ELBO

我们希望模型的「生成真实图的概率」$p_\theta(x_0)$ 尽量大。直接算它很难,但可以用变分推断得到它的下界(ELBO,Evidence Lower Bound):

$$ \log p_\theta(x_0) \ge \mathbb E_q\Big[\underbrace{\log p_\theta(x_T)}_{\text{先验项}} + \sum_{t}\underbrace{\log\frac{p_\theta(x_{t-1}\mid x_t)}{q(x_t\mid x_{t-1})}}_{\text{去噪匹配项}}

  • \underbrace{\log q(x_1\mid x_0)}_{\text{单步重建项}}\Big]

$$

直觉:要让反向去噪 $p_\theta(x_{t-1}\mid x_t)$ 尽量贴近「真实的前向 $q(x_{t-1}\mid x_t,x_0)$」。 两者都是高斯,高斯之间的「贴近程度」用 KL 散度 衡量。而两个高斯 KL 最小,等价于让它们的均值相等。

把 (5)/(7) 代入,经过一段(这里不展开的)代数,KL 项全部塌缩成: $$ \|\,\tilde\epsilon_t - \epsilon_\theta(x_t,t)\,\|^2 $$ 其中 $\tilde\epsilon_t$ 就是前向实际加进去的那个噪声 $\epsilon$(公式 2 里的那个)。

最终的「极简损失」

于是 DDPM 作者证明:训练目标可以简化成——只预测噪声的均方误差:

$$ \boxed{\,\mathcal L_{\text{simple}} = \mathbb E_{t,\ x_0,\ \epsilon}\Big[\big\|\epsilon - \epsilon_\theta\big(\sqrt{\bar\alpha_t}\,x_0 + \sqrt{1-\bar\alpha_t}\,\epsilon,\ t\big)\big\|^2\Big]\,} \tag{8} $$

这就是阶段 4 train.py 里那一行 F.mse_loss(pred_noise, noise) 的全部理论来源。

为什么能这么简单?因为:(a) 前向封闭形式让我们能「随机抽一个 t、一步造出 $x_t$」;
(b) 反向均值可表为噪声的线性函数;(c) 高斯 KL 退化为均值匹配 = 噪声匹配。
三层巧合叠起来,才得到这个「预测噪声」的朴素目标。

再强调一遍(最重要的一句话)

模型学的是「噪声 $\epsilon$」,不是「干净图 $x_0$」。
干净图是推理时通过「反复调用去噪网络」间接还原的,不是网络的直接输出。

2.6 完整训练流程(把公式串成伪代码)

# 准备(一次性)
betas      = linspace(1e-4, 0.02, T)      # 噪声时间表
alphas     = 1 - betas
alpha_bars = cumprod(alphas)              # \bar\alpha_t

for each batch of real images x0:
    t    = randint(0, T)                  # 随机抽一个时间步
    eps  = randn_like(x0)                 # 抽真实噪声
    xt   = sqrt(alpha_bars[t]) * x0 + sqrt(1 - alpha_bars[t]) * eps   # 公式(2) 一步加噪
    pred = model(xt, t)                   # 网络预测噪声 \epsilon_\theta
    loss = mse(pred, eps)                 # 公式(8)
    loss.backward(); opt.step()

注意这里没有任何循环——全靠封闭形式 (2) 一步到位。这就是 DDPM 训练快又稳的原因。


2.7 推理(采样):从噪声一步步退回去

训练好后,$\epsilon_\theta$ 固定。我们从这个分布采样(从 $x_T\sim\mathcal N(0,1)$ 出发):

$$ x_{t-1} = \frac{1}{\sqrt{\alpha_t}}\left(x_t - \frac{1-\alpha_t}{\sqrt{1-\bar\alpha_t}}\,\epsilon_\theta(x_t,t)\right) + \sigma_t\,z \tag{9} $$

其中 $z\sim\mathcal N(0,1)$ 是采样时的新随机噪声,$\sigma_t$ 是反向方差(DDPM 取固定值)。 最后到 $t=1$ 时不再加 $z$(或取很小),得到 $x_0$。

用最小例子跑一遍「推理」

  • 起点:$x_{1000} \sim \mathcal N(0,1)$,假设抽到 $x_{1000}=0.3$(基本是纯噪声)。
  • 每一步:网络看到 $x_t$ 和 $t$,预测这步噪声 $\epsilon_\theta$,按 (9) 算 $x_{t-1}$。
  • 随着 $t$ 减小,$\sqrt{\bar\alpha_t}$ 变大(信号权重回升),噪声项变小。
  • 走到 $t=0$,输出从「噪声」变成了「一个有结构的值」——对应一张去噪后的图。

为什么「反复去噪」就能生成合理图片? 因为训练时,网络在千万张真实图上学会了「给定带噪图,噪声应该长什么样」。 推理时每次都按真实数据学来的规律把噪声抠掉,抠到最后剩下的就是「符合真实数据分布」的结构。 (类比:一个看过一万张猫的人,给你一张糊猫图,他每次都能正确指出「这里糊的是猫耳朵的轮廓」, 反复擦,最后就还原出一只合理的猫——哪怕他从来没见过这张特定的图。)


2.8 一页速查(背这 5 个公式就够)

公式名字用途
$\alpha_t=1-\beta_t,\ \bar\alpha_t=\prod\alpha_s$噪声表准备系数
$x_t=\sqrt{\bar\alpha_t}x_0+\sqrt{1-\bar\alpha_t}\epsilon$前向封闭形式训练时一步加噪 (2)
$\mathcal L=\\epsilon-\epsilon_\theta(x_t,t)\^2$简化损失训练目标 (8)
$\mu_\theta=\frac{1}{\sqrt{\alpha_t}}\!\left(x_t-\frac{1-\alpha_t}{\sqrt{1-\bar\alpha_t}}\epsilon_\theta\right)$反向均值去噪一步核心 (7)
$x_{t-1}=\mu_\theta+\sigma_t z$采样更新推理迭代 (9)

2.9 自测(合上文档试试)

  1. 用 1 维例子手算:$x_0=3,\ \beta=0.2$,抽 $\epsilon=0.4$,算 $x_1$。
  2. 写出口语版:为什么训练能「一步跳到任意 t」而不用循环?
  3. 公式 (2) 里 $\sqrt{\bar\alpha_t}$ 和 $\sqrt{1-\bar\alpha_t}$ 分别随 $t$ 怎么变?$t=T$ 时 $x_T$ 是什么?
  4. 训练目标为什么是「预测噪声」而不是「预测干净图」?简述三层原因。
  5. 推理时第一步 $x_T$ 从哪来?最后一步为什么不加随机噪声?
  6. (进阶)如果 $\beta_t$ 全设为 0.5(很大),前向会怎样?训练/采样会受什么影响?

2.10 行动项

  • [ ] 手算 2.9 第 1 题,验证和公式 (2) 一致
  • [ ] 在纸上推导一次 2.3 的「两步合并成一个噪声」(方差相加那步)
  • [ ] 用 numpy 写 q_sample(x0, t, alpha_bars, noise) 并打印几张图的噪声渐进图
  • [ ] 读 DDPM 原论文(Ho et al. 2020)第 2 节,对照本笔记确认符号一致
  • [ ] 确认能不看资料默写出 2.8 的 5 个公式,再进入阶段 3(U-Net)
阶段 2 完成标志:能口头+手写推导 (2)(7)(8),并说清「为什么预测噪声就行」。
之后阶段 3 学网络结构、阶段 4 把它们写成可运行 PyTorch。