Stable Diffusion 模型微调技术探究
本文均仅作学习、交流使用。
本文仍在修订中。
这篇文章并不着重于原理解释,而是主要针对其代码和实践方面的技术性探究。只有密切涉及到的原理才会展开,否则便从简了。
在阅读本文后,你将了解到
- Diffusion Model 的基本原理
- Textual Inversion 的基本原理,以及如何使用该技术微调模型
- LoRA 的原理,以及如何使用 LoRA 微调模型
- 如何将两种微调技术结合使用
- 图像生成的一般性 Pipeline 及部分训练、推理实践经验
文章目前还没有涉及到各种后处理,如果还有机会的话也许会加。
Stable Diffusion,一种 Diffusion Model
Diffusion Model,或者称扩散模型,是近期很火的一种图像生成技术。它的原理直白来讲其实较为简单,就是从一张噪声图像 z 中不断剔除噪声,使之剩余部分最终成为一张有意义的图像 y。这个过程可以写为
在每一步中,x 的噪声都会减少一些,最终得到 y。了解相关任务的人可以意识到,这种技术除了 AIGC 之外,其实适用于所有涉及图像修改的任务:只要你能定义好「噪声」的物理形式,模型就能用于高分、修复、上色、去雾……
不过我们这里所说要说的主要是指 Latent Diffusion Model。相比直接在图像上执行操作而言,这种模型将「噪声」从图像的像素空间挪动到了图像表征的隐空间,不再是常规理解上的噪声。如此改动使之基本只适用于图像生成,但相对的速度更快。如果没有特殊说明,本文接下来的内容都是在说 Latent Diffusion Model,并特指 Stable Diffusion Model。
一个 SD 模型由以下部分组成
- VAE:变分自编码器。能够将编码和解码图像。
- U-Net:其实算是解码器,是 Diffusion Model 的主体部分,负责学习和预测噪声。
- TextModel:编码 Prompt,给图像生成提供文本约束。
- Tokenizer:其实算是 TextModel 的附属,负责将 Prompt 嵌入到模型的输入空间里。
整个过程涉及较长的 Pipeline,用到了数个预训练模型。接下来本文将从 Text2Image 和 Inpainting 两个角度阐述其运行过程。
Text2Image 文生图
Text2Image 任务,即参照给出的文字描述 Prompt 生成对应图像。这也是 SD 最原始的用途。
在一次 Text2Image 任务推理中,
- 输入的 Prompt 被 Tokenizer 解释成模型可识别的 Token 串
- TextModel 将 Token 编码为文本特征向量 ,用于约束后续的图像生成过程
- 模型首先在隐空间生成随机噪声
- U-Net 读入噪声 ,在文本约束下预测噪声,并生成去噪的表征
- 重复第 4 步直到去噪完成,得到
- 使用 VAE 解码表征 ,得到最终的图像
搞过跨模态任务的人应该相对清楚这个流程,可能唯一不太了解的就是 3、4 步的原理和详细步骤,不过这篇文章不会过多解释这件事。在实际生产中可能还会辅以超分等后续过程,以进一步提升图像质量。
Inpainting 图像修复
SD 同样也能用于图像修复。除了 SD 有提供专用于 Inpainting 的预训练模型外,借助标准的 SD 模型同样可以实现类似效果:
在这个任务里,除了输入 Prompt 外,还需要提供一张图像 和掩码 ,掩码上标记了图像中需要重绘的区域。
- 输入的 Prompt 被 Tokenizer 解释成模型可识别的 Token 串
- TextModel 将 Token 编码为文本特征向量 ,用于约束后续的图像生成过程
- 模型从经过掩码处理的图像初始化表征 ,表征同样依照标准流程叠加噪声(所以 最开始仍全是噪声)
- U-Net 读入表征 ,在文本约束下预测噪声,并生成去噪的表征
- 保留掩码区域的模型预测结果,其他区域使用真实图像表征 覆盖掉,然后重复第 4 步( 是理想状态下的噪声)
- 使用 VAE 解码表征 ,得到最终的图像 ,将掩码部分取出覆盖至原始图像
有人会注意到第 5 步有问题,因为隐空间的图像表征并不能和原图像素一一对应,有时尺度会差很多。这一点看起来并不能避免。
Diffusion Model 的训练
Diffusion Model 在训练时的步骤与它推理的方法相关。
- 采样一张图像 、步数
- 将真实图像经 VAE 编码得到 ,而后依照步数 生成指定强度的噪声 叠加到编码 上,得到
- 将 送给 U-Net 解码,得到
- 生成 步的噪声 叠加至编码 ,得到真实的
- 计算模型输出与真实结果的损失,优化 U-Net
不过采样 的不确定性太大,通过一些额外的推导可将 合并为一项,从而减少训练难度。这些在原论文中可以查阅。
为什么会有微调的需求
SD 的初衷是让人通过自然语言描述来生成一张图,那么为什么还会有微调技术?原因是现实和理想差得有些远。
现在,假设我们要描述一种新的画风,亦或是一个新的人物。在有 CLIP 这种 open-world 模型后,也许你可以不用训练模型而通过具体的语言去描述它:
试过这么干的人可能已经在笑了。且不论你能否用合适、恰当的语言详细表述一个「新概念」——例如上图的普拉娜,她很可爱,你需要看看——的所有特征,另一个更加要命的问题是其实 CLIP 它不完全理解你写的句子。虽然 CLIP 在设计上希望对齐人类自然语言,但实际试验中它的局限性依然较大,比如只能描述简单场景(复杂构图就没了)、更关注常见且显著的名词(概念细节就没了)、难以理解抽象词汇(画风就没了)……所以即使你能用完美的词汇和句子描述出这个「概念」,CLIP 也只会给你浇一盆冷水:
CLIP 的提示工程对于 SD 是有用的,但没那么有用。别想太多。为了让模型的效果更好一些,我们还是要回到微调。接下来要说的 SD 的两种微调技术分别作用于 Text 侧和 Image 侧[1]。
Textual Inversion
其实就是固定模型后学习提示词,是一种 Prompt Learning。
对一般人来说从头训练模型有些抽象,需要大量的数据和资源,往往不能承受。于是一种更高效并且也有效的技术 Prompt Learning 诞生了,或者在这里叫作 Textual Inversion,随你喜欢。它的基本思路就是,固定文本侧模型的全部参数,只创造并学习一个新的词汇,新学习的词汇能在已有知识结构下综合地描述一种概念。由于需要学习的参数量极少,TI 对设备性能和数据的需求都很低,并且效果往往都不错。不过,借助模型知识的自然也会受制于预训练数据的分布。如果微调数据与之分布相差太远,那微调效果相应地也会受到影响。
具体步骤为:
- 向 Tokenizer 中插入新的 Token t,并在词嵌入矩阵中对应插入新的 slot。
- 固定所有网络参数,在训练中通过梯度反传优化 t 的嵌入。
在 TI 中可能涉及到以下特殊的超参数:
placeholder_tokens
:新词语的列表。虽然从直觉上来说为一个新概念设置一个新词语听起来很对,但是一般会设置多个词语。它的数量会限制概念的复杂性。init_tokens
:使用何种方法初始化待优化词语的嵌入向量。虽然一个更好的嵌入会使得效果更好,但它不该是你在最初就关心的事情。clip_skip
:使用 CLIP 的中间层特征作为文本特征,越靠近最终层,特征越反映文本整体特征和高维意义。不过这东西一般由基础模型决定,你没得选。clip_ti_decay
:在训练过程中削减新词语的强度。随着训练过程进行,新词语的嵌入范数会被逐渐限制在某个数值范围内。防止其过分激活,遮盖其他词语特征。
LoRA
LoRA 是另一种微调技术,同样是向网络中引入少量可训练参数,将模型知识适配到下游任务。在 CLIP 微调里有种类似的东西叫作 Adatper,与 LoRA 透露着相同的思想。但总体来说 LoRA 的故事似乎要更有趣一些,这里就不展开了。
LoRA 会冻结整个网络的参数,然后向网络的线性层中插入参数量极少的可训练 LoRA 层。这些层以旁路方式挂载在网络里,在训练中会随着梯度优化逐渐对原始网络施加额外影响。
相比 TI,LoRA 的学习空间相对更大、更自由。
在 LoRA 中可能涉及到以下特殊的超参数:
lora_rank
:LoRA 矩阵的秩。LoRA 向网络中插入的可学习矩阵 是低秩矩阵,从而降低了实际可学习的参数量。- ……似乎也没什么了
二者共同使用
然后就有人意识到,这两种微调方式有着组合使用的价值:
- TI 能在相对粗粒度上学习已有概念的组合,能作为微调文本侧的微调手段。经验上,训练文本侧的难度更大,不宜给予太多参数。[1]
- LoRA 能够学习相对细粒度的新知识,可作为图像侧的微调手段。
所以合理结合二者似乎能得到更有趣的效果。实际测试中,先 TI 后 LoRA 要更加容易一些。但我认为这并不是合理的方法。在我的直觉看来,LoRA 与 TI 应分别用于低维特征和高维特征的学习。考虑到深度模型倾向于先学习低频特征再学习高频特征,应是先 LoRA 后 TI。具体到我们要做的事情上,就是先使用 LoRA 学习画风,再使用 TI 学习高阶概念。虽然这样做的效果目前并不好,但接下来的实验都是这么做的。
以下为经过相对简单配置后 LoRA 的微调效果。可见经过 LoRA 的调整后,新生成绘图的画风明显改变,更加接近训练集图像。除了展示效果外,我想通过这个例子来说明一件事情:AI 绘图的画风可以在精心调整后转变为任何一种。有人能注意到新图像对细节的理解变差了(比如手),但这种问题只能算是 side problem,更别提有人还会 AI + 手绘修图了。
在加载学习了普拉娜特征的 TI 后,图像中的细节更加倾向于该人物。上下比较两张图,可以发现模型理解了黑色发箍+蝴蝶结、全黑色上衣+白色领结,前发同样去掉了左侧而仅保留右侧麻花辫,表情神态更加接近于该角色。如果进一步仔细微调 + Inpainting 后将产生更好的效果。
代码
本文并不打算介绍 WebUI 等工具的用法。
在学习这堆东西的时候我主要参考了这份代码。没有过分封装,适合学习。有兴趣的话也可以参考我的代码,能够更自由地组合两种微调方式,后续整理完会放出来。
- 其实 LoRA 也能作用于 Text 侧。但是按照我调 CLIP 的经验,这样做之后性能似乎就没有变好过。大概对 SD 微调有一定的指导意义。 ↩