文章目录
前言
本人在学习自然语言处理的过程中看到了一篇特别好的关于Transformer介绍的文章,于是进行了翻译与总结,原文参@JayAlammar 。
在之前的工作中,我们学习了注意力机制,注意力机制能够用来提高神经网络在机器翻译领域的使用性能。在本文中,我们将介绍基于注意力机制的Transformer结构。谷歌云建议使用Transformer作为参考模型来使用其Cloud TPU产品。Transformer结构最大的优势在于其可以进行并行化运算,从而大大提高效率,本文将拆解Transfomer结构来了解其内部机理。
在本文中,我们尝试把事情简化一点,由大到小逐层介绍概念,希望能够让小白能够通过本文了解Transformer的结构。
以层视角了解Transformer结构
- 首先将模型看作一个单独的黑盒,在机器翻译应用程序中,它将获取一种语言的句子,并将其输出为另一种语言的翻译。
- 将黑盒打开我们可以看到该模型由编码器与解码器构成,在编码器与解码器之间存在联系。
3. 可以看到编码器由6个编码器组件构成,相应的解码器也由6个解码器组件构成。
4. 每一个编码器组件由以下两层网络构成,各编码器组件之间不共享权重。
4. 编码器组件的的输入首先经过一个自注意力层,该层能够在编码器输入句子时查看重要词汇(因为其对重要词汇进行了特殊编码),后文将详细介绍自注意力机制。之后将自注意力的输出输入到前馈神经网络。The exact same feed-forward network is independently applied to each position.
解码器组件在编码器组件的基础上添加了一个编码-解码注意力层,该层能够关注到输入句子中与输出相关的部分。
引入张量
上文对模型的主要架构进行了介绍,本节主要介绍张量(向量)以及其在各个模块的运算,从而将训练过的模型的输入转化为输出。
与一般的自然语言处理方式相同,我们首先将输入词汇转化为其词嵌入表示(将每一个词汇表示为一个维度为512的向量,下图用方格表示这些向量)。
在将模型输入到编码器之前,需要将输入文本中每一个句子转换为一个张量列表,张量列表中每一个张量的大小为512,列表的大小是一个超参数,可以人为设置,在训练句子中过程列表长度一般取文本中最大的句子的长度。
在词嵌入后,将经过词嵌入获得的张量列表输入到有两层网络构成的编码器组建中。
词嵌入张量在编码器组件中进行计算时存在不同,在自注意力层,不同位置输入的词嵌入计算存在依赖关系,而在前馈网络层不同位置的词向量之间的计算是相互独立的,因此词向量在前馈网络层计算时是可以并行化的。
后文将例子聚焦到更短的句子中从而观察编码器组件子层中的具体计算。
编码器组件
正如前文所说,将张量列表输入到编码器组件中首先经过一个自注意力层,之后经过一个前馈网络层,之后将输出的张量列表输入到下一个编码器组件中。如下图所示,每个位置的单词首先经过一个自注意力层,之后每一个张量都各自地通过一个前馈网络层——完全相同的网络。
概念层面理解自注意力
不要看到自注意力这个概念就望而却步,本文作者在阅读论文 the Attention is All You Need之前就从未接触过这个概念,下文将介绍该论文的主要工作。
假定下文是我们想要翻译的句子:
The animal didn't cross the street because it was too tired.
例子中的it
表示什么含义,是指代animal
还是street
。人类很容易理解其意思,但算法(algorithm)很难理解该问题。在模型处理单词it
时,自注意力机制将it
与animal
联系起来。
当模型处理每个单词(输入序列中的每个位置)时自注意机制使其能够查看输入序列中其他位置,从而寻找有助于更好地编码这个单词的信息。
在RNN网络中,循环网络能够在隐藏层将之前输入的单词向量与当前输入单词的向量进行融合,自注意力机制是Transformer结构中将对其他相关词汇的“理解”融入当前正在处理的词汇中的方法。
it
时,注意力机制的一部分集中于The animal
,并将其一部分表现形式融入到it
的编码中。
可以通过Tensor2Tensor notebook加载Transformer模型并可视化此模型。
详解自注意力
本节将学习如何用向量计算自注意力,之后介绍用矩阵实现自注意力的计算。
首先,将输入单词的嵌入与三个向量(这些向量在训练时会不断更新权值)相乘获得三个向量Query、Key、Value。
其次,计算自注意力的得分,假设我们需要计算这个例子中的第一个单词Thinking
的得分,需要计算该句子中每一个单词与该词之间的得分,此得分决定了当我们在某个位置编码一个单词时,我们对输入句子的其他位置投入了多少注意力。
可以通过计算要获得打分的单词query与句中各个单词key的点积来获取得分,因此当我们要计算#1位置单词的自注意力时,可以通过计算
q
1
q_1
q1和
k
1
k_1
k1的点积来获取得分1,通过计算
q
1
q_1
q1和
k
2
k_2
k2的点积来获取得分2。
在第三步,将得分除以8(本文使用的关键词向量的维数为64,其平方根为8,除数可以取其他值,这里是默认值)。
在第四步,将第三步结果通过softmax操作计算,Softmax将第三步获得的得分归一化,使它们都为正,且加起来和为1。
softmax得分决定了句中每个单词在当前位置的影响力大小,显而易见,当前位置的单词将会获得最大的softmax得分,但在处理当前位置的单词时,关注其他位置的单词也具有重要作用。
在第五步,将每个value向量与softmax得分相乘(准备将它们相加)。此处的想法是保持我们想要关注单词的value不变,并淹没不相关的单词的value(例如,通过将它们乘以微小的数字,如0.001)。
在第六步,对第五步的权重向量求和。此步骤将获得在句中当前位置的自注意力层输出。
至此,本文介绍了对于向量的自注意力计算过程。获得的向量可以发送给前馈神经网络层进行计算。但是在实际情况中,为了更快地处理这些计算,这些计算是用矩阵的形式进行的。
矩阵层面的自注意力计算
第一步,计算Query、Key、Value的矩阵。通过词嵌入矩阵X乘权重矩阵
W
Q
W^Q
WQ、
W
k
W^k
Wk、
W
v
W^v
Wv来获得相应的矩阵Q、K、V。
多头注意力
通过添加多头注意力机制,该论文进一步细化了自注意力层。这主要在两个方面改善了注意力层的执行能力。
- 其扩大了模型聚焦于不同位置的能力,在上节的例子中,Z1包含了各个单词的部分信息,但其包含的信息可以被真正决定句子含义的单词所主导。
- 它给注意层提供了多个“表示子空间”。如下文所示,在计算多头注意力时,X与一系列的Query、Key、Value权重矩阵相乘(Transformer使用8头注意力,所以最终的编码器与解码器与8组),每一组的参数都是随机初始化的。在训练之后,输入词嵌入(或来自低层的编码/解码器)与每一组权重相乘便可以产生不同的表示子空间。
上述自注意力计算过程完全相同,用8个不同的权重矩阵进行8次不同的计算,最终获得8个不同的Z矩阵。
这里存在一个问题,前馈层输入是一个矩阵,而这里产生了8个矩阵,所以我们需要一种方法把这8个矩阵压缩成1个矩阵。
如何进行计算了?可以将这8个矩阵拼接,之后和一个权重矩阵
W
o
W^o
Wo相乘来计算结果。
以上为多头注意力的全部内容。此处涉及多个矩阵。将这些计算过程汇总到一张图上如下图所示。
此处谈及了头注意力,回顾之前的例子,当我们在例句中编码单词“it”时,可以由下图看出不同的头注意力都集中在哪里。
然而,如果我们把所有的注意力都集中在这幅图上,注意力就更难解释了:
添加位置信息
上文所介绍的模型存在问题,即在建模的过程中未考虑单词的顺序。
为了添加位置信息,Transformer结构在输入词嵌入添加了一个向量。这些向量采用固定的公式计算,该方法有助于确定每个单词的位置,或序列中不同单词之间的距离。
这里的直觉是将位置相关的向量添加到词嵌入中,之后通过计算投影到Q、K、V向量中,之后进行点积相关的注意力计算,从而输入的嵌入向量便会提供距离信息。
假设嵌入的维数是4,则实际的位置编码方式如下文所示:
位置信息情况介绍:
如下图所示,每一行都对应于一个向量的位置编码,第一行是我们添加到输入序列中第一个单词的嵌入向量。每行包含512个值——每个值都在1到-1之间。用颜色标记其序列值便可以可视化数据。
位置编码的公式在论文第3.5节中有所介绍。可以通过函数get_timing_signal_1d()来生成位置编码。 位置编码的方式有多种,其优点是能够扩展到看不见的序列长度(例如训练的模型被要求翻译一个比我们训练集中的任何句子都长的句子)。
2020年7月更新:上面显示的位置编码来自Transformer的transformer2transformer实现。该论文中所展示的方法与上文介绍的位置词嵌入方法略有不同,其不是直接连接正弦与余弦产生的两个信号,而是交织地使用这两个信号。下图显示了其可视化情况。点击此处可获得相应代码。
计算残差
需要注意一个细节,在编码器组件的Self-Attention与Feed Forward层之间有一个Add&Normalize层,该层将Self-Attention输入前后的数据进行拼接,之后再经过一个归一化层进行归一化操作。
将Add&Normalize层可视化如下图所示。
解码器中也存在相同的Add&Normalize操作。如下图所示为由两个编码器组件与一个解码器组件组成的Transformer结构。
解码器介绍
通过上文的学习便对编码器与解码器都有了比较深的了解,下文将介绍两者是如何协同工作的。
编码器从最底层编码器组件开始处理输入序列,直至顶部编码器输出注意力向量K和V,注意力向量K和V将在解码器组件的Encoder-Decoder Attention层中使用,通过此种方法解码器可以关注到输入序列的位置信息。
重复上文所述过程直至出现一个特殊符号,此时transformer的解码器组件结束输出。每一步的输出在下一步被馈送到底部解码器组件中,解码器组件将像编码器组件一样冒泡解码结果。就像我们对编码器输入所做的那样,我们将位置编码嵌入并添加到这些解码器组件输入中以指示每个单词的位置。
解码器组件与编码器组件的区别:
- 在解码器组件中,self-attention层只允许在输出序列中添加较早的位置。在注意力计算中该操作可通在softmax操作之前设置遮罩来实现,具体操作为将不需要传递的值初始化为-inf。
- 解码器的“Encoder-Decoder Attention” 层类似于多头注意力,该层从其下层获取Q矩阵,并从编码器组件堆栈的输出中获取矩阵K和V。
Linear层与Softmax层
解码器堆栈输出一个浮点数向量。如何将其转换为一个单词是Softmax层与Linear层的工作。
Linear层:线性层是一个简单的全连接网络,其将解码器组件产生的向量投射到一个更大的Logits向量。假设输出词汇为10000,则Logits向量的输出也为10000,每一个向量上的数字对应一个词汇。
Softmax层:Softmax层将Linear层的输出转化为[0,1]之间的概率,这些概率之和为1,并且选概率最高的词汇作为输出。
模型训练
上文通过Transformer模型的训练过程对其整个前向传播过程做了介绍,下文将介绍其在训练过程中的一些细节。
无论是训练获取模型参数还是利用训练好的模型进行预测,其前行传播过程都一样,不同之处在于在模型训练的过程中,需要将输出结果与实际标签进行比较。
为了可视化问题,假定输出仅包含6个词汇(“a”, “am”, “i”, “thanks”, “student”以及 “” (结束标志))。其与数字序列的对应关系如下。
在确定了词汇表之后,可以用长度相同的向量来表示每一个词汇,其中一种是one-hot模式,如单词“am”可以如下的向量表示:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/12364283984eda564f50c30c339fbd8e.png#pic_center)
在本节的基础上,下文将介绍损失函数,训练阶段的目的就是最小化损失函数。
损失函数
假设我们正在训练模型,模型的第一步是把“merci”翻译成“thanks”,这意味着我们希望输出中“thanks”这个词的概率最大,但由于该模型还没有经过训练,这种情况发生的概率微乎其微。
可以通过计算交叉熵损失来比较两种概率分布的差别。其实际原理是通过作差来计算实际输出与期望输出的误差。具体细节参考 cross-entropy 和 Kullback–Leibler divergence。
上文介绍的一个句子仅由1个单词构成,大多数情况一个句子由多个单词构成,如输入“je suis étudiant”对应输出“i am a student”。这意味着我们希望模型连续输出概率分布,其中:
- 每个概率分布由宽度为vocab_size的向量表示(在上文给出的例子中vocab_size的尺寸为6,但实际情况尺寸为30000或50000n);
- 第一个输出的概率分布在单词“i”相关的单元格上概率最高;
- 第二个输出的概率分布在单词“am”相关的单元格上概率最高;
- 以此类推直到第五个输出分布表示“”符号,该符号也有一个与10000个元素词汇表相关联的单元格。
在一个较大的数据集上进行多次训练,我们希望其输出的概率分布如下:
- 由于模型每次产生一个输出,假设模型从概率分布中选择概率最高的单词作为最终结果并丢弃其余的单词。这是一种贪婪解码的输出结果的方法;
- 另一种方法是保留上面的两个单词(例如,“I”和“a”),然后在训练中运行模型两次:一次假设第一个输出位置是单词“I”,另一次假设第一个输出位置是单词“a”,因为模型同时考虑了位置#1和#2,所以模型出错的概率比较小。同理对位置#2和#3进行同样的操作,依次进行直至句子结束,该方法称为“光束搜索”。
结论与展望
本文对Transformer进行了简要介绍,想要深入理解可以学习以下知识:
- 阅读论文Attention Is All You Need或Transformer博客Transformer: A Novel Neural Network Architecture for Language Understanding、Tensor2Tensor announcement;
- 观看视频 Łukasz Kaiser’s talk 来了解模型及其细节;
- 学习并实践Jupyter Notebook provided as part of the Tensor2Tensor repo;
- 学习Tensor2Tensor repo。
后续工作: - Depthwise Separable Convolutions for Neural Machine Translation
- One Model To Learn Them All
- Discrete Autoencoders for Sequence Models
- Generating Wikipedia by Summarizing Long Sequences
- Image Transformer
- Training Tips for the Transformer Model
- Self-Attention with Relative Position Representations
- Fast Decoding in Sequence Models using Discrete Latent Variables
- Adafactor: Adaptive Learning Rates with Sublinear Memory Cost
致谢:
感谢Illia Polosukhin, Jakob Uszkoreit, Llion Jones , Lukasz Kaiser, Niki Parmar,和Noam Shazeer 为本文提出的宝贵意见。
可以通过Twitter来留下您宝贵的意见。