Transformer论文精读(李沐)

发布时间 2023-03-24 14:33:12作者: 诸葛村夫CC

摘要

序列转录模型:给你一个序列,生成一个序列

simple network architecture:“简单的模型”不再也不应该是一个贬义词,简单高效应当是值得提倡的

BLEU:机器翻译中的衡量标准

 

结论

1.transformer是第一个仅使用注意力机制的序列转录模型

2.训练的快

3.transformer可以用在文本以外(图片、视频)的数据上,且不那么有时序关系的数据上。

 

1 导言

1. LSTM\GRU是当前时序主流模型,结构上有语言模型和编码-解码器

2. RNN模型处理序列时只能一个一个按顺序进行,这就决定了在时间顺序上难以并行。此外,如果需要记住长时间的依赖关系就需要设置一个比较大的ht(隐藏层输出),这就比较占内存。这方面大家已经做了很多的努力,提出了很多方法提高RNN的并行度,但没有解决本质问题

3. Transformer能极大提高并行度

 

2 相关工作

1. 如何使用CNN替换RNN来减少时序计算,但难以建模长序列。因为CNN一次只能看很小的感受野,如果需要将两个距离较远像素点的信息融合起来,需要经过很多层卷积,但transformer在一层上就可以看到整个研究区域。CNN好在可以做多通道,认为每个通道识别一个不同的模式,transformer也想要多通道的这种效果,所以提出multi-head attention

2. 自注意力机制是已有的,不是transformer首次提出

3. memory networks曾红极一时

4. transformer是第一个只依赖于自注意力机制的来做编码-解码器架构的模型

 

3 模型架构

编码-解码器:编码器将输入长度为n的序列编码为模型能够理解的长度为n的序列,解码器输出长度为m的序列。注意,解码器的输出长度与编码器的输入输出长度可以不相同。但编码器在编码时可以同时看到整个序列,解码器在解码时必须是一个一个输出的,第t个输出可以看到之前t-1个输出(并同时作为t时刻的输入),这称为自回归模型。

3.1 编解码器

编码器叠了6个完全一样的层,每个层中 有两个子层,一个是多头注意力一个是全连接层(MLP),每个子层都用了残差连接,并且加了Layer Norm。既然要用残差,就要求输入输出维度相同,所以它每个层的输出都设置为512。

Layer Norm:BN是将每一个特征进行归一化,LN是将每一个样本进行归一化,避免样本不一样长的时候BN不好操作的问题。注意这个特征是讲特征表示的意思,也就是上面说的那个512,同时也是CNN中通道的意思。序列是另外一个维度,不要和特征搞混了(有时经过处理的输入数据也成为特征,这是更广义的理解),如果加进序列维度的话,序列中的每一个元素都有512个特征进行表示。下图中蓝色表示BN,黄色表示LN,可以发现如果每个 样本的序列长度不同,那么在BN的时候,由于0填充的问题,每次算出来的均值和方差可能抖动是比较大的,LN由于跟着样本的大小在变,所以可以避免这个问题。此处李沐提到一个问题:“上面的解释是LN这篇文章中作者的解释,但一个好用的东西之后大家对他的理解和原文写的可能是不一样的,后来又有一篇文章介绍LN好用是在于对梯度啊等其他方面的提升。”我个人认为原作者在提出一个方法的时候,尤其是深度学习领域的东西,他对自己的东西的理解可能都是不深刻的,无法完全前瞻到实用阶段该方法能体现出的全部价值。

 

解码器:与编码器不一样的第三个子层是掩码多头注意力,因为解码器做的是一个自回归,即当前的输入是之前时刻的输出,不能看到之后时刻的东西,但由于注意力机制的特性,一次能够看到完整的序列信息,所以为了保证训练和预测时的一致性,把t时刻以后的东西遮住。

 3.2 注意力

具体来说,每一层的输出是value的加权和(所以输出的维度和value的维度是一样的),每一个value的权重是每个value对应的key和query相似度(或者叫compatibility function,不同的注意力机制有不同的算法)算出来的。

3.2.1 Scaled Dot-Product Attention

首先Q与K等长(不等长有不等长的算法)的为dk,V的维度是dv,所以输出长度就是dv

Q与K做内积(向量投影,点积,点乘,dot-product),作为相似度,再除以根号dk进行缩放,再用一个softmax得到权重(各项权重加起来为1是比较合理的)

 

 这个过程在具体应用中使用矩阵运算如上图所示。一共两次矩阵乘法,第一次QK得到权重(n*m),再用权重矩阵乘到V上,得到输出。

要注意每个矩阵的行数和列数的变化。

Q\K的列数虽然是一样的(也就是说每一个Q和K向量的维度一样,这样才能内积),但QK矩阵行数不一样,也就是说QK的个数是不一样的。

K和V的行数(个数)是一样的。

最终输出的个数与Q的个数是一样的,也就是有多少个query,最终就能得到多少个特征表示,每个表示长度为dv。

当dk变得比较大时,即向量长度较长时,得到的值就会比较大(小),那么值与值之间的差距就被拉大,得到的softmax结果也更加向两端靠拢。但softmax函数的最终目标就是得到向两端靠拢的最终结果,这就造成这个模型在一开始训练的时候梯度就很小,很难收敛。

mask是指将K中不应该用到的值换成一个非常大的负数,它们在经过softmax以后变成0。

3.2.2 Multi-Head Attention

将原始Q/K/V输入进一个线性层(线性层就是把原始向量投影到一个比较低的维度),然后做h次attention,再线性投影回去。

为什么多头:简单的点乘注意力机制中没有可以学习的参数,具体函数就是做内积。但就像CNN一样,我们希望学出一些不一样的模式出来,所以就先投影到低维,投影操作中有一个w参数是可以学的,也就是说投影的方式是可以学的。希望能够在h个学出来的不同的投影方法,使得在投影进去的那个度量空间中匹配不同的模式需要的相似函数(compatibility function)。

由于要保证输入输出的维度都是512(要残差),那么内部进行多头的时候要考虑维度问题。原文选择h=8,即8个头,所以每个头里就是64维。也就是说将原始dk=dv=dmodel长度投影到64维上。

3.2.3 Application of Attention in our Model

首先,假设你输入的句子长度(机器翻译)为n,它的输入就是n个长度为d的向量(假设pn大小为1,这里李沐老师没有说pn是什么),也就是说每一个词都表示为一个固定长度的向量。按照文中图2,注意力层的三个输入就是Q\K\V,但其实是一根线分为三个箭头的,也就是说是同一个东西复制了三份,这叫做自注意力机制(Q\K\V是同一个东西,就是自己本身)。

那么n个输入也就是n个query,也就能得到n个输出(query个数=输出个数),且输出的长度是value的长度dv,那么最终输入和输出的大小就一模一样了。注意,之前讲输出是value的加权和,所以要把所有value加在一起,所以输出和value的个数(行数)没有关系,输出用的是Q的个数和V的长度。

在此,如果不考虑多头,由于QKV都是自己本身,所以输出就是输入的加权和,权重又来自于自己和自己的相似度,所以实际上算的权重是自己本身的某一个向量和其他什么位置上的向量的相似度。如果考虑多头,因为有投影的存在,是会投出一些不一样的东西。

在解码器部分,仍然要输入一个长为m(与输出长度相同)东西复制为三份(也就是自注意力),先进入一个掩码多头注意力层,掩码之前解释过了。注意再往后一层就不再是自注意力了,中间这一层用编码器的输出作为K和V,用解码器第一层的输出作为Q,相当于这一层输出的东西是编码器输出的V的加权和,权重来自于解码器自己给出的一个Q和编码器给出的K算的相似度,解码器是从编码器输出的东西里面挑自己感兴趣的内容。

3.3 Position-wise Feed-Forward Networks

这里就是一个最简单的全连接MLP,Position-wise(按位置排列)的意思是同一个MLP在每个位置上逐个进行处理,而不是说把n个输入一股脑输入到一个巨大的MLP中去映射。文中的公式,x就是512维的输入,W1将其变换为2048维,W2再投影回512维。max(0,f(x))就是ReLU激活。

李沐:“说白了就是MLP只作用在最后一个维度”,他的意思是说将输入视为一个完整的三维矩阵的时候,MLP只作用在最后一个维度上。如果用pytorch实现,都不用改代码,因为pytorch默认你给MLP输入3D矩阵的时候,只作用在最后一维。

所以在第一个注意力子层中做的,实际是将整个序列中的信息抓取出来,做一个汇聚(aggregation),所以注意力子层的输出中已经有了整个序列上的信息,所以在使用MLP投影(映射到模型输出更想要的那个语义空间)的时候逐个独立做就可以了。其实RNN中每一次循环的那个隐层也是一个MLP,而且和transformer一样,对于每一个时间点的输入的MLP的参数是一样的,不同的只是RNN使用了上一时间步的输出并到这一步的输入中去来传递序列信息的,transformer每一个输入中都有全局序列信息。

3.4 Embeddings and Softmax

模型最初的输入是一个个词(词源/token),需要将其先映射到一个向量

embedding:对于任何一个词,学习一个长为d(512)的向量来表示它。

注意,transformer的两个embedding(编解码器的输入处各一个),以及解码器softmax层之前的一个Linear层用的是同一个训练好的东西,权重是一样的。

此外,还要将权重乘以一个根号dmodel(512),李沐:“因为你在学embedding的时候,多多少少会把每个向量的L2 norm(欧氏距离)学成相对来说比较小的一个值,比如说1,不管维度多大最后这个值都会等于1,那么维度一大权重就会比较小。但要在后面使用Positional encoding,它不会随着长度变长把norm固定住,所以要将上面的东西放大一点,使得他们在scale上大家差不多”

这个没听懂

3.5 Positional Encoding

为什么存在:因为attention实际上是没有序列信息的,attention输出的是V的加权和,权重是Q和K的距离,它与序列信息无关,也就是说你这个权重得到的时候,并不体现任何位置信息。这意味着一句话输入模型后,顺序任意打乱得到的attention结果是一样的。

位置编码所做的就是将位置(0,1,2,...)使用长为512的向量的表示出来,然后把位置编码结果加到embedding结果里面去,使得每个词都带有位置信息。

由于位置编码是用sincos函数编的,其值在-1,1之间抖动,所以之前embedding的结果需要放大到-1,1之间