学习一个深度学习模型,我们首先需要从理论的角度理解它的构架,进而理解代码。
Transformer背景
首先我们知道,神经网络有一个巨大的家族,其中的CNN(卷积神经网络)源于视觉研究,目标是让机器自动学习图像特征,而RNN的出现是源于对记忆和序列建模的需求,目标是处理自然语言、语音等时序数据。循环神经网络(RNN)的关键思想是:不仅考虑当前输入,它还会记住之前的输入信息,把历史信息通过隐藏状态(hidden state)传递到下一步,从而实现“记忆”。所以,RNN可以用于自然语言处理(机器翻译、文本生成),语音处理(语音识别),时间序列预测(天气预测)等。
在RNN之前,语言模型主要是采用N-Gram,即预测当前词是什么的时候,我们只假设它和前面的N个词相关。显然这个模型并不靠谱,因为有时候关键信息藏在几句话之前。所以RNN出现了,它理论上可以向前/前后看任意多词:
双向循环神经网络结构
因此,RNN一定程度解决了N-Gram无法处理的问题(RNN详解见循环神经网络)。但是RNN也存在自己的不足:
1. RNN在处理长序列问题的时候,反向传播梯度会变得极小或极大,造成梯度消失或梯度爆炸
2. RNN难以捕捉长距离的依赖关系
3. RNN必须逐步处理输入,无法并行计算,训练速度缓慢
针对以上问题,科学家们想到了通过引入单元状态(cell state)的方法(长短时记忆网络)。LSTM的输入包括:当前时刻的输入值,上一时刻LSTM的输出值
,以及上一时刻的单元状态
。LSTM的输出包括:当前时刻输出值
,和当前时刻单元状态
。LSTM的关键就是怎样控制长期状态
。它引入了三个状态开关,即三个门:
1. 遗忘门:它决定了上一个时刻的单元状态有多少保留到当前时刻单元状态
2. 输入门:它决定当前时刻网络输入有多少保留到单元状态
3. 输出门:它控制单元状态有多少输出到LSTM的当前输出值
LSTM的前向计算
这样,遗忘门的控制可以保留很早期的信息,输入门的控制又可以避免当前无关紧要的信息进入记忆,输出门的控制又可以保证输出结果中包含早期的记忆。LSTM的引入使RNN能保留长期的依赖信息,缓解了梯度消失/爆炸的问题。后来在LSTM的基础上还发展出了GRU,Seq2Seq架构。但是,这些模型并没有本质上解决长距离依赖和训练效率低的问题。直到Transformer的出现。
(LSTM详解见长短时记忆网络(LSTM))
Transformer前言
一、独热编码(One-Hot Encoding)
计算机只能处理0/1编码,所以在面对文字的时候,我们需要一个词表来表示词(Token),即通过0/1将词表示为高维度稀疏向量(维度高,但大部分元素是0)。但是这样表示出来的句子无法包含上下文信息,词与词之间完全没有关系。因此在此基础上,我们引入了词嵌入。
二、词嵌入
所谓词嵌入,就是为词表中的每个词分配一个词向量,将其映射到语义空间中。这里语义空间维度表示词向量的维度,语义空间维度越高,语义表达能力就越强。词向量的每个值都是浮点数,词向量之间的距离也和词的语义距离有关。例如,我们可以将“红苹果”表示为 [0.13, 0.16, -0.89, 0.9] ,“青苹果”表示为 [0.12, 0.17, -0.88, 0.9] ,两者十分相似。常见的语义空间维度包括512(Transformer),12288(GPT3)。
三、嵌入矩阵
假设我们通过一个维度为的语义空间表示了一个大小为
的词表,则其中的每个Token都是一个维度为
的浮点向量,该词表也就是一个
的矩阵。我们想要表示一个句子,那么在词表中选择我们用到的词组成一个嵌入矩阵即可。例如“红苹果不是青苹果”可以表示为:
四、位置编码
在实现通过嵌入矩阵表示一个句子之后,我们如何把每个词在句子中的位置表示给模型呢,毕竟词之间的位置差异也会反映词向量的相关关系。这里,我们就需要用到位置编码。与词嵌入相似,位置编码也是一种将句中位置关系映射到一个向量中的方法。Transformer的位置编码为:
其中表示单词在句子中的位置(0, 1, 2...),
表示当前维度的下标,即一个词嵌入的第
个位置,
表示语义空间总维度。
这样,假设词向量维度,则对于一个三个Token的句子,它的位置编码矩阵为:
最后,Transformer再将词嵌入矩阵与位置编码矩阵相加,得到文本序列的词和词序信息。
至于这个位置编码是如何设计出来的,可以参考博客:Transformer位置编码设计原理详解
自注意力机制
在图像分类任务中,我们知道判断图像类别是通过对图像的特征进行分析得到的,那么在序列中我们也可以通过观察句子中特定的词来理解句子的意思,这种对序列特征的观察就叫注意力。因此对于一个句子,其中的每个Token都应该有不同的注意力权重,当然,这个权重是相对于任意两个Token之间的关系来说的。
那么Transformer是怎么做的呢?
首先,假设我们得到了一个句子的两个词向量和
,该向量包含了位置编码信息。现在,我们通过每个Token
映射出三个向量,表示为:
,query;
,key和
,value。其中这个三个向量的维度可以和词向量维度不同,
的维度可以和
、
不同。这三个向量是如何映射得到的呢:
其中为三个可学习的权重矩阵。假设Token的维度为128(1*128),则通过128*64的权重矩阵,我们可以得到一个1*64的
。
接下来,我们计算对
的注意力和
对
的注意力:
这样,我们通过将两个64*1和1*64的向量相乘,会得到两个结果,当这两个向量维度较大时,点积的数值幅度会显著增大,导致训练时梯度消失。为了解决这个问题,Transformer对点积的结果进行缩放,缩放因子为:,
为向量维度,即:
当一个序列中有个Token时,我们两两计算其注意力,可以得到一个
的注意力矩阵,表示为:
接下来,我们对这个注意力矩阵进行归一化,使其等同于概率分布。这里的归一化是针对注意力矩阵的每一行进行的,即一个句子中的第个Token(第
行),这样,我们可以得到第
个Token相对于句子中其它Token的概率分布。
最后一步,我们会用到value矩阵。假设一个句子有6个Token(
),则
矩阵会是一个6*64的结构,每一行表示一个Token的value,将其与
矩阵相乘(6*6):
可以得到一个6*64的矩阵,这个矩阵就是最终的注意力矩阵。
为什么Transformer要这样做呢?
首先,计算了句子中一个Token与另一个Token的匹配程度(注意力分数),矩阵中第
行第
列即表示了
Token对
Token的注意力。Softmax后我们就可以知道哪些位置相对来说更重要。而为了结合具体的语义信息,我们还需要将这个归一化权重与句子的初始嵌入相乘得到加权词矩阵,这样,这个最后的矩阵就是“每个Token的新表示”矩阵,包括了上下文信息的矩阵。
部分内容参考:零基础入门深度学习(9) - Transformer (2/3)
多头注意力
多头注意力机制,按其字面信息,即表示多个参数向量组——多个向量。单头注意力存在一定缺陷,每个序列的Token只能通过一个投影矩阵
去学习相关性和信息,但是语言的关系是多样的(比如语法结构和语义依赖关系是两种完全不同的语义空间),这样,我们就需要多个注意力头在不同的子语义空间计算注意力,关注一个序列的不同面。
为了不增加计算量和模型参数量,通常设置每个注意力头的维度是原先的,即:
计算过程:
首先不同投影矩阵把输入序列映射到多个子空间,每个子空间的维度是
,
在每个子空间分别计算注意力,得到多个head
拼接所有head的结果,
为Token数,
为head数,
为子空间维度
乘投影回原先语义空间维度
因果注意力
与普通的注意力不同,Transformer在生成文本时只能看到该位置之前的Token,因此与用于理解文本的双向注意力相比,单向注意力机制——因果注意力会有所不同。这种单向注意力机制可以通过掩码实现。
假设输入序列的Token数为,掩码矩阵定义为:
不难发现,表示允许当前位置和历史位置交互,
表示禁止与未来位置交互,其中
表示
向量的位置,
表示
向量的位置,即
位置的注意力只能由
位置之前的Token决定。掩码矩阵
实际上是一个上三角矩阵:
在计算注意力权重时,掩码矩阵直接通过相加融入注意力矩阵:
Transformer整体架构
Transformer总体上采用编码器+解码器的结构:
首先,编码器负责接收序列输入inputs,输出为编码到语义空间的一系列Token。具体来说,输入先转换为词嵌入,再引入位置嵌入,然后输入多头注意力层;接下来,残差网络的引入(Add)解决了梯度消失等问题(详见ResNet);层归一化(Norm)保证训练稳定,加速模型收敛;FFN提供非线性变化,同时为Token内各个维度提供了信息整合的渠道,即Token向量内部变换。上述子串构成了一个编码器层,一个完整的Transformer编码器包含N个编码器层。
解码器负责生成新的序列。解码序列中有两个特殊的Token:[start]和[end],表示生成序列的开始与结束。
解码器的结构和编码器非常相似,除了两点不同:第一点不同,解码器只能根据已经生成的Token去生成新的Token,因此需要使用因果注意力机制,即用Masked Multi-Head Attention
替代了编码器中的Multi-Head Attention
。
第二点不同,解码器在生成每个Token时,需要将编码器的输出序列也作为输入序列的一部分,因此需要一个额外的Multi-Head Attention
子层实现编码器和解码器的交叉注意力机制。具体来说,解码器根据上一子层Masked Multi-Head Attention
的输出投影向量,根据编码器对应层的输出投影
,
向量,再根据注意力公式进行计算,从而使得解码器输出的Token可以融合编码器输出Token的信息。
一个完整的Transformer解码器包含个解码器层,在Transformer的设计中,编码器和解码器层数相同。这样,每个解码器层都可以将对应的编码器层输出作为输入,从而可以同步顶层、中层和低层信息。
解码器最终如何输出词表中的词呢?当解码器的最后一个Transformer层输出后,这个输出经过一次线性变换Linear
,将维度从词向量维度变为词表的大小
,再经过
Softmax
归一化后,使之产生概率的意义。即最后的输出Output Probabilities
是一个向量,其维度是词表大小,这个向量的每个元素对应词表中的词的预测概率。对这个概率进行采样,就可以输出对应的词。
(以上内容参考零基础入门深度学习(10) - Transformer (3/3))