神经翻译笔记7. Transformer

文章目录

神经翻译笔记7. Transformer

编码器-解码器架构,是使用神经网络方法作为机器翻译问题解决方案的基石,自问世以来广受关注,也获得了比较大的成功。然而,这种架构主要使用是RNN,而由前面对RNN的介绍可知,这种网络在时刻 t t t的隐藏状态是由前一时刻的隐藏状态和该时刻的输入共同计算得出,因此不太好并行化,在句子很长的时候容易陷入性能瓶颈。基于CNN的编码器试图解决这样的算力问题,但是如何学习长距离依赖又成为了一个难点。注意力机制使得模型能够对长距离依赖更好地建模,但是在被Bahdanau引入以后开始的一段时间该机制一直都紧密依附于底层的RNN,因此也受限于RNN按时间逐步展开的计算方式。对此,[Vaswani2017]提出了里程碑式的工作Transformer,发扬了注意力机制的优势,摒弃了RNN和CNN各自的弱点,将机器翻译的研究工作带入了一个新的时代。而在其基础上发展出来的BERT则更是将大部分NLP任务的SOTA效果提高到了一个新的高度

本文参考了如下资料

体系结构

本节所有配图如无特别说明均出自原文[Vaswani2017]

神经翻译笔记7. Transformer

上图给出了Transformer的整体结构,可见它仍然沿用了前述的编码器解码器架构(左为编码器,右为解码器),但是无论编码器和解码器都不再使用RNN,而是大量使用了注意力机制、残差连接 (图中的Add操作) 和层归一化 (图中的Norm操作)

  • 编码器包括了 N = 6 N=6 N=6个结构相同的层,每层由两部分(两个子层)构成,其一是多头自注意力机制,其二是一个全连接层。每个子层都会使用残差连接(图中的Add操作)然后接一个层归一化(图中的Norm操作),即各子层的输出为

    L a y e r N o r m ( x + S u b L a y e r ( x ) ) {\rm LayerNorm}(x + {\rm SubLayer}(x)) LayerNorm(x+SubLayer(x))

    其中 S u b L a y e r \rm SubLayer SubLayer是全连接操作或多头自注意力操作。编码器的所有向量(包括词向量)维度都为 d m o d e l = 512 d_{\rm model}=512 dmodel​=512。残差连接的使用使得训练可以更加稳定,保留更多原始信息,尤其是在Transformer有很多子层的情况下。关于LayerNorm的使用,相关学者作了若干研究,将在后面单独发文讨论

  • 解码器也分 N = 6 N=6 N=6层,每层结构也都相同。其与编码器不同的结构主要有两点:其一,是自注意力部分使用了掩码,使解码到位置 i i i时解码器看不到该位置后面的内容;其二,是在自注意力部分和全连接部分中间插了一个对编码器输出的多头注意力子层

在图中可以见到有两个比较新,同时比较重要的概念:多头注意力 (Multi-head Attention) 和位置编码

多头注意力

在介绍多头注意力机制之前,有必要再厘清注意力机制的一般形式,以及Transformer基本单元对注意力机制的使用方法

注意力机制的一般形式

注意力机制本质是学习一个函数,其接受输入可以看作是一种查询query,给出的输出是值向量value的加权和,权重由query和键key一起计算并经过softmax归一化后得出,每个键与每个值一一对应。该函数的核心是一个打分函数 S c o r e \rm Score Score,作用可以看作是判定各键向量 k i \boldsymbol{k}_i ki​与查询向量 q \boldsymbol{q} q的相似度。即

α i = exp ⁡ ( S c o r e ( q , k i ) ) ∑ j exp ⁡ ( S c o r e ( q , k j ) ) A t t e n t i o n ( q , K , V ) = ∑ i α i v i \begin{aligned} \alpha_i &= \frac{\exp({\rm Score}(\boldsymbol{q}, \boldsymbol{k}_i))}{\sum_j \exp({\rm Score}(\boldsymbol{q}, \boldsymbol{k}_j))} \\ {\rm Attention}(\boldsymbol{q}, \boldsymbol{K}, \boldsymbol{V}) &= \sum_i\alpha_i\boldsymbol{v}_i \end{aligned} αi​Attention(q,K,V)​=∑j​exp(Score(q,kj​))exp(Score(q,ki​))​=i∑​αi​vi​​

如果不考虑键值分开的情况,例如[Meng2018]所提出的网络结构,对一般NMT模型都有 k = v \boldsymbol{k} = \boldsymbol{v} k=v。对于前述的使用RNN的编码器解码器架构,每个查询向量 q \boldsymbol{q} q是解码器每一时间步最后一个隐藏层的输出向量,每个键向量 k i \boldsymbol{k}_i ki​是编码器每一时间步最后一个隐藏层的输出向量。对自注意力机制,通常有 q = k = v \boldsymbol{q} = \boldsymbol{k} = \boldsymbol{v} q=k=v。注意:这里不要和Transformer的真正实现相混淆,见下文

Transformer基本单元所使用的注意力机制

Transformer所使用的注意力机制的基础是内积注意力机制(即之前所提到的Luong注意力机制/乘性注意力机制),但是在此基础上引入了一个缩放因子 d k \sqrt{d_k} dk​ ​。引入该缩放因子的原因是文章认为当 d k d_k dk​比较大时,两个向量的内积也会比较大,所以会进入softmax函数的饱和区,导致softmax函数的梯度比较小。这样,对单个查询向量和键向量,有

S c o r e ( q , k i ) = ⟨ q , k i ⟩ d k \begin{aligned} {\rm Score}(\boldsymbol{q}, \boldsymbol{k}_i) &= \frac{\langle \boldsymbol{q}, \boldsymbol{k}_i \rangle}{\sqrt{d_k}} \\ \end{aligned} Score(q,ki​)​=dk​ ​⟨q,ki​⟩​​

将所有查询向量、键向量打包成矩阵,有

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 {\rm Attention}(\boldsymbol{Q},\boldsymbol{K},\boldsymbol{V}) = {\rm softmax}\left(\frac{\boldsymbol{Q}\boldsymbol{K}^\mathsf{T}}{\sqrt{d_k}}\right)\boldsymbol{V} Attention(Q,K,V)=softmax(dk​ ​QKT​)V

这里 Q K T \boldsymbol{QK}^\mathsf{T} QKT实际上就可以得到一个各查询词到各目标词的注意力热区图

下图给出了上述注意力机制的示意图 (图来自原始论文)

神经翻译笔记7. Transformer

多头注意力机制

所谓多头注意力机制,实际上就是模型计算注意力时不止依赖一个注意力函数,而是将前述的三个输入矩阵分别再做 h h h个线性变换 (这里 h h h是一个超参数)。这样模型能够联合获取不同位置上不同子表示空间的信息。Transformer每个基本单元的每个head都会学习三个线性变换矩阵 W Q ∈ R d m o d e l × d q , W K ∈ R d m o d e l × d k , W V ∈ R d m o d e l × d v \boldsymbol{W}^Q \in \mathbb{R}^{d_{\rm model} \times d_q}, \boldsymbol{W}^K \in \mathbb{R}^{d_{\rm model} \times d_k}, \boldsymbol{W}^V \in \mathbb{R}^{d_{\rm model} \times d_v} WQ∈Rdmodel​×dq​,WK∈Rdmodel​×dk​,WV∈Rdmodel​×dv​,对应作用于前述的查询矩阵、键矩阵和值矩阵 Q , K , V \boldsymbol{Q}, \boldsymbol{K}, \boldsymbol{V} Q,K,V。在得到各矩阵的注意力结果后,模型将其拼接起来并再做一次线性变换(学习另一个变换矩阵 W O ∈ R h d v × d m o d e l \boldsymbol{W}^O \in \mathbb{R}^{hd_v \times d_{\rm model}} WO∈Rhdv​×dmodel​),即:

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 ) W O 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 ) \begin{aligned} {\rm MultiHead}(\boldsymbol{Q}, \boldsymbol{K}, \boldsymbol{V}) &= {\rm Concat}({\rm head}_1, \ldots, {\rm head}_h)\boldsymbol{W}^O \\ {\rm head}_i &= {\rm Attention}\left(\boldsymbol{QW}^Q_i, \boldsymbol{KW}^K_i, \boldsymbol{VW}^V_i\right) \end{aligned} MultiHead(Q,K,V)headi​​=Concat(head1​,…,headh​)WO=Attention(QWiQ​,KWiK​,VWiV​)​

不同的 W \boldsymbol{W} W可以将输入映射到不同的空间,从而学到多种表示。这里head不同会造成映射矩阵不同,同时由于对自注意力机制有 Q = K = V \boldsymbol{Q} = \boldsymbol{K} = \boldsymbol{V} Q=K=V,对编-解码器注意力机制有 K = V \boldsymbol{K} = \boldsymbol{V} K=V,即便在同一个head里,由于使用的映射矩阵不同,同样的输入也可以映射到不同空间。在fairseq的多头注意力机制实现中,有一段代码能够说明输入向量与参与注意力机制计算的向量之间的关系

if self.self_attention:
    q = self.q_proj(query)
    k = self.k_proj(query)
    v = self.v_proj(query)
elif self.encoder_decoder_attention:
    # encoder-decoder attention
    q = self.q_proj(query)
    if key is None:
        assert value is None
        k = v = None
    else:
        k = self.k_proj(key)
        v = self.v_proj(key)

文章中有 d q = d k = d v = d m o d e l / h d_{q}=d_k=d_v = d_{\rm model}/h dq​=dk​=dv​=dmodel​/h。可以看到每个head进行了降维,个人认为是在保持计算量不变,避免参数量增多的情况下让表示在多个低维空间中解耦。(参考讨论

下图给出了多头注意力机制的示意图

神经翻译笔记7. Transformer

如Transformer体系结构图所示,多头注意力机制主要用在两处

  • 一处是编码器和解码器内部的自注意力,此时查询向量、键向量和值向量都由前一层的输出提供。注意编码器任意位置的向量可以看到前一层所有位置的向量,但是解码器不能这么做,对位置 i i i的向量不能看到其后各位置向量的信息,因此需要加上一个掩码,在做softmax之前将对应位置的信息都掩盖掉

    自注意力的好处是缩短了句子内任何位置之间单词的距离,因此可以保持更多远程依赖的信息。此外,自注意力机制的计算复杂度比较小,如果句子长度为 n n n,自注意力机制的计算复杂度是 O ( n 2 ⋅ d ) O(n^2\cdot d) O(n2⋅d),而RNN的复杂度是 O ( n ⋅ d 2 ) O(n\cdot d^2) O(n⋅d2),卷积核大小为 k k k的CNN是 O ( k ⋅ n ⋅ d 2 ) O(k\cdot n\cdot d^2) O(k⋅n⋅d2),因此当 n ≪ d n \ll d n≪d时(也就是大部分机器翻译使用的场景),自注意力的效率也比较高。当 n > d n > d n>d时,可以只让注意力机制注意某个单词周围的 r r r个词。此外,使用自注意力机制得到的模型可解释性比较强

  • 另一处是编码器和解码器之间的注意力,此时查询向量由前一层解码器的输出提供,键向量和值向量由编码器最后一层的输出提供。这遵循了经典的带注意力机制的编码器-解码器结构

每个位置上的前馈网络

无论是编码器还是解码器,每一层最后一个子层都会学习两个权重矩阵和偏置项,对输入每个位置上的向量做两次线性变换和一个非线性变换

F F N ( x ) = R e L U ( x W 1 + b 1 ) W 2 + b 2 {\rm FFN}(\boldsymbol{x}) = {\rm ReLU}(\boldsymbol{xW}_1+\boldsymbol{b}_1)\boldsymbol{W}_2+\boldsymbol{b}_2 FFN(x)=ReLU(xW1​+b1​)W2​+b2​

同一层不同位置上的向量共享这四个参数,但是同一位置不同层使用的变换各不相同。这也可以看作是使用大小为1的卷积核做两次一维卷积操作。 F F N \rm FFN FFN的输入输出维度都是 d m o d e l = 512 d_{\rm model}=512 dmodel​=512,中间维度 d f f = 2048 d_{\rm ff} = 2048 dff​=2048

FFN层的作用莫衷一是,有讨论认为其作用有两个:其一,混合了不同head之间的信息;其二,所有注意力相关的计算都是线性变换,FFN层引入了非线性变换,提升了网络的表达能力。实践中,去掉FFN会导致网络翻译效果BLEU降低,但是并不明显(大概1个点)

位置编码

由于模型中并没有使用RNN和CNN,因此需要一些其它方式捕捉词序信息。本文使用的方法是对每个位置 p o s pos pos使用一个固定的公式编码,得到一个 d m o d e l d_{\rm model} dmodel​维的位置向量信息。对第 p o s pos pos个位置的位置向量,其第 i i i个维度的分量计算方法为

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 ) \begin{aligned} {\rm PE}_{(pos, 2i)} &= \sin\left(pos/10000^{2i/d_{\rm model}}\right) \\ {\rm PE}_{(pos, 2i+1)} &= \cos\left(pos/10000^{2i/d_{\rm model}}\right) \end{aligned} PE(pos,2i)​PE(pos,2i+1)​​=sin(pos/100002i/dmodel​)=cos(pos/100002i/dmodel​)​

使用三角函数的好处是,由于sin/cos函数存在和差化积公式,对任意偏移量 k k k, P E p o s + k {\rm PE}_{pos + k} PEpos+k​可以表示为 P E p o s {\rm PE}_{pos} PEpos​的线性组合。在获得位置向量以后,将其与每个 p o s pos pos的词向量相加,就能得到最后送进Transformer第一层的向量

然而,并非只有这一种方法可以表达位置信息。以Transformer为基础的划时代预训练模型BERT就是直接像词向量一样学习位置的表示信息

其它

词嵌入

原始论文实现时,将词向量权重进行了增大,增大 d m o d e l \sqrt{d_{\rm model}} dmodel​ ​倍。文章并没有明确对这样的做法进行解释,有讨论认为是如果不做这样的增大,会使位置编码信息占比太多,影响模型对单词意思的表示

Dropout

原始论文在两处使用了dropout

  • 每个sublayer的输出。即更确切地有

L a y e r N o r m ( x + D r o p o u t ( S u b L a y e r ( x ) ) ) {\rm LayerNorm}(x + {\rm Dropout}({\rm SubLayer}(x))) LayerNorm(x+Dropout(SubLayer(x)))

  • 词嵌入和位置嵌入的加和
上一篇:高等工程数学总结


下一篇:微信小程序的组件封装中插槽的使用