算法可视化与交互学习平台

学习 Diffusion / DDPM:从加噪去噪到图像生成Diffusion / DDPM: From Noising and Denoising to Image Generation

从 DDPM 的前向扩散公式出发,用最小二维数据和 16×16 几何图像理解 forward 加噪、高斯噪声张量 epsilon、噪声预测训练目标、reverse 去噪采样和 scheduler 直觉,并通过 Tiny DDPM 实验观察模型如何从雪花噪声中恢复图像结构。

Deep LearningIntermediateFree
Kernel
1

先抓住 DDPM 的核心直觉

DDPM(Denoising Diffusion Probabilistic Model)是最经典的一类 Diffusion Model。它的核心不是“直接画出图像”,而是学会一件更小、更稳定的事情:

在某个噪声等级 t 下,判断 x_t 里混进了哪一份噪声 epsilon。

训练时,我们人为把干净数据 x0 加噪成 xt,并且保存真实噪声 epsilon。这里的 epsilon 不是一个标量,而是和图像同形状的噪声张量:每个像素、每个通道都有独立采样的高斯随机值。模型学习预测这整张噪声图。生成时,从纯噪声开始,反复预测噪声、扣掉噪声,结构就会一步步浮现。

严格说,本模块是 DDPM 的教学版骨架:重点讲 DDPM 的 forward 加噪、噪声预测训练目标和 reverse 去噪直觉;最后的 Tiny 实验为了可视化做了最小化处理,不覆盖完整论文里的 ELBO 推导和所有方差参数化细节。

学习主线是:

DDPM forward 加噪规则 -> epsilon 噪声张量如何采样 -> 噪声预测训练目标 -> Reverse 去噪采样 -> Tiny DDPM 图像生成实验
2

一张图看 DDPM 算法路径

DDPM 有两个方向相反的过程。Forward process 是人为规定的加噪规则,不需要学习;reverse process 是模型要学习的去噪过程。

Forward process: x0 -> x1 -> x2 -> ... -> xT 干净数据逐步变成标准高斯噪声 Reverse process: xT -> xT-1 -> ... -> x1 -> x0 模型从噪声逐步恢复结构

为什么不直接训练模型从 x_T 一步生成 x_0?因为这太难。DDPM 把一个困难的大跳跃拆成很多小步,每一步只做一点点去噪。

关键要记住:forward 是我们写死的数学过程,模型不需要学它;模型真正学习的是在给定 x_tt 时,预测混入的那一整份噪声张量 epsilon。这一思想非常适合图像生成:先从随机噪声中恢复大结构,再逐步恢复局部形状、边界与细节。Tiny DDPM 实验会把这个过程缩小到 16×16 简单图案里,让你直接看到结构如何从噪声中浮现。

3

DDPM Forward Process:一步跳到任意噪声等级

DDPM 的 forward process 是人为定义的加噪规则。训练时可以直接从干净样本 跳到任意噪声等级 ,不必真的连续加噪 t 次。

原始干净数据,可以是二维点、图像或 latent
Clean data
第 t 个噪声等级的数据
Noisy data at timestep t
加入的标准高斯噪声张量;与 x0 同形状,每个维度独立采样
Standard Gaussian noise
从第 1 步到第 t 步累计保留下来的原始信号比例
Cumulative signal retention up to t

这个高斯分布公式在说什么

这个式子读作:给定上一时刻的数据 ,下一时刻的数据 是从一个高斯分布中采样出来的。这个高斯分布的均值是 ,协方差是 。这里 是单位矩阵,表示每个维度都加入独立同分布的高斯噪声。

它和采样公式是等价的

从高斯分布采样可以写成“均值 + 标准差乘以标准高斯噪声”。所以这个分布形式等价于把上一时刻信号保留一部分,再加入一部分独立高斯噪声。

从单步加噪开始

Diffusion 的 forward process 先定义一个单步马尔可夫加噪过程。每一步只保留一部分上一时刻的信号,并加入一份新的高斯噪声。

展开两步,看累计保留比例从哪里来

把第一步代入第二步,原始信号 前面的系数会连乘,因此出现累计保留比例

把多份高斯噪声合并

多份独立标准高斯噪声的线性组合仍然是高斯噪声。上式里的两项噪声可以合并成一项新的标准高斯噪声,其总方差为

推广到任意 timestep

表示从第 步到第 步的 连乘,就得到可以直接从 采样到 的闭式公式。

如何理解两个系数

控制原始信号还剩多少, 控制累计噪声混入多少。噪声等级越高,原始信号权重越小,噪声权重越大。

epsilon 不是一个数,而是一整份噪声张量

如果 是一张 的图像,那么 也是 。同一个 timestep 下, 是全图共享的噪声强度系数,但 在每个像素、每个通道上都有自己的随机值。

正态分布曲线里采样到的是横轴值

钟形曲线的 y 值是概率密度,用来说明哪些位置更容易被抽到;真正采样返回的是横轴上的数值。代码里的 `torch.randn_like(x0)` 会返回一整份和 同形状的随机张量,里面的值通常靠近 0,少数会靠近

noise = torch.randn_like(x0) # same shape as x0; sampled values, not density values

最小代码

noise = torch.randn_like(x0) a = alpha_bar[t] xt = torch.sqrt(a) * x0 + torch.sqrt(1 - a) * noise
4

逐步演算:一个像素 / 一个维度如何被加噪

准备一个干净像素值和一份噪声
x0 = 1.0
epsilon = -0.4
alpha_bar_t = 0.64
Initial Variables
x0
1
epsilon
-0.4
alpha_bar_t
0.64
Step 1 Variables
x0
1
epsilon
-0.4
alpha_bar_t
0.64
Step 1 / 4
5

高斯分布观察:epsilon 到底怎么采样

在逐步演算里,我们先看了单个像素或单个维度的计算。但在真正的 DDPM forward process 里,更准确的说法不是“得到一个固定结果”,而是:给定上一时刻 后,下一时刻 来自一个条件概率分布。

这句话的意思是: 会围绕一个中心随机波动。这个中心不是原来的 ,而是衰减后的信号:

扩散范围由协方差控制:

这里 是单位矩阵,表示每个维度都加入独立同分布的高斯噪声。也就是说,如果数据是二维点,两个坐标分别独立加噪;如果数据是图像,每个像素、每个 RGB 通道或 latent 维度都按同样规则独立加入高斯噪声。

容易混淆的一点是:正态分布的钟形曲线里,横轴是可能被抽到的随机值,纵轴是概率密度。采样得到的是横轴上的值,不是纵轴高度。也就是说,torch.randn_like(x) 返回的是很多类似 0.23-1.510.88 的随机数,而不是概率密度。

高斯采样有一个通用写法:

把 DDPM 的均值和标准差代进去:

就得到 forward process 的采样形式:

所以,分布写法和采样写法描述的是同一件事:前者强调“ 来自哪个概率分布”,后者强调“如何真正采样出一个 ”。

alpha_t 接近 1:中心接近上一时刻数据,方差很小,加噪很轻。 alpha_t 变小:中心向 0 衰减,方差变大,加噪更强。 同一个 t:噪声强度系数相同,但每个像素 / 通道的 epsilon 值不同。 同一个 x_{t-1}:每次重新采样 epsilon_t,都会得到不同的 x_t。
6

一维高斯加噪图解

这张图解释的是 forward diffusion 最核心的一步:给定上一时刻 ,下一时刻 的条件分布 长什么样。 最重要的一点:图里的曲线不是 本身,也不是 的运动轨迹,而是 的概率分布。也就是说,它描述的是:如果固定 ,然后反复随机加噪很多次,所有可能得到的 会集中在哪里。 单步加噪公式是 ,其中 。因为每次采样到的 都不同,所以同一个 每次都会产生不同的 。重复一万次后,把这些 统计起来,就会得到图中的钟形曲线。 可以把它想成往墙上扔飞镖:目标中心是均值 ,每次都有随机误差。飞镖不会都落在一个点,而是围绕中心分布;扔得足够多之后,中间最多,两边越来越少,就形成高斯形状。 图中的 x 轴表示 可能取到的值;y 轴表示 出现在该位置附近的概率密度。曲线越高,表示采样结果越容易落在这里;曲线越宽,表示随机波动越大。 绿色 :几乎记住原数据,均值接近 ,方差很小,所以曲线很窄、很尖、集中在 附近。 蓝色 :开始明显加噪,均值向 衰减,曲线变宽。 橙色 :噪声已经很强, 可能到处波动,所以曲线更宽、更平。 为什么曲线越来越宽?因为噪声方差 变大, 的随机波动更强。为什么曲线中心往 靠?因为 会缩小原始结构,均值 会向 收缩。 所以 forward process 的本质不是“画曲线”或“操作曲线”,而是每一步随机采样一个新的 。曲线只是所有可能 的统计图,描述 最可能出现在哪里。
7

交互观察:调节 alpha_t 看 DDPM 单步加噪

调节上一时刻数据 x_{t-1}、保留比例 alpha_t 和采样数量,观察理论高斯曲线与实际采样直方图是否重合。这个实验帮助理解:forward process 给出的不是单个确定值,而是一整个条件分布;采样得到的是横轴上的 x_t 数值,曲线高度只是概率密度。

Parameter Panel
4 Params
8

二维高斯加噪观察:每个维度独立扰动

二维情况和一维没有本质区别,只是一个数变成了一个二维向量。给定上一时刻点 ,forward 加噪仍然是: 其中 。这里的 是二维单位矩阵,表示第 1 维和第 2 维分别加入独立同分布的高斯噪声。因为协方差是 ,两个方向的方差相同、相关性为 ,所以采样点会围绕均值 形成近似圆形云团。 这个二维点云就是图像 / latent 加噪的最小版本:二维点 多维向量 图像张量,本质还是同一个公式。

Parameter Panel
5 Params
9

二维加噪过程观察:结构如何逐步变成噪声

这个实验用二维点云模拟图像 / latent 中的“结构”。 是干净数据, 是第 个噪声等级下的数据。 每个 timestep 都按闭式公式直接采样:。 当 较小时, 较大,原始结构仍然清楚;当 变大时, 变大,随机噪声越来越主导。接近最后一步时,点云基本忘记原始结构,逐渐接近标准高斯噪声。

Parameter Panel
6 Params
10

训练目标:预测噪声 epsilon

模型输入带噪样本 和噪声等级 ,输出它认为混入的噪声张量 。训练目标就是让预测噪声接近 forward process 中真实加入的整份噪声

forward 加噪时真实加入的噪声张量;形状和 x_t 相同
True noise added in the forward process
神经网络预测的噪声
Noise predicted by the neural network
带噪数据
Noisy data
噪声等级
Noise level

先看训练样本从哪里来

训练时, 不是数据集里原本存在的样本,而是我们用干净样本 和随机噪声张量 主动合成出来的。对于图像, 的形状和 完全一样,每个元素独立来自标准正态分布。

真正关键:训练时我们知道真实噪声

虽然 是随机噪声,但它不是未知答案。因为 forward 加噪是我们自己做的,所以生成 时用到的那一份 会被保存下来,直接作为监督信号。

loss 在比较什么

模型要学的不是“这张图是什么”,而是“这个带噪样本里混入了哪一份噪声”。如果预测噪声和真实噪声越接近,MSE loss 就越小。

为什么不是直接预测 x0

直接预测 也可以设计成训练目标,但不同 timestep 下数据分布变化很大,学习会更不稳定。预测 的好处是:无论 怎么变,目标噪声都来自标准高斯分布,尺度更统一。

为什么模型能预测随机噪声

神经网络不是凭空预测一份完全不可见的随机数。它看到的是混合结果 和噪声等级 。在给定 后,模型可以利用训练数据分布中的结构线索,估计哪部分更像原始信号,哪部分更像噪声。

最本质的一句话

神经网络能预测噪声,不是因为噪声本身可预测,而是因为真实数据有强结构规律,噪声没有结构。模型在大量训练样本中学到“什么像真实数据”,于是面对带噪样本时,就能估计哪些部分更不像真实数据,从而估计混入的噪声。

和 reverse 去噪的关系

训练好以后,生成阶段没有真实 可以看。模型只能根据当前 预测 ,再用这个预测噪声反推出更干净的样本。

最小代码

noise = torch.randn_like(x0) t = torch.randint(0, T, (batch_size,)) xt = sqrt_alpha_bar[t] * x0 + sqrt_one_minus_alpha_bar[t] * noise noise_pred = model(xt, t) loss = ((noise_pred - noise) ** 2).mean()
11

Reverse Sampling:用预测噪声反推出更干净的样本

如果模型能估计 xt 中的噪声,就可以反推出当前 xt 对应的干净样本估计 x0_hat。采样器再根据这个估计把 xt 推向更干净的状态。

根据当前 xt 和预测噪声得到的干净样本估计
Estimated clean sample
模型预测的噪声
Predicted noise
当前 timestep 的累计信号保留比例
Cumulative signal retention

采样的直觉

每一步都先问模型:这里面像噪声的部分是什么?然后根据公式把这部分扣掉一点。重复多次后,从随机噪声中恢复出训练数据分布里的结构。

DDIM 风格的最小更新

12

从雪花噪声中恢复图像:训练一个 Tiny DDPM

这个实验把二维点云换成最小图像:程序生成一批 16×16 的简单图案,让 TinyConvDenoiser 学会在不同 timestep 下预测噪声。它是一个 DDPM 风格的教学实验:forward 加噪和 epsilon 预测目标来自 DDPM,网络和采样器为了能在浏览器里快速跑完而做了极简化。 训练时,我们先用 主动制造带噪图,再让模型学习 。这里的 是和图像同形状的噪声张量,代码里就是 `torch.randn_like(clean)`:每个像素、每个通道都独立采样一个标准高斯随机值。 因此模型不是直接“画图”,而是学会:在一张被雪花噪声吞没的图里,哪些部分更像噪声。训练完成后,就可以从纯噪声开始,一步步扣掉预测噪声,让图像结构重新浮现。 这里的扩散步数 表示把“从干净图像到纯噪声”的 forward 路径切成多少个 timestep。 不是越大越好,而是在生成质量、计算成本和可学习性之间做平衡: 太小,相当于每一步变化很大,结构会突然被破坏,reverse 去噪更难学; 较大,每一步只是小修正,更像沿楼梯下楼,过程更稳定,但训练和采样都要经历更多步,计算会更慢。 可以把 理解成生成路径的分辨率。 大,路径更细,每一步任务更简单; 小,路径更粗,速度更快,但每一步要跨越的变化更难。原始 DDPM 常用 ,因为很多小步更接近连续 diffusion process;但真实推理时,DDIM、DPM-Solver、Euler、UniPC 等快速 sampler 往往只用 20 到 50 步,LCM 或 Turbo 类方法甚至可以用 1 到 4 步。它们的目标都是:用更少步骤,仍然保持可接受的生成质量。 所以观察这个实验时,不要把“扩散步数 T”理解成绝对越大越好。它真正控制的是:把复杂生成拆成多少个小修正。调大 时,注意 forward 行是不是更平滑、reverse 行是不是更稳定;调小 时,注意速度是否更快,但最终结构是否更容易失真。

Parameter Panel
5 Params
AI
问问 LLM:解释你的 DDPM 实验结果