📑 本页目录(点开跳转)
02b · 调度表与反向过程
⏱ 26 分钟 | ⭐ β 那张表在决定什么,以及反向为什么不能照抄前向
🎯 一句话
上一页说前向是一条写死的公式 —— 但那张表具体怎么写,直接决定有多少步是白走的。 这一页先讲调度表,再回答那个自然会冒出来的问题: 既然前向能一步跳到任意 $t$,反向为什么必须一步步走?
📉 四、β 调度:这条曲线在决定什么
$\beta_t$ 那张表怎么填,决定了「信息被毁掉的节奏」。两个常见选择:
- linear:$\beta_t$ 从 0.0001 线性涨到 0.02(DDPM 原文)
- cosine:不直接定 $\beta$,而是先画出 $\bar\alpha_t$ 想要的形状,再反推 $\beta$(Improved DDPM)
$$\bar\alpha_t = \frac{f(t)}{f(0)}, \qquad f(t) = \cos^2\!\left(\frac{t/T + s}{1+s}\cdot\frac{\pi}{2}\right), \quad s = 0.008$$
人话:cosine 让「原图还剩多少」沿一条平缓的余弦曲线下落,而不是像 linear 那样前段陡跌。那个小小的 $s$ 是防止 $t=0$ 附近 $\beta$ 掉到 0 的偏置项。
# 两种 β 调度下,「原图还剩多少」随 t 怎么衰减
import torch
T = 1000
# linear:DDPM 原论文
beta_lin = torch.linspace(1e-4, 0.02, T)
abar_lin = torch.cumprod(1 - beta_lin, dim=0)
# cosine:Improved DDPM 提出的,直接定义 ᾱ_t 而不是先定义 β_t
s = 0.008
t = torch.arange(T + 1, dtype=torch.float64)
f = torch.cos((t / T + s) / (1 + s) * torch.pi / 2) ** 2
abar_cos = (f / f[0])[1:] # ⭐ 先画出 ᾱ 的形状,β 是反推出来的
print(f"{'t':>5} {'sqrt_ab_lin':>12} {'sqrt_ab_cos':>12} {'SNR linear':>11} {'SNR cosine':>11}")
for i in [0, 99, 199, 399, 599, 799, 999]:
al, ac = abar_lin[i].item(), abar_cos[i].item()
print(f"{i+1:5d} {al**0.5:11.4f} {ac**0.5:11.4f} "
f"{al/(1-al):11.3f} {ac/(1-ac):11.3f}")
# 「一半信号一半噪声」(SNR = 1)出现在哪一步
half_lin = int((abar_lin / (1 - abar_lin) >= 1).sum())
half_cos = int((abar_cos / (1 - abar_cos) >= 1).sum())
print(f"\nSNR 掉到 1 之前,linear 撑了 {half_lin} 步({half_lin/T:.0%}),"
f"cosine 撑了 {half_cos} 步({half_cos/T:.0%})")
print(f"终点 sqrt(abar_T):linear {abar_lin[-1].item()**0.5:.5f},cosine {abar_cos[-1].item()**0.5:.5f}")
真实输出:
t sqrt_ab_lin sqrt_ab_cos SNR linear SNR cosine
1 0.9999 1.0000 9997.341 24221.327
100 0.9471 0.9859 8.710 34.833
200 0.8118 0.9480 1.933 8.872
400 0.4418 0.8047 0.242 1.837
600 0.1609 0.5838 0.027 0.517
800 0.0391 0.3067 0.002 0.104
1000 0.0064 0.0000 0.000 0.000
SNR 掉到 1 之前,linear 撑了 259 步(26%),cosine 撑了 496 步(50%)
终点 sqrt(abar_T):linear 0.00635,cosine 0.00000
这里的 SNR(信噪比) 就是 $\bar\alpha_t / (1-\bar\alpha_t)$:信号功率比噪声功率。SNR = 1 是「一半信号一半噪声」那条线。
⭐ 这张表就是 cosine 后来更常用的全部理由:
| 对比项 | linear | cosine |
|---|---|---|
| SNR ≥ 1 撑到第几步 | 259(26%) | 496(50%) |
| $t=400$ 时原图还剩 | 0.44 | 0.80 |
| 后段在干什么 | 74% 的步数花在几乎纯噪声上 | 一半步数留给还有内容的区间 |
linear 调度把信息毁得太快:走到四分之一,图就已经糊到看不出内容了,剩下 740 步都在给一团接近纯噪声的东西继续加噪 —— 这些步既学不到什么,采样时也白走。cosine 把衰减摊平,每一步都还在做有意义的事。
⚠️ cosine 的一个实现细节:按上式反推出来的 $\beta_t$ 在最后一步会等于 1.0(因为 $s$ 的定义让 $\bar\alpha_T$ 恰好为 0),代进后面的公式会除以零。所有实现都会把 $\beta$ clip 到 0.999。抄公式时别漏了这一句。
🔄 五、反向过程:唯一需要学的那一半
要生成图,就得反着问:给定 $x_t$,$x_{t-1}$ 是什么?
直接求 $q(x_{t-1}\mid x_t)$ 要用到整个数据分布 —— 而那正是你想学的东西,绕回去了。
⭐ 但如果额外告诉你 $x_0$,这个后验有闭式解,而且还是个高斯:
$$q(x_{t-1}\mid x_t, x_0) = \mathcal{N}\!\left(\tilde\mu_t(x_t, x_0),\; \tilde\beta_t I\right)$$
$$\tilde\mu_t = \frac{\sqrt{\bar\alpha_{t-1}}\,\beta_t}{1-\bar\alpha_t}\,x_0 + \frac{\sqrt{\alpha_t}\,(1-\bar\alpha_{t-1})}{1-\bar\alpha_t}\,x_t, \qquad \tilde\beta_t = \frac{1-\bar\alpha_{t-1}}{1-\bar\alpha_t}\,\beta_t$$
人话:已知起点和终点,中间那一步就是两者的一个加权平均,权重由调度决定,再配一个固定的方差。$t$ 大的时候权重偏向 $x_t$,$t$ 小的时候偏向 $x_0$。
问题只剩一个:推理时你没有 $x_0$(有的话还生成什么)。所以拿一个网络去估它。而由第二节的闭式采样式一变形,
$$x_0 = \frac{x_t - \sqrt{1-\bar\alpha_t}\;\varepsilon}{\sqrt{\bar\alpha_t}}$$
知道 $x_t$ 和 $\varepsilon$,就等于知道 $x_0$。 于是「估原图」和「估噪声」在数学上是同一件事 —— ⭐ 而工程上普遍选后者,为什么,就是下一章的正题。
⭐ 所以「模型到底学什么」这个问题有了确切答案: 它学的不是「怎么画一只猫」,而是「给你一张糊掉的图和一个时刻 $t$,指出这上面加了哪些噪声」。 这是一个回归任务,不是什么神秘的创作能力。至于这个网络长什么样(U-Net、DiT),是第 7 章的事。
🚧 为什么前向能一步到位,反向必须一步步走
| 对比项 | 前向 | 反向 |
|---|---|---|
| 每一步在做什么 | 加一份和数据无关的高斯噪声 | 问「这坨像素最可能是从什么演变来的」 |
| 是不是线性 | 是(乘一个数 + 加噪声) | 否 —— 网络是非线性的 |
| 能不能合并 | ⭐ 能,两个高斯相加还是高斯 | ❌ 不能,两个非线性映射合起来没有闭式 |
所以那 1000 步是一步一步走出来的,这就是扩散模型慢的根源。 ⚠️ 但「不能精确合并」不等于「不能近似跳过」—— 怎么把 1000 步压到 50 步,是第 4 章的正题。
🔗 这一章连到哪里
| 相关的地方 | 为什么 |
|---|---|
| 全景导论 10b · 图像生成 | 14 分钟的鸟瞰版,讲为什么图像走扩散、语言走自回归。它的深挖清单把这一整块判成「要外找」,本章就是那份外找的第一页 |
| 强化学习基础 02 · MDP | 第一节说前向过程是马尔可夫链。那一章专门有一节讲马尔可夫性质(「未来只取决于现在,与过去无关」),还给了个反例说明它是「状态怎么定义」的属性、不是问题本身的属性 |
| 机器学习的数学原理 13 · EM 与高斯混合 | $x_1 \dots x_T$ 全是隐变量 —— 扩散模型是个隐变量模型。那一章用高斯混合把「有隐变量该怎么办」讲透了,下一章的变分下界直接接着它用 |
| 机器学习与深度学习基础 15 · PyTorch 实战手册 | 上面几段代码里的 torch.cumprod、randn_like、广播如果读着吃力,那一章有一份可直接抄的 PyTorch 模板 |
✅ 检查点
- linear 和 cosine 两种调度,SNR 掉到 1 各撑了多少步?为什么这个差别让 cosine 更受欢迎?
- 模型到底在学什么?用一句不含「生成」「创作」的话说清楚。
- 前向能一步跳到任意 $t$,反向为什么不能?
👀 答案
- linear 259 步(26%),cosine 496 步(50%)。linear 把信息毁得太快,剩下 74% 的步数都在给一团接近纯噪声的东西继续加噪,既学不到东西、采样时也白走;cosine 把衰减摊平,每一步都还在做有意义的事。
- 给定一张加了噪的图和一个时刻 $t$,指出这上面加了哪些噪声。 这是一个回归任务。
- 前向每步是「加一份和数据无关的噪声」,是线性的、两个高斯能合并成一个;反向每步要问「这坨像素最可能是从什么演变来的」,依赖数据分布而且是非线性的,两个非线性映射合起来没有闭式解。(近似跳过是另一回事,04 章讲。)
🛑 可以停在这里
到这里前向和反向两条路你都看清了:一条写死、一条要学,而且清楚了为什么反向不能像前向那样抄近道。
⚠️ 什么时候回来:读完 04 章之后 —— 那一章会告诉你反向其实可以跳步,只是换掉了一个前提。回头再看这一页最后那一节,会更清楚它换掉的是什么。
⚡ 走神救援
先记住这几件事
- 调度表控制不同时间段保留多少信号,不只是给步骤编号。
- 用信噪比比较线性和余弦调度,观察信息何时接近消失。
- 前向闭式加噪不等于反向也有精确的一步解;采样加速另需合适的方法。
下一节 👉 03-训练目标为什么是预测噪声.md