1、什么是attention?

在人类的理解中,对待问题是有明显的侧重。具体举个例子来说:“我喜欢踢足球,更喜欢打篮球。”,对于人类来说,显然知道这个人更喜欢打篮球。但对于深度学习来说,在不知道”更“这个字的含义前,是没办法知道这个结果的。所以在训练模型的时候,我们会加大“更”字的权重,让它在句子中的重要性获得更大的占比。比如:
C ( s e q ) = F ( 0.1 ∗ d ( 我 ) , 0.1 ∗ d ( 喜 ) , . . . , 0.8 ∗ d ( 更 ) , 0.2 ∗ d ( 喜 ) , . . . ) C(seq) = F(0.1*d(我),0.1*d(喜),...,0.8*d(更),0.2*d(喜),...) C(seq)=F(0.1d()0.1d()...0.8d()0.2d()...)

2、什么是self-attention?

在知道了attention在机器学习中的含义之后(下文都称之为注意力机制)。人为设计的注意力机制,是非常主观的,而且没有一个准则来评定,这个权重设置为多少才好。所以,如何让模型自己对变量的权重进行自赋值成了一个问题,这个权重自赋值的过程也就是self-attention。

self-attention

3、self-attention的原理

定义:假设有四个输入变量 a 1 a^1 a1 a 2 a^2 a2 a 3 a^3 a3 a 4 a^4 a4,希望它们经过一个self-attention layer之后变为 b 1 b^1 b1 b 2 b^2 b2 b 3 b^3 b3 b 4 b^4 b4

a 1 a^1 a1 b 1 b^1 b1做例子, b 1 b^1 b1这个结果是综合了 a 1 a^1 a1 a 2 a^2 a2 a 3 a^3 a3 a 4 a^4 a4而得出来的一个结果。

既然得到一个 b b b是要综合所有的 a a a才行,那么最直接的做法就是 a 1 a^1 a1 a 2 a^2 a2 a 3 a^3 a3 a 4 a^4 a4都做一次运算,得到的结果就代表了这个变量的注意力系数。直接做乘法太暴力了,所以选择一个更柔和的方法:引入三个变量 W q W^q Wq W k W^k Wk W v W^v Wv,这三个变量与 a 1 a^1 a1相乘得到 q 1 q^1 q1 k 1 k^1 k1 v 1 v^1 v1,同样的方法对 a 2 a^2 a2 a 3 a^3 a3 a 4 a^4 a4都做一次。

至于这里的 q , k , v q,k,v q,k,v具体代表什么,下面就慢慢展开讲解。

qkv

那么拿自己的 q q q与别人的 k k k相乘就可以得到一个系数 α \alpha α。这里 q 1 q^1 q1在和其他的 k k k做内积时,可近似的看成是在做相似度计算。比如:
α 1 , 1 = q 1 ⋅ k 1 α 1 , 2 = q 1 ⋅ k 2 α 1 , 3 = q 1 ⋅ k 3 α 1 , 4 = q 1 ⋅ k 4 \alpha_{1,1} =q^1\cdot k^1\\ \alpha_{1,2} =q^1\cdot k^2\\ \alpha_{1,3} =q^1\cdot k^3\\ \alpha_{1,4} =q^1\cdot k^4\\ α1,1=q1k1α1,2=q1k2α1,3=q1k3α1,4=q1k4

在实际的神经网络计算过程中,还得除于一个缩放系数 d \sqrt{d} d ,这个 d d d是指 q q q k k k的维度,因为 q q q k k k会做内积,所以维度是一样的。之所以要除 d \sqrt{d} d ,是因为做完内积之后, α \alpha α会随着它们的维度增大而增大,除 d \sqrt{d} d 相当于标准化。

alpha

得到了四个 α \alpha α之后,我们分别对其进行softmax,得到四个 α ^ 1 \hat{\alpha}_1 α^1,增加模型的非线性。

softmax

四个 α ^ \hat{\alpha} α^分别是 α ^ 1 , 1 \hat{\alpha}_{1,1} α^1,1 α ^ 1 , 2 \hat{\alpha}_{1,2} α^1,2 α ^ 1 , 3 \hat{\alpha}_{1,3} α^1,3 α ^ 1 , 4 \hat{\alpha}_{1,4} α^1,4,别忘了还有我们一开始计算出来的 v 1 v^1 v1 v 2 v^2 v2 v 3 v^3 v3 v 4 v^4 v4。可能有读者就会问了,直接把各个 α ^ 1 \hat{\alpha}_1 α^1直接与各个 a a a相乘不就得出了最后的结果了吗?虽然这么说也没错,但为了增加网络深度,将 a a a变成 v v v也可以减少原始的 a a a对最终注意力计算的影响。

那么距离最后计算出 b 1 b^1 b1只剩最后一步,我们将所有的 α ^ 1 \hat{\alpha}_1 α^1与所有的 v v v分别相乘,然后求和,就得出 b 1 b^1 b1啦!具体计算如下:
b 1 = α ^ 1 , 1 ∗ v 1 + α ^ 1 , 2 ∗ v 2 + α ^ 1 , 3 ∗ v 3 + α ^ 1 , 4 ∗ v 4 b^1=\hat{\alpha}_{1,1}*v^1+\hat{\alpha}_{1,2}*v^2+\hat{\alpha}_{1,3}*v^3+\hat{\alpha}_{1,4}*v^4 b1=α^1,1v1+α^1,2v2+α^1,3v3+α^1,4v4
公式简化为:
b 1 = ∑ i α ^ 1 , i ∗ v i b^1=\sum_i\hat{\alpha}_{1,i}*v^i b1=iα^1,ivi
sum

同样的计算过程,我们对剩下的 a a a都进行一次,就可以得到 b 2 b^2 b2 b 3 b^3 b3 b 4 b^4 b4。每个 b b b都是综合了每个 a a a之间的相关性计算出来的,这个相关性就是我们所说的注意力机制,。那么我们将这样的计算层称为self-attention layer。

self-attention layer

我们把一个句子中的每个字代入上图的 x 1 x^1 x1 x 2 x^2 x2 x 3 x^3 x3 x 4 x^4 x4,就可以将self-attention应用到自然语言处理的领域了。

4、self-attention的优点

从第三节[self-attention的原理](## 3、self-attention的原理)中可以看出,这一层需要学习的参数只有 W q W^q Wq W k W^k Wk W v W^v Wv,大部分变量来自于内部计算得出来的,所以它的参数量少但每个参数所涵盖的信息多,这是它的第一个优点。

每个 b b b的计算都是独立的,这一点相比之前的RNN来说很不一样,RNN是需要等前面的 a 1 a^1 a1算完了才能算 a 2 a^2 a2,是串行的。所以RNN无论是训练还是推理,都会因为不能计算并行而变慢,这是它的的第二个优点。

RNN的一个最大的问题是:前面的变量在经过多次RNN计算后,已经失去了原有的特征。越到后面,最前面的变量占比就越小,这是一个很反人类的设计。而self-attention在每次计算中都能保证每个输入变量 a a a的初始占比是一样的,这样才能保证经过self-attention layer计算后他的注意力系数是可信的。

所以总结下来,它的三个优点分别是:

  • 需要学习的参数量少
  • 可以并行计算
  • 能够保证每个变量初始占比是一样的

5、Multi-head self-attention

这里继续讲解multi-head self-attention,所谓head也就是指一个 a a a衍生出几个 q , k , v q,k,v q,k,v。上述所讲解的self-attention是基于single-head的。以2 head为例:

首先, a i a^i ai先生成 q 1 q^1 q1 k 1 k^1 k1 v 1 v^1 v1。然后,接下来就和single-head不一样了, q i q^i qi生成 q i , 1 , q i , 2 q^{i,1},q^{i,2} qi,1,qi,2,生成的方式有两种:

1. q i q^i qi乘上一个 W q , 1 W^{q,1} Wq,1得到 q i , 1 q^{i,1} qi,1,乘上 W q , 2 W^{q,2} Wq,2得到 q i , 2 q^{i,2} qi,2,这个和single-head的生成是差不多的;
2. q i q^i qi直接从通道维,平均拆分成两个,得到 q i , 1 , q i , 2 q^{i,1},q^{i,2} qi,1,qi,2

这两种方式,在最后结果上都差不多。至于为啥,后面会讲一下原因。

那么这里的图解使用第1个方式,先得到 q i , 1 q^{i,1} qi,1 k i , 1 k^{i,1} ki,1 v i , 1 v^{i,1} vi,1。对 a j a^j aj做同样的操作得到 q j , 1 q^{j,1} qj,1 k j , 1 k^{j,1} kj,1 v j , 1 v^{j,1} vj,1。这边需要注意的一点, q i , 1 q^{i,1} qi,1是要和 k j , 1 k^{j,1} kj,1做矩阵乘法,而非 k j , 2 k^{j,2} kj,2,一一对应。后面计算就和single-head一样了,最后得到 b i , 1 b^{i,1} bi,1

multi-head_1

第二步,对 q i , 2 q^{i,2} qi,2 k i , 2 k^{i,2} ki,2 v i , 2 v^{i,2} vi,2做一样的操作,得到 b i , 2 b^{i,2} bi,2

multi-head_2

这里我们算出的 b i , 1 b^{i,1} bi,1 b i , 2 b^{i,2} bi,2是同维度的,我们可以将其concat在一起,再通过一个 W o W^o Wo把他转成想要的维度。这也就不难理解,为什么说multi-head的两种生成方式是一样的,因为最终决定是输出维度的是 W o W^o Wo。我们可以将multi-head的过程看成是cnn中的隐藏层,multi-head的数量也就对应着Conv2D的filter数量,每一个head各司其职,提取不同的特征。

multi-head_3

6、Position Encoding

最后需要讲解的一点是位置编码。如果读者已经理解了self-attention的原理,不难发现,对于各个 x x x来说,无论相隔多远多近,互相影响程度是一样的,最粗暴的的情况当然是两个 x x x离的越远,互相之间的attention越小,当然一些倒装句的存更需要position encoding。例如:“我爱你”和“你爱我”输入到self-attention layer,其计算的结果是一样的。

position encoding

这里我们采用的位置编码是和 a i a^i ai一样维度的 e i e^i ei,融合方式是: e i e^i ei直接与 a i a^i ai相加。这里 e i e^i ei的内容是人为设定的,可以被训练,也有固定的,即sin cosine function。作者尝试后,说二者效果差不多。

还有一个延申的问题:为什么融合方式是相加,而非concat?

一种从线性代数的角度的说法是:

假设位置编码输入是: p i p^i pi是位置编码的原始输入,它是类似one-hot的向量concat而来,将 x i x^i xi p i p^i piconcat在一起之后,乘上一个 W W W,我们可以将 W W W看成两个部分, W I W^I WI W P W^P WP。整个相乘的过程可以拆成 W I ∗ x i + W P ∗ p i W^I * x^i + W^P * p^i WIxi+WPpi W I ∗ x i W^I * x^i WIxi可视作是 a i a^i ai W P ∗ p i W^P * p^i WPpi可视作 e i e^i ei。所以直接相加和concat其实没什么不同。

position encoding add

7、self-attention的变种和应用

本文所说的self-attention是single-head的,也就是每个 a a a只对应一个 q , k , v q,k,v q,k,v,还有multi-head的,即对应多个 q , k , v q,k,v q,k,v。而原理也是基于NLP方向讲解的,后续也有人将它应用在了CV中,就是大名鼎鼎的Vision Transformer。这些就以后有空再更新吧。

另外附上一个讲的也很好的博客:超详细图解Self-Attention

8、总结

至此self-attention的相关内容以及全部讲解完了,对CV部分有兴趣的同学可以继续阅读Vision Transformer的讲解。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐