多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

扩散模型完全指南:从噪声到清晰图像的生成原理与实操

扩散模型完全指南:从噪声到清晰图像的生成原理与实操 我第一次真正理解扩散模型是在跑通一个从纯噪声恢复出清晰人脸的训练脚本之后。我盯着那张从一片雪花点里逐渐浮现的五官看了很久脑子里只有一个念头这玩意到底是怎么做到的一个在训练时拼命往图片上加噪、把图像彻底毁掉的模型到了推理阶段反而能凭空从噪声里还原出清晰的图像。这不是魔法这是一套严谨的概率框架。这篇文章我想把扩散模型Diffusion Model从原理到实操完整地拆一遍重点回答三个问题它为什么选择噪声作为起点训练时到底在学什么以及推理时怎么从噪声中一步步抽出干净图像。同时会附上我在训练和调参过程中踩过的坑、试出来的经验以及一些可以直接抄走的结论。适合刚接触扩散模型、想深入理解底层逻辑或者准备自己训练一个模型的读者。1. 核心思路拆解为什么偏偏是噪声1.1 生成模型的两条路线一步到位还是迭代精修先说说扩散模型在整个生成模型版图里的位置。早年的生成对抗网络GAN思路是一步到位——生成器直接从一个随机向量映射出一张图判别器负责挑毛病两者对抗着共同进步。GAN速度确实快生成质量也高但问题有两个一是训练极其不稳定模式坍塌是家常便饭二是生成过程像个黑盒你很难控制它一步步怎么变。VAE变分自编码器走的是另一条路把图像压缩到一个低维潜变量空间再从这个空间解码出图像。它训练稳定但生成的图像偏模糊因为它本质上是在学一个平均。扩散模型的思路跟前两者都不一样。它不是一步生成而是把“生成”拆成几千个微小的步骤。训练的时候把一张清晰图像逐步加噪直到变成纯噪声生的时候从纯噪声出发逐步去噪每一步只去掉一点点噪声最终还原出图像。这个思路直观上就像一个雕塑家不是一锤子砸出形状而是一刀一刀慢慢修出来。为什么这个思路有效核心在于把一个困难的大问题拆解成很多个简单的小问题。直接从随机噪声预测一整张清晰的猫脸很难但“把当前这张噪声很多的图变得稍微干净一点”就简单得多。模型不需要一步到位只需要每步做好一个微小的去噪动作几千步累积下来效果就出来了。1.2 前向过程把图像一步步毁掉是有讲究的扩散模型的训练数据来自一个“人为设计”的过程叫前向过程。具体做法是取一张真实图像按一个预设的时间表在 T 步之内逐步往图像里添加高斯噪声。每一步加的噪声都不大但累积到最后图像会彻底淹没在噪声里成为一张近似纯高斯白噪声的图。这里有个细节特别重要每一步加的噪声不是随意定的而是按照一个预先设计好的“噪声调度表”来控制的。常用的调度策略有线性调度、余弦调度以及后来改进的 sigmoid 调度。线性调度在前中期加噪速度比较均匀余弦调度在接近纯噪声阶段会稍微放缓sigmoid 调度则让中间阶段变化更平缓。这个调度表直接影响训练难度和生成质量我后面实操部分会细说。前向过程还有一个很方便的性质不需要一步步去推。因为在每一步加的是高斯噪声多个高斯分布的叠加仍然服从高斯分布。所以给定原始图像可以直接一步算出任意时间步 t 对应的加噪结果。这个性质是扩散模型训练高效的关键——不然每张图都要从头加 T 次噪声训练效率就太低了。1.3 为什么一定是高斯噪声而不是其他干扰你可能要问为什么要选高斯噪声均匀分布的噪声不行吗椒盐噪声不行吗答案是高斯噪声在数学上有很多非常优美的性质刚好被扩散模型用上了。第一个性质是“可加性”——多层高斯叠加还是高斯。这让前向过程可以直接跳到任意时间步不像其他分布那样需要一步一步算。第二个性质是“重参数化”——高斯分布可以用“均值 标准差乘以标准正态随机变量”的方式表达这让反向传播可以穿过采样操作模型可以端到端训练。第三个性质是“热力学含义”——高斯过程是热力学中的扩散过程的数学近似这也是“扩散模型”这个名字的由来。从更实际的角度讲自然图像的边缘、纹理等高频信息在加噪过程中会先被淹没而低频的轮廓信息最后才消失。这就意味着反向去噪的过程实际上是“先定轮廓再修细节”这个生成顺序非常符合人类对图像的认知习惯。这也是为什么扩散模型生成图像的整体结构和语义一致性经常比 GAN 好——它有一个由粗到细的天然层次。2. 数学原理与训练目标模型到底在学什么2.1 前向过程的数学描述与重参数化技巧用数学语言来描述前向过程设原始图像是 x_0第 t 步的加噪图像是 x_t。每一步可以写成q(x_t | x_{t-1}) N(x_t; sqrt(1 - β_t) * x_{t-1}, β_t * I)这里的 β_t 是第 t 步的噪声方差是预先确定的一个小数值。整个前向过程就是一连串这种条件分布的连乘。由于高斯分布的可加性从 x_0 可以直接跳到第 t 步x_t sqrt(α_bar_t) * x_0 sqrt(1 - α_bar_t) * ε其中 ε 是标准高斯噪声α_t 1 - β_tα_bar_t 是从第 1 步到第 t 步 α 的连乘。这个公式非常重要训练的时候我们根本不需要逐步加噪只需要从这个公式出发一次就能得到任意时间步的加噪结果。重参数化技巧体现在采样 x_t 时我们不是直接“从分布里抽一个值”而是先抽一个标准高斯噪声 ε再用公式把它变换成我们想要的样本。这个变换过程是确定性的、可微的所以梯度可以顺利传回模型参数。没有这个技巧采样操作就无法反向传播训练就无从谈起。2.2 训练目标模型不是预测图而是预测噪声那么模型在训练时到底在学什么很多人会下意识地以为模型在学“如何把噪声图变清晰”。但实际上目前最主流的扩散模型架构学习的目标是给定加了噪的 x_t 和时间步 t预测当时加的噪声 ε 是多少。为什么预测噪声而不是直接预测原图两个原因。第一是数学上的简洁性在训练目标函数里噪声 ε 是从标准高斯分布中采样的它的分布形态最简单预测它等价于做了一个“单位方差标准化”的回归数值上更好优化。第二是信息量的分配在加噪过程中原图信息已经被打散到噪声里直接预测原图会让模型无从下手而预测噪声是“吾日三省吾身我加了什么”任务更聚焦。损失函数用的是简单的 L2 距离均方误差L E_{t, ε}[ || ε - ε_θ(sqrt(α_bar_t) * x_0 sqrt(1 - α_bar_t) * ε, t) ||^2 ]注意这里期望是对时间步 t、噪声 ε、训练图像 x_0 三者一起求的。在实际训练中每个 batch 的每个样本会随机抽一个时间步 t模型需要学会在不同噪声程度下都做出准确的预测。这就意味着模型内部必须理解“当前图像有多糊”时间步 t 的嵌入向量就是用来提供这个信息的。2.3 网络结构U-Net、时间嵌入与注意力机制预测噪声的网络目前的主流选择是基于 U-Net 架构的卷积网络关键组件有三个。第一个是 U-Net 的编码器-解码器结构通过下采样逐步扩大感受野提取高层语义特征再通过上采样恢复到原图尺寸。中间的跳跃连接把编码器的细节特征传给解码器保证生成图像的边缘和纹理不丢失。第二个是时间步嵌入。扩散模型的核心条件是“现在加了多少噪声”模型必须知道当前处于哪个时间步才能预测出对应规模的噪声。通常把时间步 t 编码成正弦位置向量再加到模型的每一层特征上。这跟 Transformer 里的位置编码思路同源。实际实现里时间嵌入会在每个分辨率层级都注入一次让模型在不同尺度上都感知到噪声水平。第三个是注意力机制。高分辨率特征上引入自注意力可以让模型捕捉远距离依赖关系——比如生成人像时左眼和右眼需要对称这种全局一致性光靠卷积很难保证。Stable Diffusion 这类大规模模型还在交叉注意力里加入了文本条件让模型可以根据提示词生成对应的图像内容。3. 反向去噪与采样策略从噪声中抽出图像3.1 反向过程的核心每步只去一点噪前向过程是把清晰图变噪声反向过程则是反过来从纯噪声出发逐步恢复图像。但如果真的去学习一个反向的逐步条件分布数学上非常困难。扩散模型的巧妙之处在于当每步加的噪声足够小的时候反向过程的每一步也近似服从高斯分布因此可以用一个神经网络来拟合。推理阶段我们有一个已经训练好的噪声预测网络 ε_θ。给定第 t 步的噪声图 x_t 和时间步 t我们可以估算出去噪后的图像x_0_est (x_t - sqrt(1 - α_bar_t) * ε_θ(x_t, t)) / sqrt(α_bar_t)这个 x_0_est 是对原始图像的预测。但我们不会直接用这个预测作为最终输出因为单步预测误差很大。合理的做法是基于 x_0_est 重新计算第 t-1 步的图像给它留一点合理的噪声。这个操作本质上是在“预测原图”和“保留噪声”之间取一个平衡。去掉多少噪声、保留多少噪声由时间表 α_bar_t 控制。整个过程重复几百到上千次图像会逐渐从模糊变清晰。3.2 DDPM 采样与 DDIM 采样速度与质量的平衡最原始的反向采样方法来自 DDPMDenoising Diffusion Probabilistic Models它严格遵循马尔可夫链的推导每一步都会加入一个采样得到的随机噪声所以生成过程是随机的。DDPM 的优点是数学严谨、生成质量好缺点是要完整走完 T 步每一步都要过一遍网络速度非常慢。在 T1000 的情况下生成一张图意味着 1000 次前向推理这在算力有限时是难以接受的。DDIMDenoising Diffusion Implicit Models给出了一个关键改进把反向过程改成确定性的。它不再在每一步添加随机噪声而只是迭代地去噪。这样有两个好处一是可以使用大步长跳跃采样比如只用 50 步就能逼近 1000 步的质量二是由于过程是确定性的从同一个初始噪声出发会得到同一个结果这让“潜在空间插值”成为可能——你可以在两个噪声向量之间做插值生成过渡自然的图像序列。DDIM 的加速原理一部分来源于它的非马尔可夫设定。原本 DDPM 要求状态序列必须严格满足马尔可夫性质每一步只依赖于上一步而 DDIM 允许更一般化的分布族只要边缘分布能和训练时对齐就行中间步骤可以用大步长跳。这相当于换了一条更快但还算稳的路。3.3 Classifier-Free Guidance可控生成的核心手段扩散模型有一个很实际的需求让生成结果符合某个条件比如“生成一只猫”而不是随便生成任意图像。最干净有效的方案是 classifier-free guidance简称 CFG这也是 Stable Diffusion 等模型默认采用的技术。CFG 的想法是同时训练一个无条件模型和一个条件模型实际的训练方式是随机丢弃条件同一个网络身兼两职。推理时同时算无条件噪声和无条件噪声然后做一个外推插值ε_final ε_uncond guidance_scale * (ε_cond - ε_uncond)guidance_scale 通常取 7 到 12。这个值越大生成结果越符合条件但多样性会下降图像也可能变得过饱和甚至失真。它相当于在“听用户的话”和“保持自然真实”之间做推拉。我实测下来7.5 到 8 是一个相对稳妥的通用值但具体还得看模型和数据分布。4. 实操过程与关键细节从零训练一个扩散模型4.1 数据准备与预处理不要低估这一步很多人一开始就把注意力放在网络结构上结果数据没处理好训练出来的模型要么什么都学不会要么生成图像全是噪声。根据我自己的经验数据准备阶段有几个容易踩的坑。第一是图像尺寸的统一。扩散模型对输入尺寸比较敏感如果你混着 256x256 和 512x512 的图一起训练模型很难收敛到清晰的结果。我倾向先把所有图像中心裁剪并缩放到固定尺寸比如 64x64 或 128x128小尺寸训练速度快验证想法足够了再往大尺寸做。第二是数据增强策略。扩散模型不像分类模型那样需要大量随机裁剪翻转——它的任务核心是还原真实分布过度增强反而会让模型学到错误的“噪声版”图像。我实际用的只有随机水平翻转偶尔加一点轻微的色彩抖动。竖直翻转在自然图像上慎用因为会破坏“上-下”的语义关系对医学影像或卫星图这种没有明确方向性的数据可以放宽。第三是标准化方式。图像像素值需要从 [0, 255] 映射到 [-1, 1]即 pixel pixel / 127.5 - 1。这个映射和 U-Net 最后一层的激活函数是配套的。要保证模型的输出范围和数据范围一致否则损失函数永远降不到理想水平。我见过有人忘了归一化效果一直很差找了好久原因。4.2 网络与训练配置我常用的参数组合对于 CIFAR-10 或 64x64 的 FFHQ 这类小规模数据集我常用一套比较稳定的配置可以直接抄作业配置项我的推荐值备注图像尺寸64x64验证想法显存友好时间步 T1000标准配置训练时可抽任意 t噪声调度余弦调度比线性调度更稳不易出现过曝网络结构U-Net带时间嵌入 自注意力通道数从 128 起步逐层翻倍损失函数L2MSE主流方案L1 也可以但收敛稍慢优化器Adamlr2e-4weight_decay 可设为 0Batch Size64显存不够就降到 32同时调低学习率EMA0.999对生成质量有明显提升训练步数约 50 万到 100 万小数据集基本够用这里重点说说 EMA指数移动平均。训练过程中模型参数是持续变化的最后几步可能刚好落在参数空间的坏点上直接用它做推理生成出来的图往往不太稳定。维护一组参数的滑动平均值相当于在训练轨迹上做平滑能显著提升生成质量。很多代码库把这个实现藏在工具函数里但它对效果的影响非常实在。4.3 训练过程中的监控方法训练扩散模型的一大痛点是没有一个直接可读的指标告诉你“模型快好了没”。Loss 会下降但下降并不直接等于生成质量好。我的做法是定期保存模型 checkpoint并且每隔固定步数就停下训练跑一次完整的 DDIM 采样把生成的图像直接保存下来肉眼观察。这是最靠谱的监控方式。另外一个有用的工具是“噪声预测可视化”。在固定一组验证图像上抽取几个固定的时间步 t把模型预测出来的噪声画出来。如果预测噪声看起来像真实的随机噪声说明模型学得不错如果预测噪声里还能看出图像的轮廓说明模型还没完全收敛对去噪任务的理解还不够透彻。训练曲线的形态也有规律。前期 loss 下降非常快但这时生成的图像全是乱的中期 loss 趋于平缓图像开始出现有意义的颜色块和边缘后期 loss 几乎不动但生成质量依然在缓慢提升。如果 loss 卡住不动超过 20 万步可能需要调整学习率或者检查数据是否有问题。4.4 推理参数选择步数与引导强度的配合推理阶段能调的参数不多但每个都立竿见影。最重要的两个是采样步数和 classifier-free guidance scale。采样步数的选择跟采样器相关。DDPM 采样器基本上需要完整走 1000 步质量才有保证换成 DDIM 采样器后在 50 步左右就能达到接近收敛的效果再增加到 100 步质量提升就很有限了。实践中如果想进一步提速可以考虑 DPM-Solver 或 Euler 等更高级的采样器它们利用噪声调度的连续性做更高阶的数值近似20 步就能出不错的结果。guidance scale 的调节我在 3.3 已经说过。这里补充一个经验对于本身质量很高的模型guidance scale 可以适当调低7 左右就好对于小模型或者训练不充分的数据集guidance scale 可以调到 10 到 12能掩盖一部分生成瑕疵但要注意不要调太高否则会出现色彩过艳、边缘发硬的现象。还有一个容易被忽略的推理参数是随机种子。扩散模型推理是采样过程同一个输入图像在不同种子下会得到不同结果。排查问题时固定种子相当于控制了变量能帮你判断是模型问题还是随机性问题。5. 常见问题与排查技巧实录5.1 训练了很长时间生成图像仍然全是噪声这种情况最让人崩溃我遇到过不下三次。排查顺序建议按下面三个方向来。第一确认噪声预测是否正确。在训练脚本里加一段调试代码固定一张验证图和一个时间步 t比如 t500手动加噪后让模型预测噪声对比真实噪声和预测噪声的距离。如果两者的差异非常大说明模型整体认知有问题优先检查网络结构是否实现了时间嵌入以及时间步 t 是否真的传进了网络。第二检查图像归一化和反归一化逻辑。模型输出范围是 [-1, 1]保存图像时要先做 (x 1) / 2 映射到 [0, 1]再乘 255 转成 uint8。如果你在保存前忘了这步看到的就全是黑底或花屏看起来像噪声其实是数值范围错了。第三确认 noise schedule 是否合理。我试过把 β 上限定得过高导致中后期的 x_t 几乎完全淹没在噪声里模型学不到有效梯度。把时间表画出来看一遍如果 β_t 增长过陡可以换成余弦调度。5.2 生成图像模糊细节纹理缺失模型能生成轮廓正确的图像但缺乏细节这个问题的根源通常是模型容量不够或者训练不充分。用小数据集和大模型学到的往往只是“平均脸”效应生成结果趋于保守细节丢失严重。解决办法是增大模型容量加宽通道数、降低学习率延长训练时间或者换用更大的数据集。另外可以试试把 EMA decay 调大一点比如从 0.999 调到 0.9999让参数平均更“慢热”有时候能保留更多高频细节。还有一个容易被忽略的原因推理时用的采样器精度不够。如果只在 10 步以内用 DDIM 采样图像确实会比较模糊。换用 DPM-Solver 或者 Euler 采样器在同样的步数下细节保留能力要好很多。这个坑很多教程都没提我是在一次次对比实验里发现的。5.3 采样速度太慢一张图等半分钟如果是训练好的模型推理慢首要优化方向是采样器升级。默认的 DDPM 1000 步在 CPU 上确实等到天荒地老在 GPU 上也需要几秒到几十秒。第一步换成 DDIM 并用 50 步第二步换 DPM-Solver 20 步第三步把模型从 fp32 转成 fp16推理速度大致能提升一倍多。如果是在训练过程中每次验证都要跑采样建议把验证频率放宽比如从每 1000 步改成每 5000 步每次都固定只跑 30 步的 DDIM不求精确只求趋势。等训练彻底结束再跑高质量采样。5.4 显存不足训练时 OOM显存不足是自建扩散模型最常遇到的物理瓶颈。最直接的办法是减小 batch size但要注意同步调低学习率保证训练的稳定性。我的经验是 batch size 减半学习率也减半收敛速度会稍慢一点但不容易炸。另一个有效手段是梯度累积。把 batch size 设为 16但每 4 个 batch 做一次参数更新等效 batch size 仍然是 64。这样显存只吃 16 张图的空间但训练效果接近 64 的大 batch。缺点是训练时间会变长因为每个 batch 都要单独前向和反向传播。还有一招是使用混合精度训练AMP。把模型参数和梯度保持在 fp16但损失计算和优化器更新保持在 fp32能显著减少显存占用。我实测下来2GB 显存可以勉强跑一个 64x64 的轻量扩散模型想跑 512x512 的 Stable Diffusion 级模型至少需要 8GB 以上显存。6. 顺带聊聊 Stable Diffusion扩散模型在潜空间的实践如果你接触扩散模型是看了 Stable Diffusion 的各种出图效果那你应该知道它其实并不直接在像素空间做扩散而是先通过一个 VAE 把图像压缩到潜空间latent space在低维潜变量上执行扩散和去噪过程最后再用 VAE 的解码器把潜变量还原成图像。这样做的好处非常直观像素空间的 512x512 图像数据量是 262144 维而潜空间经过 8 倍下采样后只有 4096 维左右。在低维空间里扩散算力消耗大幅下降这也是 Stable Diffusion 能在消费级显卡上跑起来的原因。latent diffusion 的架构还带来了一个便利——潜空间的语义信息比像素空间更紧凑条件控制比如文本、边缘图、深度图更容易注入。理解了这个设计之后很多 Stable Diffusion 的“神奇操作”就都能解释清楚了。比如为什么在 WebUI 里调 CFG 值会影响生成风格强弱为什么不同采样器Euler、DPM、DDIM出图效果差别那么大为什么模型会默认输出 512x512 而不是 1024x1024。它本质上还是那套“预测噪声、迭代去噪”的流程只是在不同的空间里跑而已。如果你准备本地部署 Stable Diffusion建议优先用整合包把基础环境跑通再逐步换成手动部署。整合包背后做的事其实就是配置 Python 虚拟环境、安装 PyTorch、拉取模型权重、启动 WebUI 界面只是把步骤封装好了。手动部署能让你更清楚每个环节发生了什么排查问题也更有底。我个人在实际操作中有一个体会跑通一个扩散模型并不难难的是理解每个组件存在的理由。所有看似“魔改”的优化——DDIM、CFG、EMA、latent diffusion——都不是凭空冒出来的都是为了解决一个具体的问题。你只有亲手把每一步的输入输出都打印出来看过、亲手把生成质量调坏再调好过才能真正把这些组件内化成自己的东西。最后再分享一个小技巧在项目初期先用极小规模的数据集和极小的模型完整跑通训练和采样流程再把规模逐步放大。这个策略能帮你节省至少一周的调试时间。我所有出过问题的环节几乎都是在最小配置下暴露出来的。
返回列表