我们知道,attention目前已经深度学习中一种普通的方法。在这篇文章中,我们将介绍Transformer,一种在使用attention时能够加速训练的模型,不仅如此,在一些任务上,它的效果还要比 Google Neural Machine Translation model出色,下面我们拆分详细分析下这个模型。
The Transformer是2017年由Google的一篇问题Attention is all you need 提出, 其中此处是tensorflow的实现,其作为Tensor2Tensor的一个子模块,pytorch的一个由Harvard’s NLP groups的实现这里。
当我们做机器翻译的时候,即如下图,存在若干encoders 和decoders
其中encoders和decoders均由几个相同的encoder或decoder层堆叠组成,这些encoder的结构完全相同,只是其参数不同。
每个encoder可分解为两层,其输入分别经过self-attention—该层能够获取输入的各词两两之间的相关信息,具体后面分析,然后经过一个普通的前馈神经网络,每个输入位置对应的网络是独立互不干扰的。
每个decoder同样具有与encoder相同的两层,但其在两层直接又夹了一attention 层,能够帮助decoder集中于输入句子的相关部分(其作用于seq2seq model中的attention相似)
从上面我们大致看出翻译的基本机构,下面我们具体从更细的张量角度来看整个流程是怎么运作的。最开始,我们通过embedding algorithm将输入的词转化为向量,即:
对于最底层的encoder, 其输入的是一个句子中所有词向量的list, 如果我们的训练集句子长度不一,我们以句子的最大长度作为list 的容量,其他较短的句子中进行padding(填充)以达到相同的长度,这样对于每个句子,其输入的维度都将保持一致。对于list中每个词的向量,其维度我们事先进行训练设置的参数保持一致。
对于其他层的encoder,其输入为上一层encoder的输出,每层的输入输出的张量维度信息都保持一致。
当我们想将下面的句子翻译时:
”The animal didn't cross the street because it was too tired”
单词‘it’指的是什么?是街道还是动物?对于人类很简单,对于算法并不容易理解;
这里模型处理方式是,对于一个输入句子的每个词,self-attention允许它“看”该句子的所有的其他词,通过这种方式,能够“理解”该句子中其他相关的词。具体可视化关联性,可参考Tensor2Tensor notebook
下面我们具体看一下一个词怎么和其他词进行联系,具体的过程是什么样的。
第一步:首先创建三个矩阵 W Q , W K , W V W^Q, W^K,W^V WQ,WK,WV,其维度为512*64(512为词向量维度,64为attention维度),然后对每一次词,将创建的矩阵与词向量相乘得到对应的Query Vector, Key Vector和Value Vector。
第二步:对于每个词,计算一个分数。比如这个例子中,对于第一个词“Thinking”,我们需要分别计算句子中每个词(包括它自己)与该词的关联性分值,如图所示:
第三步:将上述得到的分值再除以一个共同的值 d k \sqrt{d_k} dk,这里为8,其目的在于有更稳定的梯度,也其他为其他数值。
第五步:将softmax的值与values值求积
我们然后将为该词新生成的向量传给下一层进行计算。
上述过程可以用矩阵成绩来完成:
如第一步,直接使用一个句子所有词相乘,其中矩阵X中每一行代码输入句子的一个词:
论文中进一步优化self-attention, 通过多头(“multi-headed”)机制,它通过两种方式提升attention layer的表现。
如果像上述提到的计算方式,当我们使用 8个heads时,将会得到8个不同的Z矩阵。
此时有些问题,就是下一层的输入要求的是一个矩阵,而我们现在有8个,因此我们需要将其压缩为一个。怎样做呢,如下图所示:
小结
经过“multi-head”学习后,让我们重新修正上述中的例子,看句子中“it”表示的含义
在上述模型中,还有词的顺序信息没有被利用,被模型丢弃,为了解决上述方法,the transform对于每个词增加 了一个位置向量,这个向量能够通过学习表征到一些特定的模式,帮助决定每个词的位置,或者不同词之间的距离。
那么特定的模式是什么样子呢:
在下面图中,每一行代表一个位置编码,其中共有20个词,每个词的词向量维度为512。为什么看起来是中间分裂的,是因为左半部是由于sine函数产生,右半部分由cose函数产生,他们然后拼接形成每个词的位置编码。
位置编码的具体公式见原论文,虽然它并不是仅有的位置编码方式,但也能够对未知长度的序列进行编码,比如当我们进行翻译比我们训练集更长的句子时。
值得注意的是,在每一个encoder层,都有一个残差连接,然后接一个lay-normalization层:
该残差结构同样对decoder有效,当我们考虑到两层的encoders和decoders时,结果如下:
通过对encoder部分的分析,我们已了解大部分概念,下面详细了解decoder层是如何工作的。首先值得encoder层初始的输入为句子中词的向量;顶层的encoder输出为一套attention 向量 K和V;它能够被每一个decoder的encoder-decoder attention 层来使用,以帮助decoder集中在输入句子中合适的位置,如图所示:
decoder部分中每一步的输出用于底层decoder层的输入,正如encoder所做的,decoder中我们也将位置向量作为输入来表示每个词的位置,如图所示
在解码器中的self attention 层与编码器中的稍有不同,在解码器中,self-attention 层仅仅允许关注早于当前输出的位置。在softmax之前,通过遮挡未来位置(将它们设置为-inf)来实现。
"Encoder-Decoder Attention "层工作方式跟multi-headed self-attention是一样的,除了它从前层获取输出转成query矩阵,接收最后encoder层的key和value矩阵做key和value矩阵作为相应矩阵。
最后decoder层输入一个浮点型的向量,怎么把它转成一个词呢,这就是最后线性层和softmax层的作用。
线性层是一个简单的全连接层,通过将decorders的输出投射到一个更加大的向量,称为“logits vector”,其大小和词汇保持一致,然后通过softmax输出一个概率分值,并选择概率最高的作为下一个词。
当我们进行训练的时候,需要表征预测的词和真实词的差异,怎么比较这两概率分布呢,我们简单地将两个分布相减。也可参考交叉熵和KL散度的计算。
上述仅考虑一个词,当我们对一个句子进行训练时,比如
输入:“je suis étudiant”
输出: “i am a student”
此时我们的目标输出为:
在一个大的训练集上训练足够多的次数后,我们希望模型输出的结果:
现在,因为模型每步只产生一组输出,假设模型选择最高概率的,去除其他的部分,这种产生的预测结果的方法,称为greedy解码。
另一种是每一步保留头部高概率的两个输出,根据这两个输出在预测下一步,再保留头部高概率的两个输出,重复直到结束,即beam-search,该过程只在预测阶段需要。
如果想了解更深,下面是一些步骤:
后续工作: