本文主要对论文“Attention is All You Need”的核心架构进行介绍。
整个模型从输入到输出,每一块都进行了讲解。
下图是Transformer的架构:
上图是Attention is All You Need这篇论文中提到的Transformer模型架构。上图左侧代表的是编码器,右侧是解码器。
模型参数数据:
模型的输入维度: d m o d e l d_{model} dmodel = 512
1.模型的输入
这个模型同时有两个输入,我们将分别介绍两个输入代表的什么意思。
举例:将“I am a student”翻译为中文,对应的是“我是一个学生”。
那么
Inputs = “I am a student”
Outputs = “我是一个学生”
Inputs是一次性输入到模型中,而Outputs不是。例如:如果我们要预测“是”的下一个单词,那么Outputs=“我是”;如果我们要预测的是“一个”的下一个单词,那么Outputs=“我是一个”。也就是利用之前输出的单词去预测下一个单词。
之后利用Word2Vec、FastText、Glove或者其他的方法将单词转为嵌入向量。
2.位置编码(Positional Encoding)(内容较多)
对于为什么要使用位置编码,论文中作者也提到了。因为Transformer没有使用可以记录序列位置信息的循环层、卷积层,而是使用的纯Attention结构,而Attention是没有记录序列位置信息的能力的,所以要加上位置编码以记录序列中tokens 的相对或者绝对位置信息。
位置编码的维度跟嵌入向量的的维度是一样的为 $ d_{model} $,目的是为了便于跟“Input Embedding”相加,没错是相加,不是追加。计算过程如下:
关于是应该相加还是追加的问题,有人提出过,tensor2tensor维护人员martinpopel也做出了回应,详细可以参考下面的连接:
Why add positional embedding instead of concatenate?
我总结一下上面的大概意思:
先定义两个缩写词:PE(Positional Encoding/Embeddings) WE(Word Embeddings)
对于为什么使用PE+WE而不是PE追加WE?martinpopel认为在许多工作中,相加要比追加更加高效,并且也举出一些例子:比如将从左到右跟从右到左的LSTM状态相加,又或者将基于字符的嵌入跟基于词的嵌入相加等等。并且由于跳跃连接(skip connection)的存在,使得二者相加后的信息可以在编码器中传播的更远。
其实还有一个问题,大家可能会有疑问,如何分离PE+WE呢?不分开的话,如何使用它的位置信息呢?其实martinpopel对这一点也做出了回应,结合提问者Akella回答,可以做出下面的解释:就是大家可能认为将位置编码跟Embeddings分开是一种优势(就是一般感觉分开后更容易利用位置信息,加到WE中,反而会有一种PE信息消失的感觉),实际上需要知道的是,WE是一个可学习的参数,或许Transformer同样可以从PE+WE中学习到有用的特征。
实际上我个人感觉上面的回应都是直觉上的感觉,martinpopel也没有提出实验进行论证,比如实验对比相加和追加的效果。
深度学习就像是一个黑盒子,或许设计者也不知道它从中学习到了什么,凭借合理的假设对模型进行推演,如果实验效果好,那假设就可能是有用的。
如何计算位置编码?
论文中使用下面的公式来计算固定位置编码:
P
E
(
p
o
s
,
2
i
)
=
s
i
n
(
p
o
s
/
1000
0
2
i
/
d
m
o
d
e
l
)
P
E
(
p
o
s
,
2
i
+
1
)
=
c
o
s
(
p
o
s
/
1000
0
2
i
/
d
m
o
d
e
l
)
(1)
PE_{(pos,2i)} = sin(pos/10000^{2i/d_{model}}) \\PE_{(pos,2i+1)} = cos(pos/10000^{2i/d_{model}}) \tag{1}
PE(pos,2i)=sin(pos/100002i/dmodel)PE(pos,2i+1)=cos(pos/100002i/dmodel)(1)
其中
p
o
s
pos
pos 是单词在seq中的位置,取值范围为
[
0
,
l
e
n
(
s
e
q
)
−
1
]
[0, len(seq) - 1]
[0,len(seq)−1] ,
i
i
i 是嵌入向量维度,取值范围为
[
0
,
d
m
o
d
e
l
−
1
]
[0, d_{model} -1]
[0,dmodel−1] 。
所以给定嵌入向量维度之后,输入句子的每一个位置向量就是固定的,下图代表的是嵌入向量维度为128,句子最大长度为50的位置编码表:
上图中,每一行代表一个位置编码,例如第1行,代表第1个单词的位置便面,第55行就代表第55个单词的位置编码。
每一个位置编码的维度都对应一个正弦信号。波长形成一个从 2 π 2\pi 2π 到 10000 ⋅ 2 π 10000 \cdot 2 \pi 10000⋅2π 的几何连续。之所以选择这个函数,是因为作者猜测它会比较容易的学习相对位置的参与,因为对于任意固定的偏移 k k k , P E p o s + k PE_{pos+k} PEpos+k 能够被表示为 P E p o s PE_{pos} PEpos 的一个线性函数。
我们也试验过使用学习的位置嵌入,结果发现两种方式取得几乎一样的结果。我们选择正弦版本,是因为他可能允许模型推断序列的长度比训练中遇到的更长。
3.编码器
编码器的部分实际上就是上图圆角矩形包围的部分,它包含两个子层。接下来我们还是自底向上开始介绍:
Attention函数——铺垫:
一个attention函数可以看做是将一个query跟一组key-value对映射到一个输出。query、keys、values和输出都是向量。输出是values的加权和,每一个values对应的权重是由兼容函数(compatibility function)根据query和对应的key计算出来的。
这里说一下我对兼容函数的理解:
这里兼容函数的目的是根据query和keys来计算values的权重;
但是根据query和keys来计算values的权重的方法不止一个,本文用的是点积,还可以用加法;
兼容的意思就是这个函数可以随时替换成别的函数。
类似于编程中的向后兼容,就是即使换了具体的实现,但是原来的接口还是可用。
Scaled Dot-Product Attention
放缩点积注意的输入为queries、keys和values,queries和keys的维度为 d k d_k dk ,values的维度为 d v d_v dv 。然后我们计算query跟所有的keys的点积,然后除以 d k \sqrt{d_k} dk ,之后再通过softmax函数计算所有values的权重。在此Transformer模型中, d k = d v d_k=d_v dk=dv 。
为了方便计算,我们将所有的queries打包到矩阵Q中,同理将keys和values也打分别包到矩阵K和V中。于是计算Attention函数的公式就可以写成下面的样子:
A
t
t
e
n
t
i
o
n
(
Q
,
K
,
V
)
=
s
o
f
t
m
a
x
(
Q
K
T
d
k
)
V
(2)
Attention(Q,K,V)=softmax(\frac{QK^T}{\sqrt{d_k}})V \tag{2}
Attention(Q,K,V)=softmax(dkQKT)V(2)
这个公式中,
Q
K
T
QK^T
QKT 对应上图的 MatMul 操作, 再对其除以
d
k
\sqrt{d_k}
dk 对应的是Scale操作, Mask(opt.)是可选操作,在编码器中没有用到,在后面解码器中会具体讲解它的作用,之后进行softmax得到每个词的影响力分布,即每个词对这个位置的影响有多大,最后跟矩阵V相乘,得到Attention函数输出。
两个最常用的Attention函数是加法注意(additive attention) 和 点积注意(dot-product (multi-plicative) attention) 。上面这个模型就是用的点积注意,不过缩放因子用的是 1 d k \frac{1}{\sqrt{d_k}} dk1 。这两个函数的理论复杂度是一样的,不过在实践中,点积注意速度更快,更节省空间,因为后者可以使用高度优化的矩阵乘法代码实现。
如果 d k d_k dk 比较小,两个机制表现是相似的,如果 d k d_k dk 值较大的话,加法注意要优于点积注意。我们怀疑对于较大的 d k d_k dk 值,点积的大小会增大,从而将softmax函数推入一个梯度极小的区域。为了抵消这个影响,我们将点积乘以 1 d k \frac{1}{\sqrt{d_k}} dk1 。
Multi-Head Attention
为什么使用多头注意?
多头注意使得模型可以增强模型关注不同位置的能力,如果只有一个注意力头,那么它可能将更多的注意放到某一个词上,而使得其他词的影响力很小。例如:翻译“The animal didn’t cross the street because it was too tired”,其中“it"指的是“the animal”。如果是单头注意力可能会将大部分注意放到“the animal”上。但如果是多头注意,就会发现“tired”也会获得一定的注意,如下图:
实际上“the animal”跟tired“结合在一起会使得对“it”的表达更完整。
怎样进行多头注意?
作者的做法是将K、Q、V头投影到不同的子空间中,投影的参数随机初始化,之后就可以并行的计算attention函数,最后对结果进行拼接。论文中作者投影了h次,h=8,并且
d
k
=
d
v
=
d
m
o
d
e
l
/
h
d_k=d_v=d_{model}/ h
dk=dv=dmodel/h 。
M
u
l
t
i
H
e
a
d
(
Q
,
K
,
V
)
=
C
o
n
c
a
t
(
h
e
a
d
1
,
⋯
,
h
e
a
d
h
)
W
O
w
h
e
r
e
h
e
a
d
i
=
A
t
t
e
n
t
i
o
n
(
Q
W
i
Q
,
K
W
i
K
,
V
W
i
V
)
(3)
MultiHead(Q,K,V)=Concat(head_1,\cdots,head_h)W^O \\where \, head_i=Attention(QW_i^Q,KW_i^K,VW_i^V) \tag{3}
MultiHead(Q,K,V)=Concat(head1,⋯,headh)WOwhereheadi=Attention(QWiQ,KWiK,VWiV)(3)
其中投影参数矩阵
W
i
Q
∈
R
d
m
o
d
e
l
×
d
k
W_i^Q \in \mathbb{R}^{d_{model} \times d_k}
WiQ∈Rdmodel×dk ,
W
i
K
∈
R
d
m
o
d
e
l
×
d
k
W_i^K \in \mathbb{R}^{d_{model} \times d_k}
WiK∈Rdmodel×dk ,
W
i
V
∈
R
d
m
o
d
e
l
×
d
v
W_i^V \in \mathbb{R}^{d_{model} \times d_v}
WiV∈Rdmodel×dv ,
W
O
∈
R
h
d
v
∈
d
m
o
d
e
l
W^O \in \mathbb{R}^{hd_v \in d_{model}}
WO∈Rhdv∈dmodel 。
由于每个注意力头的维度都降低了,所以其总的计算代价跟全维度单头注意力的计算代价相似。
最后再回到编码器部分
上面已经将Multi-Head Attention的结构和计算方式说完了,下下面我们再重新继续向上捋:
这里我们假设PE+WE的结果为X。显然编码器的输入就是X,然后我们再看X的流向,其中X一份复制流向了 Add&Norm
,另外三份X的复制流向了多头注意力层,结合多头注意力层的输入我们知道,这三部分分别对应V、K、Q,于是Q=K=V=X。我们再假设多头注意力的输出为Z,那么在 Add&Norm
这一层进行的计算就是
N
o
r
m
(
X
+
Z
)
Norm(X+Z)
Norm(X+Z) 。
这里我们稍微解释一下Norm的含义,Norm全称Normalization,即归一化。
那么使用它的目的是什么呢?
是为了使得神经元的输入数据保持独立同分布。具体算法也有很多种,BN、LN,IN、GN等等。
这里再解释一下上面位置编码提到的跳跃连接(skip connection),在这篇论文中称为残差连接(residual connection),其实这二者说的是同一件事,跳跃连接了解决梯度消失问题,同时可以将PE+WE携带的信息传递到更深的层。对应的是上图中X传输到 Add&Norm
层的那个连接。
继续向上看,就是一个前馈神经网络层,它包括两个线性转换,中间是一个ReLU激活函数。
F
F
N
(
x
)
=
m
a
x
(
0
,
x
W
1
+
b
1
)
W
2
+
b
2
(4)
FFN(x)=max(0,xW_1+b_1)W_2+b_2 \tag{4}
FFN(x)=max(0,xW1+b1)W2+b2(4)
线性变换在不同位置上是相同的,他们层与层之间使用不同的参数。另一个描述的方式是内核大小为1的两个卷积。输入输出的维度是
d
m
o
d
e
l
=
512
d_{model}=512
dmodel=512 ,内层的维度是
d
f
f
=
2048
d_{ff}=2048
dff=2048 。
在前馈神经网络之后同样也使用了跳跃连接和归一化处理。
最后我们再看左侧的N,这个N代表这个编码器可以叠加,论文中作者使用了6层的编码器叠加。
4.解码器
右侧圆角矩形包围的部分就是解码器。相比于编码器多了一个子层。并且也是多层可叠加的,Transformer中,解码器部分也堆了6层。
下面开始自底向上开始解释解码器部分:
首先是解码器的输入部分,这里需要注意的是,解码器的输入只能早于当前的输出位置。举例来说:将“我是一名学生” 翻译成“I am a student”。如果当前解码器已经输出了“I am”,准备预测“am”的下一个单词,这时解码器的输入只能是“I am”,至于后面未预测的单词在Attention的softmax层之前要用 -inf
来代替。当模型预测到句子结束符时,整个句子才算翻译完成。
接下来是Masked Multi-Head Attention,这个要说一下,这个跟之前的多头注意相比多了一个Mask操作,没错就是我们之前在编码器放缩点积注意模型中,忽略掉的那个可选Mask操作。目的就是我们上一段说的将还未预测的单词用 -inf
代替。
之后还需要说的是中间层的输入部分,编码器的输出作为中间Multi-Head Attention的输入,对应V和K,解码器第一层的输出作为Multi-Head Attention的输入中的Q。
之后的部分就跟解码器是一样的了,不再赘述。
5.预测输出单词
将解码器的输出通过一个Linear层,得到logits向量,这个logits向量跟模型词库的大小是一样的,然后通过softmax层得到每一个词的概率,选取概率最大的那个词作为我们的要预测的下一个单词。
最后:
以上我们就将Transformer模型从输入到输出全部都讲解完了。虽然讲完了,但是应该还有一些细节可能没有涉及到,所以非常建议看一下下面的参考列表。其中参考【6】中文译本必看,这篇文章从头到尾将Transformer讲了一篇,非常详细,配图也非常形象,还有gif动图帮助理解。至于其他的参考,有条件就看,没条件就略过吧。
参考:
【2】https://www.youtube.com/watch?v=iDulhoQ2pro
【3】https://www.youtube.com/watch?v=TQQlZhbC5ps&ab_channel=CodeEmporium
【4】https://www.youtube.com/watch?v=biO70u1Pdd4&ab_channel=TheA.I.Hacker-MichaelPhi
【5】https://www.youtube.com/watch?v=YIEe7d7YqaU&ab_channel=ChrisMcCormickAI
【6】The Illustrated Transformer 还可以看 ——> The Illustrated Transformer【译】
【7】Why add positional embedding instead of concatenate?