扩散模型万字详细讲解(拆解数学公式保证你能看懂)
文章目录
-
- 一、为什么扩散模型居然能“从一团雪花里洗出一张图”?
- 二、先建立全局观:扩散模型到底在做什么?
- 三、前向扩散公式: q ( x t ∣ x t − 1 ) q(x_t|x_{t-1}) q(xt∣xt−1) 到底是什么意思?
- 四、为什么大家老在讲 q ( x t ∣ x 0 ) q(x_t|x_0) q(xt∣x0)?它比 q ( x t ∣ x t − 1 ) q(x_t|x_{t-1}) q(xt∣xt−1) 强在哪?
- 五、这条闭式公式是怎么“长”出来的?
- 六、训练时模型到底学什么?为什么不直接预测 x 0 x_0 x0?
- 七、反向扩散为什么可能?从纯噪声往回走,凭什么走得通?
- 八、从噪声预测到反向采样:DDPM 的核心更新公式
- 九、scheduler 到底是什么?它为什么像“去噪节拍器”?
- 十、DDPM 到底是什么?它的气质是“稳、随机、慢”
- 十一、DDIM 又是什么?为什么它能更快?
- 十二、DDPM 和 DDIM 的关系,不要死记公式,先抓住这件事
- 十三、极简 PyTorch:一步制造 x t x_t xt
- 十四、极简 PyTorch:训练时怎么学“猜噪声”
- 十五、极简 PyTorch:DDPM 采样骨架长什么样?
- 十六、再把 scheduler 说透一点:它不是装饰件,而是采样的“算法外壳”
- 十七、为什么扩散模型最终能学到“图像分布”?
- 十八、你必须真正记住的 6 句话
- 十九、下一步最适合继续学什么?
本文通过直观的比喻和极简的 PyTorch 代码,彻底拆解扩散模型最核心的数学骨架——为什么模型能从噪声里“洗”回图像?以及你必须真正搞懂的四个关键词: q ( x t ∣ x 0 ) q(x_t|x_0) q(xt∣x0)、反向采样、scheduler、DDPM/DDIM。
一、为什么扩散模型居然能“从一团雪花里洗出一张图”?
第一次接触扩散模型时,很多人都会有同一个震撼感:
一张图片被加噪到几乎什么都看不清后,模型居然还能一步步把它还原回来?这不是魔法吗?
其实它不是魔法,而是一个非常精妙的“先学会如何弄脏,再学会如何反着擦干净”的过程。
1. 核心比喻:先学会“泼墨”,才能学会“清洗”
想象你手里有一张清晰的照片。
老师做了这样一件事:
- 第一步,往照片上轻轻泼一点灰
- 第二步,再泼一点
- 第三步,再泼一点
- 重复很多很多次
最后,这张照片完全被灰尘淹没,变成了一张纯噪声图。
这叫 前向扩散(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 x0→x1→x2→⋯→xT
- 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 xT→xT−1→xT−2→⋯→x0
生成时我们没有真实图片,只有一团随机噪声 x T x_T xT。
模型的工作就是:
在每一步根据当前的带噪图 x t x_t xt,预测“该减掉多少噪声”,然后得到更干净一点的 x t − 1 x_{t-1} xt−1。
一路走到最后,就还原出了一张图。
三、前向扩散公式: q ( x t ∣ x t − 1 ) q(x_t|x_{t-1}) q(xt∣xt−1) 到底是什么意思?
扩散模型的前向过程是人为规定好的,不需要学习。
它的定义非常简单:
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(xt∣xt−1)=N(xt;1−βtxt−1,βtI)
第一次看到这个公式,很多人会被吓住。其实它只是在说一件很朴素的事:
从第 t − 1 t-1 t−1 步到第 t t t 步,我们做两件事:
- 把原图信号稍微缩小一点
- 再往里面加一点高斯噪声
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−βtxt−1+βtϵ,ϵ∼N(0,I)
这里:
- 1 − β t \sqrt{1-\beta_t} 1−βt:负责“保留多少原信号”
- β t \sqrt{\beta_t} βt:负责“加入多少新噪声”
其中 β t \beta_t βt 一般很小,比如 0.0001、0.001 这种量级。
所以每一步并不是“啪”一下把图毁掉,而是:
只轻轻地往图上撒一层非常薄的噪声。
2. 直观比喻:照片每轮都被盖一层半透明毛玻璃
假设你把一张照片放在桌上。
每一轮你都给它盖上一层很薄的半透明毛玻璃:
- 第一层时,还基本看得清
- 第十层时,轮廓开始模糊
- 第一百层时,只剩模糊的色块
- 第一千层时,和纯随机噪声差不多
这就是前向扩散的本质。
它不是暴力破坏,而是:
很多次、每次都很轻微的模糊与加噪。
四、为什么大家老在讲 q ( x t ∣ x 0 ) q(x_t|x_0) q(xt∣x0)?它比 q ( x t ∣ x t − 1 ) q(x_t|x_{t-1}) q(xt∣xt−1) 强在哪?
虽然前向过程是一步一步定义的,但训练时如果每次都真的从 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(xt∣x0)
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=1∏tα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(xt∣x0)=N(xt;αˉtx0,(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=αˉtx0+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=α1x0+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=α2x1+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α1x0+α2(1−α1)ϵ1+1−α2ϵ2
你会发现两件事:
- 原始信号前面的系数在不断连乘
- 所有噪声项加起来以后,依然还是一个高斯噪声
为什么噪声加起来还是高斯?
因为:
独立高斯变量的线性组合,仍然是高斯变量。
于是我们就可以把一大堆噪声项合并成一个新的标准高斯 ϵ \epsilon ϵ,最终得到:
x t = α ˉ t x 0 + 1 − α ˉ t ϵ x_t = \sqrt{\bar{\alpha}_t}x_0 + \sqrt{1-\bar{\alpha}_t}\epsilon xt=αˉtx0+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=αˉtx0+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=αˉtxt−1−αˉ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} xt−1”这件事就是可学习的。
1. 关键思想:大问题拆成很多个很小的逆问题
如果你问模型:
- “这团纯噪声原来是哪只猫?”
这很难。
但如果你问:
- “这一步只加了一点点噪声,现在请你把这点噪声减回去。”
这就容易很多。
所以扩散模型不是在做一次巨大的逆变换,而是在做:
成百上千次微小修正。
2. 概率视角:学习一个反向条件分布
正向过程是:
q ( x t ∣ x t − 1 ) q(x_t|x_{t-1}) q(xt∣xt−1)
反向过程想学的是:
p θ ( x t − 1 ∣ x t ) p_\theta(x_{t-1}|x_t) pθ(xt−1∣xt)
也就是:
给定当前带噪状态 x t x_t xt,前一时刻那个稍微更干净的图像 x t − 1 x_{t-1} xt−1 最可能长什么样。
实践中,我们通常把这个反向分布也建模成高斯:
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θ(xt−1∣xt)=N(xt−1;μθ(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=αˉtxt−1−αˉ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)=αt1(xt−1−αˉt1−α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) xt−1∼N(μθ(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−βtxt−1+βtϵ
这里的 β t \beta_t βt 不是随便来的,而是由一个 schedule 决定的。
常见的 schedule 有:
- 线性增长(linear)
- 余弦调度(cosine)
- 其他专门设计的噪声曲线
这相当于规定了一张“噪声进度表”:
- 第 1 步加多少
- 第 10 步加多少
- 第 500 步加多少
2. 在采样过程里,scheduler 决定“怎么走回去”
采样时 scheduler 还负责另一件事:
根据模型预测的噪声,按照某种数值积分规则,算出下一步的 x t − 1 x_{t-1} xt−1。
这件事很像:
- 模型负责说“应该往哪个方向去噪”
- 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) xt−1=μθ(xt,t)+σtz,z∼N(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=αˉtxt−1−αˉ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) xt−1=μθ(xt,t)+σtz,z∼N(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=αˉtx0+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. 它保存整套时间表
比如:
betasalphasalphas_cumprod- 推理时实际使用哪些 timestep
2. 它实现一步步更新规则
比如:
- DDPM 的随机更新
- DDIM 的确定性/半确定性更新
- 其他更高级的 ODE/SDE 求解器
3. 它控制速度与质量的权衡
同一个模型:
- 用 100 步 scheduler,通常质量更稳
- 用 20 步 scheduler,通常更快
- 用不同公式的 scheduler,观感和细节会明显不同
所以你要建立一个非常重要的认知:
神经网络负责预测噪声。
scheduler 负责把“噪声预测”翻译成“下一步图像该怎么更新”。
这两者缺一不可。
十七、为什么扩散模型最终能学到“图像分布”?
从更高层的角度看,扩散模型之所以成立,是因为它做了这样一件事:
1. 正向过程把真实图像分布慢慢推向简单分布
真实图像分布很复杂:
- 猫、狗、人脸、风景
- 纹理、光照、结构都很复杂
但当你不断加高斯噪声后,这个复杂分布会逐渐变得简单,最后接近标准高斯分布。
也就是:
- 难分布 -> 简单分布
2. 反向过程则学习把简单分布拉回复杂分布
训练好后,模型学会了一系列微小逆变换:
- 从“很脏”变“稍微干净”
- 从“稍微干净”变“更清晰”
- 一步一步最终回到真实图像流形
所以你可以把扩散模型理解成:
它先把世界上复杂的图像分布打散成一锅高斯粥,再学会怎样把这锅粥一勺一勺重新摆盘成图片。
十八、你必须真正记住的 6 句话
如果这篇很长,你至少要把下面 6 句话刻进脑子里:
-
前向扩散不是暴力摧毁图像,而是很多次、每次都很轻微地加噪。
-
q ( x t ∣ x 0 ) q(x_t|x_0) q(xt∣x0) 的闭式公式说明:任意时刻的带噪图,本质上就是“原图 + 噪声”的线性混合。
-
训练时模型最经典的任务不是直接画图,而是预测加入的噪声 ϵ \epsilon ϵ。
-
反向生成不是一次性把纯噪声变成图片,而是很多次微小去噪修正。
-
scheduler 负责规定噪声时间表和反向更新策略,它直接影响速度和质量。
-
DDPM 更随机、更稳、通常更慢;DDIM 更确定、更快、常用于加速采样。
十九、下一步最适合继续学什么?
如果你已经看懂这一篇,最自然的下一步有 3 个方向:
-
Classifier-Free Guidance
为什么一句提示词可以把采样轨迹“拉向”你想要的内容。 -
从噪声预测推到 x 0 x_0 x0 / v v v prediction
三种参数化到底有什么关系,为什么现在很多模型喜欢v-pred。 -
把这套扩散公式和 DiT/U-Net 接起来
也就是网络到底怎样接收(x_t, t, condition)并输出噪声。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)