7.1 注意力
回顾第 5 章,对于 word2vec 和其他静态嵌入,无论上下文如何,一个词的意义表示始终是同一个向量。例如,chicken 总是由同一个固定向量表示。因此,词 it 的静态向量或许能以某种方式编码“它是一个用于动物和无生命实体的代词”这一信息。但在具体上下文中,it 的意义要丰富得多。请看下面两个句子中的 it:
(7.1) The chicken didn’t cross the road because it was too tired.
(7.2) The chicken didn’t cross the road because it was too wide.
在 (7.1) 中,it 指 chicken(即读者知道是鸡累了);在 (7.2) 中,it 指 road(读者知道是路太宽)。[1] 也就是说,要计算句子的意义,就必须使第一句中 it 的意义与 chicken 相关联,而第二句中 it 的意义与 road 相关联;这种表示必须对上下文敏感。
进一步考虑像因果语言模型一样从左向右阅读,把句子处理到 it 为止:
(7.3) The chicken didn’t cross the road because it
此时,我们还不知道 it 最终会指代哪个事物!因此,这一时刻的 it 表示可能同时包含 chicken 和 road 的某些方面,就像读者正在猜测接下来会发生什么。
词语与可能相距很远的其他词语存在丰富语言关系,这一事实贯穿整个语言系统。再看两个例子:
(7.4) The keys to the cabinet are on the table.
(7.5) I walked along the pond, and noticed one of the trees along the bank.
在 (7.4) 中,短语 The keys 是句子的主语。在英语及许多语言中,主语必须与动词 are 在语法数上保持一致;这里两者都是复数。在英语中,不能让 is 这样的单数动词与 keys 这样的复数主语搭配(第 19 章将进一步讨论一致关系)。在 (7.5) 中,根据 pond 等上下文词语,我们知道 bank 指池塘或河流的岸边,而不是金融机构(第 9 章将进一步讨论词义)。
这些例子的共同要点是:帮助我们计算词语上下文意义的词,可能在句子或段落中相距很远。Transformer 可以整合这些有帮助的上下文词语之意义,构建词义的上下文表示 (contextual representation),即上下文嵌入 (contextual embedding)。在 Transformer 中,我们逐层构建越来越丰富的输入词元意义上下文化表示。在每一层,模型把上一层中关于词元 i i i 的信息与相邻词元的信息结合起来,为每个位置上的每个词产生上下文化表示。
注意力 (attention)是 Transformer 中的一种机制:它对第 k k k 层上下文中其他适当词元的表示进行加权和组合,从而构建第 k + 1 k+1 k + 1 层的词元表示。
图 7.3 第 k + 1 k+1 k + 1 层计算词 it 的表示时所用的自注意力权重分布 α \alpha α 。计算 it 的表示时,我们以不同程度关注第 k k k 层中的各个词;颜色越深,表示自注意力值越高。请注意,Transformer 高度关注与词元 chicken 和 road 对应的列。这一结果十分合理,因为 it 出现时既可能与 chicken 共指,也可能与 road 共指,所以我们希望 it 的表示能利用这些先前词语的表示。图片改编自 Uszkoreit(2017)。
图 7.3 展示了一个根据 Transformer 简化而来的示意例子(Uszkoreit, 2017)。图中描述的情形是:当前词元为 it,我们需要利用所有先前词元在第 k k k 层的表示,计算该词元在 Transformer 第 k + 1 k+1 k + 1 层的上下文表示。图中以颜色表示上下文词语上的注意力分布:词元 chicken 和 road 都具有较高的注意力权重,这意味着在计算 it 的表示时,我们会重点利用 chicken 和 road 的表示。这有助于构建 it 的最终表示,因为 it 最终会与 chicken 或 road 其中之一共指。
下面介绍这种注意力分布如何表示和计算。
7.1.1 注意力的形式化描述 ¶ 如前所述,注意力计算是一种为 Transformer 特定层中的某个词元计算向量表示的方法:它有选择地关注并整合上一层中先前词元的信息。注意力接收与位置 i i i 上输入词元对应的输入表示 x i \mathbf { x } _ { i } x i ,以及由先前输入 x 1 . . x i − 1 \mathbf { x } _ { 1 } . . \mathbf { x } _ { i - 1 } x 1 .. x i − 1 构成的上下文窗口,并产生输出 a i \mathbf { a } _ { i } a i 。
在因果式、从左到右的语言模型中,上下文可以包含任意先前词语。也就是说,处理 x i \mathbf { x } _ { i } x i 时,模型既能访问 x i \mathbf { x } _ { i } x i ,也能访问上下文窗口中所有先前词元的表示(上下文窗口可包含数千个词元),但不能访问 i i i 之后的词元。(相比之下,第 9 章会把注意力推广为也能向前查看未来词语的形式。)
图 7.4 展示了整个因果自注意力层中的信息流;同样的注意力计算会在每个词元位置 i i i 上并行执行。因此,一个自注意力层把输入序列 ( x 1 , . . . , x n ) \left( \mathbf { x } _ { 1 } , . . . , \mathbf { x } _ { n } \right) ( x 1 , ... , x n ) 映射为同样长度的输出序列 ( a 1 , . . . , a n ) \left( \mathbf { a } _ { 1 } , . . . , \mathbf { a } _ { n } \right) ( a 1 , ... , a n ) 。
图 7.4 因果自注意力中的信息流。处理每个输入 x i \mathbf { x } _ { i } x i 时,模型会关注直到并包括 x i \mathbf { x } _ { i } x i 在内的所有输入。
注意力的简化版本 从本质上说,注意力其实只是上下文向量的加权和;复杂之处在于如何计算权重以及具体对什么求和。出于教学目的,我们先介绍一种简化的注意力直觉:词元位置 i i i 上的注意力输出 a i \mathbf { a } _ { i } a i ,就是所有 j ≤ i j \leq i j ≤ i 的表示 x j \mathbf { x } _ { j } x j 的加权和。我们用 α i j \alpha _ { i j } α ij 表示 x j \mathbf { x } _ { j } x j 应当对 a i \mathbf { a } _ { i } a i 贡献多少:
Simplified version: a i = ∑ j ≤ i α i j x j (7.6) \text { Simplified version: } \quad \mathbf {a} _ {i} = \sum_ {j \leq i} \alpha_ {i j} \mathbf {x} _ {j}\tag{7.6} Simplified version: a i = j ≤ i ∑ α ij x j ( 7.6 ) 每个 α i j \alpha _ { i j } α ij 都是一个标量:对输入求和以计算 a i \mathbf { a } _ { i } a i 时,它用于给输入 x j \mathbf { x } _ { j } x j 的值加权。这个 α \alpha α 权重应当怎样计算?在注意力机制中,我们根据每个先前嵌入与当前词元 i i i 的相似程度,按比例为它加权。因此,注意力的输出是先前词元嵌入的总和,每个嵌入的权重取决于它与当前词元嵌入的相似度。我们使用点积计算相似度分数;点积把两个向量映射为一个取值从 − ∞ -\infty − ∞ 到 ∞ \infty ∞ 的标量。分数越大,所比较的向量越相似。随后使用 softmax 对这些分数进行归一化,得到权重向量 α i j , j ≤ i \alpha _ { i j }, j \leq i α ij , j ≤ i 。
Simplified Version: score ( x i , x j ) = x i ⋅ x j (7.7) \text { Simplified Version: } \quad \operatorname{score} (\mathbf {x} _ {i}, \mathbf {x} _ {j}) = \mathbf {x} _ {i} \cdot \mathbf {x} _ {j}\tag{7.7} Simplified Version: score ( x i , x j ) = x i ⋅ x j ( 7.7 ) α i j = softmax ( score ( x i , x j ) ) ∀ j ≤ i (7.8) \alpha_ {i j} = \operatorname{softmax} (\operatorname{score} (\mathbf {x} _ {i}, \mathbf {x} _ {j})) \quad \forall j \leq i\tag{7.8} α ij = softmax ( score ( x i , x j )) ∀ j ≤ i ( 7.8 ) 因此,在图 7.4 中,我们通过计算三个分数 x 3 ⋅ x 1 \mathbf { x } _ { 3 } \cdot \mathbf { x } _ { 1 } x 3 ⋅ x 1 、x 3 ⋅ x 2 \mathbf { x } _ { 3 } \cdot \mathbf { x } _ { 2 } x 3 ⋅ x 2 和 x 3 ⋅ x 3 \mathbf { x } _ { 3 } \cdot \mathbf { x } _ { 3 } x 3 ⋅ x 3 来计算 a 3 \mathbf { a } _ { 3 } a 3 ;再用 softmax 将其归一化,并把所得概率作为权重,表示各输入与当前位置 3 的相对相关程度。当然,x i \mathbf { x } _ { i } x i 的 softmax 权重很可能最高,因为 x i \mathbf { x } _ { i } x i 与自身非常相似,会产生很高的点积。但其他上下文词也可能与 i i i 相似,softmax 同样会给这些词分配一定权重。然后,我们把这些权重作为公式 7.6 中的 α \alpha α 值,计算加权和 a 3 \mathbf { a } _ { 3 } a 3 。
公式 7.6–7.8 中的简化注意力展示了计算 a i \mathbf { a } _ { i } a i 的注意力方法:把 x i \mathbf { x } _ { i } x i 与先前向量比较,把分数归一化为概率分布,再用该分布为先前向量之和加权。现在,我们可以去掉这些简化。
使用查询、键和值矩阵的单个注意力头 在理解注意力的简单直觉后,下面介绍真正的注意力头 (attention head),也就是 Transformer 实际使用的注意力版本。(在 Transformer 中,head 一词经常用于指特定的结构化层。)注意力头使我们能够分别表示每个输入嵌入在注意力过程中扮演的三个不同角色:
• 当前元素,与先前输入进行比较。我们把这一角色称为查询 (query)。
• 作为先前输入,与当前元素进行比较,以确定相似度权重。我们把这一角色称为键 (key)。
• 最后,作为先前元素的值 (value),经过加权和求和,用于计算当前元素的输出。
为了表示这三个不同角色,Transformer 引入权重矩阵 W Q \mathbf{W}^{Q} W Q 、W K \mathbf{W}^{K} W K 和 W V \mathbf{W}^{V} W V 。这些权重把每个输入向量 x i \mathbf { x } _ { i } x i 投影为它充当查询、键或值时的表示:
q i = x i W Q ; k i = x i W K ; v i = x i W V (7.9) \mathbf {q} _ {i} = \mathbf {x} _ {i} \mathbf {W} ^ {\mathbf {Q}}; \quad \mathbf {k} _ {i} = \mathbf {x} _ {i} \mathbf {W} ^ {\mathrm{K}}; \quad \mathbf {v} _ {i} = \mathbf {x} _ {i} \mathbf {W} ^ {\mathrm {V}}\tag{7.9} q i = x i W Q ; k i = x i W K ; v i = x i W V ( 7.9 ) 有了这些投影,计算当前元素 x i \mathbf { x } _ { i } x i 与某个先前元素 x j \mathbf { x } _ { j } x j 的相似度时,我们会计算当前元素的查询向量 q i \mathbf { q } _ { i } q i 与先前元素的键向量 k j \mathbf { k } _ { j } k j 之间的点积。此外,点积结果可以是任意大的正值或负值;对很大的值取指数可能造成数值问题,并使训练期间的梯度丢失。为避免这一问题,我们根据嵌入大小对点积进行缩放:除以查询和键向量维度 d k d_k d k 的平方根。因此,我们用公式 7.11 取代简化的公式 7.7。随后产生 α i j \alpha _ { i j } α ij 的 softmax 计算保持不变,但注意力头的输出现在以值向量 v \mathbf v v 的加权和为基础(公式 7.13)。
下面给出从单个输入向量 x i \mathbf { x } _ { i } x i 计算单个自注意力输出向量 a i \mathbf { a } _ { i } a i 的最终公式。该版本通过求先前元素值的总和来计算 a i \mathbf { a } _ { i } a i ;每个值的权重由其键与当前元素查询的相似度决定:
q i = x i W Q ; k j = x j W K ; v j = x j W V (7.10) \mathbf {q} _ {i} = \mathbf {x} _ {i} \mathbf {W} ^ {\mathrm{Q}}; \quad \mathbf {k} _ {j} = \mathbf {x} _ {j} \mathbf {W} ^ {\mathrm{K}}; \quad \mathbf {v} _ {j} = \mathbf {x} _ {j} \mathbf {W} ^ {\mathrm{V}}\tag{7.10} q i = x i W Q ; k j = x j W K ; v j = x j W V ( 7.10 ) score ( x i , x j ) = q i ⋅ k j d k (7.11) \operatorname{score} \left(\mathbf {x} _ {i}, \mathbf {x} _ {j}\right) = \frac {\mathbf {q} _ {i} \cdot \mathbf {k} _ {j}}{\sqrt {d _ {k}}}\tag{7.11} score ( x i , x j ) = d k q i ⋅ k j ( 7.11 ) α i j = softmax ( score ( x i , x j ) ) ∀ j ≤ i (7.12) \alpha_ {i j} = \text {softmax} (\operatorname {score} (\mathbf {x} _ {i}, \mathbf {x} _ {j})) \forall j \leq i\tag{7.12} α ij = softmax ( score ( x i , x j )) ∀ j ≤ i ( 7.12 ) h e a d i = ∑ j ≤ i α i j v j (7.13) \mathbf {h e a d} _ {i} = \sum_ {j \leq i} \alpha_ {i j} \mathbf {v} _ {j}\tag{7.13} head i = j ≤ i ∑ α ij v j ( 7.13 ) a i = h e a d i W O (7.14) \mathbf {a} _ {i} = \mathbf {h e a d} _ {i} \mathbf {W} ^ {\mathrm{O}}\tag{7.14} a i = head i W O ( 7.14 ) 图 7.5 以计算序列中第三个输出 a 3 \mathbf a_3 a 3 的值为例,展示了这一过程。
请注意,我们还引入了一个矩阵 W O \mathbf{W} ^ { \mathrm{O} } W O ,它与注意力头相乘。为了重塑注意力头输出的形状,这个矩阵是必需的。注意力输入 x i \mathbf { x } _ { i } x i 和注意力输出 a i \mathbf { a } _ { i } a i 都具有相同的维度 [ 1 × d ] [1 \times d] [ 1 × d ] 。我们经常把 d d d 称为模型维度 (model dimensionality)。正如第 7.2 节将讨论的,每个 Transformer 块的输出 h i \mathbf { h } _ { i } h i 以及 Transformer 块内部的中间向量,也都具有相同的维度 [ 1 × d ] [1 \times d] [ 1 × d ] 。所有内容具有相同维度,使 Transformer 具有很强的模块化特性。
图 7.5 使用因果式(从左到右)自注意力,计算序列第三个元素 a 3 \mathbf a_3 a 3 的值。
下面讨论各张量的形状。我们如何从输入处的 [ 1 × d ] [1 \times d] [ 1 × d ] 得到输出处的 [ 1 × d ] [1 \times d] [ 1 × d ] ?来看全部内部形状。查询向量和键向量使用维度 d k d_k d k ,两者的维度均为 [ 1 × d k ] [1 \times d_k] [ 1 × d k ] ,因此可以计算点积 q i ⋅ k j \mathbf{q}_i \cdot \mathbf{k}_j q i ⋅ k j ,得到一个标量。值向量另用维度 d v d_v d v 。变换矩阵 W Q \mathbf{W} ^ { \mathrm { Q } } W Q 的形状为 [ d × d k ] [d \times d_k] [ d × d k ] ,W K \mathbf{W} ^ { \mathrm { K } } W K 为 [ d × d k ] [d \times d_k] [ d × d k ] ,W V \mathbf{W} ^ { \mathrm { V } } W V 为 [ d × d v ] [d \times d_v] [ d × d v ] 。因此,公式 7.13 中 h e a d i \mathrm{head}_i head i 的输出形状为 [ 1 × d v ] [1 \times d_v] [ 1 × d v ] 。为了得到所需的输出形状 [ 1 × d ] [1 \times d] [ 1 × d ] ,必须重塑注意力头输出,所以 W O \mathbf W^{\mathrm{O}} W O 的形状为 [ d v × d ] [d_v \times d] [ d v × d ] 。在最初的 Transformer 工作中(Vaswani et al., 2017),d d d 为 512,d k d_k d k 和 d v d_v d v 均为 64。
多头注意力 公式 7.11–7.13 描述的是单个注意力头。但实际上,Transformer 会使用多个注意力头。其直觉是,每个头可能出于不同目的关注上下文:不同的头可以专门表示上下文元素与当前词元之间不同的语言关系,或者在上下文中寻找特定类型的模式。
因此,在多头注意力 (multi-head attention)中,同一模型深度上并行排列着 A A A 个独立注意力头。每个头都有自己的一组参数,使它能够对输入之间关系的不同方面进行建模。于是,自注意力层中的每个头 c c c 都有自己的查询、键和值矩阵:W Q c \mathbf{W} ^ { \mathrm { Qc } } W Qc 、W K c \mathbf{W} ^ { \mathrm { Kc } } W Kc 和 W V c \mathbf{W} ^ { \mathrm { Vc } } W Vc 。模型用这些矩阵把输入投影为每个头各自的查询、键和值嵌入。
使用多个头时,输入和输出仍采用模型维度 d d d ;查询与键嵌入的维度为 d k d_k d k ,值嵌入的维度为 d v d_v d v 。(在最初的 Transformer 论文中,d k = d v = 64 d_k=d_v=64 d k = d v = 64 、A = 8 A=8 A = 8 、d = 512 d=512 d = 512 。)因此,对每个头 c c c ,权重层 W Q c \mathbf{W} ^ { \mathrm { Qc } } W Qc 的形状为 [ d × d k ] [d \times d_k] [ d × d k ] ,W K c \mathbf{W} ^ { \mathrm { Kc } } W Kc 的形状为 [ d × d k ] [d \times d_k] [ d × d k ] ,W V c \mathbf{W} ^ { \mathrm { Vc } } W Vc 的形状为 [ d × d v ] [d \times d_v] [ d × d v ] 。
下面是在注意力中加入多个头之后的公式;图 7.6 展示了其直观原理。
q i c = x i W Q c ; k j c = x j W K c ; v j c = x j W V c ; 1 ≤ c ≤ A (7.15) \mathbf {q} _ {i} ^ {c} = \mathbf {x} _ {i} \mathbf {W} ^ {\mathrm {Q c}}; \quad \mathbf {k} _ {j} ^ {c} = \mathbf {x} _ {j} \mathbf {W} ^ {\mathrm {K c}}; \quad \mathbf {v} _ {j} ^ {c} = \mathbf {x} _ {j} \mathbf {W} ^ {\mathrm {V c}}; \quad 1 \leq c \leq A\tag{7.15} q i c = x i W Qc ; k j c = x j W Kc ; v j c = x j W Vc ; 1 ≤ c ≤ A ( 7.15 ) score c ( x i , x j ) = q i c ⋅ k j c d k (7.16) \operatorname{score} ^ {c} \left(\mathbf {x} _ {i}, \mathbf {x} _ {j}\right) = \frac {\mathbf {q} _ {i} ^ {c} \cdot \mathbf {k} _ {j} ^ {c}}{\sqrt {d _ {k}}}\tag{7.16} score c ( x i , x j ) = d k q i c ⋅ k j c ( 7.16 ) α i j c = softmax ( score c ( x i , x j ) ) ∀ j ≤ i (7.17) \alpha_ {i j} ^ {c} = \text {softmax} (\text {score} ^ {c} (\mathbf {x} _ {i}, \mathbf {x} _ {j}))\quad \forall j \leq i\tag{7.17} α ij c = softmax ( score c ( x i , x j )) ∀ j ≤ i ( 7.17 ) h e a d i c = ∑ j ≤ i α i j c v j c (7.18) \mathbf {h e a d} _ {i} ^ {c} = \sum_ {j \leq i} \alpha_ {i j} ^ {c} \mathbf {v} _ {j} ^ {c}\tag{7.18} head i c = j ≤ i ∑ α ij c v j c ( 7.18 ) a i = ( h e a d 1 ⊕ h e a d 2 ⋯ ⊕ h e a d A ) W O (7.19) \mathbf {a} _ {i} = \left(\mathbf {h e a d} ^ {1} \oplus \mathbf {h e a d} ^ {2} \dots \oplus \mathbf {h e a d} ^ {A}\right) \mathbf {W} ^ {\mathrm{O}}\tag{7.19} a i = ( head 1 ⊕ head 2 ⋯ ⊕ head A ) W O ( 7.19 ) MultiHeadAttention ( x i , [ x 1 , … , x i − 1 ] ) = a i (7.20) \text { MultiHeadAttention } (\mathbf {x} _ {i}, [ \mathbf {x} _ {1}, \dots , \mathbf {x} _ {i - 1} ]) = \mathbf {a} _ {i}\tag{7.20} MultiHeadAttention ( x i , [ x 1 , … , x i − 1 ]) = a i ( 7.20 ) 图 7.6 对输入 x i \mathbf { x } _ { i } x i 进行多头注意力计算,产生输出 a i \mathbf { a } _ { i } a i 。一个多头注意力层包含 A A A 个注意力头,每个头都有自己的查询、键和值权重矩阵。为了能在页面中容纳,本图令 A = 4 A = 4 A = 4 ,这个数值小于通常采用的头数。每个注意力头的输出形状为 [ 1 × d v ] [ 1 \times d _ { v } ] [ 1 × d v ] ;这些输出被拼接起来,再由矩阵 W O \mathbf { W } ^ { O } W O 投影到另一个空间。通常会设置各头的维度 d v d _ { v } d v ,使 d v = d / A d _ { v } = d / A d v = d / A ,于是 W O \mathbf { W } ^ { O } W O 是形状为 [ A d v × d ] = [ d × d ] [ A d _ { v } \times d ] = [ d \times d ] [ A d v × d ] = [ d × d ] 的方阵。最终结果被投影到 d d d 维,因而输出与输入大小相同。
请注意,在公式 7.20 中,MultiHeadAttention 是当前输入 x i x_i x i 以及所有其他输入的函数。对于本章使用的因果式或从左到右注意力,其他输入只位于左侧;第 9 章还会介绍一种注意力版本,其中注意力也是右侧词元的函数。公式 7.35 引入屏蔽右侧上下文的概念时,我们将重新讨论因果输入这一思想。
A A A 个头中的每个头,其输出形状都是 [ 1 × d v ] [1 \times d_v] [ 1 × d v ] 。因此,具有 A A A 个头的多头层会输出 A A A 个形状为 [ 1 × d v ] [1 \times d_v] [ 1 × d v ] 的向量。把它们拼接起来,可得到维度为 [ 1 × A d v ] [1 \times A d_v] [ 1 × A d v ] 的单个输出。随后再使用一个线性投影 W O ∈ R A d v × d \mathbf { W } ^ { \mathrm { O } } \in \mathbb { R } ^ { A d _ { v } \times d } W O ∈ R A d v × d 重塑该输出,最终在每个输入位置 i i i 得到输出形状正确、为 [ 1 × d ] [1 \times d] [ 1 × d ] 的多头注意力向量 a i \mathbf a_i a i 。注意力头的维度 d v d_v d v 被设为 d v = d / A d_v=d/A d v = d / A ,这意味着多头注意力的参数开销与单个注意力头大致相同。