扩散模型近年在图像生成领域的表现令人瞩目,与生成对抗网络不同,它并不要求一个网络直接输出逼真图像,而是把生成任务拆成两个方向相反的过程。正向过程从清晰图像出发,按固定规则逐步加入高斯噪声,直到图像退化成近似纯噪声;逆向过程从噪声出发,训练神经网络学习每一步如何撤销噪声,逐步还原出清晰样本。理解正向扩散与逆向去噪的关系,是掌握扩散模型训练和采样的关键。

正向扩散:把图像逐步推入噪声空间
正向扩散通常被定义为一个不含可学习参数的马尔可夫链。给定一张原始图像 x0,每一步根据上一时刻图像 xt-1 生成 xt,其条件分布为高斯分布。常见写法是 xt 等于根号 αt 乘以 xt-1,再加上根号 1 减 αt 乘以一个标准高斯噪声 ε。这里 αt 是预设的噪声缩放系数,通常从接近 1 逐渐减小到接近 0。αt 越大,当前步保留原图成分越多;αt 越小,噪声注入越强。
上面的逐步计算虽然直观,但要在训练中逐时刻模拟非常耗时。由于每一步都是高斯转移,可以利用重参数化把 T 步合并成一步:xt 可以直接由 x0 和噪声 ε 生成,系数使用 ᾱt,也就是从第 1 步到第 t 步所有 α 的累积乘积。这样一来,任意时刻的加噪样本都可以高效生成,训练时不需要完整展开整条链。噪声调度策略会直接影响最终分布是否接近标准正态,常用的有线性调度和余弦调度。线性调度让 ᾱt 匀速下降,余弦调度在中段保留更多图像结构,通常能让模型学到更细致的纹理。
正向扩散最终得到 xT,当 T 足够大且调度设计合理时,xT 近似服从标准正态分布。这个终点之所以重要,是因为逆向采样需要从一个易采样的先验分布开始。如果正向不能把图像完全破坏到纯噪声,逆向起点就难以覆盖所有可能样本,生成的多样性会下降。
逆向去噪:训练网络逐步恢复信息
逆向过程的目标是学习一个参数化分布 pθ(xt-1|xt),用来近似正向扩散的真实后验 q(xt-1|xt, x0)。由于正向过程是已知的高斯分布,给定 x0 条件下的后验也是高斯分布,其均值和方差都可以解析表示。实际网络中通常用一个编码器-解码器结构来预测每一步的均值,而方差可以固定,也可以作为可学习参数。为了简化训练,DDPM 提出不直接预测原始图像 x0,而是预测正向过程中加入的噪声 ε。这个改变让损失函数变成了简单的均方误差,对象就是当前时间步加入的噪声。
为什么预测噪声比预测原始图像更稳定?从梯度尺度看,当 ᾱt 接近 0 时,x0 的回归目标会包含很大幅度的信号,数值波动明显;而噪声始终是零均值、单位方差的高斯变量,回归目标范围固定。因此,网络输出噪声残差更容易被优化。实际训练时,从数据集中随机采一张图像,再随机采一个时间步 t 和噪声 ε,用正向公式生成 xt,把 xt 和时间步 t 输入网络,让网络输出尽可能接近 ε。这个过程不依赖对抗训练,也不要求判别器,训练曲线通常比较稳定。
采样阶段则从标准正态分布采样 xT,然后沿着时间步从 T 到 1 反向迭代。每一步用当前 xt 减去网络预测的噪声,再经过系数缩放,最后加入一个小的随机噪声以保持随机性。这个循环的每一步都会逐渐减少噪声,最终得到 x0。因为逆向过程每一步都存在随机性,同一个起点可以生成不同的结果,这也是扩散模型适合多样化图像生成的原因之一。
代码示例:简化版正向加噪与训练损失
下面用 PyTorch 展示一个简化实现,主要包括方差调度、正向加噪以及训练损失计算。这里为了便于理解,没有包含完整 U 形网络,只保留核心流程。
import torch
import torch.nn.functional as F
def linear_beta_schedule(timesteps):
beta_start = 1e-4
beta_end = 0.02
return torch.linspace(beta_start, beta_end, timesteps)
T = 1000
betas = linear_beta_schedule(T)
alphas = 1.0 - betas
alpha_bars = torch.cumprod(alphas, dim=0)
def forward_diffusion_sample(x0, t, noise):
alpha_bar_t = alpha_bars[t].view(-1, 1, 1, 1)
xt = torch.sqrt(alpha_bar_t) * x0 + torch.sqrt(1.0 - alpha_bar_t) * noise
return xt
def train_step(model, x0, optimizer):
batch = x0.shape[0]
noise = torch.randn_like(x0)
t = torch.randint(0, T, (batch,), device=x0.device)
xt = forward_diffusion_sample(x0, t, noise)
predicted_noise = model(xt, t)
loss = F.mse_loss(predicted_noise, noise)
optimizer.zero_grad()
loss.backward()
optimizer.step()
return loss.item()
上述代码中,alpha_bars 是每一步 α 值的累积乘积,用于从 x0 直接计算任意时刻的加噪结果。训练时随机采样时间步和噪声,计算均方误差即可更新网络参数,目标非常明确。
@torch.no_grad()
def sample(model, image_size, device):
xt = torch.randn((1, 3, image_size, image_size), device=device)
for t in reversed(range(0, T)):
t_tensor = torch.tensor([t], device=device)
predicted_noise = model(xt, t_tensor)
alpha_t = alphas[t]
alpha_bar_t = alpha_bars[t]
beta_t = betas[t]
if t > 0:
noise = torch.randn_like(xt)
else:
noise = torch.zeros_like(xt)
xt = (1.0 / torch.sqrt(alpha_t)) * (
xt - ((1.0 - alpha_t) / torch.sqrt(1.0 - alpha_bar_t)) * predicted_noise
) + torch.sqrt(beta_t) * noise
return xt
采样函数从标准正态分布开始,按时间步从大到小循环。最后一步 t 等于 0 时不再加入随机噪声,以减少最终结果的抖动。每一步先减去网络预测的噪声贡献,再用后验均值系数缩放,逐步逼近清晰图像。
方差调度与采样速度的权衡
方差调度决定了正向过程中信息的破坏速度,也间接影响模型训练的难度。如果 β 过大,前几步就会丢失大量高频细节,剩余的中间时间步几乎都是噪声,网络很难学到有意义的去噪映射;如果 β 过小,正向终点不够接近标准正态,采样时需要更多步才能稳定。实践常用的线性调度将 β 从 1e-4 提高到 0.02,配合 T 等于 1000 能取得不错效果,但余弦调度在很多数据集中表现更好,因为它让信噪比下降得更平滑。
标准 DDPM 采样需要从 T 到 1 逐步迭代,速度较慢。后续工作如 DDIM 通过去随机化和跳跃步数,把采样步数从 1000 步压缩到几十步,同时保持相近的生成质量。理解正向扩散中的 ᾱt 关系,也有助于设计更好的加速采样策略。逆向每一步的方差、预测目标以及噪声注入方式都可以在不重新训练网络的情况下调整,这是扩散模型灵活性的体现。
从训练目标到图像生成应用
扩散模型的训练目标之所以简洁,是因为它没有对抗损失、感知损失或判别器结构。网络只需要做噪声回归,输出与输入同尺寸的噪声图。配合时间步嵌入,模型能感知当前阶段需要去除多少噪声。早期时间步网络主要处理粗粒度结构,后期时间步则负责恢复细节纹理,这种层次化去噪过程让扩散模型生成的图像往往具有更高的多样性和细节真实感。
在图像处理任务中,正向扩散与逆向去噪的思想可以迁移到图像修复、超分辨率、图像编辑等方向。例如图像修复可以在逆向过程中只对缺失区域应用去噪,对已知区域保持约束;超分辨率可以在加噪过程中使用低分辨率图像作为条件;图像编辑则通过修改文本条件或注意力图来控制去噪方向。掌握基础的扩散过程,是把这些扩展方法落地的关键。