万字幅图一网打尽ransformer

开通VIP,畅享免费电子书等14项超值服

首页

好书

留言交流

下载APP

联系客服

今天要和大家介绍的一篇论文是谷歌2017年所发表的一篇论文,名字叫做”Attention is all you need“[1]。以下为文章目录,大家可以快速定位到自己关注部分的内容。

公众号后台回复“Transformer”即可获得本文最新高清PDF文件!

虽然,网上已经有了大量的关于这篇论文的解析,不过好菜不怕晚笔者在这里也会谈谈自己对于它的理解以及运用。按照我们一贯解读论文的顺序,首先让我们先一起来看看作者当时为什么要提出Transformer这个模型?需要解决什么样的问题?现在的模型有什么样的缺陷?

在论文的摘要部分作者提到,现在主流的序列模型都是基于复杂的循环神经网络或者是卷积神经网络构造而来的Encoder-Decoder模型,并且就算是目前性能最好的序列模型也都是基于注意力机制下的Encoder-Decoder架构。为什么作者会不停的提及这些传统的Encoder-Decoder模型呢?接着,作者在介绍部分谈到,由于传统的Encoder-Decoder架构在建模过程中,下一个时刻的计算过程会依赖于上一个时刻的输出,而这种固有的属性就限制了传统的Encoder-Decoder模型就不能以并行的方式进行计算,如图1-1所示。

This inherently sequential nature precludes parallelization within training examples, which becomes critical at longer sequence lengths, as memory constraints limit batching across examples.

随后作者谈到,尽管最新的研究工作已经能够使得传统的循环神经网络在计算效率上有了很大的提升,但是本质的问题依旧没有得到解决。

Recent work has achieved significant improvements in computational efficiency through factorization tricks and conditional computation, while also improving model performance in case of the latter. The fundamental constraint of sequential computation, however, remains.

因此,在这篇论文中,作者首次提出了一种全新的Transformer架构来解决这一问题,如图1-2所示。当然,Transformer架构的优点在于它完全摈弃了传统的循环结构,取而代之的是只通过注意力机制来计算模型输入与输出的隐含表示,而这种注意力的名字就是大名鼎鼎的自注意力机制(self-attention),也就是图1-2中的Multi-Head Attention模块。

To the best of our knowledge, however, the Transformer is the first transduction model relying entirely on self-attention to compute representations of its input and output without using sequence- aligned RNNs or convolution.

总体来说,所谓自注意力机制就是通过某种运算来直接计算得到句子在编码过程中每个位置上的注意力权重;然后再以权重和的形式来计算得到整个句子的隐含向量表示。最终,Transformer架构就是基于这种的自注意力机制而构建的Encoder-Decoder模型。

在介绍完整篇论文的提出背景后,下面就让我们一起首先来看一看自注意力机制的庐山真面目,然后再来探究整体的网络架构。

首先需要明白一点的是,所谓的自注意力机制其实就是论文中所指代的“Scaled Dot-Product Attention“。在论文中作者说道,注意力机制可以描述为将query和一系列的key-value对映射到某个输出的过程,而这个输出的向量就是根据query和key计算得到的权重作用于value上的权重和。

An attention function can be described as mapping a query and a set of key-value pairs to an output, where the query, keys, values, and output are all vectors. The output is computed as a weighted sum of the values, where the weight assigned to each value is computed by a compatibility function of the query with the corresponding key.

不过想要更加深入的理解query、key和value的含义,得需要结合Transformer的解码过程,这部分内容将会在后续进行介绍。具体的,自注意力机制的结构如图1-3所示。

从图1-3可以看出,自注意力机制的核心过程就是通过Q和K计算得到注意力权重;然后再作用于V得到整个权重和输出。具体的,对于输入Q、K和V来说,其输出向量的计算公式为:

其中Q、K和V分别为3个矩阵,且其(第2个)维度分别为 (从后面的计算过程其实可以发现(1.1)中除以的过程就是图1-3中所指的Scale过程。

之所以要进行缩放这一步是因为通过实验作者发现,对于较大的来说在完成后将会得到很大的值,而这将导致在经过sofrmax操作后产生非常小的梯度,不利于网络的训练。

We suspect that for large values of dk, the dot products grow large in magnitude, pushing the softmax function into regions where it has extremely small gradients.

如果仅仅只是看着图1-3中的结构以及公式中的计算过程显然是不那么容易理解自注意力机制的含义,例如初学者最困惑的一个问题就是图1-3中的Q、K和V分别是怎么来的?下面,我们来看一个实际的计算示例。现在,假设输入序列“我 是 谁”,且已经通过某种方式得到了1个形状为的矩阵来进行表示,那么通过图1-3所示的过程便能够就算得到Q、K以及V[2]。

从图1-4的计算过程可以看出,Q、K和V其实就是输入X分别乘以3个不同的矩阵计算而来(但这仅仅局限于Encoder和Decoder在各自输入部分利用自注意力机制进行编码的过程,Encoder和Decoder交互部分的Q、K和V另有指代)。此处对于计算得到的Q、K、V,你可以理解为这是对于同一个输入进行3次不同的线性变换来表示其不同的3种状态。在计算得到Q、K、V之后,就可以进一步计算得到权重向量,计算过程如图1-5所示。

如图1-5所示,在经过上述过程计算得到了这个注意力权重矩阵之后我们不禁就会问到,这些权重值到底表示的是什么呢?对于权重矩阵的第1行来说,0.7表示的就是“我”与“我”的注意力值;0.2表示的就是“我”与”是”的注意力值;0.1表示的就是“我”与“谁”的注意力值。换句话说,在对序列中的“我“进行编码时,应该将0.7的注意力放在“我”上,0.2的注意力放在“是”上,将0.1的注意力放在谁上。

同理,对于权重矩阵的第3行来说,其表示的含义就是,在对序列中”谁“进行编码时,应该将0.2的注意力放在“我”上,将0.1的注意力放在“是”上,将0.7的注意力放在“谁”上。从这一过程可以看出,通过这个权重矩阵模型就能轻松的知道在编码对应位置上的向量时,应该以何种方式将注意力集中到不同的位置上。

不过从上面的计算结果还可以看到一点就是,模型在对当前位置的信息进行编码时,会过度的将注意力集中于自身的位置(虽然这符合常识)而可能忽略了其它位置[2]。因此,作者采取的一种解决方案就是采用多头注意力机制(MultiHeadAttention),这部分内容我们将在稍后看到。

It expands the model’s ability to focus on different positions. Yes, in the example above, contains a little bit of every other encoding, but it could be dominated by the the actual word itself.

在通过图1-5示的过程计算得到权重矩阵后,便可以将其作用于V ,进而得到最终的编码输出,计算过程如图1-6所示。

根据如图1-6所示的过程,我们便能够得到最后编码后的输出向量。当然,对于上述过程我们还可以换个角度来进行观察,如图1-7所示。

从图1-7可以看出,对于最终输出“是”的编码向量来说,它其实就是原始“我 是 谁”3个向量的加权和,而这也就体现了在对“是”进行编码时注意力权重分配的全过程。

当然,对于整个图1-5到图1-6的过程,我们还可以通过如图1-8所示的过程来进行表示。

可以看出通过这种自注意力机制的方式确实解决了作者在论文伊始所提出的“传统序列模型在编码过程中都需顺序进行的弊端”的问题,有了自注意力机制后,仅仅只需要对原始输入进行几次矩阵变换便能够得到最终包含有不同位置注意力信息的编码向量。

对于自注意力机制的核心部分到这里就介绍完了,不过里面依旧有很多细节之处没有进行介绍。例如Encoder和Decoder在进行交互时的Q、K、V是如何得到的?在图1-3中所标记的Mask操作是什么意思,什么情况下会用到等等?这些内容将会在后续逐一进行介绍。

下面,让我们继续进入到MultiHeadAttention机制的探索中。

经过上面内容的介绍,我们算是在一定程度上对于自注意力机制有了清晰的认识,不过在上面我们也提到了自注意力机制的缺陷就是:模型在对当前位置的信息进行编码时,会过度的将注意力集中于自身的位置, 因此作者提出了通过多头注意力机制来解决这一问题。同时,使用多头注意力机制还能够给予注意力层的输出包含有不同子空间中的编码表示信息,从而增强模型的表达能力。

Multi-head attention allows the model to jointly attend to information from different representation subspaces at different positions.

在说完为什么需要多头注意力机制以及使用多头注意力机制的好处之后,下面我们就来看一看到底什么是多头注意力机制。

如图1-9所示,可以看到所谓的多头注意力机制其实就是将原始的输入序列进行多组的自注意力处理过程;然后再将每一组自注意力的结果拼接起来进行一次线性变换得到最终的输出结果。具体的,其计算公式为:

其中

同时,在论文中,作者使用了个并行的自注意力模块(8个头)来构建一个注意力层,并且对于每个自注意力模块都限定了。从这里其实可以发现,论文中所使用的多头注意力机制其实就是将一个大的高维单头拆分成了个多头。因此,整个多头注意力机制的计算过程我们可以通过如图1-10所示的过程来进行表示。

注意:图中的d_m 就是指

如图1-10所示,根据输入序列X和 我们就计算得到了,进一步根据公式就得到了单个自注意力模块的输出;同理,根据X和就得到了另外一个自注意力模块输出。最后,根据公式将水平堆叠形成,然后再用乘以便得到了整个多头注意力层的输出。同时,根据图1-9中的计算过程,还可以得到。

到此,对于整个Transformer的核心部分,即多头注意力机制的原理就介绍完了。

在多头注意力中,对于初学者来说一个比较经典的问题就是,在相同维度下使用单头和多头的区别是什么?这句话什么意思呢?以图1-10中示例为例,此时的自注意力中使用了两个头,每个头的维度为,即采用了多头的方式。另外一种做法就是,只是用一个头,但是其维度为,即采用单头的方式。那么在这两种情况下有什么区别呢?

首先,从论文中内容可知,作者在头注意力机制与多头个数之间做了如下的限制

从式可以看出,单个头注意力机制的维度乘上多头的个数就等于模型的维度。

注意:后续的d_m,以及都是指代模型的维度。

同时,从图1-10中可以看出,这里使用的多头数量,即。此时,对于第1个头来说有:

对于第2个头来说有:

最后,可以将在横向堆叠起来进行一个线性变换得到最终的。因此,对于图1-10所示的计算过程,我们还可以通过图1-13来进行表示。

从图1-13可知,在一开始初始化这3个权重矩阵时,可以直接同时初始化个头的权重,然后再进行后续的计算。而且事实上,在真正的代码实现过程中也是采用的这样的方式,这部分内容将在3.3.2节中进行介绍。因此,对图1-13中的多头计算过程,还可以根据图1-14来进行表示。

说了这么多,终于把铺垫做完了。此时,假如有如图1-15所示的头注意力计算过程:

如图1-15所示,该计算过程采用了头注意力机制来进行计算,且头的计算过程还可通过图1-16来进行表示。

那现在的问题是图1-16中的能够计算得到吗?答案是不能。为什么?因为我没有告诉你这里的等于多少。如果我告诉你多头,那么毫无疑问图1-16的计算过程就等同于图1-14的计算过程,即

且此时。但是如果我告诉你多头,那么图1-16的计算过程会变成

那么此时则为。

现在回到一开始的问题上,根据上面的论述我们可以发现,在固定的情况下,不管是使用单头还是多头的方式,在实际的处理过程中直到进行注意力权重矩阵计算前,两者之前没有任何区别。当进行进行注意力权重矩阵计算时,越大那么就会被切分得越小,进而得到的注意力权重分配方式越多,如图1-19所示。

从图1-19可以看出,如果,那么最终可能得到的就是一个各个位置只集中于自身位置的注意力权重矩阵;如果,那么就还可能得到另外一个注意力权重稍微分配合理的权重矩阵;同理如此。因而多头这一做法也恰好是论文作者提出用于克服模型在对当前位置的信息进行编码时,会过度的将注意力集中于自身的位置的问题。这里再插入一张真实场景下同一层的不同注意力权重矩阵可视化结果图:

同时,当不一样时,的取值也不一样,进而使得对权重矩阵的scale的程度不一样。例如,如果,那么当时,则;当时,则。

所以,当模型的维度确定时,一定程度上越大整个模型的表达能力越强,越能提高模型对于注意力权重的合理分配。

在正式介绍Transformer的网络结构之前,我们先来一起看看Transformer如何对字符进行Embedding处理。

熟悉文本处理的读者可能都知道,在对文本相关的数据进行建模时首先要做的便是对其进行向量化。例如在机器学习中,常见的文本表示方法有one-hot编码、词袋模型以及TF-IDF等。不过在深度学习中,更常见的做法便是将各个词(或者字)通过一个Embedding层映射到低维稠密的向量空间。因此,在Transformer模型中,首先第一步要做的同样是将文本以这样的方式进行向量化表示,并且将其称之为Token Embedding,也就是深度学习中常说的词嵌入(Word Embedding)如图2-1所示。

如果是换做之前的网络模型,例如CNN或者RNN,那么对于文本向量化的步骤就到此结束了,因为这些网络结构本身已经具备了捕捉时序特征的能力,不管是CNN中的n-gram形式还是RNN中的时序形式。但是这对仅仅只有自注意力机制的网络结构来说却不行。为什么呢?根据自注意力机制原理的介绍我们知道,自注意力机制在实际运算过程中不过就是几个矩阵来回相乘进行线性变换而已。因此,这就导致即使是打乱各个词的顺序,那么最终计算得到的结果本质上却没有发生任何变换,换句话说仅仅只使用自注意力机制会丢失文本原有的序列信息。

如图2-2所示,在经过词嵌入表示后,序列“我 在 看 书”经过了一次线性变换。现在,我们将序列变成“书 在 看 我”,然后同样以中间这个权重矩阵来进行线性变换,过程如图2-3所示。

根据图2-3中的计算结果来看,序列在交换位置前和交换位置后计算得到的结果在本质上并没有任何区别,仅仅只是交换了对应的位置。因此,基于这样的原因,Transformer在原始输入文本进行Token Embedding后,又额外的加入了一个Positional Embedding来刻画数据在时序上的特征。

Since our model contains no recurrence and no convolution, in order for the model to make use of the order of the sequence, we must inject some information about the relative or absolute position of the tokens in the sequence.

说了这么多,那到底什么又是Positional Embedding呢?数无形时少直觉,下面我们先来通过一幅图直观看看经过Positional Embedding处理后到底产生了什么样的变化。

如图2-4所示,横坐标表示输入序列中的每一个Token,每一条曲线或者直线表示对应Token在每个维度上对应的位置信息。在左图中,每个维度所对应的位置信息都是一个不变的常数;而在右图中,每个维度所对应的位置信息都是基于某种公式变换所得到。换句话说就是,左图中任意两个Token上的向量都可以进行位置交换而模型却不能捕捉到这一差异,但是加入右图这样的位置信息模型却能够感知到。例如位置20这一处的向量,在左图中无论你将它换到哪个位置,都和原来一模一样;但在右图中,你却再也找不到与位置20处位置信息相同的位置。

下面,笔者通过两个实际的示例来进行说明。

如图2-5所示,原始输入在经过Token Embedding后,又加入了一个常数位置信息的的Positional Embedding。在经过一次线性变换后便得到了图2-5左右边所示的结果。接下来,我们再交换序列的位置,并同时进行Positional Embedding观察其结果。

如图2-6所示,在交换序列位置后,采用同样的Positional Embedding进行处理,并且进行线性变换。可以发现,其计算结果同图2-5中的计算结果本质上也没有发生变换。因此,这就再次证明,如果Positional Embedding中位置信息是以常数形式进行变换,那么这样的Positional Embedding是无效的。

在Transformer中,作者采用了如公式所示的规则来生成各个维度的位置信息,其可视化结果如图2-4右所示。

其中就是这个Positional Embedding矩阵,表示具体的某一个位置,表示具体的某一维度。

最终,在融入这种非常数的Positional Embedding位置信息后,便可以得到如图2-7所示的对比结果。

从图2-7可以看出,在交换位置前与交换位置后,与同一个权重矩阵进行线性变换后的结果截然不同。因此,这就证明通过Positional Embedding可以弥补自注意力机制不能捕捉序列时序信息的缺陷。

说完Transformer中的Embedding后,接下来我们再来继续探究Transformer的网络结构。

如图2-8所示便是一个单层Transformer网络结构图。

如图2-8所示,整个Transformer网络包含左右两个部分,即Encoder和Decoder。下面,我们就分别来对其中的各个部分进行介绍。

首先,对于Encoder来说,其网络结构如图2-8左侧所示(尽管论文中是以6个这样相同的模块堆叠而成,但这里我们先以堆叠一层来进行介绍,多层的Transformer结构将在稍后进行介绍)。

如图2-9所示,对于Encoder部分来说其内部主要由两部分网络所构成:多头注意力机制和两层前馈神经网络。

The encoder is composed of a stack of N = 6 identical layers. Each layer has two sub-layers. The first is a multi-head self-attention mechanism, and the second is a simple, position- wise fully connected feed-forward network.

We apply dropout to the output of each sub-layer, before it is added to the sub-layer input and normalized.

进一步,为了便于在这些地方使用残差连接,这两部分网络输出向量的维度均为。

对于第2部分的两层全连接网络来说,其具体计算过程为

其中输入的维度为,第1层全连接层的输出维度为,第2层全连接层的输出为,且同时对于第1层网络的输出还运用了Relu激活函数。

到此,对于单层Encoder的网络结构就算是介绍完了,接下来让我们继续探究Decoder部分的网络结构。

同Encoder部分一样,论文中也采用了6个完全相同的网络层堆叠而成,不过这里我们依旧只是先看1层时的情况。对于Decoder部分来说,其整体上与Encoder类似,只是多了一个用于与Encoder输出进行交互的多头注意力机制,如图2-10所示。

不同于Encoder部分,在Decoder中一共包含有3个部分的网络结构。最上面的和最下面的部分(暂时忽略Mask)与Encoder相同,只是多了中间这个与Encoder输出(Memory)进行交互的部分,作者称之为“Encoder-Decoder attention”。对于这部分的输入,Q来自于下面多头注意力机制的输出,K和V均是Encoder部分的输出(Memory)经过线性变换后得到。而作者之所以这样设计也是在模仿传统Encoder-Decoder网络模型的解码过程。

In 'encoder-decoder attention' layers, the queries come from the previous decoder layer, and the memory keys and values come from the output of the encoder. This mimics the typical encoder-decoder attention mechanisms in sequence-to-sequence models

为了能够更好的理解这里Q、K、V的含义,我们先来看看传统的基于Encoder-Decoder的Seq2Seq翻译模型是如何进行解码的,如图2-11所示。

如图2-11所示是一个经典的基于Encoder-Decoder的机器翻译模型。左下边部分为编码器,右下边部分为解码器,左上边部分便是注意力机制部分。在图2-11中,表示的是在编码过程中,各个时刻的隐含状态,称之为每个时刻的Memory;表示解码当前时刻时的隐含状态。此时注意力机制的思想在于,希望模型在解码的时刻能够参考编码阶段每个时刻的记忆。

因此,在解码第一个时刻'<s>'时,会首先同每个记忆状态进行相似度比较得到注意力权重。这个注意力权重所蕴含的意思就是,在解码第一个时刻时应该将的注意力放在编码第一个时刻的记忆上(其它的同理),最终通过加权求和得到4个Memory的权重和,即context vector。同理,在解码第二时刻'我'时,也会遵循上面的这一解码过程。可以看出,此时注意力机制扮演的就是能够使得Encoder与Decoder进行交互的角色。

回到Transformer的Encoder-Decoder attention中,K和V均是编码部分的输出Memory经过线性变换后的结果(此时的Memory中包含了原始输入序列每个位置的编码信息),而Q是解码部分多头注意力机制输出的隐含向量经过线性变换后的结果。在Decoder对每一个时刻进行解码时,首先需要做的便是通过Q与 K进行交互(query查询),并计算得到注意力权重矩阵;然后再通过注意力权重与V进行计算得到一个权重向量,该权重向量所表示的含义就是在解码时如何将注意力分配到Memory的各个位置上。这一过程我们可以通过如图2-12和图2-13所示的过程来进行表示。

如图2-12所示,待解码向量和Memory分别各自乘上一个矩阵后得到Q、K、V。

如图2-13所示,在解码第1个时刻时,首先Q通过与K进行交互得到权重向量,此时可以看做是Q(待解码向量)在K(本质上也就是Memory)中查询Memory中各个位置与Q有关的信息;然后将权重向量与V进行运算得到解码向量,此时这个解码向量可以看作是考虑了Memory中各个位置编码信息的输出向量,也就是说它包含了在解码当前时刻时应该将注意力放在Memory中哪些位置上的信息。

进一步,在得到这个解码向量并经过图2-10中最上面的两层全连接层后,便将其输入到分类层中进行分类得到当前时刻的解码输出值。

当第1个时刻的解码过程完成之后,解码器便会将解码第1个时刻时的输入,以及解码第1个时刻后的输出均作为解码器的输入来解码预测第2个时刻的输出。整个过程可以通过如图2-14所示的过程来进行表示。

如图2-14所示,Decoder在对当前时刻进行解码输出时,都会将当前时刻之前所有的预测结果作为输入来对下一个时刻的输出进行预测。假设现在需要将'我 是 谁'翻译成英语'who am i',且解码预测后前两个时刻的结果为'who am',接下来需要对下一时刻的输出'i'进行预测,那么整个过程就可以通过图2-15和图2-16来进行表示。

如图2-15所示,左上角的矩阵是解码器对输入'<s> who am'这3个词经过解码器中自注意力机制编码后的结果;左下角是编码器对输入'我 是 谁'这3个词编码后的结果(同图2-12中的一样);两者分别在经过线性变换后便得到了Q、K和V这3个矩阵。此时值得注意的是,左上角矩阵中的每一个向量在经过自注意力机制编码后,每个向量同样也包含了其它位置上的编码信息。

进一步,Q与K作用和便得到了一个权重矩阵;再将其与V进行线性组合便得到了Encoder-Decoder attention部分的输出,如图2-16所示。

如图2-16所示,左下角便是Q与K作用后的权重矩阵,它的每一行就表示在对Memory(这里指图2-16中的V)中的每一位置进行解码时,应该如何对注意力进行分配。例如第3行的含义就是在解码当前时刻时应该将的注意力放在Memory中的'我'上,其它同理。这样,在经过解码器中的两个全连接层后,便得到了解码器最终的输出结果。接着,解码器会循环对下一个时刻的输出进行解码预测,直到预测结果为'<e>'或者达到指定长度后停止。

同时,这里需要注意的是,在通过模型进行实际的预测时,只会取解码器输出的其中一个向量进行分类,然后作为当前时刻的解码输出。例如图2-16中解码器最终会输出一个形状为[3,tgt_vocab_len]的矩阵,那么只会取其最后一个向量喂入到分类器中进行分类得到当前时刻的解码输出。具体细节见后续代码实现。

在介绍完预测时Decoder的解码过程后,下面就继续来看在网络在训练过程中是如何进行解码的。从2.2.3小节的内容可以看出,在真实预测时解码器需要将上一个时刻的输出作为下一个时刻解码的输入,然后一个时刻一个时刻的进行解码操作。显然,如果训练时也采用同样的方法那将是十分费时的。因此,在训练过程中,解码器也同编码器一样,一次接收解码时所有时刻的输入进行计算。这样做的好处,一是通过多样本并行计算能够加快网络的训练速度;二是在训练过程中直接喂入解码器正确的结果而不是上一时刻的预测值(因为训练时上一时刻的预测值可能是错误的)能够更好的训练网络。

例如在用平行预料'我 是 谁'<==>'who am i'对网络进行训练时,编码器的输入便是'我 是 谁',而解码器的输入则是'<s> who am i',对应的正确标签则是'who am i <e>'。

假设现在解码器的输入'<s> who am i'在分别乘上一个矩阵进行线性变换后得到了Q、K、V,且Q与K作用后得到了注意力权重矩阵(此时还未进行softmax操作),如图2-17所示。

从图2-17可以看出,此时已经计算得到了注意力权重矩阵。由第1行的权重向量可知,在解码第1个时刻时应该将(严格来说应该是经过softmax后的值)的注意力放到'<s>'上,的注意力放到'who'上等等。不过此时有一个问题就是,在2.2.3节中笔者介绍到,模型在实际的预测过程中只是将当前时刻之前(包括当前时刻)的所有时刻作为输入来预测下一个时刻,也就是说模型在预测时是看不到当前时刻之后的信息。因此,Transformer中的Decoder通过加入注意力掩码机制来解决了这一问题。

self-attention layers in the decoder allow each position in the decoder to attend to all positions in the decoder up to and including that position. We need to prevent leftward information flow in the decoder to preserve the auto-regressive property. We implement this inside of scaled dot-product attention by masking out (setting to −∞) all values in the input of the softmax which correspond to illegal connections.

如图2-18所示,左边依旧是通过Q和K计算得到了注意力权重矩阵(此时还未进行softmax操作),而中间的就是所谓的注意力掩码矩阵,两者在相加之后再乘上矩阵V便得到了整个自注意力机制的输出,也就是图2-10中的Masked Multi-Head Attention。

那为什么注意力权重矩阵加上这个注意力掩码矩阵就能够达到这样的效果呢?以图2-18中第1行权重为例,当解码器对第1个时刻进行解码时其对应的输入只有'<s>',因此这就意味着此时应该将所有的注意力放在第1个位置上(尽管在训练时解码器一次喂入了所有的输入),换句话说也就是第1个位置上的权重应该是1,而其它位置则是0。从图2-17可以看出,第1行注意力向量在加上第1行注意力掩码,再经过softmax操作后便得到了一个类似的向量。那么,通过这个向量就能够保证在解码第1个时刻时只能将注意力放在第1个位置上的特性。同理,在解码后续的时刻也是类似的过程。

到此,对于整个单层Transformer的网络结构以及编码解码过程就介绍完了,更多细节内容见后续代码实现。

在刚接触Transformer的时候,有的人会认为在Decoder中,既然已经有了Attention mask那么为什么还需要Positional Embedding呢?如图2-18所示,持这种观点的朋友认为,Attention mask已经有了使得输入序列依次输入解码器的能力,因此就不再需要Positional Embedding了。这样想对吗?

根据2.2.4节内容的介绍可以知道,Attention mask的作用只有一个,那就是在训练过程中掩盖掉当前时刻之后所有位置上的信息,而这也是在模仿模型在预测时只能看到当前时刻及其之前位置上的信息。因此,持有上述观点的朋友可能是把“能看见”和“能看见且有序”混在一起了。

虽然看似有了Attention mask这个掩码矩阵能够使得Decoder在解码过程中可以有序地看到当前位置之前的所有信息,但是事实上没有Positional Embedding的Attention mask只能做到看到当前位置之前的所有信息,而做不到有序。前者的“有序”指的是喂入解码器中序列的顺序,而后者的“有序”指的是序列本身固有的语序。

如果不加Postional Embedding的话,那么以下序列对于模型来说就是一回事:

虽然此时Attention mask具有能够让上述序列一个时刻一个时刻的按序喂入到解码器中,但是它却无法识别出这句话本身固有的语序。

在Transformer中各个部分的Q、K、V到底是怎么来的一直以来都是初学者最大的一个疑问,并且这部分内容在原论文中也没有进行交代,只是交代了如何根据Q、K、V来进行自注意力机制的计算。虽然在第2部分的前面几个小节已经提及过了这部分内容,但是这里再给大家进行一次总结。

根据图2-8(Transformer结构图)可知,在整个Transformer中涉及到自注意力机制的一共有3个部分:Encoder中的Multi-Head Attention;Decoder中的Masked Multi-Head Attention;Encoder和Decoder交互部分的Multi-Head Attention。

① 对于Encoder中的Multi-Head Attention来说,其原始q、k、v均是Encoder的Token输入经过Embedding后的结果。q、k、v分别经过一次线性变换(各自乘以一个权重矩阵)后得到了Q、K、V(也就是图1-4中的示例),然后再进行自注意力运算得到Encoder部分的输出结果Memory。

② 对于Decoder中的Masked Multi-Head Attention来说,其原始q、k、v均是Decoder的Token输入经过Embedding后的结果。q、k、v分别经过一次线性变换后得到了Q、K、V,然后再进行自注意力运算得到Masked Multi-Head Attention部分的输出结果,即待解码向量。

③ 对于Encoder和Decoder交互部分的Multi-Head Attention,其原始q、k、v分别是上面的带解码向量、Memory和Memory。q、k、v分别经过一次线性变换后得到了Q、K、V(也就是图2-12中的示例),然后再进行自注意力运算得到Decoder部分的输出结果。之所以这样设计也是在模仿传统Encoder-Decoder网络模型的解码过程。

在第2部分中,笔者详细介绍了单层Transformer网络结构中的各个组成部分。尽管多层Transformer就是在此基础上堆叠而来,不过笔者认为还是有必要在这里稍微提及一下。

如图3-1所示便是一个单层Transformer网络结构图,左边是编码器右边是解码器。而多层的Transformer网络就是在两边分别堆叠了多个编码器和解码器的网络模型,如图3-2所示。

如图3-2所示便是一个多层的Transformer网络结构图(原论文中采用了6个编码器和6个解码器),其中的每一个Encoder都是图3-1中左边所示的网络结构(Decoder同理)。可以发现,它真的就是图3-1堆叠后的形式。不过需要注意的是其整个解码过程。

在多层Transformer中,多层编码器先对输入序列进行编码,然后得到最后一个Encoder的输出Memory;解码器先通过Masked Multi-Head Attention对输入序列进行编码,然后将输出结果同Memory通过Encoder-Decoder Attention后得到第1层解码器的输出;接着再将第1层Decoder的输出通过Masked Multi-Head Attention进行编码,最后再将编码后的结果同Memory通过Encoder-Decoder Attention后得到第2层解码器的输出,以此类推得到最后一个Decoder的输出。

值得注意的是,在多层Transformer的解码过程中,每一个Decoder在Encoder-Decoder Attention中所使用的Memory均是同一个。

由于在实现多头注意力时需要考虑到各种情况下的掩码,因此在这里需要先对这部分内容进行介绍。在Transformer中,主要有两个地方会用到掩码这一机制。第1个地方就是在2.2.4节中介绍到的Attention Mask,用于在训练过程中解码的时候掩盖掉当前时刻之后的信息;第2个地方便是对一个batch中不同长度的序列在Padding到相同长度后,对Padding部分的信息进行掩盖。下面分别就这两种情况进行介绍。

如图3-3所示,在训练过程中对于每一个样本来说都需要这样一个对称矩阵来掩盖掉当前时刻之后所有位置的信息。

从图3-3可以看出,这个注意力掩码矩阵的形状为[tgt_len,tgt_len],其具体Mask原理在2.2.4节中笔者已经介绍过l,这里就不再赘述。在后续实现过程中,我们将通过generate_square_subsequent_mask方法来生成这样一个矩阵。同时,在后续多头注意力机制实现中,将通过attn_mask这一变量名来指代这个矩阵。

在Transformer中,使用到掩码的第2个地方便是Padding Mask。由于在网络的训练过程中同一个batch会包含有多个文本序列,而不同的序列长度并不一致。因此在数据集的生成过程中,就需要将同一个batch中的序列Padding到相同的长度。但是,这样就会导致在注意力的计算过程中会考虑到Padding位置上的信息。

如图3-4所示,P表示Padding的位置,右边的矩阵表示计算得到的注意力权重矩阵。可以看到,此时的注意力权重对于Padding位置山的信息也会加以考虑。因此在Transformer中,作者通过在生成训练集的过程中记录下每个样本Padding的实际位置;然后再将注意力权重矩阵中对应位置的权重替换成负无穷,经softmax操作后对应Padding位置上的权重就变成了0,从而达到了忽略Padding位置信息的目的。这种做法也是Encoder-Decoder网络结构中通用的一种办法。

到此,对于Transformer中所要用到Mask的地方就介绍完了,下面正式来看如何实现多头注意力机制。

根据前面的介绍可以知道,多头注意力机制中最为重要的就是自注意力机制,也就是需要前计算得到Q、K和V,如图3-6所示。

然后再根据Q、K、V来计算得到最终的注意力编码,如图3-7所示:

同时,为了避免单个自注意力机制计算得到的注意力权重过度集中于当前编码位置自己所在的位置(同时更应该关注于其它位置),所以作者在论文中提到通过采用多头注意力机制来解决这一问题,如图3-8所示。

综上所述,我们可以给出类MyMultiHeadAttentiond的定义为

在上述代码中,embed_dim表示模型的维度(图3-8中的d_m);num_heads表示多头的个数;bias表示是否在多头线性组合时使用偏置。同时,为了使得实现代码更加高效,所以Pytorch在实现的时候是多个头注意力机制一起进行的计算,也就上面代码的第17-22行,分别用来初始化了多个头的权重值(这一过程从图3-8也可以看出)。当多头注意力机制计算完成后,将会得到一个形状为[src_len,embed_dim]的矩阵,也就是图3-8中多个水平堆叠后的结果。因此,第24行代码将会初始化一个线性层来对这一结果进行一个线性变换。

在定义完初始化函数后,便可以定义如下所示的多头注意力前向传播的过程

在上述代码中,query、key、value指的并不是图3-6中的Q、K和V,而是没有经过线性变换前的输入。例如在编码时三者指的均是原始输入序列src;在解码时的Mask Multi-Head Attention中三者指的均是目标输入序列tgt;在解码时的Encoder-Decoder Attention中三者分别指的是Mask Multi-Head Attention的输出、Memory和Memory。key_padding_mask指的是编码或解码部分,输入序列的Padding情况,形状为[batch_size,src_len]或者[batch_size,tgt_len];attn_mask指的就是注意力掩码矩阵,形状为[tgt_len,src_len],它只会在解码时使用。

注意,在上面的这些维度中,tgt_len本质上指的其实是query_len;src_len本质上指的是key_len。只是在不同情况下两者可能会是一样,也可能会是不一样。

在定义完类MyMultiHeadAttentiond后,就需要定义出多头注意力的实际计算过程。由于这部分代码较长,所以就分层次进行介绍。

在上述代码中,第17-23行所做的就是根据输入进行线性变换得到图3-6中的Q、K和V。

接着,在上述代码中第5-6行所完成的就是图3-7中的缩放过程;第8-16行用来判断或修改attn_mask的维度,当然这几行代码只会在解码器中的Masked Multi-Head Attention中用到。

继续,在上述代码中第1-7行所做的就是交换Q、K、V中的维度,以便于多个样本同时进行计算;第9行代码便是用来计算注意力权重矩阵;其中上contiguous()方法是将变量放到一块连续的物理内存中;bmm的作用是用来计算两个三维矩阵的乘法操作[4]。

需要提示的是,大家在看代码的时候,最好是仔细观察一下各个变量维度的变化过程,笔者也在每次运算后进行了批注。

进一步,在上述代码中第2-3行便是用来执行图3-3中的步骤;第4-8行便是用来执行图3-5中的步骤,同时还进行了维度扩充。

最后,在上述代码中第1-4行便是用来对权重矩阵进行归一化操作,以及计算得到多头注意力机制的输出;第14行代码便是用来对多个注意力的输出结果进行线性组合;第16行代码用来返回线性组合后的结果,以及多个注意力权重矩阵的平均值。

在实现完类MyMultiHeadAttention的全部代码后,便可以通过类似如下的方式进行使用。

在上述代码中,第6-11行其实也就是Encoder中多头注意力机制的实现过程。同时,在计算过程中还可以打印出各个变量的维度变化信息:

在前面几部分内容中,笔者陆续介绍了多头注意力机制的原理、Transformer中编码器和解码器的工作流程以及多头注意力的实现过程等。接下来,笔者将会一步一步地来详细介绍如何通过Pytorch框架实现Transformer的整体网络结构,包括Token Embedding、Positional Embedding、编码器和解码器等。

下面,首先要介绍的就是对于Embedding部分的编码实现。

这里首先要实现的便是最基础的Token Enbedding,也是字符转向量的一种常用做法,如下所示:

如上代码所示便是TokenEmbedding的实现过程,由于这部分代码并不复杂所以就不再逐行进行介绍。注意,第12行代码对原始向量进行缩放是出自论文中3.4部分的描述。

在2.1.2节中笔者已经对Positional Embedding的原理做了详细的介绍,其每个位置的变化方式如式所示。

进一步,我们还可以对式中括号内的参数进行化简得到如式中的形式。

由此,根据式便可以实现Positional Embedding部分的代码,如下所示:

如上代码所示便是整个Positional Embedding的实现过程,其中第5行代码是用来初始化一个全0的位置矩阵(也就是图1中从左往右数第2个矩阵),同时还指定了一个序列的最大长度;第6-10行是用来计算每个维度(每一列)的相关位置信息;第19行代码首先是在位置矩阵中取与输入序列长度相等的前x_len行,然后在加上Token Embedding的结果;第20行是用来返回最后得到的结果并进行Dropout操作。同时,这里需要注意的一点便是,在输入x的维度中batch_size并不是第1个维度。

在实现完这部分代码后,便可以通过如下方式进行使用:

在介绍完Embedding部分的编码工作后,下面就开始正式如何来实现Transformer网络结构。如图4-2所示,对于Transformer网络的实现一共会包含4个部分:TransformerEncoderLayer、TransformerEncoder、TransformerDecoderLayer和TransformerDecoder,其分别表示定义一个单独编码层、构造由多个编码层组合得到的编码器、定义一个单独的解码层以及构造由多个解码层得到的解码器。

需要注意的是,图4-2中的一个EncoderLayer指的就是图3-2中的一个对应的Encoder,DecoderLayer同理。

首先,我们需要实现最基本的编码层单元,也就是图4-2中的TransformerEncoderLayer,其内部结构为图4-3所示的前向传播过程(不包括Embedding部分)。

对于这部分前向传播过程,可以通过如下代码来进行实现:

在上述代码中,第10行用来定义一个多头注意力机制模块,并传入相应的参数;第11-20行代码便是用来定义其它层归一化和线性变换的模块。在完成类MyTransformerEncoderLayer的初始化后,便可以实现整个前向传播的forward方法:

在上述代码中,第7-8行便是用来实现图4-3中Multi-Head Attention部分的前向传播过程;第10-11行用来实现多头注意力后的Add&Norm部分;第13-16行用来实现图4-3中最上面的Feed Forward部分和Add&Norm部分。

这里再次提醒大家,在阅读代码的时候最好是将对应的维度信息带入以便于理解。

在实现完一个标准的编码层之后,便可以基于此来实现堆叠多个编码层,从而得到Transformer中的编码器。对于这部分内容,可以通过如下代码来实现:

在上述代码中,第8-10行便是用来实现多个编码层堆叠起来的效果,并完成整个前向传播过程;第11-13行用来对多个编码层的输出结果进行层归一化并返回最终的结果。

在完成Transformer中编码器的实现过程后,便可以将其用于对输入序列进行编码。例如可以仅仅通过一个编码器对输入序列进行编码,然后将最后的输出喂入到分类器当中进行分类处理,这部分内容在后续也会进行介绍。下面先看一个使用示例。

在上述代码中,第2-6行定义了编码器中各个部分的参数值;第11-12行则是首先定义一个编码层,然后再定义由多个编码层组成的编码器;第15-16行便是用来得到整个编码器的前向传播输出结果,并且需要注意的是在编码器中不需要掩盖当前时刻之后的位置信息,所以mask=None。

在介绍完编码器的实现后,下面就开始介绍如何实现Transformer中的解码器部分。同编码器的实现流程一样,首先需要实现的依旧是一个标准的解码层,也就是图4-4所示的前向传播过程(不包括Embedding部分)。

对于这部分前向传播过程,可以通过如下代码来进行实现:

在上述代码中,第10行代码用来定义图4-4中Masked Multi-head Attention部分的前向传播过程;第12行则是用来定义图4-4中编码器与解码器交互的多头注意力机制模块;第14-24行是用来定义剩余的全连接层以及层归一化相关操作。在完成类MyTransformerDecoderLayer的初始化后,便可以实现整个前向传播的forward方法:

在上述代码中,第12-14行用来完成图4-4中Masked Multi-head Attention部分的前向传播过程,其中的tgt_mask就是在训练时用来掩盖当前时刻之后位置的注意力掩码;第16-17行用来完成图4-4中Masked Multi-head Attention之后Add&Norm部分的前向传播过程;第19-21行用来实现解码器与编码器之间的交互过程,其中memory_mask为None,memory_key_padding_mask为src_key_padding_mask用来对编码器的输出进行(序列)填充部分的掩盖,这一点同编码器中的key_padding_mask原理一样;第23-31行便是用来实现余下的其它过程。

在实现完一个标准的解码层之后,便可以基于此来实现堆叠多个解码层,从而得到Transformer中的解码器。对于这部分内容,可以通过如下代码来实现:

在上述代码中,第4行用来克隆得到多个解码层;第20-25行用来实现多层解码层的前向传播过程;第28行便是用来返回最后的结果。

在实现完Transformer中各个基础模块的话,下面就可以来搭建最后的Transformer模型了。总体来说这部分的代码也相对简单,只需要将上述编码器解码器组合到一起即可,具体代码如下所示:

在上述代码中,第15-17行是用来定义编码器部分;第19-21行是用来定义解码器部分;第22行用来以某种方式初始化Transformer中的权重参数,具体实现在稍后的内容中。在定义完类MyTransformer的初始化函数后,便可以来实现Transformer的整个前向传播过程,代码如下:

在上述代码中,src表示编码器的输入;tgt表示解码器的输入;src_mask为空,因为编码时不需要对当前时刻之后的位置信息进行掩盖;tgt_mask用于掩盖解码输入中当前时刻以后的所有位置信息;memory_mask为空;src_key_padding_mask表示对编码输入序列填充部分的Token进行mask;tgt_key_padding_mask表示对解码输入序列填充部分的Token进行掩盖;memory_key_padding_mask表示对编码器的输出部分进行掩盖,掩盖原因等同于编码输入时的mask操作。

到此,对于整个Transformer的网络结构就算是搭建完毕了,不过这还没有实现论文中基于Transformer结构的翻译模型,而这部分内容笔者也将会在下一节中进行详细的介绍。当然,出了上述模块之外,Transformer中还有两个部分需要实现的就是参数初始化方法和注意力掩码矩阵生成方法,具体代码如下:

在实现完Transformer的整个完了结构后,便可以通过如下步骤进行使用:

在上述代码中,第7-13行用来生成模拟的输入数据;第15-16行用来实例化类MyTransformer;第17行用来生成解码输入时的注意力掩码矩阵;第18-21行用来执行Transformer网络结构的前向传播过程。

经过前面几节内容的介绍,相信各位读者对于Transformer的基本原理以及实现过程已经有了一个较为清晰的认识。不过想要对一个网络模型有更加深刻的认识,那么最好的办法便是从数据预处理到模型训练,自己完完全全的经历一遍。因此,为了使得大家能够更加透彻的理解Transformer的整个工作流程,在本节中笔者将继续带着大家一起来还原论文中的文本翻译模型。

如图5-1所示便是Transformer网络的整体结构图,对于这部分内容在上一节内容中总体上算是介绍完了,只是在数据预处理方面还未涉及。下面,笔者就以Multi30K[9]中的English-German平行语料为例进行介绍(注意这并不是论文中所用到数据集)。

本部分完整代码可参见[11]。

如下所示便是一条平行预料数据,其中第1行为德语,第2行为英语,后续我们需要完成的就是搭建一个翻译模型将德语翻译为英语。

在正式介绍如何构建数据集之前,我们先通过几张图来了解一下整个构建的流程,以便做到心中有数,不会迷路。

如图5-2所示,左边部分为原始输入,右边部分为目标输入。从图5-2可以看出,第1步需要完成的就是对原始语料进行tokenize操作。如果是对类似英文这样的语料进行处理,那就是直接按空格切分即可。但是需要注意的是要把其中的逗号和句号也给分割出来。第2步需要做的就是根据tokenize后的结果对原始输入和目标输入分别建立一个字典。第3步需要做的则是将tokenize后结果根据字典中的索引将其转换成token序列。第4步则是对同一个batch中的序列以最长的为标准其它样本进行padding,并且同时需要在目标输入序列的前后加上起止符(即'<bos>'和'<eos>') 。

如图5-3所示,在完成前面4个步骤后,对于目标序列来说第5步需要做的就是将第4步处理后的结果划分成tgt_input和tgt_output。从图5-3右侧可以看出,tgt_input和tgt_output是相互对应起来的。例如对于第1个样本来说,解码第1个时刻的输入应该是'2',而此时刻对应的正确标签就应该是tgt_output中的'8';解码第2个时刻的输入应该是tgt_input中的'8',而此时刻对应的正确标签就应该是tgt_output中的'45',以此类推下去。最后,第6步则是根据src_input和tgt_input各自的padding情况,得到一个padding mask向量(注意由于这里tgt_input中的两个样本长度一样,所以并不需要padding),其中'T'表示padding的位置。当然,这里的tgt_mask并没有画出。

同时,图5-3中各个部分的结果体现在Transformer网络中的情况如图5-4所示。

以上就是基于Transformer架构的翻译模型数据预处理的整个大致流程,下面我们开始正式来通过编码实现这一过程。

第1步:定义tokenize

如果是对类似英文这样的语料进行处理,大部分就是直接按空格切分即可。但是需要注意的是单词中的某些缩写也需要给分割出来,例如'you're'需要分割成'you'和're''。因此,这部分代码可以借助torchtext中的get_tokenizer方法来实现,具体代码如下:

这里返回的是两个tokenizer,分别用于对德语和英语进行序列化。例如对于如下文本来说

其tokenize后的结果为:

第2步:建立词表

在完成上述过程后,我们将得到两个Vocab类的实例化对象。

一个为原始序列的字典:

一个为目标序列的字典:

此时,我们就需要定义一个类,并在类的初始化过程中根据训练语料完成字典的构建,代码如下:

其中min_freq表示在构建词表时忽略掉出现次数小于该值的字。

第3步:转换为Token序列

在得到构建的字典后,便可以通过如下函数来将训练集、验证集和测试集转换成Token序列:

在上述代码中,第11-4行分别用来将原始序列和目标序列转换为对应词表中的Token形式。在处理完成后,就会得到类似如下的结果:

其中左边的一列就是原始序列的Token形式,右边一列就是目标序列的Token形式,每一行构成一个样本。

第4步:padding处理

从上面的输出结果(以及图5-2中第③步后的结果)可以看到,无论是对于原始序列来说还是目标序列来说,在不同的样本中其对应长度都不尽相同。但是在将数据输入到相应模型时却需要保持同样的长度,因此在这里我们就需要对Token序列化后的样本进行padding处理。同时需要注意的是,一般在这种生成模型中,模型在训练过程中只需要保证同一个batch中所有的原始序列等长,所有的目标序列等长即可,也就是说不需要在整个数据集中所有样本都保证等长。

因此,在实际处理过程中无论是原始序列还是目标序列都会以每个batch中最长的样本为标准对其它样本进行padding,具体代码如下:

第5步:构造mask向量

在处理完成图5-2中的第④步后,对于图5-3中的第⑤步来说就是简单的切片操作,因此就不作介绍。进一步需要根据src_input和tgt_input来构造相关的mask向量,具体代码如下:

在上述代码中,第1-4行是用来生成一个形状为[sz,sz]的注意力掩码矩阵,用于在解码过程中掩盖当前position之后的position;第6-17行用来返回Transformer中各种情况下的mask矩阵,其中src_mask在这里并没有作用。

第6步:构造DataLoade与使用示

经过前面5步的操作,整个数据集的构建就算是已经基本完成了,只需要再构造一个DataLoader迭代器即可,代码如下:

在上述代码中,第2-4行便是分别用来将训练集、验证集和测试集转换为Token序列;第5-10行则是分别构造3个DataLoader,其中generate_batch将作为一个参数传入来对每个batch的样本进行处理。在完成类LoadEnglishGermanDataset所有的编码过程后,便可以通过如下形式进行使用:

各位读者在阅读这部分代码时最好是能够结合图5-2到5-4进行理解,这样效果可能会更好。在介绍完数据集构建的整个过程后,下面就开始正式进入到翻译模型的构建中。

总体来说,基于Transformer的翻译模型的网络结构其实就是图5-4所展示的所有部分,只是在前面介绍Transformer网络结构时笔者并没有把Embedding部分的实现给加进去。这是因为对于不同的文本生成模型,其Embedding部分会不一样(例如在诗歌生成这一情景中编码器和解码器共用一个TokenEmbedding即可,而在翻译模型中就需要两个),所以将两者进行了拆分。同时,待模型训练完成后,在inference过程中Encoder只需要执行一次,所以在此过程中也需要单独使用Transformer中的Encoder和Decoder。

首先,我们需要定义一个名为TranslationModel的类,其前向传播过程代码如下所示:

在上述代码中,第7-12行便是用来定义一个Transformer结构;第13-16分别用来定义Positional Embedding、Token Embedding和最后的分类器;第29-39行便是用来执行整个前向传播过程,其中Transformer的整个前向传播过程在第4节中已经介绍过,在这里就不再赘述。

在定义完logits的前向传播过后,便可以通过如下形式进行使用:

接着,我们需要再定义一个Encoder和Decoder在inference中使用,代码如下:

在上述代码中,第1-5行用于在inference时对输入序列进行编码并得到memory(只需要执行一次);第7-11行用于根据memory和当前解码时刻的输入对输出进行预测,需要循环执行多次,这部分内容详见模型预测部分。

在定义完成整个翻译模型的网络结构后下面就可以开始训练模型了。由于这部分代码较长,所以下面笔者依旧以分块的形式进行介绍:

第1步:载入数据集

首先我们可以根据前面的介绍,通过类LoadEnglishGermanDataset来载入数据集,其中config中定义了模型所涉及到的所有配置参数。

第2步:定义模型并初始化权重

在载入数据后,便可以定义一个翻译模型TranslationModel,并根据相关参数对其进行实例化;同时,可以对整个模型中的所有参数进行一个初始化操作。

第3步:定义损失学习率与优化器

在上述代码中,第1行是定义交叉熵损失函数,并同时指定需要忽略的索引ignore_index。因为根据图5-3的tgt_output可知,有些位置上的标签值其实是Padding后的结果,因此在计算损失的时候需要将这些位置给忽略掉。第2行代码则是论文中所提出来的动态学习率计算过程,其计算公式为:

具体实现代码为:

通过CustomSchedule,就能够在训练过程中动态的调整学习率。学习率随step增加而变换的结果如图5-5所示:

从图5-5可以看出,在前warm_up个step中,学习率是线性增长的,在这之后便是非线性下降,直至收敛与0.0004。

第4步:开始训练

在上述代码中,第5-9行是用来得到模型各个部分的输入;第10-18行是计算模型整个前向传播的过程;第21-25行则是执行损失计算与反向传播;第27-29则是将每个step更新后的学习率送入到模型中并更新参数;第31行是用来计算模型预测的准确率,具体过程将在后续文章中进行介绍。以下便是模型训练过程中的输出:

在介绍完模型的训练过程后接下来就来看模型的预测部分。生成模型的预测部分不像普通的分类任务只需要将网络最后的输出做argmax操作即可,生成模型在预测过程中往往需要按时刻一步步进行来进行。因此,下面我们这里定义一个translate函数来执行这一过程,具体代码如下:

在上述代码中,第6行是将待翻译的源序列进行序列化操作;第8-11行则是通过函数greedy_decode函数来对输入进行解码;第12行则是将最后解码后的结果由Token序列在转换成实际的目标语言。同时,greedy_decode函数的实现如下:

在上述代码中,第3行是将源序列输入到Transformer的编码器中进行编码并得到Memory;第4-5行是初始化解码阶段输入的第1个时刻的,在这里也就是'<sos>';第6-18行则是整个循环解码过程,在下一个时刻为EOS_IDX或者达到最大长度后停止;第8-9行是根据当前解码器输入的长度生成注意力掩码矩阵tgt_mask;第10行是根据memory以及当前时刻的输入对当前时刻的输出进行解码;第12-14行则是分类得到当前时刻的解码输出结果;第15行则是将当前时刻的解码输出结果头当前时刻之前所有的输入进行拼接,以此再对下一个时刻的输出进行预测。

最后,我们只需要调用如下函数便可以完成对原始输入语言的翻译任务:

在上述代码中,第5-14行是定义网络结构,以及恢复本地保存的网络权重;第15行则是开始执行翻译任务;第19-28行为翻译示例,其输出结果为:

由于篇幅限制,余下两节内容在这里就不在介绍。公众号回台回复“Transformer”即可获得全文高清PDF!

在本篇文章中,笔者首先详细地介绍了Transformer论文的动机以及自注意力机制的原理与多头注意力机制的作用;然后介绍了Transformer中的位置编码以及整个预测和训练过程中模型的编码解码过程;接着进一步介绍了Transformer的网络结构、自注意力机制的原理实现以及Transformer网络的实现;最后,笔者还通过三个实例(包括论文中的翻译模型、文本分类模型,以及对联生成模型)从代码的角度来介绍了整个Transformer网络的原理与使用示例。

本次内容就到此结束,感谢您的阅读!如果你觉得上述内容对你有所帮助,欢迎分享至一位你的朋友!若有任何疑问与建议,请添加笔者微信或加群进行交流。青山不改,绿水长流,我们月来客栈见!

转发送礼活动

转发本文至朋友圈,然后添加掌柜好友(nulls8),掌柜将在周五(20220519)晚8点选择点赞数最多的前3位客官(需在截止时间前将截图私信给掌柜),一人送出一份小礼物。

礼物包括:

①《动手学深度学习》、《统计学习方法》和《跟我一起学机器学习》任选一本;

②「月来客栈」定制马克杯一个;

③《赤壁赋》大号鼠标垫一个。

[1]Vaswani A, Shazeer N, Parmar N, et al. Attention is all you need

THE END
0.机器翻译能达60个语种3000个方向,近日又夺全球五冠,这家牛企是谁?比赛的主要宗旨是评估机器翻译最新发展水平,传播通用测试数据集和公共训练数据,改进机器翻译评估评测方法。此次大赛共发布中文-英语、捷克语-英语、法语-德语、德语-英语、因纽特语-英语、泰米尔语-英语、日语-英语、普什图语-英语、波兰语-英语、俄语-英语、高棉语-英语等11个语言对、22个语言方向的机器翻译评测任务。jvzquC41pg}t0|npc0ipo7hp1e532;6/233198iqe/oj|wj|zv713?69:0yivvq
1.技术实践神经机器翻译不同于前面的越南语翻英语是基于已经处理好的语料,我们找的中英翻译用语料在训练和验证等使用前均需要先做预处理。根据GNMT作者在github上的推荐,我们直接修改repo上英德翻译预处理的文件 (wmt16_en_de.sh) 以实现相关的预处理如下: 数据集如果是sgm格式的先转换成raw text格式 jvzquC41yy}/lrvk|joykw3eqo5bt}nengy0ojhjkpkutjsunczjqw
2.docs/datasets.md·PaddlePaddle/PaddleNLPWMT14ENDE WMT14 EN-DE 经过BPE分词的英语-德语翻译数据集 paddlenlp.datasets.load_dataset('wmt14ende') 机器同传 数据集名称简介调用方法 BSTC 千言数据集:机器同传,包括transcription_translation和asr paddlenlp.datasets.load_dataset('bstc', 'asr') 文本生成 数据集名称简介调用方法 Poetry 中文诗歌古典文集jvzquC41ikzfg7hqo1vbfmqgrcjenn4RcfjmgWQR1drpd8igxgrpr8iqeu5ec}fugvy/om
3.如何从零开始开发神经机器翻译系统·MachineLearningMastery看一下,如果你想要更多的分步教程,在使用文本数据时充分利用深度学习方法。 如何在 Keras 中开发神经机器翻译系统BjörnGroß,保留一些权利。 教程概述 本教程分为 4 个部分;他们是: 德语到英语翻译数据集 准备文本数据 训练神经翻译模型 评估神经翻译模型 jvzquC41yy}/mjsenq{e0ls1crgdjnhp1or.ojxvgt.|q43;7818>
4.AttentionIsAllYouNeed翻译我们只进行了少量的实验来选择dropout,注意力(attention)和残差(residual)(第5.4节),在第22节开发数据集上的学习速率和束大小(beam size),所有其他参数从英语到德语的基础翻译模型保持不变。在推理过程中,我们将最大输出长度增加到输入长度+300。我们使用beam size 为21和 jvzquC41yy}/lrfpuj{/exr1r1j99o;29dgc5;
5.LANGUAGETRANSLATIONWITHTORCHTEXT三年一梦利用torchtext类来处理一个著名的数据集,包含了一些英文和德文句子。利用该数据处理sequence-to-sequence模型,通过注意力机制,可以将德语翻译成英语。基于this tutorialfrom PyTorch community memberBen Trevettand was created bySeth Weidmanwith Ben’s permission.在文末你会用torchtext类:jvzquC41yy}/ewgnqiy/exr1mkth/uuu1r524@7286=/j}rn
6.奇点临近,新研究使用数据多样性再次大幅提升神经网络翻译性能|一然后他们利用向后模型来翻译训练集的目标句子,获得了更多补充原始训练数据集的源句。他们还对向前的模型进行了类似的训练,以使用各种目标句子集来扩充训练数据集。之后,他们使用增强的数据再次训练了模型,并得到了最终的翻译模型。 这一新模型在WMT’14英语至德语的翻译任务中获得了有史以来最高的BLEU分数:30.7。它jvzquC41yy}/z~jskw4dqv487;=2997731747?58279
7.大语言模型常见任务及评测数据集汇总(一):70余个数据集!WMT’16 English-German:另一个广泛使用的英文-德语翻译数据集,包含了约40亿个词对。 WMT’17 English-Chinese:英文-中文翻译数据集,包含了约20亿个词对。 IWSLT:国际工作坊口语翻译评测(International Workshop on Spoken Language Translation)提供的数据集,包含多个语言对的翻译数据,其中包括英文-中文。 jvzquC41dnuh0lxfp0tfv8gpi€j|r4ctvodnn4fgvgjn|4359863:92
8.机器翻译方向数据集合集!机器翻译数据集本文将为您介绍经典、热门的数据集,希望对您在选择适合的数据集时有所帮助。 1 QUAK 发布方: Upstage·高丽大学 发布时间: 2022 韩英合成机器翻译质量预测数据 (韩英神经机器翻译的一个合成质量估计数据集,QUAK) 是指韩语句子和英语机器翻译句子,并且每个句子的机器翻译结果的质量是OK/这是一个机器翻译质量预测模型jvzquC41dnuh0lxfp0tfv8P{|{e2;:>1ctzjeuj1fgzbkux136744=98:
9.102个模型40个数据集,这是你需要了解的机器翻译SOTA论文为了探索当前最佳的 NMT 模型,我们选了几个常见的数据集,并看看在 Transformer 之后,还有哪些激动人心的研究成果。我们发现不同的 NMT 模型都有其侧重的数据集,但最常用的还是 WMT 英法数据集或英德数据集。除此之外,我们也特意找了中英数据集,看看适合翻译中文的模型又是什么样的。 jvzquC41fg|fnxugt0gmk‚zp0eun1jwvkerf1A6:646
10.学习常见相关公开数据集汇总(图像处理相关数据集、自然语言处理相关有很多种方式可以使用这些数据集。比如:你可以使用它们来锻炼你的各种深度学习方法技巧;你可以使用它们来磨练你的技能,了解如何识别和构建每个问题,思考独特的使用案例和公布你的新发现! 数据集分为三类:图像处理相关数据集,自然语言处理相关数据集和语音处理相关数据集。如下: jvzquC41dnuh0lxfp0tfv8|gkzooa<>7:;:678ftvkimg8igvcomu864:79869:
11.多语言图像描述数据集——Multi30k:开启多模态翻译新纪元教育与评测:作为标准数据集,Multi30k可用于评估不同算法性能,同时也是教学过程中引入机器学习概念的优秀案例。 项目特点 多语言支持:涵盖英语、德语、法语和捷克语,促进了多语言间的相互翻译研究。 精细的数据处理:附带的预处理工具和子词模型降低了入门门槛,加快了研究进程。 jvzquC41dnuh0lxfp0tfv8lkvdrpih5228;0c{ykenk0fnyckny03<>779=97
12.【校级一流本科立项课程】“德语翻译实践(1)”课程——知行合一自2004年北航设立德语本科专业以来,《德语翻译实践(1)》就是面向德语专业三年级本科生开设的专业核心课程。该课程已进行了十余年的教学实践,积累了较丰富的课堂教学经验。课程内容主要以对德语经典文本的试译与讲评为主。 课程将翻译实践放在首位,重视实用性与学术性文本的翻译选材。通过高标准的翻译文本的选择,增强教jvzquC41pg}t0kzcc0kew7hp1ktgq86227568;=40jzn
13.有声翻译(英语德语互译版)相似应用下载相关专题 最新专题 德语翻译app合集 英汉互译翻译软件免费 中英互译翻译app 德语翻译app 翻译英汉互译软件有哪些 翻译英汉互译app 翻译英汉互译软件 英语有声读物app推荐 有声翻译下载 中文德语翻译app下载 英语德语app下载 英汉互译翻译app 翻译英汉互译软件下载 中英互译翻译软件推荐 中英互译翻译软jvzquC41o0}bpmtwlkg/exr1crvt1>>48384