Transformer的原理
《Attention Is All You Need》 是一篇Google提出的将Attention思想发挥到极致的论文。这篇论文中提出一个全新的模型,叫 Transformer,抛弃了以往深度学习任务里面使用到的 CNN 和 RNN ,目前大热的BERT就是基于Transformer构建的,这个模型广泛应用于NLP领域,例如机器翻译,问答系统,文本摘要和语音识别等等方向。
Transformer总体结构
和Attention模型一样,Transformer模型中也采用了 encoer-decoder 架构。但其结构相比于Attention更加复杂,论文中encoder层由6个encoder堆叠在一起,decoder层也一样。
每一个encoder和decoder的内部简版结构如下图。
对于encoder,包含两层:一个self-attention层和一个前馈神经网络。self-attention能帮助当前节点不仅仅只关注当前的词,从而能获取到上下文的语义。
decoder也包含encoder提到的两层网络,但是在这两层中间还有一层attention层,帮助当前节点获取到当前需要关注的重点内容。
现在我们知道了模型的主要组件,接下来我们看下模型的内部细节。首先,模型需要对输入的数据进行一个embedding操作,也可以理解为类似w2c的操作,embedding结束之后,输入到encoder层,self-attention处理完数据后把数据送给前馈神经网络,前馈神经网络的计算可以并行,得到的输出会输入到下一个encoder。
Self-Attention
self-attention思想和attention类似,但self-attention是Transformer用来将其他相关单词的“理解”转换成正在处理的单词的一种思路,举个例子:
The animal didn’t cross the street because it was too tired
这里的”it”代表的是”animal”还是”street”,对于机器来说,是很难判断的,self-attention能够让机器把”it”和”animal”联系起来。
详细的处理过程如下:
self-attention计算三个新的向量。
在论文中,向量的维度是$512$维,把这三个向量分别称为Query、Key、Value,这三个向量是用embedding向量与一个矩阵相乘得到的结果,这个矩阵是随机初始化的,维度为$(64,512)$。第二个维度需要和embedding的维度一样,其值在BP的过程中会一直进行更新,得到的这三个向量的维度是$64$,低于embedding维度。Query、Key、Value这三个向量对于attention来说很重要。
计算self-attention分数值
该分数值决定了在某个位置encode一个词时,对输入句子的其他部分的关注程度。这个分数值的计算方法是Query与Key做点乘。以下图为例,首先需要针对Thinking这个词,计算出其他词对于该词的一个分数值,首先是针对于自己本身即$q1·k1$,然后是针对于第二个词即$q1·k2$
把点乘的结果除以一个常数再做softmax
这里用结果除以$8$,这个值一般是采用上文提到的矩阵的第一个维度的开方即$64$的开方$8$,当然也可以选择其他的值,然后把得到的结果做一个softmax的计算。得到的结果即是每个词对于当前位置的词的相关性大小。
把Value和softmax得到的值进行相乘,并相加,得到的结果即是self-attention在当前节点的值。
在实际的应用场景,为了提高计算速度,此处采用矩阵方式,直接计算出Query, Key, Value的矩阵,然后把embedding的值与三个矩阵直接相乘,把得到的新矩阵Q与K相乘,乘以一个常数,做softmax操作,最后乘上V矩阵
这种通过 query 和 key 的相似性程度来确定 value 的权重分布的方法被称为scaled dot-product attention。
Multi-Headed Attention
这篇论文给self-attention加入了另外一个机制,被称为“multi-headed” attention,该机制不仅仅只初始化一组Q、K、V的矩阵,而是初始化多组,transformer是使用了8组,所以最后得到的结果是8个矩阵。
但前馈神经网络没法输入8个矩阵,所以需要把8个矩阵降为1个。首先,把8个矩阵连在一起得到一个大的矩阵,再随机初始化一个矩阵和这个组合好的矩阵相乘,最后得到一个最终的矩阵。
这就是multi-headed attention的全部流程了,所有的矩阵总体流程如下。
Positional Encoding
到目前为止,transformer模型中还缺少一种解释输入序列中单词顺序的方法。为了处理这个问题,transformer给encoder层和decoder层的输入添加了一个额外的向量Positional Encoding,维度和embedding的维度一样,这个向量采用了一种很独特的方法来让模型学习到这个值,这个向量能决定当前词的位置,或者说在一个句子中不同的词之间的距离。这个位置向量的具体计算方法有很多种,论文中的计算方法如下:
其中$pos$是指当前词在句子中的位置,$i$是指向量中每个值的index。可以看出,在偶数位置,使用正弦编码,在奇数位置,使用余弦编码。
代码:1
2
3
4position_encoding = np.array([[pos / np.power(10000, 2.0 * (j // 2) / d_model) for j in range(d_model)] for pos in range(max_seq_len)])
position_encoding[:, 0::2] = np.sin(position_encoding[:, 0::2])
position_encoding[:, 1::2] = np.cos(position_encoding[:, 1::2])
最后把这个Positional Encoding与embedding的值相加,作为输入送到下一层。
Layer normalization
在transformer中,每一个子层(self-attetion,ffnn)之后都会接一个残缺模块,并且有一个Layer normalization
Normalization把输入转化成均值为0方差为1的数据,在把数据送入激活函数之前进行normalization(归一化),可以使输入数据不落在激活函数的饱和区。
Batch normalization的主要思想是:在每一层的每一批数据上进行归一化。具体做法就是对每一小批数据,在批这个方向上做归一化。如下图所示:
右半边求均值是沿着数据 batch_size的方向进行,其计算公式如下:
Layer normalization 也是归一化数据的一种方式,但 layer normalization 是在每一个样本上计算均值和方差,而不是BN那种在批方向计算均值和方差!
下面看一下 LN 的公式:
把两个encoders叠加在一起就是下图这样的结构。
Decoder层
上图是transformer的一个详细结构。可以看到decoder部分其实和encoder部分大同小异,但在最下面额外多了一个masked multi-head attention,这里的mask也是transformer一个很关键的技术。
Mask
mask 表示掩码,它对某些值进行掩盖,使其在参数更新时不产生效果。Transformer 模型里面涉及两种 mask,分别是 padding mask 和 sequence mask。
其中,padding mask 在所有的 scaled dot-product attention 里面都需要用到,而 sequence mask 只有在 decoder 的 self-attention 里面用到。
Padding Mask
因为每个批次输入序列长度是不一样的,所以要对输入序列进行对齐。具体来说,就是给在较短的序列后面填充 0。但是如果输入的序列太长,则是截取左边的内容,把多余的直接舍弃。因为这些填充的位置,其实是没什么意义的,所以attention机制不应该把注意力放在这些位置上,需要进行一些处理。
具体的做法是,把这些位置的值加上一个非常大的负数(负无穷),这样经过 softmax,这些位置的概率就会接近0!
Padding mask 实际上是一个张量,每个值都是一个Boolean,值为 false 的地方就是需要进行处理的地方。
Sequence mask
文章前面也提到,sequence mask 是为了使得 decoder 不能看见未来的信息。也就是对于一个序列,在 time_step 为 $t$ 的时刻,我们的解码输出应该只能依赖于 $t$ 时刻之前的输出,而不能依赖 $t$ 之后的输出。因此需要把 $t$ 之后的信息给隐藏起来。
具体操作:产生一个上三角矩阵,上三角的值全为0,把这个矩阵作用在每一个序列上。
- 对于 decoder 的 self-attention,里面使用到的 scaled dot-product attention,同时需要padding mask 和 sequence mask 作为 attn_mask,具体实现就是两个mask相加作为attn_mask。
- 其他情况,attn_mask 一律等于 padding mask。
输出层
当decoder层全部执行完毕后,在结尾再添加一个全连接层和softmax层就可以将得到的向量映射为需要的词。假如我们的词典是$w$个词,那最终softmax会输入$w$个词的概率,概率值最大的对应的词就是我们最终的结果。
BERT的原理
之前的文章从attention讲解到了transformer,本文将会针对目前大热的BERT进行讲解,bert的内部结构主要是transformer,如果您对transformer并不了解,请参阅我之前的博文。
从创新的角度来看,BERT其实并没有过多的结构方面的创新点,其和GPT一样均是采用的transformer的结构,相对于GPT来说,其是双向结构的,而GPT是单向的,如下图所示
其次BERT在多方面的nlp任务变现来看效果都较好,具备较强的泛化能力,对于特定的任务只需要添加一个输出层来进行fine-tuning即可。
结构
对于BERT的内部结构,官网提供了两个版本,$L$表示的是transformer的层数,$H$表示输出的维度,$A$表示mutil-head attention的个数
从模型的层数来说其实已经很大了,但是由于transformer的residual模块,层数并不会引起梯度消失等问题,但是并不代表层数越多效果越好,有论点认为低层偏向于语法特征学习,高层偏向于语义特征学习。
预训练模型
首先介绍下预训练模型,举个例子,假设有大量的维基百科数据,那么可以用这部分巨大的数据来训练一个泛化能力很强的模型,当需要在特定场景使用时,例如做文本相似度计算,那么,只需要简单的修改一些输出层,再用自己的数据进行一个增量训练,对权重进行一个轻微的调整。
预训练的好处在于在特定场景使用时不需要用大量的语料来进行训练,节约时间效率高效,BERT就是这样一个泛化能力较强的预训练模型。
BERT的预训练过程
BERT的预训练阶段包括两个任务,一个是Masked Language Model,还有一个是Next Sentence Prediction。
Masked Language Model
mml可以理解为完形填空,作者会随机mask每一个句子中 $15\%$ 的词,用其上下文来做预测,例如:my dog is hairy → my dog is [MASK]
此处将”hairy”进行了mask处理,然后采用非监督学习的方法预测mask位置的词是什么,但是该方法有一个问题,因为是mask $15\%$ 的词,其数量已经很高了,这样就会导致某些词在fine-tuning阶段从未见过,为了解决这个问题,作者做了如下的处理:
- 80%的时间是采用[mask],my dog is hairy → my dog is [MASK]
- 10%的时间是随机取一个词来代替mask的词,my dog is hairy -> my dog is apple
- 10%的时间保持不变,my dog is hairy -> my dog is hairy
那么为什么要以一定的概率使用随机词呢?因为transformer要保持对每个输入token分布式的表征,否则Transformer很可能会记住这个[MASK]就是”hairy”。至于使用随机词带来的负面影响,文章中提到,所有其他的token(即非”hairy”的token)共享 $15\%*10\% = 1.5\%$ 的概率,其影响是可以忽略不计的。
Next Sentence Prediction
选择一些句子对A与B,其中 $50\%$ 的数据B是A的下一条句子,剩余 $50\%$ 的数据B是语料库中随机选择的,学习其中的相关性,添加这样的预训练的目的是目前很多NLP的任务比如QA和NLI都需要理解两个句子之间的关系,从而能让预训练的模型更好的适应这样的任务。
输入
BERT的输入可以是单一的一个句子或者是句子对,实际的输入值是segment embedding与position embedding相加。