详解Transformer 中Self-Attention 以及Multi-HeadAttention 最近Transformer在CV领域很⽕,Transformer是2017年Google在Computation and Language 上发表的,当时主要是针对⾃然语⾔处理领域提出的(之前的RNN模型记忆长度有限且⽆法并⾏化,只有计算完时刻后的数据才能计算时刻的数据,但Transformer都可以做到)。在这篇⽂章中作者提出了Self-Attention 的概念,然后在此基础上提出Multi-Head Attention ,所以本⽂对Self-Attention 以及Multi-Head Attention 的理论进⾏详细的讲解。在阅读本⽂之前,建议⼤家先去看下李弘毅⽼师讲的Transformer的内容。本⽂的内容是基于李宏毅⽼师讲的内容加上⾃⼰阅读⼀些源码进⾏的总结。
素材解析
⽂章⽬录
前⾔
如果之前你有在⽹上找过lf-attention或者transformer的相关资料,基本上都是贴的原论⽂中的⼏张图以及公式,如下图,讲的都挺抽象的,反正就是看不懂(可能我太菜的原因)。就像李弘毅⽼师课程⾥讲到的"不懂的⼈再怎么看也不会懂的"。那接下来本⽂就结合李弘毅
⽼师课上的内容加上原论⽂的公式来⼀个个进⾏详解。
Self-Attention
下⾯这个图是我⾃⼰画的,为了⽅便⼤家理解,假设输⼊的序列长度为2,输⼊就两个节点,然后通过Input Embedding也就是图中的将输⼊映射到。紧接着分别将分别通过三个变换矩阵(这三个参数是可训练的,是共享的)得到对应的
(这⾥在源码中是直接使⽤全连接层实现的,这⾥为了⽅便理解,忽略偏执)。其中
代表query,后续会去和每⼀个进⾏匹配
代表key,后续会被每个匹配青瓜
代表从中提取得到的信息
后续和匹配的过程可以理解成计算两者的相关性,相关性越⼤对应的权重也就越⼤
t i t i +1x ,x 12f (x )a ,a 12a ,a 12W ,W ,W q k v q ,k ,v i i i q k k q v a q k v
假设那么:
前⾯有说Transformer是可以并⾏化的,所以可以直接写成:
同理我们可以得到和,那么求得的就是原论⽂中的,就是,就是。接着先拿和每个进⾏match,点乘操作,
接着除以
得到对应的,其中代表向量的长度,在本⽰例中等于2,除以的原因在论⽂中的解释是“进⾏点乘后的数值很⼤,导致
通过softmax后梯度变的很⼩”,所以通过除以来进⾏缩放。⽐如计算:同理拿去匹配所有的能得到,统⼀写成矩阵乘法形式:
接着对每⼀⾏即和分别进⾏softmax处理得到和,这⾥的相当于计算得到针对每个的权
重。到这我们就完成了公式中部分。
上⾯已经计算得到,即针对每个的权重,接着进⾏加权得到最终结果:
统⼀写成矩阵乘法形式:
到这,Self-Attention 的内容就讲完了。总结下来就是论⽂中的⼀个公式:
a =1(1,1),a =2(1,0),W =q (0,11,1)q =1(1,1)=(0,11,1)(1,2), q =2(1,0)=(0,11,1
)(1,1)
=(q 2q 1)=(1,01,1)(0,11,1)(1,11,2
)
(k 2k 1)(v 2v 1)(q 2q 1)Q (k 2k 1)K (v 2v 1
)V q 1k d αd k i d d α1,i α=1,1=d q ⋅k 11=21×1+2×0
0.71
α=1,2=d q ⋅k 12=21×0+2×1
1.41
q 2k α2,i =(α α2,12,2α α1,11,2
)d
(q 2q 1)(k 2k 1)T (α,α)1,11,2(α,α)2,12,2(,)α^1,1α^1,2(,)α^2,1α^2,2α^v Attention(Q ,K ,V )softmax()d k QK T
αv b =1×α^1,1v +1×α^1,2v =2(0.33,0.67)
b =2×α^2,1v +1×α^2,2v =2(0.50,0.50)
=(b 2b 1
)( α
^2,1α^2,2 α^1,1α^1,2)(v 2v 1)Attention(Q ,K ,V )=softmax()V
d k QK T
Multi-Head Attention
刚刚已经聊完了Self-Attention模块,接下来再来看看Multi-Head Attention模块,实际使⽤中基本使⽤的还是Multi-Head Attention模块。原论⽂中说使⽤多头注意⼒机制能够联合来⾃不同head部分学习到的信息。Multi-head attention allows the model to jointly attend to information from different reprentation subspaces at different positions.其实只要懂了Self-Attention模块Multi-Head Attention模块就⾮常简单了。
⾸先还是和Self-Attention模块⼀样将分别通过得到对应的,然后再根据使⽤的head的数⽬进⼀步把得到的均分成份。⽐如下图中假设然后拆分成和,那么就属于head1,为什么一直打嗝
属于head2。
看到这⾥,如果读过原论⽂的⼈肯定有疑问,论⽂中不是写的通过映射得到每个head的吗:但我在github上看的⼀些源码中就是简单的进⾏均分,其实也可以将设置成对应值来实现均分,⽐如下图中的Q通过就能得到均分后的
。通过上述⽅法就能得到每个对应的
参数,接下来针对每个head使⽤和Self-Attention中相同的⽅法即可得到对应的结果。
接着将每个head得到的结果进⾏concat拼接,⽐如下图中(得到的)和(得到的)拼接在⼀起,(得到的)和(得到的
)拼接在⼀起。
接着将拼接后的结果通过(可学习的参数)进⾏融合,如下图所⽰,融合后得到最终的结果
。到这,Multi-Head Attention 的内容就讲完了。总结下来就是论⽂中的两个公式:
历史的
Self-Attention 与Multi-Head Attention 计算量对⽐在原论⽂章节3.2.2中最后有说两者的计算量其实差不多。Due to the reduced dimension of each head, the total computational cost is similar to that of single-head attention with full dimensionality.下⾯做了个简单的实验,这个model⽂件⼤家先忽略哪来的。这个Attention 就是实现Multi-head Attention 的⽅法,其中包括上⾯讲的所有步骤。
⾸先创建了⼀个Self-Attention 模块(单头)a1,然后把proj变量置为Identity(Identity对应的是Multi-Head Attention 中最后那个的映射,单头中是没有的,所以置为Identity即不做任何操作)。再创建⼀个Multi-Head Attention 模块(多头)a2,然后设置8个head。
创建⼀个随机变量,注意shape
使⽤fvcore分别计算两个模块的FLOPs
a i W ,W ,W q k v q ,k ,v i i i h q ,k ,v i i i h h =2q 1q 1,1q 1,2q 1,1q 1,2W ,W ,W i Q i K i V
Q ,K ,V i i i head =i Attention(QW ,KW ,V W )i Q i K i V
W ,W ,W i Q i K i V W 1Q
Q 1head i Q ,K ,V i i i Attention(Q ,K ,V )=i i i softmax()V d k Q K i i
T
i b 1,1head 1b 1b 1,2head 2b 1b 2,1head 1b 2b 2,2head 2b 2W O b ,b 12MultiHead(Q ,K ,V )=Concat(head ,...,head )W 1h O
where head =Attention(QW ,KW ,V W )i i Q i K i V
存款利率表
W o
from fvcore .nn import FlopCountAnalysis
from model import Attention
def main ():
# Self-Attention
a1 = Attention (dim =512, num_heads =1)
a1.proj = torch .nn .Identity () # remove Wo
# Multi-Head Attention
a2 = Attention (dim =512, num_heads =8)
# [batch_size, num_tokens, total_embed_dim]
t = (torch .rand (32, 1024, 512),)
flops1 = FlopCountAnalysis (a1, t )
print ("Self-Attention FLOPs:", al ())
flops2 = FlopCountAnalysis (a2, t )
print ("Multi-Head Attention FLOPs:", al ())
if __name__ == '__main__':
main ()终端输出如下, 可以发现确实两者的FLOPs差不多,Multi-Head Attention ⽐Self-Attention 略⾼⼀点:
Self-Attention FLOPs: 60129542144
Multi-Head Attention FLOPs: 68719476736
其实两者FLOPs的差异只是在最后的上,如果把Multi-Head Attentio 的也删除(即把a2的proj也设置成Identity),可以看出两者FLOPs是⼀样的:
浪漫作文Self-Attention FLOPs: 60129542144
Multi-Head Attention FLOPs: 60129542144猴头健胃灵片
Positional Encoding
如果仔细观察刚刚讲的Self-Attention和Multi-Head Attention模块,在计算中是没有考虑到位置信息的。假设在Self-Attention模块中,输⼊得到。对于⽽⾔,和离它都是⼀样近的⽽且没有先后顺序。假设将输⼊的顺序改为,对结果是没有任何影响的。下⾯是使⽤Pytorch做的⼀个实验,⾸先使⽤nn.MultiheadAttention 创建⼀个Self-Attention 模块(num_heads=1),注意这⾥在正向传播过程中直接传⼊,接着创建两个顺序不同的变量t1和t2(主要是将和的顺序换了下),分别将这两个变量输⼊Self-Attention模块进⾏正向传播。
W O W O a ,a ,a 123b ,b ,b 123a 1a 2a 3a ,a ,a 132b 1QKV QKV q ,k ,v 222q ,k ,v 333
import torch .nn as nn
m = nn .MultiheadAttention (embed_dim =2, num_heads =1)
t1 = [[[1., 2.], # q1, k1, v1
[2., 3.], # q2, k2, v2
[3., 4.]]] # q3, k3, v3
t2 = [[[1., 2.], # q1, k1, v1
[3., 4.], # q3, k3, v3
[2., 3.]]] # q2, k2, v2
q , k , v = torch .as_tensor (t1), torch .as_tensor (t1), torch .as_tensor (t1)
print ("result1: \n", m (q , k , v ))
q , k , v = torch .as_tensor (t2), torch .as_tensor (t2), torch .as_tensor (t2)
print ("result2: \n", m (q , k , v ))
对⽐结果可以发现,即使调换了和的顺序,但对于
是没有影响的。为了引⼊位置信息,在原论⽂中引⼊了位置编码positional encodings 。To this end, we add "positional encodings" to the input embeddings at the bottoms of the encoder and decoder stacks.如下图所⽰,位置编码是直接加在输⼊的中的,即和拥有相同的维度⼤⼩。关于位置编码在原论⽂中有提出两种⽅案,⼀种是原论⽂中使⽤的固定编码,即论⽂中给出的sine and cosine functions ⽅法,按照该⽅法可计算出位置编码;另⼀种是可训练的位置编码,作者说尝试了两种⽅法发现结果差不多(但在ViT论⽂中
使⽤的是可训练的位置编码)。
超参对⽐关于Transformer中的⼀些超参数的实验对⽐可以参考原论⽂的表3,如下图所⽰。其中:N表⽰重复堆叠Transformer Block的次数表⽰Multi-Head Self-Attention输⼊输出的token维度(向量长度)表⽰在MLP(feed forward)中隐层的节点个数h表⽰Multi-Head Self-Attention中head的个数表⽰Multi-Head Self-Attention中每个head的key(K)以及query(Q)的维度
表⽰dropout层的drop_rate
到这,关于Self-Attention、Multi-Head Attention以及位置编码的内容就全部讲完了,如果有讲的不对
的地⽅希望⼤家指出。q ,k ,v 222q ,k ,v 333b 1a ={a ,...,a }1n pe ={pe ,...,pe }1n a ={a ,...,a }1n d model d ff d ,d k v P drop
未雨绸缪什么意思