Transformer组件(一):Attention机制
paperreading
本文字数:2.5k 字 | 阅读时长 ≈ 9 min

Transformer组件(一):Attention机制

paperreading
本文字数:2.5k 字 | 阅读时长 ≈ 9 min

要讲 attention,肯定就离不开Attention is All You Need这篇文章,虽然我之前都是看的视觉任务,但是视觉任务比如 ViT 中基本用的都是自注意力(self-attention),而原论文是做机器翻译的,除了 self-attention 在 encoder-decoder 之间还用到了 attention 机制,就是说 kv 是来自于编码器,q 是来自于解码器。这里我建议不论是不是做 nlp 中的任务,只要用到 transformer,都要看一看这篇原始文章,收获颇丰

1. Self-Attention 的原理

(1)Self-Attention 的计算公式

首先给出计算公式,如下,QKV 分别代表 Query,Key 以及 Value,即查询、键以及值,他们通过 X 乘上一个权重得到,即$Q=W^{Q}X$, $K=W^{K}X$, $V=W^{V}X$,得到 QKV 之后,我们来计算 attention 如下

$$
Attention(Q,K,V) = softmax(\frac{QK^{T}}{\sqrt{d_{k}}})V
$$

首先$QK^{T}$计算attention矩阵,也就是计算QK相关性,随后除上一个数是为了避免QK的值过大,如果QK计算的值之间差距过大的话,他们通过softmax之后最大值和最小值会无限接近于1和0,导致产生的梯度较小,模型无法更新

(2)Self-Attention 例子解释

下面参考“我想吃酸菜鱼”这个例子,看一下self-attention是如何一步步计算的(例子来源于网络)

对于“我想吃酸菜鱼”这个例子,每一个字都被嵌入到768维的空间中,因此$X$的输入维度为[6, 768]

接下来我们用$X$分别计算$QKV$,以$Q$为例,$Q.shape=[6,768]\times [768, 768]^{T}=[6,768]$,$K,V$同理

随后我们通过$QK$计算 attention 矩阵,我们用 “我”“我想吃酸菜鱼” 中的每一个字分别计算相似度,这里用的是内积的操作(dot product),计算后的权重如下图,计算完之后,我们需要对每一行进行 softmax 操作来归一化权重(即 “我”“我想吃酸菜鱼” 每一个字的关注度和为 1)

最后就是执行 attention 操作了,如下图,最终的“我”这一维度依然有 768 个元素,他的每一个元素都是“我想吃酸菜鱼”这六个字的特征加权得到

为什么是加权和?因为“我”这个位置的结果等于“我”对“我”的注意力乘上“我”这一维度的第一个元素,“我”对“想”的注意力乘上“想”的第一个元素,以此类推乘 6 次,768 维的每一维都是如此。也就是说“我”这个位置的最终结果就是“我想吃酸菜鱼”每一个特征的加权和,只是他们的权重有所不同

(3)Maybe 更通俗的解释?

(4)Self-Attention 计算示意图

假设我们有句子 “我 想 吃 酸菜鱼”,计算 Self-Attention 后,每个字的最终表示如下:

输入:
我    想    吃    酸    菜    鱼
 ↓      ↓      ↓      ↓      ↓      ↓   
QKV计算

Attention 计算(加权关系):
[我]  =  α₁₁[我] + α₁₂[想] + α₁₃[吃] + α₁₄[酸] + α₁₅[菜] + α₁₆[鱼]
[想]  =  α₂₁[我] + α₂₂[想] + α₂₃[吃] + α₂₄[酸] + α₂₅[菜] + α₂₆[鱼]
[吃]  =  α₃₁[我] + α₃₂[想] + α₃₃[吃] + α₃₄[酸] + α₃₅[菜] + α₃₆[鱼]
[酸]  =  α₄₁[我] + α₄₂[想] + α₄₃[吃] + α₄₄[酸] + α₄₅[菜] + α₄₆[鱼]
[菜]  =  α₅₁[我] + α₅₂[想] + α₅₃[吃] + α₅₄[酸] + α₅₅[菜] + α₅₆[鱼]
[鱼]  =  α₆₁[我] + α₆₂[想] + α₆₃[吃] + α₆₄[酸] + α₆₅[菜] + α₆₆[鱼]

最终输出:
我'   想'   吃'   酸'   菜'   鱼'

可以把 Self-Attention 想象成:

  1. 每个字都在和其他字开会,讨论自己的最终理解方式。
  2. 每个字都听取了其他字的意见,但听取的程度(权重)不同。
  3. 最终,每个字都变成了一个更加综合的表达,带有整个句子的全局信息。

2. Attention is all you need

上面我们讲解了 self-attention 机制,其实就是自己更新自己,看看自己对自己的哪些地方关注度高,哪些地方关注度低,而最初我们就说过,attention is all you need 这篇文章中的 encoder 和 decoder 交互不是 self-attention,而是 attention,如下图

这里 decoder 只是提供了 Q,KV 需要 encoder 提供,下面我们详细解释一下到底如何理解 transformer 中的 qkv,知乎

在 attention 中,Q 是一组查询语句,V 是数据库,里面有若干数据项。对于每一条查询语句,我们期望从数据库中查询出一个数据项(加权过后的)来。如何查询?这既要考虑每个 q 本身,又要考虑 V 中每一个项。如果用 K 表示一组钥匙,这组钥匙每一把对应 V 中每一项,代表了 V 中每一项的某种查询特征,(所以 K 和 V 的数量一定是相等的,维度则没有严格限制,做 attention 时维度和 q 一样只是为了在做点积时方便,不过也存在不用点积的 attention)。

然后对于每一个 Q 中的 q,我们去求和每一个 k 的 attention,作为对应 value 的加权系数,并用它来加权数据库 V 中的每一项,就得到了 q 期望的查询结果。所以 query 是查询语句,value 是数据项,key 是对应每个数据项的钥匙。名字起得是很生动的。不过和真正的数据库查询不一样的是,我们不仅考虑了查询语句,还把数据库中所有项都加权作为结果。所以说是全局的。

那么就会存在一个问题,为什么在 transformer 这篇文章中,encoder 的输出作为 KV,decoder 的作为 Q 呢?

  1. 对于一个文本,我们希望找到某张图片中和文本描述相关的局部图像,怎么办?文本作 query(查询),图像做 value(数据库)
  2. 对于一个图像,想要找一个文本中和图像所含内容有关的局部文本,如何设计?图像作 query,文本作 value.
  3. 自注意力(我查我自己):我们想知道句子中某个词在整个句子中的分量(或者相关文本),怎么设计?句子本身乘以三个矩阵得到 Q,K,V,每个词去查整个句子。
  4. 交叉注意力(查别人):transformer 模型的 decoder 中,由 decoder 的输入经过变换作为 query,由 encoder 的输出作为 key 和 value(数据库)。value 和 query 来自不同的地方,就是交叉注意力。可以看到 key 和 value 一定是代表着同一个东西。即:[Q,(K,V)]。如果用 encoder 的输出做 value,用 decoder 的输入做 key 和 query 那就完完全全不 make sense 了。所以从宏观上就可以判断谁该作 query,谁该作 value 和 key 。而不是乱设计。

对 self-attention 和 attention 做一个总结
(1)传统的 Attention 机制在一般任务的 Encoder-Decoder model 中,输入 Source 和输出 Target 内容是不一样的,比如对于英-中机器翻译来说,Source 是英文句子,Target 是对应的翻译出的中文句子,Attention 机制发生在 Target 的元素 Query 和 Source 中的所有元素之间。简单的讲就是 Attention 机制中的权重的计算需要 Target 来参与的,即在 Encoder-Decoder model 中 Attention 权值的计算不仅需要 Encoder 中的隐状态而且还需要 Decoder 中的隐状态。
(2) 而 Self Attention 顾名思义,指的不是 Target 和 Source 之间的 Attention 机制,而是 Source 内部元素之间或者 Target 内部元素之间发生的 Attention 机制,也可以理解为 Target=Source 这种特殊情况下的注意力计算机制。例如在 Transformer 中在计算权重参数时将文字向量转成对应的 KQV,只需要在 Source 处进行对应的矩阵操作,用不到 Target 中的信息。
比如翻译 I love china 这句话,我们已经翻译了"我",那么这里的 source 就是指的 I love china 这句话,target 就是"我"

3. Multi-head Attention

attention 的最后我们讲解 multi-head attention 的方法,如下图

一句话概括 multi-head 是怎么做的,参考”我想吃酸菜鱼“那个例子,我们将每一个字的特征映射到 768 维的空间中,我们将 768 等分为 3 份,即每份 256,然后分别对每一份进行 attention 操作,最后将分别 attention 的结果 concat 起来。这里的 3 就是 head 数量

为什么要用 multi-head?

这来自于作者的猜测,在 conv 中,我们有 channel 的概念,不同的 channel 代表不同的形式,因此作者也希望在 transformer 中包含这种范式,所以取了多个 head,希望 transformer 也能学习到 conv 中的那种形式,即不同的 channel(head)学习到不同的东西