零基础读懂Stable Diffusion
点击上方“小白学视觉”,选择加"星标"或“置顶”
重磅干货,第一时间送达
极市导读
一文搞懂Stable Diffusion是什么,怎么训练和使用,语义信息影响生成图片的过程。
前几个月AIGC可谓是大热了一把,各种高质量的生成图片层出不穷,而其中最重要的开源模型Stable Diffusion也受到了各种技术商业上的热捧,以很快的速度不断的向前迭代着。之前作为一个没有相关知识基础的小白,为了了解相关的技术知识,找了很多文章看,最后还是发现Jay Alammar的这篇文章讲的最为通俗易懂,于是决定简单翻译一下,方便更多人从零开始了解这项强大的技术。
由于原文篇幅较长,所以这里分为三部分进行讲解:
-
第一部分,主要讲“是什么”的问题,包括Stable Diffusion是什么,里面的各个模块是什么 -
第二部分,主要讲“怎么办”的问题,也就是Diffusion怎么训练以及怎么使用的问题。 -
第三部分,主要讲“如何控制”的问题,具体阐述语义信息到底是怎么影响生成图片的过程的。
接下来正式进入第一部分的介绍,谈谈Stable Diffusion是什么,以及里面的一些模块是什么的问题。
原文链接:The Illustrated Stable Diffusion 有能力和时间的小伙伴还是更推荐阅读原文噢
作者:Jay Alammar
译者:曾飞飞(知乎)
零基础读懂Stable Diffusion(I):怎么组成
AI图像生成最近所展现出的潜力可谓是让人大开眼界,它能够从一些简单的文字描述开始,变魔法一般的变出高质量的图片。不用说,这已然深刻的拓宽了人类创作艺术的方式。而其中,Stable Diffusion的公布算是一个里程碑事件了,它的开源不仅仅意味着面向大众群体公开了一个极高质量的模型,与此同时这个模型甚至能保持很快的运行速度和较低的显存需求,不可谓不厉害。
用过了Diffusion这个神奇的技术后,你可能会好奇它到底为什么能有如此好的效果,这里将给出一个尽量简单直白的解释。
Stable Diffusion模型确实是多才多艺的,它可以出色的完成很多任务,比如文生图,图生图,特定角色的刻画,甚至超分或者Inpainting,但作为最基础的一篇介绍,这里我们首先就着重讲解最基础的“文生图”模块,也就是txt2img部分。下图是一个基本的文生图展示,输入是“天堂(paradise)、广袤的(cosmic)、海滩(beach)”,可以看到最右边的生成图片很好的符合了输入的要求,图中不仅有蓝天白云,广阔的海滩也一望无际:
虽然本文暂时还没讲到图生图模块(也就是所谓的img2img),但这个模块的示意图我们也暂时放一下,如下图所示,这次的输入从单纯的“文字”变成了“图片+文字”的形式,生成的结果是由原始图片和文字提示词共同决定的。这次输入是"海盗船(pirate ship)"和上面img2img生成的图片,最后输出的结果也确实把输入图片的帆船变成了海盗船
现在,让我们正式开始了解这项技术背后的原理吧。
一、组成模块
Stable Diffusion其实是个比较杂合的系统,里面有着各种各样的模型模块。那首先映入眼帘的问题是,怎么把人类理解的文字转换为机器理解的数学语言,毕竟计算机是不懂英文的嘛。这个时候就需要一个text understander帮忙转化。在生成图像前,下图中蓝蓝的text understander先把文字转换成某种计算机能理解的数学表示:
蓝蓝的text understander(也就是一个文字的encoder编码器)把人类语言转换成计算机能理解的语义内容
我们后续在第三篇中会讲到这个text understander到底是怎么理解文字和怎么训练的,但现在暂时让我略过这一部分内容,我们只要知道这个text understander是个特别的Transformer语言模型就好了。它的输入是人类语言,输出是一系列的向量,这些向量的语义对应着我们输入的文字。
那么现在,有了可以代表语义的向量(就比如下面的蓝色3*5方格),我们就把这个语义向量交给真正的图片生成器了,也即下图中粉粉的Image Generator。
蓝色方格的语义向量被输入到粉色的图片生成器中,正式开始生成图片
这个粉色的图片生成器(Image Generator)可以分解成两个子模块来看
1,图片信息生成器
这个下图中粉色的模块是Stable Diffusion的秘密武器,也是Stable Diffusion和其他diffusion模型最大的区别,很多性能上的提升就来源于此。
首先,最需要明确的一点:图片信息生成器不直接生成图片,而是生成的较低维度的图片信息,也就是所谓的隐空间信息(information of latent space)。这个隐空间信息在下面的流程图中表现为那个粉色的4*3的方格,后续再将下图中这个隐空间信息输入到下图中黄色的Decoder里,就可以成功生成图片了。Stable Diffusion主要引用的论文“latent diffusion”中的latent也是来源于隐变量中的“隐”(latent)。
一般的diffusion模型都是直接生成图片,并不会有先生成隐变量的过程,所以普通的diffusion在这一步上需要生成的信息更多,负荷也更大。因而之前的diffusion模型在速度上和资源利用上都比不过Stable Diffusion。那技术上来说,这个图片隐变量到底是怎么生成的呢?这其实是由一个Unet和一个Schedule算法共同完成的。schedule算法控制生成的进度,unet就具体去一步一步地执行生成的过程。Stable Diffusion中,整个unet的生成迭代过程大概要重复50~100次,隐变量的质量也在这个迭代的过程中不断的变得更好。下图中粉色的Image Information Creator左下角的循环标志也正是象征着这个迭代的过程。
2,图片解码器
图片解码器也就是我们上面说的decoder,它从图片信息生成器(Image Information Creator)中接过图片信息的隐变量,将其升维放大(upscale),还原成一张完整的图片。图片解码器只在最后的阶段起作用,也是我们真正能获得一张图片的最终过程。
上面粗略的聊了一下Stable Diffusion每个模块的功能,下面我们来更具体的了解一下这个系统中输入输出的向量形状,这样的话对Stable Diffusion的工作原理应该能有更直观的认识:
-
Text Encoder (蓝色模块) 功能:将人类语言转换成机器能理解的数学向量 输入:人类语言 输出:语义向量(77,768) -
Image Information Creator (粉色模块) 功能:结合语义向量,从纯噪声开始逐步去除噪声,生成图片信息隐变量 输入:噪声隐变量(4,64,64)+语义向量(77,768) 输出:去噪的隐变量(4,64,64) -
Image Decoder 功能:将图片信息隐变量转换为一张真正的图片。输入:去噪的隐变量(4,64,64) 输出:一张真正的图片(3,512,512)
大概流程中的向量形状变化就是这样,至于语义向量的形状为什么是奇怪的(77,768)的形状,我们后面讲到Text Encoder里面的CLIP模型的时候还会讲到,这里就暂且按下不表。
二、扩散(Diffusion)到底是什么意思?
Diffusion模型,翻译成中文也就是扩散模型,那这个扩散到底体现在什么地方呢?这就是我们这第二部分着重要描述的过程。首先我们先用random函数生成一个隐变量大小的纯噪声【下图中左下透明4*4】。而扩散的过程发生在Image Information Creator中,有了初始的纯噪声【下图中左下透明4*4】+语义向量【下图左上蓝色3*5】后,unet会结合语义向量不断的去除纯噪声隐变量中的噪声,重复50~100次左右就完全去除了噪声,同时不断的向隐变量中注入语义信息,我们就得到了一个有语义的隐变量【下图粉色4*4】。别忘了我们还有一个scheduler,它就用来控制unet去噪的强度,以统筹整个去噪的过程。scheduler可以在去噪的不同阶段中动态地调整去噪强度,也可以在某些特殊的任务里,匀速地去除噪声,这都取决于我们一开始的设计。
这个扩散过程是一步一步迭代去噪的,每一步都向隐变量中注入语义信息,不断重复直到去噪完成为止。为了有个直观的认识,我们可以把初始的纯噪声【下图左上透明4*4】和最后的去噪隐变量【下图右上粉色4*4】都通过最后的Image Decoder,看看会出来什么样的图片。不出意料,纯噪声本身没有任何有效信息,解码出来的图片也会是纯噪声,如下图左侧所示。而最后的去噪隐变量由于已经耦合了语义信息,因此最后解码出来的也是一张包含语义信息的有效图片,如下图右侧图片所示。
刚才我们也提到过,扩散过程是一个多次迭代的过程。每一步迭代的输入都是一个隐变量,输出也是一个隐变量,只不过输出的这个隐变量噪声更少,并且语义信息更多。下图中4*4的隐变量不断从透明变粉的过程就代表了这个迭代的过程,颜色越粉,迭代次数越多,噪声也就越少。
这个时候我们再偷偷用Image Decoder提前看一下每一步所对应的图片,就会看到我们想要的图片一步一步地脱胎于噪声的全过程:
这是一个神奇的过程,下面的视频展示了迭代去噪的全过程,我们可以看一下视频的展示:
三、总结
至此,我们已经了解了Stable Diffusion是什么,以及其中的种种模块是什么,甚至还简单的窥视了一下它的工作过程。至于为什么Diffusion如何训练、如何控制,鉴于篇幅原因,就容我放到后文中再去细讲了。这里最后再简单做一下总结:
-
第一部分介绍了一些Stable Diffusion中的主要模块——包括一个Text Understander处理语义信息,一个Image Information Creator生成图片的隐变量,一个Image Decoder利用隐变量生成真正的图片。 -
其次还介绍了一下Diffusion生成图片的流程——包括向量形状在系统中经历的一系列变化,以及各个阶段图片隐变量解码后的可视化。
零基础读懂Stable Diffusion(II):怎么训练
一,Diffusion怎么训练
Diffusion模型能够生成高质量图片,其核心原因在于我们现在有着极其强大的计算机视觉模型。只要数据集够大,我们强大的模型就能学习到任何复杂的操作。那具体diffusion里面让unet学习了怎样一个操作呢?简单来说,就是“去噪”。
那如何为去噪的任务设计数据集呢?很简单,我们只要向普通的照片里添加噪声,不就有了加噪的图片了嘛。假定我们现在有一张金字塔的图片,我们用random函数生成从强到弱各个强度的噪声,比如下图中0~3共计4个强度的噪声。现在我们选定个某个强度的噪声,比如下图中选了噪声1,并且把这个噪声添加到图片里:
现在,我们就制作完成了训练集里面的一张图片。按照这样的操作,选一张图片,再选一种强度的噪声混合,我们还可以制作很多训练集。比如下面就选了图书馆的一张照片,混合了强度为2的噪声,创造了一个更模糊一点的训练样本:
上面仅仅作为一个简单的例子,所以噪声只设置了四个档位。实际上我们可以更细腻地划分噪声的等级,将其分为几十个甚至上百个档位,这样就可以创建出成千上万个训练集。比如我们现在噪声设置成100个档位,下面就展示了利用不同的档位结合不同的图片创建6张训练集的过程:
这样的话,一组训练集包括了三样东西:噪声强度(上图数字),加噪后的图片(上图左列图片),以及噪声图(上图右列图片)就可以了。训练的时候我们的unet只要在已知噪声强度的条件下,学习如何从加噪后的图片中计算出噪声图就可以了。注意,我们并不直接输出无噪声的原图,而是让unet去预测原图上所加过的噪声。当需要生成图片的时候,我们用加噪图减掉噪声就能恢复出原图了。
具体的一个训练过程就如下图所示,一共分四步走:
-
从训练集中选取一张加噪过的图片和噪声强度,比如下面的加噪街道图和噪声强度3。 -
输入unet,让unet预测噪声图,比如下图的unet prediction。 -
计算和真正的噪声图之间的误差 -
通过反向传播更新unet的参数。
那完成训练后,我们该如何生成图片呢?
二,Diffusion怎么生成图片
假设我们现在已经按照上面的步骤训练好了一个unet,这就意味着它就可以成功从一个加噪的图片中推断出噪声了。如下图中,知道噪声强度的情况下,给unet输入一张有噪图,unet就输出有噪图上面加过的噪声:
既然现在噪声图能够被推断出来,我们只要把加噪后的图片减去这个噪声图,就可以轻松得到一张略微去噪的图片了:
重复这个过程,预测噪声图,再减去噪声图,进行第二步去噪:
不断地重复这个过程,不断的去除一张噪声图片的噪声,最终我们就可以得到一张很棒的图片。这个图片是接近训练集分布的,它和训练集保有相同的像素规律。比如你用一个艺术家数据集去训练,它就会遵循美学的颜色分布,你用真实世界的训练集去训练,它的结果就会尽量遵循真实世界的规律。现在,你已经了解了Diffusion模型的基本规律了,这不仅仅适用于Stable Diffusion,也适用于OpenAI的Dall-E 2和Google的Imagen。
注意到上面这个过程中我们暂时还没有引入文字和语义向量的控制。也就是说,如果单纯的按照上面的流程走,我们可能能得到一些很炫酷的图片,但我们没有办法去控制最后的结果到底是什么。那如何引入文字控制呢?这就要使用语言模型和Attention机制来引入语义啦。
三、总结
-
Diffusion's Training: 利用 “噪声强度、噪声图、加噪后图片”组成训练集,训练unet,使其学习如何从加噪后的图片推断出所加的噪声。 -
Diffusion's Inference: 利用训练好的unet,从纯噪声中一步一步去噪,得到合理正常的图片。
零基础读懂Stable Diffusion(III):怎么控制
第二部分中我们着重讲述了如何训练Stable Diffusion中的unet,同时,也了解了在训练好unet之后怎么使用它来去除噪声以及生成图片。然而,还有一个重要的地方我们尚未提及,那就是可控性。我们如何用语言来控制最后生成的结果?答案也很简单——注意力机制。在第一部分中我们讲到,一个unet除了要接收噪声图之外,还要接受我们用Text Encoder预先提取的语义信息。那这个语义信息怎么在生成图片的过程中使用呢?我们直接使用注意力机制在unet内层层耦合即可。下图中每个黄色的小方块都代表一次注意力机制的使用,而每次使用注意力机制,就发生了一次图片信息和语义信息的耦合。每一个unet内部,这样的操作都会发生很多次很多次,直到最后一个unet为止,一直不断重复这耦合的过程。
有人可能就要问了,语义信息是语义信息,图片信息是图片信息,怎么能用attention耦合到一块呢?计算机怎么把这两种完全不同的信息联系到一起的呢?这就要说到我们这篇文章的主角——CLIP模型了。
一,CLIP模型介绍
自从2018年Bert发布以来,Transformer的语言模型就成了主流。Stable Diffusion起初的版本便是用的基于GPT的CLIP模型,而最近的2.x版本换成了更新更好的OpenCLIP。语言模型的选择直接决定了语义信息的优良与否,而语义信息的好坏又会影响到最后图片的多样性和可控性。Google在Imagen论文中做过实验,可以发现不同语言模型对生成结果的影响是相当大的。
那像CLIP这样的语言模型究竟是怎么训练出来的呢?它们是怎么样做到结合人类语言和计算机视觉的呢?接下来我们就来好好了解一下。
首先,要训练一个结合人类语言和计算机视觉的模型,我们就必须有一个结合人类语言和计算机视觉的数据集。CLIP就是在像下面这样的数据集上训练的,只不过图片数据达到了可怕的4亿张而已。事实上,这些数据都是从网上爬取下来的,同时被爬取下来的还有它们的标签或者注释。
CLIP模型包含一个图片Encoder和一个文字Encoder。训练过程可以这么理解:我们先从训练集中随机取出一张图片和一段文字。注意,文字和图片未必是匹配的,CLIP模型的任务就是预测图文是否匹配,从而展开训练。在随机取出文字和图片后,我们再用图片Encoder和文字Encoder分别压缩成两个embedding向量,称之为图片embedding和文字embedding【下图两个蓝色3*1向量】。
然后我们用余弦相似度来比较两个embedding向量的相似性,以判断我们随机抽取的文字和图片是否匹配。一开始的时候,就算图片和文字配对的很好,但由于我们的两个Encoder刚刚初始化,参数都是混乱的,因此两个embedding向量一定也是混乱的,计算出来的相似度往往会接近于0。这就会出现下图这种状况,明明图文是一对,他们的label是similar,但是余弦相似度算出来的prediction却是not similar:
这个时候,标签(similar)和预测结果(not similar)不匹配,我们就根据这个结果去反向更新两个Encoder的参数:
不断地重复这个反向传播的过程,我们就能够训练好两个Encoder。对于配对的图片和文字,这两个Encoder最后就可以输出相似的embedding向量,计算余弦相似度就可以得到接近1的结果。而对于不匹配的图片和文字,这两个Encoder就会输出很不一样的embedding向量,使得余弦相似度计算出来接近0. 这个时候,你给CLIP一张小狗的图片,同时再给出文字描述:“小狗照片”,CLIP模型就会生成两个相似的embedding向量,从而判断出文字和图片是匹配的。这个时候,计算机视觉和人类语言这两个原本不相干的信息就通过CLIP联系到了一块,二者就拥有了统一的数学表示了。你可以将文字通过一个Text Encoder转换成图片信息,也可以将文字通过Image Encoder转换成语言信息,二者就能够相互作用了。这也是Diffusion模型中可以通过文字生图片的秘密所在。
值得注意的是,就像经典的word2vec训练时一样,训练CLIP时不仅仅要选择匹配的图文来训练,还要适当选择完全不匹配的图文给机器识别,作为负样本来平衡正样本的数量。
二,给图片注入语义
有了训练好的CLIP模型,我们就获得了一个完美的工具,用来联系语义信息和计算机视觉信息了。那如何利用CLIP模型呢?一个很直白的思路就是,对于一段描述文字,我们先用CLIP的Text Encoder去压缩成embedding向量。在unet的去噪过程中,我们就不断地用attention机制给去噪的过程注入这个embedding向量,就可以不断注入语义信息了。
回忆一下,在第二部分中,我们介绍了没有语义信息时unet的输入输出情况。大概就是下图这个样子,输入是噪声强度和加噪图,输出是加噪图上所加的噪声。
现在有了语义的embedding向量,稍作修改之后,我们就有了新的输入输出关系图。语义embedding向量在下图中表示为蓝色3*5的方格:
训练的数据集也添加了文字的数据对,现在输入变成了噪声强度+加噪图+文字:
要更清楚的知道语义向量是这么作用于这个过程中的,我们必须更深入的了解一下Unet里面是怎么工作的。
1,没有文字时的Unet
首先,没有文字的时候,unet宏观的工作模式我们已经很清楚了。unet的输入是加噪图和噪声强度,输出是加噪图上所加的噪声:
然后,当我们要更细致的了解unet里面到底是什么样的时候,就可以把上图中代表unet的小粉块放大来看。如下图所示,整个unet内部的流程特点如下
-
整个unet是由一系列Resnet构成的。 -
每一层的输入都是上一层的输出。 -
一些输出用residual connection直接跳跃到后面去,如下图中的虚线,这种residual+skip的操作也是很经典的unet思想。 -
噪声强度被转换成一个embedding向量,输入到每一个子的Resnet里面。这也是噪声强度能控制unet的底层原理所在。
2,有文字时的Unet
了解了没有文字时的unet是怎么工作的之后,接下来我们就要加入文字的处理了。首先,还是看宏观的输入输出,相比上面额外添加了文字embedding作为输入【下图蓝色3*5】:
我们再细看一下粉色的unet模块里到底发生了什么。如下图所示,每个Resnet不再和相邻的Resnet直接连接,而是在中间新增了Attention的模块。CLIP Encoder得到的语义embedding就用这个Attention模块来处理。
注意到,Resnet模块并不直接以文字作为输入,而是使用了Attention作为后处理模块。现在Unet就可以通过这一方式成功利用CLIP的语义信息了。就此,我们也就能成功完成文生图的任务。
三、总结
最后,再简单归纳一下第三部分的内容,给我们的Stable Diffusion的理解系列画上一个完美的句点:
-
语义信息怎么来?——我们介绍了CLIP模型以及它的训练方式,主要运用匹配的图片对和不匹配的图片对让CLIP模型去判断,通过判断结果更新两个Encoder的参数。 -
语义信息怎么用?——我们观察了不用语义信息的Unet,并将其推广到使用语义信息的Unet。具体来说,就是在内部的每一个小小的Resnet后面加上一个Attention模块处理语义信息。
如果你成功地看到了这里,那么首先要恭喜你看完了这共计一万字的大长文。希望这篇文章能够给你一个基本的直觉,让你明白Stable Diffusion是怎么工作的。事实上,还有很多其他的模块或者是细节,但是一旦你理解了这些基础模块的工作原理,那些附加的额外模块相信你也可以轻松理解。
下载1:OpenCV-Contrib扩展模块中文版教程
在「小白学视觉」公众号后台回复:扩展模块中文教程,即可下载全网第一份OpenCV扩展模块教程中文版,涵盖扩展模块安装、SFM算法、立体视觉、目标跟踪、生物视觉、超分辨率处理等二十多章内容。
下载2:Python视觉实战项目52讲 在「小白学视觉」公众号后台回复:Python视觉实战项目,即可下载包括图像分割、口罩检测、车道线检测、车辆计数、添加眼线、车牌识别、字符识别、情绪检测、文本内容提取、面部识别等31个视觉实战项目,助力快速学校计算机视觉。
下载3:OpenCV实战项目20讲 在「小白学视觉」公众号后台回复:OpenCV实战项目20讲,即可下载含有20个基于OpenCV实现20个实战项目,实现OpenCV学习进阶。
交流群
欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~