Stable Diffusion 基础模型结构超级详解!
1. Transformer
第一个只用 Attention 机制来解决序列到序列问题的模型,最早被 Google 用来解决翻译问题
对于中英翻译而言,需要解决三个具体的问题:
-
如何用数字表示中文和英文
-
如何让神经网络理解语义
-
如何让神经网络生成英文
1.1 Tokenize
神经网络模型不能直接处理文本,需要先将文本转换为模型能够处理的数字,这个过程被称为编码 (Encoding)
-
先使用分词器 (Tokenizers) 将文本按词、子词、符号切分为 token,Token 是指模型处理的基本数据单位
-
将 tokens 映射到对应的 token 编号(token IDs)
这个编码的过程相当于将复杂问题转化为数字问题,把文本的非结构化数据转化为结构化数据。
分词是把文本中的句子、段落切分成一个字符串序列
Tokenization是为数值化作准备,数值化的过程必然需要映射,而映射又需要一个目标集合或映射表。如果切分的子单元种类非常多,就需要一个非常庞大的映射表,这会导致巨大的内存消耗以及过多的计算量,这显然是不理想的。分词的难点在于将原文本切分成子单元的过程中,如何获得一种理想的切分效果,使文本中所有的token都具有正确的语义,并且不会存在遗漏的问题。
根据切分粒度的不同,分词策略大概可以分为三种:按词切分 (Word-based)、按字符切分 (Character-based)、按子词切分 (Subword)
按词切分
按词切分是一种自然而然的方法,因为人类对自然语言文本的理解就是按照这种方式进行切分的,例如:今天天气很好 --> ["今天", "天气", "很好"]
It's a nice day --> ['It's', 'a', 'nice', 'day']
-
适用场景:英文或拉丁语系,直接按照英文中自然的空格作为分隔符进行切分
-
不适用:中日韩类的文字没有自然空格,切分很容易导致出现歧义
-
优点:按词切分非常切合人类的阅读习惯,一方面能够很好地保留词的边界信息,另一方面也能很好地保留词的语义信息
-
缺点:
-
按词切分需要构造的词典太过庞大,严重影响计算效率和消耗内存 eg. “dog” 和 “dogs”、“run” 和 “running”
-
词表中的低频词/稀疏词在模型训练过程中无法得到充分训练,进而模型不能充分理解这些词的含义
- 例如,按词切分会把文本中所有出现过的独立片段都作为不同的 token,从而产生巨大的词表,而实际上词表中很多词是相关的,例如 “dog” 和 “dogs”、“run” 和 “running”,如果给它们赋不同的编号就无法表示出这种关联性。
-
按字符切分
按字符切分是按某一种语言最小符号来进行切分的,简单说就是英文(拉丁语系)以字母为单位,中文日文韩文等则是以字为单位进行切分
例如:今天天气很好 --> ["今", "天", "天", "气", "候", "很", "好"]
It's a nice day --> ['I', 't', "'", 's', ' ', 'a', ' ', 'n', 'i', 'c', 'e', ' ', 'd', 'a', 'y']
-
优点:词表会大大减小,26个字母基本就能够覆盖所有词,5000多个中文基本也能组合覆盖大部分的词汇
-
缺点:严重丢失词汇的语义信息和边界信息,从直觉上来看,字符本身并没有太大的意义,因此将文本切分为字符之后就会变得不容易理解, 中文字符会比拉丁字符包含更多的信息,相对影响较小。而且,这种方式切分出的 tokens 会很多,例如一个由 10 个字符组成的单词就会输出 10 个 tokens,而实际上它们只是一个词
按子词切分
与按词或字符切分不同,子词切分方法将单词分解为更小的单位,这些单位可以是词干、词缀、或者是基于统计的方法生成的子词单元
例如:Let’s do tokenization --> ['Let’s', 'do', 'token', 'ization']
-
优点:能够在一定程度上平衡词汇表的大小和处理词汇的能力,同时保留语义信息
-
对于土耳其语等黏着语言,可以通过串联多个子词构成几乎任意长度的复杂长词
可以看到,“tokenization” 被切分为了 “token” 和 “ization”,不仅保留了语义,而且只用两个 token 就表示了一个长词,这种策略只用一个较小的词表就可以覆盖绝大部分的文本。
BPE
在 Transformer 论文中,使用了 Byte Pair Encoding (BPE) 作为分词策略,BPE是一种基于频率统计的子词切分方法
BPE的工作原理:
-
首先,每个单词被拆分成最小的字符单元,例如,
"lower"
会被拆分为["l", "o", "w", "e", "r"]
-
统计字符对: 统计词汇表中最常出现的相邻字符对,例如,如果
["l", "o"]
在词汇表中出现的次数最多,那么将这两个字符对合并为一个新的子词单元 -
合并字符对: 将最常见的字符对合并成一个新的子词单元,并重复此过程,直到达到预设的词汇表大小,经过若干次合并后,
"lower"
可能会变成["low", "er"]
-
生成最终的词汇表: 最终词汇表包括字符和子词的组合,这些组合可以表示训练数据中的所有单词
将文本切分为 token以后,将 tokens 通过映射表到映射到对应的 token 编号,就完成了第一步,如何用数字表示中文和英文。
1.2 Embedding
例如:我爱吃苹果 ➡️ 我|爱|吃|苹果
我喜欢苹果手机 ➡️ 我|喜欢|苹果|手机
把句子换成 token 后,对每个 token 都分配一个可以学习的参数向量,这个向量就是这个 token 的 Embedding:
Transformer 模型的输入词向量被设置为 512维(那么上图中这个可学习参数的size就是 4 * 512,把每个 token 都映射到 512 维的向量,可以理解每个维度都代表了某种语义)
-
词向量的维度决定了模型能捕捉到多少信息,维度越高就可以捕捉到更多的语义信息
-
512维的词向量足够大,可以有效地表示单词及其上下文信息,但又不会过大,以至于模型难以训练或过度复杂
例如:
(图一:假设有两个维度分别为大小和重量,那么可以在重量和大小组成的二维空间里可视化一组 token,其中一个维度代表“重量”这个语义,一个维度代表“大小”这个语义)
(图二:可以简单的理解,假设每个 token 有 512 维,那么就有 512 个可以描述这个 token 的语义,最终每个 token 都被嵌入到这个 512 维的空间里,语义相似的 token 距离更近,语义不同的 token 距离更远)
(图三:假设有国王、皇后、男人、女人这四个 token 的向量,国王 token 向量➖男人token向量与皇后token向量➖女人token向量大致相等,都代表皇权语义)
以下两个句子都有苹果这个 token,但是在不同语境下,“苹果”的意思不同:
[我] [爱] [吃] [苹果],[苹果] token 注意到上文的 token [吃] ,模型训练的过程中,更新自身 Embedding 偏向水果这个语义,[我] [喜欢] [苹果] [手机],[苹果] token 注意到下文的 token [手机],从而把自身 Embedding 偏向电子产品语义:
在 Transformer 里,通过 token 之间的注意力,让每个 token 根据上下文不断更新自身的 Embedding,这是怎么做到的呢?
1.3 Self Attention
原理
首先,每个 token 都有初始 Embedding 向量,通过三个线性层的映射,分别得到三个向量 Q、K、V(Query、Key、Value)
(称为 Self Attention中的Self的原因就是 Q、K、V 都是根据 token 本身向量经过一些线性映射得到)
-
Q 向量:向其他 token 进行查询
-
K 向量:对其他 token 的查询进行应答
-
V 向量:更新其他 token 的 Embedding
Self Attention 可以简单理解为 Q 与 K 进行相似度匹配,匹配后取得的结果就是V。举个例子我们在某宝上搜索东西,输入的搜索关键词就是 Q,商品对应的描述就是 K,Q 与 K 匹配成功后搜索出来的商品就是 V,例如:
-
相似度:Q · K
-
相似度向量:
-
转化为百分比向量:
-
生成更新后的新的 [苹果] token 向量表示:
具体实现
把所有的 Q 合在一起得到 Q 矩阵,同理,得到 K、V 矩阵,通过矩阵运算计算所有 token 经过一个自注意力后的值:
-
:所有的 Q 向量和所有的 K 向量两两进行点积计算
-
结果:
(n:这句话里token个数)
-
:特征向量的维度,原文为 512
为啥除以?
是因为Q、K 向量初始化的时候都是均值为0,方差为1,经过点乘后第k 个Q和K的值进行乘积相加,均值仍为0,方差为,让乘积后仍保持方差为1,所以要除以标准差,这样训练时梯度更新会更稳定。
1.4 MultiHead Attention
原理
类似于卷积神经网络里的多个卷积核,在 Self Attention 里面可以指定多个 Head 来进行多个 Attention 操作
例如 [苹果] token 经过多次线性映射生成多个 Q、K、V 向量,Transformer 中分成八个头:
(虽然分成了八个头,但是还是像之前自注意力机制一样去计算Attention,第一组中的 Q 向量与其他 token 第一组中 K 向量进行点积操作计算相似度,用其他 token 第一组中的 V 向量更新自身 token 的值,每个 token 的第 i 个 head 只和其他 token 的第 i 个 head 进行 Attention 计算,相当于每个头计算一次self-attention,八个头计算八次,八组 Q、K、V 得到八个新的 V 向量,拼接起来即得到 token 经过多头注意力后的 V 向量)
(但是有个问题要注意,可以发现图中为了表示方便,拆分成多个头是简单的复制,每个头都一样的,但是Transformer 中不是复制,而是平分成多个头,所以每个头可以处理不同的空间, token 是 512 维,拆分为 8 个头,则每个头的维度为 64,最终每个头生成的新的向量表示为 64 维,八个头拼接以后,维度回到512维的 Embedding )
具体实现
以拆分三个头为例:
-
把 Q、K、V 向量拆分成多份,每份代表一个 token,每个 token 的第 i 个 head 只和其他 token 的第 i 个 head 进行 Attention 计算
-
最后,所有头的输出被拼接在一起,然后再通过一个线性层进行融合,得到最终的注意力输出向量
-
每个 head 使用不同的线性变换,这意味着它们可以从输入序列的不同子空间中学习不同的特征关联,关注的子空间不一样,那么这个多头的机制能够联合来自不同head部分学习到的信息,学习到更丰富的上下文信息,这就使得模型具有更强的认识能力
1.5 Position Encoding
问题:
-
Transformer 不是逐字地输入句子,而是将句子中的所有词并行的输入到神经网络中,并行输入有助于缩短训练时间,学习长依赖信息
-
没有考虑词序,如何理解句子的意思呢?——位置编码:词在句子中的位置编码
离散编码
-
模型如果遇到句子 token 长度大于训练时句子 token 长度,模型就没有办法处理
连续编码
使用 Sin 函数
-
通过设置输入的系数,可以改变 sin 函数的波长,系数越小,波长越长
-
波长短的 sin 函数生成位置编码的低维度,波长长的 sin 函数生成位置编码的高维度
-
如何用 Sin 函数做位置编码?
-
位置编码的维度和原始特征编码的维度保持一致(512维)
- 可以看到我爱吃苹果这个句子,不同 token 的原始编码在下面加上位置编码,维度保持一致,每个维度的值都用一个sin函数利用它在序列里的位置生成,维度越高,波长越长,位置变化就越慢,每个位置的sin函数系数用wi表示
-
-
Transformer 中用 Sin 函数和 Cos 函数交替编码
用 Sin 和 Cos 交替编码有什么好处呢?——只与相对位置有关,与绝对位置无关
通过K和Q向量的点积来计算注意力,对于sin和cos交替编码的两个位置向量,一个位置是t,一个位置是Delta t,表示他们之间的相对位置是Delta t,他们之间进行点积,可以看到最后的结果只有Delta t,只与相对位置有关,与绝对位置无关,这对自然语言处理是非常重要的,同一个词组不论出现在句子的什么位置,表达的意思都是一样的,也就是这个词组的意思,只和组成这个词的单个字的相对位置有关,与它在句子里的绝对位置无关.
位置编码公式,其中pos是位置索引,i 是维度索引, 是模型的维度
例1:对于位置索引 ( pos = 0 ) 的 token [我]
-
依此类推,计算每个位置索引和每个维度的编码值,为每个位置生成一个 512 维的向量
-
利用正弦和余弦函数的不同频率来捕捉位置信息,保证不同的位置有不同的编码,同时位置接近的编码有一定的相关性
2. Stable Diffusion 模型结构
2.1 扩散模型
扩散模型,简单来说,首先它会将图片通过增加噪声的方式进行“扩散”,也就是让它变得更模糊,当图片被扩散以后,信息也分散了,就有了更充分的空间,然后再去除噪声,生成新的图片.
扩散模型原理:
-
前向扩散过程(Forward Diffusion Process) → 图片中持续添加噪声
-
反向扩散过程(Reverse Diffusion Process) → 持续去除图片中的噪声
前向扩散
-
前向扩散过程,其是一个不断往图像上加噪声的过程,对于初始数据
,设置扩散步数为 T 步,每一步增加一定的噪声,如果我们设置的 T 足够大,那么就能够将初始数据转化成随机噪声矩阵
-
为什么要噪声?为什么要这么麻烦要一步一步来,而不训练一个网络一步到位呢?
-
因为直接移除像素会导致信息丧失,添加噪声则可以让模型更加学习到图片的特征,并且随机噪声增加了模型生成时的多样性,而一步一步来可以控制这一过程,同时提高了去噪过程中的稳定性
-
-
每一步需要添加多少噪声呢?
-
根据 schedule 算法来决定,不同的 schedule 有不同的方法
-
一般来说,由Schedule算法进行统筹控制,所以扩散过程是固定的,那么基于初始数据和任意的扩散步数可以采样得到对应的数据 𝑋𝑖
反向扩散
-
扩散模型的反向扩散过程和前向扩散过程正好相反,是一个在图像上不断去噪的过程。下面是一个直观的例子,将随机高斯噪声矩阵通过扩散模型的反向扩散过程,预测噪声并逐步去噪,最后生成一个清晰的图片
-
训练目标:将扩散模型每次预测出的噪声和每次实际加入的噪声做回归,让扩散模型能够准确的预测出每次实际加入的真实噪声
-
举例说明:例如从训练数据里拿一张图片,在里面添加一定量的噪声,假设添加步数等于 50 的时候的噪声,输入到 U-Net 网络,通过输入图片和步数 50,可以预测出图片中的噪声(U-Net 结构后面会详细阐述),然后只需要把有输入的图片减去噪声就能得到原图
-
但是当噪声很多的时候,网络并无法预测精准的图片细节,只能预测一个模糊的图片轮廓。把这个图片轮廓当作原图,添加比之前少一点的噪声进去,然后再预测它的噪声,这样不断重复,直到得到原图
-
2.2 整体架构
-
整体架构图
-
Stable Diffusion大致由三个模型组成,首先是 CLIP 模型,文本编码器将文字转化为向量作为输入,然后扩散模型用来生成图片,它是在图片压缩降维后的潜在空间进行(扩散模型的输入和输出都是潜在空间的图像特征),最后是一个 VAE 模型,把潜在空间中的图像特征还原成图片
-
潜在空间源于潜在扩散模型,是扩散模型的一种变体,最大的区别在于它先把图片压缩,降低维度,压缩后所在的空间就叫潜在空间,这么做可以大幅度减少计算量,例如原图是 512 * 512 ,压缩成 64* 64 ,减少了很多计算量,但是压缩以后再还原,会损失掉一些细节
-
在 FP16 精度下 Stable Diffusion 模型大小 2G(FP32:4G),其中U-Net大小 1.6G,VAE模型大小 160M 以及 CLIP Text Encoder 模型大小235M(约 123M 参数),其中 U-Net 结构包含约860M参数,FP32精度下大小为3.4G左右
-
-
如何把文字的内容加进去,引导图片生成我们想要的内容?
-
首先先把文字用 CLIP 文本编码器转成文本特征,注意 CLIP 模型只能用英文作为输入,不能用中文,把文本特征加入到 U-Net 的输入当中,为了加入文本特征,U-Net 里添加了 Attention,然后预测它的噪声,但是如果只按之前的做法,最后生成的图片只是有点像我们的文本输入,并不能得到精确描述文本内容的图片
-
所以用到 classifier free guidance 的方法加强引导,首先预测两个特征,一个有文本特征引导,一个没有,然后两者相减,得到的就是在文本引导下改变了的不同的地方,然后把改变了的信号放大,假设放大 7.5 倍,然后加上没有文本特征引导的噪声,这就得到了一个加强了文本引导的噪声,这个放大的数值就是一个 guidance scale,这个数值通常在6 7 左右,这里可以看一下不同数值最后出来的图片效果,数值越大,生成的图片越精细。
用 classifier free guidance 的方法加强引导,guidance scale = 7.5:
不同 guidance scale 最后的图片效果:
-
Stable Diffusion 整体结构
-
首先使用 CLIP Text Encoder 模型作为 SD 模型中的前置模块,将输入的文本信息进行编码,生成与文本信息对应的 Text Embeddings 特征矩阵,再将 Text Embeddings 用于SD模型中来控制图像的生成
-
中间“图像优化模块”由一个 U-Net 网络和一个 Schedule算法 共同组成,U-Net网络负责预测噪声,不断优化生成过程,在预测噪声的同时不断注入文本语义信息。而schedule算法对每次U-Net预测的噪声进行优化处理(动态调整预测的噪声,控制U-Net预测噪声的强度),从而统筹生成过程的进度(在SD中,U-Net 的迭代优化步数(Timesteps)大概是 50 或者 100 次,在这个过程中潜在空间中特征的质量不断的变好(纯噪声减少,图像语义信息增加,文本语义信息增加))
-
U-Net 网络和 Schedule 算法的工作完成以后,SD 模型会将优化迭代后的特征输入到图像解码器(VAE Decoder)中,重建成图像(训练过程简单而言就是从训练集中选取一张加噪过的图片和 timestep,然后将其输入到U-Net中,让U-Net预测噪声,接着再计算预测噪声与真实噪声的误差(loss),最后通过反向传播更新U-Net的权重参数)
-
训练完以后生成图片只需要下半部分的反向扩散过程,用 U-Net 对噪声图片进行去噪,生成图片
-
将 U-Net + Schedule 算法的迭代去噪过程的每一步结果都用图像解码器进行重建,可以直观的感受到从纯噪声到有效图片的全过程:(这里可以看一下不断预测噪声的整个过程,这里的噪声看着和之前黑白的噪声不一样,是因为这个是从潜在空间还原出来的噪声)
-
SD模型文生图和图生图的区别:
2.3 U-Net 架构
U-Net 是从噪音中生成图像的主要组件,在预测过程中,通过反复调用 U-Net ,将 U-Net 预测输出的噪声图片从原有的噪声中去除,得到逐步去噪后的图像。在 Stable Diffusion 中,U-Net 可以用于优化潜在空间的表示,以提高图像生成的效率和质量,它通过学习从潜在空间到图像空间的映射关系,将低维的潜在表示解码为高维的图像空间,从而实现高质量的图像生成
传统 U-Net 架构
-
U-Net 最初设计用于医学图像分割,特点是一种对称的编码器-解码器结构,中间通过跳跃连接直接传递特征图,这种结构能够在图像的不同层次中保留丰富的细节信息(从下面的网络结构图来看,就知道为什么是U-Net了,网络结构就是一个U型,经典的编码器解码器结构,左边部分是编码器,右边部分是解码器,中间灰色的箭头线就是跳跃连接(Skip Connection)部分,其中,蓝紫箭头:3×3卷积核做卷积,再用ReLU激活函数输出特征通道;灰色箭头:进行裁剪和复制;红色箭头:2×2池化核进行最大池化/下采样;绿色箭头:2×2卷积核做上采样;青蓝箭头:1×1卷积核做卷积)
-
特征提取与下采样
-
在 U-Net 的编码器部分,输入图像首先经过一系列卷积层和池化层进行处理,目的是提取图像的特征并逐渐降低图像的空间维度
- 最左上角处理:输入图片大小为 572×572 ,经过 64 个 3×3 的卷积核进行卷积,再通过 ReLU 函数后得到 64 个 570×570 的特征通道。把这 570 × 570 × 64 的结果再经过 64 个 3×3 的卷积核进行卷积,同样通过 ReLU 函数后得到 64 个 568×568 的特征提取结果。这就是第一层的处理,然后通过2×2的池化核,对图片下采样为原来大小的一半284×284×64。把284×284×64的结果,经过128个3×3×64的卷积核进行卷积,得到282×282×128(这部分实际上是对284×284×64全部做128个卷积)
-
-
上采样和特征融合
-
在 U-Net 的解码器部分,通过上采样操作逐步恢复图像的尺寸,这一过程不仅仅是简单地增加图像的尺寸,更重要的是恢复图像的细节信息
-
最右上角处理:392×392×128白蓝框来源于两个部分,蓝色部分是下面一层上采样得到的;白色部分是同一层编码器复制裁剪得到的,左边568×568×64结果复制再裁剪中间部分,得到392×392×64,加上上采样的64合起来,得到128维,可以看到每一层都有这个复制裁剪的部分,那么,为什么需要这个?
-
-
为什么需要跳跃连接?
-
跳跃连接的主要作用是将编码器阶段捕获的高级别、全局特征与解码器阶段的局部、细节特征结合起来。这种结合帮助模型在恢复图像尺寸的同时,也能够精确地恢复图像的细节和结构,这对于图像分割和生成任务至关重要(跳跃连接的主要作用是结合编码器部分的浅层信息和解码器部分的深层信息,深层信息就是经过多次下采样后的低分辨率信息,浅层信息通过跳跃连接从编码器传递到解码器上的高分辨率信息,为网络提供更加精细的特征)
-
通过这种方式,U-Net 能够有效地处理和生成高质量的图像,不仅保留了图像的全局信息,也精确地恢复了局部细节
-
Stable Diffusion 中的 U-Net
Stable Diffusion 中的 U-Net ,在传统深度学习时代的 Encoder-Decoder 结构的基础上,增加了 ResNetBlock(包含 Time Embedding)模块,Spatial Transformer( SelfAttention + CrossAttention + FeedForward)模块以及CrossAttnDownBlock,CrossAttnUpBlock和CrossAttnMidBlock模块
上图中包含 Stable Diffusion U-Net 的十四个基本模块:
-
GSC 模块:Stable Diffusion U-Net 中的最小组件之一,由 GroupNorm + SiLU + Conv 三者组成
-
在生成式模型中,GroupNorm的效果一般会比BatchNorm更好,生成式模型通常比较复杂,因此需要更稳定和适应性强的归一化方法
-
通常需要使用不同的Batch-Size进行训练和微调,这会导致 BatchNorm在训练期间的不稳定性,而GroupNorm不受Batch-Size的影响
-
GroupNorm 是一种基于通道分组的归一化方法,更适应通道数的变化,而不需要大量调整
-
GroupNorm 能够更好地适应不同的数据分布,因为它不像 Batch Normalization那样依赖于整个批量的统计信息
-
-
DownSample 模块:Stable Diffusion U-Net 中的下采样组件,使用了Conv(kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))进行采下采样
-
UpSample 模块:Stable Diffusion U-Net 中的上采样组件,由插值算法(nearest)+ Conv 组成
-
ResNetBlock 模块:借鉴ResNet模型的“残差结构”,让网络能够构建的更深的同时,将 Time Embedding 信息嵌入模型
-
Time Embedding 正是输入到 ResNetBlock 模块中,为 U-Net 引入了时间信息(时间步长T,T的大小代表了噪声扰动的强度),模拟一个随时间变化不断增加不同强度噪声扰动的过程,让 SD 模型能够更好地理解时间相关性
-
在SD模型调用U-Net重复迭代去噪的过程中,希望在迭代的早期,能够生成整幅图片的轮廓与边缘特征,随着迭代的深入,再补充生成图片的高频和细节特征信息。由于在每个ResNetBlock模块中都有Time Embedding,就能告诉U-Net现在是整个迭代过程的哪一步,并及时控制U-Net够根据不同的输入特征和迭代阶段而预测不同的噪声残差
-
-
CrossAttention 模块:将文本的语义信息与图像的语义信息进行 Attention 机制,增强输入文本 Prompt 对生成图片的控制
-
Context Embedding用来生成 K 和 V,Latent Feature 用来生成 Q
-
在Stable Diffusion中,主要的目的是想把文本信息注入到图像信息中里,所以用图片token对文本信息做 Attention实现逐步的文本特征提取和耦合
-
-
SelfAttention 模块:SelfAttention 模块的整体结构与 CrossAttention 模块相同,输入全部都是图像信息,不再输入文本信息
-
输入只有图像信息,所以SelfAttention主要是为了让SD模型更好的学习图像数据的整体特征
-
SelfAttention还能减少平移不变性问题,SelfAttention模块可以在不考虑位置的情况下捕捉特征之间的关系
-
-
FeedForward 模块:Attention 机制中的经典模块,由 GeGlU+Dropout+Linear 组成
-
BasicTransformer Block模块是在CrossAttention子模块的基础上,增加了SelfAttention子模块和Feedforward子模块共同组成的
-
每个子模块都是一个残差结构,能让文本的语义信息与图像的语义信息更好的融合,还能通过SelfAttention机制让模型更好的学习图像的特征
-
-
BasicTransformer Block 模块:由 LayerNorm+SelfAttention+CrossAttention+FeedForward 组成,是多重 Attention 机制的级联,并且也借鉴ResNet 模型的“残差结构”,通过加深网络和多Attention机制,大幅增强模型的学习能力与图文的匹配能力
-
Spatial Transformer 模块:由 GroupNorm+Conv+BasicTransformer Block+Conv 构成
-
Spatial Transformer模块和ResNetBlock模块一样接受两个输入:一个是ResNetBlock模块的输出,另外一个是输入文本Prompt经过CLIP Text Encoder模型编码后的Context Embedding
-
两个输入首先经过Attention机制(将Context Embedding对应的语义信息与图片中对应的语义信息相耦合),输出新的Latent Feature,再将新输出的Latent Feature与输入的Context Embedding再做一次Attention机制,从而使得SD模型学习到了文本与图片之间的特征对应关系
-
Spatial Transformer模块不改变输入输出的尺寸,只在图片对应的位置上融合了语义信息
-
-
DownBlock 模块:由两个 ResNetBlock 模块组成
-
UpBlock_X 模块:由 X 个 ResNetBlock 模块和一个 UpSample 模块组成
-
CrossAttnDownBlock_X 模块:是 Stable Diffusion U-Net 中 Encoder 部分的主要模块,由 X 个(ResNetBlock模块+Spatial Transformer模块)+DownSample模块组成
-
CrossAttnUpBlock_X 模块:是Stable Diffusion U-Net中Decoder部分的主要模块,由X个(ResNetBlock模块+Spatial Transformer模块)+UpSample模块组成
-
CrossAttnMidBlock 模块:是Stable Diffusion U-Net中Encoder和ecoder连接的部分,由ResNetBlock+Spatial Transformer+ResNetBlock组成
【从整体上看,不管是在训练过程还是前向推理过程,Stable Diffusion中的U-Net在每次循环迭代中Content Embedding部分始终保持不变,而Time Embedding每次都会发生变化。和传统深度学习时代的U-Net一样,Stable Diffusion中的U-Net也是不限制输入图片的尺寸,因为这是个基于Transformer和卷积的模型结构】
U-Net 主要在“扩散”循环中对高斯噪声矩阵进行迭代降噪,并且每次预测的噪声都由文本和timesteps进行引导,将预测的噪声在随机高斯噪声矩阵上去除,最终将随机高斯噪声矩阵转换成图片的隐特征。
【在U-Net执行“扩散”循环的过程中,Content Embedding 始终保持不变,而 Time Embedding 每次都会发生变化,每次 U-Net 预测的噪声都在 Latent 特征中减去,并且将迭代后的 Latent 作为 U-Net 的新输入】
U-Net 在 SD 中的作用小结:
-
细节的捕捉与增强:Stable Diffusion利用U-Net的跳跃连接来维持和增强图像的细节,这些连接允许在生成过程中直接使用来自编码器的高分辨率特征,从而在解码器阶段细化图像的细节
-
多尺度特征融合:通过U-Net的编码器-解码器结构,Stable Diffusion能够融合不同尺度的特征,这对于生成与文本描述相匹配的复杂图像至关重要。这种结构使模型能够在保持全局一致性的同时,精确控制图像的局部细节
-
迭代细化:Stable Diffusion在图像生成过程中采用迭代细化的策略,每一步都利用U-Net架构对图像进行进一步的优化和细化。这种方式使得最终生成的图像不仅细节丰富,而且与输入的文本描述高度一致
2.4 ControlNet
基本原理
-
ControlNet 是什么?
-
固定构图、定义姿势、描绘轮廓、单凭线稿就能生成一张丰满精致的插画!
-
在 ControlNet 之前,基于扩散模型的 AI 绘画非常难以控制,扩散图片的过程充满了随机性,如果不能做到精确的控制,只能反复尝试得到想要的东西,ControlNet 简单来说是对大扩散模型做微调的额外网络
-
-
ControlNet 的核心作用是基于一些额外的输入给它的信息来给扩散模型的生成提供明确的指引
-
例如:输入一个提示词“跳舞”,那画面中的人物、角色可能会有无数种舞蹈姿势,而 ControlNet 的精髓就在于给它输入一张记录了某种姿势的信息的图片来指导它做图,这张图片上各种不同颜色的点、线,代表的就是人物的五官、四肢、关节,用大量图片数据训练出来的 ControlNet 能读懂图上的这些东西,知道你想要的是哪种姿势,然后输出对应的结果
-
这个和图生图有点像,本质上都是通过一些方式给模型提供额外的信息,但是 ControlNet 记录的信息比图生图更为纯粹,排除了图片本身元素,例如上面已经有的颜色和线条的影响,只是单纯输入姿势这一个点的信息,就不会对输入的提示词构成太多影响,是精准控制里很重要的一个点
-
网络结构
-
ControlNet 是一种“辅助式”的神经网络结构,通过在 Stable Diffusion 模型中添加辅助模块,从而引入“额外条件”来控制AI绘画的生成过程
-
ControlNet 模型的最小单元
-
使用 ControlNet 模型之后,Stable Diffusion 模型的权重被复制出两个相同的部分,分别是“锁定”副本(locked)和“可训练”副本
-
ControlNet 主要在“可训练”副本上施加控制条件,然后将施加控制条件之后的结果和原来SD模型的结果相加(addd)获得最终的输出结果
-
其中“锁定”副本中的权重保持不变,保留了 Stable Diffusion 模型原本的能力;与此同时,使用额外数据对“可训练”副本进行微调,学习想要添加的条件,因为有Stable Diffusion模型作为预训练权重,所以使用小批量数据集就能对控制条件进行学习训练,同时不会破坏Stable Diffusion模型原本的能力
-
另外,ControlNet 模型的最小单元结构中有两个 zero convolution 模块,它们是 1×1 卷积,并且权重和偏置都初始化为零。这样一来,在开始训练 ControlNet 之前,所有 zero convolution 模块的输出都为零,使得 ControlNet 完完全全就在原有 Stable Diffusion 底模型的能力上进行微调训练,不会产生大的能力偏差
-
如果zero convolution模块的初始权重为零,那么梯度也为零,ControlNet模型将不会学到任何东西。那么为什么“zero convolution模块”有效呢?
-
假设 ControlNet 的权重为:
,梯度求导:
-
-
这意味着只要
,一次梯度下降迭代后 w 变成非零值,这样就能让 zero convolution 模块成为具有非零权重的卷积层,不断优化参数
-
由于训练开始前 zero convolution 模块的输出都为零,所以 ControlNet 未经训练的时候输出为 0(这种情况下对SD模型是没有任何影响的,就能确保SD模型原本的性能完整保存,之后ControlNet训练也只是在原SD模型基础上进行优化。ControlNet模型思想使得训练的模型鲁棒性好,能够避免模型过度拟合,并在针对特定问题时具有良好的泛化性,在小规模甚至个人设备上进行训练成为可能)
-
ControlNet 模型完整网络结构
-
从 ControlNet 整体的模型结构上可以看出,其主要在 Stable Diffusion 的 U-Net 中起作用,ControlNet主要将Stable Diffusion U-Net的Encoder部分和Middle部分进行复制训练,在Stable Diffusion U-Net的Decoder模块中通过skip connection加入了zero convolution模块处理后的特征,以实现对最终模型与训练数据的一致性
-
由于 ControlNet 训练与使用方法是与原始的 Stable Diffusion U-Net 模型进行连接,并且 Stable Diffusion U-Net 模型权重是固定的不需要进行梯度计算,这种设计思想减少了 ControlNet 在训练中一半的计算量,在计算上非常高效,能够加速训练过程并减少GPU显存的使用。在单个Nvidia A100 PCIE 40G的环境下,实际应用到Stable Diffusion模型的训练中,ControlNet仅使得每次迭代所需的GPU显存增加大约23%,时间增加34%左右。同时由于Stable Diffusion U-Net是经典的U-Net结构,因此 ControlNet 架构有很强的兼容性与迁移能力
- 具体地,ControlNet 包含了 12 个编码块和 1 个 Stable Diffusion U-Net 中间块的“可训练”副本。这 12 个编码块有 4 种分辨率,分别是64×64、32×32、16×16 和 8×8,每种分辨率对应 3 个编码块。ControlNet 的输出被添加到 Stable Diffusion U-Net 的 12 个残差结构和 1 个中间块中
-
ControlNet的输入包括Latent特征、Time Embedding、Text Embedding以及额外的condition。其中前三个和 SD 的输入是一致的,而额外的condition 是 ControlNet 独有的输入,额外的 condition 是和输入图片一样大小的图像,比如边缘检测图、深度信息图、轮廓图等
-
ControlNet 一开始的输入 Condition 怎么与 SD 隐空间模型的特征结合呢?
-
在这里 ControlNet 主要是训练过程中添加了四层卷积层,将图像空间 Condition 转化为隐空间 Condition。这些卷积层的卷积核为4×4,步长为2,通道分别为16,32,64,128,初始化为高斯权重,并与整个 ControlNet 模型进行联合训练
- 在训练 ControlNet 模型的过程中,Stable Diffusion 底模型权重不更新,只更新 ControlNet 模型权重
小结:
-
ControlNet:通过训练另外一个网络去调整 U-Net ,这个网络可以输入一些用来作为控制条件的图像
-
其他微调模型训练方法
-
DreamBooth:直接用新的图片和关键字去调整 U- Net 里的参数,保存的时候要保存整个修改过的模型,文件很大
-
LoRA:在 U-Net 里加一些层,去训练这些层去调整 U-Net 的输出,保存的时候只需要保存这些层,文件很小,使用的时候需要使用基础模型
-
Textual Inversion:调整 clip 让它符合新图片的文本特征,文件更小,只需要保存学习到的特征
-
style="display: none !important;">