目录
1、transformer
最近三年以内深度学习里面最重要的文章之一
可以认为是开创了继MLP、CNN、RNN之后的第四大类模型
2、摘要
在主流的序列转录模型里面,主要是依赖于比较复杂的循环或者是卷积神经网络,一般是使用encoder和decoder的架构
序列转录模型:给定一个序列,然后生成另外一个序列,比如机器翻译
在性能最好的模型之中,通常也会在编码器和解码器之间使用注意力机制
这篇文章提出了一个新的简单的架构(simple,之前都倾向于写成novel),这个模型就是Transformer仅仅依赖于注意力机制,而没有用之前的循环或者卷积。
做了两个机器翻译的实验,显示这个模型在性能上特别好,可以并行度更好然后使用更少的时间来训练
模型达到了28.4的BLEU
BLEU score:机器翻译里面常用的一个衡量标准
这篇文章一开始写的时候是针对机器翻译这个小任务写的,但是随着之后BERT、GPT把这个架构用在更多的语言处理的任务上的时候,整个工作就出圈了,最近用在了图片和video上面,几乎什么东西都能用
3、结论
transformer这个模型是第一个仅仅使用注意力机制做序列转录的模型,将之前的循环层全部换成了multi-headed self-attetion(多头注意力)
在机器翻译的这个任务上面,transformer能够训练的比其他的架构都要快很多,而且在实际的结果上确实是效果更好
作者对于这种纯基于注意力机制的模型感到非常激动,作者想要把它用在文本以外的别的数据上面,使得生成不那么时序化也是另外的一个研究方向
这篇文章所有的代码放在tensor2tensor这个库里面,作者将整个代码放在了结论的最后(如果有代码的话,通常会把这个代码放在摘要的最后一句话,因为现在神经网络的文章里面细节通常是比较多的,简简单单的一篇文章很难把所有的细节都讲清楚,所以最好第一时间公布代码,让别人能够很方便地复现文章,然后这样就能够扩大文章的影响力)
4、文章的第一部分 导言
这里的导言写的比较短,基本可以认为是前面摘要的前面一半的扩充
在时序模型里面,2017最常用的是RNN、LSTM、GRU,有两个比较主流的模型:
- 一个叫做语言模型
- 另一个是当输出结构化信息比较多的时候会使用编码器和解码器架构
RNN的特点
- RNN中给定一个序列,它的计算是把这个序列从左到右一步一步往前做,假设序列是一个句子的话,就是一个一个词,对第t个词会计算出一个输出叫做ht(也叫做它的隐藏状态),ht是由前面一个词的隐藏状态(h(t-1))和当前第t个词本身决定的,这样就可以把前面学到的历史信息通过h(t-1)放到当下,然后和当前的词做一些计算,然后得到输出
**RNN如何有效处理时序信息的关键:**他将之前的信息全部放在隐藏状态里,然后一个一个放下去。他的问题也来自于这里:
- 它是一个一步一步计算(时序)的过程,比较难以并行,在时间上难以并行,使得在计算上性能比较差
- 历史信息是一步一步地往后传递的,如果时序比较长的话,那么很早期的时序信息在后面的时候可能丢掉,如果不想丢掉的话,可能需要ht要比较大,如果要做比较大的ht,在每一个时间步都得把他存下来,导致内存开销比较大。
attention在RNN上的应用
- 在这篇文章之前,attention已经成功地用在编码器和解码器里面了,主要是用在怎么样把编码器的东西有效地传给解码器
这篇文章提出来的transformer是一个新的模型,不再使用之前被大家使用的循环神经层,而是纯基于注意力机制,并行度比较高,这样的话它能够在比较短的时间之内做到比之前更好的结果
4、文章的第二部分 相关工作
如何使用卷积神经网络替换循环神经网络,减少时序的计算
主要问题:用卷积神经网络的话,对于比较长的序列难以建模
卷积做计算的时候每次是去看一个比较小的窗口,如果两个像素相隔比较远的话,需要用很多层卷积堆积起来,最后才能够把这两个隔得比较远的像素融合起来。但是如果是使用transformer里面的注意力机制的话,每一次都能够看到所有的像素,使用一层就能看到整个序列
卷积的好处是可以做多个输出通道,一个输出通道可以认为是识别不一样的模式,作者也想要实现这种效果,所以提出了一个叫做multi-headed attention(多头注意力机制),用于模拟卷积神经网络多输出通道的一个效果
自注意力机制,虽然很重要,但是早期有人提出来了,不是本文的创新点
5、文章的第三部分 模型架构
序列模型里面比较好的是编码器和解码器的架构
- 编码器:将一个长为n的x1到xn的输入,编码器会把它表示成为一个也是长为n但是其中每一个zt对应xt的向量的表示,这就是编码器的输出,就是将原始的输入变成机器学习可以理解的一系列向量
- 解码器:拿到编码器的输出,然后生成一个长为m的一个序列(n和m是不一样长的,可以一样,也可以不一样)。他和编码器最大的不同之处在于,在解码器中,词是一个一个生成的(因为对于编码器来讲,很可能是一次性能看全整个句子,但是在解码的时候只能一个一个的生成),即自回归(auto-regressive),在这里面输入又是输出,在过去时刻的输出也会作为当前时刻的输入
- transformer是使用了编码器和解码器的架构,具体来讲它是将自注意力和point-wise、fully connected layers一个一个堆在一起,下图是一个编码器和解码器的架构。
- 左边圈出来的部分是编码器,右边圈出来的部分是解码器
- 左下角的input是编码器的输入,如果是中文翻英文的话,输入就是中文的句子
- 右下角的outputs是解码器的输入,解码器在做预测的时候是没有输入的,实际上就是解码器在之前时刻的一些输出作为输入
- shifted right就是一个一个往右移
- input embedding:嵌入层。进来的是一个一个的词,需要将它们表示成向量
- poositional encoding:
- Nx:N代表层数
- 左边红色圆圈圈出来的部分可以看作transformer block
- multi-headed attention 多头注意力机制
- feed forward:前馈神经网络
- add&norm:add表示残差的连接
- 编码器的输出会作为解码器的输入
- 解码器和编码器有点像,右侧红色圆圈圈出来的部分是一样的,但是多了下面的masked multi-headed attention(多头注意力机制)
- 解码器可以认为是红色圆圈中的三部分组成的一个块重复n次
- 解码器的输出进入一个输出层,然后做一个softmax就能得到最终的输出
- 红色方括号所扩起来的部分就是标准的神经网络的做法
- 上图所示的确是一个标准的编码器解码器的架构,只是说中间的每一块有所不同,然后是编码器和解码器之间的连接有所不同
6、文章的第三部分 模块实现
编码器
编码器是用一个n等于6的一个完全一样的层,上图中的左边红色圈部分算作一层,然后重复6次
- 每一个layer中会有2个sub-layer
- 第一个sub-layer叫做multi-headed self-attention
- 第二个sub-layer是一个simple position-wise fully connected feed-forward network,说白了就是一个MLP
- 对于每一个子层采用了一个残差连接,最后再使用layer normalization
子层的公式如下图中黄色高亮部分所示
- sublayer(x):输入进来以后先进入子层
- x+sublayer(x):因为是残差连接,所以将输入和输出加在一起,最后进入他的layernorm
- 因为残差连接需要输入和输出是一样大小的,如果大小不一样的话,需要做投影,为了简单起见,讲么一个层的输出维度变成512(固定长度表示是的模型相对来说是比较简单的,调参的话只需要调一个参数就行了,就是模型的输出维度,另外一个参数是要复制多少块n)
layernorm层次归一化
通过和batchnorm批量归一化对比来解释一下什么是layernorm
考虑一个最简单的二维输入的情况,在二维输入的情况下输入是一个矩阵,每一行是一个样本,每一列是一个特征
batchnorm所干的事情就是每一次将每一列(特征)在它的一个小mini-batch里面的均值变成零,方差变成1
- 把这个向量本身的均值减掉,然后再除以他的方差就可以了
- 在计算均值的时候,是在每个小批量里面(一条向量里面算出他的均值和方差)
- 在训练的时候可以做小批量,在预测的时候会把全局的均值算出来
- 在预测的时候会把全局的均值算出来,整个数据扫一遍之后,在所有数据上平均的均值方差存起来,在预测的时候再使用
batchnorm还会学 lambda和beta 出来:可以把向量通过学习放成一个方差为任意某个值,均值为任意某个值的东西
layernorm和batpchnorm在很多时候几乎是一样的
- 对于一个同样的二维输入来说(最简单的情况),layernorm对每个样本做normalization而不是对每个特征做normalization(之前是将每个列的均值变为0,方差变为1,现在是把每一个行变成均值为0,方差为1,这里的每一个行表示一个样本,所以可以认为layernorm就是把整个数据转置一下放到batchnorm里面出来的结果,再转置回去)
- 在transformer或者正常的RNN里面,输入是一个三维的东西,输入的是一个序列的样本,每一个样本其实里面有很多元素(比如一个句子里面有n个词,每个词有个向量hebatch的话,就是一个3D的东西)。最大的正方形表示batch(样本),但是列不再是特征了,而是序列的长度(sequence),对每个sequence(每个词)都有自己的向量,即feature
如果还是用batchnorm的话,每次是取一根特征,然后把他的每个样本里面的所有元素,以及他的整个batch取出来,如下图立方体中蓝色正方形所示,然后把他的均值变为0方差变成1,就相当于是切一块下来然后拉成一个向量,然后再进行运算。
如果是laynorm的话,就是对每个样本进行横切,如下图立方体中橘黄色正方形所示
切法不一样会带来不同的效果,为什么layernorm用的多一点?
在时序序列模型中,每个样本的长度可能会发生变化,如下图中红色阴影所示,没有的东西一般是会放零进去
如果是用batchnorm的话,切出来的效果如下图蓝色中所示,其余地方补零
如果是layernorm的话,切出来的效果如下图黄色所示
- 这里主要的问题是在算均值和方差上面,对于batchnorm来说,会对上图中切出来的阶梯形的部分进行求解(只有这部分是有效值,其他地方因为是补零,所以其实没有太多作用),如果样本长度变化比较大的时候,每次做小批量的时候,算出来的均值和方差的抖动相对来说是比较大的
- 另外,在做预测的时候要把全局的均值和方差记录下来,这个全局的均值和方差如果碰到一个新的预测样本,如果碰到一个特别长的,因为在训练的时候没有见过这种长度的,那么在之前计算的均值和方差可能就不那么好用了。
- 相反,对于layernorm相对来说没有太多的问题,因为他是按照每个样本来进行均值和方差的计算,同时也不需要存下一个全局的均值和方差(不管样本的长短,均值和方差的计算都是以样本为单位的),这样的话相对来讲更稳定一些
解码器
解码器跟编码器很像,跟编码器一样是由(n=6)个同样的层构成的,每个层里面跟编码器一样有两个子层
解码器和编码器的不同之处在于解码器里面用了第三个子层,他同样是多头的注意力机制,跟编码器一样同样用了残差连接,用了layernorm
解码器中做的是自回归,也就是说当前的输出的输入集是上面一些时刻的输出,意味着在做预测的时候不能看到之后的那些时刻的输出
- 在注意力机制中,每一次能够看到完整的输入,这里要避免这个情况的发生,也就是说在解码器训练的时候,在预测第t个时刻的输出时候不应该看到t时刻以后的那些输入,他的做法是通过一个带掩码的注意力机制,如下图中的masked所示,这也是与解码器其他地方的不同之处,这个masked的作用是保证输入进来的时候,在t时间是不会看到t时间以后的那些输入,从而保证训练和预测的时候行为是一致的
子层
注意力层
注意力:注意力函数是一个将 query 和一些 key-value对 映射成输出的函数
- 里面所有的query、key-value、输出都是向量
- 输出是value的加权和,所以输出的维度和value的维度是一样的
- 对于每一个value的权重,他是value对应的key和query的相似度(compatibility function,不同的注意力机制有不同的算法,不同的相似度函数导致不一样的注意力的版本)计算得来的
transformer中使用到的注意力机制
scaled dot-product attention
- query和key长度是等长的,都等于dk(可以不等长的,不等长有别的计算方法)
- value的长度等于dv(输出的长度也应该是dv)
- 具体的计算方法:对每一个query和key最内积,然后将其作为相似度(如果两个向量做内积的话,如果这两个向量的 long 是一样的,那么内积的值(余弦值)越大,就表示这两个向量的相似度就越高,如果内积等于零(两个向量正交),就是说这两个向量没有相似度),算出来之后再除以根号dk(即向量的长度),然后再用softmax来得到权重。因为对于一个query,假设给n个key、value对的话,就会算出n个值,因为这个query会跟每个key做内积,算出来之后再放进softmax就会得到n个非负的和为1的权重(对于权重来说,非负、和为1是比较好的权重),然后将这些权重作用在value上面,就能得到输出了。
- 在实际中不能一个一个做运算,运算起来比较慢,文章提出query可以写成矩阵,可能不只是一个query,也可能有n个query,query的个数和key value的个数可能是不一样的,但是长度一定是一样的,这样才能做内积
- 给定query和key这两个矩阵,相乘就会得到一个n*m的矩阵,如下图所示,他的每一行(如图中蓝色的线所示),就是一个query对所有key的内积值,再除以根号dk后做softmax(对每一行做softmax,行与行之间是独立的),这样就能得到权重,然后再乘V(V是一个m行dv列的矩阵),得到一个n * dv的矩阵(这个矩阵的每一行就是所需要的输出)
对于key、value对和n个query,可以通过两次矩阵乘法来做整个计算,key和value在实际中对应的就是序列,这样就等价于是在并行地计算里面的每个元素(矩阵乘法便于并行)
文中所提出的注意力机制和其他注意力机制的区别
一般有两种比较常见的注意力机制
- 加型注意力机制:可以处理query和key不等长的情况
- 点积注意力机制:点积注意力机制和文中所提出的注意力机制是一样的(唯一的区别就是文中所提出来的注意力机制多除以了一个根号dk,这个根号dk就是命名中提到的scaled)
这两种注意力机制其实都差不多,文章选用的是点积注意力机制,因为实现起来比较简单,而且效果比较好,两次矩阵乘法就能算好
这里为什么要除以根号dk?
- 当dk不是很大的时候除不除都没关系,但是当dk比较大的时候,也就是两个向量长度比较长的时候,做点积的时候值可能比较大也可能比较小
- 当值比较大的时候,向量之间相对的差距就会变大,就导致值最大的那个值进行softmax操作后就会更接近1,剩下的值就会更靠近于0,值就会向两极分化,当出现这种情况后,在算梯度的时候,梯度会比较小(softmax最后的结果是所希望的预测值置信的地方尽量靠近1,不置信的地方尽量靠近0,这样就差不多收敛了,梯度就会变得比较小,就会跑不动)
- 在transformer里面一般用的dk比较大(512),所以除以根号dk是一个不错的选择
整个注意力机制的计算过程
- Q代表query矩阵
- K代表key矩阵
- mask主要是为了避免在第t时刻的时候看到以后时间的东西,具体来说,假设query和key是等长的,长度都为n,而且在时间上是能对应起来的,对第t时刻的qt在做计算的时候,应该只是看到k1一直到k(t-1),而不应该看到kt和它之后的东西,因为kt在当前时刻还没有。
- 但是在做注意力机制的时候,会发现其实qt在跟所有k里面的东西全部做运算,从k1一直算到kn,只要保证在计算权重的时候,不要用到后面的东西就可以了
- mask是说对于qt和kt之后的用于计算的那些值,把他们替换成非常大的负数,这些大的负数在进入softmax做指数的时候就会变成0,所以导致softmax之后出来对应的权重都会变成0,而kt之前所对应的值会有权重
这样在计算输出的时候就只用到了v对应的v1一直到v(t-1)的结果,而vt后面的东西并没有用到
所以mask的效果是在训练的时候,让第t个时间的query只看到对应的前面的那一些key、value对,使得在做预测的时候能够进行一一对应
multi-head
与其做一个单个的注意力函数,不如把整个query、key、value投影到一个低维,投影h次,然后做h次的注意力函数,再将每一个函数并在一起,再投影回来得到最终的输出,如上图右图所示
- 原始的value、key、query进入一个线性层(线性层将其投影到比较低的维度),然后再做一个scaled dot-product attention(如上图左图所示),做h次,得到h个输出,再把这些输出向量全部合并到一起,最后做一次线性的投影,然后回到multi-head attetion
为什么要做多头注意力机制?
- dot-product的注意力中没有什么可以学的参数,具体函数就是内积。有时候为了识别不一样的模式,希望有一些不一样的计算像素的办法
- 如果是用加型attention的话,里面其实是有一个权重可以学习到的,但是本文使用的是内积,它的做法是先投影到低维,这个投影的w是可以学的,也就是说,有h次机会希望可以学到不一样的投影方法,使得再投影进去的度量空间中能够匹配不同模式所需要的相似函数,然后最后把所得到的东西再做一次投影(这里有点像在卷积神经网络里面有多个输出通道的感觉)
- 具体的计算(公式如下图):在multi-head的情况下,还是以前的Q、K、V,但是输出已经是不同头的输出做contact运算再投影到一个WO里面,对每一个头,就是把Q、K、V通过不同的可以学习的WQ、WK、WV投影到dv上面,再做注意力函数,然后再出来就可以了
- 实际上h是等于8的,就是用8个头
- 注意力的时候,因为有残差连接的存在,使得输入和输出的维度是一样的,所以他投影的时候,投影的就是输出的维度除以h(因为输出维度是512,除以8之后,就是每一次把它投影到64维的维度,然后在这个维度上面计算注意力函数,最后再投影回来)
- 虽然公式中看起来有很多小矩阵的乘法,实际上在实现的时候也可以通过一次矩阵的乘法来实现(可以作为一个练习题来练习如何实现)
在transformer模型中是如何实现注意力的?
三种实现情况
第一个注意力的使用,编码器中的多头注意力机制的使用
- 编码器的输入(假设句子长度是n的话,他的输入其实是n个长为d的向量,每一个输入的词对应的是一个长为d的向量,一共有n个)
- 这个注意力层有三个输入,分别表示的是key、value、query。这里一根线复制成三根线表示同样一个东西,既作为key,也作为value和query,这个东西叫做自注意力机制,就是说key、value和query其实是一个东西,就是他自己本身
- 这里输入了n个query,每个query会得到一个输出,最终会得到n个输出,而且这个输出和value因为长度是一样的,所以输出的维度其实也是d,即输入和输出的大小其实是一样的,输出长也为n。
- 输出其实就是value的加权和,权重来自query和key
- 假设不考虑多头和有投影的情况,输出其实是输入的加权和,权重来自于自己本身跟各个向量之间的相似度。如果有多头的话,因为有投影,会学习出h个不一样的距离空间出来,使得得出来的东西会有点不一样
第二个注意力层的使用,解码器中MasKed-多头注意力机制:
- 解码器也是一样的,也是同一个东西复制了三次
- 解码器的输入也是一样的,只是长度可能变成了m,维度其实也是一样的,所以它跟编码器一样的也是自注意力,唯一不一样的是里面有一个mask(mask的作用:在解码器计算query对应的输出的时候,不应该看到第t时刻后面的东西,意味着后面的东西要设为0)
第三个注意力层的使用,解码器中的多头注意力机制
- 它不再是自注意力了,key和value来自编码器的输出,query来自解码器下一个attention的输入
- 编码器最后一层的输出就是n个长为d的向量
- 解码器的masked attention的输出是m个长为d的向量
- 编码器的输出作为key和value传入到这个注意力层中,解码器的下一层输出作为query传入到这个注意力层中,意味着对于解码器的每一个输出,作为query要计算出一个所要的输出,这个输出是来自于value的一个加权和(来自于编码器的输出的加权和,权重的粗细程度取决于query与编码器输出的相似度,如果相似度比较高的话,权重就会大一点,相反,如果相似度比较低的话,权重就会小一点)
- 这个attention中所要做的其实就是有效地把编码器里面的输出根据需要截取出来。
- attention如何在编码器和解码器之间传递信息的时候起作用:根据在解码器输入的不同,根据当前的向量在编码器的输出里面挑选感兴趣的东西,也就是去注意感兴趣的东西,那些不那么感兴趣的东西就可以忽略掉
feed forward MLP
其实就是一个fully connected feed-forward network,就是一个MLP,但是不同之处在于他是applied to each position seperately and identically(就是把同一个MLP对每个词作用一次,即position-wise,说白了就是MLP只是作用在最后一个维度)
- position:输入序列中有很多个词,每一个词就是一个点,这些点就是position
- 具体公式如下图所示,xW1+b1就是一个线性层,max就是relu激活层,最后再有一个线性层
- 在注意力层的输入(每一个query对应的输出)的长为512,x就是一个512的向量,W1会把512投影成2048(等价于将他的维度扩大了4倍),以为最有有残差连接,所以还需要投影回去,所以W2又把2048投影回512
- 这其实就是一个单隐藏层的MLP,中间的隐藏层将输入扩大4倍,最后输出的时候又回到输入的大小(如果用pytorch来实现的话其实就是把两个线性层放在一起,而不需要改任何参数,因为pytorch在输入是3d的时候,默认就是在最后一个维度做计算)
整个transformer是如何抽取序列信息,然后把这些序列信息加工成最后所想要的语义空间向量?
首先考虑一个最简单的情况(没有残差连接、attention也是单头、没有投影),如下图所示
输入就是长为n的向量,在进入attention之后,就会得到同样长度的输出,最简单的attention其实就是对输入进行加权求和,加权和之后进入MLP,每个MLP对每一个输入的点做运算会得到输出,最后就得到整个transformer块的输出(输入和输出的大小都是一样的)
- 在整个过程中attetion所起到的作用就是把整个序列里面的信息抓取出来做一次汇聚(aggregation),因为已经抓取序列中感兴趣的信息,所以在做投影、MLP、映射成为更想要的语义空间的时候,因为加权之后的结果已经包含了序列种的信息,所以每个MLP只要再每个点独立进行运算就行了,因为序列信息已经汇聚完成,所以在做MLP的时候是可以分开做的
作为对比,RNN(没有隐藏层的MLP,纯线性)的实现过程
RNN的输入也是向量,对于第一个点也是做一个线性层
对于下一个点,如何利用序列信息,还是用之前的MLP(权重跟之前是一样的),但是时序信息(上图中绿色曲线表示之前的信息,蓝色曲线表示当前的信息)方面,是将上一个时刻的输出放回来作为输入的一部分与第二个点一起作为输入,这样就完成了信息的传递
RNN和transformer都是用一个线性层或者MLP来进行语义空间的转换,但是不同之处在于传递序列信息的方式:RNN把上一时刻信息的输出传入下一时候做输入,但是在transformer中是通过attention层全局地拉取整个序列里面的信息,然后再用MLP做语义转换
他们的关注点都是如何有效地使用序列信息
embeding
因为输入是一个一个的词(或者叫词源,token),需要将其映射成向量。embeding的作用就是给任何一个词,学习一个长为d的向量来表示它(本文中d等于512)
编码器的输入需要embeding
解码器的输入也需要embeding
在softmax前面的线性也需要embeding
本文中这3个embeding是一样的权重,这样子训练起来会简单一点
另外还将权重乘了根号d:维度一大的话,权重值就会变小,之后要加上positional encoding,他不会随着长度变长把他的long固定住,因此乘上了根号d之后,使得他们在scale差不多
positional encoding
attention不会有时序信息,输出是value的加权和,权重是query和key之间的距离,和序列信息无关(也就是说给定一句话,把顺序任意打乱之后,attetion出来的结果都是一样的,顺序会变,但是值不会变,这样是存在问题的,所以需要把时序信息加入进来)
RNN是如何添加时序信息的?RNN将上一个时刻的输出作为下一个时刻的输入来传递历史信息
attention是在输入里面加入时序信息,将输入词所在的位置信息加入到输入里面(positional encoding),具体公式如下图所示
- 在计算机中,数字是用一定长度的向量来表示的
- 词在嵌入层会表示成一个长为d的向量,同样用一个长为d的向量来表示数字,也就是词的位置,这些数字的不同计算方法是用周期不一样的sin和cos函数的值来算出来的,所以说任何一个值可以用长为d的向量来表示,然后这个长为d的记录了时序信息的向量和嵌入层相加,就完成了将时序信息加进数据中的操作,如模块中的图中的编码器与解码器所示,因为是cos和sin的函数所以是在+1和-1之间抖动的,所以乘了一个根号d,使得每个数字也是差不多在正负1的数值区间里面
7、文章的第四部分 为什么要用自注意力
相对于使用循环层或者卷积层,使用自注意力有多好
下表比较了四种不一样的层
- 第一个是自注意力层
- 第二个是循环层
- 第三个是卷积层
- 第四个是构造出来的受限的自注意力
- 第一列是计算复杂度,越低越好
- 第二列是顺序的计算,越少越好。指的是在算layer的时候,下一步计算必须要等前面多少步计算完成。相当于是说非并行度
- 第三列说的是信息从一个数据点走到另一个数据点要走多远,越短越好
- n是序列的长度
- d是向量的长度
- 整个自注意力就是几个矩阵做运算,其中一个矩阵运算时query矩阵(n行,n个query,列数是d,也就是维度是d)乘以key矩阵(也是n*d),两个矩阵相乘,算法复杂度就是n平方乘以d,别的矩阵运算复杂度也是一样的。因为只是牵涉到矩阵的运算,矩阵中可以认为并行度是比较高的,所以是o(1),最大长度是说从一个点的信息想跳到另一个点要走多少步,在attention里面,一个query可以跟所有的key做运算,而且输出是所有value的加权和,所以query跟任何一个很远的key、value对只要一次就能过来,所以长度是比较短的
- 对循环层来说,如果序列是乘了n的话就一个一个做运算,每个里面主要的计算就是n * n的矩阵的dense layer然后再乘以长为d的输入,所以是n平方,然后要做n次,所以是n * d平方
- 对比自注意力和RNN的计算复杂度,其实是有一定的区别的,取决于n大还是d大。在本文中d是512,n也差不多是几百,现在比较大的模型d可以做到2048甚至更大,n相对来说也会做的比较多,所以现在看起来其实这两种的计算复杂度是差不多的(n和d在差不多的数据上面),但是在循环的时候因为要一步一步做运算,当前时刻的值需要等待前面完成,所以导致了是一个长为n的序列化的操作,在并行上比较吃亏。在最初点的那个历史信息到最后一个点的时候需要走过n步才能过去,所以循环的最长距离是o(n),即RNN对特别长的序列的时候做的不够好,因为信息从一开始走,走着走着就走丢了,而不是像attention一样直接一步就能到
- 卷积在序列上的具体做法是用一个1d的卷积,所以它的kernel就是k,n是长度,d就是输入的通道数和输出的通道数,所以这里是k乘n乘d的平方。k一般不大(一般是3或者5,所以一般可以认为是常数),所以导致卷积的复杂度和RNN的复杂度差不多,但是卷积的好处在于只用卷积就完成了,并行度很高,所以卷积做起来通常比RNN要快一点,另外卷积每一次一个点是由一个长为k的窗口来看,所以信息在k距离内是能够一次完成传递的,如果超过k的话,传递信息就需要通过多层一层一层上去,但是由于是log的操作,所以也不会有太大的麻烦
- 最后一个层是在做注意力的时候,query只跟最近的r个邻居做运算,这样就不用算n平方了,但是问题在于这样的话,两个比较长的远一点的点需要走几步才能过来
- 一般来说使用attetion主要是关心对于特别长的序列是否能将整个信息揉的比较好一点所以在实际过程中,带限制的自注意力使用的不是特别多,一般都是用最原始的版本,而不做受限,所以基本就是考虑前三种层
- 实际中,当序列的长度和整个模型的宽度差不多而且深度都一样的话的时候,基本上前三个模型的算法复杂度都是差不多的,attention和卷积相对来说计算会好一点,attention在信息的糅合性上会好一点,所以用了self-attention看上去对长数据处理更好一些,但是实际上attention对整个模型做了更少的假设,导致需要更多的数据和更大的模型才能训练出来跟RNN和CNN同样的效果,所以导致现在基于transformer的模型都特别大
7、文章的第五部分 实验
训练的一些设置
训练数据集和batching
使用了两个任务:
- 英语翻德语标准的WMT 2014的数据,里面有4.5w个句子,使用了byte-pair encoding(bpe,不管是英语还是德语,一个词里面有很多种变化,如果直接把每个词做成一个token的话,会导致字典里面的东西会比较多,而且一个动词可能有好几种不同的变化形式,在做成不一样的词的时候,它们之间的区别模型是不知道的,bpe相对来说就是把词根给提出来,这样的好处是整个字典比较小),这里使用的是37000个token的字典,他是在英语和德语之间共享的,也就是说不再为英语构造一个字典也不再为德语构造一个字典,这样的好处是整个编码器和解码器的embeding就可以使用同一个东西,而且整个模型变得更加简单,也就是之前说的编码器和解码器的embeding是权重共享的
- 英语翻法语:使用了一个更大的数据集
硬件和schedule
- 训练使用了8个P100的GPU,后来使用tpu(tpu更适合做大的矩阵乘法)
- base模型使用小一点的参数,每一个batch训练的时间是0.4秒,一共训练了10w步,一共在8台gpu上训练了12个小时(基本上一个模型训练12个小时也是一个不错的性能)
- 这个大的模型,一个batch训练需要一秒钟,一共训练了30W步,最后一台机器训练了3.5天,其实也是一个可以承受的范围
在训练器上使用的是Adam
学习率是根据以下公式计算出来的:学习律是根据模型宽度的-0.5次方(就是说当模型越宽的时候,学习的向量越长的时候,学习率会低一点)
- 存在一个warmup,就是从一个小的值慢慢地爬到一个高的值,爬到之后,再根据步数按照0.5次方衰减,最后warmup是4000
- 学习率几乎是不用调的:第一,adam对学习率不敏感;学习率已经把模型考虑进来了,schedule也已经算是不错的schedule了,所以学习率是不需要调的
正则化
总共使用了三个正则化
residual dropout:对每一个子层(包括多头的注意力层和之后的MLP),在每个层的输出上,在进入残差连接之前和进入到layernorm之前,使用dropout(dropout率为0.1,也就是把输出的10%的那些元素值乘0.1,剩下的值乘1.1)。另外在输入加上词嵌入再加上position encoding的时候,也用了一个dropout。也就是说,基本上对于每一个带权重的层,在输出上都使用了dropout,虽然dropout率不是特别高,但是使用了大量的dropout层来对模型做正则化
label smoothing(inception v3 中发表的)
使用softmax去学东西的时候,正确的标号是1,错误的标号是0,对于正确的label的softmax值,让他去逼近于1,但是softmax的值是很难去逼近于1的,因为他里面是指数,比较soft(需要输出接近无限大的时候,才能逼近于1),这样使得训练比较困难。
一般的做法是不要搞成特别难的0和1,可以把1的值往下降一点,比如降成0.9,本文中是直接降成了0.1,就是说对于正确的那个词,只需要softmax的输出(置信度)到0.1就可以了,而不需要特别高,剩下的值就可以是0.9除以字典大小,这里会损失perplexity(log lost做指数),基本上可以认为是模型的不确信度(正确答案只要10%是对的就行了,不确信度会增加,所以这个值会变高),但是在模型不那么确信的情况下会提升精度和BLUE的分数(这两个才是真正所要关心的点)
下表表示的是不同的超参数之间的对比
- n表示要堆多少层
- d表示模型的宽度,即token进来要表示成一个多长的向量
- dff表示MLP中间隐藏层的输出的大小
- h表示注意力层的头的个数
- dk、dv分别是一个头里面key和value的维度
- Pdrop表示dropout的丢弃率
- els表示最后label smoothing的时候要学的label的真实值是多少
- train steps表示要训练多少个batch
整个模型参数相对来说还是比较简单的,基本上能调的就是上面的这些超参数剩下的东西基本上都是可以按比例算过来的,这也是transformer的一个好处,虽然看上出模型比较复杂,但是实际上没有太多可以调的东西,这个设计对后面的人来说相对更加方便一点
8、文章评价
写作
- 非常简洁,每一句上基本上在讲一个事情
- 没有使用太多的写作技巧
- 这种写法并不是很推荐,因为对一篇文章来说,需要讲一个故事来让读者有代入感,能够说服读者
- 一般说可以选择把东西减少一点,甚至把一些不那么重要的东西放在附录里面,但是在正文的时候,最好还是讲个故事:为什么做这些事情以及设计的理念、对整个文章的一些思考,这让会使得文章更加有深度
transformer模型
- 现在不仅仅是用在机器翻译上,也能够用在几乎所有的NLP任务上面,在bert,gpt,后续能够训练很大的预训练模型,能够极大提升所有NLP任务的性能,类似于CNN对整个计算机视觉的改变:能够训练一个大的CNN模型,使得别的任务也能够从中受益,CNN使得整个计算机视觉的研究者提供了一个同样的框架,使得只要学会CNN就行了,而不需要去管以前跟任务相关的海量专业知识(比如特征提取、任务建模等)
- 对于transformer来说,之前需要做各种各样的数据文本的预处理,然后根据NLP的任务设计不也一样的架构,现在不需要了,使用了整个transforner架构就能够在各个任务上取得非常好的成绩,而且它的预训练模型也让大家的训练变得更加简单
- 现在transformer不仅仅是用在自然语言上面,也在图片、语音、video上面取得了很大的进展
- 之前计算机视觉的研究者使用CNN,而在语言处理使用RNN,在别的方面用别的模型,现在发现同样一个模型能够在所有领域都能用,让大家的语言的一样了,任何一个领域的研究做的一些突破能够很快地在别的领域被使用,能够极大地减少一个新的技术在机器学习里面各个领域被应用的时间
- 人对世界的感知是多模态的:图片、文字、语音,现在transformer能够把这些所有不同的数据给融合起来,因为大家都用一个同样的架构抽取特征的话,就可以抽取到一个同样的语义空间,使得我们可以用文本、图片、语音、视频等训练更好更大的模型
虽然transformer这些模型取得了非常好的实验性的结果,但是对它的理解还是处于比较初级的阶段。 - 虽然标题说只需要attention就够了,但是最新的研究表明,attention只是在transformer里面起到把整个序列的信息聚合起来的作用,但是后面的MLP以及残差连接是缺一不可的,如果把这些东西去掉的话,attention基本上什么东西都训练不出来所以模型也不只是说只需要attention就够了
- attention根本就不会对数据的顺序做建模,为什么能够打赢RNN呢?RNN能够显示建模的序列信息理论上应该比MLP效果更好现在大家觉得它使用了一个更广泛的归纳偏置,使得他能够处理一些更一般化的信息,这也是为什么说attetion并没有做任何空间上的一些假设,它也能够跟CNN甚至是比CNN取得更好的结果,但是他的代价是因为他的假设更加一般,所以他对数据里面抓取信息的能力变差了,以至于说需要使用更多的数据、更大的模型,才能训练出想要的效果,这也是为什么现在transformer模型越来越大
- attention也给了研究者一些鼓励,原来CNN和RNN之外也会有新的模型能打败他们。现在也有一些工作说,就用MLP或者就用一些更简单的架构也能够在图片或者文本上面取得很好的结果
但是后面的MLP以及残差连接是缺一不可的,如果把这些东西去掉的话,attention基本上什么东西都训练不出来所以模型也不只是说只需要attention就够了 - attention根本就不会对数据的顺序做建模,为什么能够打赢RNN呢?RNN能够显示建模的序列信息理论上应该比MLP效果更好现在大家觉得它使用了一个更广泛的归纳偏置,使得他能够处理一些更一般化的信息,这也是为什么说attetion并没有做任何空间上的一些假设,它也能够跟CNN甚至是比CNN取得更好的结果,但是他的代价是因为他的假设更加一般,所以他对数据里面抓取信息的能力变差了,以至于说需要使用更多的数据、更大的模型,才能训练出想要的效果,这也是为什么现在transformer模型越来越大
- attention也给了研究者一些鼓励,原来CNN和RNN之外也会有新的模型能打败他们。现在也有一些工作说,就用MLP或者就用一些更简单的架构也能够在图片或者文本上面取得很好的结果
- 未来肯定会有很多新的架构出现,让整个领域更加有意思一些