6.3 扩散模型(一)DDPM原理
1.生成这一摊事,最后怎么就落到扩散模型头上了
说起来,生成模型这几年的风向变了好几回。早些年大家折腾GAN(生成对抗网络),它的思路挺巧,一边训一个生成器造假,一边训一个判别器打假,两边互相逼着长进,后来StyleGAN把人脸生成推到了吓人的精度。再后来VAE(变分自编码器)又火了一阵,它把编码和重参数化玩得漂亮,配一个KL散度做正则(KL散度我们在1.4 节详细聊过,它衡量两个分布差得有多远),整出一个连续可插值的隐空间。可惜GAN训练不稳,VAE画出来的东西偏糊,两家各有各的难处。那时候大家用GAN做图像生成,常常要调参调到深夜还稳不下来。
扩散模型冒出来其实是2020年的事。DDPM这篇论文一出来,大家发现这套路画出来的图又清晰、训练又稳,慢慢地就成了图像生成的主流。再后来Latent Diffusion把扩散搬到隐空间里做,配上前一阵4.10 节讲的CLIP做文本条件,就成了大家熟知的Stable Diffusion。如今扩散模型早已不只是画图玩具,电影分镜、广告插画、乃至药物分子设计,到处都能见到它落地的影子。说到底,扩散模型能成主流,靠的是两条腿走路,一是效果确实好,二是训练确实稳。
我们今天就先把DDPM这件事的原理讲清楚,下一篇再去聊加速采样那些事。
2.核心就两件事:前向加噪,反向去噪
扩散模型的核心,说穿了就是两个过程。一个叫前向过程(forward process),做的事情是往图里加噪声。另一个叫反向过程(reverse process),做的事情是从噪声里把图还原回来。这两个过程名字对称,做的事恰好相反。
我们一个一个看。
前向过程是预先定好的,不用学。它的思路朴素得可爱,给一张干净的图,往里加一点点高斯噪声,得到一张稍微花一点的图,再往这张花的图上加一点点噪声,得到一张更花的图,这样一步一步加下去。只要步数够多,最后这张图就被噪声彻底淹掉了,变成一张纯粹的随机噪声,肉眼看上去和电视没信号时的雪花点一模一样。
这个过程为什么是预先定好的呢,因为加噪声的规则是一组固定的公式。我们设总步数为 , 就是前向过程一共要走多少步,通常取 。再设一个单调下降的序列 ,每一项 都是一个介于 和 之间的小正数,叫作噪声调度(noise schedule), 控制第 步加的噪声有多大。这些 在训练之前就已经定死,不参与梯度更新。
给定初始的干净图 ( 就是第 步那张还没动过的原图,下标 表示步数),第 步加噪之后得到的那张图记作 。每一步的加噪规则是这样的:
我们一项一项拆。等号右边的 是上一步那张图, 就是上面说的噪声调度。 是这一步加进去的高斯噪声, 这个希腊字母我们全文都用来表示噪声,下标 说明它是第 步的那一份。 是标准正态分布,意思就是它的均值是 、方差是 ( 是单位矩阵,对角线全是 、其余位置全是 )。前面那两个根号是给定的固定系数,用来控制上一步的图和这一步的噪声各占多少分量。一句话总结,每一步都是把上一步的图稍微缩小一点点,再补上一点点噪声。
这里有个特别漂亮的性质,叫作闭式跳跃加噪。它说的是,给定原图 ,我们其实不用真的一步一步加,可以直接跳到任意中间步 。我们再设两个记号,,,也就是前 个 全部乘在一起( 和 都是中间量,记下来方便后面写公式)。那么:
这个闭式是怎么推出来的。 关键用到一个高斯分布的性质:两个独立高斯 和 相加,还是高斯,方差是 。我们从单步公式 出发,把 再展开一代:,代进去:
后面两个噪声项都是独立的标准正态乘个系数,合起来方差是 ,所以合并成 ( 仍是标准正态)。于是 。这样一层层往回展开到 ,前面的系数连乘就是 ,后面噪声的方差凑成 ,闭式就出来了。这个性质是扩散模型能高效训练的基础——训练时每张图随机挑一个 ,一步就能算出 ,不用真跑 步前向。
这里 仍然是标准正态噪声,只不过这一次是从头到尾只采样一次的累计噪声。这个公式特别重要,训练时我们要随便挑一个步数 、随便采一份噪声 ,就能立刻拿到对应的 ,不需要把前 步全跑一遍。我记得小张第一次看代码的时候,半天没反应过来这个跳跃是怎么回事,盯着这行公式看了小半个钟头才连连点头。
到这一步,前向过程就讲完了,确实不用学。
3.反向去噪,这才到了要学的部分
前向是顺着往下坡走,加噪声很容易。反向过程则要逆着往回走,把噪声一点点扒掉,还原出一张干净的图,这件事就没那么轻松了。
我们想要的是反向过程的每一步,给定当前这张加噪图 ,能猜出来上一步那张稍微干净的图 长什么样。从概率上讲,前向过程的每一步 是高斯分布,前向好算。可反向过程的后验 (给定当前图倒推上一步图的概率)就麻烦了,它没有解析形式。换句话说,反向这一步没法用解析公式直接算出来,得另想办法。
数学上有个技巧,是用一个神经网络去近似这个反向的后验分布。我们把这个神经网络记作 ,下标 表示它内部的参数(就是那些权重,要靠训练学出来)。 表示在参数 控制下、给定 时预测 的概率分布。这个 通常用一个U-Net来实现,U-Net是那种先把图一层层压缩、再一层层还原回去、中间用跳接把细节补回来的网络结构,特别适合像素级的预测任务。
到这里问题就变成了,怎么训这个U-Net。
4.训练目标:让模型预测噪声
DDPM最妙的一步,就在这里。直觉上你可能觉得,模型应该预测原图 (毕竟我们最后想要的就是原图),可原作者偏偏挑了另一条路子,让模型去预测每一步加进去的那个噪声 。
为什么不预测原图呢,预测噪声的好处有这么几条。其一,原图里信息太多,像素之间的关联又复杂又多样,模型学起来吃力。噪声就简单多了,它就是一个标准正态分布,每一维独立、方差一致,是个特别干净的目标。其二,从数学上看,反向后验分布的均值里恰好就藏着这个 ,把 猜准了,均值也就准了, 自然就有指望。其三,预测噪声这件事和重参数化(VAE里那个把随机性从参数里挪到外部噪声上的技巧)思路一脉相承,训练的梯度好算。
那个简单的"猜噪声"损失,其实是从 ELBO(变分下界)一路化简推出来的。 这条推导我们交代一下,免得你觉得这个损失是凭空冒出来的。和 VAE 一样,扩散模型的目标也是最大化数据对数似然 ,但直接算不出来,于是用变分下界:。用前向马尔可夫链 的结构展开、用贝叶斯公式把反向后验 凑出来,经过一番冗长的代数化简,ELBO 最终分解成 项 KL 散度之和:
这里有个关键事实:前向后验 是可以算出闭式的,它是个高斯,均值是 ( 就是用闭式跳跃公式反解出来的那一份噪声),方差也是固定的。而模型 也设成同方差的高斯,只让均值 由 U-Net 学。于是每一步的 KL 就是两个同方差高斯均值之差的平方,把 和 代进去化简,那个 会露出来:
DDPM 的关键简化就在这一步:原作者发现前面那一坨加权系数 对最终效果影响不大,干脆扔掉,把所有 等权对待,损失就退化成下面那个干净极简的形式。所以那个"猜噪声"损失不是拍脑袋,而是变分下界去掉加权系数后的简化版。
具体的训练步骤说起来不复杂。我们从训练集里随便抽一张原图 ,随便挑一个步数 ,随便采一份标准正态噪声 ,用上面那个闭式公式直接算出 。然后把 和 一起喂给U-Net,让它预测一份噪声 ( 就是模型猜出来的噪声,那个小帽子符号表示预测值)。预测出来之后,和真值 做均方误差,作为损失:
我们一行行看。 是损失函数, 是期望符号,意思是对后面这三个随机变量(原图 、步数 、噪声 )所有可能的取值求平均。 表示参数为 的U-Net接收 和 后输出的预测噪声。中间那个 是欧几里得范数的平方,就是把两个向量每个分量做差、平方、再全部加起来。
就这么一个简单的损失,整个训练就跑起来了。每一次迭代都是上面这套流程,模型反复地猜噪声、对照真值、调整参数,慢慢地越猜越准。说起来,这么朴素的目标最后能画出那么漂亮的图,第一次看的人常常会觉得有些不可思议。
5.生成:从一块乱石里凿出形象
训练完之后,怎么真的生成一张新图呢。
做法是从一张纯粹的随机噪声出发,也就是先采一个标准正态的 (这是第 步、也就是最末那一步的图,纯噪声)。然后从 一步一步往回走,每一步都让U-Net根据当前的 和 预测出一份噪声 ,再按照固定的公式把这份噪声从图里减掉、再加回一点点随机扰动(防止走得太死板),得到上一步的 。我们就这样一直走到 ,最后得到的 就是一张全新的图。
这套走法有个朴素的直觉比喻。我说像雕塑家从一块乱石里一点点凿出形象,每一锤下去都看不出什么,可连着凿上几百锤,脸的轮廓、衣褶的走向就慢慢浮出来了。我之前翻过一本讲雕塑的书,里面提到米开朗基罗说过类似的话,说石头里本来就藏着那尊雕像,他只是把多余的部分敲掉。这话拿来形容扩散模型的反向去噪,竟意外地贴切。也有人拿冲洗老照片来打比方,底片一开始一片模糊,慢慢显影液把影像一层层还原回来,思路也是通的。这两个例子都指向同一件事,就是生成这件事在这里被拆成了成百上千个微小步骤,每一步只做一点点修正,最后凑出来的效果反而特别扎实。
这套流程有个绕不开的麻烦,就是慢。生成一张图要走 步, 时就是 次完整的U-Net前向,画一张图几秒钟起步,比GAN一次前向出图要慢得多。所以后面大家想了很多办法去加速,比如DDIM、DPM-Solver这些采样器,能把步数压到二三十步,下一篇我们会专门聊。这也是扩散模型在大规模落地时绕不过去的坎。
6.说两句闲话
到这里,DDPM的主干就讲完了。说白了就是这么几样东西,一个固定的前向加噪过程用来造训练样本,一个U-Net学着去预测每一步的噪声,一个反向采样的循环用来生成新图,外加一个干净到有点出人意料的均方误差损失。后面那些花活,包括Latent Diffusion把扩散搬到隐空间、Stable Diffusion把文本条件接进来、还有最近很火的用Transformer替换U-Net的DiT(Transformer我们在3.4 节讲过,是当前大多数大模型的底座),其实都是在这套骨架上做改良。
下一章我们去看采样加速这件事,看看怎么把那 步压下去,让一张图能在眨眼之间画完。
练习
Q1. DDPM的前向加噪过程为什么说"不用学"?闭式跳跃公式 解决了什么问题?
前向加噪的规则是一组固定公式(噪声调度 在训练前就定死、不参与梯度更新),所以不用学。闭式跳跃公式解决的是"不用一步一步加噪"的问题:给定原图 ,不用真跑 步前向,直接挑任意中间步 、采一次累计噪声 就能一步算出 。训练时每张图随机挑一个 、采一份 立刻拿到 ,这是扩散模型能高效训练的基础。它由"两个独立高斯相加还是高斯、方差相加"这条性质一层层往回展开得到。
Q2. DDPM让U-Net预测的是噪声 而不是原图 ,这么做有哪些好处?
好处有三条。其一,原图信息太多、像素关联又复杂又多样,模型学起来吃力;噪声是标准正态分布,每维独立、方差一致,是个特别干净的目标。其二,从数学上看,反向后验分布的均值里恰好藏着 ,把 猜准了均值就准、 自然有指望。其三,预测噪声和VAE的重参数化思路一脉相承(把随机性挪到外部),训练梯度好算。所以损失就是简单的 ,朴素却有效。
Q3. 易错点:DDPM的反向后验 没有解析形式,那训练时损失里的 是怎么和"预测噪声"对上的?
关键在于条件后验 (多给了 这个条件)是有闭式的,它是个高斯,均值是 ,方差固定。模型 也设成同方差高斯、只让U-Net学均值 。于是从ELBO分解出的每一步KL就是两个同方差高斯均值之差的平方,把 和 代入化简,那个用闭式跳跃反解出的 就露出来了,损失正比于 。DDPM的关键简化是把前面的加权系数扔掉、所有 等权对待。所以"预测噪声"不是拍脑袋,是变分下界去掉加权后的简化版。
Q4.(面试题) 请说明 DDPM 的前向加噪、反向去噪、训练损失三者的关系,并解释为什么 DDPM 训练稳但生成慢。
三者关系:前向加噪是固定过程(噪声调度 预先定死),把干净图 一步步加噪到纯噪声 ,靠闭式跳跃 一步算出任意中间步;反向去噪是要学的部分,用一个U-Net 近似反向后验 ,从纯噪声 一路走到 ;训练损失就是让U-Net预测每一步加进去的噪声 ,做均方误差 ,每次迭代随机抽 、挑 、采 、算 、预测、对照真值调参。训练稳的原因:损失是从变分下界(ELBO)化简来的、有明确理论根,目标(预测标准正态噪声)干净简单,不像GAN那样两个网络对抗博弈、容易模式崩溃,所以训练曲线平稳。生成慢的原因:生成时要从纯噪声 一步步反向走到 , 就要跑1000次完整的U-Net前向,画一张图几秒起步,远不如GAN一次前向出图。所以后来才有DDIM、DPM-Solver这些采样器把步数压到二三十步来加速,这是扩散模型大规模落地绕不过去的坎。