上一讲,我们一起体验了 CLIP 这个多模态的模型。在这个模型里,我们已经能够把一段文本和对应的图片关联起来了。看到文本和图片的关联,想必你也能联想到过去半年非常火热的“文生图”(Text-To-Image)的应用浪潮了。相比于在大语言模型里 OpenAI 的一枝独秀。文生图领域就属于百花齐放了,OpenAI 陆续发表了 DALL-E 和 DALL-E 2,Google 也不甘示弱地发表了 Imagen,而市场上实际被用得最多、反馈最好的用户端产品是 Midjourney。
不过,在整个技术社区里,最流行的产品则是 Stable Diffusion。因为它是一个完全开源的产品,我们不仅可以调用 Stable Diffusion 内置的模型来生成图片,还能够下载社区里其他人训练好的模型来生成图片。我们不仅可以通过文本来生成图片,还能通过图片来生成图片,通过文本来编辑图片。
那么今天这一讲,我们就来看看如何使用 Stable Diffusion,做到上面这些事情。
使用 Stable Diffusion 生成图片
文生图
可能你还没怎么体验过文生图的应用,那我们先用几行最简单的代码体验一下。在这一讲里,我建议一定要用 Colab 或者其他的 GPU 环境,因为用 CPU 来执行的话,速度会慢到让人无法接受。
安装依赖包:
%pip install diffusers accelerate transformers
代码:
from diffusers import DiffusionPipeline
pipeline = DiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5")
pipeline.to("cuda")
image = pipeline("a photograph of an astronaut riding a horse").images[0]
image
输出结果:
代码非常简单,只有寥寥几行。这里,我们使用了 Huggingface 的 Diffusers 库,通过 DiffusionPipeline 加载了 RunwayML 的 stable-diffusion-v1-5 的模型。然后,指定了这个 Pipeline 使用 CUDA 也就是利用 GPU 来进行计算。最后向这个 Pipeline 输入了一段文本,通过这段文本我们就生成了一张图片。
这里,我们画的是在 Stable Diffusion 里非常经典的一张“宇航员在太空骑马”的图片。之所以画这么一张图片,是为了证明我们并不是通过“搜索”的方式找到一张已经存在的图片。比如,上一讲里我们介绍过 CLIP 模型,其实就可以完成从文本到图片的搜索功能。而 Stable Diffusion,是真的让 AI“画”出来一张新的图片。毕竟,以前宇航员也从来没有在太空骑过马,也不可能有人拍下过这样的照片。
Stable Diffusion 的基本原理
Stable Diffusion 生成的图片效果的确不错,相信你也很好奇这个事情的原理是什么。其实,Stable Diffusion 背后不是单独的一个模型,而是由多个模型组合而成的。整个 Stable Diffusion 文生图的过程是由这样三个核心模块组成的。
第一个模块是一个 Text-Encoder,把我们输入的文本变成一个向量。实际使用的就是我们上一讲介绍的 CLIP 模型。因为 CLIP 模型学习的是文本和图像之间的关系,所以得到的这个向量既理解了文本的含义,又能和图片的信息关联起来。
第二个是 Generation 模块,顾名思义是一个图片信息生成模块。这里也有一个机器学习模型,叫做 UNet,还有一个调度器(Scheduler),用来一步步地去除噪声。这个模块的工作流程是先往前面的用 CLIP 模型推理出来的向量里添加很多噪声,再通过 UNet+Scheduler 逐渐去除噪声,最后拿到了一个新的张量。这个张量可以认为是一个尺寸上缩小了的图片信息向量,里面隐含了我们要生成的图片信息。
最后一个模块,则是 Decoder 或者叫做解码器。背后也是一个机器学习的模型,叫做 VAE。它会根据第二步的返回结果把这个图像信息还原成最终的图片。
这个过程,可以结合 Stable Diffusion 相关论文里的一张模型架构图来看。
这样听起来可能有点太理论了,那我们还是看看具体的代码和图片生成的过程吧,这样就比较容易理解图片是怎么生成的了。
我们先把 DiffusionPipeline 打印出来,看看它内部是由哪些部分组成的。
pipeline
输出结果:
StableDiffusionPipeline {
"_class_name": "StableDiffusionPipeline",
"_diffusers_version": "0.15.1",
"feature_extractor": [
"transformers",
"CLIPFeatureExtractor"
],
"requires_safety_checker": true,
"safety_checker": [
"stable_diffusion",
"StableDiffusionSafetyChecker"
],
"scheduler": [
"diffusers",
"PNDMScheduler"
],
"text_encoder": [
"transformers",
"CLIPTextModel"
],
"tokenizer": [
"transformers",
"CLIPTokenizer"
],
"unet": [
"diffusers",
"UNet2DConditionModel"
],
"vae": [
"diffusers",
"AutoencoderKL"
]
}
这个对象里面有 3 部分。
1. Tokenizer 和 Text_Encoder,就是我们上面说的把文本变成向量的 Text Encoder。可以看到我们这里用的模型就是上一讲的 CLIP 模型。
2. UNet 和 Scheduler,就是对文本向量以及输入的噪声进行噪声去除的组件,也就是 Generation 模块。这里用的是 UNet2DConditionModel 模型,还把 PNDMScheduler 用作了去除噪声的调度器。
3. VAE,也就是解码器(Decoder),这里用的是 AutoencoderKL,它会根据上面生成的图片信息最后还原出一张高分辨率的图片。
剩下的 feature_extractor,可以用来提取图像特征,如果我们不想文