本文主要对论文“Attention is All You Need”的核心架构进行介绍。
下图是Transformer的架构:
Attention
一个attention函数可以看做是将一个query跟一组key-value对映射到一个输出。query、keys、values和输出都是向量。输出是values的加权和,每一个values对应的权重是由兼容函数(compatibility function)根据query和对应的key计算出来的。
Scaled Dot-Product Attention
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的权重。
为了方便计算,我们将所有的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
(1)
Attention(Q,K,V)=softmax(\frac{QK^T}{\sqrt{d_k}})V \tag{1}
Attention(Q,K,V)=softmax(dk
QKT)V(1)
两个最常用的Attention函数是加法注意(additive attention) 和 点积注意(dot-product (multi-plicative) attention) 。上面这个模型就是用的点积注意,不过缩放因子用的是
1
d
k
\frac{1}{\sqrt{d_k}}
dk
1 。加法注意计算兼容函数用的是一个单隐层的前馈神经网络。这两个函数的理论复杂度是一样的,不过在实践中,点积注意速度更快,更节省空间,因为后者可以使用高度优化的矩阵乘法代码实现。
如果 d k d_k dk 比较小,两个机制表现是相似的,如果 d k d_k dk 值较大的话,加法注意要由于点积注意。我们怀疑对于较大的 d k d_k dk 值,点积的大小会增大,从而将softmax函数推入一个梯度极小的区域。为了抵消这个影响,我们将点积乘以 1 d k \frac{1}{\sqrt{d_k}} dk 1 。
Multi-Head Attention
多头注意力使用的不是单一的注意函数,作者发现分别将queries、keys、values用不同的、学习线性投影分别映射到 d k , d k , d v d_k,d_k,d_v dk,dk,dv 维是有益的,并且投影了h次。对于queries、keys、values的每一个映射版本,都可以并行的执行attention函数,生成 d v d_v dv 维的输出值。最后将他们连接在一起并再次投影得到最终值。这就是上图右侧的多头注意力。
多头注意能够使模型在不同的表示子空间、不同的位置共同关注信息。如果只有单一的注意力头,平均会抑制这种情况。
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
)
(2)
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{2}
MultiHead(Q,K,V)=Concat(head1,⋯,headh)WOwhereheadi=Attention(QWiQ,KWiK,VWiV)(2)
其中投影参数矩阵
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 。
作者部署了8个并行的注意力层(注意力头),即 h = 8 h=8 h=8 ,对于每一层,设置 d k = d v = d m o d e l / h d_k=d_v=d_{model}/ h dk=dv=dmodel/h ,由于每个注意力头的维度都降低了,所以其总的计算代价跟全维度单头注意力的计算代价相似。
Position-wise Feed-Forward Networks
除了attention子层,编码器和解码器的每一层都包含了一个全连接的前馈网络,每一个位置都是独立部署并且是一样的。这包括两个线性转换,中间是一个ReLU**函数。
F
F
N
(
x
)
=
m
a
x
(
0
,
x
W
1
+
b
1
)
W
2
+
b
2
(3)
FFN(x)=max(0,xW_1+b_1)W_2+b_2 \tag{3}
FFN(x)=max(0,xW1+b1)W2+b2(3)
线性变换在不同位置上是相同的,他们层与层之间使用不同的参数。另一个描述的方式是内核大小为1的两个卷积。输入输出的维度是
d
m
o
d
e
l
=
512
d_{model}=512
dmodel=512 ,内层的维度是
d
f
f
=
2048
d_{ff}=2048
dff=2048 。
Embeddings and Softmax
跟其他的序列转移模型一样,我们使用学习的嵌入(learned embeddings)将输入和输出的tokens转换为维度为 d m o d e l d_{model} dmodel 的向量。我们通常也使用学习的线性变换和softmax函数将解码器的输出转换为预测下一个token的概率。在这个模型中,在两个嵌入层之间使用相同的权重矩阵,… ,在嵌入层,我们用 d m o d e l \sqrt{d_{model}} dmodel 乘以这些权重。
Position Encoding
因为这个模型中没有循环层也没有卷积层,为了使模型能够利用序列的信息,我们必须注入序列中tokens的相对或者绝对信息。为了达到这个目的,在输入嵌入中添加了“位置编码”,在每个编码器和解码器栈底部。位置编码跟嵌入的维度相同,都是 d m o d e l d_{model} dmodel ,以便于二者相加。对于位置编码也有许多选择,例如学习到的编码或者固定的编码。
在这项工作中,我们使用不同频率的 sine 和 cosine 函数:
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
)
(4)
PE_{(pos,2i)} = sin(pos/10000^{2i/d_{model}}) \\PE_{(pos,2i+1)} = cos(pos/10000^{2i/d_{model}}) \tag{4}
PE(pos,2i)=sin(pos/100002i/dmodel)PE(pos,2i+1)=cos(pos/100002i/dmodel)(4)
其中
p
o
s
pos
pos 是位置,
i
i
i 是维度。也就是说,每一个位置编码的维度都对应一个正弦信号。波长形成一个从
2
π
2\pi
2π 到
100002
⋅
π
10000 2 \cdot \pi
100002⋅π 的几何连续。之所以选择这个函数,是因为我们猜测它会比较容易的学习相对位置的参与,因为对于任意固定的偏移
k
k
k ,
P
E
p
o
s
+
k
PE_{pos+k}
PEpos+k 能够被表示为
P
E
p
o
s
PE_{pos}
PEpos 的一个线性函数。
我们也试验过使用学习的位置嵌入,结果发现两种方式取得几乎一样的结果。我们选择正弦版本,是因为他可能允许模型推断序列的长度比训练中遇到的更长。