【论文3】Transformer论文逐段精读

  1. 1.标题
  2. 2.摘要
  3. 3.导言
  4. 4.结论
  5. 5.相关工作
  6. 6.模型
  7. 7.实验
  8. 8.评论
  9. 9.面试题

这篇文章可以认为是最近三年以内深度学习里面最重要的文章之一,它可以认为是开创了继MLP cl和RNN之后的第四大类模型。在上个月,斯坦福联合了100多名作者写了一篇200多页的综述文章,也就是讲Transformer模型和它之后的一些变种,他们甚至提议说将这一类模型叫做基础模型,可以见它对整个领域的影响力是有多大。

1.标题

首先我们看一下标题,标题是Attention Is All You Need ,就是说你就需要注意就行了,当然在英语中,这也是一句合法的话,就是说对小孩说集中一下注意力,不要东看西看,然后这个标题成为了一个梗,就是什么什么is all you need,然后在后续出了很多很多篇文章,然后你就把这个词换成任何你想要的词,只要你换成这个词,基本上你的文章能够上头条。

2.摘要

这个摘要的第一句话是说,在主流的序列转录模型里面,

所谓的序列转录模型就是说给你一个序列,你生成另外一个序列,机器翻译说给一句英文,甚至一句中文,当然是一个序列转录模型。

这样的模型主要是依赖于比较复杂的循环,或者是卷积神经网络,然后它一半是用一个叫做encoder和decoder的架构。

当然这句话意思是说你多多少少知道这个模型的encoder、decoder的架构什么样子,CNN、RNN是什么样子。

然后第二句话是说,在性能最好这些模型里面,通常也会在你的编码器和解码器之间使用一个叫做注意力机制的东西。

基本上就是说这篇文章讲的是我要做序列到序列的生产,但是现在主流的模型是干什么东西。

第三句话是说我这篇文章提出了一个新的简单的架构。

所以现在比较有意思的是,之前我们都说我们提供一个novel,就是比较有意思的架构,现在基本上因为我们的模型其实现在都挺复杂的,如果你能做一个simple的架构,其实也挺好了,只要你的结果好,大家其实还是挺喜欢简单的架构,我们之前讲的Resnet,其实也是一个比较简单的架构,所以大家挺喜欢的,这个也是整个研究氛围的一个转变,我们说simple这个词不再是一个贬义词,而是一个褒义词,这样的结果好。

它说这个模型的名字叫Transformer,中文翻译叫做变形金刚。

这个也是比较有意思的取名方法,你当然可以说我把我的模型取得一些大家很熟知的一些名词,就很容易被记住,但是你这个问题是说,如果你的文章没有出名,大家去搜你文章的时候,根本就搜不到你的文章,还是搜到的是变形金刚,取名字也是一个非常重要的事情,好的文章一般有一个比较好的名字。

我们之前讲过的Resnet,Resnet这个名字其实挺好的,叫resnet,很好记对吧,然后我们讲到第一篇文章Alexnet,其实那篇文章根本就没有提到Alexnet这个名字,他根本没有给自己的文章取个名字,估计作者也没想到自己会那么火,所以他说我们做了一个神经网络,因为这篇文章是开创性的工作,大家重复你的结果的时候,总要给你的模型取个名字,然后提到你的时候最好有个名字,所以大家给了你一个名字叫做Alexnet,Alex来自与第一作者的名字,所以对后面文章的作者来讲,如果不想让别人给你取名字的话,当然给自己取一个比较好的名字比较重要。

然后它接着说,我这个模型仅仅依赖于注意力机制,而没有用之前的循环或者是卷积。

这就是它的贡献了,它提出了一个新的模型,简单,然后跟之前大家表现很好的模型的架构都长得不一样。

接下来说我做了两个机器翻译的实验,显示这个模型在性能上特别好,他说可以并行度更好,然后许用更少的时间来训练,他说我的模型达到了28.4的BLEU。

BLEU是在机器翻译里面大家经常用的一个衡量标准,如果你不做机器翻译的话,你可能不一定明白BLEU是干什么事情,但是没关系。

它说我这个是在英语到德语的一个翻译工作,比目前的最好的结果了两个BLEU,然后在一个英语到法语的翻译的任务上面,他们做了一个单模型,比所有的模型都要效果要好,然后它只在八个GPU上训练了3.5天,最后他说我的Transformer架构能够泛化到一些别的任务上面都很好。

那基本上你可以看到是说,我提出了一个新的模型,主要用在是什么呢,在机器翻译这个任务上面,所以这篇文章一开始写的时候是针对机器翻译这个小任务写的,所以他整个写作的时候,他去假设你前面是知道的,然后提出一个模型,然后就主要在我的机器翻译上结果很好。这个也是比较有意思的工作,他一开始做的是机器翻译,这个比较相对来说小一点的领域上面,之所以说小,是因为你会发现机器翻译也就那么几家公司关心,你能够提供动机翻译的那些服的公司,其实全世界范围来讲也就那么多家。

但是随着之后bert、gbt把这个架构用在更多的在源处理的任务上的时候,整个这个工作要出圈了,然后最近当然大家都知道用在了图片上面,用在了video上面,几乎上什么东西都能用,所以它真正的火出圈是在这个地方。

但是你第一次读这个文章的时候, 你可能看到机器翻译这一块,你可能不那么感兴趣,当然了,现在我们知道这篇文章非常重要。

3.导言

这里的导言写的是比较短的,基本上可以认为是他前面摘要的前面一半的一个扩充。

第一段
第一段话是说,在时序模型里面,当前最常用的是RNN(这是2017年),然后它包括了LSTM、GRU,然后接下来他当然是说,在这里面有两个比较主流的模型,一个叫做语言模型,另外一个是当你的输出结构化信息比较多的时候,大家会用一个叫做编码器和解码器的架构。

第二段
第二段话是讲RNN的特点是什么,同样也是它的缺点是什么。

在RNN里面,给你一个序列的话,它的计算是把这个序列从左往右移一步一步往前做。假设你的一个序列是一个句子的话,它就是一个词一个词的看。

对第t个词,它会计算一个输出叫做ht,也叫它的隐藏状态,然后呢它的ht是由前面一个词的隐藏状态,叫ht-1,和当前第t个词本身决定的,这样子的话它就可以把前面学到的历史信息,通过ht-1放到当下,然后和当前的词做一些计算,然后得到输出,这也是RNN如何能够有效处理时序信息的一个关键之所在,它把之前的信息全部放在隐藏状态里面,然后一个一个放下去。

但它的问题也来自于这里。

第一个说它是一个时序,就是一步一步计算的过程,它比较难以并行,就是说你在算第t个词的时候,算ht的那个出处的时候,你必须要保证第前面那个词的ht-1输入完成了,假设你的句子有100个词的话,那么就是说你得时序地上100步,导致说你在这个时间上你无法并行,现在在主流的GPU和那些加速器,比如说TPU一样的,大家都是成千上万个线程,你无法在这个上面并行的话,导致你的并行度比较低,使得你在计算上性能比较差。

第二个也是因为这个原因,你的历史信息是一步一步的往后传递的,如果你的时序比较长的话,那么你在很早期的那些时序信息,在后面的时候可能会丢掉,如果你不想丢掉的话,那你可能得要ht要比较大,就说你得做一个比较大的ht,但是这个的问题是说,如果你做比较大的ht,你在每一个时间不都得把它存下来,导致你的内存开销是比较大的。

当然他也提到过,这一块其实大家在过去这些年做了非常多的改进,不管是并行的改进,以及做一些分解的方法使得我们能够提升并行度,但是本质上还是没有解决太多问题。

第三段
第三段,他其实讲attention在RNN上的应用。

在这篇文章之前,attention已经被成功地用在编码器和解码器里面了,它主要是用在怎么样把编码器的东西很有效的传给解码器,主要是用到这一块,就是说你跟RNN是一起使用的,这个其实是讲这一段的事情。

第四段
最后一段讲的是这篇文章提出来的Transformer,这是一个新的模型,不再使用之前被大家使用的循环神经层,而是纯基于注意力机制了,他说我这个东西是可以变形的。

因为之前你攻击的就是时序神经网络主要是按时序地做运算,现在你做了attention之后,你可以完全做并行.。

因为它现在纯用的attention,所以它的并行度是比较高的,这样子的话它能够在比较短的时间之内,做到一个跟之前可能更好的结果。

这就是导言干的事情,总体来看这个导言是写的比较短的,可以认为就是摘要的前面几句话的一个稍微的扩充版本,对自己提出的,也就是一句话带过了。

这么写的原因,我觉得应该是因为这篇文章提出来的东西是比较多的,它是一个比较不一样的一个网络里面有一些核心的东西在里面。然后是发表在NeurIPS上面,NeurIPS是一个篇幅比较短的一个会议,它是一个单列的,然后也就八页吧,所以导致说你要在这么一个短的模板里面写下很多东西是很难的,那你就得压缩掉一些东西。

4.结论

结论的第一句话是说我们介绍了Transformer这个模型,这是第一个做序列转录的模型,然后仅仅使用注意力,它把之前所有的循环程全部换成了multi-headed self-attention,基本上可以看到这篇文章主要用的是提出了是这样一个层。

第二句话是说,在机器翻译这个任务上面,Transformer能够训练的比其他的架构都要快很多,而且呢在实际的结果上确实是效果比较好。

然后第三段是说,他对于这种纯基于注意力机制的模型感到非常的激动,他想把它用在一些别的任务上面,他觉得可以用在文本以外的数据上,包括了图片、语音、video,然后他说使得生成不那么时序化也是另外一个研究的方向。

其实现在看起来作者多多少少是预测的未来,Transformer真的在各种别的数据上以及这一块做的是比较好,虽然这些工作基本上都不失于本篇文章作者完成的,都是由别人完成的,但是本文的作者基本上是看准了大方向的。

最后一句话是说,这篇文章所有代码放在tensor2tensor这个库里面。

这也是比较有意思的写法,他把整个代码放在了结论的最后,但是现在我们知道如果你有代码的话,通常你会把这个代码放在你摘要的最后一句话。因为现在神经网络的文章里面细节通常是比较多的,简简单单的一篇文章很难把所有的细节写清楚,所以你最好第一时间公布你的代码,让别人能够很方便的重复你的文章,然后这样能扩大你文章的影响力。

5.相关工作

第一段
首先他第一段提的是如何使用卷积神经网络来替换掉你的循环神经网络,使得减少你的时序的计算。他又提到,这些工作主要的问题是说用卷积神经网络对于比较长的序列难以建模。

这是因为我们知道卷积做计算的时候,每一次它去看一个一个比较小的一个窗口,比如说看一个3x3的一个像素块,如果你两个像素隔得比较远的话,你得需要用很多层卷积,一层一层上去,才能够最后把这两个隔得远的像素给你融合起来。

但是他说如果使用Transformer里面的注意力机制的话,每一次我能看到所有的像素,我一层就能够把整个序列给你看到,相对来说就没有这个问题。但是他又提到,卷积的一个比较好的地方是可以做多个输出通道,一个输出通道可以认为是它可以去识别不一样的模式,所以他说我也想要这样子的多输出通道的效果,所以他提出了一个叫做Muti-Headed Attention,就是多头的注意力机制,所以可以模拟卷积神经网络多输出通道的一个效果。

第二段
接下来第二段他讲的是自注意力机制,其实这个是Transformer里面一个关键性的点,但是他说这个工作其实之前已经有人提出来了,并不是我这个工作的创新。

第三段
另外他又提到一个叫做memory networks的东西。

这个在17年的时候也算是一个研究的重点,如果大家不知道的话,我们可以跳过。

第四段
在我们best knowledge里面,我们的Transformer是第一个只依赖于自注意力,来做这种encode到decode的架构的模型。

这就是相关工作的章节,关键是说你要讲清楚,跟你论文相关的那些论文是谁,跟你的联系是什么,以及说你跟他们的区别是什么。

6.模型

接下来是第三章模型架构,我们知道深度神经网络的论文里面最重要的就是这一章了,那一章怎么讲你这个神经网络长什么样子。

第一段
第一句话说这些序列模型里面,现在比较好的是一个叫做编码器和解码器的架构,然后他解释一下什么是编码器、解码器。

就对编码器来讲,它会将一个输入,就是一个长为n的一个x1一直到xn的一个东西,假设你是一个句子的话,有n个词的话,那么第xt就表示你的第t个词,他说将这个序列呢编码器会把它表示一个也是长为n,但是呢其中每一个zt,它对应的是xt的一个向量的表示,假设你是一个句子的话,那么zt就表示你第t个词的一个向量的表示,这就是你的编码器的输出,就是这样一些原始的一些输入变成一个机器学习可以理解的一系列的向量。

那对解码器来讲,我会拿到编码器的输出,然后它会生成一个长为m的一个序列。
首先注意到n和m是不一样长的, 可以一样可以不一样, 比如说你英文句子翻译中文句子的话, 那么两个句子很有可能是不一样长的。
他跟编码器的一个大的不一样,在解码器里面,你的这个词是一个一个生成的,因为对编码器来讲,你很有可能是一次性能看全整个句子,就是说做翻译的时候,我可以把整个英语的句子给你,但是你在解码的时候,只能一个一个的生成,这个东西叫做一个叫做自回归,叫做auto-regressivet的一个模型,在这个里面,你的输出又是你的输入。

具体的看一下,在最开始我给定的z,那么你要去生成第一个输出,叫做y1,在拿到y1之后,我就可以去生成我的y2,然后一般来说你要生成yt的话,你可以把之前所有的y1到yt-1全部拿到,也就是说你在翻译的时候,你是一个词一个次地往外蹦,所以就是说你在过去时刻的输出,也会作为你当前时刻的输入,所以这个叫做自回归。

第二段
然后它又很简单地来了一句说Transformer是使用了一个编码器解码器的架构,具体来说它是将一些自注意力和point-wise,fully connected layers,然后把一个一个堆在一起的,我们在下面的统一给大家展现这个架构。

这个图如果你讲Transformer的话,很有可能你就是把这个图复制一下,然后放到你的ppt里面给大家讲,这就意味着说,如果你写论文的话,有一张比较漂亮的、能够把整个全局画清楚的图是非常重要的,因为很有可能别人讲你的论文的时候,就是把这个图搬过去。如果你的图画的不够好的话,别人可能还花半天来讲这些东西,如果你画的很好的话,就是一张图能够搞定所有东西。所以就是说在神经网络年代,会画图是一个很基础的技能。

但具体到这篇文章的话,这张图画的是挺好的。

首先你看到它是一个编码器和解码器的架构,这个东西(左边)是你的编码器,这一块(右边)是你的解码器,(Inputs)这是编码器的输入,就是比如说你中文翻英文的话,那么这就是你的中文的句子,然后(Outputs)这是你解码器的输入,在解码器在做预测的时候是没有输入的,实际上它就是解码器在之前时刻的一些输出作为输入在这个地方,所以这个地方写的是一个output,然后他说shifted right就是一个一个往后往右移。然后看到是你的输入进来,先进入一个嵌入层,那可是大家都要干的事情,就是说你进来是一个一个词,我要把它表述成一个向量。那这个地方加了一个叫做positional encoding,我们等会再来讲。这个地方(左上方)就是你的核心的一个编码器的架构了,这个n是说你这个层有n个,就是n个这样层摞在一起,比如说你在讲Resnet的时候,我们说的一个残差块是一个块,然后你把n个块摞在一起,最后摞成了你的东西,在这个地方(左上方)你可以认为这个叫做Transformer block,也是Transformer的一个块,具体你进去看的话,你会发现是说第一个一个叫做Multi-headed Attention,然后再有一个前馈神经网络,然后他有一个什么这个东西,这个东西大家知道这个就是一个残差的连接,然后这个norm我们等会儿再讲,基本上可以看到是说一个注意力层,再加上一个基本就是一个MLP,然后在中间有一点的残差连接,然后再有一些的normalization,然后你的编码器的输出就会作为你的解码器的一个输入, 在这个地方放进来。解码器的话跟编码就有点像,所以这一块是一样的,但是它多了一个叫做Masked的一个多头注意力机制,当然我们等会会来讲,同样道理的话,你可以基本上可以认为就是解码器其实就是这三块组成一个块,然后把它重复n次,会得到你最后的一个解码器,最后你的输出进入一个输出层,然后做一个softmax就会得到你的输出,这个是这一块就是标准的神经网络的做法。它确实是一个比较标准的编码器解码器的架构,只是说你中间的每一块跟之前是一个不一样的地方,但还有一个是说你怎么样,这个东西怎么过来,也是有一点的不一样的。

接下来我们看一下每一个具体模块是怎么实现的。

首先他给大家介绍了一下它的编码器,编码器是用一个n等于六个的一个完全一样的层,也就是之前我们画的这一块(左边),就是说,他把这个东西叫做layer,然后再用重复六个layer出来,每个layer里面会有两个sub-layers,就是一个子层,第一个sub-layer叫做multi-head self-attention,这个词已经出现很多次了,但是现在没有解释,他在之后才会解释。第二个子层名字很长,simple,position-wise fully connected feed-forward network,后面这个词是一个词,它说白了就是一个MLP,然后所以他为什么加一个simple在这里,他为了显得fancy一点,就把名字搞得特别长,我们之后再来解释。他说对每一个子层,他用了一个残差连接,我们上一期已经讲过残差连接了,他说最后我们在使用一个叫做layer normalization的东西,解释完这一些之后,他说我这个子层,其实它的公式要写出来就是长成这个样子的,LayerNorm(x + Sublayer(x)),就是说你的输入x进来,然后先进入你的那个子层,你是自注意力也好,mp也好,然后因为是残差连接,他就把输入和输出加在一起,最后进入他的LayerNorm。然后说他说为了简单起见,因为我的残差连接需要需要你的输入和输出是一样大小,如果不一样大小的话,你得做投影,从而为了简单起见,我就把每一个层它的输出的维度变成512,也就是说你对每一个词,你不管在哪一层,我都做了是512的这个长度的表示。这个我们之前讲的CNN是不一样的,或者我们之前做MLP的时候,经常会把维度啊往要么是往下减,要么CNN的话是空间维度往下减,但是channel维度往上拉,但是这个地方其实它就是固定长度来表示,使得这个模型相对来说是比较简单的,然后调参也就调一个参就行了,另外一个参数说你要复制多少块,所以这个简单设计影响到后面一系列网络,他说,bert怎么样GBT怎么样,实际上也就是两个操场是可以调的,你就要多少层,然后每一层里面那个维度有多大,也就是这两个参数。

接下来给大家解释一下什么是LayerNorm,可能你不做这一块的话,可能之前是不知道LayerNorm LayerNorm也是因为Transformer这篇文章被大家广为知道。

如果你写篇文章的话,你说我用了别人的东西,最好在文章里面真的讲一下它是什么东西,你不能真的指望别人都知道所有的细节,能能够花几句话讲清楚是不错,不然的话别人还得去点开那个链接去看一下到底是什么东西,是给大家带来了困难。

接下来我们通过跟batch norm来对比来解释一下什么是layernorm,以及为什么我们在这些变长的应用里面不使用batchnorm。

所以我们考虑一个最简单的二维输入的情况,二维输入的话我就是输入是一个矩阵,然后我的每一行是一个样本,这是我的x,这个是我的batch,然后我的每一列是我的一个特征,那么这写的就是一个feature,batchnorm的时候干的事情就是每一次我去把我的每一个列,就是每一个特征,把它在一个小mini-batch里面,它的均值变成0方差变成1,你怎么把一个向量变成均值为0、方差为1呢,这是你把它的这个向量本身的均值减掉,然后再除以它的方差就行了,这个地方你算均值的时候,是在每一个小批量里面,就这条向量里面算出它的一个均值,算出它的一个方差。这个是在训练的时候,你可以做小批量,在预测的时候,你会把一个全局的一个均值给算出来,这个你认为是以整个数据扫一遍之后,在所有数据上那些平均的那个均值方差存起来,在预测的时候再使用。当然batchnorm还会去学一个能把它一个伽马出来,就是说我可以把这个向量通过学习可以放成一个任意方差为某个均值为某个值的一个东西。layernorm跟batch norm在很多时候是几乎是一样的,除了他做的方法有点不一样之外,还如果同样的是我这一个二维输入的话,layernorm干的事情就是对每个样本做了normalization,而不是对每个特征做了,就之前我是把每一个列它的均值变0方差变1,现在是我把每一个行变成均值为0、方差为1,这个行就表示的是一个样本,所以你可以认为这个layernorm就是整个把数据转置一下放到batchnorm里面出来的结果,再转置回去一下,基本上可以得到自己的东西了,这个是当你的输入是二维的时候最简单的情况,但是在我们的Transformer里面,或者说正常的RNN里面,它的输入是一个三维的东西,因为它输的是一个序列的样本,就是每一个样本其实是里面有很多个元素,它是一个序列,你给一个句子里面有n个词,所以每个词有个向量的话,还有一个batch的话,那么就是个3D的东西,我们把它画出来,就是长成这个样子的这个地方,还是你的batch,还是你的样本,列不再是我的特征了,而是我的那个序列的长度,我们写成sequence,然后对每一个sequence就是对每个词,我当然是有自己的向量,那我再画一个额外的维度画在这个地方,这个就是我的feature了,如果在之前的话,Transformer里面这个地方就是长,这个东西长的就是n,那feature就是d,d在刚刚我们设成了512,那么如果你还是用batchnormalization的话,你每次是取一根特征,然后把它的每个样本里面所有的元素,这那个序列的元素,以及它的整个batch全部搞出来,把他的均值变成0、方差变成1,就是说我这么切一下,切一块出来,把它拉成个向量,然后跟之前作一样的运算。如果是layernorm的话,那么就是对每个样本就是这么切一下我用黄色来表示,就是横着切一下,就这两种切法不一样,但说切法不一样,它是会带来不一样的结果,具体来说为什么layer norm用的多一点,一个原因是在持续的这些序列模型里面,你的每个样本的长度可能会发生变化,比如说我们这个地方,第一个样本的长度是这样长的,第二个样本可能会长一点,第三个样本可能会短一点,第四个样本是中间长,可能是长度是这样子变换的,那些没有的东西,一般我们是把它放上0进去.那我们看一下这两种切法会有不一样什么的结果,如果是用batchnorm的话,我切出来效果就是一个跟画出来结果一样,对每一个特征你切出来东西会是一个这样子的东西,剩下的东西当然是填的是0了。如果是layernorm的话,第一个样本它切出来长度是一个这样子的长度,第二个样本当然会长一点,是这样子的长度,第三个是短一点,第四个是中等长度,这里的主要的问题是在算均值和方差的上面,对于batchnorm来说,我算均值的时候其实是通过这样子来算的,但是我画线阴影的区域的只是有效值,别的值的话其实没什么太多用,你会发现如果你的样本长度变化比较大的时候,你每次做小批量的时候,你算出来的均值方差,它的抖动相对来说是比较大的,而且这个另外一个问题是说,因为我们记得我们在做预测的时候,我们要把这个全局的均值和方差记录下来,那么这个全局的均值方差,如果碰到一个新的预测样本,如果特别特别长怎么办,我碰到一个那么那么长的东西,那么我是不是在训练的时候没见过那么伸出去那么多,那么我在之前算的均值和方差很有可能是不那么好用的,但反过来讲,对layernorm相对来说没有太多这个问题,是因为它是每个样本自己来算我的均值和方差,我也不需要存在一个全局的一个均值方差,因为这东西是对每个样本来做的,所以相对来说你不管样本是长还是短,反正我算均值是在你自己里面算的,这样子的话相对来说它稳定一些,这也是layernorm大家去看那篇文章的时候,他是给大家这么解释的,但实际上来说我们知道一个很好用的一个东西,原文写的东西可能和之后大家的理解是不一样的,在之后又有一篇文章来解释为什么layernorm,有效是更多是从一个对梯度,对于事物的那些normalization,然后提升它的常数来解释的。

在讲完编码器的架构之后,我们来看一下解码器。

解码器跟编码是一个很像的东西,首先它跟编码器角一样是由n等于6个同样的层构成的,每个层里面呢跟编码器有两个一样的子层,但是不一样的在于,解码器里面用了一个第三个子层,它同样是一个多头的注意力机制,跟编码器一样我们同样的用了残差连接,用的log,另外一个是我们知道在解码器的时候,他做的是一个自回归,也就是说你当前的输入集是上面一些时刻的输出,意味着是说你在做预测的时候,你当然不能看到之后的那些时刻的输出,但是我们知道在注意力机制里面, 他每一次能看到整个完整的输入,所以这个地方我们要避免这个情况发生,也就是说在解码器训练的时候,再预测第t个时刻的,输出的时候你不应该看到t时刻以后的那些输入,它的做法是通过一个带掩码的注意力机制。如果你回过头来看这个图的话,你会发现这个地方是有一个masked的,你别的那些黄色的块都是多头的注意力,但是这个地方是有个masked,保证你输入进来的时候,在t时间是不会看到t时间以后的那些输入,从而保证你训练和预测的时候行为是一致的。

在看完我们的编码器和解码器的架构之后,我们来看一下每一个子层具体是怎么定义的。

注意力层
第一段话就是一个对注意力的一个非常一般化的介绍,属于你懂的话,你看完之后就懂了,如果你不懂的话,你开完之后可能还是不懂。但不管怎么样,我们就按照这一段话给大家来解释一遍,首先他说,注意力函数呢是一个将一个query和一些key-value对映射成一个输出的一个函数,这里面所有的query呀key value和output都是一些向量,具体来说你的output是你的valve的一个加权和,所以就导致说你的输出的维度跟你的value的维度是一样的,另外一个是说这个权重是怎么来的,对于每一个value的权重,它是这个value对应的key和你这个查询这个query的相似度算来的,这个相似度或者叫做compatibility function,不同的注意力机制有不同的算法,如果我们画一个简单示意图,可以长成这样子:

假设我有三个value和三个对应的key,假设我们现在给一个query,这个query跟第一个第二个key比较近,就是放在这个地方,那么你的输出就是这三个v的相加,但是这个地方的权重会比较大一点,这个地方权重也可能比较大,但是这个地方的权重就会比较小一点,因为这个权重是等价于你的query和你对应的key的那个相似度。

同样道理, 我假设再给你一个query,但是他是跟最后那一个key比较像的话,那么这样子你再去算他的v的时候,就会发现它对后面的权重会比较高一点,中间权重也还不错,最后的权重是比较小一点,就会得到一个新的输出,虽然你的key value并没有变,但是随着你query的改变,因为权重分配不一样,导致你的输出会有不一样,这就是注意力机制,因为不同的相似函数导致不一样的注意力的版本。

所以接下来这一章就讲的是Transformer自己用到的这一个注意力是什么样子计算的,他取的名字叫做scaled dot-product attention,虽然名字比较长,实际上是最简单的注意力机制了。他说我这个里面query和key,它的长度是等长的,都等于dk。因为你可以不等长,不正常是有别的办法算的。然后它的value是dv,当然你的输出也一样的是dv了,他说我具体计算的是,我对我每一个query和我的key做内积,然后把它作为相似度,你可以认为两个向量作了内积的事儿,如果这两个向量的long-norm是一样的话,那么你的内积的值越大,就是它的余弦值,那么就表示这两个向量的相似度就越高,如果你的内积是0了,那就等于是两个向量正交的,就是没有相似度。然后算出来之后,他再除以根号dk,就是你这个向量的长度,然后再用一个soft max来得到你的权重,因为你给一个query,假设给n个key value pair的话,那么就会算出n个值,因为这个query会跟每个key做内积,算出来之后再放进soft max就会得到n个非负的,而且加起来和等于1的一个权重,对于权重我们觉得当然是非负加起来等于1依旧是比较好的权重,然后我们把这些权重作用在我们的value上面,就会得到我们的输出了,在实际中当然我们不能一个一个这么做运算算起来比较慢。


所以他下面给了一个在实际中的时候,我们应该怎么样算的,他说我的query可以写成一个矩阵,就是我其实可能不止一个query,我有n个query,那我们画出来就是一个假设是一个Q是这个地方,那么有n行,然后你的维度是等于dk的,同样道理的话你的k也是一个同样的东西,但你的可能会长一点或者短一点都没关系,假设你是m,就是你的query个数和你的key value的个数可能是不一样的,但是它的长度一定是一样的,这样子我才能做内积,然后给定这两个矩阵,我把它一乘就会得到一个n乘以m的一个东西,所以这个东西里面它的每一行,就这个蓝色的线就是一个query对所有key的那一个内积值,然后我们再除以这个根号dk,再做soft max,所谓的soft max就是对每一行做soft max,然后每行一行之间是独立的,这样子就会得到我的权重,然后再乘以我的v,我的v是有一个叫m行的,然后它的列数是dv的一个矩阵则是v,然后这两个矩阵乘的话就会得到一个成为n乘以dv的一个东西,我们写的这个地方n乘以dv,那么这个地方每一行就是我们要的一个输出了,所以这里你可以看到是说对于组key value对和你n个query的话,我可以通过两次矩阵乘法来把整个计算做掉,这些query、key value在实际中对应的就是我的序列,所以这样导致说我基本上可以并行的计算里面每个元素,因为矩阵乘法是一个非常好并行的东西。
接下来一段他说我提出来的注意力机制跟别的区别是什么样子,他说一般有两种比较常见的注意力机制,一种叫做加型的注意力机制,它可以处理你的,query和你的key不等长的情况,另外一个叫做点积的注意力机制,他说点积的注意力跟我的机制是一样的,除了我这里除了一个这个数(根号dk)之外,所以你可以看到它的名字叫做scaled,就是除了那个东西,然后是点积注意力机制。接下来他说这两种注意力机制其实都差不多,但是它选用的是点乘,这是因为这个实现起来比较简单,而且会比较高效,因为这就是两次矩阵乘法就能算好。

当然你需要解释一下,你为什么不直接用最简单的点乘注意力,你为什么要这里要出一个根号dk。

他说当你的dk不是很大的时候,其实你出不出都没关系,但是当你的dk比较大的时候,也就是说两个向量的长度比较长的时候, 那么你做点积的时候,这些值呢可能就会比较大,但也可能是比较小了,当你的值相对来说比较大的时候,你之间的相对的那些差距啊就会变大,就导致说你值最大的那一个值做出来soft max就会更加靠近于1,剩下那些值呢就会更加靠近于0,就是你的值就会更加向两端靠拢,当你出现这样子的情况的时候,你算梯度的时候,你会发现梯度比较小,因为soft max最后的结果是什么,最后的结果就是我希望我的预测值的置信的地方尽量靠近1,不置信的地方尽量靠近0,这样子的时候,我说我的收敛就差不多了,这时候你的梯度就会变得比较小,那你就会跑不动,所以他说我们在Transformer里面一般用的dk比较大,之前说过是512,所以除以一个根号dk是一个不错的选择。

整个这个注意力的计算,他在上面有张图给大家画了出来,可以看到你在里面要两个矩阵,一个是query一个是key做矩阵乘法,然后再除以根号dk,这个地方我们一会儿讲,然后再做soft max,做出来结果最后跟你的值的那个矩阵做矩阵乘法就会得到你的输出了,这个是通过计算图来展示你这个是怎么做的。

另外一个我们要讲到是怎么样做mask,mask主要是为了避免你在第t时间的时候看到以后时间的东西,具体来说,我们假设我们的query和key是等长的,他们长度都为n,而且在时间上是能对应起来的,然后对第t时间课的qt,这是我的query,那么我在做计算的时候,我应该只是看k1一直到kt-1,而不应该去看了kt和他之后的东西,因为kt在当前时刻还没有,但是我们知道在注意力机制的时候,其实你会看到所有,你qt会跟所有k里面的东西全部做运算,就是kt一直算算算算到kn,那这个时候怎么办,就是说我们发现其实你算还是可以算的,就是说你把这些值全部给你算出来,然后在算出来之后,我们只要保证说在计算权重的时候,就是算输出的时候,我们不要用到后面的一些东西就行了,具体来说他就在你这个地方加了一个mask,mask的意思是说,对于qt和kt和他之后的计算那些值,我给你换成一个非常大的负数,比如说负的一一的十次方,那么这一个那么大的负数在, 进入soft max做指数的时候,它就会变成0,所以导致soft max之后出来的这些东西,它的它对应的那些权重都会变成0,而只会前面这些值出效果,这样子的话我在算我的output的时候,我只用了v对应的v1一直到vt-1的结果就用上了它,而后面的东西我没有看。所以这个mask效果是在我训练的时候,我让你t个时间的query只看我对应的前面那一些的key value pair,使得我在做预测的时候,我跟现在这个是能够一一对应上的。

在讲完注意力机制的计算之后,我们来看一下multi-head是在干什么事情。

我们首先还是回到我们的文字那部分,他这里说与其我做一个单个的注意力函数,不如说我把整个query,key value投影到一个低维,投影h次,然后再做h次的注意力函数,然后每一个函数的输出我把它并在一起,然后再投影来会得到我的最终的输出。

他说我们在图二给大家演示这个效果,那我们就跳回图二看一眼它是怎么做的,然后他说这个是我们原始的value、key、query,然后在这地方我们进入一个线性层,线性层就是把你投影的比较低的维度,然后再做一个scaled dot-product attention,就是这个东西了,全部放进来,然后我们这里做h次会得到h的输出,我们再把自己这些向量全部合并在一起,最后做一次线性的投影,会回到我们的multi-head attention。

所以为什么要做多头注意力机制呢,如果我们回过头来看这个dot-product的注意力的话,你会发现里面没有什么可以学的参数,你的具体函数就是你的内积,但有时候我为了识别不一样的那些模式,我希望你可能有一些不一样的计算像素的办法,如果你用的是加性attention的话,这里没有提到的,那里面其实还是有一个权重你来学的,你也许可以学到这些东西。

他说那我不用那个,那我用这个的话,我的一个做法是我先让你投影到一个低维,这个投影的w是可以学的,也就是说我给你h次会,希望你能学到不一样的投影的方法,使得在那个投影进去的那个度量空间里面能够去匹配不同模式它需要的一些相似函数,然后最后把这些东西回来,最后再做一次投影,所以跟我们之前说到的有点像在卷积网络里面,你有多个输出通道的感觉。

然后我们看一下这个东西的具体的公式是怎么算的,那你会发现是在Multi-Head的情况下,你还是以前的Q、K、V,但是呢你的输出已经是你不同的头的那一个输出的做concat起来,再投影到一个WO里面的,对每一个头他就是把你的Q、K、V,然后通过一个不同的可以学习的WQ、WK、WV投影到一个低维上面,在做我们之前提到过的注意力函数,然后再出来就行了。

这个地方你可以说每一个里面当时怎么算的,他们在实际上来说,他用的h是等于8的,就是用8个头,而且我们知道你的注意力的时候,因为有残差连接的存在,使得你的输入和输出的维度至少是一样的,所以它的做法是说你投影的时候,它投影的就是你的输出的维度除以h,因为我们之前我的输出维度是512,所以除以8之后,就是每一次我们把它投影到一个64维的一个维度,然后在上面算你的注意力函数然后再并起来再投影回来。虽然这个地方你看到是非常多的小矩阵的乘法,实际上你在实现的时候也可以通过一次的矩阵乘法来实现,这个可以作为一个练习题,大家去看一下怎么样实现它。

在讲完多头注意力是如何实现了之后,在3.2章的最后个小节里面讲的是在Transformer这个模型里面是如何使用注意力的,他这里讲了三种使用的情况,我们最简单的方法是回到我们之前那个架构图,看一看它到底是怎么被用的。

我们回到我们的架构图,我们看到的是黄色这个东西表示的是注意力的层,这个地方一共有三种不一样的注意力层。首先我们看一下我们的编码器的注意力是在干什么事情,然后我们分别来看一下每一个注意力层,它的输入和输出分别是什么,这个其实对应的是刚刚我们那一小节里边的三段话。

首先我们来看一下我们的编码器的注意力是在干什么事情。

我们知道编码器的输入这个地方,假设你的句子长度是n的话,它的输入其实是一个n个长为d的向量,假设我们的pn大小设成1了。

我们把它画出来就是每一个输入它的词,对应的是一个长为d的向量,然后我们这里一共有n个这样子的东西,然后我们来看一下注意力层,它有三个输入,他分别表示的是key、value和query,然后这个地方是你一根线过来,然后他复制成了三下,意思就是说同样一个东西我既作为key,也作为value,也作为query,所以这个东西叫做自注意力机制,就是说你的key、value和query其实就是一个东西,就是自己本身,然后我们知道那么这个地方我们输入了n个query,那么每个query我会拿到一个输出,那么意味着我会有n个输出,而且这个输出和value因为长度是一样的话,那么我的输出的维度其实也是那个d,就是意味着我的输入和输出的大小其实是一个东西。

但我们也可以把它画出来,画出来的话,其实你就是你的输出也是跟它长度一样长,长为n的一个东西,对于每一个query,我会计算一个这样子的输出。

因为我们知道这个输出其实就是你的value的一个加权和,权重是来自于query和key的一些东西,但它本身是一个东西,那么就意味着说,他的这个东西实际上本身就是你的输入的一个加权的一个和,然后这个绿色线代表权重的话,因为这个权重其实本身就是这一个向量,这个向量跟每一个输入的别的向量计算相似度,那么他跟自己算肯定是最大的,就是说你这根线肯定是最粗的,假设这个线跟你最这边这个向量也相似度比较高的话,那么这个权重也会稍微高一点。

假设我们不考虑多头和有投影的情况,你的输出其实就是你的输入的一个加权和,你的权重来自于你自己本身跟各个向量之间的一个相似度,但如果我们说过有多头的话, 因为有投影,其实我们在这个地方会学习h个不一样的距离空间出来,使得你出来的东西当然是会有一点点不一样了,这个就是第一个注意力层是如何用的。

解码器

解码器它这个地方一样的,是一个东西过来,然后复制成了三次,然后解码器的输入也是一样的,只是长度可能变成了一个长为m的样子,然后你的维度其实也是一样的,所以它跟编码器是一样的自注意力,唯一不一样的是这里有个masked这个东西。

我们之前有解释过,在解码器的时候,比如说你算这一个query它对应的输出的时候,它是不应该看后面那些东西,所以意味着是说在解码器的时候,你的这些后面的东西要设成0,我们用黄色的线表示一下,就是说后面这些东西呢这些权重你要设成零,在解码器的时候,这就是masked它的一个作用。

第三个注意力层

然后我们看第三个注意力层,也就是在这个地方,这个地方你看到是他不再是自注意力了,而是你的key和你的value来自于你的编码器的输出,然后你的query是来自于你解码器下一个attention的输入。

我们知道你的编码器最后一层的输出就是n个长为d的向量,我们把它画在这个地方还是那么你的解码器的masked attention就最下面那个attention它的输出是,m个也是长为d的向量,我们也把它画在这个地方,这里你的编码器的输出作为value和key进来,然后你的解码器下一层的输出呢作为query进来,意味着是说对解码器的每一个输出作为query我要算一个我要的输出,假设我用蓝色的表示的话,那么你的输出是我们知道是来自于value的一个加权和,那我就是来自于你的编码器它的输出的加权和,我如果把它划过来,就是有一个这样子的东西过来,一些权重过来,这个权重它的粗细程度就是取决于我这个query跟这个东西的相似度,假设我这个东西跟这个东西相似度比较高的话,那么我的权重这个地方就会大一点,如果相似度比较低的话,权重就会小一点,那意味着是说在这个attention干的事情其实就是去有效的把你的编码器里面的一些输出根据我想要的东西给它拎出来。

举个具体的例子,假设你是在做英文翻译中文,我假设第一个词是hello对应的向量是这个东西,然后我第二个词是hello world的话,那么中文第一个是你好,所以你会知道在算的时候,如果它作为query的时候,那么去看hello的这个向量应该是会相近一点,给它一个比较大的权重,但是world这个是后面的次相关,我发现到word这个词跟我这个query相关度没那么高,在计算的相似度的时候,那么就是说在算的时候,我会给它一个比较大的权重,在这一个上面,但是我在后面如果还有你好,世界的话,如果是这个的话,那么在这个query的时候,我再去算它的输出这个东西的时候,它那么就会给第二个向量一个比较大的一个权重出来。

意味着是说根据你在解码器的时候你的输入的不一样,那么我会去根据你的当前的那一个向量去在编码器的输出里面去挑我感兴趣的东西,也就是你注意到你感兴趣的东西,那些没有跟你不那么感兴趣的东西你就可以忽略掉它,这个也是说attention是如何在编码器和解码器之间传递信息的时候起到的一个作用。

这样我们就讲完了Transformer里面三个attention到底是在干什么事情,那么接下来我们要去讲蓝色这个feed forward是在干什么东西。

3.3节讲的就是这个名字很长的point-wise feed forward network,他说他其实就是一个fully connected feed-forward network,就是一个MLP了,但是他不一样的是说他是applied to each position seperately and identically。position是什么东西呢,就是你输入的那一个序列不是有很多很多个词吗,每个词它就是一个点,他就是那一个position,然后他就是把一个MLP对每一个词作用一次,然后对每个词作用的是同样一个MLP,所以这个就是point wise的意思,它说白了就是MLP只是作用在最后一个维度。

具体来看一下它是怎么写的,xW1+b1就是一个线性层,max(0,xW1+b1)就是一个Relu的激活层,然后再有一个线性层max(0,xW1+b1)W2+b2,我们知道在我们的注意力层,它的输入就每一个query它对应的那一个输出,它是常为512,那么就是说这个x就是一个512的一个向量,他说w1我会把512投影成2048,这个维度就等于是我把它的维度扩大了四倍,因为最后你有一个残差连接,你还得投影回去,所以W2又把2048投影回了512,所以这个东西说白了就是一个单隐藏层的MLP,然后中间隐藏层把你的输入扩大四倍,最后输出的时候也回到你输入的大小,说白了就是你用pytorch来实现的话,它其实就是把两个线性层放在一起,你都不需要改任何参数,因为pytorch去当你的输入是一个3d的时候,它默认就是在最后一个维度做计算。

为了更好的理解,我们用图把他跟你的attention这个东西给大家画一下,以及说它跟我们之前的rn它的区别在什么地方,我们这里还是考虑一个最简单的情况,就是没有残差连接,也没有lay alone,然后你的attention也是一个单头,然后没有投影,我们知道我们的输入就是一个长为n的一个一些向量,在进入attention之后,就是attention之后,我们就会得到同样长度的一些输出,在最简单的情况的attention,其实说白了就是对你的输入做一个加权的和,然后加权和之后我们进入我们的MLP,就是那个point wise的MLP,虽然我们画了几个东西, 但其实它就一个,就是说每一个红色的方块之间的权重是一样的,然后每个MLP对每一个输入的点呢做运算会得到一个输出,最后就得到了整个Transformer块的一个输出是这样子,虽然它的输入和输出都是大小都是一样的,所以这个地方你看到的是说,attention起的作用是什么东西,他就是把整个序列里面的信息抓取出来,做一次汇聚aggregation,所以这个东西已经就有了我序列中感兴趣的东西,信息已经抓取出来了,以至于我在做投影,在做MLP的时候,映射成我更想要的那个语义空间的时候,因为这个东西已经含有了我的序列信息,所以每个MLP只要在对每个点独立做就行了,因为这个地方历史信息、序列信息已经被汇聚完成,所以这个地方是可以分开做的,也就是整个Transformer是如何抽取序列信息,然后把这些信息加工成我最后要的那个语义空间那个向量的过程。

作为对比我们看一下RNN是怎么做的,我们知道RNN的输入跟你是一样,就是一些向量,然后对于第一个点呢,说白了你也就是做一个线性层,我们做一个最简单的,就是一个没有隐藏层的MLP,就是一个纯线性的层,第一个点就是直接做出去就完事了,对于下一个点,我是怎么样利用我的序列信息的呢,我还是用之前这个MLP它的权重跟之前是一样的,但是呢我的时序信息我用绿色表示,它就是把这个东西它的上一个时刻的输出放回来,作为跟输入一起并入进去,这样子我就完成了我信息的一个传递,然后用绿色的线表示的是之前的信息,蓝色的线当时表示的是我当前的信息,这样子我会得到一个当前的一个输出,历史信息就是上一次的那个输出作为历史信息进来,然后得到我当前的一个输出。
所以可以看到是说RNN跟Transformer是一样的,都是用一个线性层或者说一个MLP来做一个语义空间的一个转换,但是不一样的是你如何传递序列的信息,RNN是把上一个时刻的信息输出传入下一个时候做输入,但是在Transformer里面,它是通过一个attention层,然后再全局的去拉到整个序列里面信息,然后再用MLP做语义的转换,这个是两个模式之间的区别,但是它的关注点都是在你怎么有效的去使用你的序列的信息。

这样我们就只剩最后两个层了,第一个层是叫做embedding,后面一个层叫做positional encoding。

这embedding大家都知道,因为我的输入是一个个的词,或者一个叫词源,叫token,那我需要把它映射成一个向量,embedding就是说给任何一个词,我学习一个长为d的一个向量来表示它,这个d,当然这个地方你可以认为就是等于512了,他这里是说你的编码器要一个embedding,你的解码器的输入也要有个embedding,这货在你的soft max前面那个线性,也需要一个embedding,他说我这三个是一样的权重,这样子我训练起来会简单一点。另外一个有意思的是说他把权重乘了一个根号d,d就是512,为什么做这个事情,是因为你在学embedding的时候,多多少少会把每一个向量它的L2 Norm学成,相对来说比较小的,比如说学成1吧,就不管你的维度多大的话,最后你的值都会等于1,那就是说你的维度一大,你学的一些权重值就会变小,但是你之后我要加上这个东西(positional encoding),加这个东西的时候,它不会随着你的长度变成了它把你的L2 Norm固定住,所以它成了它之后,使得这么两个相加的时候, 再一个scale上大家都差不多,就是他做了一个hat。

下面一个东西叫做positional encoding,你为什么有这个东西,是因为你发现,attention这个东西是不会有时序信息的,你想一想你的输出是什么东西,你的输出是你的value的一个加权和,你这个权重是query和key的之间的那个距离,它跟你的序列信息是无关的,就我根本就不会去看你那个key value里面那些对在那个序列里面哪个地方,所以意味着说我给你一句话,我把顺序任何打乱之后,我attention出来结果都是一样的,顺序会变,但是值不会变,这个当然是有问题的,在处理时序数据的时候,我给你一句话,假设我把里面的词给你完全打乱,那么你语义肯定会发生变化,但是你的attention不会处理这个情况,所以我需要把时序信息加进来,RNN是怎么加呢,RNN是上一个时刻的输出作为下一个时刻的输入来传递我的历史的信息,所以它本来就是一个时序的一个东西,但是attention不是,他的做法是说我在我的输入里面加入时序信息,就是说你这一个词,他在一个位置i,i这个位置这个数字12345,加到你的输入里边,所以这个东西叫做positional encoding。

具体来说他后面给了一个公式,它是具体是怎么算的,我就不给大家完全讲个公式,给大家讲一下大概的思路是什么样子,在计算机里面我们怎么表示一个数字,假设我用一个32位的整数来表示数字的话,那就是用32个bit,每个bit上面有不同的值来表示012345678,你可以认为就是说我一个数字是用一个长为32的一个向量来表示的,现在我一个词在嵌入层会表示成一个长为512的向量,同样我用一个长为512的向量来表示一个数字,表示你这个位置012345678,具体那些值是怎么算出来的,是用周期不一样的sincos函数的值来算出来的,所以导致说,我任何一个值可以用一个长为512的一个向量来表示它,然后这个长为512的记录了时序信息的一个东西跟你的嵌入层相加,就会完成了我把时序信息加进我的数据的一个做法。

如果我们回到之前的架构图的话,你可以看到我的输入进来,进入embedding层之后, 那么对每个词都会拿到那个向量长为512的一个向量,然后positional encoding,就是你这个词在我这个句子中的位置告诉他,他返回给你一个长为512的一个向量表示这个位置,把这两个加起来就行了,这个东西因为是cos和sin的一个函数,它是在正1负1之间抖动的,所以这个东西成了一个根号d,使得每个数字也是在差不多的正1到负1之间这个数值区间里面,然后进去之后,那么我就完成了在输入里面加入了信息,这是因为我后面整个这一块是顺序不变,就是说我的输入的序列,不管我怎么打乱我的顺序,进去之后,我的输出那些值是不变的,最多是我的顺序发生了相应的变化,所以他就把顺序信息直接在数据里面那个值给加进去了。

这样我们就讲完了, 第三章也就是整个Transformer模型的架构。

第四章是大概解释一下为什么要用自注意力。你发现读到这里为止, 整个这篇文章是告诉你我这个模型长什么样子,并没有告诉你说我为什么要这么做,以及我整个设计理念是怎么回事,但这个地方大家给你稍微讲了一下,我为什么要这么做,它主要说的是相对于你用循环层或者卷积层的时候,我用自注意力有多么好,但是整个来说这篇文章对整个模型的解释其实是比较欠缺的。

然后整个这一段话他解释的其实就是我们这一个表,我们就直接给大家讲一下这个表是在干什么。

他说我们比较了四种不一样的层,第一个当然是他们关注的自注意力, 然后是循环层、卷积层,另外一个是它构造出来一个受限的自注意力。它有三列做比较,第一列是说我的计算复杂度当然是越低越好,第二个是说我的顺序的计算越少越好,顺序的计算就是说你下一步计算必须要等前面多少步计算完成,在算一个Layer的时候,你越不要等那么你的并行度就越高,最后一个是说一个信息从一个数据点走到另外一个数据点要走多远,这也是越短越好。

自注意力
我们分别来看一下每一个层它代表的数值是什么意思,首先我们来看自注意力,n这个地方是你序列的长度,d是你向量的长度,我们知道整个自注意力的话,其实说白了就是几个矩阵做运算,其中一个矩阵是你的query的矩阵乘以你的key的矩阵,你的query矩阵是有n行,你的n个query,然后你的列数是d,就是你的维度是d,然后你的key也是一样的,也是n乘d,所以两个矩阵一乘的话,那么算法复杂度就是n的平方乘以d,另外当然还有一些别的矩阵运算,但是它的复杂度都是一样的,所以是有个O(n^2 *d)的这个地方,然后你的sequential operation,因为你就是那么几个矩阵乘法,矩阵里面它可以认为是并行度比较高的,所以这个地方是一个O(1)的,最大的长度是说,你从一个点的信息想跳到另外一个点要走多少步,我们知道在attention里面,就是一个query可以跟所有的key去做运算,而且你的输出是你所有value的一个加权和,所以就是说任何query跟任何一个很远的一个key value pair,我只要一次就能过来,所以这个长度是比较短的。

循环层
然后看一下循环层,如果你的序列是乘了n的话,它就一个一个做运算,每个里面呢它的主要的计算就是一个n乘以n的一个矩阵,就是一个dense layer乘以你一个成为d的一个输入,所以它是一个n平方,然后要做n次,所以是n乘d的平方,然后你对比一下这两个东西是有一定区别的,就真的取决于是n大还是d大,如果你n大的话,当然它过一点,你d大的话是下面一个过一点,实际上来说你的d这个地方是512,你的n也差不多是几百的样子,现在当然是比较大的模型的话,d可以做到2048甚至更大,你的n相对来说也会做的比较大,也是几千的样子,所以你其实现在看起来这两个东西都差不多,就是n和d的,其实在差不多的数据上面,所以这两个都差不多,但是在循环的时候,因为你是要一步一步做运算,当前时间刻的那个词需要等待前面那个东西完成,所以导致你是一个成为n的一个序列化的操作,在并行上是比较吃亏的,我们之前提到过,另外一个是说你最初点的那个历史信息需要到最后那一个点的话,需要走过n步才能过去,所以他这个地方的最长是O(n),所以大家会批评RNN说你对特别长的序列的时候做的不够好,因为你的信息一开始走啊走啊走,就走丢了,而不像attention一样,就可以直接一步就能过去。

卷积层
然后看一下卷积,大家也没有特别解释卷积在序列上怎么做,具体做法是它用一个1d的卷积,所以他的kernel就是个k,所以就是不是k平、就是k,n是你的长度,然后d就是你的输入的通道数和输出的通道数,所以这个地方是k乘n乘d的平方,k一般也不大,k一般就三啊五啊、几,所以这个东西你也可以认为是常数,所以导致说卷积的复杂度和RNN的复杂度其实是差不多的,但是卷积的好处是说,你就是一个卷积操作就完成了,里面的并行度很高,所以卷积做起来通常比RNN要快一点,另外一个是说卷积每一次一个点是一个成为k的一个窗口来看,所以他一次一个信息在k距离内是能够一次就能传递,如果你超过k了的话,他要传递信息的话,他要通过多层一层一层上去,但是它是一个log的一个操作,所以这个东西也不亏。

受限的自注意力
最后一个东西他是说当我做注意力的时候,我的query只跟我最近的r个邻居去做运算,这样子的话我就不用去算n平方这个的东西了,但他的问题是说,这样子的话有两个比较长的远的一个点, 需要走几步才能过来,所以他就损失了这个东西,一般来说在实际上来说,我们用attention主要是关心特别长的序列,你真的能够把整个信息揉的比较好一点,所以在实际过程中,这一块感觉上用的不是那么多,大家都是用最原始的版本,不用太做受限了,所以基本上就是考虑这三个。

所以基本上可以看起来是说就实际中,就是当你的序列的长度,和你的整个模型的宽度差不多的时候,而且大家深度都一样的话,基本上这三个模型的算法复杂都是差不多的,当然是说你的attention和卷积相对来说计算会好一点,另外一个是说attention在信息的糅合性上会好一点,所以你可能认为这个地方还是能影响一些东西了,所以你看上去是说用了self-attention之后,是不是你的感觉上对长数据处理更好,而且可能算得也不快也不会慢,但实际上其实也不是这样子的,实际上是说attention对你整个模型的假设做了更少,导致说你需要更多的数据和更大的模型才能训练出来跟你的RNN和CNN同样的效果,所以导致现在基于Transformer的模型都是特别大特别贵。

7.实验

第五章是讲你训练的一些设置是怎么样的,首先他说我的训练数据集和我的batching怎么做的,他用了两个人物,一个是英语翻德语,他用的是标准的WMT 2014的数据,它这个里面有4.5万个句子的对,他说他用的是byte-pair encoding,就是bpe。大概的思想是说,你不管是英语还是德语,其实一个词里面有很多种变化,就是什么加ing,加es,但是你如果直接把每一个词做成一个token的话,你会导致你的字典里面的东西会比较多,而且一个动词的可能有几种变化形式,你做成不一样的词的时候,他们之间的区别模型是不知道了。bpe相对来说就是把你那些词根给你提出来,这样好处是说它可以把整个字典这样的比较小,它这个地方用的是37000个token的一个字典,而且它是在英语和德语之间是共享的,就是说我们不再为英语构造一个字典,不再为德语构造一个字典,这样的好处是说我整个编码器和解码器的一个embedding,就可以用一个东西了,而且整个模型变得更加简单,也就是他之前所谓的我的编码器、解码器那个embedding它是共享权重的,另外一个英语到法语的话,他用了一个更大的一个数据集, 在接下来的硬件和schedule部分,他说我训练使用了八个P100的GPU,这个是比较有意思的,就是说在三年前,google的工作还是大量使用gpu的,但是这个之后基本上就很少见了,因为在这个之后, google让内部的员工尽量使用tpu,限制你们使用gpu,这篇文章多多少少也是推动这个进展,这是因为Transformer里面我们知道基本就是一些比较大的矩阵做乘法,tpu这种东西就是特别适合做大的矩阵乘法,以致于Google说,你们其实挪到tpu也没有太多问题,可能性能还更好一些。他说我们的base模型呢是用的一个小一点的参数,他每一个bench训练的时间是0.4秒,然后我们一共训练了10万步,一共就是在我的八个gpu上训练了12个小时,就基本上一台机器训练12个小时,也是不错的一个性能,他说我一个大的模型,这样一个batch训练需要一秒钟,然后他一共训练了30万步,最后是一台机器3.5天,那其实也是一个可承受的范围,但是这个朋友之后的那些工作基本上是属于大家几乎不能承受的时间成本了。
在训练器上面,他们用的是Adam,这是它的参数,β2我觉得可能不是最常用的,β2我记得应该是0.99还是0.999,所以他选了一个稍微少一点的值,然后他的学习率是用这个公式算出来的

有意思的是他的学习率是根据你的模型的那个宽度的-0.5次方,就是说当你的模型越宽的时候,就是你学的那些向量越长的时候,你的学习率要低一点,另外一个是它有一个warmup,就是从一个小的值慢慢地爬到一个高的值,爬到之后,再根据你的步数按照0.5次方衰减,最后他说我的warmup是4000,有意思的是,基本可以看到说这个地方没有东西可以调的,就是你的学习力几乎是不用调的,取决于第一adam对学习率确实不那么敏感,第二个是说他这个地方也是把两个模型考虑进来了,这个schedule就已经也算是不错的schedule了,所以在学习率是不需要调的。

然后在5.4的时候讲的他使用的正则化,他用了三个正则化,第一个是他用的是一个叫做residual dropout,说白了就是说对每一个子层,子层就是包括了你的多头的注意力层和你之后的MLP,在每个层的输出上,在他进入残差连接之前和在进入layer alone之前,他使用了一个dropout,他的dropout率是0.1,也就是说把这些输出的10%的那些元素只乘0.1,剩下的那些只能乘以1.1。
另外一个他在输入加上你的词嵌入,再加上你的positional enconding的时候,在它上面也用了一个dropput,也就是把10%的元素值乘了0.1,有意思的是说你基本看到对每一个带权重的乘,他在输出上都使用的dropout,用的是比较狠的,虽然这个dropout率并不是特别高,但是它使用了大量的dropout的层来对它的模型做正则化。
然后另外一个他使用的是一个label smoothing,这个技术是在inception v3,意思是说我们用softmax去学一个东西的时候,我的标号是正确的是1、错误的是0,就是我用对于正确的那一个label的softmax的值去逼近于1,但我们知道softmax是很难逼近于1的,因为它里面是一个指数,它是一个很soft的一个东西,就是说它需要你的输出接近无限大的时候才能逼近于1,这个使得训练比较难,一般的做法是说你不要让搞成那么特别难的0和1,你可以把那个一的值往下降一点,比如说降成0.9,但这个地方他降得比较狠,他是降成了0.1,就是说对于正确的那个词,我只需要我的softmax的输出是到0.1就行了,叫置信度是0.1就行了,不需要做的很高,剩下的那些值就可以是0.9除以你的字典的大小。他说这里会损失你的perplexity,perplexity是你的log lost做指数,基本上可以认为是你的模型不确信度。因为你这个地方让你学说我正确答案,我也只要给个10%是对的就行了,所以当然你的不确信度会增加,所以你这个值会变高,但是他说我的模型会不那么确信会提升我的精度和我的BLUE的分数,因为精度和blue的分数才是我们关心的重点,所以这个地方他说觉得没关系。

那我们接下来看第二个表,第二个表表示的是不同的超参数之间的一些对比,我们可以看一下有哪些超参数,就回忆一下n是什么,n是你要堆多少层,d是你这个模型的宽度,就是一个token进来要表示成一个多长的向量,dff表示的是你拿MLP中间那个隐藏层的输出的大小,h是你的头的个数,就是你注意力层的头的个数,dk、dv分别是你一个头里面那个key和那个value那个维度,你的Pdropout是你的丢弃的率,以及这个是说你最后 label smoothing的时候你要学的那个label的真实值是等于多少,最后一个是说你要训练多少个batch。

然后可以看一下它的base模型, base模型我们知道是说他用了六个层,然后每一层的宽度是512,然后这个东西基本上就是它的四倍了,然后头的数乘以你的维度数是等于他的,就是8乘64 等于500兆,dropout率是0.1,然后这个是0.1,然后最后是训练了10万步。

另外一个是他的big模型,基本上可以看到是说,这个东西没有变,但是模型的宽度乘了两倍,然后啊这个东西也当然是跟着翻倍了,他也把这个头的个数乘了两倍,然后这个东西就不用变了,另外一个是你的模型更加复杂了,所以他用了一个比较大的丢弃率0.3,而且模型更加复杂了,收敛会慢一点,因为你的学习率是变低了的,这个地方他训练的30万个批量大小。

所以基本上可以看到是,整个模型参数相对来说还是比较简单的,基本上你能调的就是一个要多少个层,然后你的模型有多宽,以及说你要多少个头,剩下的这些东西基本上都是可以按比例算过来的, 这个也是Transformer架构的一个好处,虽然你看上去那个模型比较复杂,但是也没有太多东西可以调,这个设计上来说让后面的人更加方便一点,比如说bert它其实就是把这个价格拉过去,然后把这个把这几个参数改了一下,gpt也就是基本上就是copy过去,然后稍微调一下就行了,让后面的人的工作变得简单了很多。

最后一个表是另外一个NLP任务上,大家不熟的话,我们就可以跳过来,你放一个也挺好的,你不放其实也没关系,就是他是说我的结果还不错,就是不仅仅是在机器翻译上还行, 我又找了一个另外一个人,说效果也还挺好的,就是这个要表达的一个观点。

8.评论

最后我们来评价一下这篇文章,我们首先看一下它的写作,这篇文章的写作是非常简洁的,因为就是说,他每一句话就基本上在讲一件事情,你看那么短短的一篇文章,我们读下下来大概就花了一个半小时,也能理解是说你有那么多东西,你要塞到那么短的一个篇幅里面,你只能用这么写。
另外一个是说他没有用太多的写作技巧,基本上就是说你看我提出一个什么东西,这个模型长什么样子,跟CNN和RNN比是什么样子,最后的实验结果是什么东西,这个不是那么推荐的一种写法,因为对一篇文章来说,你需要在讲一个故事,让你的读者有代入感,能够convince读者。
但是我也能理解说,你要在一篇文章里面发现那么多东西的话,也没那么多篇幅来讲一个很好的故事,一般的建议是说,假设你写篇文章的话,你可以选择把你的东西减少一点,甚至把一些不那么重要的东西放到你的附录里面,但是在正文的时候,你还是最好讲个故事,说你为什么做这个事情,你的一些设计的理念是什么样子的,你的对整个文章的一些思考是什么样子的,这个东西让大家会觉得你的文章更加有深度一些。

接下来我们评论一下Transformer这个模型本身,我们现在当然可以看到Transformer模型不仅仅是用在机器翻译上面,它也能够用在几乎所有的NLP的任务上面,在后续的工作,bert、gpt 让大家能够训练很大的预训练模型,能够极大的提升所有NLP里面的任务的性能,这个有点像CNN在对整个计算机视觉的改变,我们能够训练一个大的CNN的模型,使得别的人物也能够从中受益。
另外一个是说,CNN给整个计算机视觉的研究者提供了一个同样的一个框架,使得我只要学会CNN就行了,而不需要去管以前跟任务相关的那么多的专业的知识,比如说做特征提取,对整个任务怎么建模,Transformer也是一样的,之前我们要做各种各样的数据文本的预处理,然后我要根据NLP的任务给你设计不一样的架构,现在不需要了,我们用整个transform这个架构就能够在各个任务上做得非常好的成绩,而且他预设的模型也让大家的训练变得更加简单。
当然我们还看到的是Transformer现在不仅仅是用在自然语言上面,,也在图片上、语音上、media上取得了很大的进展,这个是一个非常有影响力的一件事情,这是因为之前计算机视觉的研究者是用CNN,而在原处理你用RNN,然后别的人用别的模型,现在是说大家发现同样一个模型能够在所有领域都能用,就是让大家的语言变成一样了,就以前你用python,我用java,现在我说大家都用python了,那你任何一个领域的研究者做的一些突破能够很快的在别的领域被使用,能够极大的减少一个新的技术在机器学习里面各个领域被应用的那个时间。
另外一块的话我们知道人对世界的感知是多模态的,我们看见图片,我们读文字,我们听到语音,现在Transformer能够把这些所有的不同的数据给你融合起来,因为大家都用一个同样的架构抽取特征的话,那么他可以抽到一个同样的语义空间使得我们可以用文本、图片、语音、视频,能训练更好更大的模型,也可以看到这一块应该是现在和未来的一个研究重点。

但反过来讲,虽然Transformer这些模型取得了非常好的实验性的结果,但是我们对它的理解还在比较初级的阶段,第一个是说这篇文章的标题叫做你只需要attention就行了,但是最新的一些结果表明attention只是在Transformer里面起到一个作用,它的主要作用是把整个序列的信息给大家聚合起来,但是后面的MLP以及你的残差连接是缺一不少了,如果你把这些东西去掉的话,attention基本上什么东西的训练不出来,所以你这个模型attention也不是说你只需要它就行了。第二个是说, attention根本就不会去对你这个数据的那个顺序做建模,他为什么能够打印RNN,RNN能够显示的建模这个序列信息理论上应该比你的mlp效果更好,现在大家觉得他使用了一个更广泛的归纳偏置,使得他能处理一些更一般化的信息,这也是为什么说,而attention并没有做任何空间上的一些假设,它也能够跟CNN甚至是比CNN取得更好的一些结果。但是它的代价是说因为它的假设更加一般,所以他对数据里面抓取信息的能力变差了,以至于说你需要使用更多的数据,使用更大的模型才能训练出你要的效果,这也是为什么现在Transformer模型一个比一个大训练,一个比一个贵。

另外最后要提到一点是说,Attention也给了研究者一些鼓励说,原来在CNN和RNN之外也会有新的模型能打败他们,现在有很多工作说我就用MLP或者就用一些更简单的架构也能够在图片上面在文本上面取得很好的结果,所以我觉得在未来肯定会有更多新的架构出现让整个领域更加有意思一些。

9.面试题

  1. Transformer为何使用多头注意力机制?(为什么不使用一个头)
  2. Transformer为什么Q和K使用不同的权重矩阵生成,为何不能使用同一个值进行自身的点乘? (注意和第一个问题的区别)
  3. Transformer计算attention的时候为何选择点乘而不是加法?两者计算复杂度和效果上有什么区别?
  4. 为什么在进行softmax之前需要对attention进行scaled(为什么除以dk的平方根),并使用公式推导进行讲解
  5. 在计算attention score的时候如何对padding做mask操作?
  6. 为什么在进行多头注意力的时候需要对每个head进行降维?(可以参考上面一个问题)
  7. 大概讲一下Transformer的Encoder模块?
  8. 为何在获取输入词向量之后需要对矩阵乘以embedding size的开方?意义是什么?
  9. 简单介绍一下Transformer的位置编码?有什么意义和优缺点?
  10. 你还了解哪些关于位置编码的技术,各自的优缺点是什么?
  11. 简单讲一下Transformer中的残差结构以及意义。
  12. 为什么transformer块使用LayerNorm而不是BatchNorm?LayerNorm 在Transformer的位置是哪里?
  13. 简答讲一下BatchNorm技术,以及它的优缺点。
  14. 简单描述一下Transformer中的前馈神经网络?使用了什么激活函数?相关优缺点?
  15. Encoder端和Decoder端是如何进行交互的?(在这里可以问一下关于seq2seq的attention知识)
  16. Decoder阶段的多头自注意力和encoder的多头自注意力有什么区别?(为什么需要decoder自注意力需要进行 sequence mask)
  17. Transformer的并行化提现在哪个地方?Decoder端可以做并行化吗?
  18. Transformer训练的时候学习率是如何设定的?Dropout是如何设定的,位置在哪里?Dropout 在测试的需要有什么需要注意的吗?
  19. 解码端的残差结构有没有把后续未被看见的mask信息添加进来,造成信息的泄露。