扩散模型这两个词,放在不同圈子里指向完全不同的东西。气象那边说的是高斯烟羽模型,描述污染物随风飘散的浓度分布;人工智能领域说的则是生成模型,核心思想简单到让不少人第一反应是"就这":给一张图反复加噪声,直到它彻底变成雪花点,再训练一个网络把加噪过程一步步倒回去。过去两年里,你看到的那些以假乱真的AI绘画、图像编辑、视频生成,绝大多数底层都站着这套思路。这篇博文只讲后者,梳理它是怎么从物理学论文里一步步走到今天这个位置的,中间哪些论文是绕不开的里程碑,哪些工程细节决定了它能否被普通人生搬硬套地跑起来,以及我在复现这些模型时踩过的坑。
作为一名常年跟生成模型打交道的从业者,我觉得扩散模型的发展史几乎是近五年深度学习领域最值得复盘的一条线。它不像之前某些技术那样靠单个爆点一战成名,而是从理论到工程再到生态,一层一层磨出来的。对新手来说,理解这条线能帮你少走很多弯路;对想上手做项目的人来说,搞清楚它为什么这样设计,比单纯跑通一个开源仓库重要得多。
1. 扩散模型的前传:从热力学假设到生成模型雏形
1.1 为什么所有人都从"非平衡热力学"讲起
扩散模型的名字很直白,直觉就来自物理里的扩散现象——一滴墨水滴进清水,最终会均匀散开。把这个过程反过来,就是从均匀的噪声里"聚拢"出一张清晰图像。2015年之前,很少有人认真想过这种逆向过程可以被神经网络学会。
当时的生成模型主流是两派:一派是GAN,靠生成器和判别器互相对抗,训练技巧多得像玄学,动不动就崩;另一派是VAE,求的是潜在空间的概率分布,生成的图总有种"糊了一层滤镜"的感觉。扩散模型最初的定位很尴尬:它的采样速度慢得惊人,生成质量又不如GAN,尤其在高清图像上根本没有优势,因此学术界之外几乎没人关注它。
这条线的真正源头要追溯到2015年ICLR上的一篇论文,题目是《Deep Unsupervised Learning using Nonequilibrium Thermodynamics》。作者是Sohl-Dickstein等人,他们从非平衡统计力学的角度提出了一个框架:前向过程是逐步加噪直到完全变成噪声,反向过程则由一个参数化的马尔可夫链来逼近真实逆过程。论文里其实已经写清楚了训练目标是最大化对数似然的一个变分下界,也就是后来DDPM里那套损失函数的前身。
但当时这篇文章的落点非常学术,实验也仅仅停留在MNIST这种玩具数据集上,生成的数字模糊得只能勉强辨认。最要命的是训练需要反复模拟完整马尔可夫链,计算开销大得吓人。这篇论文被引用了很多年,但真正"复活"它的是2019到2020年那批把推导和工程细节补齐的人。
1.2 "扩散模型"和"高斯扩散"不是一回事
在往下讲之前,有必要先做个名词澄清。网络上搜"扩散模型",经常蹦出来"python 大气污染高斯扩散模型 代码"这类结果。气象和环境领域所说的高斯扩散模型解决的是污染物在大气中如何稀释扩散,核心公式推导出来是一个烟羽浓度分布表达式,参数包括风速、源强、扩散系数。它和我们现在讨论的AI生成模型除了共享"扩散"这个物理意象之外,几乎没有交集。如果你是为了做AI绘画或文生图来找资料,看到高斯烟羽的代码可以直接跳过;反过来,如果是为了毕业论文里的大气污染模拟搜索,也别点进Diffusion Models的论文列表浪费时间。这个区分在刚开始接触的人中几乎每天都能见一次。
2. DDPM登场:扩散模型第一次变得"可用"
2.1 Ho等人究竟改了什么
2020年,Jonathan Ho等人发表了《Denoising Diffusion Probabilistic Models》,缩写DDPM。这篇论文可以被视为扩散模型真正进入深度学习主流的转折点。网上几乎所有扩散模型代码教程都以这篇为起点,不是没有原因的。
DDPM的核心贡献不是提出全新的理论,而是把之前那套复杂框架做了大量简化,让它能实打实地跟GAN扳手腕。具体来说有三个关键改动。
第一个改动是前向过程固定下来,不再用学习到的方差参数,而是用一组预先设计好的方差调度表(variance schedule),从接近0的噪声逐步增加到一个很大的值。这一步让加噪过程变成了一个完全确定、没有可学习参数的流程,训练时只需要直接采样任意时间步的带噪图像就够了。
第二个改动是把逆向过程重新参数化。学习"直接预测噪声"而不是"预测图像均值"。这个设计初看很奇怪,但效果非常实:模型只需要学会预测当前图像里混入了多少噪声,损失函数就是简单的MSE(均方误差)。后面大量实验证明,这种间接路径比直接预测图像本身更容易优化,尤其在图像域上收敛速度明显更快。
第三个改动是发现了使用简单的均方误差损失、配合足够多的扩散时间步(论文默认1000步),模型能生成出当时几乎与GAN持平的高质量图像。这在CIFAR-10和CelebA等数据集上都得到了验证,而训练过程比GAN稳定太多了——没有判别器、没有对抗损失、没有模式坍塌。你只需要一个U-Net加一个简单的MSE损失,剩下的就是等它慢慢收敛。
2.2 为什么U-Net成了标准选择
DDPM里负责去噪的网络是U-Net,这个选择被后来的几乎所有扩散模型继承下来。为什么偏偏是它?可以从任务性质来理解:去噪任务需要输入和输出分辨率一致,这和图像分割很像。U-Net的编码器-解码器结构加跨层跳跃连接,可以在下采样捕捉高阶语义信息的同时,通过跳跃连接保留局部细节,避免生成图像糊掉。
以我跑DDPM的经验,U-Net里的时间步嵌入(time embedding)对质量影响非常大。许多初学者复现DDPM时只用简单的常数把时间步喂给网络,效果明显不如论文里那种正弦位置编码再经过两层MLP的方式。时间步嵌入的作用是让网络知道"现在处理的噪声水平有多大",模型在去噪初期和末期做的事情完全不同——初期偏向恢复整体轮廓,末期偏向精修细节。它在训练时使用越来越大的正弦变换值,本质上是一种条件输入。这个模块看似不起眼,但丢掉之后训练损失会掉得很慢,生成样本质量也大打折扣。
DDPM训练也可以看作是一个纯粹的"配方"玩法:给定一个从0到T均匀采样的时间步,从训练集抽取一张图,按对应方差注入噪声,然后让网络预测加入的噪声。整个训练集被反复利用,随机的时间步骤给网络提供了不同难度级别的去噪任务。这种训练方式的好处是极其均匀、极其稳定,坏处是推理时你必须从T到0跑满一千步,一步一算,速度自然慢得让人怀疑人生。
2.3 加速采样:从DDIM到扩散模型的工程优化
DDPM虽然有质量,但采样速度太慢。于是很快出现了一大批围绕"加速采样"的优化研究。其中绕不开的是2020年底提出的DDIM(Denoising Diffusion Implicit Models)。DDIM的核心思想是把采样过程从马尔可夫链改成非马尔可夫过程,允许跳步采样——比如原来1000步,现在可以只取50步、20步,结果也不会差太多。常见做法是在采样时用一个更粗的时间步子集,配合不同的随机种子组合。
从从业者角度看,DDIM更大的意义是提供了一个确定性的采样映射:同样的初始噪声,走同样的采样轨迹,得到的生成结果基本一致。这个性质让"潜空间编辑"成为可能,也就是在噪声空间中插值或微调,输出端产生平滑的视频效果或风格混合。如果没有DDIM,今天很多花式玩扩散模型的技巧几乎都不成立。
另一个重要的加速方案是蒸馏类的思路,比如通过多个teacher模型逐步蒸馏到一个student模型,让采样步数降到几步甚至一步。这类方法在学术界很热,但从工程落地角度,目前主流Stable Diffusion生态里大家更常用的是DDIM或DPM-Solver这类基于常微分方程数值求解器的采样器,训练好的模型不需要额外蒸馏,直接在采样阶段替换求解器就能提速。DPM-Solver这类方法把扩散模型的反向过程看作一个常微分方程,利用多项式的数值积分近似,20步就能得到可接受的图像。这部分优化是实际应用中性价比最高的一项,因为你什么都没改,只换了一个采样器,速度就能翻好几倍。
3. 潜在扩散模型:Stable Diffusion的工程飞跃
3.1 高分辨率困境与感知压缩的思路
DDPM质量不错,但有一个致命短板:直接在高分辨率像素空间上训练,内存和时间开销都大到不可接受。尝试在512×512甚至1024×1024上长时间训练的人都知道那种痛苦——一张显卡顶着上限跑,batch size调不下来,生成一张图要等几分钟。要想把扩散模型推到产品级别,必须压缩计算域。
2022年初的《High-Resolution Image Synthesis with Latent Diffusion Models》给出了解决路线:先训练一个自编码器(通常是VQ-GAN或KL-VAE)把图像压缩到一个低维潜在空间,然后在潜在空间里跑扩散过程。这就是"潜在扩散模型"(Latent Diffusion Model,LDM)。图片尺寸缩小到原来的1/8甚至1/16,计算量自然大幅下降,而自编码器的解码器负责把潜在表示还原回高清图像。
这个设计的精妙之处在于,它把"感知压缩"和"生成"两件事拆开了。感知压缩负责把图像压缩成人类视觉上几乎无损的低维潜码,扩散过程的每一步不需要关心单个像素值,只需要学习在潜空间里的概率分布。就像写文章不需要先考虑每个字的肌肉运动,而是先在"语义层"组织语言。这个分离让扩散模型在高分辨率场景下从"实验室玩具"变成了"可落地的工业化方案"。
3.2 Stable Diffusion生态:从LDM到开源大爆发
潜在扩散模型论文一经发布,配套的代码和预训练权重就开放出来,后来正式入了Stable Diffusion这个名字。在我看来,Stable Diffusion对行业的最大贡献不是模型效果本身,而是一套可复用的工程标准:U-Net的计算完全放在潜在空间,文本提示词通过CLIP文本编码器变成向量后注入去噪过程,调度器支持多种采样策略,权重分模块加载可以像积木一样插拔。
这个设计让社区涌现出大量二创玩法。比如ControlNet,通过给一张条件图(深度图、线稿、姿势骨架)单独训练一个冻结主干之外的控制分支,实现对生成图像的细粒度控制。再比如LoRA,在冻结原始权重的大前提下,通过在部分矩阵上插入低秩适应模块做轻量微调,让普通玩家用一张不算好的显卡就能训练出某个特定风格或人物。这些围绕潜在扩散模型的周边生态,已经远远超出了论文作者最初的想象。
我必须承认,潜在扩散模型的实际训练门槛其实不低——自编码器的压缩和解码质量,直接决定了最终画质。想要跑通一整套训练流程,至少需要熟悉Transformers、卷积架构、注意力机制、CLIP编码器等多套组件。好在社区已经把这些都集齐了,初学者更常见的使用路径是直接拿Stable Diffusion开源的模型权重,用Diffusers库的几行代码完成文本到图像的推理。这是不是在做研究另说,但对于产品原型验证和内容生产来说,性价比极高。
3.3 条件生成与无分类器引导:为什么提示词能影响画风
扩散模型早期的无条件生成,纯粹是"随机噪声到随机图像"的过程。但真实场景需要可控生成,于是条件生成成了热点。所谓条件生成,就是给去噪网络额外输入一个条件变量,比如类别、文本描述、深度图。Stable Diffusion里最常用的做法是"交叉注意力(cross-attention)":在U-Net的每个分辨率层级,把文本嵌入向量与图像特征做交叉注意力计算,让文本语义引导图像的生成方向。
还有一项至关重要的技巧叫"无分类器引导"(Classifier-free Guidance,CFG)。它不额外训练分类器,而是在训练时随机将条件置空,让网络同时学会"有条件的去噪"和"无条件的去噪"。推理时,整个生成方向被推向条件与无条件之间的差异,控制力度由一个scale参数决定。scale越大,生成结果越文本一致,但过度增加会损失多样性甚至出现画质崩坏。这个技巧让提示词对输出效果的控制变得非常明确,也是为什么同样的模型,调高CFG scale之后画面会变得细节锐利但有些"用力过猛"的原因。
在实际复现Stable Diffusion风格迁移时,我发现条件注入的位置和时机可以玩出很多花样。有人喜欢把文本条件同时喂到多个尺度,生成结果全局语义更强;有人只在低分辨率阶段注入,保持细节自然。但最基本的原则是:条件信息必须与去噪网络当前看到的噪声水平匹配,否则网络会在不该受到强烈文本影响的时间步被拉扯,结果往往是主体正确但细节一团乱。
3.4 采样调度器与推理参数的选择
跑扩散模型,你可能听过cheduler、sampler这些词。调度器决定每个时间步用的是哪种噪声幅度,采样器决定步长和更新公式。同一个模型,换不同的采样器,画面风格和收敛速度都会有明显差异。比如DPM-Solver用20步就能接近DDIM100步的效果,而如果只要快速预览,有些采样器支持8步甚至4步出图,虽然细节会略有损失。
从我个人的经验看,生产环境里最常用的组合是:训练时用线性(linear)或scaled linear调度,推理时优先尝试DPM-Solver或Euler采样器。如果你生成的图总是偏灰或者偏白,大概率是调度器设置和模型训练时不统一造成的——这个问题在社区里非常常见,根本原因不是你网络写得不对,而是噪声尺度定义不一致。Diffusers库已经帮用户屏蔽了多数细节,但当你开始改造源码或自定义训练流程时,就必须认真对照论文里的beta schedule定义。
4. 实战:从零复现一个轻量级扩散模型
4.1 数据集与基础架构选择
现在网上各种Stable Diffusion二次封装工具很多,但从学习角度讲,亲手复现一个在MNIST或CIFAR-10上的DDPM还是很有必要的。它能帮你真正理解时间步嵌入、噪声调度、去噪网络的相互作用。我一般建议的路径是:先在MNIST上跑通无条件DDPM,再增加简单条件(如类别标签),最后再切换到潜空间和文本条件。
数据准备阶段有个容易被忽略的点:归一化一定要做好。图像像素值应缩放到[-1, 1],而不是[0, 1],这样加噪后信息才能对称地淹没在噪声中。如果你只缩放到[0, 1],梯度在经过激活函数之后很容易出现偏移。我自己调试时最常遇到的不收敛问题,有一半都源于这个看似不起眼的细节。
网络结构方面,新手可以直接用一个四层下采样和四层上采样的U-Net,注意力模块先在最低分辨率层加入以减少计算量。时间步嵌入用正弦位置编码加MLP,所有残差块都加上它。这个规模在单块消费级显卡上跑MNIST,大概几百个epoch就能得到不错的效果。
4.2 训练步骤与损失函数细节
训练循环非常简单,伪代码层面:
- 从训练集中采一个batch图像
- 对每张图随机采一个时间步t(均匀分布)
- 根据预定义噪声调度计算出对应噪声强度
- 将图像加噪得到x_t
- 把x_t和时间步嵌入送入U-Net,输出预测噪声
- 计算预测噪声与真实噪声之间的MSE损失并回传
看似简单,但里面藏着一个关键细节:训练过程中,时间步t的采样要尽量覆盖整个0到T区间,否则网络对某些噪声水平的去噪能力会明显偏弱。更优的做法是使用"重要性采样"或time step grouped采样,让低频段和时间步分布更均匀。不过对小数据集来说,均匀采样已经够用。
损失函数的缩放也很微妙。论文里用的是标准的MSE,但在实际实现中,有人会在损失前面乘以时间步相关的权重。这个权重在推导时可以来自变分下界的展开项。多数现成代码直接不做额外缩放也照样能收敛。我的经验是,初期不要引入太多花活,先让标准MSE稳定下降,再逐步尝试变种。
训练时还要注意学习率和batch size的关系。DDPM对batch size比较敏感,过小的batch会让梯度噪声变大,生成样本在细节上容易不稳定。我用MNIST时batch size 64起步,CIFAR-10则至少128。如果显存不够,宁可调小模型通道数也别硬扛。这一点和GAN很不一样,GAN经常小batch也能跑,但扩散模型的经验是batch size明显影响收敛质量。
4.3 推理采样与图像质量评测
训练完成后,推理过程就是反向跑马尔可夫链:从纯高斯噪声出发,按时间步从T到0逐步去噪。标准DDPM采样要跑1000步,非常慢。与训练时完全一致,每一步都加入一个小的随机项,保证生成多样性。如果你用DDIM,就可以把时间步压缩到50步甚至20步,质量损失在可控范围内。
评估生成质量,最常用的指标是FID(Fréchet Inception Distance)。它把生成图像和真实图像分别用预训练网络提取特征,计算两组特征分布之间的Wasserstein距离,数值越低代表分布越接近。FID之外还有IS(Inception Score),更侧重单张图的可识别性和多样性。但因为FID计算方式更符合人类对图像分布相似度的感知,实际用得更广。
我在实测中见过不少"FID漂亮但肉眼效果一般"的情况,尤其在特定小数据集上。因为FID对样本数量、图像分辨率都比较敏感。**建议在训练过程中定期采样几条固定种子链,直接目测去噪过程,比只看指标更接地气。**这个习惯我保持到现在——数值可以骗人,但采样轨迹骗不了你的眼睛。
4.4 代码实现中的常见坑
跑扩散模型,有几类问题几乎每个人都会遇到。
第一类是训练损失正常下降,但采样结果全是噪声。这个通常是采样器和噪声调度不匹配。比如训练时beta从0.0001线性增到0.02,推理时却用了cosine调度,那就对不上。还有采样时忘了累加噪声项或者时间步方向搞反,也容易出现一地鸡毛。
第二类是生成图像整体偏暗或偏亮。排查思路很简单:检查图像归一化是否存在双重处理。比如模型内部已经做了tanh激活,外部又做了一次[0,1]缩放,就会导致灰度偏移。
第三类是训练时间太长但效果提升缓慢。先别急着加模型容量,而是要确认是否每个batch里时间步覆盖率足够。如果大多数时间步都集中在噪声很小或很大的区域,模型的中间难度学习就会很差。我发现一个很好用的技巧:训练时记录每个时间步的损失,输出一张"损失-时间步"曲线。如果曲线在中间段高得离谱,说明时间步采样分布需要调整。
另外,需要注意随机种子管理。扩散模型训练对随机种子的敏感度不算极端,但推理时如果你想稳定复现某个效果或做AB测试,最好固定噪声种子,并且把采样器设置固定下来。否则看起来是同一个模型,两颗不同的随机噪声种子可以生成完全不同的画面,容易让人误判为模型问题。
我还想强调一点:多GPU训练时,全局batch大小对BN层影响很大,而U-Net里常常有Group Normalization而不是Batch Normalization,很多开箱即用的框架对BN处理得很好,但对GroupNorm的分布式同步反而需要格外小心。如果你是自己搭工程,建议直接使用GroupNorm,否则跨卡训练时精度和行为很容易漂移。
5. 模型选型与落地建议:不同需求怎么选
5.1 从DDPM到LDM,技术选择的取舍
学完基础,面对真实项目时大家最常见的困惑是:我到底该用Stable Diffusion还是从头训练一个自己的扩散模型?答案取决于你的数据和任务。如果你的需求是“给特定风格数据做定向生成”,那用Stable Diffusion加LoRA微调是最经济的路径;如果你的数据非常特殊,比如医学影像、工业缺陷检测,预训练权重和领域不匹配,那就需要从头或者从中间层开始高强度的微调。
LDM系列中,还有SDXL这类进一步放大基础模型规模的方案。它引入了更大的U-Net主干和更精细的文本编码组合,效果比早期的SD1.4/1.5更细腻,但显存和推理开销也明显上升。实际选型时要考虑部署场景:To C的实时交互产品,更看重低延迟,可能需要蒸馏蒸馏后的轻量模型;面向内容质量的工作流,则愿意多花几秒换更好的画面。这个权衡不是越新越好,而是要跟业务指标绑定。
5.2 小团队或个人玩家的实操心得
对于个人开发者或者三五人的小团队,我没有太多建议要讲大道理,只说最实在的几点。
- 不要一开始就尝试全流程复现LDM,那个工程量很大,其中包括自编码器、CLIP文本编码、U-Net、采样器等模块,任何一个环节出问题都会把你拖垮。
- 直接用现成Diffusers库起步,先跑通文生图推理,再研究细粒度控制,比如通过lora、Textual Inversion之类的方案学习风格。
- 数据管理一定要自动化。AI绘画模型的文本-图像数据对质量要求极高,清洗、去重、打标这三个环节占用的时间往往比训练还多。一条标签混乱的数据,比十条低分辨率数据更伤模型。
- 显卡推荐至少在16GB显存以上,否则训练LoRA都很勉强。用小显存不是不能跑,但迭代速度和实验效率会让人非常痛苦。Cloud GPU按时计费反而是小团队更好的一种选择,很多商业平台支持一键开启训练环境,省去环境配置的老大难问题。
还有一个容易犯的错:把开发环境和推理环境混在一起。**强烈建议训练和推理分开管理,因为训练时对CUDA、PyTorch版本的要求和推理时不一样,混用很容易出现玄学问题,比如dataloader worker段错误、显存碎片化等。**我自己维护项目的习惯是训练用一台大显存机器,推理部署用容器镜像固定环境,两者之间只通过模型权重文件对接。
5.3 最后再分享一个实际工作中的小技巧
如果你做的是类似“风格迁移”或者“人物一致性生成”的任务,可以先跑一次高质量的图像重构,直接观察自编码器的压缩瓶颈在哪。很多时候生成的图像主体很好,但细节比如文字、细条纹,总是一塌糊涂,这不是扩散模型的问题,而是潜在空间压缩阶段丢掉的细节太多。尝试换用更高分辨率的自编码器或者增加重构阶段的损失权重,往往比拼命调U-Net更有效。
这个经验是我在一次产品原型迭代中踩了大坑才发现的。当时团队花了大量精力调CFG scale和采样步数,画面依然有微妙的纹理畸变,最后把注意力转向autoencoder,问题在一周内就解决了。扩散模型是一个“木桶效应”非常明显的技术栈,训练质量、文本编码、潜空间重构、采样调度器,每一环都会成为短板。你在学习它的发展史时,也要记住这个教训——单点突破很难,系统性理解才能让你真正驾驭它。