文章目录

本文通过直观的比喻和极简的 PyTorch 代码,彻底拆解扩散模型最核心的数学骨架——为什么模型能从噪声里“洗”回图像?以及你必须真正搞懂的四个关键词: q ( x t ∣ x 0 ) q(x_t|x_0) q(xtx0)、反向采样、scheduler、DDPM/DDIM。


一、为什么扩散模型居然能“从一团雪花里洗出一张图”?

第一次接触扩散模型时,很多人都会有同一个震撼感:

一张图片被加噪到几乎什么都看不清后,模型居然还能一步步把它还原回来?这不是魔法吗?

其实它不是魔法,而是一个非常精妙的“先学会如何弄脏,再学会如何反着擦干净”的过程。

1. 核心比喻:先学会“泼墨”,才能学会“清洗”

想象你手里有一张清晰的照片。

老师做了这样一件事:

  1. 第一步,往照片上轻轻泼一点灰
  2. 第二步,再泼一点
  3. 第三步,再泼一点
  4. 重复很多很多次

最后,这张照片完全被灰尘淹没,变成了一张纯噪声图。

这叫 前向扩散(Forward Diffusion)

然后老师让学生干什么?

不是直接从纯噪声凭空猜出整张照片,而是只做一个更容易的小任务:

“每次只猜:这一小步,刚才被加进来了多少噪声?”

如果学生每一小步都猜得准,他就能把图一小步一小步擦回来。

这叫 反向扩散(Reverse Diffusion)

所以扩散模型真正的厉害之处在于:

它把一个极难的‘从纯噪声直接画图’问题,拆成了很多个很小的‘这一小步该减掉多少噪声’问题。


二、先建立全局观:扩散模型到底在做什么?

你可以先把扩散过程粗暴记成下面两条:

1. 正向过程:不断加噪

x 0 → x 1 → x 2 → ⋯ → x T x_0 \rightarrow x_1 \rightarrow x_2 \rightarrow \cdots \rightarrow x_T x0x1x2xT

  • x 0 x_0 x0:原始干净图像
  • x t x_t xt:第 t t t 步的带噪图像
  • x T x_T xT:接近纯高斯噪声

2. 反向过程:不断去噪

x T → x T − 1 → x T − 2 → ⋯ → x 0 x_T \rightarrow x_{T-1} \rightarrow x_{T-2} \rightarrow \cdots \rightarrow x_0 xTxT1xT2x0

生成时我们没有真实图片,只有一团随机噪声 x T x_T xT

模型的工作就是:

在每一步根据当前的带噪图 x t x_t xt,预测“该减掉多少噪声”,然后得到更干净一点的 x t − 1 x_{t-1} xt1

一路走到最后,就还原出了一张图。


三、前向扩散公式: q ( x t ∣ x t − 1 ) q(x_t|x_{t-1}) q(xtxt1) 到底是什么意思?

扩散模型的前向过程是人为规定好的,不需要学习。

它的定义非常简单:

q ( x t ∣ x t − 1 ) = N ( x t ; 1 − β t   x t − 1 , β t I ) q(x_t|x_{t-1}) = \mathcal{N}\left(x_t; \sqrt{1-\beta_t}\,x_{t-1}, \beta_t I\right) q(xtxt1)=N(xt;1βt xt1,βtI)

第一次看到这个公式,很多人会被吓住。其实它只是在说一件很朴素的事:

从第 t − 1 t-1 t1 步到第 t t t 步,我们做两件事:

  1. 把原图信号稍微缩小一点
  2. 再往里面加一点高斯噪声

1. 公式拆成人话

把上面的高斯分布写成显式采样形式,就是:

x t = 1 − β t   x t − 1 + β t   ϵ , ϵ ∼ N ( 0 , I ) x_t = \sqrt{1-\beta_t}\,x_{t-1} + \sqrt{\beta_t}\,\epsilon,\quad \epsilon \sim \mathcal{N}(0, I) xt=1βt xt1+βt ϵ,ϵN(0,I)

这里:

  • 1 − β t \sqrt{1-\beta_t} 1βt :负责“保留多少原信号”
  • β t \sqrt{\beta_t} βt :负责“加入多少新噪声”

其中 β t \beta_t βt 一般很小,比如 0.00010.001 这种量级。

所以每一步并不是“啪”一下把图毁掉,而是:

只轻轻地往图上撒一层非常薄的噪声。

2. 直观比喻:照片每轮都被盖一层半透明毛玻璃

假设你把一张照片放在桌上。

每一轮你都给它盖上一层很薄的半透明毛玻璃:

  • 第一层时,还基本看得清
  • 第十层时,轮廓开始模糊
  • 第一百层时,只剩模糊的色块
  • 第一千层时,和纯随机噪声差不多

这就是前向扩散的本质。

它不是暴力破坏,而是:

很多次、每次都很轻微的模糊与加噪。


四、为什么大家老在讲 q ( x t ∣ x 0 ) q(x_t|x_0) q(xtx0)?它比 q ( x t ∣ x t − 1 ) q(x_t|x_{t-1}) q(xtxt1) 强在哪?

虽然前向过程是一步一步定义的,但训练时如果每次都真的从 x 0 x_0 x0 加噪到 x t x_t xt,那就太慢了。

大家更想直接问:

“能不能一步就从干净图 x 0 x_0 x0 直接采样出任意时刻的 x t x_t xt?”

答案是:可以。这就是著名的:

q ( x t ∣ x 0 ) q(x_t|x_0) q(xtx0)

1. 先引入两个记号

定义:

α t = 1 − β t \alpha_t = 1 - \beta_t αt=1βt

再定义累计乘积:

α ˉ t = ∏ s = 1 t α s \bar{\alpha}_t = \prod_{s=1}^{t} \alpha_s αˉt=s=1tαs

它表示:

从第 1 步到第 t t t 步,原始信号总共还剩下多少。

2. 结论公式

通过把一步一步的高斯噪声链条连起来,可以得到:

q ( x t ∣ x 0 ) = N ( x t ; α ˉ t   x 0 ,    ( 1 − α ˉ t ) I ) q(x_t|x_0) = \mathcal{N}\left(x_t; \sqrt{\bar{\alpha}_t}\,x_0,\; (1-\bar{\alpha}_t)I\right) q(xtx0)=N(xt;αˉt x0,(1αˉt)I)

等价地写成采样形式就是:

x t = α ˉ t   x 0 + 1 − α ˉ t   ϵ , ϵ ∼ N ( 0 , I ) x_t = \sqrt{\bar{\alpha}_t}\,x_0 + \sqrt{1-\bar{\alpha}_t}\,\epsilon,\quad \epsilon \sim \mathcal{N}(0, I) xt=αˉt x0+1αˉt ϵ,ϵN(0,I)

这就是扩散模型最关键的一条公式之一。

3. 它到底在说什么?

这条公式在说:

t t t 时刻的带噪图,其实就是“干净图的一部分 + 高斯噪声的一部分”的线性混合。

并且:

  • t t t 很小时, α ˉ t \bar{\alpha}_t αˉt 接近 1,原图占主导
  • t t t 很大时, α ˉ t \bar{\alpha}_t αˉt 接近 0,噪声占主导

所以可以把它想象成一个“信号衰减旋钮”:

  • 越往后,原图音量越小
  • 噪声音量越大

最后原图彻底被噪声淹没。

4. 为什么这个公式如此重要?

因为它让训练变得非常高效。

原本你要这样做:

  • x 0 x_0 x0 开始,循环加噪 t t t 次,才能得到 x t x_t xt

现在你可以一步到位:

  • 直接采样一个噪声 ϵ \epsilon ϵ
  • 用上面那条闭式公式一次算出 x t x_t xt

于是训练时只需要随机采一个 t t t,就能直接制造对应噪声级别的训练样本。

这就是扩散模型能高效训练的关键原因之一。


五、这条闭式公式是怎么“长”出来的?

你不一定要手推到每一个细节,但一定要吃透它背后的直觉。

先看前两步:

x 1 = α 1 x 0 + 1 − α 1 ϵ 1 x_1 = \sqrt{\alpha_1}x_0 + \sqrt{1-\alpha_1}\epsilon_1 x1=α1 x0+1α1 ϵ1

x 2 = α 2 x 1 + 1 − α 2 ϵ 2 x_2 = \sqrt{\alpha_2}x_1 + \sqrt{1-\alpha_2}\epsilon_2 x2=α2 x1+1α2 ϵ2

x 1 x_1 x1 代进去:

x 2 = α 2 α 1 x 0 + α 2 ( 1 − α 1 ) ϵ 1 + 1 − α 2 ϵ 2 x_2 = \sqrt{\alpha_2\alpha_1}x_0 + \sqrt{\alpha_2(1-\alpha_1)}\epsilon_1 + \sqrt{1-\alpha_2}\epsilon_2 x2=α2α1 x0+α2(1α1) ϵ1+1α2 ϵ2

你会发现两件事:

  1. 原始信号前面的系数在不断连乘
  2. 所有噪声项加起来以后,依然还是一个高斯噪声

为什么噪声加起来还是高斯?

因为:

独立高斯变量的线性组合,仍然是高斯变量。

于是我们就可以把一大堆噪声项合并成一个新的标准高斯 ϵ \epsilon ϵ,最终得到:

x t = α ˉ t x 0 + 1 − α ˉ t ϵ x_t = \sqrt{\bar{\alpha}_t}x_0 + \sqrt{1-\bar{\alpha}_t}\epsilon xt=αˉt x0+1αˉt ϵ

所以这条公式本质上并不神秘,它只是:

“信号一路衰减 + 许多小噪声叠加后仍是高斯”的结果。


六、训练时模型到底学什么?为什么不直接预测 x 0 x_0 x0

训练时,我们已经知道:

  • 干净图 x 0 x_0 x0
  • 随机采样的噪声 ϵ \epsilon ϵ
  • 按公式合成出来的带噪图 x t x_t xt

所以我们完全知道“这张图里被加进去了什么噪声”。

于是最自然的训练目标是:

让模型看到 x t x_t xt 和时间步 t t t 后,预测出这次加入的噪声 ϵ \epsilon ϵ

也就是:

ϵ ^ θ = ϵ θ ( x t , t ) \hat{\epsilon}_\theta = \epsilon_\theta(x_t, t) ϵ^θ=ϵθ(xt,t)

损失函数通常写成:

L s i m p l e = E x 0 , ϵ , t [ ∥ ϵ − ϵ θ ( x t , t ) ∥ 2 ] \mathcal{L}_{simple} = \mathbb{E}_{x_0,\epsilon,t}\left[\|\epsilon - \epsilon_\theta(x_t, t)\|^2\right] Lsimple=Ex0,ϵ,t[ϵϵθ(xt,t)2]

1. 为什么预测噪声很合理?

因为从公式:

x t = α ˉ t x 0 + 1 − α ˉ t ϵ x_t = \sqrt{\bar{\alpha}_t}x_0 + \sqrt{1-\bar{\alpha}_t}\epsilon xt=αˉt x0+1αˉt ϵ

你一旦知道了 x t x_t xt 和噪声 ϵ \epsilon ϵ,就可以反推出干净图 x 0 x_0 x0

x 0 = x t − 1 − α ˉ t   ϵ α ˉ t x_0 = \frac{x_t - \sqrt{1-\bar{\alpha}_t}\,\epsilon}{\sqrt{\bar{\alpha}_t}} x0=αˉt xt1αˉt ϵ

也就是说:

预测噪声,本质上等价于在恢复原图。

2. 为什么大家更喜欢预测噪声,而不是直接预测 x 0 x_0 x0

因为预测噪声有几个实际好处:

  • 目标形式统一,所有时间步都能写成同一个样子
  • 数值上往往更稳定
  • 在早期 DDPM 训练里效果很好

当然,后来也出现了:

  • 直接预测 x 0 x_0 x0
  • 预测 velocity( v v v-prediction)

这些都是等价重参数化的不同口味。

但入门时,你先牢牢记住:

最经典的扩散训练,就是“看带噪图,猜这次加进去的噪声”。


七、反向扩散为什么可能?从纯噪声往回走,凭什么走得通?

这可能是全篇最关键的心理门槛。

很多人会问:

正向加噪是简单的,但反着来不是“一杯泼出去的水收回来”吗?为什么能做到?

答案是:

如果一步加得足够小,那么“从 x t x_t xt 回到 x t − 1 x_{t-1} xt1”这件事就是可学习的。

1. 关键思想:大问题拆成很多个很小的逆问题

如果你问模型:

  • “这团纯噪声原来是哪只猫?”

这很难。

但如果你问:

  • “这一步只加了一点点噪声,现在请你把这点噪声减回去。”

这就容易很多。

所以扩散模型不是在做一次巨大的逆变换,而是在做:

成百上千次微小修正。

2. 概率视角:学习一个反向条件分布

正向过程是:

q ( x t ∣ x t − 1 ) q(x_t|x_{t-1}) q(xtxt1)

反向过程想学的是:

p θ ( x t − 1 ∣ x t ) p_\theta(x_{t-1}|x_t) pθ(xt1xt)

也就是:

给定当前带噪状态 x t x_t xt,前一时刻那个稍微更干净的图像 x t − 1 x_{t-1} xt1 最可能长什么样。

实践中,我们通常把这个反向分布也建模成高斯:

p θ ( x t − 1 ∣ x t ) = N ( x t − 1 ; μ θ ( x t , t ) , Σ θ ( x t , t ) ) p_\theta(x_{t-1}|x_t) = \mathcal{N}(x_{t-1}; \mu_\theta(x_t,t), \Sigma_\theta(x_t,t)) pθ(xt1xt)=N(xt1;μθ(xt,t),Σθ(xt,t))

其中最核心的是学会均值 μ θ \mu_\theta μθ

3. 直观比喻:雾中下楼梯

想象你在浓雾中下楼。

你看不清一楼长什么样,但你通常还能判断:

  • 下一阶台阶大概在哪里
  • 身体应该往哪个方向挪一点

扩散模型也是一样。

它未必一眼就知道完整的原图,但它可以每一步都做出一个比较靠谱的局部修正:

“往这边挪一点,会更像干净图。”

反复很多次,就回到了图像分布。


八、从噪声预测到反向采样:DDPM 的核心更新公式

如果模型已经预测出了噪声 ϵ θ ( x t , t ) \epsilon_\theta(x_t, t) ϵθ(xt,t),那么我们就能先估计当前时刻对应的干净图:

x ^ 0 = x t − 1 − α ˉ t   ϵ θ ( x t , t ) α ˉ t \hat{x}_0 = \frac{x_t - \sqrt{1-\bar{\alpha}_t}\,\epsilon_\theta(x_t,t)}{\sqrt{\bar{\alpha}_t}} x^0=αˉt xt1αˉt ϵθ(xt,t)

接着,DDPM 会根据这份估计,构造出反向一步的高斯均值。

常见的一种写法是:

μ θ ( x t , t ) = 1 α t ( x t − 1 − α t 1 − α ˉ t ϵ θ ( x t , t ) ) \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) μθ(xt,t)=αt 1(xt1αˉt 1αtϵθ(xt,t))

然后从下面这个高斯分布里采样:

x t − 1 ∼ N ( μ θ ( x t , t ) , σ t 2 I ) x_{t-1} \sim \mathcal{N}\left(\mu_\theta(x_t,t), \sigma_t^2 I\right) xt1N(μθ(xt,t),σt2I)

1. 这条公式到底在干什么?

它的直觉其实不复杂:

  • 当前图 x_t 里既有信号也有噪声
  • 模型先估计“哪些部分是噪声”
  • 然后从当前图里把这部分噪声扣掉一些
  • 得到更干净一点的上一时刻图像

2. 为什么还要再采一次随机噪声?

因为 DDPM 的反向过程本身是随机的

这意味着:

  • 即使同样的初始噪声和提示词
  • 在反向每一步里加入的随机项不同
  • 也可能导致最终图像细节不同

这就是 DDPM 采样“有随机性”的来源。

你可以把它理解成:

老师每次给你一个“朝正确方向修正”的建议,但允许你在这个方向附近有一点自然抖动。


九、scheduler 到底是什么?它为什么像“去噪节拍器”?

很多初学者对 scheduler 很迷糊,觉得它像某种神秘插件。

其实 scheduler 本质上就是:

规定“每一步加多少噪声 / 去多少噪声”的时间表。

1. 在前向过程里,scheduler 决定 β t \beta_t βt

还记得前向公式:

x t = 1 − β t x t − 1 + β t ϵ x_t = \sqrt{1-\beta_t}x_{t-1} + \sqrt{\beta_t}\epsilon xt=1βt xt1+βt ϵ

这里的 β t \beta_t βt 不是随便来的,而是由一个 schedule 决定的。

常见的 schedule 有:

  • 线性增长(linear)
  • 余弦调度(cosine)
  • 其他专门设计的噪声曲线

这相当于规定了一张“噪声进度表”:

  • 第 1 步加多少
  • 第 10 步加多少
  • 第 500 步加多少

2. 在采样过程里,scheduler 决定“怎么走回去”

采样时 scheduler 还负责另一件事:

根据模型预测的噪声,按照某种数值积分规则,算出下一步的 x t − 1 x_{t-1} xt1

这件事很像:

  • 模型负责说“应该往哪个方向去噪”
  • scheduler 负责说“这一步具体迈多大步、要不要带随机性”

所以你可以把二者分工记成:

  • 模型:像导航,告诉你方向
  • scheduler:像驾驶策略,决定这一步怎么走

3. 为什么同一个模型换个 scheduler,生成效果就会变?

因为 scheduler 直接影响:

  • 步长大小
  • 随机性强弱
  • 采样速度
  • 细节保真度

这就像同一个人从山顶下山:

  • 一种策略是每次走小步,稳但慢
  • 一种策略是迈大步,快但风险高
  • 一种策略是完全按固定轨迹滑下去

所以 scheduler 不是模型参数本身,但它深刻影响生成质量和速度


十、DDPM 到底是什么?它的气质是“稳、随机、慢”

DDPM(Denoising Diffusion Probabilistic Models)是最经典的扩散采样范式之一。

它的特点可以概括成三点:

1. 每一步都带随机采样

DDPM 在反向更新时,通常会从高斯分布里再采一次随机噪声。

也就是说:

x t − 1 = μ θ ( x t , t ) + σ t z , z ∼ N ( 0 , I ) x_{t-1} = \mu_\theta(x_t, t) + \sigma_t z,\quad z \sim \mathcal{N}(0,I) xt1=μθ(xt,t)+σtz,zN(0,I)

这使得采样是随机轨迹

2. 步数通常较多

经典 DDPM 往往需要很多步,比如:

  • 1000 步
  • 250 步
  • 至少几十步以上

每一步只修一点点,因此稳定,但慢。

3. 直观气质:像“谨慎的工匠”

DDPM 像一个非常谨慎的修复师:

  • 每次都只敢擦一点
  • 每次都留一点随机探索
  • 因此通常更稳,更接近原始概率建模设定

但代价就是:

慢。


十一、DDIM 又是什么?为什么它能更快?

DDIM(Denoising Diffusion Implicit Models)可以理解成:

在保留同一个训练模型的前提下,设计了一种更“确定性”的反向走法。

它最重要的思想是:

不一定每一步都要重新注入随机性。

1. DDIM 的直观理解:走“确定性捷径”

DDPM 像是在山路上一级一级往下走,而且每一级都允许一点随机抖动。

DDIM 则更像:

  • 你已经知道大致方向
  • 那我就沿着一条更平滑的确定性轨迹往下走
  • 可以少走很多步

于是 DDIM 常常能在:

  • 50 步
  • 20 步
  • 甚至更少步

就生成出还不错的图。

2. DDIM 的核心更新直觉

DDIM 通常先利用模型预测噪声,估计出 x ^ 0 \hat{x}_0 x^0,再根据一个确定性公式直接构造下一步:

粗暴理解就是:

  • 先推断“这一步对应的干净图大概是什么”
  • 再根据预定的噪声轨迹,直接跳到更早一步

它不像 DDPM 那样每一步都强制重新随机采样。

如果把它写得更具体一点,DDIM 的思路是:

先根据模型预测的噪声,估计出当前时刻对应的干净图:

x ^ 0 = x t − 1 − α ˉ t   ϵ θ ( x t , t ) α ˉ t \hat{x}_0 = \frac{x_t - \sqrt{1-\bar{\alpha}_t}\,\epsilon_\theta(x_t,t)}{\sqrt{\bar{\alpha}_t}} x^0=αˉt xt1αˉt ϵθ(xt,t)

然后不再像 DDPM 那样“先算均值,再额外采一个随机噪声”,而是直接把这对信息:

  • x ^ 0 \hat{x}_0 x^0:我推断出来的干净图
  • ϵ θ ( x t , t ) \epsilon_\theta(x_t,t) ϵθ(xt,t):我推断出来的噪声方向

投影到一个更早的时间点 t ′ t' t

x t ′ ≈ α ˉ t ′ x ^ 0 + 1 − α ˉ t ′   ϵ θ ( x t , t ) x_{t'} \approx \sqrt{\bar{\alpha}_{t'}}\hat{x}_0 + \sqrt{1-\bar{\alpha}_{t'}}\,\epsilon_\theta(x_t,t) xtαˉt x^0+1αˉt ϵθ(xt,t)

这条式子虽然看起来简短,但它背后的含义非常重要:

我并不是一级一级“随机摸索”着往回走,而是先估计“这张图真正应该长什么样”,再沿着一条一致的去噪轨迹,直接跳到更早一步。

3. 为什么 DDIM 更快?

很多人会误以为 DDIM 快,是因为它“单步计算更省”。

其实不是。

DDIM 和 DDPM 的单步核心成本都差不多,因为每一步都还是要跑一次同样的去噪网络 ϵ θ ( x t , t ) \epsilon_\theta(x_t,t) ϵθ(xt,t)

它真正快的原因是:

DDIM 允许你用更少的时间步完成采样。

也就是说:

  • DDPM 的快慢通常是:1 步网络前向 × 很多步
  • DDIM 的快慢通常是:1 步网络前向 × 更少步

4. 为什么 DDIM 敢“少走很多步”?

这才是本节最关键的点。

第一层原因:DDPM 每一步都重新加随机性,不太适合大跨步

DDPM 的更新是:

x t − 1 = μ θ ( x t , t ) + σ t z , z ∼ N ( 0 , I ) x_{t-1} = \mu_\theta(x_t, t) + \sigma_t z,\quad z \sim \mathcal{N}(0,I) xt1=μθ(xt,t)+σtz,zN(0,I)

也就是说,每往前走一步:

  • 先根据模型预测,往更干净方向修正
  • 再重新注入一点随机噪声

所以 DDPM 更像是在一条会抖动的山路上往下走:

  • 每一步都带一点随机摆动
  • 步子通常不能迈太大
  • 否则误差和随机扰动会更明显地积累

这就是为什么 DDPM 往往更适合走很多个小步。

第二层原因:DDIM 把反向过程改成了更接近 ODE 的确定性轨迹

DDIM 在最典型的 η = 0 情况下,不会在每一步重新采一个新的高斯噪声。

这意味着它不再是:

  • “边去噪,边重新随机抖一下”

而更像是:

  • “我先估计出当前对应的干净图 x ^ 0 \hat{x}_0 x^0
  • “再沿着这条一致的方向,直接映射到更早的时刻”

于是它更接近一条确定性轨迹

一旦轨迹是确定性的,你就不必老老实实走完训练时定义的全部 1000 个小台阶,而可以只选一串更稀疏的时间点,例如:

  • 999 -> 979 -> 959 -> ... -> 19 -> 0

也就是每次跨很多级台阶。

5. 最形象的比喻:为什么 DDIM 能跳步?

DDPM 像这样下楼:

  • 每下一层,都先看一下方向
  • 然后脚下还会随机晃一下
  • 所以你更愿意一阶一阶慢慢走

DDIM 像这样下楼:

  • 你已经大致看清了楼梯主方向
  • 而且脚下不会每一步都故意晃一下
  • 所以你可以直接跨好几阶

这就是 DDIM 能更快的真正原因:

不是它每一步更省算力,而是它的轨迹更平滑、更确定,因此允许稀疏采样和大步跳跃。

6. 用一句特别硬的总结来记

DDPM 更像在采样一个随机过程,所以通常小步走。
DDIM 更像在解一条确定性轨迹,所以可以大步跳。

7. 所以 DDIM 最终快在哪里?

因为它允许你:

  • 采样时不必严格走训练时那 1000 个小台阶
  • 可以挑一个更稀疏的时间步序列,比如只走 50 步

这就像本来有 1000 级小台阶,你现在每次跨 20 级。

所以速度大幅提升。

8. DDIM 的代价是什么?

通常是:

  • 速度更快
  • 随机性更少
  • 某些设置下多样性会下降

十二、DDPM 和 DDIM 的关系,不要死记公式,先抓住这件事

很多人学到这里会被一堆更新公式绕晕。

其实你只要先抓住这个核心区别:

1. 两者训练时通常可以共用同一个去噪模型

也就是说,网络本身都在学:

ϵ θ ( x t , t ) \epsilon_\theta(x_t, t) ϵθ(xt,t)

区别主要不在“模型长什么样”,而在于:

采样时你选择怎样的反向轨迹。

2. 两者最本质的区别

  • DDPM:把反向过程当作随机过程
  • DDIM:构造一条更确定性的隐式轨迹

所以它们更像:

  • 同一辆车
  • 两种不同驾驶方式

而不是两种完全不同的神经网络。


十三、极简 PyTorch:一步制造 x t x_t xt

先看最关键的训练公式如何落成代码。

import torch


def q_sample(x0, t, alpha_bar):
    """
    x0: 干净图像, shape = (B, C, H, W)
    t:  每个样本对应的时间步, shape = (B,)
    alpha_bar: 预先准备好的累计乘积表, shape = (T,)
    """
    noise = torch.randn_like(x0)

    # 取出每个样本对应时间步的 alpha_bar_t
    a_bar_t = alpha_bar[t].view(-1, 1, 1, 1)

    xt = torch.sqrt(a_bar_t) * x0 + torch.sqrt(1 - a_bar_t) * noise
    return xt, noise

这段代码干的事就是:

随机选一个时间步,把干净图和高斯噪声按比例混合,直接得到该时刻的带噪图。

这正是:

x t = α ˉ t x 0 + 1 − α ˉ t ϵ x_t = \sqrt{\bar{\alpha}_t}x_0 + \sqrt{1-\bar{\alpha}_t}\epsilon xt=αˉt x0+1αˉt ϵ

的直接实现。


十四、极简 PyTorch:训练时怎么学“猜噪声”

import torch.nn.functional as F


def training_step(model, x0, t, alpha_bar):
    xt, noise = q_sample(x0, t, alpha_bar)

    # 模型输入:带噪图 xt 和时间步 t
    pred_noise = model(xt, t)

    # 让模型去拟合真实噪声
    loss = F.mse_loss(pred_noise, noise)
    return loss

这就是扩散模型训练最核心的骨架。

它不像分类网络那样学 label,也不像 GPT 那样学 next token。

它学的是:

“给你一张带噪图,请告诉我里面混了多少噪声。”


十五、极简 PyTorch:DDPM 采样骨架长什么样?

@torch.no_grad()
def ddpm_sample(model, shape, alpha, alpha_bar, sigma):
    """
    shape: (B, C, H, W)
    alpha: 每一步的 alpha_t
    alpha_bar: 每一步的 alpha_bar_t
    sigma: 每一步反向采样的标准差
    """
    x = torch.randn(shape)  # 从纯噪声开始
    T = len(alpha)

    for t in reversed(range(T)):
        t_batch = torch.full((shape[0],), t, dtype=torch.long, device=x.device)
        eps_theta = model(x, t_batch)

        a_t = alpha[t]
        a_bar_t = alpha_bar[t]

        mean = (1 / torch.sqrt(a_t)) * (
            x - ((1 - a_t) / torch.sqrt(1 - a_bar_t)) * eps_theta
        )

        if t > 0:
            z = torch.randn_like(x)
            x = mean + sigma[t] * z
        else:
            x = mean

    return x

你看着这段代码时,脑子里要自动翻译成一句人话:

“先从纯噪声开始,模型每一轮估计噪声,再把当前图往更干净的方向推一点。”


十六、再把 scheduler 说透一点:它不是装饰件,而是采样的“算法外壳”

很多教程会把 scheduler 轻描淡写带过,这很容易让人产生误解。

其实在现代扩散框架里,scheduler 经常承担了非常多的工作:

1. 它保存整套时间表

比如:

  • betas
  • alphas
  • alphas_cumprod
  • 推理时实际使用哪些 timestep

2. 它实现一步步更新规则

比如:

  • DDPM 的随机更新
  • DDIM 的确定性/半确定性更新
  • 其他更高级的 ODE/SDE 求解器

3. 它控制速度与质量的权衡

同一个模型:

  • 用 100 步 scheduler,通常质量更稳
  • 用 20 步 scheduler,通常更快
  • 用不同公式的 scheduler,观感和细节会明显不同

所以你要建立一个非常重要的认知:

神经网络负责预测噪声。
scheduler 负责把“噪声预测”翻译成“下一步图像该怎么更新”。

这两者缺一不可。


十七、为什么扩散模型最终能学到“图像分布”?

从更高层的角度看,扩散模型之所以成立,是因为它做了这样一件事:

1. 正向过程把真实图像分布慢慢推向简单分布

真实图像分布很复杂:

  • 猫、狗、人脸、风景
  • 纹理、光照、结构都很复杂

但当你不断加高斯噪声后,这个复杂分布会逐渐变得简单,最后接近标准高斯分布。

也就是:

  • 难分布 -> 简单分布

2. 反向过程则学习把简单分布拉回复杂分布

训练好后,模型学会了一系列微小逆变换:

  • 从“很脏”变“稍微干净”
  • 从“稍微干净”变“更清晰”
  • 一步一步最终回到真实图像流形

所以你可以把扩散模型理解成:

它先把世界上复杂的图像分布打散成一锅高斯粥,再学会怎样把这锅粥一勺一勺重新摆盘成图片。


十八、你必须真正记住的 6 句话

如果这篇很长,你至少要把下面 6 句话刻进脑子里:

  1. 前向扩散不是暴力摧毁图像,而是很多次、每次都很轻微地加噪。

  2. q ( x t ∣ x 0 ) q(x_t|x_0) q(xtx0) 的闭式公式说明:任意时刻的带噪图,本质上就是“原图 + 噪声”的线性混合。

  3. 训练时模型最经典的任务不是直接画图,而是预测加入的噪声 ϵ \epsilon ϵ

  4. 反向生成不是一次性把纯噪声变成图片,而是很多次微小去噪修正

  5. scheduler 负责规定噪声时间表和反向更新策略,它直接影响速度和质量。

  6. DDPM 更随机、更稳、通常更慢;DDIM 更确定、更快、常用于加速采样。


十九、下一步最适合继续学什么?

如果你已经看懂这一篇,最自然的下一步有 3 个方向:

  1. Classifier-Free Guidance
    为什么一句提示词可以把采样轨迹“拉向”你想要的内容。

  2. 从噪声预测推到 x 0 x_0 x0 / v v v prediction
    三种参数化到底有什么关系,为什么现在很多模型喜欢 v-pred

  3. 把这套扩散公式和 DiT/U-Net 接起来
    也就是网络到底怎样接收 (x_t, t, condition) 并输出噪声。

Logo

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

更多推荐