Transformer:NLP中预训练模型的核心算法

随着各大厂提供的深度学习框架的易用性不断提高,利用一个预训练模型来完成一项NLP任务也变得越来越容易,但这并不意味着可以不必深入研究算法模型的本质;恰恰相反,为了提升模型的的表现效果,我们必须先弄清楚算法的原理,才能知道模型的上限以及如何调整繁多的超参数来逼近这个上限。
这篇文章旨在分析Transformer模型,因为几乎所有的预训练模型都是以它为核心的。我主要参考了Jay Alammar的The Illustrated Transformer一文,本文中所有图片(除第一个外)、视频均来自于该文章。

1、背景——传统RNN类模型的不足

大约在2010年前后,随着理论的突破和硬件能力的提升,深度学习迎来了它的「爆发期」1 。由于应用了BP算法的神经网络模型在许多领域取得了非常显著的突破,再加上当时各大机构不断发布隐含层越来越多的模型,以至于让很多人(包括我在内)认为,深度学习几乎就是深度神经网络模型的营销词汇了。

了解深度学习的人大概都听过两种典型的神经网络模型:卷积神经网络(CNN)与循环神经网络(RNN),前者在计算机视觉领域取得不俗的成绩,后者则主要应用于自然语言处理领域——与其说是模型更适应与特定领域,倒不如说是特定领域催生了这两类模型:CNN中的卷积核就好比一个个针对图片像素的特征提取器,而RNN中的循环设定则专门为了捕获自然语言句子的长程依赖信息。

此处单说RNN的不足。

如果读者有需要,可以阅读我的另一篇文章:双向长短记忆网络(BiLSTM)来了解RNN。

其实从名字也大概能想到,既然RNN需要「循环」,那么就势必需要拿到上一个时间步的输出来作为当前的输入,这就需要等待,没什么分布式方法能解决这个问题。而这种等待对于动辄需要训练成百上千万参数的神经网络模型来说,无疑是耗时巨大的。

另外一个不足之处在于,尽管RNN类模型(BiRNN,LSTM,BiLSTM等)通试图过各种手段来捕获句子的长程依赖,但仍无法做到尽善尽美,有一些信息仍然无法被捕捉,而模型的复杂度已经无法再提升了(会出现过拟合)。

以上两点,特别是第二点,是影响基于此类方法的模型在NLP任务中无法取得质的突破的重要原因。

2、预备知识

为了理解Transformer模型,需要有一些预备知识。

2.1 注意力机制

一个来源于计算机视觉领域的例子可以让人很好地理解什么是注意力机制。给定如下一张图片:
在这里插入图片描述
左侧是原图,右图展示了人们在面对这张图时所关注的部分,亮度越高表明受关注度越高。

总结来说,我们所接收到的、支持我们完成某项任务的信息(可以是图片、声音、文本等)通常都是有冗余的,如果能够正确地对这些信息进行筛选,理论上可以帮助我们更快更好地完成任务。

实际上,我们的大脑就是遮掩工作的。仍然拿上图来举例,假如要回答问题“空中的飞盘是什么颜色的?”那么只需要观察飞盘附近的内容就可以做出正确的回答。也就是说,我们根据要完成的任务(要回答的问题)来调整了注意力 ,以更集中于关键信息。

尽管我们要识别那个物体是一个「飞盘」需要用到图片之外的先验信息;判断它「在空中」也需要图片中相对位置等信息。但不可否认的是,最关键的内容仍集中于物体附近。

2.2 Sequence2equence model

序列到序列模型,通常被简称为seq2seq。顾名思义,它指的就是一类以序列为输入并以序列为输出的模型。NLP领域的许多任务都可以用seq2seq模型来解决,如翻译(输入的seq是一个句子,输出的seq是另一种语言的句子)、自动摘要(输入的seq是一篇文章,输出的seq是文章简介)以及问答(输入的seq是一个问题,输出的seq是答案文本)等。

seq2seq示例

2.3 Encoder & Decoder

Encoder&Decoder通常被翻译为编码器/解码器,实际上,对于NLP来说,它们也是「舶来品」——在通信、文件传输等领域早就有类似的概念。为什么NLP领域要把这种概念引入呢?我们知道,现代计算机并不能直接处理文本,而是要先将其转化为数字才行。转化的方式有很多种,而最优的方式,则应该能够最大程度上保留原始文本的语义信息。

注意,语义是不依赖于自然语言而存在的 。例如,想要表达“我想喝水”这个信息,可以用英语“I want to drink water”,也可以用韩语“물먹고싶어요”来表达。如果对方熟悉两种语言,那无论你用哪种表达方式,他都知道你在表达“想喝水”这个事实。在这个对话过程中,实际上已经使用了Encoder&Decoder:想喝水的人把脑中的信息用一种特定的语言表达出来,这是Encode的过程;对方接收到编码后的信息并理解,这是Decode的过程。

可以看出,人类在进行自然语言对话的过程中,其核心过程是:

「语义」—<Encode>—「语言」—<Decode>—「语义」—<Encode>—「语言」…

如果人要想通过自然语言与机器进行交互,那么它也必须像人一样具备接收自然语言(这很简单)、捕获其语义并输出自然语言(这很难)的能力。可以看出,机器处理语言的过程中,就是一个从语言到语义再到语言的过程。采用Encoder&Decoder模型来处理这个过程,本质上和人类交流所采用的方法是一致的:先通过Encoder来对语言进行编码,得到一种被用来表示语义的向量;根据这个语义表示向量,Decoder对其进行解码,并转化成用于完成不同任务的自然语言。

编码器/解码器的概念是相对的,就像私钥与公钥一样,你可以用私钥加密,公钥解密;也可以用公钥加密,私钥解密。

encoder&decoder示例

3、Transformer

3.1 简介

Transformer是论文《Attention is All You Need》提出的一个概念。从这篇文章的题目就可以看出来,其重点是在讲Attention的,准确来说,是self-attention。论文的作者认为,他们提出的这个基于self-attention的新方法,即Transformer,非常适合语言表示,并给出了一个用于与RNN类模型进行比较的的例子2

要想知道句子“I arrived at the bank after crossing the ___.”中单词“bank”的含义是什么,先得知道句子结尾处的单词是“street”还是“river”。假如最后一个单词是“street”,那么RNN类模型会逐个把“bank”和“street”之间的每一个单词输入进模型,然后才能做出判断。这里的逐个的意思,就是在每个时间步只读入一个单词。并且,作者还指出,已经有研究表明,花费的时间步越多,对于RNN类模型做出正确选择的难度越大。这个意思就是说,此类模型捕获具有长程依赖信息的能力其实是不够的。

那Transformer模型是怎么做的呢?

作者指出,相对于RNN类模型,Transformer进行预测所需要的步骤更少,而且是可以根据经验控制的(这意味着它是模型的一个超参数)。在每一步中,Transformer均使用self-attention机制来一次性处理整个句子,而不像RNN那样逐个处理。对于上个例子,在一步计算完成后,模型就可以将注意力集中在“street”上从而确定“bank”指代的是一个银行而非河岸。

另外,Transformer方法抛弃了RNN类模型在时间步上的循环过程,这就意味着它可以很好地利用现代计算框架进行并行加速计算。

3.2 模型拆解

本小节以翻译任务为例,拆解Transformer模型并一探究竟。

我们已经知道,Transformer本质上也是一个Encoder&Decoder模型,因此,它的整体架构可以抽象如下:
在这里插入图片描述
在论文中,Encoder层和Decoder层分别是由6个Encoder和6个Decoder堆叠而成的,这个数字“6”是一个超参数,没有什么特殊的含义(还记得上一节说的Transformer的步骤是可控的吗?)。所以,更详细一些的架构图如下:
在这里插入图片描述
所有的Encoder的结构都是一样的,但具有各自不同的参数。每个Encoder都有如下两层结构:
在这里插入图片描述
这里终于出现了“主角”——self-attention。可以看出,对于所有进入Encoder的输入,首先会将其放入self-attention层进行计算,其输出再传入一个前馈神经网络进行计算。

这里需要注意一下,self-attention层的输入其实是构成句子的单词的向量拼接后的向量矩阵,输出是维度相同的矩阵;作为前馈神经网络的输入时,每个单词(处理后)的向量又拆分开来单独进入自己的网络模型进行计算,下面会看到,正是这种每个位置上的单词互不影响的运算方式,使得它可以并行加速。

Decoder的结构与Encoder类似,所不同的是,在两层之间又加入了一层Encoder-Decoder Attention层:
在这里插入图片描述

3.3 数据流转情况

知道了模型的架构是什么之后,接下来看一下数据是如何在模型内进行流转的。

准备进入模型的单词首先要转化成词向量,论文里词向量的维度为512,即每个单词用512维的向量来表示。为简单起见,这里假设输入的句子只有2个单词,且每个单词的向量用四个格子来表示:
在这里插入图片描述
这里可以看出,在从self-attention层进入前馈神经网络(Feed-Forward Networks)层时,每个单词(处理后)的向量又分别进入自己位置对应的FFN,这些FFN之间是互不影响的,因此这一步的运算可以并行。

3.4 self-attention的计算细节

通过前面那个“bank”是指代银行还是河岸的例子,相信大家都已经对self-attention的工作机制有了初步的认识,本小节来分析如何在模型层面实现这种看上去很神奇的机制。

整个计算一共分为6步。

第一步,对于输入的每个位置上的向量(称之为位置向量,对于第一个Encoder来说,就是每个单词的Embedding),另外创建三个维度相同向量,并且给它们分别取名为Query Vector、Key Vector和Value Vector。实际上,这三个向量是通过将位置向量与三个矩阵(类似地,三个矩阵分别叫作Query Matrix、Key Matrix和Value Matrix)做矩阵乘法得到的。
在这里插入图片描述
如上图,要想得到 q 1 q_1 q1,只需要计算 x 1 ∗ W Q x_1*W^Q x1WQ即可。同理可以得到其他的向量。

这三个向量的维度要比位置向量小,在论文中使用的是64,而位置向量的是512。

之所以选用64,其实是与后面要说的「多头注意力机制」(Multi-Head Attention)有关,论文选用了8个头,而 8 ∗ 64 = 512 8*64=512 864=512

第二步,对于每一个位置上的单词计算其相对于其他位置单词的注意力得分。计算方式如下图:
在这里插入图片描述
上图给出了计算单词“Thinking”相对于其他单词的注意力得分,实际上就是将目标单词的Query Vector与其他每个单词的Key Vector做点积。

第三到四步,对得到的分数进行规范化,具体做法就是,先将每个分数除以8,然后再利用softmax算法,将它们归一化:
在这里插入图片描述

数字8是三个向量维度的算法平方根,之所以对分数除以8,是为了在反向传播的时候使之有更加稳定的梯度。

第五步,将目标单词相对于其他单词的注意力得分乘以对应的Value Vector。相当于对每一个Value Vector按照注意力得分进行了不同程度的收缩。

第六步,将上一步得到的所有的向量相加,作为当前位置上的单词进行self-attention计算后的输出:
在这里插入图片描述
对其他各个位置上的单词进行类似的运算,就可以得到self-attention层的最终输出。

3.5 self-attention运算的矩阵形式

观察以上计算过程可以发现,通过矩阵运算的方式可以一次性求出每个位置上的输出。具体地,可以分为两步:
第一步,计算各个位置的Query Vector、Key Vector和Value Vector的组合。实际上就是将各输入向量组合成一个矩阵,分别与Query Matrix、Key Matrix和Value Matrix,如下图所示:
在这里插入图片描述
左侧的 X X X的第一行表示第一个字符的Embedding向量,第二行表示第二个字符的Embedding向量,以此类推。

第二步,把上一节3-6步的计算过程合并:
在这里插入图片描述

3.6 Multi-Head Attention

原论文中提出了一种被称之为多头注意力(Multi-Head Attention)的方法,来增强模型的表现效果。所谓的“多头”,指的就是应用多次前两节提到的self-attention层的计算过程。论文中self-attention的head数量是8。

由前两节的内容可知,self-attention层的计算需要三个Matrix,输出一个指定维度的矩阵;那么具有8个head的self-attention层则需要24个Matrix,且输出8个指定维度的矩阵:
在这里插入图片描述
下面需要对输出的8个矩阵进行处理以使之符合下一层的输入要求。处理的过程很简单,将它们拼接起来,然后再与一个特定维度的权重矩阵做点积即可:
在这里插入图片描述
这就是多头注意力机制的整个过程。Jay Alammar给出了一个完整的流程图来展示整个计算过程:
在这里插入图片描述
以上内容就是Transformer中的核心思想了。不过在放弃RNN类模型中逐时间步运算的机制以提升效率的同时,上述方法也没有很好的利用各个单词在句子中位置信息,因此,必须加以改进。

3.7 引入位置编码

改进的方法为:给输入模型的每个单词的Embedding中加入该单词在句子中的位置信息,这种位置信息被称之为Positional Encoding。具体地,如下图所示:
在这里插入图片描述
可以看出,对于每一个单词,新的输入为「原有Embedding向量」+「位置编码向量」——它们的维度是相同的,因此相加后维度不变。

作者认为,通过这样的方式加入位置数据,从而使得它们在模型中传播时能够提供有价值的信息。

接下来的问题是,位置向量是如何生成的呢?

一种用于生成位置向量的算法的可视化效果如下图:
在这里插入图片描述
上图中,每一行都是一个位置向量,有512个值——对应于Embedding的维度,且所有的值取值范围均为 [ − 1 , 1 ] [-1,1] [1,1];第一行的位置向量对应第一个单词,第二行对应第二个,向下以此类推。

原论文并非使用的上述位置向量,在论文的第3.5节,作者给出了生成位置向量的计算公式,Jay Alammar给出了实现该公式的代码。其实该公式就是一个自变量有两个(一个是单词在句子中的位置,一个是定长向量的维度位置)的分段函数,根据维度位置的奇偶来确定要使用 s i n sin sin函数还是 c o s cos cos函数。

3.8 Residual connection & Normalization

在Encoder中,对于self-attention层和前馈神经网络(FFN)层的输出还做了一些处理:首先与该层的输入做“差”,然后再正则化:
在这里插入图片描述
类似地,Decoder中也有类似的操作,假如Transformer只有两个Encoder,两个Decoder,那么它们的架构如下:
在这里插入图片描述

3.9 Decoder

Decorder中的大部分内容与Encoder是一样的,这里主要看一下Decoder层是如何工作的。

模型在Encoder层完成计算后的输出被转换成了注意力矩阵,这些矩阵就是Decoder层的输入。需要注意的是,在Decoder层的输出是按照时间步逐个输出的。也就是说,Decoder会把上一个时间步的输出和Encoder层的输出一起纳入模型进行运算,从而得到当前时间步的输出。

无法上传动图,可以参考Jay Alammar的动图

由于Decoder是按时间步输出的,因此,其中的self-attention的工作机制与Encoder中的略有不同:它只与当前位置之前的单词计算注意力得分(通过将之后的位置全部标记为-inf,这也是为什么在论文的Figure1给出的模型架构图中,Encoder的第一层被称为“Masked Multi-Head Attention”的原因)。

3.10 最后的处理

经过Encoder和Decoder之后,如何把输出转化为字符呢?这就需要用到最后的Linear层和softmax层了。

Linear层就是一个全连接神经网络,它的作用就是把Decoder的输出转化成一个高维度的logits向量。

假设模型的词表为10000,那么logits向量的维度就是10000,每个位置上的浮点数对应于该单词的得分。

softmax层的作用就是将logits向量转化成概率的形式,从而便于选取得分最高的那个位置所对应的单词作为输出。
在这里插入图片描述

4、结语

本文旨在分析Transformer模型的架构,关于模型训练中相关参数的设置以及self-attention的优势分析,可以查看原论文和Jay Alammar的文章进行深入了解。


  1. 深度学习发展史 ↩︎

  2. Transformer: A Novel Neural Network Architecture for Language Understanding ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

芳樽里的歌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值