Stable Diffusion 模型微调技术探究

本文均仅作学习、交流使用。

本文仍在修订中。

这篇文章并不着重于原理解释,而是主要针对其代码和实践方面的技术性探究。只有密切涉及到的原理才会展开,否则便从简了。

在阅读本文后,你将了解到

  • Diffusion Model 的基本原理
  • Textual Inversion 的基本原理,以及如何使用该技术微调模型
  • LoRA 的原理,以及如何使用 LoRA 微调模型
  • 如何将两种微调技术结合使用
  • 图像生成的一般性 Pipeline 及部分训练、推理实践经验

文章目前还没有涉及到各种后处理,如果还有机会的话也许会加。

Stable Diffusion,一种 Diffusion Model

Diffusion Model,或者称扩散模型,是近期很火的一种图像生成技术。它的原理直白来讲其实较为简单,就是从一张噪声图像 z 中不断剔除噪声,使之剩余部分最终成为一张有意义的图像 y。这个过程可以写为

z=xTxT1xT2xT3x0=y\mathbf z=\mathbf x_{T} \to \mathbf x_{T-1} \to \mathbf x_{T-2} \to \mathbf x_{T-3} \to \dots \to \mathbf x_0 = \mathbf 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 T\mathcal T 生成对应图像。这也是 SD 最原始的用途。

Text2Image 的流程。示意图中的 Latent 可能有空间维度。

在一次 Text2Image 任务推理中,

  1. 输入的 Prompt T\mathcal T 被 Tokenizer 解释成模型可识别的 Token 串
  2. TextModel 将 Token 编码为文本特征向量 T\mathbf T,用于约束后续的图像生成过程
  3. 模型首先在隐空间生成随机噪声 xT\mathbf x_T
  4. U-Net 读入噪声 xT\mathbf x_T,在文本约束下预测噪声,并生成去噪的表征 xT1\mathbf x_{T-1}
  5. 重复第 4 步直到去噪完成,得到 x0\mathbf x_0
  6. 使用 VAE 解码表征 x0\mathbf x_0,得到最终的图像 I\mathcal I

搞过跨模态任务的人应该相对清楚这个流程,可能唯一不太了解的就是 3、4 步的原理和详细步骤,不过这篇文章不会过多解释这件事。在实际生产中可能还会辅以超分等后续过程,以进一步提升图像质量。

Inpainting 图像修复

SD 同样也能用于图像修复。除了 SD 有提供专用于 Inpainting 的预训练模型外,借助标准的 SD 模型同样可以实现类似效果:

在这个任务里,除了输入 Prompt T\mathcal T 外,还需要提供一张图像 I\mathcal I 和掩码 M\mathcal M,掩码上标记了图像中需要重绘的区域。

Inpainting 的流程。示意图中的 Latent 有空间维度。Noise Scheduler 的作用涉及到训练细节,可简单理解为向图像叠加噪声,以符合模型的数据分布。

  1. 输入的 Prompt T\mathcal T 被 Tokenizer 解释成模型可识别的 Token 串
  2. TextModel 将 Token 编码为文本特征向量 T\mathbf T,用于约束后续的图像生成过程
  3. 模型从经过掩码处理的图像初始化表征 x^\hat \mathbf x,表征同样依照标准流程叠加噪声(所以 xT\mathbf x_T 最开始仍全是噪声)
  4. U-Net 读入表征 xT\mathbf x_T,在文本约束下预测噪声,并生成去噪的表征 xT1\mathbf x_{T-1}
  5. 保留掩码区域的模型预测结果,其他区域使用真实图像表征 x^+N\hat \mathbf x + N 覆盖掉,然后重复第 4 步(NN 是理想状态下的噪声)
  6. 使用 VAE 解码表征 x0\mathbf x_0,得到最终的图像 I\mathcal I',将掩码部分取出覆盖至原始图像

有人会注意到第 5 步有问题,因为隐空间的图像表征并不能和原图像素一一对应,有时尺度会差很多。这一点看起来并不能避免。

Diffusion Model 的训练

Diffusion Model 在训练时的步骤与它推理的方法相关。

  1. 采样一张图像 I\mathcal I、步数 tt
  2. 将真实图像经 VAE 编码得到 x\mathbf x,而后依照步数 tt 生成指定强度的噪声 NtN_t 叠加到编码 x\mathbf x 上,得到 xt\mathbf x_t
  3. xt\mathbf x_t 送给 U-Net 解码,得到 xt1\mathbf x_{t-1}
  4. 生成 t1t-1 步的噪声 Nt1N_{t-1} 叠加至编码 x\mathbf x,得到真实的 xt1\mathbf x_{t-1}
  5. 计算模型输出与真实结果的损失,优化 U-Net

不过采样 I,Nt,Nt1\mathcal I, N_t, N_{t-1} 的不确定性太大,通过一些额外的推导可将 Nt,Nt1N_t, N_{t-1} 合并为一项,从而减少训练难度。这些在原论文中可以查阅。

为什么会有微调的需求

SD 的初衷是让人通过自然语言描述来生成一张图,那么为什么还会有微调技术?原因是现实和理想差得有些远。

现在,假设我们要描述一种新的画风,亦或是一个新的人物。在有 CLIP 这种 open-world 模型后,也许你可以不用训练模型而通过具体的语言去描述它:

一张普拉娜:a girl, white bow hair tie, white extra long hair, red pupils, black sailor suit, black short skirt, black stocking, white bow tie, black windbreaker...... I've tried my best.

试过这么干的人可能已经在笑了。且不论你能否用合适、恰当的语言详细表述一个「新概念」——例如上图的普拉娜,她很可爱,你需要看看——的所有特征,另一个更加要命的问题是其实 CLIP 它不完全理解你写的句子。虽然 CLIP 在设计上希望对齐人类自然语言,但实际试验中它的局限性依然较大,比如只能描述简单场景(复杂构图就没了)、更关注常见且显著的名词(概念细节就没了)、难以理解抽象词汇(画风就没了)……所以即使你能用完美的词汇和句子描述出这个「概念」,CLIP 也只会给你浇一盆冷水:

一张基准模型在提示下生成的图像

CLIP 的提示工程对于 SD 是有用的,但没那么有用。别想太多。为了让模型的效果更好一些,我们还是要回到微调。接下来要说的 SD 的两种微调技术分别作用于 Text 侧和 Image 侧[1]

Textual Inversion

其实就是固定模型后学习提示词,是一种 Prompt Learning。

对一般人来说从头训练模型有些抽象,需要大量的数据和资源,往往不能承受。于是一种更高效并且也有效的技术 Prompt Learning 诞生了,或者在这里叫作 Textual Inversion,随你喜欢。它的基本思路就是,固定文本侧模型的全部参数,只创造并学习一个新的词汇,新学习的词汇能在已有知识结构下综合地描述一种概念。由于需要学习的参数量极少,TI 对设备性能和数据的需求都很低,并且效果往往都不错。不过,借助模型知识的自然也会受制于预训练数据的分布。如果微调数据与之分布相差太远,那微调效果相应地也会受到影响。

具体步骤为:

  1. 向 Tokenizer 中插入新的 Token t,并在词嵌入矩阵中对应插入新的 slot。
  2. 固定所有网络参数,在训练中通过梯度反传优化 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 向网络中插入的可学习矩阵 WW 是低秩矩阵,从而降低了实际可学习的参数量。
  • ……似乎也没什么了

二者共同使用

然后就有人意识到,这两种微调方式有着组合使用的价值:

  1. TI 能在相对粗粒度上学习已有概念的组合,能作为微调文本侧的微调手段。经验上,训练文本侧的难度更大,不宜给予太多参数。[1]
  2. LoRA 能够学习相对细粒度的新知识,可作为图像侧的微调手段。

所以合理结合二者似乎能得到更有趣的效果。实际测试中,先 TI 后 LoRA 要更加容易一些。但我认为这并不是合理的方法。在我的直觉看来,LoRA 与 TI 应分别用于低维特征和高维特征的学习。考虑到深度模型倾向于先学习低频特征再学习高频特征,应是先 LoRA 后 TI。具体到我们要做的事情上,就是先使用 LoRA 学习画风,再使用 TI 学习高阶概念。虽然这样做的效果目前并不好,但接下来的实验都是这么做的。

以下为经过相对简单配置后 LoRA 的微调效果。可见经过 LoRA 的调整后,新生成绘图的画风明显改变,更加接近训练集图像。除了展示效果外,我想通过这个例子来说明一件事情:AI 绘图的画风可以在精心调整后转变为任何一种。有人能注意到新图像对细节的理解变差了(比如手),但这种问题只能算是 side problem,更别提有人还会 AI + 手绘修图了。

a girl, white bow hair tie, white extra long hair, red pupils, black sailor suit, black short skirt, black stocking, white bow tie, black windbreaker

在加载学习了普拉娜特征的 TI 后,图像中的细节更加倾向于该人物。上下比较两张图,可以发现模型理解了黑色发箍+蝴蝶结、全黑色上衣+白色领结,前发同样去掉了左侧而仅保留右侧麻花辫,表情神态更加接近于该角色。如果进一步仔细微调 + Inpainting 后将产生更好的效果。

arona1 arona2 arona3 arona4, a girl, white bow hair tie, white extra long hair, red pupils, black sailor suit, black short skirt, black stocking, white bow tie, black windbreaker

代码

本文并不打算介绍 WebUI 等工具的用法。

在学习这堆东西的时候我主要参考了这份代码。没有过分封装,适合学习。有兴趣的话也可以参考我的代码,能够更自由地组合两种微调方式,后续整理完会放出来。


  1. 其实 LoRA 也能作用于 Text 侧。但是按照我调 CLIP 的经验,这样做之后性能似乎就没有变好过。大概对 SD 微调有一定的指导意义。

Stable Diffusion 模型微调技术探究
https://blog.chenc.me/2024/01/08/stable-diffusion-finetuning/
作者
CC
发布于
2024年1月8日
许可协议