译自:https://jalammar.github.io/illustrated-transformer/
在之前的文章中作者介绍了Attention机制,在处理当下的深度学习任务中attention机制无处不在,他可以提升机器翻译应用的表现。在接下来的这篇文章中作者将介绍Transformer,一个用attention加速并且可训练的模型。在一些特定的任务上Transformer模型的表现效果要比Google Neural Machine Translation的效果还要好,其最大的优点是它的并行性。实际上 Google Cloud’s推荐将Transformer作为Cloud TPU的推导模型。现在我们将Transformer拆解开来看看它是如何工作的。
Transformer在文章 Attention is All You Need中提出,其中的TensorFlow中的Transformer应用的是Tensor2Tensor的子模块。哈佛的NLP团队专门制作了对应的PyTorch的指南说明。本文旨在简化难度,一步一步地解释其中的概念,希望有助于初学者更容易地理解。
A High-Level Look
我们先将Transformer视为一个黑箱,在一个机器翻译的应用中,输入应该是一种语言的句子,输出是翻译过后的另一种语言的句子。
黑盒下是由encoding部分,decoding部分还有他们之间的连接组成的。
encoding部分是由一系列的encoder所组成的(本例中堆叠了6个encoder),decoding部分堆叠相同数量的decoder。
上述的encoders拥有相同的结构(但是,它们不共享权重)。每一个encoder可以分解成两个子层:
encoder的输入首先会传入进一个Self-Attention层,该层帮助encoder能够看到输入序列中的其他单词当它编码某个词时。后面,我们会细看self-attention的内部结构。Self-Attention的输出会传递到前馈神经网中,每个对应位置的前馈神经网络是相互独立的。
deocder也同样地拥有这两层,不同的是,decoder中Self-Attention和前馈神经网络之间还有一层Attention结构,该Attention使decoder将注意力集中在输入句子的相关部分上(与seq2seq models的attention作用相似)。
Bringing The Tensors Into The Picture
我们已经了解了transformer的关键组成部分,下面让我们把vectors/tensors带入其中,看看输入是如何通过各个模块计算出输出的。
与一般的NLP任务类似,我们需要用embedding algorithm将输入单词转换为向量。
每个单词都被嵌入成一个512维的向量,我们用上图的形式来表示向量
词嵌入的过程只在最低端的encoder中进行。这样每个encoder的都会接收到一个list(其中每个元素都是512维的词嵌入表示),最底层的encoder的输入是word embedding,其他encoder的输入是前个encoder的输出。list的尺寸是可以设置的超参,通常是训练集的最长句子的长度。
在对输入序列做词嵌入表示之后,它们会传入encoder的两个子层。
这里能看到Transformer的一个关键特性,每个位置的词仅仅流过它自己的encoder路径。在self-attention层中,这些路径两两之间是相互依赖的。前向网络层则没有这些依赖性,但这些路径在流经前馈神经网络时可以并行执行。
接下来我们会拿短句进行举例,我们会展示encoder的子层到底进行了什么操作。
Now We’re Encoding!
encoder接受一组向量作为输入,先经过一个self-attention层,之后传入进一个前馈神经网络之中,之后将输出传给下一个encoder。
每个位置的词向量被送入self-attention模块,然后是前馈神经网络(每个向量都流经完全相同的网络结构)
Self-Attention at a High Level
不要被self-attention这个词的表面意思所迷惑,看起来好像每个人都对他很熟悉,但是在读到Attention is all you need这篇文章的之前,下面我们将逐步讲解它是如何工作的。以下面我们想翻译的这句话为例,”The animal didn't cross the street because it was too tired
”,在这句话里“it”指的是什么?“it”指的是“street”还是“animal”,这对人来说是一个简单的问题,但是它对算法来说是不简单。在模型处理“it”时,self-attention会将“it”与“animal”关联起来。
在模型处理每个单词时(输入序列的每一个位置),self-attention允许模型看到输入句子的其他位置信息作辅助线索来更好地编码当前词。如果你对RNN熟悉,就能想到RNN的隐状态是如何允许之前的词向量来解释合成当前词的解释向量。Transformer使用self-attention来将相关词的理解编码到当前词中。
当编码"it"时(编码器的最后层输出),部分attention集中于"the animal",并将其表示合并进入到“it”的编码中
上图是Tensor2Tensor notebook的可视化例子
Self-Attention in Detail
我们先看下如何利用向量计算self-attention,再看下如何以矩阵方式计算。
第一步,根据encoder的输入向量(在本例中,是每个单词的词嵌入表示),生成三个向量,对每个词向量,生成Query-vector, Key-vector, Value-vector,生成方法为当前词的词嵌入表示分别乘以三个矩阵,这些矩阵在训练过程中需要学习。【注意:不是每个词向量独享3个matrix,而是所有输入共享3个转换矩阵;权重矩阵是基于输入位置的转换矩阵;有个可以尝试的点,如果每个词独享一个转换矩阵,会不会效果更厉害呢?】
注意到这些新向量的维度比输入词的词嵌入向量的维度要小(512–>64),它们的维度是64维,然而词嵌入表示,encoder的输入\输出是512维,并不是必须要小的,这是为了让多头attention的计算更稳定。
利用x1与权重矩阵WQ相乘得到q1,Query-vector会与这个词产生关联。我们最终会为输入句子中的每一个单词创建一个Query-vector, 一个Key-vector, 一个Value-vector。
所谓的Query/Key/Value-vector是什么?
这种抽象化的表示对计算和思考attention是有益的,当你读完下面attention是如何计算的之后,你将对这些向量的角色有更清晰的了解。
第二步,计算attention就是计算一个分值。对“Thinking Matchines”这句话,对“Thinking”(pos#1)计算self-attention 分值。我们需要计算输入句子中每个词与“Thinking”的评估分,这个分决定着编码“Thinking”时(某个固定位置的单词时),对每个输入词需要集中多少关注度。
这个分,通过“Thing”对应query-vector与所有词的key-vector依次做点积得到。所以当我们计算位置#1的self-attention时,第一个分值是q1和k1的点积,第二个分值是q1和k2的点积。
第三、四步,除以8(8是由文中key-vector的维度64开根号得到的,当然还可以默认其他数值),这样梯度会更稳定。然后加上softmax操作,归一化分值使得全为正数且加和为1。
softmax分值决定着在这个位置,每个词的表达程度(关注度)。很明显,在这个位置的词应该有最高的归一化分数,但有些时候会有助于关注该词的相关的词。
第五步,将softmax分值与value-vector按位相乘。保留关注词的value值,削弱非相关词的value值。
第六步,将所有加权向量加和,产生该位置的self-attention的输出结果。
上述就是self-attention的计算过程,生成的向量流入前向网络。在实际应用中,上述计算是以速度更快的矩阵形式进行的。下面我们看下在单词级别的矩阵计算。
Matrix Calculation of Self-Attention
第一步,计算Query/Key/Value matrix,将所有输入词向量合并成输入矩阵X,并且将其分别乘以权重矩阵Wq,Wk,Wv。
输入矩阵X的每一行表示输入句子的一个词向量。可以看到这个运算是一个降维的过程。(512->64,图中是4->3)
最后,鉴于我们使用矩阵处理,将步骤2~6合并成一个计算self-attention层输出的公式。
矩阵形式的self-attention计算过程
The Beast With Many Heads
论文进一步增加了multi-headed的机制到self-attention上,在如下两个方面提高了attention层的效果:
- multi-headed机制扩展了模型集中于不同位置的能力。在上面的例子中,z1实际大部分由自己词所决定,只包含了其他词的很少信息。在翻译 “The animal didn’t cross the street because it was too tired”时,当我们想知道单词"it"指的是什么,这个模型会起到作用。
- multi-headed机制赋予attention多种子表达方式。像下面的例子所示,在multi-headed attention下有多组Query/Key/Value权重矩阵,而非仅仅一组(论文中使用8-heads,所以针对每组encoder/decoder我们都设计8组权重矩阵)。每一组都是随机初始化。经过训练之后,每一组都可以将输入向量(inputs embedding)映射到不同的子表达空间中。
每个head都有一组WQ/WK/WV matrix,以生成不同的Q/K/V
如果我们计算multi-headed的self-attention,会分别有八组不同的Q/K/V matrix,我们会得到八个不同的Z。
这么多的矩阵这会带来点麻烦,前向网络并不能接收8个矩阵,而是希望输入是一个矩阵(每个单词一个向量表示),所以我们需要一种可以将8个矩阵合并成1个矩阵的方法。
我们应该怎么做呢?我们concat上述8个矩阵,并将其与一个额外的矩阵WO相乘。
上述就是multi-headed self-attention的内容,我认为还仅是一部分矩阵,下面尝试着将它们放到一个图上,可视化如下:
现在加入multi-head attention之后,重新看下当编码“it”时,attention head会被集中到哪里?
编码"it"时,一个attention head集中于"the animal",另一个head集中于“tired”,某种意义上讲,模型对“it”的表达融合了的“animal”和“tired”两者
如果我们将所有的attention heads都放入到图中,就很难直观地解释了:
Representing The Order of The Sequence Using Positional Encoding
截止到目前为止,我们还没有讨论如何理解输入语句中词的顺序。
为解决词序的利用问题,Transformer对输入中的每个词都新增了一个向量,这些向量遵循模型学习的指定模式,来决定词的位置,或者序列中不同词的距离。对其理解,当其映射到Q/K/V向量以及点乘的attention时,增加这些值来提供词向量间的距离。
为了能够给模型提供词序的信息,新增位置encoding向量,每个向量值都遵循指定模式
如果假设embedding有4维,实际的位置向量将如下所示:
一个只有4维的位置向量表示例子
所谓的指定模式是什么样的呢?
在下图中,每一行代表一个向量的positional-encoding,所以第一行是我们将要加到句子中第一个词向量上的vector。每行有512值,每个值范围在[-1,1],我们将其涂色以便于能够将模式可视化。
一个真实的例子有20个词(行),每个词512维(列)。可以观察中间具有明显的分隔,那是因为左侧是用sine函数生成,右侧是用cosine生成,之后将他们concat。
位置向量编码方法在论文的3.5节有提到,也可以看代码get_timing_signal_ld(),对位置编码而言并不只有这一种方法。需要注意的是,编码方法必须能够处理未知长度的序列。
上面展示的pos-encoding图是Transformer2Transformer的实施。文章中的方法略有不同,文中的方法没有直接进行concat,是交替地整合两组信号,下图进行了展示。Here’s the code to generate it:
The Residuals
encoder中值得提出注意的一个细节是,在每个子层中(slef-attention, ffnn),都有残差连接,并且紧跟着layer-normalization。
如果我们可视化向量和layer-norm操作,将如下所示:
decoder中的子层也是如此,2层encoder+2层decoder组成的Transformer结构如下:
The Decoder Side
现在我们已经了解了encoder侧的大部分概念,也基本了解了decoder的工作方式,下面看下他们是如何共同工作的。
encoder从输入序列的处理开始,最后的encoder的输出被转换为一组attention向量K和V,它俩被每个decoder的"encoder-decoder atttention"层来使用,帮助decoder集中于输入序列的合适位置。
在编码之后,是解码过程;解码的每一步输出一个元素作输出序列
下面的步骤一直重复直到一个特殊符号出现表示transformer的decoder完成了翻译输出。每一步的输出被喂到下一个decoder中,与encoders一样decoders以冒泡的形式传递decoding的结果。与对encoder的输入所做的处理一样,对decoder的输入词嵌入增加pos-encoding以表明decoder输入的每个词的位置。
decoder中的self attention层与encoder中的稍有不同:
在decoder中,self-attention层仅关注早于当前输出位置的单词。在softmax之前,通过遮挡未来位置(将它们设置为-inf)来实现。
"Encoder-Decoder Attention "层工作方式跟multi-headed self-attention是一样的,除了一点,它从前层获取输出生成Query矩阵,接收最后的encoder层的Key和Value矩阵做Key和Value矩阵。
The Final Linear and Softmax Layer
decoder最后输出浮点向量,如何将它转成词?这是最后的Linear层和Softmax层的主要工作。
Linear层是个简单的全连接层,将decoder的最后输出映射到一个非常大的logits向量上。
假设模型已知有1万个单词(输出的词表)从训练集中学习得到。那么,logits向量就有1万维,每个值表示是某个词的可能倾向值,这就是线性层应该做的。softmax层将这些分数转换成概率值(都是正值,且加和为1),最大值对应的单词就是这一步的输出单词。
图中从decoder的最后的输出开始,直到最后得到一个输出单词
Recap Of Training
现在我们已经了解了一个训练完毕的Transformer的前向过程,顺道看下如何训练模型也是非常有用的。
在训练时,未训练的模型将经历上述的前向过程,当我们在标记训练集上训练时,可以对比预测输出与实际输出。
为了可视化,假设我们的输出词表一共只有6个单词(“a”, “am”, “i”, “thanks”, “student”, “<eos>”(句子结尾的标志))
在训练之前需要把输出词典进行预处理。
一旦定义了词表,我们就能够构造一个同维度的向量来表示词表中的每个单词,比如one-hot编码,下面拿“am”进行编码举例。
举例采用one-hot编码输出词表
下面让我们讨论下模型的loss损失,在训练过程中用来优化的指标,指导学习得到一个非常准确的模型。
The Loss Function
我们用一个简单的例子来示范训练,比如翻译“merci”为“thanks”。那意味着输出的概率分布指向单词“thanks”,但是由于模型未训练是随机初始化的,不太可能就是期望的输出。
由于模型参数是随机初始化的,未训练的模型输出随机值。我们可以对比真实输出,然后利用误差反向传播算法调整模型权重,使得模型输出与真实输出更接近
如何对比两个概率分布呢?简单采用 cross-entropy或者Kullback-Leibler divergence中的一种。
鉴于这是个极其简单的例子,更真实的情况是,我们会使用一个句子作为输入。比如,输入是“je suis étudiant”,期望输出是“i am a student”。在这个例子下,我们期望模型输出连续的概率分布满足如下条件:
1 每个概率分布都与词表同维度。(图中的例子是6,但实景情况下会是30000或者50000)。
2 第一个概率分布对“i”具有最高的预测概率值。
3 第二个概率分布对“am”具有最高的预测概率值。
4 等等,一直到第五个输出指向"<end of sentence>"标记。<eos>标记也在词典中。
对一个句子而言,训练模型的目标概率分布
在足够大的训练集上训练足够时间之后,我们期望产生的概率分布如下所示:
训练好之后,模型的输出是我们期望的翻译。当然,这并不意味着这一过程是来自训练集(详见: cross validation).。
注意,每个位置都有概率值,即便与输出无关,这也是softmax对训练有帮助的地方。
现在,因为模型每步只产生一组输出,假设模型从词表的概率分布中选择最高概率的单词,扔掉其他的部分,这是种产生预测结果的方法,叫做greedy 解码。
另外一种方法是beam search,每一步仅保留最头部高概率的两个输出(比如说‘I’和‘a’),再根据这俩输出再预测下一步的输出(会执行两遍,一次假设第一个输出是‘I’,第二次假设第一个输出是‘a’),两种假设都会被保留,无论哪个版本产生的错误都更少,再保留头部高概率的两个输出。重复直到预测结束。在我们的实验中。beam_size被设置成了2(这意味着在任何时候都将两个部分假设(未完成的翻译)保留在内存中),top_beams也被设置成了2(表示我们将生成;两份翻译结果),beam_size和top_beams都是可以通过实验调整的超参数。
Go Forth And Transform
希望本文能够帮助读者对Transformer的主要概念理解有个破冰效果,如果想更深入了解,建议如下步骤:
1 阅读 Attention Is All You Needpaper,Transformer的博客文章Transformer: A Novel Neural Network Architecture for Language Understanding,Tensor2Tensor使用说明。
2 观看"Łukasz Kaiser’s talk",梳理整个模型及其细节。
3 做一下项目Jupyter Notebook provided as part of the Tensor2Tensor repo
4 尝试下项目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
版权声明:
本站所有资源均为站长或网友整理自互联网或站长购买自互联网,站长无法分辨资源版权出自何处,所以不承担任何版权以及其他问题带来的法律责任,如有侵权或者其他问题请联系站长删除!站长QQ754403226 谢谢。
- 上一篇: 【译】图解Transformer
- 下一篇: assert()函数用法总结