1 引言

各位朋友大家好,欢迎来到月来客栈。在前面的几篇文章中,笔者陆续介绍了多头注意力机制的原理、Transformer中编码器和解码器的工作流程以及多头注意力的实现过程等。在这篇文章中,笔者将会一步一步地来详细介绍如何通过Pytorch框架实现Transformer的整体网络结构,包括Token Embedding、Positional Embedding、编码器和解码器等。

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

2 Embedding 实现

2.1 Token Embedding

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

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

2.2 Positional Embedding

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

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

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

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

图 1. Positional Embedding 计算过程图

2.3 Embedding代码示例

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

3 Transformer实现

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

3.1 编码层的实现

首先,我们需要实现最基本的编码层单元,也就是如图2所示的前向传播过程(不包括Embedding部分)。

图 2. 编码层前向传播过程

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

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

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

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

3.2 编码器实现

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

在上述代码中,第1-2行是用来定义一个克隆多个编码层或解码层功能函数;第12行中的encoder_layer便是一个实例化的编码层,self.layers中保存的便是一个包含有多个编码层的ModuleList。在完成类MyTransformerEncoder的初始化后,便可以实现整个前向传播的forward方法:

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

3.3 编码器使用示例

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

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

3.4 解码层的实现

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

图 3. 解码层前向传播过程

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

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

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

3.5 解码器实现

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

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

3.6 Transformer网络实现

在实现完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中还有两个部分需要实现的就是参数初始化方法和注意力掩码矩阵生成方法,具体代码如下:

3.7 Transfromer使用示例

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

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

4 总结

在这篇文章中,笔者首先介绍了Embedding部分代码的实现,包括Token Embedding和Positional Embedding;接着分别详细介绍了编码层的实现过程、编码器的实现过程、编码器的使用示例、解码层的实现过程、解码器的实现过程以及整个Transformer的实现过程等;最后通过一个示例来介绍了如何使用类MyTransformer。在下一篇文中,笔者将会基于现在实现的MyTransformer类来搭建一个真实的文本翻译模型,以便更加清楚的理解Transformer的工作流程。

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

引用

[1] LANGUAGE TRANSLATION WITH TRANSFORMER https://pytorch.org/tutorials/beginner/translation_transformer.html

[2] The Annotated Transformer http://nlp.seas.harvard.edu/2018/04/03/attention.html

[3] SEQUENCE-TO-SEQUENCE MODELING WITH NN.TRANSFORMER AND TORCHTEXT https://pytorch.org/tutorials/beginner/transformer_tutorial.html

[4] Transformer model for language understandinghttps://tensorflow.google.cn/text/tutorials/transformer?hl=en#multi-head_attention

[5] 代码仓库 https://github.com/moon-hotel/TransformerTranslation

推荐阅读

[1] This post is all you need(①多头注意力机制原理)

[2] This post is all you need(②位置编码与编码解码过程)

[3] This post is all you need(③网络结构与自注意力实现)