针对主流的序列转录工作如:语言建模和机器翻译,大多数研究都是围绕着RNN的方法进行的,例如长短期记忆(LSTM)和门控循环神经网络(GRU)。对于传统的RNN来说,其计算方式是沿着序列从左到右一步一步进行,假设输入序列是句子,RNN就是一个词一个词逐一的去看。对于当前这个词t来说RNN会计算其隐含层状态
h
t
h_t
ht,这个
h
t
h_t
ht是由上一个词的隐含层状态
h
t
−
1
h_{t-1}
ht−1和当前这个词t决定。这样就做到了考虑了输入和输出序列的中的字符位置进行计算。这是RNN这类模型的优势,但是同时也未其带来了弊端: 1.由于他依赖于时序的输入,这使得其难以实现并行操作。(因为你想计算
h
t
h_t
ht就必须先计算
h
t
−
1
h_{t-1}
ht−1),这使得对一些特殊硬件GPU、TPU的多线程利用率有限。 2.由于计算是时序一步步计算的,这是得在计算到后面时有可能会忘记前面所学到的知识。如果不想发生这种情况可能意味着你需要部署一个较大的隐含层去记录信息这增大了内存开销,或者进行一个反向的由后到前的学习,这样会使得运算量倍增。 作者同样指出attention已经被用在了一些RNN模型上了,但是这些模型主要关心的是如何将编码器的内容有效的传递给解码器,归根结底这种注意机制还是与RNN一起使用的。
编码器由
N
=
6
N=6
N=6 个相同的layers组成(这个N是可调的)。每一个layers由两个子层组成分别是上面提到的Multi-Head Attention和各位置独立的全连接层(可以简单的理解为是MLP)组成,这两部分都使用残差链接。最后进行layer normalization(层归一化)。 具体公式是:
L
a
y
e
r
N
o
r
m
(
x
+
S
u
b
l
a
y
e
r
(
x
)
)
LayerNorm(x+Sublayer(x))
LayerNorm(x+Sublayer(x)) , 具体步骤如下:
1
S
u
b
l
a
y
e
r
(
x
)
Sublayer(x)
Sublayer(x) 输入进入子层 2
(
x
+
S
u
b
l
a
y
e
r
(
x
)
(x+Sublayer(x)
(x+Sublayer(x) 残差连接 3
L
a
y
e
r
N
o
r
m
(
x
+
S
u
b
l
a
y
e
r
(
x
)
)
LayerNorm(x+Sublayer(x))
LayerNorm(x+Sublayer(x)) 进出层归一化
由于残差连接需要输入和输出之间是一样大小。所以作者将每一个层的输入维度变成了
d
m
o
d
e
l
=
512
d_{model}=512
dmodel=512大小。直接使用固定长度来表示。最后调参只需要注意两个参数分别是N和这里的
d
m
o
d
e
l
d_{model}
dmodel。这个简单的设计最后影响到了后面的一系列网络,例如Bert和GBT。
3.2 解码器
解码器这边同样由
N
=
6
N=6
N=6 个相同的layers组成,解码器这边除了上面编码器提到的两个sub-layer;还增加了一个Masked Multi-Head Attention的子块。与编码器类似,我们在每个子层周围使用残差连接,然后进行层归一化。为什么加入这个遮蔽多头注意力子块呢?时序预测是依赖当前输入和以前的预测输出进行预测,我们不希望看到当前时刻之后的输入进入注意力层来干扰我们的预测。所以引入遮蔽多头注意力子块以防止引入当前时刻的后续时刻输入。这种屏蔽与输出嵌入偏移一个位置的事实相结合,确保了位置
t
t
t的预测仅依赖于小于t的位置处的已知输出。
3.3 Attention
attention可以被描述为将query和一组key-value对映射到输出,其中query,key,value和输出都是向量。输出为计算值的加权求和,权重是value对应key和我们查询的query的相似度(compatibility function)计算得到的。相似度越大,对应的权重越大。 本文使用的是Scaled Dot-Product Attention,其中query和key的维度为
d
k
d_k
dk;value的维度为
d
v
d_v
dv,输出值的维度应该也是
d
v
d_v
dv的。query和key进行内积,将这个内积作为相似度。两个向量(query和key)如果维度相同,这两个向量的内积值越大,说明其相似度越大,如果两个向量内积为0,则说明其正交了则相似度为0。然后将每个点积结果除以
d
k
\sqrt{d_k}
dk。并应用softmax函数来获得value的权重。 为了增加运算速度,作者将一个个query向量打包成了矩阵Q,key和value打包成了K和V,具体输出矩阵如下:
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
Attention(Q,K,V)=softmax( \frac {QK^T} {\sqrt{d_k}} ) V
Attention(Q,K,V)=softmax(dkQKT)V
Attention有两个最长用的函数,分别是加法attention和点积(乘法)attention。作者这里使用的是改进的点attention,该方法的优点是速度快、占用空间小。作者这里的创新是引入了缩放因子
1
d
k
\frac{1}{\sqrt{d_k}}
dk1,为什么引入这个缩放因子呢? 当我们输入的
d
k
d_k
dk较大时,那么query和key之间相对的差距就会更大,就导致值更大(或更小)的值在经过softmax后会更加趋近于1(或0)。这导致值会更加向两边靠拢。此时计算梯度的难度就会增加,因为上面提到作者用的
d
k
=
512
d_k=512
dk=512相对较大,这使得引入这个缩放因子成为了一个较好的选择。
3.4 Multi-Head Attention
作者这里提到使用
d
m
o
d
e
l
d_{model}
dmodel维的key、value和query执行单个的attention函数相比,将query、key和value映射低维空间上,投影
h
h
h次,再做
h
h
h次的attention函数,最后每一个函数的输出将其并在一起,再投影回原来的高维空间这样的效果是更加有益的,具体流程图论文中也有展示。 图中的Linear层就是将query、key、value映射到低维空间,然后在Scaled Dot-Product Attention 中进行我们上节提到的n次attention函数,在Concat层上进行合并,最后进入Linear层回到高维空间。
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
MultiHead(Q,K,V) = Concat(head_1,...,head_h)W^O
MultiHead(Q,K,V)=Concat(head1,...,headh)WO
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
)
where \ head_i=Attention(Q{w_{i}}^Q,K{w_{i}}^K,V{w_{i}}^V)
whereheadi=Attention(QwiQ,KwiK,VwiV)
在Multi-Head上还是以前的
(
Q
,
K
,
V
)
(Q,K,V)
(Q,K,V)但是具体的输出是不同的头的Concat之后再投影到
W
O
W^O
WO的结果。对于每一个不同的头通过不同的可以独立学习的
W
Q
W^Q
WQ、
W
K
W^K
WK、
W
V
W^V
WV 投影到d_v上再进行上面的attention函数就可以了。 本论文中我们采用
h
=
8
h=8
h=8个平行的attention head。前面提到的
d
m
o
d
e
l
=
512
d_{model}=512
dmodel=512,则投影到低维的空间大小为
d
k
=
d
m
o
d
e
l
/
h
=
64
d_k=d_{model}/h=64
dk=dmodel/h=64。
编码器和解码器中的每个层都包含一个完全连接的前馈网络,可以简单的理解为是MLP。该前馈网络包括两个线性变换,并在第一个的最后使用ReLU激活函数。公式如下:
F
F
N
(
x
)
=
m
a
x
(
0
,
x
W
1
+
b
1
)
W
2
+
b
2
FFN(x)=max(0,xW_1+b_1 ) W_2+b_2
FFN(x)=max(0,xW1+b1)W2+b2 具体步骤分解:
1
x
W
1
+
b
1
xW_1+b_1
xW1+b1 进行第一次线性变化; 2
m
a
x
(
0
,
x
W
1
+
b
1
)
max(0,xW_1+b_1 )
max(0,xW1+b1)第一次线性变化的结果进入ReLU激活函数; 3
m
a
x
(
0
,
x
W
1
+
b
1
)
W
2
+
b
2
max(0,xW_1+b_1 ) W_2+b_2
max(0,xW1+b1)W2+b2 进行第二次线性变化;
输入和输出的维度是
d
m
o
d
e
l
=
512
d_{model}=512
dmodel=512,内层的维度为
d
f
f
=
2048
d_{ff}=2048
dff=2048。
3.7 Embeddings and Softmax
因为这里我们输入是一个个词(或者说是叫token),我们需要将一个个词映射成相对应的向量。这里embedings的目的就是学习一个长度为d的向量去表示这个token。在该的模型中,我们在两个嵌入层和pre-softmax线性变换之间共享相同的权重矩阵。这样可以让学习更简单。在嵌入层中,我们将这些权重乘以
d
m
o
d
e
l
\sqrt{d_{model}}
dmodel。
P
E
(
p
o
s
,
2
i
)
=
s
i
n
(
p
o
s
/
1000
0
(
2
i
/
d
m
o
d
e
l
)
)
PE_{(pos,2i)}=sin(pos/10000^{(2i/d_{model})})
PE(pos,2i)=sin(pos/10000(2i/dmodel))
P
E
(
p
o
s
,
2
i
+
1
)
=
c
o
s
(
p
o
s
/
1000
0
(
2
i
/
d
m
o
d
e
l
)
)
PE_{(pos,2i+1)}=cos(pos/10000^{(2i/d_{model})})
PE(pos,2i+1)=cos(pos/10000(2i/dmodel))
l
r
a
t
e
=
d
m
o
d
e
l
−
0.5
⋅
m
i
n
(
s
t
e
p
_
n
u
m
−
0.5
,
s
t
e
p
_
n
u
m
⋅
w
a
r
m
u
p
_
s
t
e
p
s
−
1.5
)
lrate = {d_{model}}^{-0.5}·min(step\_num^{-0.5},step\_num·warmup\_steps^{-1.5})
lrate=dmodel−0.5⋅min(step_num−0.5,step_num⋅warmup_steps−1.5)
N
N
N是具体的Transformer架构要堆几层,
d
m
o
d
e
l
d_{model}
dmodel是token未来要表示成多大的向量,
d
f
f
d_{ff}
dff表示MLP中间的那个隐含层的输出大小;
h
h
h是多头注意力里面头的个数;
d
k
d_k
dk,
d
v
d_v
dv分别是注意中key和value的那个维度;
P
d
r
o
p
P_{drop}
Pdrop正则化的丢弃率;
τ
t
s
\tau_{ts}
τts表示要学习的label的真实值是多少。
在这项工作中,作者提出了Transformer,Transformer的应用不仅仅在机器翻译上,在几乎NLP的工作上都是适用的,甚至是后来的一些CV上的工作。总的来说Transformer采用分布式训练,在GPU上具有更好的训练效率。同时在分析预测更长的文本时,能够捕捉间隔较长的语义关联效果。最后自注意力可以产生更具可解释性的模型。可以从模型中检查注意力分布。各个注意头 (attention head) 也可以学会执行不同的任务。 但是反过来说文章标题是“Attention Is All You Need”,但是attention的作用主要是将序列的信息聚合起来,但是文本使用的其他工作例如MLP、残差连接都是缺一不可的。将这些东西去掉只使用Attention我们同样也无法得到想要的训练结果。同时对于Attention来说其对数据的抓取能力没用那么强,以至于我们可能需要更加庞大的数据和模型才能得到相对较好的结果,这也就是为什么看到后期的一些Transformer越做越大的原因。