考试通知
一小时搞懂扩散模型:从DDPM原理到Stable Diffusion实战 1. 为什么扩散模型值得你花一小时搞懂如果你最近在关注图像生成领域大概率已经被扩散模型这四个字反复刷屏。不管是文生图、图生图、超分辨率重建还是视频生成、音频合成背后几乎都站着同一个核心架构——Diffusion Model。但很多人第一次翻开相关论文时看到那一堆前向加噪、反向去噪、变分下界、马尔可夫链的公式第一反应往往是这玩意儿真的能学会吗。我刚开始接触的时候也一样。那会儿我还在用传统的生成对抗网络做图像修复效果时好时坏训练过程像在走钢丝生成器和判别器稍微不平衡就崩了。后来切换到扩散模型虽然训练稳定了很多但理解成本确实上来了。所以这篇内容的目标很明确用最通俗的方式把扩散模型的核心原理、公式推导、论文脉络和实操路径一次性讲清楚让你在一小时内建立起完整的认知框架而不是被零散的博客和视频带偏。这篇文章适合三类人第一类是完全没接触过生成模型、但想快速入门的初学者第二类是有一定深度学习基础、想搞懂扩散模型数学本质的开发者第三类是已经在用相关工具做图像生成、但想深入理解底层机制以便调参和优化的从业者。不管你属于哪一类我都会从最直觉的层面开始逐步过渡到公式和代码确保每一步都有落脚点。提示本文涉及的公式推导会尽量用文字和图示配合说明遇到不理解的符号可以先跳过回头再看第二遍效果会好很多。2. 从加噪到去噪扩散模型到底在干什么2.1 一个生活化的类比墨水滴入清水想象你有一杯清水往里面滴一滴墨水。刚开始墨水集中在一个点形状清晰。但随着时间推移墨水逐渐扩散到整杯水中最终变成均匀的浅色液体。这个过程是自发的、不可逆的——你不可能指望墨水自己重新聚集成一滴。扩散模型的核心思想就藏在这个过程里。前向过程就是滴墨水把一张清晰的图像逐步加入噪声直到它变成完全随机的噪声图。反向过程就是倒放录像训练一个神经网络让它学会从纯噪声中一步步恢复出清晰图像。注意这里不是真的让墨水自己聚回去而是让模型学会每一步应该去掉多少噪声。这个类比之所以重要是因为它解释了为什么扩散模型比生成对抗网络更稳定。生成对抗网络是让两个网络互相博弈一个负责造假一个负责鉴假训练过程容易失衡。而扩散模型的目标非常明确每一步的去噪结果都有明确的监督信号模型只需要尽可能准确地预测噪声不需要和另一个网络斗智斗勇。2.2 前向过程把图像一步步变成噪声前向过程也叫扩散过程它是一个固定的马尔可夫链不需要学习。具体来说给定一张图像 ( x_0 )我们按照以下公式逐步加入高斯噪声[ q(x_t | x_{t-1}) \mathcal{N}(x_t; \sqrt{1-\beta_t} x_{t-1}, \beta_t I) ]其中 ( \beta_t ) 是每一步的噪声方差通常从 0.0001 逐渐增加到 0.02总共进行 ( T ) 步常见取值是 1000 步。这个公式的意思是每一步的新图像 ( x_t ) 是在上一步图像 ( x_{t-1} ) 的基础上乘以一个衰减系数 ( \sqrt{1-\beta_t} )再加上一定强度的高斯噪声。你可能会问为什么要乘衰减系数因为如果不衰减图像的数值会越来越大最终发散。乘以 ( \sqrt{1-\beta_t} ) 是为了保持方差稳定让整个过程在数值上可控。这个细节在原始论文里没有过多强调但在实际实现中非常关键否则训练很容易出现数值不稳定。更妙的是这个逐步加噪的过程可以合并成一步。通过重参数化技巧我们可以直接从 ( x_0 ) 得到任意时刻的 ( x_t )[ q(x_t | x_0) \mathcal{N}(x_t; \sqrt{\bar{\alpha}_t} x_0, (1-\bar{\alpha}_t) I) ]其中 ( \alpha_t 1-\beta_t )( \bar{\alpha}t \prod{s1}^{t} \alpha_s )。这个公式的推导用到了高斯分布的可加性具体过程在论文附录里有详细说明。它的意义在于训练时我们不需要真的迭代 1000 步来加噪而是可以随机采样一个 ( t )直接计算出对应的 ( x_t )大大提高了训练效率。2.3 反向过程让神经网络学会去噪反向过程才是真正需要学习的地方。我们希望训练一个神经网络 ( \epsilon_\theta )让它能够预测每一步加入的噪声。具体来说给定 ( x_t ) 和时刻 ( t )网络输出对噪声的估计 ( \epsilon_\theta(x_t, t) )然后我们用这个估计来还原 ( x_{t-1} )[ p_\theta(x_{t-1} | x_t) \mathcal{N}(x_{t-1}; \mu_\theta(x_t, t), \Sigma_\theta(x_t, t)) ]其中均值 ( \mu_\theta ) 可以通过预测的噪声计算出来[ \mu_\theta(x_t, t) \frac{1}{\sqrt{\alpha_t}} \left( x_t - \frac{\beta_t}{\sqrt{1-\bar{\alpha}t}} \epsilon\theta(x_t, t) \right) ]这个公式看起来复杂但逻辑很清晰当前图像 ( x_t ) 减去网络预测的噪声再除以衰减系数就得到了上一步的图像估计。方差 ( \Sigma_\theta ) 在原始论文中固定为 ( \beta_t )后来的一些改进工作让它也可以学习但固定值在大多数情况下已经够用。训练目标也很直接让网络预测的噪声和实际加入的噪声尽可能接近。损失函数就是简单的均方误差[ L \mathbb{E}{t, x_0, \epsilon} \left[ | \epsilon - \epsilon\theta(\sqrt{\bar{\alpha}_t} x_0 \sqrt{1-\bar{\alpha}_t} \epsilon, t) |^2 \right] ]这个损失函数的美妙之处在于它非常简单不需要对抗训练不需要复杂的技巧就是回归问题。这也是扩散模型训练稳定的根本原因。2.4 采样过程从纯噪声生成图像训练完成后采样过程就是从纯高斯噪声 ( x_T \sim \mathcal{N}(0, I) ) 开始逐步应用反向过程直到得到 ( x_0 )。每一步的公式是[ x_{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 ]其中 ( z \sim \mathcal{N}(0, I) ) 是重新加入的随机噪声( \sigma_t ) 控制噪声强度。这个随机项很重要它让采样过程具有随机性从而生成多样化的结果。如果去掉这个噪声采样就变成了确定性过程生成的图像会缺乏多样性。实际采样时1000 步的迭代非常慢。后来提出的 DDIM 采样方法可以将步数压缩到 50 步甚至 20 步同时保持生成质量。DDIM 的核心思想是构造一个非马尔可夫的前向过程使得反向过程可以跳步执行。这个改进在实际应用中非常重要因为生成速度直接决定了用户体验。3. 公式推导从变分下界到简单损失函数3.1 为什么要从变分下界说起扩散模型的训练目标最初是从变分自编码器的框架推导出来的。我们希望最大化模型对训练数据的对数似然 ( \log p_\theta(x_0) )但这个值无法直接计算因为它需要对所有可能的隐变量积分。于是我们引入变分下界也叫证据下界[ \log p_\theta(x_0) \geq \mathbb{E}{q(x{1:T}|x_0)} \left[ \log \frac{p_\theta(x_{0:T})}{q(x_{1:T}|x_0)} \right] ]这个下界可以进一步分解为三项重建项、先验匹配项和扩散项。原始论文经过一系列推导最终将损失函数简化为前面提到的简单均方误差形式。这个简化过程非常关键因为它把复杂的变分推断变成了一个可以直接优化的回归问题。具体推导涉及对高斯分布 KL 散度的计算每一步都有明确的解析解。如果你对数学细节感兴趣建议直接看原始论文的附录 B那里有完整的推导过程。我这里只强调一个关键点最终损失函数之所以能简化是因为高斯分布之间的 KL 散度有闭式解而且大部分项在训练中可以忽略或合并。3.2 重参数化技巧的妙用重参数化技巧是扩散模型能够高效训练的核心。它的核心思想是把随机采样过程拆分成确定性变换和标准噪声的组合。具体来说从 ( \mathcal{N}(\mu, \sigma^2) ) 采样可以写成 ( \mu \sigma \cdot \epsilon )其中 ( \epsilon \sim \mathcal{N}(0, 1) )。这个技巧在扩散模型中有两处关键应用。第一处是在前向过程中我们可以直接从 ( x_0 ) 计算 ( x_t )而不需要逐步迭代。第二处是在反向过程中网络预测的是噪声 ( \epsilon )而不是直接预测图像这让训练目标更加稳定。我刚开始理解这个技巧时总觉得它只是数学上的小把戏。但后来在实现中发现如果不使用重参数化训练时就需要对每个样本进行完整的 1000 步加噪计算量巨大且无法并行。使用重参数化后我们可以随机采样 ( t )一次性计算出 ( x_t )训练效率提升了几十倍。3.3 时间步嵌入让网络知道当前处于哪个阶段网络需要知道当前处理的是第几步因为不同阶段的去噪难度完全不同。早期阶段噪声很大网络需要做粗略的恢复后期阶段噪声很小网络需要做精细的调整。为了让网络感知时间步 ( t )我们使用位置编码将其映射为一个高维向量然后注入到网络的每一层。具体实现上通常使用正弦位置编码和 Transformer 中的做法类似。编码后的向量经过两层全连接网络再添加到每个残差块中。这个设计让网络能够根据时间步动态调整行为是扩散模型能够处理多步去噪的关键。在实际调参时时间步嵌入的维度是一个需要关注的超参数。维度太小网络无法区分不同时间步维度太大参数量增加且容易过拟合。常见取值是 128 或 256具体取决于模型规模和数据集复杂度。4. 论文脉络从 DDPM 到 Stable Diffusion 的演进路线4.1 DDPM扩散模型的奠基之作DDPM 是扩散模型的里程碑式工作它首次证明了扩散模型可以生成高质量的图像。这篇论文的核心贡献有三点第一提出了前向加噪和反向去噪的完整框架第二推导了简化的损失函数第三在多个数据集上验证了方法的有效性。不过 DDPM 也有明显的局限。首先是采样速度慢生成一张图像需要 1000 步迭代在实际应用中几乎不可接受。其次是生成质量虽然不错但和当时最好的生成对抗网络相比还有差距。这些问题在后来的工作中逐步得到解决。我在复现 DDPM 时踩过一个大坑原始论文使用的网络架构是 U-Net但细节参数没有完全公开。我一开始用了自己设计的 U-Net结果训练损失下降很慢生成效果也很差。后来对照开源实现才发现残差块的连接方式、注意力层的放置位置、归一化层的类型都会显著影响效果。所以如果你要复现建议直接参考官方开源代码不要自己从头设计。4.2 DDIM把采样步数压缩到 50 步DDIM 的核心贡献是提出了一种非马尔可夫的反向过程使得采样可以跳步执行。具体来说DDIM 不再要求每一步都依赖前一步而是可以跳过若干步直接预测。这让采样步数从 1000 步降到 50 步甚至 20 步速度提升了几十倍。DDIM 的另一个重要特性是确定性采样。当设置特定的参数时DDIM 的采样过程完全确定给定相同的初始噪声生成的图像完全一致。这个特性在需要可复现结果的场景中非常有用比如学术实验和产品测试。我在实际使用中发现DDIM 在步数较少时生成质量会有所下降尤其是细节部分。一个实用的技巧是先用 DDIM 快速生成一张草图再用少量步数的 DDPM 进行精修。这样可以在速度和质量的权衡中找到不错的平衡点。4.3 Stable Diffusion让扩散模型走向大众Stable Diffusion 的最大创新是在潜在空间中进行扩散而不是直接在像素空间。具体来说它先用一个自编码器将图像压缩到低维潜在空间然后在潜在空间中执行扩散过程。这样做的好处是计算量大幅降低因为潜在空间的分辨率远小于原始图像。这个设计让扩散模型可以在消费级显卡上运行真正走向了大众。我之前在 8GB 显存的显卡上跑像素空间的扩散模型 batch size 只能设为 1训练一次要好几周。切换到潜在空间后同样的显卡可以跑 batch size 8训练时间缩短到几天。Stable Diffusion 还引入了交叉注意力机制让文本条件能够控制生成内容。具体来说文本经过编码器得到嵌入向量然后通过交叉注意力注入到 U-Net 的每一层。这个设计让文生图成为可能也是当前大多数图像生成产品的基础架构。4.4 后续演进从 SDXL 到视频生成在 Stable Diffusion 之后社区涌现了大量改进工作。SDXL 通过更大的模型和更精细的条件控制进一步提升了生成质量。视频生成方面一些工作将扩散模型扩展到时序维度通过 3D U-Net 或时序注意力实现视频生成。这些改进的核心思路是一致的在保持训练稳定的前提下提升生成质量、速度和可控性。对于初学者来说不需要一上来就追最新论文先把 DDPM 和 DDIM 搞透后面的改进自然容易理解。5. 实操路径从零跑通一个扩散模型5.1 环境准备与依赖安装先准备好基础环境。推荐使用 Python 3.9 以上版本PyTorch 2.0 以上CUDA 11.8 以上。如果你没有本地显卡可以使用云端的 GPU 实例按小时计费成本可控。安装依赖时除了 PyTorch 本身还需要安装一些辅助库pip install torch torchvision pip install numpy matplotlib tqdm pip install einops pip install pilloweinops是一个张量操作库在实现注意力机制时非常方便。tqdm用于显示训练进度。这些库都很轻量不会增加太多环境负担。注意安装 PyTorch 时一定要选择与 CUDA 版本匹配的轮子否则会出现无法调用 GPU 的情况。可以在 PyTorch 官网找到对应的安装命令。5.2 数据集准备与预处理扩散模型对数据集的要求相对宽松但图像尺寸需要统一。常见做法是将图像缩放到 256x256 或 512x512然后归一化到 [-1, 1] 范围。归一化到 [-1, 1] 而不是 [0, 1] 的原因是扩散过程的高斯噪声均值为 0对称区间更利于数值稳定。数据增强方面随机水平翻转是安全且有效的。但要注意垂直翻转和旋转可能会破坏图像的语义比如人脸倒过来就不太自然。所以增强策略要根据数据集特点来选择。如果你用的是自定义数据集建议先统计图像的数量和分辨率分布。如果图像数量少于 10000 张训练时容易过拟合可以考虑使用预训练模型进行微调而不是从头训练。5.3 模型定义U-Net 的关键细节U-Net 是扩散模型最常用的骨干网络。它的结构分为编码器、瓶颈层和解码器三部分编码器逐步下采样提取特征解码器逐步上采样恢复分辨率同时通过跳跃连接将编码器的特征传递到解码器。在扩散模型中U-Net 需要额外接收时间步嵌入。具体做法是在每个残差块中将时间步嵌入经过两层全连接后加到特征图上。这个操作让网络能够根据当前时间步调整行为。注意力层的放置也很关键。通常在较低分辨率的层使用自注意力因为高分辨率层的计算量太大。Stable Diffusion 还在中间层加入了交叉注意力用于注入文本条件。我在实现时发现GroupNorm 比 BatchNorm 更适合扩散模型。因为扩散模型的 batch size 通常较小BatchNorm 的统计量不稳定而 GroupNorm 不受 batch size 影响。这个细节在论文里没有特别强调但在实际训练中影响很大。5.4 训练循环与损失监控训练循环的核心逻辑很直接随机采样一个 batch 的图像随机采样时间步 ( t )计算加噪后的图像让网络预测噪声计算均方误差反向传播更新参数。for epoch in range(num_epochs): for images in dataloader: t torch.randint(0, T, (images.size(0),), devicedevice) noise torch.randn_like(images) x_t sqrt_alphas_cumprod[t] * images sqrt_one_minus_alphas_cumprod[t] * noise predicted_noise model(x_t, t) loss F.mse_loss(predicted_noise, noise) optimizer.zero_grad() loss.backward() optimizer.step()监控损失时不要只看绝对值要看趋势。正常情况下损失会快速下降然后趋于平稳。如果损失震荡严重可能是学习率太大如果损失下降很慢可能是模型容量不够或学习率太小。我建议每隔几个 epoch 生成一些样本看看效果。即使损失在下降生成质量也可能没有同步提升因为损失和感知质量之间不是完全对应的。早期发现生成质量差可以及时调整架构或超参数避免浪费训练时间。5.5 采样与结果评估训练完成后采样过程就是从纯噪声开始逐步去噪。使用 DDIM 采样可以将步数压缩到 50 步左右。采样时要注意初始噪声的分布要和训练时一致都是标准高斯分布。评估生成质量时常用的指标是 FID它衡量生成图像分布和真实图像分布的距离。FID 越低越好但计算 FID 需要大量样本而且对采样步数敏感。实际使用中可以结合人眼观察和 FID 指标综合判断。我在评估时发现一个现象FID 很低的模型生成图像可能缺乏多样性因为模型可能过拟合了训练集的分布。所以除了 FID还要看生成图像的多样性比如用不同的初始噪声生成多张图像观察差异是否明显。6. 踩坑实录那些论文里不会告诉你的细节6.1 噪声调度线性 vs 余弦原始 DDPM 使用的是线性噪声调度即 ( \beta_t ) 从 0.0001 线性增加到 0.02。但后来有工作指出线性调度在低分辨率图像上表现不错但在高分辨率图像上后期噪声太大导致图像细节丢失。余弦调度是另一种常见选择它让 ( \bar{\alpha}_t ) 按照余弦函数变化使得噪声在中间阶段增加更快后期增加更慢。实测下来余弦调度在 512x512 以上分辨率的图像上效果更好细节保留更完整。选择哪种调度取决于你的数据集和分辨率。如果拿不准可以先跑两组小规模实验对比看哪种调度的生成质量更好。这个对比实验成本不高但收益很明显。6.2 学习率与优化器的选择扩散模型对学习率比较敏感。学习率太大损失震荡不收敛学习率太小训练速度慢且容易陷入局部最优。常见做法是使用 AdamW 优化器学习率设为 1e-4 到 2e-4配合余弦退火调度。我试过用 SGD 训练扩散模型结果收敛非常慢而且对学习率极其敏感。后来换成 AdamW训练稳定了很多。所以除非有特殊需求否则建议直接用 AdamW。另外权重衰减也很重要。适当的权重衰减可以防止过拟合但太大又会导致欠拟合。常见取值是 0.01 到 0.05具体要根据数据集大小和模型容量调整。6.3 批次大小与梯度累积批次大小直接影响训练稳定性和显存占用。批次太小梯度估计噪声大训练不稳定批次太大显存不够。如果显存有限可以使用梯度累积多次前向传播后累积梯度再一次性更新参数。梯度累积的实现很简单就是在反向传播时不立即更新参数而是累积梯度达到指定次数后再更新。这样可以在小显存上模拟大批次的效果。我在 8GB 显存的显卡上通过梯度累积将等效批次大小从 4 提升到 32训练稳定性明显改善。6.4 采样步数与生成质量的权衡采样步数越多生成质量通常越好但速度越慢。DDIM 可以在 50 步左右达到不错的质量但如果你追求极致质量可以用 100 步甚至 200 步。实际产品中通常会在速度和质量之间找平衡点比如 30 步左右。一个实用的技巧是先用少量步数生成低质量图像如果用户满意再逐步增加步数精修。这样可以在交互场景中提供更好的体验。我在做图像编辑工具时就用过这个策略用户反馈很好。7. 完整数据集与代码资源的获取思路7.1 公开数据集的选用建议图像生成领域有几个常用的公开数据集比如 CIFAR-10、CelebA、LSUN 等。CIFAR-10 分辨率低适合快速验证算法CelebA 是人脸数据集适合做人脸生成LSUN 包含多个场景类别适合做多样化生成。选择数据集时要考虑你的目标和计算资源。如果只是学习原理CIFAR-10 就够了训练速度快一天就能跑完。如果想做产品级应用可能需要更大规模的数据集训练时间会显著增加。7.2 代码复现的注意事项复现论文时最大的坑是细节不一致。论文里可能省略了一些实现细节比如初始化方式、归一化层的位置、激活函数的类型。这些细节看似不重要但实际影响很大。我的建议是优先参考官方开源代码如果官方没有开源就找 star 数高的第三方实现。在复现时先用小规模数据跑通流程确认损失能正常下降再逐步扩大规模。不要一上来就用全量数据训练否则发现问题时已经浪费了大量时间。7.3 从零实现 vs 使用现有框架如果你只是想快速上手可以使用现有的扩散模型框架比如 Hugging Face 的 diffusers 库。它提供了预训练模型和训练脚本几行代码就能跑通。但如果你想深入理解原理建议从零实现一遍哪怕只是在小数据集上跑通。我自己是从零实现了一遍 DDPM虽然花了不少时间但对原理的理解深刻了很多。后来再用 diffusers 时遇到问题也能快速定位因为知道底层在做什么。所以我的建议是先跑通现有框架建立信心再回头从零实现加深理解。8. 一些个人体会与后续学习方向扩散模型的学习曲线确实比一般的深度学习模型陡一些因为涉及变分推断和马尔可夫链的知识。但一旦跨过这个门槛你会发现它的设计非常优雅训练稳定性和生成质量都让人满意。我在实际项目中使用扩散模型时最大的感受是调参比生成对抗网络简单很多但采样速度仍然是瓶颈。如果你的应用对实时性要求高可以考虑使用蒸馏技术将大模型压缩成小模型或者使用一致性模型进一步减少采样步数。后续学习方向方面建议先深入理解 DDPM 和 DDIM然后关注潜在空间扩散和条件生成。这两个方向是当前应用最广泛的。如果对视频生成感兴趣可以进一步学习时序扩散模型。但不管哪个方向基础原理都是一致的把基础打牢后面的扩展自然水到渠成。最后分享一个小技巧在学习过程中多动手画图。把前向过程、反向过程、损失函数的计算流程画成流程图比单纯看公式理解得快很多。我当初就是画了十几张图才把整个框架理清楚。希望这篇内容能帮你少走一些弯路更快进入扩散模型的世界。