浅谈stable diffusion (二又五分之一)
本系列的第三篇,如果你看过前俩篇,想必已经对stable diffuison原理和优势与劣势甚至未来的发展方向有了一定了解。
stable diffuison发展至今,商业(实用)属性已经相当高了,所以我们对stable diffusion剩下的细枝末节就从实际操作的角度讲起。
那么便从stable diffusion webUI讲起......
如果你不想了解任何和原理有关的东西,你甚至能把这篇文章当做新手教程,hhh~
Stable Diffusion web UI
A browser interface based on Gradio library for Stable Diffusion.
整体介绍
webui是基于gradio库搭建的图形界面,可以实现stable diffusion原始的txt2img和img2img模式,并提供了一键安装并运行的脚本。此外,webui还集成了许多二次开发功能,如outpainting、inpainting、color sketch等。它还提供了X/Y/Z图等脚本生成方式以及文本反转、超网络等图形界面训练按钮,以及一系列Extras方法的快捷使用。
虽然webui自称为stable diffusion的web图形界面,但实际上它是AI图像生成的综合工具。由于基于gradio的优势,它在搭建、扩展、多平台适配上都非常方便和实用。然而,受限于gradio和图形界面,一些精细的调整或者对生成流程的微调无法进行精细操作。虽然其中一些流程可以通过添加插件弥补,但仍有一些限制。
总的来说,stable diffusion web ui是初次接触扩散模型甚至是生成模型的新手最适合的图形化操作界面。接下来,我将按照webui的界面一一分析其中的原理、使用方法、效果和影响。此外,还可以通过webui进行许多扩展和调整,使其更适合个人的需求和喜好。

webui 原版样式
Stable Diffusion checkpoint

位置在webui的右上角,也是最重要的选项,可以在此处选择你的stable diffusion模型。
checkpoint(权重点),意味着里面存放着模型的权重参数,checkpoint 文件本质是一个变量名-变量值的词典。
一些疑问的解答
是否选择带ema的版本
如果你在很早期就亲自部署webUI或者经历或novelai的leak模型,你通常会有疑问,我究竟该选用animefull-latest的7G版本还是final-pruned的4G版本。
4G版本其实是7G版本的修剪版本,因为novelai在训练模型时使用了ema算法,试图提升模型最终生成的效果。
Ema是一种算法,可以近似获得近n步权重的平均值。该算法基于一个假设,即在训练的最后阶段,权重会在实际的最优点附近波动。通过取最近n步权重的平均值,可以使得模型更稳定。
早期一些人有一个误区,认为修剪版本的模型删除了ema部分的权重,事实上恰恰相反,修剪模型删除了所有非ema的权重。而且由于启用ema后运行非修剪版本时等同于运行修剪模型,所以单纯从生成的角度来说,俩个模型的效果是一致的。
泄露文件里模型对应的config文件可以控制是否启用ema
但是由于修剪版本的模型删除了非ema的权重,所以相比真实训练出的权重有失真,所以笔者认为如果你有数据洁癖或者坚持某种心理玄学在进行基于novelai leak模型进行二次训练时最好选择非修剪版本,但是笔者认为ema对于扩散模型来说不是很重要(相对于GAN)。
所以答案很明显:
如果单纯用于生成应该选用不带ema的修剪模型,这样会节省内存和显存消耗。
如果用于二次训练,如果显卡不错可以考虑基于带ema的版本(这里指的是非修剪版本,因为none-ema通常指修剪版本)进行二次训练。
该选择float16还是float32版本
float16和float32代表模型的权重保存的精度,float32的长度是float16近一倍,与之带来的是float32版本的模型文件通常要大很多。通常而言我们将float32成为单精度,float16成为单精度,float64为双精度。
神经网络训练对精度降低的容忍度非常高
在实际生成的过程中,精度带来的影响微乎其微,笔者这里推荐是使用float16的版本作为生成使用,节省内存和显存。
在实际训练的过程中,对于stable diffusion来说精度带来的影响也是较小,当然实际训练过程中通常采用混合精度训练,最后使用float32的形式保存,财大气粗直接以float32的形式训练也行。
至于float16和float32版本对你来说谁更快,你应该参照你的N卡参数来判断。
说的就是你,T4
是否应该加载vae
模型文件即checkpoint是已经包含了vae(用于从隐空间解码,详见第一篇)相关参数,但是stable diffusion官方和novelai泄露出来的文件都有额外的vae,因为这些vae针对面部或者其他一些细节做了改良,所以加载vae文件实际是替换了模型文件中原有的与vae相关的参数。
当然由此引发的问题就是,如果你的模型原本的效果就很好,盲目加载vae可能弄巧成拙。或者如果你的模型文件已经注入过vae,你再加载同样的vae只是浪费时间。
我一直认为vae文件应该被称为vad(decoder,解码器)......
模型文件的后缀
模型文件的后缀为.ckpt
或者.safetensors
.ckpt
文件是用 pickle 序列化的,这意味着它们可能包含恶意代码,如果你不信任模型来源,加载 .ckpt 文件可能会危及你的安全。
.safetensors
文件是用 numpy 保存的,这意味着它们只包含张量数据,没有任何代码,加载 .safetensors 文件更安全和快速。
其中风险利弊自行衡量
模型文件后面[]内的hash值
hash值通常来标注模型的唯一性,但是由于位数较短存在撞车可能,不是很实用。
stable diffusion算hash的算法经过变动,最近算16位hash的算法更改为了sha256,但是由于位数较低,撞车概率还是很高,如果遇到了相同hash的模型想要判断是否相同还请自行算更多位数的hash值来进行比较。
文生图/txt2img

Prompt/Negative prompt
正向和反向提示词,用于文本条件生成。
正向提示词用于引导你想生成什么,反向提示词用于反向引导你不想生成的东西。
如果你阅读过前俩篇,你可能疑惑我之前没有提到过Negative prompt,那么我就在这里进行一并讲解
第一篇我们讲到在包含文本条件生成时,文本提示词被转换成语义向量,而语义向量则被送入U-Net进行噪声预测。在stable diffusion中实际上有俩组语义向量,一种用于正向提示,一组用于反向提示。那么这些提示究竟是怎么工作的呢?
我们先观看只有正向提示时的工作流程:
在stable diffusion进行采样时,算法首先使用由正向提示引导的条件采样对原始的图像(噪声)进行降噪,然后采样器再使用无条件采样再对同一图像(噪声)进行一些去噪(该过程不受文本提示影响),但是就算是无条件采样同样会是朝着一个具体的画面扩散,就像下图的篮球或酒杯(可以是任何东西,不过我们拿酒杯和篮球举例)。
在这样的一个过程中,你实际得到的图像是最开始的有条件采样得到的图像减去无条件采样的图像后的差异,这个过程重复数次最终获得你的成品图。过程如下图:

Without negative prompt, a diffusion step is a step towards the prompt and away from random images.
那么如果我们携带反向提示会发生什么(该过程将胡子作为反向提示词)?
反向提示实际上是劫持了无条件采样,我们的无条件采样过程变成了使用反向提示进行采样,那我们的图像(噪声)就向着反向提示扩散,然后我们用正向提示得到的图减去反向提示得到的图,然后如此重复,不就减少了负面提示词代表的内容出现的可能吗。过程如下图

When using negative prompt, a diffusion step is a step towards the positive prompt and away from the negative prompt.
以上的例子只是为了帮助理解而已图像的形式进行说明,实际stable diffusion的生成过程是在隐空间(latent space)进行,所以本例子并非真实的生成流程,但是本质是一致的
程序正义党不要喷我,嘤嘤嘤~
prompt的语法
如果你看到不同版本的说法,当你使用webui进行生成时请以笔者为准(从源码中整理出来,希望有人打脸)。
注意:通常有人认为|
也是一种特殊语法,但是很遗憾webUI里是没有|
这种语法的(我没有从代码中找到),这个语法仅用于novelai,用于混合不同元素,不过webui也提供了其他语法来变相实现这一过程(AND
)。
增强/减弱(emphasized)
通常有如下语法来影响一个提示词的影响程度:
() 强度变为1.1倍
[] 强度变为0.9倍
(keyword:XX) 强度变为XX倍
以下俩个例子都是强度变为1.1倍
(keyword)
(keyword:1.1)
以下俩个例子都是强度变为0.9倍
[keyword]
(keyword:0.9)
可以使用多个()或[]来影响强度
例如,多个使用时就是简单的相乘
(keyword): 1.1倍
((keyword)): 1.21倍
(((keyword))): 1.33倍
[keyword]: 0.9倍
[[keyword]]: 0.81倍
[[[keyword]]]: 0.73倍
那么假如我是大聪明,我偏要用以下写法会发生什么
[keyword:1.9]
可莉也不知道哦~.jpg
影响程度的范围在0.1到100之间
这种增强/减弱是如何实现的呢?你如果看过第一章,我们的单词其实是转化为了语义向量(768维空间中),那么缩放这个向量就能使得相关概念变强或减弱
注意当你不想使用emphasized而是想用()
作为字符串时,你可以这样输入:
\(word\)
渐变/调整作用时机(scheduled)
通常有如下语法来进行渐变(混合)俩个提示词:
[keyword1 : keyword2: amount]
可以理解为[from:to:when]
amount
的范围是0到1,代表混合强度,如何实现? 举个例子:如果你的amount
为0.75,而采样步骤是40步,那么会有40*0.75=30步的过程,提示词为keyword1
,剩下10步提示词为keyword2
所以如果你试图均匀混合,最好将amount
调为0.5左右
示例如下图:

所以由此诞生了一个非常有用的技巧,如下
我们将(ear:1.9)作为正向提示词
然后我们将这个放入反向提示词
[the: (ear:1.9): 0.5]
第一个关键词为一个毫无意义的词,第二个关键词为(ear:1.9)代表我们想要生成耳朵
假如你的采样步骤为20步,则前10步会减少一个毫无意义的东西出现的概率,后10则会执行(ear:1.9)来减少耳朵出现的概率
我们就得到了一张类似上图一般头发遮住耳朵的图
究其原因是在扩散的过程中最开始的步骤往往更重要,后面的步骤则是对细节进行了更精细的调整
通过这个技巧我们可以将一些提示词只作用于后面的步骤或者只作用于前面的步骤
值得注意的是这个语法有简写方式如下:
[to:when] - 在某步后添加某个词条(to)
[from::when] - 在某步前使用某个词条(from)
理解了,让我们来看个复杂例子
fantasy landscape with a [mountain:lake:0.25] and [an oak:a christmas tree:0.75][ in foreground::0.6][ in background:0.25] [shoddy:masterful:0.5]
以上例子采用100步采样
下方为某步数后我们实际的prompt
最开始, fantasy landscape with a mountain and an oak in foreground shoddy
25步后, fantasy landscape with a lake and an oak in foreground in background shoddy
50步后, fantasy landscape with a lake and an oak in foreground in background masterful
60步后, fantasy landscape with a lake and an oak in background masterful
75步后, fantasy landscape with a lake and a christmas tree in background masterful
交替(alternate)
一个很有意思的语法,也可以用来混合一些提示,示例如下:
[keyword1|keyword2] keyword1和keyword2在采样时被交替使用
[A|B|C|D] A,B,C,D按顺序被交替使用
比如接下来的例子
[red hair|yellow hair], long hair, 1girl
在使用DPM++ 2M Karras采样时
第10步,头发偏向黄色(使用不同种子,多次生成)

第11步,头发偏向红色(使用不同种子,多次生成)

第13步,头发偏向黄色(使用不同种子,多次生成)

如上面所说,扩散的过程中最开始的步骤往往更重要,所以在逐渐收敛的后,你的交替词往往也会效果减弱,最终发色也会其中偏向一种,不会在下一步产生大变。
组合/混合(composable)
组合的关键词是AND
,注意要大写以区分(我们实际短语的and应该是小写,笔者认为这样做是为了将prompt语法和实际自然语言进行区别),这个AND
关键词允许组合(混合)多个提示的方法(对novelai的|
的webui版实现)。
a cat AND a dog
效果如下图

值得注意的是,你可以像novelai中一般可以为每个词提供权重,默认为1,比如:
a cat :1.2 AND a dog AND a penguin :2.2
如果你的某个提示词权重低于0.1,那么该提示词对应的要素就很难产生影响
词汇的具体解析
stable diffusion使用Clip作为文本的编码器(详见第一篇),Clip 的标记器在标记之前将所有单词小写。
得益于Clip的强大,你可以在提示词里使用自然语言(主要是英语,看模型训练时的情况),也可以使用类标记语言。
提示词中开头和结尾的额外空格会被直接丢弃,词与词之间的多余空格也会被丢弃。
支持颜文字 和 emoji ,Unicode 字符(如日语字符)。
拼写错误或罕见词
提示单词可以由一个或多个符号组成,常用词通常被解析为具有共同含义或少量含义的单个标记。而拼写错误和不常见的单词被解析为多个可以被识别的标记,示例如下:
bank,bankk会被识别为bank,而bonk不会被识别为bank
_ 通常不会被转换成空格
笔者这个拼写错误被识别是很大原因因为Clip在训练过程中,人类也有拼写错误(人总会犯错,机械飞升吧~)然后联系上下文同样能产生识别这个词的效果。
而对于罕见词,他们的信息量(不准确的描述)太低会被理解为其他的词语,这也解释了颜文字和emoji的作用性相比自然语言更强,因为颜文字和emoji对特定的含义信息是强绑定的关系而且字符数短,而自然语言通常会有歧义。
词汇顺序/数量/位置影响
这是个好问题,以笔者对Clip浅薄的理解,词汇们被转化成语义向量输入到U-Net时利用了attention机制(详见第一篇),而语义向量将会添加到一个位置标记上( a position embedding token),早期的标记具有更一致的位置,因此神经网络更容易预测它们的相关性。而且由于attention机制的特殊性,每次训练时,开始的标记和结束的标记总会被注意到(attention)。而且由于标记越多,单个标记被被注意到的概率越低。
基于以上特性,笔者认为有以下几点需要注意:
开头与结尾的词往往作用性更强
提示词数量越多,单个提示词的作用性越低
开头的数个提示词的作用较强,有更强的相关
关于数量,你可能已经注意到了,当你写prompt时会有数量限制

但是在 webui中,你是可以写 75 个词汇以上的提示的。webui会自动通过对提示词进行分组。当提示超过 75 个 token
(可以理解token代表你的提示词),提交多组 75 个 token
。单个token
只具有同一组中其他内容的上下文。
每一组都会被补充至(1,77,768)的张量,然后进行合并,比如俩组就会合并为(1,154,768)的张量,然后被送入U-Net。
值得注意的是
为了避免将你的短语分成俩组,在分组时会查看附近是否有
,
来尽量还原你想要的输入然后你还能通过输入
BREAK
来快速分组,BREAK
必须为大写
emoji和颜文字
对emoji的表现良好,对热门的颜文字表现良好(欧美环境),而且作用力很强
[red hair|yellow hair], long hair, 1girl,✌️

采样方法/Sampling method

如果你阅读过第一篇或第二篇,想必对采样方法已经有了自己的理解。
webui中集成了很多不同的采样方法,这里结合设置中提供的选项,简单粗略的介绍下它们(不含数学,放心食用)。
Euler
基于Karras论文,在K-diffusion实现
20-30steps就能生成效果不错的图片
采样器设置页面中的 sigma noise,sigma tmin和sigma churn会影响
Euler a
使用了祖先采样(Ancestral sampling)的Euler方法
受采样器设置中的eta参数影响
LMS
线性多步调度器(Linear multistep scheduler)源于K-diffusion
heun
基于Karras论文,在K-diffusion实现
受采样器设置页面中的 sigma参数影响
DPM2
Katherine Crowson在K-diffusion实现
受采样器设置页面中的 sigma参数影响
DPM2 a
使用了祖先采样(Ancestral sampling)的DPM2方法
受采样器设置中的ETA参数影响
DPM++ 2S a
基于
,在K-diffusion实现的2阶单步并使用了祖先采样(Ancestral sampling)的方法受采样器设置中的eta参数影响
Cheng Lu的github](https://github.com/LuChengTHU/dpm-solver)中也提供已经实现的代码,并且可以自定义,1、2、3阶,和单步多步的选择
webui使用的是K-diffusion中已经固定好的版本
DPM++ 2M
基于
的论文,在K-diffusion实现的2阶多步采样方法被社区玩家称为最强采样器,速度和质量平衡优秀
比上方版本更优秀也更复杂
DPM++ SDE
基于
的,DPM++的SDE版本(随机微分方程),DPM++原本是ODE(常微分方程)在K-diffusion实现的版本中调用了祖先采样(Ancestral sampling)方法,所以受采样器设置中的ETA参数影响
DPM fast
基于
,在K-diffusion实现的固定步长采样方法,用于steps小于20的情况受采样器设置中的ETA参数影响
DPM adaptive
基于
,在K-diffusion实现的自适应步长采样方法受采样器设置中的ETA参数影响
LMS Karras
,运用了相关Karras的noise schedule的方法,可以算作是LMS使用Karras noise schedule的版本
DPM2 Karras
使用Karras noise schedule的版本
DPM2 a Karras
使用Karras noise schedule的版本
DPM++ 2S a Karras
使用Karras noise schedule的版本
DPM++ 2M Karras
使用Karras noise schedule的版本
DPM++ SDE Karras
使用Karras noise schedule的版本
DDIM
随latent diffusion的最初repository一起出现, 基于Jiaming Song等人的论文
目前最容易被当作对比对象的采样方法
在采样器设置界面有自己的ETA
PLMS
元老级,随latent diffusion的最初repository一起出现
UniPC
目前最新采样器,基于
的论文理论上目前最快采样器,10步即可获得高质量结果
以上采样器,读者可以从字体大小中看出笔者的偏心,字体稍大的采样器在采样方法的发展史(虽然很短)中发挥了更大的作用,笔者也比较推荐用字体更大的那几种采样器(魔法师们开心就好,不要在意)。
与采样器相关的有俩个参数
eta参数
eta (noise multiplier) for DDIM只作用DDIM,不为零时DDIM在推理时图像会一直改变,生成的图像不会最终收敛
换句话说,即使跑了数百步,图像依旧可能产生大变化。
你可能主要到了上面很多款采样器提到了祖先采样,而且它们中的大多数名称内含有
a
,或者SDE
eta (noise multiplier) for ancestral samplers 作用于名字后缀带a和SDE的所有采样器,不为零时生成的图像也不会收敛
eta noise seed delta,在该值不为零时起到固定初始值的作用,这样你就可以使用相同值还原某些其它人使用了对应eta值的图片(通过相同的seed)
对于魔法师而言,要注意eta(前俩者)不要为0,否则可能会“失去”多样性和创造性,要注意eta(第三个)要和你准备复制的“法术”的eta相同,不然无法完美复制。
sigma参数
sigma包含:sigma churn,sigma tmin,sigma noise,仅对euler, heun和dpm2这三个采样器有效
sigma churn:决定sigma noise值如何被使用,如果churn值为0,即使noise值不为0,也没有任何效果。
sigma tmin:决定最小值范围的限制,可以为0
sigma noise:噪声本身的数值大小(注意,churn>0时,噪声值本身才有意义)
对于魔法师而言,sigma值也与多样性和创造性有关。
采样步数/Sampling steps


慢慢写,敬请期待