提示:本人还在学习当中,所作笔记仅供大家参考,若有问题欢迎大家指出!
Transformer
注意力机制
定这样一个场景:把输入信息向量k-v看做是一个信息存储器(k-v,即一些键值对),现在给定一个查询向量q(目标字),用来查找井选择k-v中的某些信息(即根据q有偏重的从k-v中选取出某些东西).那么我们需要计算q对于每个k-v的偏重(注意力分数),那么我们使用一个打分函数来计算这个偏重。根据打分函数不同可以得到不同的注意力机制,比如点积,可加性注意力等等
Transformer
Transformer的编码器由6个Encoder组成,解码器由6个Decoder组成,输入数据经由6个Encoder编码后,输入到6个Decoder中完成解码,产生最终输出结果。
总模型,其中一些跳跃的箭头指残差连接,feed forward就是一个mlp
下面解释一下上面的图 左边部分是编码器,右边部分是解码器
transformer和rnn在处理时序信息的不同点在于,transformer是同时拿到所有时刻的信息,然后在输出时用掩码机制使得它看不到当前时刻后面的信息,而rnn是每次的输入是上时刻的输出和这时刻的输入,即每次记录上时刻的信息。
掩码注意力机制就是对于当前时刻后面的信息,将他全部设为一个非常小的负数,这样再SoftMax后,这些值都会变成0
在机器翻译中,编码器是将一个长为n的句子转换为也是长为n的序列,但其中每个值是对应每个词的一个向量表示。解码器是将编码器的输出加工成一个长为m的向量,解码器与编码器的不同在于编码器是一个词一个词的运算输出,解码器则不需要,生成的序列长度也是不定的(翻译的结果长度是不定的 )
对transformer的评价:
为什么现在基于transformer的模型都特别大特别贵,因为transformer需要更大的模型更多的数据去训练才能达到和cnn.rnn差不多的效果,这是为什么呢?能改进吗?
因为它是多模态的,对不同的任务都能抓取其中的特征信息,抓去的信息太广泛这样
transformer缺点,自注意力是对所有数据作加权和,没必要对所有数据加权和
然后矩阵乘法计算量挺大
为什么能打赢RNN,它不会对数据顺序建模
RNN可以显示的建模序列信息
因为用了一个跟广泛的归纳偏置,使得其能处理更一般化的信息,这就是为什么transformer更贵,模型更大的原因
rnn的缺点是每时刻都需要上一时刻的输出作为输入,无法并行。
因为需要记住前面时刻的时序信息,在句子比较长时,内存开销大
有许多改进方法,但本质问题没有解决
各种注意力机制
自注意:将x用不同的线性变换得到key,value和query来对序列抽取特征,优点•完全并行、最长序列为1、但对长序列计算复杂度高
交叉注意力:输入有x,y。K,V由x线性变换得到,Q由y线性变换得到
Transformer中的自注意力机制:见下面的左图。q,k,v都是相等的向量,都是输入向量
输出是value的加权和,每个value权重是这个value对应的每个key和query的相似度来计算的。 即相识度高的,权重会比较大
在transformer中,这个相似度的计算公式是对每一个query和key做内积,值越大则相识度越高,最后对于内积值再除以根号d,d是key的长度,最后在套上个softmax得到权重(缩放点积打分函数)
之所以除于d是因为softmax函数对于大值会输出更靠近1,而小值会靠近于0,这是因为softmax是分类运算,对于置信度大的我们希望它更靠近于1,这样就完成了分类,梯度就会渐渐收,即梯度比较小了。但在transformer的注意力中这样不利于梯度计算,因为梯度越大就越还没有到收的地步
多头注意力机制的基本思想是类似于多通道的卷积。看下面的右图
掩码的多头自注意力:
position encoding则是将这个词的位置加入到下一个计算的输入内,从而获得时序信息
即用长为512的一个向量来表示位置值,这个512是因为前面输入的长度为512,保持一致
这个向量的计算是通过一个cos,sin函数计算出来的,那么他的值会在-1到1间,和前面的输入的数据的大小差不多。 输出则就是把这个记录位置信息的向量和前面输入的向量相加即可
下面是代码是代码实现
B, N, C = x.shape
qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, self.head_dim).permute(2, 0, 3, 1, 4)#self.qkv是一个linear,输出维度由C到C*3
q, k, v = qkv.unbind(0)#输出B* self.num_heads* N*self.head_dim
q, k = self.q_norm(q), self.k_norm(k)
if self.fused_attn:
x = F.scaled_dot_product_attention(
q, k, v,
dropout_p=self.attn_drop.p if self.training else 0.,
)#B* self.num_heads* N*self.head_dim
else:#实现scaled_dot_product_attention
q = q * self.scale
attn = q @ k.transpose(-2, -1)
attn = attn.softmax(dim=-1)
attn = self.attn_drop(attn)
x = attn @ v
x = x.transpose(1, 2).reshape(B, N, C)
x = self.proj(x)#B, N, C proj是LINEAR
x = self.proj_drop(x)#
打分函数
缩放点积打分函数:q转置*k/根号d
tanh是一个激活函数,也可以用其他激活函数
若使用加性注意力,上面else部分可以这样写
class AdditiveAttentionScore(nn.Module):
def __init__(self, query_dim, key_dim, hidden_dim):
super(AdditiveAttentionScore, self).__init__()
# 假设query_dim和key_dim是相同的,这里简单将它们拼接起来
# 在实际中,你可能需要根据具体情况调整网络结构
self.fc1 = nn.Linear(query_dim + key_dim, hidden_dim)
self.fc2 = nn.Linear(hidden_dim, 1) # 输出维度为1,因为我们需要一个分数
def forward(self, query, key):
# 假设query和key的最后一个维度分别是query_dim和key_dim
# 并且它们的形状都是 (B * num_heads, N, dim)
# 我们需要将它们拼接起来
# 注意:这里需要确保query和key的N维度是匹配的,即序列长度相同
combined = torch.cat([query, key], dim=-1)
x = self.fc1(combined)
x = F.relu(x)
scores = self.fc2(x).squeeze(-1) # 挤压最后一个维度,得到形状为 (B * num_heads, N, N) 的分数
return scores
# ... (其他代码保持不变)
# 然后在原来的代码中替换else部分
else:
# 首先,我们需要将q和k的维度调整到可以计算加性注意力分数的形式
# 假设q和k的形状现在是 (B * num_heads, N, head_dim)
# 我们需要将它们重塑为 (B * num_heads, N, head_dim) 以便进行后续操作
# 但实际上,它们的形状已经是这样了,所以我们不需要进一步调整
# 计算加性注意力分数
attn_scores = self.additive_attn_score(q, k) # 假设self.additive_attn_score是AdditiveAttentionScore的实例
# 应用softmax得到注意力权重
attn_weights = attn_scores.softmax(dim=-1)
# 应用dropout(如果配置了的话)
attn_weights = self.attn_drop(attn_weights)
# 应用注意力权重到v
x = attn_weights @ v
MAE
mae:是bert和vit的结合版,用于修复还原图片文本这样
将一张图片分块后按随机遮住一部分图像块,将未遮住的部分放入编码器训练,将训练的结果加上一些空(即原来遮住的地方),放入解码器生成一张完整的图片
encoder是较大的vit,decoder是由较小的vit块构成,
covmae(mcmae)
在mae的基础是加入了特征金字塔和局部归纳偏置。
具体看下图:transformer encoder前有两个卷积网络,用于提取不同尺度的特征C1,C2,transformer encoder处理C2获得C3,C1,C2,C3分别经过mask后拼接一起进入decoder。因为卷积后面的感受野比较大,如果在C1使用随机掩码,则C2会看到C1被掩码的一些信息,所以不能使用随机掩码。作者的处理是在C3使用随机掩码,然后对C3的掩码部分分别放大2被。4倍得到C1,C2的掩码。掩码方式为对于224224,先patch embed成C56*56,然后将掩码区域设置为0然后输入到卷积中
E1E2E3加起来是decoder的输入。尺寸不同怎么相加?上采样即可
GAN-MAE:看下图即可,GAN-MAE的效果比mae好,只用200epoch就能达到比mae1600epoch的效果好,代码没有开源
MAGE:MAGE 首先使用 VQGAN [3] 编码器将原始图像转换为离散的语义符。之后,MAGE 对其进行随机掩码,并使用基于 transformer 的 encoder-decoder 结构对掩码进行重构,重构后的语义符可以通过 VQGAN 解码器生成原始图像。通过在训练中使用不同的掩码率,MAGE 可以同时进行生成模型(接近 100% 掩码率)和表征学习(50%-80% 掩码率)的训练。
CAE:CAE(Context AutoEncoder)
按顺序由上下两个部分组成,
1.可见块输入Encoder获得特征EL。一个模块R,由交叉注意力组成,接收mask token和EL作为输入,输出PL
PL输入Decoder,输出L 。
2.一个动量更新的Encoder接收被掩码的图像作为输入,输出EDL(与Encoder共参数)
loss=L与原始图片做CEloss+PL和EDL做MSEloss * a(系数)
Decoder的目标是重建被掩蔽的像素。而动量更新的encoder的输出和R的输出做对齐任务
总的来说就是可见块输入encoder后加上mask token后输入R,然后将输出Z喂入decoder重建原始图像
Z和动量encoder做celoss,(对齐任务或者说预测任务,从可见补丁预测掩蔽补丁)
videomae灵感来源于imagemae
视频相邻帧的相关性极强,使用遮掩再预测时可能出现信息泄露。(那么我们遮掩相邻帧重复的区域?)
视频时间冗余极强所以采用90%掩码使得性能提升
videomae2将原来的videomae的数据规模扩大,并使用了双掩码机制,不仅在编码器上掩码,而且在解码器上掩码
下采样,即每隔几帧采一帧,时空块嵌入就是将时空块用向量表示,encoder要大一些
其他
mocov3中提到,训练时冻结patchembed可以提高稳定性
class token:而是引入一个类似flag的class token,其输出特征加上一个线性分类器就可以实现分类
第n+1个token(class embedding)的主要特点是:(1)不基于图像内容;(2)位置编码固定。这样做有以下好处:1、该token随机初始化,并随着网络的训练不断更新,它能够编码整个数据集的统计特性;2、该token对所有其他token上的信息做汇聚(全局特征聚合),并且由于它本身不基于图像内容,因此可以避免对sequence中某个特定token的偏向性;3、对该token使用固定的位置编码能够避免输出受到位置编码的干扰。ViT中作者将class embedding视为sequence的头部而非尾部,即位置为0。这样即使sequence的长度n发生变化,class embedding的位置编码依然是固定的,因此,更准确的来说class embedding应该是第0个而非第n+1个token。另外题主说的“将前n个token做平均作为要分类的特征是否可行呢”,这也是一种全局特征聚合的方式,但它相较于采用attention机制来做全局特征聚合而言表达能力较弱。因为采用attention机制来做特征聚合,能够根据query和key之间的关系来自适应地调整特征聚合的权重,而采用求平均的方式则是对所有的key给了相同的权重,这限制了模型的表达能力。
BERT的全称,Bidirectional Encoder Representation from Transformer(来自 Transformer的双向编码器表征),可以看出BERT是基于Transformer模型的,但是只是其中的编码器。相当于只有编码器的transformer
bert的初始任务是用于完型填空,其实也是生成类模型
基于微调的NLP模型,预训练的模型抽取了足够多的信息,新的任务只需要增加一个简单的输出层
在Bert中用的是基于微调的预训练,第一次在没有标号的数据中进行预训练,然后将得到的参数用于正式训练,即初始化参数为前面得到的参数。两次训练的模型是一样的,第二次用的是有标号的数据
Bert中提出了一个特殊的分类字符cls,用于代表整个序列做最后的分类操作。这个cls类似于进入分类层前的池化操作。
因为输出维度是多维的,在进入分类层前要做池化操作降维,降到一维,但如果使用平均池化,则对所有权重取平均,不利于自注意力的表达,因为自注意力是根据相似度取不同权重的,所以用一个可学习的cls token来达到一个对所有降维的操作,并且可以获得不同的权重
bert的输入是两个句子,它将两个句子拼接起来。cls+句子A+分隔符+句子b+分隔符
如果 没有句子b则就是cls+句子A+分隔符
输入数据的处理为embed+pos+seq(embed+词位置信息+段落位置信息(0/1区分每个词是第一段还是第二段))
VIT visual transformer 将transformer应用到visual领域的一个最大的问题就是,transformer的输入是一维向量,而图片是二维向量,如果直接把图片按行拉成一维,则序列长度太大,计算复杂的大大增加,像Bert的输入也就512这样,而一张800x800的图片则就长度就是64万。
VIT的解决办法就是将图片按批量输入,取其中大小为1616为一个批量,这样输入就是196,是可以接受的
VIT这篇论文的主要贡献就是多模态的未来,可以将transformer应用到几乎任何领域
Swin transformer的一个思想也是解决transformer中输入的问题,在vit中的输入是将图片分为一小个patch,将每个patch放入模型中,而swin transformer则模仿卷积的窗口平移操作来输入数据,用一个滑动窗口来输入?
其实就是像下图,将四块分为9块,九块中的的一些块包括四块中多个块的信息,九块是怎么得到的呢?其实就是四块往右下角移动一下,将原来四块就切成了九块,然后先对四块计算,再对九块计算,所以一个block是有两个小块,即下面第二图最右边的图
patch merging操作就是从二维序列中按位置去多个点组成多个小块,即把一个二维序列重组,如下图
因为将四块分成了九块,计算复杂度增加了2.25倍,我们可以用循环移位方法使得对九块的计算还是计算四块的复杂度。但这样的问题是移位后移过来的部分是不应该做自注意力计算的,因为位置关系发生了变化,解决方法就是用掩码方式解决
GPT 用的是transformer的解码器
DETR
force work:前面的目标检测或多或少都用了许多锚框,在最后加一个nms操作,用锚框计算复杂度大,用nms则不好调参,detr则解决上面问题,即不用锚框,也不用nms,而是使用transformer
对于视频中每一帧,在这张图片上使用卷积及在这帧前后
使用transformer生成许多框,再在这些框中挑一个最合适的框给每个图片中的物体(新的目标函数二分图匹配)
二分图匹配算法解决的问题:就是n个工人去完成m个任务,怎么分配工人使得花费最少
在这里我们假设decoder生成的框就是n个工人,要从中挑选的框就是m个任务。里面的花费就是loss,这样就是一个nm的矩阵找最优解。这个找最优解的算法是匈牙利算法,有对应的函数
object queries是一个可学习的二维向量,使得最后的输出是n维,FFN其实是mlp
VLIT 就是对比学习和VIT的结合,将文本和图片分别输入模型中。提出了文本图片对的数据增强方法
pooler就是一个权重矩阵
小技巧1:对文本做一个mask,即对整个单词盖住,因为对于一些单词,如果只遮住一部分,很容易就能猜到完整的单词
group VIT:64*38是一个group token ,类似于一个cls
grouping block 其实是一个聚类中心的分配,将image token分配到group token中
Vild
左边是模型ViLD-image
GLIP:将上面的整个过程融合成一个模型