\[ \begin{align}\begin{aligned}\newcommand{\ba}{\boldsymbol{a}} \newcommand{\bb}{\boldsymbol{b}} \newcommand{\be}{\boldsymbol{e}} \newcommand{\bw}{\boldsymbol{w}} \newcommand{\bx}{\boldsymbol{x}} \newcommand{\by}{\boldsymbol{y}} \newcommand{\bz}{\boldsymbol{z}} \newcommand{\bd}{\boldsymbol{d}} \newcommand{\bv}{\boldsymbol{v}} \newcommand{\bs}{\boldsymbol{s}}\\\newcommand{\btheta}{\boldsymbol{\theta}} \newcommand{\bbeta}{\boldsymbol{\beta}} \newcommand{\bgamma}{\boldsymbol{\gamma}} \newcommand{\bsigma}{\boldsymbol{\sigma}} \newcommand{\md}{\mbox{d}} \newcommand{\bmu}{\boldsymbol{\mu}} \newcommand{\bone}{\boldsymbol{1}} \newcommand{\trans}{^{\rm\scriptsize T}} \newcommand{\var}{\mathrm{var}}\\\newcommand{\bA}{\boldsymbol{A}} \newcommand{\bB}{\boldsymbol{B}} \newcommand{\bC}{\boldsymbol{C}} \newcommand{\bD}{\boldsymbol{D}} \newcommand{\bG}{\boldsymbol{G}} \newcommand{\bI}{\boldsymbol{I}} \newcommand{\bM}{\boldsymbol{M}} \newcommand{\bP}{\boldsymbol{P}} \newcommand{\bS}{\boldsymbol{S}} \newcommand{\bU}{\boldsymbol{U}} \newcommand{\bW}{\boldsymbol{W}} \newcommand{\bX}{\boldsymbol{X}} \newcommand{\bY}{\boldsymbol{Y}} \newcommand{\bZ}{\boldsymbol{Z}} \newcommand{\cotp}{\textcolor[RGB]{48,209,88}{TP}} \newcommand{\cotn}{\textcolor[RGB]{100,210,255}{TN}} \newcommand{\cofp}{\textcolor[RGB]{94,92,230}{FP}} \newcommand{\cofn}{\textcolor[RGB]{191,90,242}{FN}}\\\newcommand{\numcotp}{\textcolor[RGB]{48,209,88}{50}} \newcommand{\numcotn}{\textcolor[RGB]{100,210,255}{30}} \newcommand{\numcofp}{\textcolor[RGB]{94,92,230}{10}} \newcommand{\numcofn}{\textcolor[RGB]{191,90,242}{10}} \DeclareMathOperator*{\argmin}{arg\,min}\end{aligned}\end{align} \]

扩散模型(尚在完善)#

学习目标与记号#

  直接学习高维数据分布并从中采样通常十分困难,扩散模型将生成过程分解为固定的逐步加噪和可学习的逐步去噪。围绕这一概率建模思想,本章将学习 DDPM、分数匹配与 SDE、条件引导、DDIM 和潜空间扩散;学习目标是能够推导任意时间步的解析采样公式,区分训练与生成路径,并核对各时间步的加噪参数、随机变量和网络输入输出的维度。

  1. 区分生成模型、判别模型与普通去噪器,建立扩散模型的概率视角;

  2. 推导高斯前向扩散的解析采样公式,并解释各时间步的加噪参数及其相关系数;

  3. 从训练和生成两条路径理解扩散模型,并核对随机变量与张量维度;

  本章沿用统一记号:普通小写字母表示标量,粗体小写字母表示向量,粗体大写字母表示矩阵或高阶张量;时间步写作下标 \(t\)⁠,网络层编号写作上标 \([l]\)⁠,转置写作 \(\trans\)⁠。\(\bx_0\) 表示干净数据,\(\bx_t\) 表示第 \(t\) 个噪声等级的随机变量;\(\boldsymbol{\epsilon}_t\) 专门表示从 \(\bx_{t-1}\)\(\bx_t\) 时单独加入的第 \(t\) 步噪声,\(\tilde{\boldsymbol{\epsilon}}_t\) 则表示把前 \(t\) 步单步噪声按各自系数合并并标准化后得到的累计等价噪声。本章各节的程序均应同时检查数值结果和数组维度。

扩散模型从干净数据逐步加噪并学习反向去噪的整体流程

图 40 固定的前向过程 \(q(\cdot\mid\cdot)\) 按照预先设定的参数逐步加入噪声;学习得到的反向过程 \(p_{\btheta}(\cdot\mid\cdot)\) 从高斯噪声逐步生成样本#

  图 40 中的 \(q(\cdot\mid\cdot)\) 不是一个需要训练的参数,而是固定前向加噪过程所对应的概率分布。预先设定参数 \(\{\beta_1,\ldots,\beta_T\}\) 后,每个条件分布 \(q(\bx_t\mid\bx_{t-1})\) 都按照相应的 \(\beta_t\)⁠,在 \(\bx_{t-1}\) 的基础上加入一定强度的高斯噪声,因此训练过程中不需要学习 \(q(\cdot\mid\cdot)\)⁠。 而 \(p_{\btheta}(\cdot\mid\cdot)\) 表示需要通过训练确定的反向去噪过程,其中 \(\btheta\) 表示神经网络的全部参数。每个条件分布 \(p_{\btheta}(\bx_{t-1}\mid\bx_t)\) 描述如何根据噪声较多的 \(\bx_t\) 得到噪声较少的 \(\bx_{t-1}\)⁠。训练的目的就是确定 \(\btheta\)⁠,使这些反向条件分布能够逐步把高斯噪声变成与训练数据相似的样本。因此,\(p_{\btheta}(\cdot\mid\cdot)\) 是一个由参数 \(\btheta\) 决定的概率模型。

随机变量与基本记号#

  令 \(\bx_0\sim p_{\mathrm{data}}\) 表示干净数据。\(T\) 表示前向加噪过程预先设定的总步数;对于 \(t\in\{1,\ldots,T\}\)⁠,\(t\) 表示当前时间步,\(\bx_t\) 表示完成第 \(t\) 步加噪后得到的随机变量。对于图像,单个样本可写成 \(\bx_0\in\mathbb{R}^{C\times H\times W}\)⁠;实际训练还要增加批次轴。

  预先设定一组参数 \(\{\beta_1,\ldots,\beta_T\}\)⁠,其中 \(0<\beta_t<1\)⁠,\(\beta_t\) 决定第 \(t\) 步的加噪强度。再定义

\[\alpha_t=1-\beta_t, \qquad \bar\alpha_t=\prod_{s=1}^{t}\alpha_s, \qquad \bar\alpha_0=1.\]

  标准离散前向过程是马尔可夫链

\[q(\bx_t\mid\bx_{t-1}) =\mathcal{N}\!\left( \sqrt{\alpha_t}\bx_{t-1}, \beta_t\bI \right).\]

等价地,可以把第 \(t\) 步写成重参数化形式

\[\bx_t =\sqrt{\alpha_t}\bx_{t-1} +\sqrt{\beta_t}\boldsymbol{\epsilon}_t, \qquad \boldsymbol{\epsilon}_t\sim\mathcal{N}(\boldsymbol{0},\bI).\]

其中 \(\boldsymbol{\epsilon}_t\) 只表示从 \(\bx_{t-1}\)\(\bx_t\) 的这一步新加入的标准高斯噪声,并且不同时间步的噪声相互独立。这一步将上一时刻信号缩小为 \(\sqrt{\alpha_t}\bx_{t-1}\)⁠,再加入方差为 \(\beta_t\) 的独立高斯噪声。若 \(\beta_t\) 充分小且累计时间足够长,\(\bx_T\) 会接近标准高斯分布。

任意时间步的解析采样#

  高斯变量的线性组合仍为高斯。把两次相邻转移展开:

\[\bx_t =\sqrt{\alpha_t\alpha_{t-1}}\bx_{t-2} +\sqrt{\alpha_t\beta_{t-1}}\boldsymbol{\epsilon}_{t-1} +\sqrt{\beta_t}\boldsymbol{\epsilon}_t,\]

其中两个噪声独立且都服从 \(\mathcal{N}(\boldsymbol{0},\bI)\)⁠。噪声项的总方差为

\[\alpha_t\beta_{t-1}+\beta_t =1-\alpha_t\alpha_{t-1}.\]

继续递推即可得到具有解析形式的重要边缘分布

\[q(\bx_t\mid\bx_0) =\mathcal{N}\!\left( \sqrt{\bar\alpha_t}\bx_0, (1-\bar\alpha_t)\bI \right),\]

以及重参数化采样式

\[\bx_t =\sqrt{\bar\alpha_t}\bx_0 +\sqrt{1-\bar\alpha_t}\tilde{\boldsymbol{\epsilon}}_t, \qquad \tilde{\boldsymbol{\epsilon}}_t\sim\mathcal{N}(\boldsymbol{0},\bI).\]

  这里用不同符号明确区分两类噪声。\(\boldsymbol{\epsilon}_t\) 始终表示从 \(\bx_{t-1}\)\(\bx_t\) 的第 \(t\) 步单独加入的噪声;\(\tilde{\boldsymbol{\epsilon}}_t\) 则表示把前 \(t\) 步单步噪声按照传播过程中产生的系数加权,再除以总标准差后得到的累计等价噪声。具体地,

\[\tilde{\boldsymbol{\epsilon}}_t =\frac{1}{\sqrt{1-\bar\alpha_t}} \sum_{s=1}^{t} \sqrt{\beta_s\prod_{j=s+1}^{t}\alpha_j}\, \boldsymbol{\epsilon}_s.\]

其中约定 \(s=t\) 时的空乘积 \(\prod_{j=t+1}^{t}\alpha_j\) 等于 \(1\)⁠。因此,\(\tilde{\boldsymbol{\epsilon}}_t\) 不是把 \(\boldsymbol{\epsilon}_1,\ldots,\boldsymbol{\epsilon}_t\) 直接相加,而是对各步噪声进行加权和标准化。由于各步噪声相互独立且服从标准高斯分布,\(\tilde{\boldsymbol{\epsilon}}_t\) 也服从标准高斯分布,可以用来从 \(\bx_0\) 一步构造 \(\bx_t\)⁠。

  因此,训练时不需要真的执行 \(t\) 次加噪:随机抽取一个 \(t\)⁠,便可一步构造 \(\bx_t\)⁠。信噪比可写为

\[\operatorname{SNR}(t)=\frac{\bar\alpha_t}{1-\bar\alpha_t}.\]

这里比较的不是 \(\bx_0\)\(\bx_t\) 本身,而是 \(\bx_t\) 中的两个加性分量:由干净样本保留下来的 \(\sqrt{\bar\alpha_t}\bx_0\) 是信号分量,由累计等价噪声 \(\tilde{\boldsymbol{\epsilon}}_t\) 产生的 \(\sqrt{1-\bar\alpha_t}\tilde{\boldsymbol{\epsilon}}_t\) 是噪声分量。上式首先是信号系数与噪声系数的平方之比。若 \(\bx_0\)\(d\) 个维度,并且每个维度都已经减去均值、再除以标准差,使其均值为 0、方差为 1,同时 \(\tilde{\boldsymbol{\epsilon}}_t\sim\mathcal{N}(\boldsymbol{0},\bI)\)⁠,那么两个分量的平均平方能量分别为 \(\bar\alpha_t d\)\((1-\bar\alpha_t)d\)⁠,二者之比正是上式。若原始数据没有采用这种尺度,上式应理解为噪声调度规定的信噪比;实际平均功率比还会受到数据尺度 \(\mathbb{E}\lVert\bx_0\rVert_2^2\) 的影响。 \(t\) 增大时,通常 \(\bar\alpha_t\) 下降,信号贡献减弱而噪声贡献增强。

  下面的动画从干净样本出发,展示前向扩散如何随时间逐步加入噪声,使样本中的原始结构不断减弱并最终接近高斯噪声。

训练路径与生成路径#

  训练阶段从数据集中抽取 \(\bx_0\)⁠,再随机抽取时间步 \(t\) 和累计等价噪声 \(\tilde{\boldsymbol{\epsilon}}_t\)⁠,利用闭合形式构造 \(\bx_t\)⁠,让带时间条件的网络学习反向信息。生成阶段则从 \(\bx_T\sim\mathcal{N}(\boldsymbol{0},\bI)\) 开始,按 \(T,T-1,\ldots,1\) 逐步得到 \(\bx_0\)⁠。前向过程固定且不需要学习;网络参数只出现在反向模型中。

  下面的 NumPy 示例完成训练数据准备中的两件事:make_linear_schedule 根据总步数和首尾噪声率生成 betaalpha 与累积乘积 alpha_barq_sample 接收一批干净样本 x0每个样本的 0 基时间步 talpha_bar 及 NumPy 随机数生成器 rng返回同形的加噪样本 xt 和本次实际使用的累计等价噪声。代码中的 noise 对应 \(\tilde{\boldsymbol{\epsilon}}_t\)⁠,可直接作为噪声预测网络的训练目标,而不表示某一个单独步骤的 \(\boldsymbol{\epsilon}_t\)⁠。

import numpy as np

def make_linear_schedule(num_steps, beta_start=1e-4, beta_end=2e-2):
    beta = np.linspace(beta_start, beta_end, num_steps, dtype=np.float64)
    if np.any(beta <= 0) or np.any(beta >= 1):
        raise ValueError("beta 必须严格位于 (0, 1)")
    alpha = 1.0 - beta
    alpha_bar = np.cumprod(alpha)
    return beta, alpha, alpha_bar

def q_sample(x0, t, alpha_bar, rng):
    """x0 的第一轴是批次;t 是长度相同的0基整数下标数组。"""
    x0 = np.asarray(x0, dtype=np.float64)
    t = np.asarray(t, dtype=np.int64)
    alpha_bar = np.asarray(alpha_bar, dtype=np.float64)
    if t.ndim != 1 or x0.shape[0] != t.shape[0]:
        raise ValueError("批次与时间步数量不一致")
    if alpha_bar.ndim != 1 or np.any((alpha_bar <= 0) | (alpha_bar >= 1)):
        raise ValueError("alpha_bar 必须是一维且严格位于 (0, 1)")
    if np.any((t < 0) | (t >= len(alpha_bar))):
        raise IndexError("时间步下标超出参数数组范围")
    a = alpha_bar[t].reshape((-1,) + (1,) * (x0.ndim - 1))
    noise = rng.normal(size=x0.shape)
    xt = np.sqrt(a) * x0 + np.sqrt(1.0 - a) * noise
    return xt, noise

  这里让 \(\beta_t\) 从起始值到终止值等距变化,再逐项计算 \(\alpha_t=1-\beta_t\)\(\bar\alpha_t\)⁠。q_samplereshape 把每个样本的系数扩展到其余维度,因而无论 x0 是向量、图像还是更高阶数组,都能按样本广播计算。这段代码只模拟前向加噪,不包含去噪网络、损失函数或反向生成循环;让 \(\beta_t\) 线性变化也只是众多可选方案之一。

  上述代码中的 t 是数组的 0 基位置,所以 t=0 对应数学记号中的第 1 步,而不是 \(\bar\alpha_0=1\) 的干净端点。若项目显式把 \(\bar\alpha_0\) 放入系数数组,索引规则必须随之调整,并通过首尾时间步测试固定下来。

常见误解与数据尺度#

  扩散链中的高斯方差与数据尺度直接相关。图像若从整数 \([0,255]\) 转为 \([-1,1]\)⁠,训练和生成必须采用同一变换;展示时再逆变换并裁剪。不能只看某一时间步的图像“很像噪声”就断言已达到标准高斯,还应检查均值、方差、相关性或终点信噪比。

Shiny 交互演示:扩散模型的前向与生成过程

  交互页面可以让 \(\beta_t\) 随时间步线性变化,并使用同一份固定的累计等价噪声 \(\tilde{\boldsymbol{\epsilon}}_t\) 直接构造任意时间步的 \(\bx_t\)⁠。另外两个标签页展示 DDPM 的逐步反向更新和无分类器引导的代数关系。反向页面中的教学预测器会读取真实 \(\bx_0\)⁠,因此只能用于核对公式,不是训练得到的生成模型。

点击打开“扩散模型逐步实验室”

核心推导与实现核验#

核心关系

\[\bx_t=\sqrt{\bar\alpha_t}\bx_0+\sqrt{1-\bar\alpha_t}\tilde{\boldsymbol{\epsilon}}_t,\qquad \tilde{\boldsymbol{\epsilon}}_t\sim\mathcal{N}(\boldsymbol{0},\bI).\]

  推导路径。 将相邻高斯转移递归代入。信号系数相乘得到 \(\sqrt{\bar\alpha_t}\)⁠;独立噪声方差相加并形成 \(1-\bar\alpha_t\)⁠,因此任意 \(t\) 的噪声样本都可从 \(\bx_0\) 一步构造。

关键条件

  推导要求每一步噪声相互独立、协方差为单位阵的标量倍数,并采用给定的线性高斯转移。若使用相关噪声或非高斯扰动,均值和协方差必须重新推导。

数据规模

  若一个批次包含 \(B\) 张图像,每张图像有 \(C\) 个通道、高为 \(H\)⁠、宽为 \(W\)⁠,则 \(\bx_0\)⁠、\(\bx_t\) 和闭合采样使用的累计等价噪声 \(\tilde{\boldsymbol{\epsilon}}_t\) 的大小都为 \(B\times C\times H\times W\)⁠。每张图像对应一个时间下标,因此时间向量长度为 \(B\)⁠;取出的 \(\bar\alpha_t\) 应整理为 \(B\times1\times1\times1\)⁠,使每张图像使用自己的噪声系数。

常见误区

  不要把 \(\beta_t\)⁠、\(\alpha_t\)\(\bar\alpha_t\) 混用;累计乘积必须按时间轴计算。也不要把单步噪声 \(\boldsymbol{\epsilon}_t\) 与闭合采样中的累计等价噪声 \(\tilde{\boldsymbol{\epsilon}}_t\) 当成同一个随机变量。训练与生成的数据归一化必须一致,时间下标从 0 还是从 1 开始也要在代码中统一。

动手检查

  固定 \(\bx_0\)\(t\)⁠,重复采样大量 \(\tilde{\boldsymbol{\epsilon}}_t\)⁠,检查闭合形式生成的 \(\bx_t\) 的经验均值接近 \(\sqrt{\bar\alpha_t}\bx_0\)⁠,逐元素方差接近 \(1-\bar\alpha_t\)⁠;再比较逐步加噪与解析采样的分布统计。

数值稳定性与规模

  时间步很多时,累计乘积可能接近 0,建议用高精度预先计算这些系数,并在需要时保存 \(\log\bar\alpha_t\)⁠。所有平方根的输入应检查理论范围,并只对舍入误差造成的微小负数作有限度的修正,避免用裁剪掩盖加噪参数或公式中的错误。

本章小结#

  1. 扩散模型用固定前向加噪链定义易处理的训练扰动,再学习反向生成过程。

  2. 具有解析形式的 \(q(\bx_t\mid\bx_0)\) 允许训练时随机抽取任意时间步,而不必逐步模拟前向链。

  3. 各时间步的加噪参数、数据尺度、时间下标和广播维度是实现中最先需要核对的四件事。