直觉:把"生成"拆成"逐步去噪"

直接让模型一次性吐出一张高清图片,是个极难的回归问题——输出空间是几百万维像素,分布极其复杂。扩散模型(Diffusion Model)换了个思路:与其学会一步到位地画,不如学会"把一张含噪图片稍微变干净一点"

它的训练分两个方向:

  • 前向过程(加噪):拿一张真图,一点点往里掺高斯噪声,掺很多步之后变成纯噪声。这一步无需学习,是写死的数学过程。
  • 反向过程(去噪):训练一个神经网络,学会预测"这一步加了什么噪声",从而反推回去。生成时,从纯噪声出发,反复去噪,最终"显影"出一张图。

把一个难问题(凭空生成)拆成几十上百个简单问题(去掉一点噪声),是扩散模型成功的核心直觉。

机制一:前向加噪是确定的数学

设原图为 x0x_0,定义一个 TT 步的马尔可夫链,每步加一点高斯噪声:

q(xtxt1)=N(xt; 1βtxt1, βtI)q(x_t \mid x_{t-1}) = \mathcal{N}\!\left(x_t;\ \sqrt{1-\beta_t}\,x_{t-1},\ \beta_t \mathbf{I}\right)

βt\beta_t 是一个预设的小数(噪声调度,noise schedule),随 tt 增大。妙处在于这条链有闭式解——可以一步跳到任意 tt,不用真的迭代 tt 次。记 αt=1βt\alpha_t = 1-\beta_tα¯t=s=1tαs\bar\alpha_t = \prod_{s=1}^t \alpha_s

xt=α¯tx0+1α¯tϵ,ϵN(0,I)x_t = \sqrt{\bar\alpha_t}\,x_0 + \sqrt{1-\bar\alpha_t}\,\epsilon,\qquad \epsilon \sim \mathcal{N}(0, \mathbf{I})

直觉读法:xtx_t 是"原图按 α¯t\sqrt{\bar\alpha_t} 衰减"加上"按 1α¯t\sqrt{1-\bar\alpha_t} 缩放的噪声"。tTt\to Tα¯t0\bar\alpha_t \to 0xTx_T 就基本是纯噪声了。这个闭式解让训练可以随机采样任意时间步,效率极高。

机制二:训练就是学"预测噪声"

反向过程要学 pθ(xt1xt)p_\theta(x_{t-1}\mid x_t)。经过推导,一个非常简洁的训练目标是:让网络 ϵθ\epsilon_\theta 预测当初加进去的噪声 ϵ\epsilon。损失就是个 MSE:

L=Ex0,ϵ,t[ϵϵθ(xt,t)2]\mathcal{L} = \mathbb{E}_{x_0,\,\epsilon,\,t}\left[\big\|\epsilon - \epsilon_\theta(x_t, t)\big\|^2\right]

训练循环异常简单:

1
2
3
4
5
6
7
8
def train_step(x0, model, betas):
t = randint(1, T) # 随机一个时间步
abar = cumprod(1 - betas)[t]
eps = randn_like(x0) # 采样真噪声
xt = sqrt(abar) * x0 + sqrt(1 - abar) * eps # 闭式加噪
eps_pred = model(xt, t) # 网络预测噪声
loss = mse(eps_pred, eps) # 越像越好
return loss

注意几个要点:

  • 网络输入除了含噪图 xtx_t,还要喂时间步 tt(通常做正弦位置编码后注入),因为"去掉多少噪声"取决于当前在第几步。
  • 网络结构常用 U-Net:下采样捕捉全局结构、上采样恢复细节、跳连保留高频信息。它本质是个"图到图"的预测器,输入输出同尺寸。
  • 训练时不需要真的跑反向链,每个样本独立采一个 tt 即可,所以可以高效并行。

机制三:采样——从噪声"显影"出图

生成时从 xTN(0,I)x_T \sim \mathcal{N}(0,\mathbf{I}) 出发,用学到的 ϵθ\epsilon_\theta 一步步去噪。DDPM 的单步更新大致是:

xt1=1αt(xtβt1α¯tϵθ(xt,t))+σtzx_{t-1} = \frac{1}{\sqrt{\alpha_t}}\left(x_t - \frac{\beta_t}{\sqrt{1-\bar\alpha_t}}\,\epsilon_\theta(x_t,t)\right) + \sigma_t z

其中 zN(0,I)z\sim\mathcal{N}(0,\mathbf I) 是每步重新注入的少量随机性(最后一步不加)。

1
2
3
4
5
6
def sample(model, betas):
x = randn(shape) # 纯噪声起点
for t in reversed(range(1, T)):
eps = model(x, t)
x = denoise_step(x, eps, t, betas) # 上面那个公式
return x # 一张图

最大的工程痛点是采样慢:原始 DDPM 要跑 TT(常达上千)次网络前向,每次都是一整个 U-Net,串行不可并行,所以一张图要几十秒。这催生了两类加速:

  • 更快的采样器(如 DDIM 及各种 ODE 求解器):把随机采样改写成确定性的常微分方程求解,可以用几十步甚至十几步逼近上千步的结果,质量损失很小。采样步数因此成了"速度 vs 质量"最直接的旋钮。
  • 潜空间扩散(Latent Diffusion):先用一个 VAE 把图像压到低维潜空间(比如把 512×512×3512\times512\times3 压到 64×64×464\times64\times4),在潜空间里做扩散,最后解码回像素。计算量随空间分辨率平方下降,显存和速度都大幅改善——这是现代文生图能跑在消费级显卡上的关键。

条件生成:文字怎么控制画面

"文生图"需要把文本条件 cc 注入去噪网络,即 ϵθ(xt,t,c)\epsilon_\theta(x_t, t, c),通常通过交叉注意力让 U-Net 的特征去 attend 文本编码。再配合 classifier-free guidance:训练时随机丢掉条件(让模型同时学会有条件和无条件预测),采样时把两者外推:

ϵ^=ϵθ(xt,t,)+w(ϵθ(xt,t,c)ϵθ(xt,t,))\hat\epsilon = \epsilon_\theta(x_t,t,\varnothing) + w\big(\epsilon_\theta(x_t,t,c) - \epsilon_\theta(x_t,t,\varnothing)\big)

引导强度 ww 越大,越贴合文本但多样性下降、易出伪影;ww 太小则"听不懂话"。这又是一个典型的可调权衡。实践中 ww 常取一个中等区间,并配合负向提示(negative prompt)——把不想要的内容也编码成一个条件,从去噪方向里减掉,相当于"既告诉模型要画什么,也告诉它别画什么"。

值得强调的是 classifier-free guidance 的代价:每个采样步要算两次网络前向(有条件和无条件各一次),所以开了 guidance 的文生图比无条件生成慢近一倍。一些实现会把这两次前向打包成一个 batch 并行,用显存换时间。这也是为什么"出图速度"和"听话程度"在工程上常常是此消彼长的——想又快又贴合文本,往往得在采样器、步数、guidance 强度之间做联合调参。

常见误区

  • 以为扩散在"画图"。它全程在预测/去除噪声,图像是去噪的副产物。
  • 把采样步数当成越多越好。超过某个点收益递减,且不同采样器的最优步数差异很大。
  • 忽视噪声调度的影响。βt\beta_t 的曲线(线性/余弦等)显著影响训练稳定性和成像质量,不是随便选的。余弦调度在高噪声段加噪更平缓,通常比线性调度更稳。
  • 把扩散模型只当"图像工具"。同一套"加噪-去噪"框架是模态无关的——只要能定义连续表示和噪声过程,音频、视频、3D、甚至分子结构都能用扩散生成。图像只是它最出圈的应用。

一点统一视角:score matching

更深一层看,去噪等价于在学习数据分布的梯度场(score,即 xlogp(x)\nabla_x \log p(x))。预测噪声 ϵθ\epsilon_\theta 和估计 score 之间只差一个缩放因子。采样过程因此可以理解为:从随机噪声出发,沿着"数据更可能出现的方向"逐步爬升。这个视角把 DDPM、score-based 模型和各种 ODE/SDE 采样器统一了起来,也解释了为什么扩散采样能改写成确定性微分方程求解——它本质上是在一个学到的向量场里积分。理解这一点,再看各种新采样器和加速技巧,就不会被名词淹没。

小结

扩散模型把"凭空生成"重构成"逐步去噪":前向过程是带闭式解的确定性加噪,训练目标简化为用 MSE 让 U-Net 预测噪声,采样则从纯噪声反复去噪显影成图。它的工程主线全在权衡——采样步数决定速度与质量、潜空间扩散用降维换算力、guidance 强度平衡"听话"与多样性。理解了"模型学的是噪声而非图像",整套机制就豁然开朗了。