自注意力 Self-attention
当我们网络的输入是一排向量时(每次的输入数量都不一样),例如一组词汇,此时我们有如下几个方法
-
one-hot编码,即对每一个词汇都进行01编码
一个词汇有唯一一个属于他的编码,但这种编码缺点是:
- 当词汇过长时,编码过长,导致内存占用较大
- 各词汇之间没有关系,例如上图中的
cat
和dog
,都属于动物这一个类别
-
Word Embedding方法,即对各个词汇使用一个向量表示,并且这个向量具有语义的资料,他会使得同一个种类的词汇之间的距离较近
此时的输出可以分为以下几类:
-
每一个向量都是一个输出的标签
此时有几个输入就有几个输出,即输入输出数目一致,这种任务叫做
Sequence Labeling
-
一整个序列只有一个输出
例如判断某一句话是正面的还是负面的还是中立的
-
不确定应该输出多少个标签,即输出的数目需要机器自己来决定
这种任务叫做
seq2seq
的任务,例如翻译、语音辨别
对于第一种情况,如果我们把所有的向量都逐个击破的话,理论上来说是可以的,但实际上存在很大的问题
例如对于词性判别,I saw a saw
,这里的两个saw
是一样的单词,但是两种不同的词性,如果使用逐个击破的方式就会把这两个单词辨别成一种词性的词汇,因此我们应该考虑上下文的问题,即上下文的词使用window都连接在一起
但是由于window大小的限制,且输入的序列可能有长有短,所以这种做法有限制
因此,为了能够考虑整个输入序列的上下文关系,因此就需要使用自注意力机制
自注意力机制模型
self-attention会把所有的输入序列全部包含起来,最终输出的标签数目与输入的序列数目一致
即,所有得到的输出向量都是考虑了所有的输入序列才得到的
自注意力机制的运作
自注意力机制的输入可能是被某个网络处理后的输出,因此这里使用 a i a_i ai 表示
b i b_i bi的产生
首先我们要找到与 a i a_i ai 相似的部分,即计算出所有部分与 a i a_i ai 的关联性,使用 α \alpha α 表示,最常用的做法是 Dot-product,即将两个向量先与矩阵 W q W^q Wq 和 W k W^k Wk 相乘,然后再把他们相乘后的结果点乘,即计算得到两个向量的关联程度
然后把所有得到的 α i , j \alpha_{i,j} αi,j 经过一个Softmax函数,进行归一化处理,得到 α ′ \alpha ' α′。接下来根据 α ′ \alpha ' α′抽取重要的信息,即相当于加权求和的过程,最终得到 b i b_i bi
这其中,哪一部分与 a i a_i ai的关联度最大,在 b i b_i bi 信息中占的比例也最大
矩阵的运算
对于
q
i
q^i
qi的产生
q
i
=
W
q
a
i
→
q
1
q
2
q
3
q
4
=
W
q
a
1
a
2
a
3
a
4
→
Q
=
W
q
I
q^i = W^qa^i \to q^1q^2q^3q^4 = W^qa^1a^2a^3a^4 \to Q = W^qI
qi=Wqai→q1q2q3q4=Wqa1a2a3a4→Q=WqI
对于
k
i
k^i
ki的产生
k
i
=
W
k
a
i
→
k
1
k
2
k
3
k
4
=
W
k
a
1
a
2
a
3
a
4
→
K
=
W
k
I
k^i = W^k a^i \to k^1k^2k^3k^4 = W^ka^1a^2a^3a^4 \to K = W^kI
ki=Wkai→k1k2k3k4=Wka1a2a3a4→K=WkI
对于
v
i
v^i
vi的产生
v
i
=
W
v
a
i
→
v
1
v
2
v
3
v
4
=
W
v
a
1
a
2
a
3
a
4
→
V
=
W
v
I
v^i = W^va^i \to v^1v^2v^3v^4 = W^va^1a^2a^3a^4 \to V = W^vI
vi=Wvai→v1v2v3v4=Wva1a2a3a4→V=WvI
对于
α
i
,
j
\alpha _{i,j}
αi,j的产生,要使用
k
j
k^j
kj 和
q
i
q^i
qi进行相乘
α
i
,
j
=
k
j
T
q
i
→
[
α
1
,
1
α
1
,
2
α
1
,
3
α
1
,
4
]
=
[
k
1
k
2
k
3
k
4
]
q
1
\alpha _{i,j} = k_j^T q^i \to \begin{bmatrix}\alpha _{1,1} \\ \alpha _{1,2} \\ \alpha _{1,3} \\ \alpha_{1,4} \end{bmatrix} = \begin{bmatrix}k^1 \\ k^2 \\ k^3\\ k^4 \end{bmatrix} q^1
αi,j=kjTqi→
α1,1α1,2α1,3α1,4
=
k1k2k3k4
q1
进而,可以得到
即:
A
=
K
T
Q
A = K^TQ
A=KTQ
A
A
A 里面就是attention的分数,在通过Sigmoid函数后,得到
A
′
A'
A′
A
′
=
σ
(
A
)
A' = \sigma(A)
A′=σ(A)
对于
b
i
b^i
bi的的产生
[
b
1
,
b
2
,
b
3
,
b
4
]
=
V
A
′
→
O
=
V
A
′
[b^1,b^2,b^3,b^4] = VA' \to O = VA'
[b1,b2,b3,b4]=VA′→O=VA′
总的来说,自注意力机制就是一组矩阵的乘法操作
其中,唯一需要我们学习的参数是 W q W^q Wq、 W v W^v Wv 和 W k W^k Wk
Multi-head Self-attention
多头自注意力是自注意力的一个进阶版本,使用比较多的头可以得到较好的效果,即使用不同的头去关注不同的部分
每次计算时,
q
i
,
1
q^{i,1}
qi,1只跟相应的
k
j
,
1
k^{j,1}
kj,1进行操作,
q
i
,
2
q^{i,2}
qi,2 只跟相应的
k
j
,
2
k^{j,2}
kj,2 进行计算操作,分别得到
b
i
,
1
b^{i,1}
bi,1 和
b
i
,
2
b^{i,2}
bi,2
然后再把得到的两个输出
b
i
,
1
b^{i,1}
bi,1 和
b
i
,
2
b^{i,2}
bi,2 连接起来,得到
b
i
b^i
bi
b
i
=
W
o
[
b
i
,
1
b
i
,
2
]
b^i = W^o \begin{bmatrix} b^{i,1} \\ b^{i,2} \end{bmatrix}
bi=Wo[bi,1bi,2]
加入位置的信息
Position Encoding,即给我们输入的序列每个位置都加上一个编码,用来表示其位置的信息
Self-attention与RNN
RNN循环神经网络
不同之处
- RNN没有办法进行并行化处理,即RNN所有的输出都是要按照顺序(从左到右)计算产生的;而Self-attenyion的运行是可以并行化的,因此效率较高
- 对于单向的RNN,每一个节点的输出只考虑了其左侧的信息,而对于Self-attention,则是考虑了全局的信息。而如果使用双向RNN,那么每一个隐状态的输出也可以看作是考虑了全局的信息
- Self-attention可以做到天涯若比邻的效果,通过计算出一个 q q q 和 k k k ,只要他们能够匹配,就可以轻易地从比较远的地方抽取信息;而对于RNN,最左侧的信息必须要一直保留,才会使得最右侧的向量获取到最左侧向量的信息。