序列编码
对于一个序列,基本的思路是将序列划分为多个状态,然后每个状态转化为统一大小的向量。此时每个序列都对应一个矩阵 X = ( x 1 , x 2 , ⋯ , x t ) X=(x_1,x_2,\cdots,x_t) X=(x1,x2,⋯,xt),其中 x i x_i xi 表示第 i i i 个状态(向量),维度为 d d d,即 X ∈ R n × d X \in \mathbb{R}^{n \times d} X∈Rn×d。
要处理这个序列,第一个思路是使用 RNN,即递归处理:
y
t
=
f
(
y
t
−
1
,
x
t
)
y_t = f(y_{t-1}, x_t)
yt=f(yt−1,xt)
其中函数 f f f 可使用原始 RNN、LSTM、或者GRU。这个思路的缺点之一是无法并行,速度较慢,这也是递归的天然缺陷。同时,这种递归思路难以学习全局结构信息,因为其本质上是一个马尔科夫决策过程。
第二个思路是使用 CNN,即窗口式遍历处理,如对大小为3的窗口,有:
y
t
=
f
(
x
t
−
1
,
x
t
,
x
t
+
1
)
y_t = f(x_{t-1}, x_t, x_{t+1})
yt=f(xt−1,xt,xt+1)
相比与RNN,CNN更方便并行计算。同样地,RNN需要逐步递归才能获得全局信息,而CNN则需要层叠来增大感受野。相比之下,Attention一步到位直接获取了全局信息,有:
y
t
=
f
(
x
t
,
A
,
B
)
y_t = f(x_t,A,B)
yt=f(xt,A,B)
Attention
Attention
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
Attention(Q,K,V) = softmax(\frac{QK^T}{\sqrt{d_k}})v
Attention(Q,K,V)=softmax(dkQKT)v
其中 Q ∈ R n × d k Q \in \mathbb{R}^{n \times d_k} Q∈Rn×dk, K ∈ R m × d k K \in \mathbb{R}^{m \times d_k} K∈Rm×dk, V ∈ R m × d v V \in \mathbb{R}^{m \times d_v} V∈Rm×dv。即Attention将原始的 n × d k n \times d_k n×dk 的矩阵编码为 n × d v n \times d_v n×dv 的矩阵。
这个过程相当于首先将 n n n 个状态和 m m m 个key可以做内积或者其他运算来得到各个状态对于各个key的相似度,然后对于这 n × m n \times m n×m 个相似度,先除以 d k \sqrt{d_k} dk 来调整大小(假设 q q q, k k k 均值为0,方差为1,则 q ⋅ k q \cdot k q⋅k 均值为0方差为 d k d_k dk),再对一个状态的 m m m 个相似度做Softmax,并将Softmax结果乘以 m m m 个key的值,这个key有 d v d_v dv 个值。
Multi-Head Attention
Multi-Head Attention是对Attention的简单扩展,将
Q
Q
Q,
K
K
K,
V
V
V先通过参数矩阵映射成
h
h
h 组,分别对这
h
h
h 组做Attention,最后将
h
h
h 个结果拼接起来就可以了:
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
)
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
)
head_i = Attention(QW^Q_i,kW^K_i,VW^V_i) \\ MultiHead(Q,K,V) = Concat(head_1, \cdots, head_h)
headi=Attention(QWiQ,kWiK,VWiV)MultiHead(Q,K,V)=Concat(head1,⋯,headh)
其中, W i Q ∈ R d k × d ~ k W^Q_i \in \mathbb{R}^{d_k \times \tilde{d}_k} WiQ∈Rdk×d~k, W i K ∈ R d k × d ~ k W^K_i \in \mathbb{R}^{d_k \times \tilde{d}_k} WiK∈Rdk×d~k, W i V ∈ R d v × d ~ v W^V_i \in \mathbb{R}^{d_v \times \tilde{d}_v} WiV∈Rdv×d~v。最终得到一个 n × h × d ~ v n \times h \times \tilde{d}_v n×h×d~v 的矩阵。
Multi-Head中不同的Head关注不同的特征。
Self Attention
实际上,Google的论文中大部分的Attention都是Self Attention,即自注意力。此时
A
=
B
=
X
A=B=X
A=B=X。更准确来说,Google使用的是Self Multi-Head Attention,即:
Y
=
M
u
l
t
i
H
e
a
d
(
X
,
X
,
X
)
Y = MultiHead(X,X,X)
Y=MultiHead(X,X,X)
此时,原始矩阵 X X X 会先与三个参数矩阵 W Q W^Q WQ, W K W^K WK, W V W^V WV 相乘得到矩阵 Q Q Q, K K K, V V V,然后再进行计算。
在结构层次上,一个Self-Attention Layer就等价于一个RNN Layer。每个状态都求出各自的
q
i
q^i
qi,
k
i
k^i
ki,
v
i
v^i
vi,然后放入Attention中。而相比于RNN,Attention中由于都是矩阵运算,所以可以用GPU来加速。
下面是对Self Attention进行可视化。可见在不同情况下,Attention关联的情况不同。
Position Embedding
从Attention的计算过程中可以发现,Attention并不能捕捉到序列的顺序,即如果将序列中的一些状态打乱,Attention得到的结果都是一样的。而Position Embedding,即位置向量,则可以解决这个问题。位置向量将每个位置编号,然后每个编号对应一个向量,通过结合状态向量和位置向量,就给每个次都引入了一定的位置信息,这样Attention就可以分辨出不同位置的状态了。在使用RNN和CNN处理序列信息时,由于这两种结构本来就可以捕捉位置信息,所以无需使用Position Embedding。但是在Attention中,Position Embedding则是位置信息的唯一来源。
此时首先状态
x
i
x_i
xi 经过特征提取得到
a
i
a_i
ai,然后将位置编码
e
i
e_i
ei 与
a
i
a_i
ai 相加,再用
a
i
a_i
ai 经过矩阵
W
W
W 得到
q
i
q^i
qi,
k
i
k^i
ki,
v
i
v^i
vi。另外,注意这里是将编码与状态特征相加而不是编码与状态拼接。其实这里所得到的结果是一致的。因为将位置编码与状态拼接后,再通过特征提取矩阵计算,实际上就是状态特征与位置信息相加。
对于这个位置编码,如果直接用从0开始的整数表示不同的位置,那么当序列较长时这个位置编码数值上相差较大,不利于神经网络的处理,并且序列较长时位置编码数值较大,可能会影响到状态信息的处理。进一步地,如果将上述编码方式归一化,即用编码除以序列长度,那么当序列较长时编码的差异不大,相当于长序列中的局部序列顺序被稀释了。
因此,位置编码的要求是既需要体现状态在序列中的先后次序关系,又需要体现编码差异不依赖序列长度,且编码需要具有一定的值域范围。所以一种思路就是用有界的周期函数来表示,其中一个选择就是三角函数,即:
P
E
(
p
o
s
)
=
sin
(
p
o
s
α
)
PE(pos) = \sin(\frac{pos}{\alpha})
PE(pos)=sin(αpos)
其中
α
\alpha
α 用来调整位置编码函数的波长,当
α
\alpha
α 较大时,波长较长,相邻状态的位置编码之间的差异较小。然而当
α
\alpha
α 较小时,波长较短,这样长序列中还是有可能会有一些不同位置的状态编码一致。若位置编码是一个
d
m
o
d
e
l
d_{model}
dmodel 维的向量,那么
[
−
1
,
1
]
d
m
o
d
e
l
[-1,1]^{d_{model}}
[−1,1]dmodel 的表示范围就要比原来的
[
−
1
,
1
]
[-1,1]
[−1,1] 要大得多。同时,不同维度应该利用不同函数来进行编码,这样才能充分利用高维空间的特性,所以可以为位置编码的每一维赋予不同的
α
\alpha
α,或者将正弦函数改为余弦函数,即:
P
E
(
p
o
s
,
2
i
)
=
sin
(
p
o
s
1000
0
2
i
d
m
o
d
e
l
)
P
E
(
p
o
s
,
2
i
+
1
)
=
cos
(
p
o
s
1000
0
2
i
d
m
o
d
e
l
)
PE(pos,2i) = \sin(\frac{pos}{10000^{\frac{2i}{d_{model}}}}) \\ PE(pos,2i+1) = \cos(\frac{pos}{10000^{\frac{2i}{d_{model}}}}) \\
PE(pos,2i)=sin(10000dmodel2ipos)PE(pos,2i+1)=cos(10000dmodel2ipos)
Transformer
Transformer本质上是一个Encoder-Decoder结构。其完整结构如下:
首先对于Encoder部分。以机器翻译为例,一个样本由原始句子和翻译后的句子组成,即“我爱机器学习”和 “i love machine learning” 共同构成一个样本。此时,输入会先经过Input Embedding提取特征,假设Embedding向量维度是512,则这个句子经过Embedding后矩阵大小为 4 × 512 4 \times 512 4×512。而为了让不同样本的长度统一,此时需要进行padding操作。假设句子最大长度为10,那么对于长度不足10的句子,需要补足到10个单词,补全的Embedding数值为0,此时输入的Embedding矩阵大小统一为 10 × 512 10 \times 512 10×512。同时,由于Attention不应该关于补全内容,所以在 Q Q Q 和 K K K 做点积后,进行Softmax之前还需要将补全位置的数据改为足够大的负数,这个过程称为 Padding Mask。
然后将特征与位置编码信息相加。之后会进入一个block,这个block会重复 N N N 遍。在一个block中输入首先经过Attention层,然后为了解决深度学习中的退化问题,加入了short-cut结构,并将相加的结果经过Layer Norm。然后在经过由两层全连接层,第一层激活函数为ReLU,第二层不使用激活函数构成的Feed Forward层。最后再经过short-cut和Layer Norm。
之后是对Decoder部分。Encoder在编码一个句子之后,将编码信息发送到Decoder,然后Decoder利用这个编码信息以及递归地输入上一步的解码信息递归地进行解码(跟RNN一样)。此时,Decoder首先输入上一个time step的输出(如果此时为第一个time step,则输入特殊符号代替)。这个输出首先通过Embedding做特征提取,然后在加入位置编码信息。之后会进入一个block,这个block同样会重复 N N N 遍。在这个block中,第一层为Masked Multi-Head Attention,是指只输入当前翻译的内容,因为后面的还未翻译,所以要mask掉。到了第二个Attention中, Q Q Q 为Masked Multi-Head Attention的输出, K K K 和 V V V 为Encoder的输出,之后再经过Feed Forward层,最后再经过线性层和Softmax,求出预测的各个候选单词的概率。
对于整体流程,是
- Encoder编码整个句子(如“我爱机器学习”)
- 将这个编码结果放到Decoder,然后输入起始符 < / s > </s> </s>
- 此时Decoder产生预测结果 “I”
- 然后将编码结果以及 < / s > </s> </s>和"I"输入到Decoder
- 此时Decoder产生预测结果 “love”
- 然后将编码结果以及 < / s > </s> </s>,“I”,和 “love” 输入到Decoder
- 此时Decoder产生预测结果 “machine”
- 然后将编码结果以及 < / s > </s> </s>,“I”,“love”,和 “machine” 输入到Decoder
- 此时Decoder产生预测结果 “learning”
- 然后将编码结果以及 < / s > </s> </s>,“I”,“love”,“machine”,和 “learning” 输入到Decoder
- 此时Decoder产生预测结果 < b o s > <bos> <bos>,即终止符
注意,上面这个是预测,即测试阶段的流程,对于训练阶段,不是将每一步的预测结果作为输入,而是直接将标签作为输入,即在训练阶段Decoder的输入为
- < / s > </s> </s>
- < / s > </s> </s>,“i”
- < / s > </s> </s>,“i”,“love”
- < / s > </s> </s>,“i”,"love ",“machine”
- < / s > </s> </s>,“i”,"love ",“machine”,“learning”
所以此时需要将标签用右上三角为全零的矩阵mask掉,此时第一个Decoder输入用来计算第一个单词 “I” 的loss,第二个Decoder输入用来计算第二个单词 “love” 的loss。可见这个输入相当于将标签右移一位,所以为shifted right。这个过程称为 Sequence Mask。注意,在Decoder中同样可能用到Padding Mask。