在复刻黏土风图生成中学习(1) — 模型微调/LoRA 原理/图生图
继续学习 Stable Diffusion,这次想以搭建一个实际可用的生图场景 — 黏土风格作为引导,弄清楚整个流程的同时,把过程中遇到的相关概念和原理也做了解,所以这篇是掺和了应用流程和原理的文章。
ComfyUI & 模型
使用 Stable Diffusion 去生成图,有非常多的插件/模型/配置相互搭配组合使用,一般用 WebUI 和 ComfyUI 这两个工具,更推荐 ComfyUI,自由串联一个个模块,流程更清楚,网上有很多在自己电脑部署使用 comfyUI 的保姆级教程,比如这个,这里就不多介绍了。
先看 ComfyUI 这个默认的最简单的 workflow:
这里面简单的几个元素概念和生图流程,上篇文章都有介绍过:最左边的 Load Checkpoint 是加载 SD 模型,接着用 CLIP 模型编码文本 → 生成隐空间原始噪声图 → 采样器根据文本和噪声图输入→在隐空间里迭代降噪生成隐空间图片→最终用VAE解码图片。
为什么叫模型 checkpoint ?模型在微调训练过程中,会在关键节点保存模型参数的状态,这个保存点被称为 checkpoint,SD 有大量基于基座模型微调训练的模型,包括官方出的,比如 SDv1.5 是从 v1.2 的基础上调整得到的,SDXL Turbo 也是基于 SDXL1.0 基础上训练的,这些模型都被称为 checkpoint,这些 checkpoint 包含了生成图所需要的全部核心组件,包括 VAE、CLIP、UNet 的模型数据,可以直接使用。
那模型文件的后缀为什么是 .safetensors
?早期的模型文件后缀是 .ckpt
(checkpoint缩写),一个通过 Python 序列化后的数据,使用时需要对它反序列化,这个反序列化过程也就容易被注入恶意代码,所以后面提出了新型安全的格式 safetensors,只包含张量数据(模型上的参数数据),无需反序列化,安全且速度快,目前模型基本都以这种方式存储。
我们用这个默认 workflow,选个模型,用纯提示词 claymation style, a tower
试试生成黏土风图片:(图上使用了 dreamshaperXL 模型,是在SDXL 的基础上微调的最受欢迎的一个模型)
可以看到效果并不是很好,比较生硬。可能加多一些细节提示词、调节下相关参数会好一些,但在图片训练过程中,黏土风格相关的图片数量应该是不多的,训练图片对应的文本描述也比较散,如果固定要这种风格,生图的 prompt 要尽量贴近训练时这类图偏对应的文本,才有可能有好一点的效果,这很难控制,也不保证效果,很难达到我们想要的风格。
模型微调
如果我要一个能更好输出黏土风格的模型,那可以给这个模型做微调,给它输入更多黏土风格的图片训练,让它学会我们具体要的是什么,针对性输出。
微调 SD 模型,目前从成本大到小,目前用得最多的有三种方式:
- Full Finetune:
- 最朴素的方式,使用图片+ 标注的数据集,进行迭代训练,对原模型所有参数进行调整,成本最高,但可以对整个模型做全面调优,大幅改变生成风格,上面的 dreamshaperXL 就是以这种方式。它训练数据量要求大、计算资源消耗高、最终模型就是包含所有模型参数的 checkpoint。
- 这种训练我理解适合大量的数据、对模型整体做调优较合适,如果只是想在特定领域,用少量数据,比如把某只猫,把某个人脸、某个物品训练进去让模型认识,那很可能出现过拟合问题(数据不够多样,污染了通用词,比如拿自家的猫训练,最终整个模型对 cat 这个输入只能生成自家的猫),或欠拟合问题(训练样本太少,没有影响到网络参数,训练无效)。
- Dreambooth:
- LoRA:训练门槛极低,只需要个人PC的算力、个位数(三五张图片)也能训练出特定人物、风格的微调模型,也能达到很好的效果,生成的是外挂文件,体积小可插拔,是目前 SD 使用最广的微调模型,原理下面细讲。
像黏土风格这种诉求,仅是一种风格化的优化,是比较适合使用 LoRA 模型的,从 civitai (最大的SD 模型社区)上找了个黏土风的 LoRA 模型 CLAYMATE,在原 workflow 简单加上这个 LoRA模型,先看看应用的效果:
同样的提示词下,效果好了很多,是比较舒服的黏土的风格。
每个 LoRA 都有个触发词,上面用的这个模型触发词就是 claymation style
,我理解相当于训练时大部分图片的 prompt 标签都加上了这个词,这样使用这个词时,模型能更好定位到训练到的数据。比如下面去掉这个claymation style
,即使用了 LoRA,也对应不上这个 LoRA 的风格。
这模型是怎么训练的?基本上流程是,选好图片→处理图片(裁剪+加提示词)→用工具 Kohya 训练→看结果调参重复,具体跟着网上教程走就行,各种参数细节参考这里,我还没真正执行过训练,先不多说。另外训练的整个代码生态都是围绕 NVIDIA 显卡建立的,Mac 没有 NVIDIA 显卡,没法训练。虽然理论上可行,但社区生态不友好,基本不可用,要训练只能搞台 PC 或用云服务器。
LoRA 原理
来具体看看 LoRA 的原理,全称 Low-Rank Adaptation,低秩适应。什么是低秩?为什么能做到微调成本低文件小效果也不差?
Full finetune
先看看正常的微调,也就是前面说的 Full finetune,下面这图很好理解,正常微调就是通过新增的训练集,重新调整这模型里面网络的参数,把这个参数更新到原有网络里,变成一个新的模型使用。
这里除了前面说的要求的训练数据量大、容易过拟合/欠拟合的问题外,还有个大的问题,就是计算量大、资源要求高。
大模型都是由多层神经网络叠加组成,Transformer 和 UNet 都是,使用这些模型时,是对这些模型正向推理,这个过程需要的资源不高,只需要把模型参数全部加载进内存,一层层正向计算就行。
但训练这些神经网络要求就比正向推理高很多,整个训练中,每一步训练的过程包括:
- 前向传播:训练数据(样本)输入当前网络,生成预测结果(跟使用模型一致)
- 损失函数计算:把预测结果跟样本对应的预期输出对比,评估差异
- 反向传播:把这个差异(损失函数的梯度),反向从输出层到输入层传播,计算每一层每个参数对这个差异的贡献,记下相应数据。
- 更新参数:对反向传播获得的数据,更新网络中每个参数值
这里内存中就需要同时存在好几个数据:1.原网络参数 2.前向传播过程中计算出来的数据,反向传播计算时需要用到 3.反向传播过程中每个参数的差异贡献(梯度值),更新参数时要用到。
所以假如整个神经网络有 n 个参数,每一步训练就要在显存存储 3n 个数据,进行 3n 次计算(正向推理、反向传播计算,更新每个参数的值)。还有一些训练优化的方法,数据的存储和计算量会更高。Stable diffusion XL 的参数量是35亿,llama 3最高参数量达到4000亿,每一步的计算量感人。
参数冻结
再来看看 LoRA 怎么解这个问题。
首先,LoRA训练过程中,会把原网络参数冻结(下图的W),不会去修改原网络参数,只让原网络参与正向推理预测结果的过程,所有对参数的调节都独立出来在 △W 上,这个△W最终也不会更新在网络上,只会在使用这个模型时外挂式地加上它,跟原模型一起叠加共同进行推理。
到这里,其实它只是换种方式,专门把训练变化的部分抽出来,其他都没变,△W 跟 W 的参数个数一样,该存储和计算的量一样。
低秩分解
下一步才是主要的,接下来需要一点点基础线性代数矩阵的知识。
前面这个分离出来的部分△W,变成下图这样,不是用跟原网络一样的参数量去表示,而是通过一个数学的方法 低秩分解 去表示,把原模型参数 W 和 △W 看成一个矩阵数据,那 △W 这个矩阵可以用两个小矩阵Wa 和 Wb 相乘去表示,这过程就叫低秩分解。
矩阵中秩(Rank)的概念简单说就是矩阵中行列较小的那个值,比如 100 x 5 的矩阵,秩就是5,把100 x 100 的矩阵,分解成 100 x 5 和 5 x 100 的矩阵相乘(相乘后是100×100的矩阵),就是低秩分解。图中的 r (rank)就是这个秩。
可以看到这个秩越小,数据量越小,比如分解成 100×10 和 10×100 两个矩阵,这俩矩阵数据量是2000,如果分解成 100 x 5 和 5 x 100 ,数据量是1000,相对于原矩阵 100×100 数据量 10000,要存储的数据量下降10倍。
所以 LoRA 训练出来的不是△W,而是分解后的两个低秩矩阵 Wa 和 Wb,这就是 Low-Rank Adaptation 低秩适应的意思。设定的秩的值越低,所要存储的数据量越低, 所以 LoRA 模型会根据训练时设置的秩值,比原模型大小低一两个数量级。同时训练过程中,因为这俩低秩矩阵跟原矩阵不是一个数量级,所需要的计算量和显存也相应减少了,在普通 PC 也能跑起来。
看起来很神奇,把一个完整的数据做这样的分解,数据的信息量肯定减少了,为什么用这种方式做微调,效果还能好?LoRA 论文中表示,在预训练的大模型上微调时,如果是处理一个细分的小任务,参数的更新主要在低维子空间中,很多高维子空间的参数在微调前后根本就没动,越简单的细分任务,对应要更新的参数维度就越低,可以简单理解为 LoRA 的低秩分解能对应到大模型低维子空间的参数更新中,更多扩展阅读参考 1 2 3。
模型微调和 LoRA 原理就介绍到这里,我们继续回到黏土风的整个 workflow。
图生图
黏土风格这种模型的应用场景是给用户当图片特效使用,也就是需要图生图的方式,前面只做了文生图,来看看图生图的 workflow,以及生成的效果:
只需要在前面的 workflow 基础上,把生成空白噪声图 Empty Latent image 节点,改成 Load image + VAE 编码节点即可。
这里的原理很好理解,文生图是用一张全是随机噪点的图作为输入,沿着 Prompt 逐步降噪生成图片。我们把这个输入换成一张真实的图片,再在上面加上一定量的随机噪点,代替完全随机噪点图进行采样迭代逐步生成图片。把这个加了噪点图片看成是图片生成降噪过程中的某一步状态的话,相当于之前图片是从 0 开始生成,现在是从某一个中间过程点开始去生成,整个流程是一样的。
这里要加多少噪点,在 KSample 这个采样器节点的 denoise 里可以设置,来看看同样的文本 prompt 和图片输入的情况下,设置不同值的效果
denoise 为 0 时,没有加噪点,也就没有去噪的过程,输出是原图,denoise 为 0.4 时,可以猜想到,这时加的噪点程度不影响图的主结构,猫戴帽子的细节也还有,那降噪生成后这些细节还会保留,随着噪点越来越多,原图的细节越来越少,到 denoise 为1.0时,也就是加100%噪点时,跟输入一张随机噪声图是一样的,跟原图就没什么关系了,只跟输入的 Prompt 文字有关系。
看起来设置 0.4 到 0.6 之间的降噪,效果还可以,即保留了原图整图的大致内容,也能适当加上黏土的风格。用其他图片尝试,也还行:
图片 Tag 生成
但这里不同的图片的 Prompt 得自己手动改,才能生成类似的图,我们正常使用这类风格化滤镜是不需要自己输入 prompt 的。我们可以加个图片 Prompt 生成器,让它描述输入的图片,再作为图片生成的过程。
我们添加 WD14 Tagger 这个插件节点,它可以从图片中反推出适用于 SD 提示词的标签(booru风格标签),我们把它解析出来的 tag,跟 LoRA 模型的触发词 claymation style page
一起组合,作为 Prompt 输入,这样就实现了只输入图片,不用修改 Prompt 就能产出对应的图了。
这里注意得用 (claymation style page:2)
这种 Prompt 权重表达方式,把这几个 LoRA 模型的触发词调高权重,不然会淹没在图片输出的众多Tag上,没法起到作用。
最终的 workflow文件(下载这张图片可在ComfyUI上导入):
到这里,我们实现了一个最简单的黏土风格图生图流程,也认识了过程中涉及到的技术原理。目前这只是个最简单的 demo,在某些图片下效果还可以,但要真正达到可用还有很多问题,后续会随着一些问题继续探索 SD 里的相关技术。
参考资料
LoRA模型微调原理:https://www.bilibili.com/video/BV1Tu4y1R7H5
利用LoRA对LLM进行参数高效的微调:https://zhuanlan.zhihu.com/p/632159261
神经网络训练:https://blog.csdn.net/brytlevson/article/details/131660289
Stable Diffusion原理可视化:https://www.youtube.com/watch?v=ezgKJhi0Czc