前言 最近 AI 绘图非常的火,其背后用到的核心技能之一便是 Diffusion Model(分散模型),尽管想要完全弄懂 Diffusion Model 和其间杂乱的公式推导需要掌握比较多的前置数学常识,但这并不妨碍咱们去了解其原理。接下来会以笔者所了解的视点去解说什么是 Diffusion Model。

本文转载自GiantPandaCV

作者丨梁德澎

欢迎重视大众号CV技能攻略,专注于核算机视觉的技能总结、最新技能盯梢、经典论文解读、CV招聘信息。

核算机视觉入门1v3辅导班

什么是 Diffusion Model

前向 Diffusion 进程

Diffusion Model 首要定义了一个前向分散进程,总共包含T个时刻步,如下图所示:

一文弄懂 Diffusion Model

最左面的蓝色圆圈 x0 表明实在天然图画,对应下方的狗子图片。

最右边的蓝色圆圈 xT 则表明纯高斯噪声,对应下方的噪声图片。

最中心的蓝色圆圈 xt 则表明加了噪声的 x0 ,对应下方加了噪声的狗子图片。

箭头下方的 q(xt|xt-1) 则表明一个以前一个状态 xt-1 为均值的高斯散布,xt 从这个高斯散布中采样得到。

所谓前向分散进程能够了解为一个马尔可夫链(见参考资料[7]),即经过逐渐对一张实在图片增加高斯噪声直到终究变成纯高斯噪声图片。

那么详细是怎样增加噪声呢,公式表明如下:

一文弄懂 Diffusion Model

也便是每一时刻步的 xt 是从一个,以 1-t 开根号乘以 xt-1 为均值,t为方差的高斯散布中采样得到的。

其间t, t ∈ [1, T] 是一系列固定的值,由一个公式生成。

在参考资料 [2] 中设置 T=1000, 1=0.0001, T=0.02,并经过一句代码生成一切 t 的值:

# https://pytorch.org/docs/stable/generated/torch.linspace.html
betas = torch.linspace(start=0.0001, end=0.02, steps=1000)

然后在采样得到 xt 的时分并不是直接经过高斯散布 q(xt|xt-1) 采样,而是用了一个重参数化的技巧(详见参考资料[4]第5页)。

简略来说便是,假如想要从一个恣意的均值 方差 ^2 的高斯散布中采样

一文弄懂 Diffusion Model

能够首要从一个规范高斯散布(均值0,方差1)中进行采样得到 ,

然后 + 就等价于从恣意高斯散布中进行采样的成果。公式表明如下:

一文弄懂 Diffusion Model

接着回来看详细怎样采样得到噪声图片 xt呢,

一文弄懂 Diffusion Model

也是首要从规范高斯散布中采样,接着乘以规范差再加上均值 ,伪代码如下:

# https://pytorch.org/docs/stable/generated/torch.randn_like.html
betas = torch.linspace(start=0.0001, end=0.02, steps=1000)
noise = torch.randn_like(x_0)
xt = sqrt(1-betas[t]) * xt-1 + sqrt(betas[t]) * noise

然后前向分散进程还有个属性,便是能够直接从 x0 采样得到中心恣意一个时刻步的噪声图片 xt,公式如下:

一文弄懂 Diffusion Model

其间的 t 表明:

一文弄懂 Diffusion Model

一文弄懂 Diffusion Model

详细怎样推导出来的能够看参考资料[4] 第11页,伪代码表明如下:

betas = torch.linspace(start=0.0001, end=0.02, steps=1000)
alphas = 1 - betas
# cumprod 相当于为每个时刻步 t 核算一个数组 alphas 的前缀乘成果
# https://pytorch.org/docs/stable/generated/torch.cumprod.html
alphas_cum = torch.cumprod(alphas, 0)
alphas_cum_s = torch.sqrt(alphas_cum)
alphas_cum_sm = torch.sqrt(1 - alphas_cum)
# 使用重参数化技巧采样得到 xt
noise = torch.randn_like(x_0)
xt = alphas_cum_s[t] * x_0 + alphas_cum_sm[t] * noise

经过上述的解说,读者应该对 Diffusion Model 的前向分散进程有比较明晰的了解了。

不过咱们的目的不是要做图画生成吗?

现在仅仅从数据会集的实在图片得到一张噪声图,那详细是怎样做图画生成呢?

反向 Diffusion 进程

一文弄懂 Diffusion Model

反向分散进程 q(xt-1|xt, x0) (看粉色箭头)是前向分散进程 q(xt|xt-1) 的后验概率散布。

和前向进程相反是从最右边的纯高斯噪声图,逐渐采样得到实在图画 x0。

后验概率 q(xt-1|xt, x0) 的形式能够依据贝叶斯公式推导得到(推导进程详见参考资料[4]第12页):

一文弄懂 Diffusion Model

也是一个高斯散布。

其方差从公式上看是个常量,一切时刻步的方差值都是能够提前核算得到的:

一文弄懂 Diffusion Model

核算伪代码如下:

betas = torch.linspace(start=0.0001, end=0.02, steps=1000)
alphas = 1 - betas
alphas_cum = torch.cumprod(alphas, 0)
alphas_cum_prev = torch.cat((torch.tensor([1.0]), alphas_cum[:-1]), 0)
posterior_variance = betas * (1 - alphas_cum_prev) / (1 - alphas_cum)

然后看均值的核算,

一文弄懂 Diffusion Model

关于反向分散进程,在采样生成 xt-1 的时分 xt 是已知的,而其他系数都是能够提前核算得到的常量。

可是现在问题来了,在实在经过反向进程生成图画的时分,x0 咱们是不知道的,由于这是待生成的方针图画。

好像变成了鸡生蛋,蛋生鸡的问题,那该怎样办呢?

Diffusion Model 练习方针

当一个概率散布q 求解困难的时分,咱们能够换个思路(详见参考资料[5,6])。

经过人为结构一个新的散布 p,然后方针就转为缩小散布 p 和 q 之间距离。

经过不断修改 p 的参数去缩小距离,当 p 和 q 足够相似的时分就能够代替 q 了。

然后回到反向 Diffusion 进程,由于后验散布 q(xt-1|xt, x0) 无法直接求解。

一文弄懂 Diffusion Model

那么咱们就结构一个高斯散布 p(xt-1|xt)(见绿色箭头),让其方差和后验散布 q(xt-1|xt, x0) 一致:

一文弄懂 Diffusion Model

而其均值则设为:

一文弄懂 Diffusion Model

和 q(xt-1|xt, x0) 的差异在于,x0 改为 x(xt, t) 由一个深度学习模型猜测得到,模型输入是噪声图画 xt 和时刻步 t 。

然后缩小散布 p(xt-1|xt) 和 q(xt-1|xt, x0) 之间距离,变成优化以下方针函数(推导进程详见参考资料[4]第13页):

一文弄懂 Diffusion Model

可是假如让模型直接从 xt 去猜测 x0,这个拟合难度太高了,咱们再继续换个思路。

前面介绍前向分散进程说到,xt 能够直接从 x0 得到:

一文弄懂 Diffusion Model

一文弄懂 Diffusion Model

将上面的公式改换一下形式:

一文弄懂 Diffusion Model

代入上面 q(xt-1|xt, x0) 的均值式子中可得(推导进程详见参考资料[4]第15页):

一文弄懂 Diffusion Model

调查上述改换后的式子,发现后验概率 q(xt-1|xt, x0) 的均值只和 xt 和前向分散时分时刻步 t 所加的噪声有关。

所以咱们相同对结构的散布 p(xt-1|xt) 的均值做一下修改:

一文弄懂 Diffusion Model

将模型改为去猜测在前向时刻步 t 所增加的高斯噪声 ,模型输入是 xt 和 时刻步 t:

一文弄懂 Diffusion Model

接着优化的方针函数就变为(推导进程详见参考资料[4]第15页):

一文弄懂 Diffusion Model

然后练习进程算法描绘如下,终究的方针函数前面的系数都去掉了,由于是常量:

一文弄懂 Diffusion Model

能够看到尽管前面的推导进程很杂乱,可是练习进程却很简略。

首要每个迭代便是从数据会集取实在图画 x0,并从均匀散布中采样一个时刻步 t,

然后从规范高斯散布中采样得到噪声 ,并依据公式核算得到 xt。

接着将 xt 和 t 输入到模型让其输出去拟合猜测噪声 ,并经过梯度下降更新模型,一向循环直到模型收敛。

而选用的深度学习模型是类似 UNet 的结构(详见参考资料[2]附录B)。

练习进程的伪代码如下:

betas = torch.linspace(start=0.0001, end=0.02, steps=1000)
alphas = 1 - betas
alphas_cum = torch.cumprod(alphas, 0)
alphas_cum_s = torch.sqrt(alphas_cum)
alphas_cum_sm = torch.sqrt(1 - alphas_cum)
def diffusion_loss(model, x0, t, noise):
    # 依据公式核算 xt
    xt = alphas_cum_s[t] * x0 + alphas_cum_sm[t] * noise
    # 模型猜测噪声
    predicted_noise = model(xt, t)
    # 核算Loss
    return mse_loss(predicted_noise, noise)
for i in len(data_loader):
    # 从数据集读取一个 batch 的实在图片
    x0 = next(data_loader)
    # 采样时刻步
    t = torch.randint(0, 1000, (batch_size,))
    # 生成高斯噪声
    noise = torch.randn_like(x_0)
    loss = diffusion_loss(model, x0, t, noise)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

Diffusion Model 生成图画进程

模型练习好之后,在实在的推理阶段就有必要从时刻步 T 开端往前逐渐生成图片,算法描绘如下:

一文弄懂 Diffusion Model

一开端先生成一个从规范高斯散布生成噪声,然后每个时刻步 t,将上一步生成的图片 xt 输入模型模型猜测出噪声。接着从规范高斯散布中采样一个噪声,依据重参数化技巧,后验概率的均值和方差公式,核算得到 xt-1,直到时刻步 1 为止。

改善 Diffusion Model

文章 [3] 中对 Diffusion Model 提出了一些改善点。

对方差 t 的改善

前面说到 t 的生成是将一个给定范围均匀的分红 T 份,然后每个时刻步对应其间的某个点:

betas = torch.linspace(start=0.0001, end=0.02, steps=1000)

然后文章 [3] 经过试验调查发现,选用这种方式生成方差 t 会导致一个问题,便是做前向分散的时分到靠后的时刻步噪声加的太多了。

这样导致的成果便是在前向进程靠后的时刻步,在反向生成采样的时分并没有发生太大的贡献,即便跳过也不会对生成成果有多大的影响。

接着论文[3] 中就提出了新的 t 生成战略,和原战略在前向分散的比照如下图所示:

一文弄懂 Diffusion Model

榜首行便是原本的生成战略,能够看到还没到最后的时刻步就现已变成纯高斯噪声了,

而第二行改善的战略,增加噪声的速度就慢一些,看起来也更合理。

一文弄懂 Diffusion Model

试验成果表明,针对 imagenet 数据集 64×64 的图片,原始的战略在做反向分散的时分,即便跳过最初的 20% 的时刻步,都不会对目标有很大的影响。

然后看下新提出的战略公式:

一文弄懂 Diffusion Model

一文弄懂 Diffusion Model

其间 s 设置为 0.008一起约束 t最大值为 0.999,伪代码如下:

T = 1000
s = 8e-3
ts = torch.arange(T + 1, dtype=torch.float64) / T + s
alphas = ts / (1 + s) * math.pi / 2
alphas = torch.cos(alphas).pow(2)
alphas = alphas / alphas[0]
betas = 1 - alphas[1:] / alphas[:-1]
betas = betas.clamp(max=0.999)

对生成进程时刻步数的改善

原本模型练习的时分是假定在 T个时刻步下练习的,在生成图画的时分,也有必要从 T 开端遍历到 1 。而论文 [3] 中提出了一种不需要从头练习就能够减少生成步数的办法,从而明显提升生成的速度。

这个办法简略描绘便是,原来是 T 个时刻步现在设置一个更小的时刻步数 S ,将 S 时刻序列中的每一个时刻步 s 和 T时刻序列中的步数 t 对应起来,伪代码如下:

T = 1000
S = 100
start_idx = 0
all_steps = []
frac_stride = (T - 1) / (S - 1)
cur_idx = 0.0
s_timesteps = []
for _ in range(S):
    s_timesteps.append(start_idx + round(cur_idx))
    cur_idx += frac_stride

接着核算新的 ,St 便是上面核算得到的 s_timesteps:

一文弄懂 Diffusion Model

伪代码如下:

alphas = 1 - betas
alphas_cum = torch.cumprod(alphas, 0)
last_alpha_cum = 1.0
new_betas = []
# 遍历原来的 alpha 前缀乘序列
for i, alpha_cum in enumerate(alphas_cum):
    # 当原序列 T 的索引 i 在新序列 S 中时,核算新的 beta
    if i in s_timesteps:
        new_betas.append(1 - alpha_cum / last_alpha_cum)
        last_alpha_cum = alpha_cum

简略看下试验成果:

一文弄懂 Diffusion Model

重视画蓝线的红色和绿色实线,能够看到采样步数从 1000 缩小到 100 目标也没有降多少。

参考资料

  • [1] www.assemblyai.com/blog/diffus…

  • [2] arxiv.org/pdf/2006.11…

  • [3] arxiv.org/pdf/2102.09…

  • [4] arxiv.org/pdf/2208.11…

  • [5] www.zhihu.com/question/41…

  • [6] www.zhihu.com/question/41…

  • [7]zh.wikipedia.org/wiki/%E9%A9…

  • [8] github.com/rosinality/…

  • [9] github.com/openai/impr…

欢迎重视大众号CV技能攻略,专注于核算机视觉的技能总结、最新技能盯梢、经典论文解读、CV招聘信息。

核算机视觉入门1v3辅导班

【技能文档】《从零搭建pytorch模型教程》122页PDF下载

QQ沟通群:444129970。群内有大佬担任回答大家的日常学习、科研、代码问题。

其它文章

深度了解变分自编码器(VAE) | 从入门到通晓

核算机视觉入门1v3辅导班

核算机视觉沟通群

用于超大图画的练习战略:Patch Gradient Descent

CV小常识讨论与分析(5)到底什么是Latent Space?

【免费送书活动】关于语义切割的亿点思考

新方案:从错误中学习,点云切割中的自我规范化层次语义表明

经典文章:Transformer是怎么进军点云学习范畴的?

CVPR 2023 Workshop | 首个大规模视频全景切割比赛

怎么更好地应对下游小样本图画数据?不平衡数据集的建模的技巧和策

Transformer沟通群

U-Net在2022年相关研究的论文推荐

用少于256KB内存完成边缘练习,开销不到PyTorch千分之一

PyTorch 2.0 重磅发布:一行代码提速 30%

Hinton 最新研究:神经网络的未来是前向-前向算法

聊聊核算机视觉入门

FRNet:上下文感知的特征强化模块

DAMO-YOLO | 超越一切YOLO,兼顾模型速度与精度

《医学图画切割》综述,胪陈六大类100多个算法

怎么高效完成矩阵乘?万文长字带你从CUDA初学者的视点入门

近似乘法对卷积神经网络的影响

BT-Unet:医学图画切割的自监督学习框架

语义切割该怎么走下去?

轻量级模型规划与部署总结

从CVPR22出发,聊聊CAM是怎么激活咱们文章的热度!

入门必读系列(十六)经典CNN规划演化的要害总结:从VGGNet到EfficientNet

入门必读系列(十五)神经网络不work的原因总结

入门必读系列(十四)CV论文常见英语单词总结

入门必读系列(十三)高效阅览论文的办法

入门必读系列(十二)池化各要点与各办法总结

TensorRT教程(三)TensorRT的安装教程

TensorRT教程(一)初次介绍TensorRT

TensorRT教程(二)TensorRT进阶介绍