扩散模型基础:前向扩散与逆向去噪
一句话概述
扩散模型(Diffusion Model)是近年来最强大的生成模型范式,也是Stable Diffusion、DALL-E 2、Midjourney等主流图像生成工具的核心技术。扩散模型包含两个过程:①前向扩散(Forward Diffusion)——逐步向数据中添加高斯噪声,经过T步后数据变为纯噪声;②逆向去噪(Reverse Denoising)——训练一个神经网络(通常为U-Net)从噪声中逐步恢复原始数据。前向过程是固定的(不需要学习),逆向过程通过学习"预测噪声"来实现数据生成。扩散模型的理论基础是随机微分方程(SDE)和变分推断,其生成质量在多个基准上超越了GAN。与GAN和VAE相比,扩散模型训练更稳定(无需对抗),生成多样性更好(不易模式坍缩),但推理速度较慢(需要多步迭代)。
💡 核心要点:①扩散模型通过逐步加噪(前向)和去噪(逆向)实现数据生成 ②前向过程是固定的马尔可夫链,逐步将数据变为高斯噪声 ③逆向过程训练神经网络预测添加的噪声,从噪声中恢复数据 ④生成时从纯噪声开始,经T步逆向去噪得到新数据 ⑤生成质量高、训练稳定,但推理速度比GAN慢
教学与演示
一、前向扩散:数据到噪声
是什么(定义):前向扩散过程(Forward Diffusion Process)是一个固定的马尔可夫链,逐步向数据x_0中添加高斯噪声。第t步的分布为q(x_t|x_{t-1})=N(x_t; √(1-β_t)·x_{t-1}, β_t·I),其中β_t是噪声调度(noise schedule),从β_1≈10^{-4}线性增长到β_T≈0.02。经过T步(通常T=1000),x_T近似服从N(0,I)。这个过程的优雅之处在于:给定x_0,可以直接采样任意时间步的x_t,而不需要逐步迭代——q(x_t|x_0)=N(x_t; √ᾱ_t·x_0, (1-ᾱ_t)·I),其中α_t=1-β_t,ᾱ_t=Πα_s。
大白话 扩散模型的前向过程就像"往一杯清水里一滴一滴加墨水"。刚加第一滴时,水还基本清澈(x_1≈x_0,噪声很少);加到第500滴时,水已经很浑浊了;加到第1000滴时,完全变成墨水(纯噪声)。神奇的是,数学家发现可以"跳步"——不需要一滴一滴地数,可以直接算"加500滴墨水后水有多黑",这个公式很简单:x_t = √ᾱ_t × 清水 + √(1-ᾱ_t) × 墨水。
为什么(原理):前向过程的设计遵循了"方差保持"(variance-preserving)原则——参数选择为√(1-β_t)和√β_t(而非1-β_t和β_t),使得每步的方差保持为1(如果x_0方差为1)。这使得噪声添加过程在数值上稳定,且x_T自然地收敛到N(0,I)。重参数化性质(可跳跃到任意t步)大大简化了训练——不需要模拟整个扩散过程,只需随机采样t和噪声即可构造训练样本。
import numpy as np
# 扩散模型的前向过程:逐步添加噪声
# 展示从清晰数据到纯噪声的转变
class ForwardDiffusion:
def __init__(self, T=200):
self.T = T
# 噪声调度:β从0.0001线性增长到0.02
self.betas = np.linspace(0.0001, 0.02, T)
self.alphas = 1.0 - self.betas
# ᾱ_t = α_1 · α_2 · ... · α_t(累积乘积)
self.alpha_bars = np.cumprod(self.alphas)
def forward_step(self, x_t_minus_1, t):
"""单步前向扩散:q(x_t | x_{t-1})"""
beta_t = self.betas[t]
noise = np.random.randn(*x_t_minus_1.shape) # 标准高斯噪声
x_t = np.sqrt(1 - beta_t) * x_t_minus_1 + np.sqrt(beta_t) * noise
return x_t, noise
def forward_direct(self, x_0, t):
"""直接采样 x_t(跳过中间步骤)"""
alpha_bar_t = self.alpha_bars[t]
noise = np.random.randn(*x_0.shape)
x_t = np.sqrt(alpha_bar_t) * x_0 + np.sqrt(1 - alpha_bar_t) * noise
return x_t, noise
diff = ForwardDiffusion(T=200)
# 创建示例数据(模拟图像的一个像素或一个特征)
np.random.seed(42)
x_0 = np.array([[[0.5, -0.3, 0.8]]]) # 1个样本,1个位置,3个特征
print("=== 扩散模型:前向扩散过程 ===\n")
print(f"初始数据 x_0: {x_0.flatten()}")
print(f"总步数 T: {diff.T}\n")
# 展示不同时间步的扩散效果
for t_fraction in [0, 0.1, 0.25, 0.5, 0.75, 1.0]:
t = int(t_fraction * (diff.T - 1))
x_t, noise = diff.forward_direct(x_0, t)
alpha_bar = diff.alpha_bars[t]
print(f"t={t:3d} (ᾱ={alpha_bar:.4f}): x_t = {np.round(x_t.flatten(), 3)}")
print("\n关键公式:x_t = √ᾱ_t · x_0 + √(1-ᾱ_t) · ε")
print("→ 直接采样的威力:不需要逐步模拟!")
# 验证:逐步模拟 vs 直接跳跃
print("\n\n=== 验证:逐步 vs 跳跃 ===")
np.random.seed(123)
x_step = x_0.copy()
for t in range(50):
x_step, _ = diff.forward_step(x_step, t)
print(f"逐步50步: {np.round(x_step.flatten(), 3)}")
np.random.seed(123)
# 重置随机种子使得噪声一致
x_direct, _ = diff.forward_direct(x_0, 49)
print(f"直接跳50步: {np.round(x_direct.flatten(), 3)}")
print("→ 两者一致!这大大简化了训练")
大白话 前向扩散最神奇的地方是"可以跳步"。你不需要"加第1滴→加第2滴→...→加第1000滴",而是可以直接用公式算"加500滴后是什么状态"。这个性质让训练变得非常简单——随机选一个时间步t,用公式算x_t,然后让神经网络猜"我加了什么噪声"。
什么用(应用):前向扩散的"直接采样"性质是扩散模型高效训练的基础。训练时,只需:①随机采样一个t~Uniform(1,T),②用公式计算x_t,③训练网络预测噪声ε。这使得每个训练步骤只需一次前向传播,不需要展开整个扩散链。DDPM使用T=1000,训练在ImageNet等大规模数据集上可行。
哪些坑(缺点):β_t的调度选择影响生成质量。线性调度(β从0.0001到0.02)简单但不一定最优——余弦调度(cosine schedule)在某些任务上效果更好,因为它在前几步保留更多信号。T的选择是速度-质量的权衡:T越大(如4000),生成质量越高但推理越慢;T越小(如200),速度快但质量略降。
二、逆向去噪:噪声到数据
是什么(定义):逆向去噪过程(Reverse Denoising Process)是扩散模型的"生成"部分。它从纯噪声x_T~N(0,I)开始,通过一个可学习的去噪网络ε_θ(x_t, t)逐步恢复数据。逆向转移概率为p_θ(x_{t-1}|x_t)=N(x_{t-1}; μ_θ(x_t,t), σ_t²I),其中μ_θ由去噪网络预测的噪声ε_θ(x_t,t)推导:μ_θ(x_t,t)=1/√α_t·(x_t - β_t/√(1-ᾱ_t)·ε_θ(x_t,t))。训练目标是让预测的噪声ε_θ(x_t,t)尽可能接近真实添加的噪声ε。
大白话 逆向去噪就像"从墨水中还原清水"。你有一杯纯墨水(纯噪声x_T),有一个"过滤器"(去噪网络),它能判断"这杯液体里有多少是墨水、有多少是水"。每次过滤,去掉一点点墨水,水就清一点点。经过1000次过滤,墨水完全去除,得到一杯清水——这杯清水就是生成的"新数据"。过滤器(网络)是怎么学会的?通过训练——给它看各种"半墨水半清水"的混合液体,告诉它"这里面有30%墨水",让它学会估计。
为什么(原理):逆向过程可以训练的根本原因是:给定x_0和x_t,反向条件分布q(x_{t-1}|x_t, x_0)也是高斯分布,其均值可以通过x_0、x_t和噪声调度参数解析计算。神经网络不需要直接学习整个分布,只需要学习预测"从x_0到x_t添加的噪声ε"——这是一个回归任务,比学习概率分布简单得多。DDPM的简化损失L_simple = E[||ε - ε_θ(x_t, t)||²],去掉了权重系数,实践证明效果更好。
import numpy as np
# 扩散模型的逆向过程:从噪声中逐步恢复数据
# 简化版——演示核心原理
class DenoisingNetwork:
"""简化的去噪网络:预测添加的噪声"""
def __init__(self, d=3):
np.random.seed(42)
self.W = np.random.randn(d, d) * 0.3
self.b = np.zeros(d)
def predict_noise(self, x_t, t):
"""预测在时间步t添加的噪声ε"""
# 实际中这是一个U-Net,输入x_t和时间嵌入
# 这里简化为线性变换
return x_t @ self.W + self.b
class ReverseDiffusion:
def __init__(self, T=200):
self.T = T
self.betas = np.linspace(0.0001, 0.02, T)
self.alphas = 1.0 - self.betas
self.alpha_bars = np.cumprod(self.alphas)
self.denoiser = DenoisingNetwork(d=3)
def reverse_step(self, x_t, t):
"""单步逆向去噪:从 x_t 恢复到 x_{t-1}"""
alpha_t = self.alphas[t]
alpha_bar_t = self.alpha_bars[t]
beta_t = self.betas[t]
# 预测噪声
eps_pred = self.denoiser.predict_noise(x_t, t)
# 预测 x_0
x_0_pred = (x_t - np.sqrt(1 - alpha_bar_t) * eps_pred) / np.sqrt(alpha_bar_t)
# 计算均值
coef1 = np.sqrt(alpha_bar_t) * beta_t / (1 - alpha_bar_t)
# 添加采样噪声(除了最后一步)
if t > 0:
noise = np.random.randn(*x_t.shape) * np.sqrt(beta_t)
else:
noise = 0
x_t_minus_1 = x_0_pred * coef1 + noise
return x_t_minus_1
def generate(self, shape, verbose=False):
"""从纯噪声生成新数据"""
x_t = np.random.randn(*shape) # x_T ~ N(0, I)
if verbose:
print(f"初始噪声 x_T: {np.round(x_t.flatten(), 3)}")
checkpoints = [200, 150, 100, 50, 0]
for t in reversed(range(self.T)):
x_t = self.reverse_step(x_t, t)
if verbose and t in checkpoints:
print(f"去噪后 x_{t:3d}: {np.round(x_t.flatten(), 3)}")
return x_t
diff = ReverseDiffusion(T=200)
print("=== 扩散模型:逆向去噪过程 ===\n")
print("从纯噪声逐步恢复到清晰数据:\n")
generated = diff.generate((1, 1, 3), verbose=True)
print(f"\n最终生成的数据: {np.round(generated.flatten(), 3)}")
print(f"\n过程:纯噪声(x_T) → 逐步去噪 → 清晰数据(x_0)")
print(f"核心:去噪网络学会从x_t中'剥离'噪声,恢复信号")
大白话 训练扩散模型就是教网络"猜噪声"。给网络看"半清半噪"的图片,问它:"我加了多少噪声?"网络猜一个答案,和真实加的噪声比较,差距越小越好。反复练习后,网络成了"噪声专家"——任何清晰度的图片,它都能精确说出其中噪声的成分。生成时,从纯噪声开始,网络一次次"剥除"噪声,最终露出清晰的图片。
什么用(应用):逆向去噪的"预测噪声"框架是DDPM、DDIM、Score-based等所有扩散模型的共同范式。Stable Diffusion在此基础上引入文本条件(通过交叉注意力),使去噪过程受文本控制。图像编辑(如Inpainting、Super-Resolution)可以通过修改逆向过程的初始条件或中间步骤来实现。
哪些坑(缺点):逆向过程需要T步迭代(通常T=1000),推理速度慢是扩散模型的主要缺点。DDIM通过确定性采样将步数从1000减少到50-100步;蒸馏方法(如Progressive Distillation)可将步数减少到1-4步。另一个挑战是去噪网络的规模——U-Net通常有数亿参数,训练和推理资源需求大。
三、DDPM的完整流程
是什么(定义):DDPM(Denoising Diffusion Probabilistic Models)由Ho等人于2020年提出,是扩散模型的标准实现。DDPM的完整流程包括:①训练——从数据采样x_0,采样t和ε,计算x_t,用去噪网络预测ε,最小化MSE损失;②生成——从x_T~N(0,I)开始,逐步应用逆向转移(DDPM采样器),经过T步得到x_0。DDPM的逆向采样包含一个随机项(添加少量噪声),使其具有多样性。
大白话 DDPM就是"训练一个去噪专家,用它逐步清理噪声"。去噪网络通常是U-Net——一种编码器-解码器结构的卷积网络,特别擅长处理图像去噪任务。U-Net的编码器逐步下采样提取特征,解码器逐步上采样恢复细节,中间有跳跃连接(skip connection)保留高频信息。
为什么(原理):DDPM选择U-Net作为去噪网络的原因:①U-Net的多尺度结构适合处理不同分辨率的噪声模式;②跳跃连接保留了图像的高频细节(纹理、边缘),这对去噪至关重要;③时间步t通过位置编码注入U-Net的每一层,使网络知道当前处于扩散的哪个阶段。DDPM的逆向采样在每一步都添加少量随机噪声(方差σ_t²),这增加了生成样本的多样性,但也导致了推理的随机性。
import numpy as np
# DDPM完整流程演示
# 对比DDPM和DDIM的采样策略
class DDPMPipeline:
def __init__(self, T=200):
self.T = T
self.betas = np.linspace(0.0001, 0.02, T)
self.alphas = 1.0 - self.betas
self.alpha_bars = np.cumprod(self.alphas)
def ddpm_sampling_step(self, x_t, t, eps_pred):
"""DDPM采样步骤(含随机噪声)"""
alpha_t = self.alphas[t]
alpha_bar_t = self.alpha_bars[t]
beta_t = self.betas[t]
# 预测x_0
x_0_pred = (x_t - np.sqrt(1 - alpha_bar_t) * eps_pred) / np.sqrt(alpha_bar_t)
# DDPM均值
coef_x0 = np.sqrt(alpha_bar_t) * beta_t / (1 - alpha_bar_t)
coef_xt = alpha_t * np.sqrt(1 - alpha_bar_t) / (1 - alpha_bar_t)
mean = coef_x0 * x_0_pred + coef_xt * x_t
# 添加随机噪声(DDPM的关键——确保多样性)
if t > 0:
noise = np.random.randn(*x_t.shape) * np.sqrt(beta_t)
x_t_minus_1 = mean + noise
else:
x_t_minus_1 = mean
return x_t_minus_1
def ddim_sampling_step(self, x_t, t, eps_pred, eta=0.0):
"""DDIM采样步骤(eta=0时为确定性采样)"""
alpha_bar_t = self.alpha_bars[t]
alpha_bar_prev = self.alpha_bars[t - 1] if t > 0 else 1.0
# 预测x_0
x_0_pred = (x_t - np.sqrt(1 - alpha_bar_t) * eps_pred) / np.sqrt(alpha_bar_t)
# 确定性分量
pred_dir = np.sqrt(1 - alpha_bar_prev) * eps_pred
# 随机分量(eta=0为确定性DDIM)
if eta > 0 and t > 0:
sigma = eta * np.sqrt((1 - alpha_bar_prev) / (1 - alpha_bar_t) * (1 - alpha_bar_t / alpha_bar_prev))
noise = np.random.randn(*x_t.shape) * sigma
else:
noise = 0
x_t_minus_1 = np.sqrt(alpha_bar_prev) * x_0_pred + pred_dir + noise
return x_t_minus_1
print("=== DDPM vs DDIM 采样策略 ===\n")
print("DDPM采样:")
print(" - 每步添加随机噪声 → 生成具有随机性")
print(" - 需要T=1000步 → 推理慢")
print(" - 多次生成同一噪声得到不同结果")
print("\nDDIM采样(eta=0,确定性):")
print(" - 不添加随机噪声 → 生成确定")
print(" - 可以跳步(如每20步采样1次) → 推理快50倍")
print(" - 同一噪声输入 → 同一输出")
print("\n两者关系:")
print(" - DDPM是DDIM在eta=1时的特例")
print(" - DDIM通过调节eta平衡速度和质量")
print(" - eta=0:最快(50步),eta=1:最好质量(1000步)")
大白话 DDPM采样就一步:"先猜加了多少噪声,然后减掉,最后加点新噪声增加随机性"。猜噪声(ε_θ(x_t,t))是最关键的部分——猜得越好,去得越干净。加新噪声是为了增加多样性——如果不加(DDIM模式),生成就是确定性的,失去了"创造性"。
什么用(应用):DDPM/DDIM是Stable Diffusion等主流生成工具的基础。DDIM的跳步采样(如50步而非1000步)使扩散模型推理变得实用。Stable Diffusion在潜在空间(Latent Space)而非像素空间进行扩散,进一步加速了过程。ControlNet通过调节去噪过程实现精确控制。
哪些坑(缺点):DDPM采样步数多、推理速度慢;DDIM虽然快但生成质量略降。扩散模型对噪声调度敏感——β_t的选择影响生成细节和多样性。大模型去噪网络的训练需要大量GPU资源(Stable Diffusion在256块A100上训练)。
概念关系图谱
| 概念 | 核心含义 | 与AI的关系 | 关联概念 |
|---|---|---|---|
| 前向扩散 | 逐步向数据加噪直至纯噪声 | 数据破坏过程,训练信号来源 | 马尔可夫链、噪声调度 |
| 逆向去噪 | 训练网络从噪声恢复数据 | 生成过程,扩散模型的核心 | 去噪网络、U-Net |
| DDPM | 去噪扩散概率模型 | 扩散模型的标准实现 | 变分推断、ELBO |
| DDIM | 去噪扩散隐式模型 | 确定性采样,加速推理 | 跳步采样、ODE |
| 噪声调度(β_t) | 控制每步添加噪声量的序列 | 影响生成质量和训练稳定性 | 线性调度、余弦调度 |
| U-Net | 编码器-解码器结构的CNN | 扩散模型的标准去噪网络 | 跳跃连接、时间嵌入 |
重点答疑
Q1: 扩散模型和VAE的本质区别是什么?
VAE学习数据的潜在空间分布,生成时从潜在空间采样并解码。扩散模型不学习显式的潜在空间,而是学习"逐步去噪"的过程。VAE一次前向传播即可生成(编码→采样→解码),扩散模型需要T步迭代。VAE样本质感通常较模糊(KL散度的平均化效应),扩散模型样本质感更锐利(无需KL散度)。扩散模型训练更稳定(简单回归任务vs概率建模),但推理更慢。
Q2: 为什么扩散模型的前向过程是固定的(不需要学习)?
前向过程只是"按照预定公式加噪声"——这是一个纯数学操作,没有需要学习的参数。如果把前向过程也做成可学习的,等于增加了一个额外的生成器,回到了"两个网络对抗"的范式,失去了扩散模型训练稳定的优势。固定的前向过程确保了训练信号的"纯净性"——网络只需要学一个任务(预测噪声)。
Q3: DDIM为什么可以跳步采样(如50步而非1000步)?
DDIM将逆向过程重新解释为求解一个常微分方程(ODE)——从x_T到x_0的确定性轨迹。在ODE框架下,可以用更大的步长(如从T=1000跳到T=500,相当于步长翻倍)来近似求解,类似于用更大的步长解微分方程。步长越大,近似误差越大(生成质量下降),但速度越快。eta参数控制"确定性vs随机性"——eta=0为纯ODE(最快),eta=1回到DDPM。
章节单词汇总
| 英文 | 音标 | 术语/释义 |
|---|---|---|
| Diffusion Model | /dɪˈfjuːʒən ˈmɑːdəl/ | 扩散模型,通过加噪-去噪实现生成 |
| Forward Process | /ˈfɔːrwərd ˈproʊses/ | 前向过程,逐步向数据添加噪声 |
| Reverse Process | /rɪˈvɜːrs ˈproʊses/ | 逆向过程,训练网络从噪声恢复数据 |
| DDPM | /diː diː piː em/ | 去噪扩散概率模型 |
| DDIM | /diː diː aɪ em/ | 去噪扩散隐式模型,加速采样 |
| Noise Schedule | /nɔɪz ˈskedʒuːl/ | 噪声调度,β_t序列控制扩散速度 |
| U-Net | /juː net/ | U形网络,扩散模型的标准去噪架构 |
| Markov Chain | /ˈmɑːrkɑːv tʃeɪn/ | 马尔可夫链,扩散过程的数学描述 |
面试练习
Q1 [单选] 扩散模型的前向过程中,数据最终变成什么?
- A. 全零向量
- B. 标准高斯噪声 N(0, I)
- C. 低维潜在编码
- D. 离散的token序列
解答:前向扩散经过T步后,x_T近似服从N(0,I)(标准高斯分布)。这是因为每步乘√(1-β_t)衰减信号,加√β_t的噪声,T步后信号完全被噪声淹没。
Q2 [单选] 扩散模型的逆向过程中,神经网络预测的是什么?
- A. 原始数据x_0
- B. 添加的噪声ε
- C. 数据分布概率
- D. 分类标签
解答:去噪网络ε_θ(x_t, t)预测的是前向过程中添加的噪声ε。预测噪声后,可以通过x_0 = (x_t - √(1-ᾱ_t)·ε_θ)/√ᾱ_t恢复原始数据。
Q3 [单选] DDIM相比DDPM的主要优势是什么?
- A. 更高的生成质量
- B. 更快的推理速度(可跳步采样)
- C. 更少的参数
- D. 更容易训练
解答:DDIM通过确定性ODE框架实现跳步采样(如50步vs DDPM的1000步),大幅提升推理速度。生成质量可能略有下降,但速度提升显著(20-50倍)。
Q4 [多选] 关于扩散模型,以下哪些说法是正确的?
- A. 前向过程是固定的马尔可夫链
- B. 可以直接采样任意时间步的x_t
- C. 训练目标是最小化预测噪声与真实噪声的MSE
- D. 生成过程只需1步前向传播
- E. DDPM的逆向采样包含随机噪声项
解答:前向过程固定、可直接跳步采样、训练目标为MSE、DDPM采样含随机噪声。生成过程需要T步迭代(通常1000或50步),不是1步。
Q5 [单选] 扩散模型的标准去噪网络架构是什么?
- A. ResNet
- B. Transformer
- C. U-Net
- D. MLP
解答:U-Net是扩散模型的标准去噪网络,其编码器-解码器结构+跳跃连接特别适合图像去噪任务。时间步t通过嵌入注入各层。