深入理解扩散模型:Diffusion Models
- 引言
- 扩散模型的原理
- 扩散过程
- 反向过程
- 优化目标
- 模型设计
- 代码实现
- Stable Diffusion、DALL-E、Imagen背后共同的套路
- Stable Diffusion
- DALL-E series
- Imagen
- Text encoder
- Decoder
- 什么是FID(Frechet Inception Distance)
- 什么是CLIP(Contrastive Language-Image Pre-Training)
- Diffusion Model: Summary
- Diffusion Model in PyTorch
- 参考资料
本文综合最近阅读的关于扩散模型的一些基础博客和文章整理而成。主要参考的内容来自 Calvin Luo 的论文,针对的对象主要是对扩散模型已经有一些基础了解的读者。
引言
继OpenAI在2021提出的文本转图像模型DALLE
之后,越来越多的大公司卷入这个方向,例如谷歌相继推出了Imagen
和Parti
。一些主流的文本转图像模型,例如DALL·E 2
,stable-diffusion
和Imagen
采用了扩散模型(Diffusion Model
)作为图像生成模型,这也引发了对扩散模型的研究热潮。
与GAN相比,扩散模型训练更稳定,而且能够生成更多样的样本,OpenAI的论文Diffusion Models Beat GANs on Image Synthesis
也证明了<font color=’red>扩散模型能够超越GAN。
论文地址:https://arxiv.org/abs/2105.05233
简单来说,扩散模型包含两个过程:前向扩散过程和反向生成过程。前向扩散过程是对一张图像逐渐添加高斯噪音直至变成随机噪音,而反向生成过程是去噪音过程,我们将从一个随机噪音开始逐渐去噪音直至生成一张图像,这也是我们要求解或者训练的部分。扩散模型与其它主流生成模型的对比如下所示:
目前所采用的扩散模型大都是来自于2020年的工作DDPM: Denoising Diffusion Probabilistic Models,DDPM对之前的扩散模型(具体见Deep Unsupervised Learning using Nonequilibrium Thermodynamics)进行了简化,并通过变分推断(variational inference
)来进行建模,这主要是因为扩散模型也是一个隐变量模型(latent variable model
),相比VAE这样的隐变量模型,扩散模型的隐变量是和原始数据是同维度的,而且推理过程(即扩散过程)往往是固定的。本文将基于DDPM详细介绍扩散模型的原理,并给出具体的代码实现和分析。
论文地址:https://arxiv.org/abs/2006.11239
扩散模型的原理
扩散模型包括两个过程:前向过程(forward process
)和反向过程(reverse process
),其中前向过程又称为为扩散过程(diffusion process
),如下图所示。无论是前向过程还是反向过程都是一个参数化的马尔可夫链(Markov chain
),其中反向过程可以用来生成数据,这里我们将通过变分推断来进行建模和求解。
扩散过程
扩散过程是指的对数据逐渐增加高斯噪声直至数据完全变成随机噪声的过程。对于原始数据,总共包含步的扩散过程的每一步都是对上一步得到的数据按如下方式增加高斯噪声:
这里为每一步所采用的方差,它介于之间。对于扩散模型,往往称不同step的方差设定为variance schedule
或者noise schedule
,通常情况下,越后面的step会采用更大的方差,即满足。在一个设计好的variance schedule下,如果扩散步数足够大,那么最终得到的就完全丢失了原始数据而变成了一个随机噪音。 扩散过程的每一步都生成一个带噪音的数据,整个扩散过程也就是一个马尔卡夫链:
扩散过程的这个特性很重要。首先,可以看到其实可以看成是原始数据和随机噪音的线性组合,其中和为组合系数,它们的平均和等于1,可以称两者分别为signal_rate
和noise_rate
参考:https://keras.io/examples/generative/ddim/#diffusion-schedule和Variational Diffusion Models
更进一步,可以基于而不是来定义noise schedule
(见Improved Denoising Diffusion Probabilistic Models
所设计的cosine schedule
),因为这样处理更直接,比如我们直接将设定为一个接近0的值,那么就可以保证最终得到的近似为一个随机噪声。其次,后面的建模和分析过程将使用这个特性。
反向过程
扩散过程是将数据噪声化,那么反向过程就是一个去噪的过程。如果我们知道反向过程的每一步的真实分布,那么从一个随机噪音开始,逐渐去噪,就能生成一个真实的样本,所以反向过程也就是生成数据的过程。
虽然分布是不可直接处理的,但是加上条件的后验分布却是可处理的,这里有:
下面来具体推导这个分布。首先根据贝叶斯公式,有:
由于扩散过程的马尔可夫链特性,知道分布(这里条件是多余的),而由前面得到的扩散过程特性可知:
所以,有:
优化目标
上面介绍了扩散模型的扩散过程和反向过程,现在我们来从另外一个角度来看扩散模型:如果把中间产生的变量看成隐变量的话,那么扩散模型其实是包含T个隐变量的隐变量模型(latent variable model
),它可以看成是一个特殊的Hierarchical VAEs
(见Understanding Diffusion Models: A Unified Perspective)
不过实际的代码实现和上述过程略有区别(见https://github.com/hojonathanho/diffusion/issues/5
:先基于预测的噪音生成,并进行了clip处理(范围[-1, 1],原始数据归一化到这个范围),然后再计算均值。这应该算是一种约束,既然模型预测的是噪音,那么我们也希望用预测噪音重构处理的原始数据也应该满足范围要求。
模型设计
前面我们介绍了扩散模型的原理以及优化目标,那么扩散模型的核心就在于训练噪音预测模型,由于噪音和原始数据是同维度的,所以我们可以选择采用AutoEncoder架构来作为噪音预测模型。DDPM所采用的模型是一个基于residual block
和attention block
的U-Net
模型。如下所示:
另外,扩散模型其实需要的是个噪音预测模型,实际处理时,我们可以增加一个time embedding
(类似transformer中的position embedding
)来将timestep编码到网络中,从而只需要训练一个共享的U-Net模型。具体地,DDPM在各个residual block都引入了time embedding,如上图所示。
代码实现
最后,我们基于PyTorch框架给出DDPM的具体实现,这里主要参考了三套代码实现:
- GitHub – hojonathanho/diffusion: Denoising Diffusion Probabilistic Models(官方TensorFlow实现)
- GitHub – openai/improved-diffusion: Release for Improved Denoising Diffusion Probabilistic Models (OpenAI基于PyTorch实现的DDPM+)
- GitHub – lucidrains/denoising-diffusion-pytorch: Implementation of Denoising Diffusion Probabilistic Model in Pytorch
Stable Diffusion、DALL-E、Imagen背后共同的套路
Framework
- Text Encoder: 使用Text Encoder将文本生成一个embedding,
- Generation Model:然后和噪声一起生成一个Generation Model,得到一个中间产物
- Decoder: 使用Decoder都得到带有噪声图片
Stable Diffusion
DALL-E series
Imagen
Text encoder
Decoder
Decoder can be trained without labelled data.
什么是FID(Frechet Inception Distance)
什么是CLIP(Contrastive Language-Image Pre-Training)
Diffusion Model: Summary
Diffusion Model in PyTorch
The easier way to use a Diffusion Model in PyTorch is to use the denoising-diffusion-pytorch package.
pip install denoising_diffusion_pytorch
一个示例代码:
import torch
from denoising_diffusion_pytorch import Unet, GaussianDiffusion
model = Unet(
dim = 64,
dim_mults = (1, 2, 4, 8)
)
model = Unet(
dim = 64,
dim_mults = (1, 2, 4, 8)
)
diffusion = GaussianDiffusion(
model,
image_size = 128,
timesteps = 1000, # number of steps
loss_type = 'l1' # L1 or L2
)
training_images = torch.randn(8, 3, 128, 128)
loss = diffusion(training_images)
loss.backward()
sampled_images = diffusion.sample(batch_size = 4)
参考资料
- 从大一统视角理解扩散模型(Diffusion Models)
- What are Diffusion Models?
- Understanding Diffusion Models: A Unified Perspective
- Generative Modeling by Estimating Gradients of the Data Distribution
- Stable Diffusion、DALL-E、Imagen 背後共同的套路
- 带你深入理解扩散模型DDPM
- https://colab.research.google.com/github/huggingface/notebooks/blob/main/diffusers/stable_diffusion.ipynb#scrollTo=AAVZStIokTVv
文章出处登录后可见!