下面是对《Attention Is All You Need》这篇论文的浅读。
参考文献:
李沐论文带读
HarvardNLP
《哈工大基于预训练模型的方法》
下面是对这篇论文的初步概览:
对Seq2Seq模型、Transformer的概括:
下面是蒟蒻在阅读完这篇论文后做的一些笔记:
为什么会出现“注意力机制”?
基于循环神经网络的序列到序列模型有一个基本假设,就是原始序列的最后一个隐含状态(一个向量)包含了该序列的全部信息。然而,该假设显然不合理,尤其是当序列比较长时,要做到这一点就更困难。为了解决该问题,注意力模型应运而生。
为了解决序列到序列模型记忆长序列能力不足的问题,一个非常直观的想法是:
当要生成一个目标语言单词时,不光考虑前一个时刻的状态和已经生成的单词,还考虑当前要生成的单词和源语言句子中的哪些单词更相关,即更关注源语言的哪些词,这种做法就叫作注意力机制(Attention mechanism)。图4-14给出了一个示例,假设模型已经生成单词“我”后,要生成下一个单词,显然和源语言句子中的“love”关系最大,因此将源语言句子中“love”对应的状态乘以一个较大的权重,如0.6,而其余词的权重则较小,最
终将源语言句子中每个单词对应的状态加权求和,并用作新状态更新的一个额外输入。
注意力权重的计算公式为:
式中, h s h_s hs表示源序列中s时刻的状态; h t − 1 h_{t-1} ht−1表示目标序列中前一个时刻的状态;attn是注意力计算公式,即通过两个输入状态的向量,计算一个源序列s时刻的注意力分数 α ^ s \hat \alpha_s α^s; α ^ = [ α ^ 1 , α ^ 2 , ⋯ , α ^ L ] \hat \alpha = [\hat \alpha_1,\hat \alpha_2,\cdots,\hat \alpha_L] α^=[α^1,α^2,⋯,α^L]。其中L为源序列的长度;最后对整个源序列每个时刻的注意力分数使用Softmax函数进行归一化,获得最终的注意力权重 α s \alpha_s αs。
注意力公式attn的计算方式有多种,如:
通过引入注意力机制,使得基于循环神经网络的序列到序列模型的准确率有了大幅度的提高。
为什么会出现“自注意力机制”?
受注意力机制的启发,当要表示序列中某一时刻的状态时,可以通过该状态与其他时刻状态之间的相关性(注意力)计算,即所谓的“观其伴、知其义”,这又被称作自注意力机制(Self-attention)。
具体地,假设输入为 n n n个向量组成的序列 x 1 , x 2 , ⋯ , x n x_1,x_2,\cdots, x_n x1,x2,⋯,xn,输出为每个向量对应的新的向量表示 y 1 , y 2 , ⋯ , y n y_1,y_2,\cdots, y_n y1,y2,⋯,yn,其中所有向量的大小均为 d d d。那么 y i y_i yi的计算公式为:
式中, j j j是整个序列的索引值; α i j \alpha_{ij} αij是 x i x_i xi与 x j x_j xj之间的注意力(权重),其通过式 (4-26)中的attn函数计算,然后再经过Softmax函数进行归一化后获得。直观上的含义是如果 x i x_i xi与 x j x_j xj越相关,则它们计算的注意力值就越大,那么 x j x_j xj对 x i x_i xi对应的新的表示 y i y_i yi的贡献就越大。
**通过自注意力机制,可以直接计算两个距离较远的时刻之间的关系。**而在循环神经网络中,由于信息是沿着时刻逐层传递的,因此当两个相关性较大的时刻距离较远时,会产生较大的信息损失。虽然引入了门控机制模型,如LSTM等,可以部分解决这种长距离依赖问题,但是治标不治本。因此,基于自注意力机制的自注意力模型已经逐步取代循环神经网络,成为自然语言处理的标准模型。
为什么会出现“Transformer”?
然而,要想真正取代循环神经网络,自注意力模型还需要解决如下问题:
- 在计算自注意力时,没有考虑输入的位置信息,因此无法对序列进行建模;
- 输入向量 x i x_i xi同时承担了三种角色,即计算注意力权重时的两个向量以及被加权的向量,导致其不容易学习;
- 只考虑了两个输入序列单元之间的关系,无法建模多个输入序列单元之间更复杂的关系;
- 自注意力计算结果互斥,无法同时关注多个输入。
下面分别就这些问题给出相应的解决方案,融合了以下方案的自注意力模型拥有一个非常炫酷的名字——Transformer。
(1)融入位置信息
位置信息对于序列的表示至关重要,原始的自注意力模型没有考虑输入向量的位置信息,导致其与词袋模型类似,两个句子只要包含的词相同,即使顺序不同,它们的表示也完全相同。为了解决这一问题,需要为序列中每个输入的向量引入不同的位置信息以示区分。有两种引入位置信息的方式:
- 位置嵌入(Position Embeddings),位置嵌入与词嵌入类似,即为序列中每个绝对位置赋予一个连续、低维、稠密的向量表示;
- 位置编码(Position Encodings),使用函数 f : N → R d f:\mathcal{N}\to \mathcal{R^d} f:N→Rd,直接将一个整数(位置索引值)映射到一个 d d d维向量上。
位置编码的映射公式如下:
式中, p p p为序列中的位置索引值; 0 ≤ i < d 0\leq i< d 0≤i<d是位置编码向量中的索引值。
无论是使用位置嵌入还是位置编码,在获得一个位置对应的向量后,再与该位置对应的词向量进行相加,即可表示该位置的输入向量。这样即使词向量相同,但是如果它们所处的位置不同,其最终的向量表示也不相同,从而解决了原始自注意力模型无法对序列进行建模的问题。
(2)输入向量角色信息
原始的自注意力模型在计算注意力时直接使用两个输入向量,然后使用得到的注意力对同一个输入向量加权,这样导致**一个输入向量同时承担了三种角色:查询(Query)、 键(Key)和值(Value)。**更好的做法是,对不同的角色使用不同的向量。为了做到这一点,可以使用不同的参数矩阵对原始的输入向量做线性变换,从而让不同的变换结果承担不同的角色。
具体地,分别使用三个不同的参数矩阵 W q , W k , W v W^q,W^k,W^v Wq,Wk,Wv将输入向量 x i x_i xi映射为三个新的向量 q i = W q x i 、 k i = W k x i 、 v i = W v x i q_i=W^qx_i、 k_i=W^kx_i 、v_i=W^vx_i qi=Wqxi、ki=Wkxi、vi=Wvxi,分别表示查询、键和值对应的向量。新的输出向量计算公式为:
式中, α ^ i = [ α ^ i 1 , α ^ i 2 , ⋯ , α ^ i L ] \hat \alpha_i=[\hat \alpha_{i1},\hat \alpha_{i2},\cdots,\hat \alpha_{iL}] α^i=[α^i1,α^i2,⋯,α^iL],其中 L L L为序列的长度。
(3)多层自注意力
原始的自注意力模型仅考虑了序列中任意两个输入序列单元之间的关系,而在实际应用中,往往需要同时考虑更多输入序列单元之间的关系,即更高阶的关系。如果直接建模高阶关系,会导致模型的复杂度过高。
- 一方面,类似于图模型中的消息传播机制 (Message Propogation),这种高阶关系可以通过堆叠多层自注意力模型实现;
- 另一方面,类似于多层感知器,如果直接堆叠多层注意力模型,由于每层的变换都是线性的(注意力计算一般使用线性函数),最终模型依然是线性的。
因此,为了增强模型的表示能力,往往在每层自注意力计算之后,增加一个非线性的多层感知器(MLP)模型。另外, 如果将自注意力模型看作特征抽取器,那么多层感知器就是最终的分类器。同时,为了使模型更容易学习,还可以使用层归一化(Layer Normalization)、残差连接(Residual Connections)等深度学习的训练技巧。自注意力层、非线性层以及以上的这些训练技巧,构成了一个更大的Transformer层,也叫作Transformer块(Block),如图4-15所示。
(4)自注意力结果互斥
由于自注意力结果需要经过Softmax归一化,导致即使一个输入和多个其他的输入相关,也无法同时为这些输入赋予较大的注意力值,即自注意力结果之间是互斥的,无法同时关注多个输入。因此,如果能使用多组自注意力模型产生多组不同的注意力结果,则不同组注意力模型可能关注到不同的输入上,从而增强模型的表达能力。
那么如何产生多组自注意力模型呢?方法非常简单,只需要设置多组映射矩阵即可,然后将产生的多个输出向量拼接。为了将输出结果作为下一组的输入,还需要将拼接后的输出向量再经过一个线性映射,映射回d维向量。该模型又叫作多头自注意力(Multi-head Self-attention)模型。 从另一方面理解,多头自注意力机制相当于多个不同的自注意力模型的集成 (Ensemble),也会增强模型的效果。类似卷积神经网络中的多个卷积核,也可以将不同的注意力头理解为抽取不同类型的特征。
以上介绍的Transformer模型可以很好地对一个序列编码。此外,与循环神经网络类似,Transformer也可以很容易地实现解码功能,将两者结合起来,就实现了一个序列到序列的模型,于是可以完成机器翻译等多种自然语言处理任务。解码模块的实现与编码模块基本相同,不过要接收编码模块的最后一层输出作为输入,这也叫作记忆(Memory),另外还要将已经部分解码的输出结果作为输入,如图4-16所示。
与循环神经网络相比,Transformer模型的优点:
- Transformer能够直接建模输入序列单元之间更长距离的依赖关系,从而使得Transformer对于长序列建模的能力更强;
- 另外,在Transformer的编码阶段,由于可以利用GPU等多核计算设备并行地计算Transformer块内部的自注意力模型,而循环神经网络需要逐个计算,因此Transformer具有更高的训练速度。
与循环神经网络相比,Transformer模型的缺点:
**参数量过于庞大。**每一层的Transformer块大部分参数集中在图4-15中的绿色方框中,即自注意力模型中输入向量的三个角色映射矩阵、多头机制导致相应参数的倍增和引入非线性的多层感知器等。 更主要的是,还需要堆叠多层Transformer块,从而参数量又扩大多倍。最终导致一个实用的Transformer模型含有巨大的参数量。以本书后续章节将要介绍的BERT模型为例, BERT-base含有12层Transformer块,参数量超过1.1亿个,而24层的BERT-large,参数量达到了3.4亿个之多。巨大的参数量导致Transformer模型非常不容易训练,尤其是当训练数据较小时。
因此,为了降低模型的训练难度,基于大规模数据的预训练模型应运而生,这也是本书将要介绍的重点内容。唯此,才能发挥Transformer模型强大的表示能力。
模型实现
新版本的PyTorch(1.2版及以上)实现了Transformer模型。其中,nn.TransformerEncoder实现了编码模块,它是由多层Transformer块构成的,每个块使用TransformerEncoderLayer实现。下面演示具体的示例。
import torch
from torch import nn# 创建一个Transformer块,每个输入向量、输出向量的维度为4、头数为2
encoder_layer = nn.TransformerEncoderLayer(d_model=4, nhead=2)
# 三个参数分别为 序列的长度、批次大小、每个输入向量的维度
src = torch.rand(2, 3, 4) # [seq_len, batch_size, input_size]
# out向量形状为[2,3,4]
out = encoder_layer(src)
print(out)
然后,可以将多个Transformer块堆叠起来,构成一个完整的nn.TransformerEncoder。
import torch
from torch import nn# 创建一个Transformer块,每个输入向量、输出向量的维度为4、头数为2
encoder_layer = nn.TransformerEncoderLayer(d_model=4, nhead=2)
# 三个参数分别为 序列的长度、批次大小、每个输入向量的维度
src = torch.rand(2, 3, 4) # [seq_len, batch_size, input_size]transformer_encoder = nn.TransformerEncoder(encoder_layer, num_layers=6)
# out向量形状为[2,3,4]
out = transformer_encoder(src)
print(out)
print(out.shape)
解码模块也类似,TransformerDecoderLayer定义了一个解码模块的Transformer块,通过多层块堆叠构成nn.TransformerDecoder,下面演示具体的调用方式。
memory = transformer_encoder(src)
decoder_layer = nn.TransformerDecoderLayer(d_model=4, nhead=2)
transformer_decoder = nn.TransformerDecoder(decoder_layer, num_layers=6)
out_part = torch.rand(2, 3, 4)
out = transformer_decoder(out_part, memory)
# out向量形状为[2,3,4]
print(out)
Abstract
主流的序列转换模型基于复杂的循环或卷积神经网络,其中包括一个编码器和一个解码器。性能最好的模型还通过注意力机制连接编码器和解码器。我们提出了一种新的简单网络架构——Transformer,它完全基于注意力机制,无需循环和卷积。在两项机器翻译任务上的实验表明,这些模型的质量更优,同时可并行化程度更高,所需的训练时间也大大减少。在 WMT 2014 英德翻译任务中,我们的模型达到了 28.4 BLEU,比包括集合在内的现有最佳结果提高了 2 BLEU 以上。在 WMT 2014 英法翻译任务中,我们的模型在 8 个 GPU 上进行了 3.5 天的训练后,取得了 41.8 分的单模型最新 BLEU 分数,这只是文献中最佳模型训练成本的一小部分。我们将 Transformer 成功地应用于英语成分解析,并同时使用大量和有限的训练数据,从而证明 Transformer 可以很好地泛化到其他任务中。
解读
这段论文概括了该论文的主要内容和贡献:
- 介绍了序列转换模型主要基于复杂的循环或卷积神经网络,通常包含编码器和解码器,最好的模型还使用了注意力机制连接编码器和解码器。
- 提出了Transformer,一个全新的简单的网络架构,完全基于注意力机制,不需要循环或卷积。
- 在两个机器翻译任务上的实验表明,该模型质量更优异,更易于并行化,训练时间也大大减少。
- 在WMT 2014英德翻译任务上,该模型BLEU得分达到28.4,超过了之前最佳结果(包括集成模型)2个BLEU。
- 在WMT 2014英法翻译任务上,该模型取得41.8的新state-of-the-art单模型BLEU得分,训练时间是之前文献中最佳模型的小部分。
- 通过英语成分解析任务表明,该模型可以很好地泛化到其他任务,即使是训练数据有限的情况。
总体上,该论文提出了Transformer作为一个全新的序列转换模型架构,完全基于注意力,结果表明它的质量更优异,训练更高效,且可以很好地泛化到其他NLP任务,是一个划时代的进步。
Introduction
循环神经网络,尤其是长短期记忆[13]和门控循环[7]神经网络,已被牢固确立为语言建模和机器翻译等序列建模和转译问题的最先进方法[35, 2, 5]。自此以后,许多人继续努力推动循环语言模型和编码器-解码器架构的发展[38, 24, 15]。
循环模型通常沿着输入和输出序列的符号位置进行因子计算。将位置与计算时间的步长对齐,它们会生成隐藏状态 h t h_t ht的序列,作为前一个隐藏状态 h t − 1 h_{t-1} ht−1和位置 t t t的输入的函数。这种固有的序列性质排除了训练实例内的并行化,这在较长的序列长度中变得至关重要,因为内存约束限制了跨实例的批处理。最近的研究通过因式分解技巧[21]和条件计算[32]显著提高了计算效率,同时也改善了后者的模型性能。然而,顺序计算的基本限制仍然存在。
在各种任务中,注意力机制已成为引人注目的序列建模和转换模型的一个组成部分,它可以对依赖关系进行建模,而无需考虑其在输入或输出序列中的距离[2, 19]。然而,除了少数情况[27],这种注意力机制都是与循环网络结合使用的。
在这项工作中,我们提出了 Transformer 模型架构,它摒弃了循环,而是完全依赖注意力机制来绘制输入和输出之间的全局依赖关系。Transformer 可以大大提高并行化程度,在 8 个 P100 GPU 上只需训练 12 个小时,翻译质量就能达到新的水平。
解读
这段论文介绍了使用循环神经网络(尤其是LSTM和GRU)在序列建模和序列转录任务(如语言模型和机器翻译)上的发展历程,以及最新使用注意力机制提升模型性能的研究进展。主要概括如下:
- 循环神经网络(尤其是LSTM和GRU)已经确立为序列建模和序列转录任务的最优方法。
- 许多后续工作持续推进循环语言模型和编码器-解码器架构的发展。
- 循环模型通常沿输入输出序列的符号位置进行计算,这决定了其固有的顺序计算特性。
- 顺序计算特性限制了训练样本内的并行化,这在长序列时成为瓶颈。
- 最新工作通过分解技巧和条件计算提高了计算效率和模型性能。
- 注意力机制已经成为各种序列建模和转录任务中强大模型的关键组成部分。
- 除了少数情况,注意力机制通常与循环网络结合使用。
- 本文提出了Transformer,一个完全依赖注意力机制的新模型架构,摒弃了循环结构。
- Transformer允许显著更高程度的并行化,在8个P100 GPU上训练12小时就能达到最优翻译质量。
总体上,这段概括总结了循环网络在序列建模中的发展,以及注意力机制的引入带来的突破,以及本文提出的全新的完全依赖注意力的Transformer架构。
Background
减少顺序计算的目标也是 Extended Neural GPU[16]、ByteNet[18]和 ConvS2S[9]的基础,它们都使用卷积神经网络作为基本构建模块,并行计算所有输入和输出位置的隐藏表示。在这些模型中,将两个任意输入或输出位置的信号联系起来所需的运算次数随位置间距离的增加而增加,ConvS2S 是线性增加,ByteNet 是对数增加。这使得学习远距离位置之间的依赖关系变得更加困难[12]。在 Transformer 中,这将被减少到一个恒定的操作数,但代价是由于平均注意力加权位置而降低了有效分辨率,我们在 3.2 节中介绍了多头注意力来抵消这一影响。
Self-attention(有时也称为intra-attention)是一种注意机制,它将单个序列的不同位置联系起来,以计算序列的表征。Self-attention已成功应用于多种任务中,包括阅读理解、抽象概括、文本蕴含和学习与任务无关的句子表征 [4,27,28,22]。
端到端记忆网络基于循环注意力机制,而非序列对齐循环,在简单语言问题解答和语言建模任务中表现出色[34]。
然而,据我们所知,Transformer 是第一个完全依靠自注意力来计算输入和输出表示而不使用序列对齐 RNNs 或卷积的转换模型。在下面的章节中,我们将描述 Transformer,激发自注意力,并讨论它与 [17, 18] 和 [9] 等模型相比的优势。
解读
这段论文主要概括了以下内容:
- 减少顺序计算也是Extended Neural GPU、ByteNet和ConvS2S等模型的基础,这些模型都使用卷积网络作为基本模块,并行地为所有输入输出位置计算隐层表示。
- 在这些模型中,连接任意两个位置的操作数量随着两位置间距离的增大而线性或对数增长,这使学习远距离依赖更困难。
- Transformer通过自注意力机制将其减少为恒定数量的操作,虽然由于平均注意加权位置导致有效分辨率下降,但通过多头注意力进行缓解。
- 自注意力(又称内部注意力)是一种注意力机制,用于关联单个序列中不同位置,以计算序列的表示。
- 自注意力已在多种任务中成功应用,如阅读理解、摘要抽象、蕴含关系和任务无关的句子表示等。
- 端到端记忆网络基于循环注意力而不是序列对齐的循环,在简单语言的问答和语言建模任务中表现良好。
- 尽我们所知,Transformer是第一个完全依赖自注意力来计算输入输出表示的序列转换模型,不使用序列对齐的RNN或卷积。
- 我们将描述Transformer模型,阐明自注意力的动机,并讨论它相对于其他模型的优势。
总之,这段概括阐明了自注意力在多个NLP任务中的应用,以及Transformer如何利用自注意力来克服基于循环或卷积的模型的局限性。
Model Architecture
大多数具有竞争力的神经序列转换模型都具有编码器-解码器结构 [5, 2, 35]。在这里,编码器将输入的符号表示序列 ( x 1 , ⋯ , x n ) (x_1,\cdots,x_n) (x1,⋯,xn)映射为连续表示序列 z = ( z 1 , ⋯ , z n ) z=(z_1,\cdots,z_n) z=(z1,⋯,zn)。给定 z z z后,解码器会逐个元素生成一个符号输出序列 ( y 1 , ⋯ , y m ) (y_1,\cdots,y_m) (y1,⋯,ym)。在每一步中,模型都是自回归的[10],在生成下一步时,将先前生成的符号作为额外输入。
Transformer model遵循这种整体架构,图 1 的左半部分和右半部分分别显示了编码器和解码器采用堆叠式自注意力和点式全连接层的整体架构。
3.1 Encoder and Decoder Stacks
Encoder
编码器由 N = 6 层相同的层堆叠组成。每一层都有两个子层。第一个是多头自注意机制,第二个是简单的位置全连接前馈网络。我们在两个子层的每个周围都采用了残差连接[11],然后进行层归一化[1]。也就是说,每个子层的输出都是 L a y e r N o r m ( x + S u b l a y e r ( x ) ) LayerNorm(x + Sublayer(x)) LayerNorm(x+Sublayer(x)),其中 S u b l a y e r ( x ) Sublayer(x) Sublayer(x)是子层本身实现的函数。为了方便这些残差连接,模型中的所有子层以及嵌入层都会产生维数为 d m o d e l = 512 d_{model}=512 dmodel=512的输出。
Decoder
解码器也由 N = 6 层相同的层堆叠组成。除了每个编码器层中的两个子层外,解码器还插入了第三个子层,对编码器堆叠的输出执行多头注意力。与编码器类似,我们在每个子层周围采用残差连接,然后进行层归一化。我们还修改了解码器堆叠中的自注意力子层,以防止位置关注到后续位置。这种掩蔽,结合输出词嵌入偏移一个位置的事实,确保了对位置 i i i的预测只能依赖小于 i i i位置的已知输出。
3.2 Attention
注意力函数可以描述为将一个查询和一组键值对映射到一个输出,其中查询、键、值和输出都是向量。输出是以值的加权和来计算的,其中分配给每个值的权重是通过查询与相应键的兼容函数来计算的。
3.2.1 Scaled Dot-Product Attention
我们将这种特殊的注意力称为 “Scaled Dot-Product Attention(缩放点积注意力)”(图 2)。输入包括维度为 d k d_k dk的查询queries 、键keys和维度为 d v d_v dv的值values。我们计算查询与所有键的点积,将每个点积除以 d k \sqrt {d_k} dk,然后应用softmax函数来获得值的权重。
实际上,我们同时对一组查询计算注意力函数,这些查询被打包成一个矩阵 Q Q Q。我们计算的输出矩阵为:
最常用的两种注意力函数是加法注意力[2]和点积(乘法)注意力。点积注意力与我们的算法相同,只是缩放因子为 1 d k \dfrac {1}{\sqrt {d_k}} dk1。加法注意力使用单隐层前馈网络计算相容函数。虽然两者的理论复杂度相似,但点积注意力在实际应用中速度更快,空间效率更高,因为它可以使用高度优化的矩阵乘法代码来实现。虽然在 d k d_k dk值较小的情况下,这两种机制的表现类似,但在 d k d_k dk值较大的情况下,加法注意力在不缩放的情况下优于点积注意力[3]。我们猜测,对于较大的 d k d_k dk值,点积的幅度会越来越大,从而将softmax函数推向梯度极小的区域。为了消除这种影响,我们将点乘缩放 1 d k \dfrac {1}{\sqrt {d_k}} dk1。(论文中的这段解释了为什么要乘上 1 d k \dfrac {1}{\sqrt {d_k}} dk1)
3.2.2 Multi-Head Attention
我们发现,与其使用 d m o d e l d_{model} dmodel维度的键、值和查询来执行单一的注意力函数,不如将查询、键和值分别线性投影到 d k , d k , d v d_k,d_k,d_v dk,dk,dv维度,并将不同的已经学习过的线性投影进行 h h h次投影。然后,我们对每个投影版本的查询、键和值并行执行注意力函数,得到 d v d_v dv维度的输出值。(论文中的这段解释了为什么要将 d m o d e l d_{model} dmodel维度的键、值和查询线性投影到 d k , d k , d v d_k,d_k,d_v dk,dk,dv维度)
如图 2 所示,这些值被串联拼接起来并再次投影,从而得到最终值。
多头注意力机制允许模型在不同位置共同关注来自不同表征子空间的信息。而在单头注意力机制的情况下,平均化会抑制这一点。(论文中的这段解释了为什么要使用Multi-head attention而不是single attention head)
在这项工作中,我们采用了 h = 8 h=8 h=8个并行注意力层,或称为 “头”。其中,我们使用 d k = d v = d m o d e l / h = 64 d_k=d_v=d_{model}/h=64 dk=dv=dmodel/h=64。由于减少了每个头的维度,总计算成本与全维度的单头注意力相似。
3.2.3 Applications of Attention in our Model
Transformer 模型以三种不同的方式使用多头注意力:
- 在 "编码器-解码器注意 "层中,查询来自前一个解码器层,而记忆键和记忆值则来自编码器的输出。这使得解码器中的每个位置都能关注输入序列中的所有位置这模仿了序列到序列模型(如 [38, 2, 9])中典型的编码器-解码器注意机制。(论文中的这段解释了为什么Transformer解码器中的每个位置都能关注输入序列中的所有位置)
- 编码器包含自注意层。在自注意层中,所有的键、值和查询都来自同一个地方,在这种情况下,就是编码器中上一层的输出。编码器中的每个位置都可以关注编码器上一层的所有位置。
- 同样,解码器中的自注意力层允许解码器中的每个位置关注解码器中包括该位置在内的所有位置。我们需要防止解码器中的信息向左流动,以保持自回归特性。我们通过屏蔽(设置为 − ∞ -\infty −∞) softmax 输入中所有与非法连接相对应的值,在缩放点积注意力内部实现这一点见图 2。(论文中的这段展示了如何防止解码器中的信息向左流动,以保持自回归特性的方法)
3.3 Position-wise Feed-Forward Networks
除了注意力子层外,我们的编码器和解码器中的每一层都包含一个全连接的前馈网络,该网络分别对每个位置进行相同的处理。这包括两个线性变换,中间有一个 ReLU 激活。
虽然不同位置的线性变换相同,但各层使用的参数不同。另一种描述方法是两个内核大小为1的卷积。输入和输出的维度为 d m o d e l = 512 d_{model}=512 dmodel=512,内层的维度为 d f f = 2048 d_{ff}=2048 dff=2048。
3.4 Embeddings and Softmax
与其他序列转换模型类似,我们使用学习到的嵌入将输入标记和输出标记转换为维数为 d m o d e l d_{model} dmodel。我们还使用通常的学习线性变换和 softmax 函数将解码器输出转换为预测的下一个标记概率。在我们的模型中,我们在两个嵌入层和pre-softmax最大线性变换之间共享相同的权重矩阵,这与 [30] 相似。在嵌入层中,我们将这些权重乘以 d m o d e l \sqrt {d_{model}} dmodel。
3.5 Positional Encoding
**由于我们的模型不包含循环和卷积,为了让模型能够利用序列的顺序,我们必须注入一些关于序列中标记的相对或绝对位置的信息。为此,我们在编码器和解码器堆叠底部的输入嵌入层中添加了 “位置编码”。位置编码的维度 d m o d e l d_{model} dmodel与嵌入层的维度相同,因此两者可以相加。**位置编码有多种选择,包括学习编码和固定编码 [9]。(论文中的这段解释了为什么我们需要在词嵌入层中添加位置编码,以及为什么位置编码可以直接同词嵌入相加)
在这项工作中,我们使用了不同频率的正弦和余弦函数:
其中, p o s pos pos是位置, i i i是维数。也就是说,位置编码的每个维度对应一个正弦波。波长形成一个从 2 π 2\pi 2π到 10000 ⋅ 2 π 10000\cdot 2\pi 10000⋅2π的几何级数。我们之所以选择这个函数,是因为我们假设它可以让模型轻松地学习到相对位置,因为对于任何固定的偏移 k k k, P E p o s + k PE_{pos+k} PEpos+k都可以表示为 P E p o s PE_{pos} PEpos的线性函数(论文中的这段解释了为什么选择这样的函数去计算位置编码)
我们还尝试用学习到的位置嵌入[9]来代替,结果发现两个版本产生的结果几乎相同(见表 3 (E)行)。我们之所以选择正弦波版本,是因为它可以让模型推断出比训练时遇到的序列长度更长的序列。(论文中的这段解释了为什么选择正弦波版本)
解读
这篇论文中Model Architecture部分主要概括了Transformer模型架构的详细设计,括以下几点:
- Transformer采用编码器-解码器架构,包含编码器和解码器两个部分。
- 编码器包含多个相同的层叠加而成,每个层有两个子层:多头自注意力机制和全连接前馈网络。
- 解码器也由多个相同的层叠加构成,除了编码器的两个子层外,还在每个层中插入第三个多头自注意力子层来利用编码器输出。
- 自注意力子层允许Transformer模型学习任意位置之间的依赖关系。
- 前馈网络对每个位置分别进行相同的全连接操作。
- 通过残差连接和层标准化来优化信息流。
- 位置编码被引入来 inject 序列顺序信息。
- 输入和输出嵌入层被共享使用,并乘以向量维度的平方根。
- softmax被用来生成预测的下一个词概率。
总体而言,这部分详细构建了Transformer的编码器-解码器架构,其独特之处在于完全依赖自注意力来学习全局依赖,并有效融入序列信息,从而克服RNN/CNN模型的局限,为更好地并行化和建模长程依赖提供可能。
Why Self-Attention
在本节中,我们将比较自注意层与循环层和卷积层的各个方面,后者通常用于将一个可变长度的符号表示序列 ( x 1 , ⋯ , x n ) (x_1,\cdots, x_n) (x1,⋯,xn)映射到另一个等长序列 ( z 1 , ⋯ , z n ) (z_1,\cdots, z_n) (z1,⋯,zn),其中 x i , z i ∈ R d x_i,z_i\in \R ^{d} xi,zi∈Rd,例如典型序列转换编码器或解码器中的隐藏层。我们使用自注意力的动机有三个。
一个是每层的总计算复杂度。另一个是可并行化的计算量,以所需的最小顺序运算次数来衡量。
第三是网络中长距离依赖关系之间的路径长度。学习长程依赖关系是许多序列转换任务的关键挑战。影响学习此类依赖关系能力的一个关键因素是前向和后向信号在网络中必须穿越的路径长度。输入和输出序列中任意位置组合之间的路径越短,学习远距离依赖关系就越容易[12]。因此,我们还比较了由不同层类型组成的网络中任意两个输入和输出位置之间的最大路径长度。
如表 1 所示,自注意层连接所有位置的连续操作数不变,而循环层则需要 O ( n ) O(n) O(n)次连续操作。就计算复杂度而言,当序列长度 n n n小于表示维度 d d d时,自注意层的计算速度要快于循环层,机器翻译中最先进的模型所使用的句子表示,如单词片[38]和字节对[31]表示,通常就是这种情况。为了提高涉及超长序列任务的计算性能,可以限制自注意力只考虑输入序列中以相应输出位置为中心的大小为 r r r的邻域。这将把最大路径长度增加到 O ( n / r ) O(n/r) O(n/r)。我们计划在今后的工作中进一步研究这种方法。
核宽 k < n k<n k<n的单个卷积层无法连接所有输入和输出位置对。在内核连续的情况下,这样做需要堆叠 O ( n / k ) O(n/k) O(n/k)个卷积层;在空洞卷积的情况下,则需要堆叠 O ( l o g k ( n ) ) O(log_k(n)) O(logk(n))个卷积层[18],从而增加了网络中任意两个位置之间最长路径的长度。卷积层通常比循环层贵 k k k倍,然而,可分离卷积[6]则大大降低了复杂度,达到 O ( k ⋅ n ⋅ d + n ⋅ d 2 ) O(k\cdot n \cdot d + n \cdot d^2) O(k⋅n⋅d+n⋅d2)。然而,即使在 k = n k=n k=n的情况下,可分离卷积的复杂度也相当于自注意层和点式前馈层的组合,而我们在模型中采用的正是这种方法。
作为附带的好处,自注意力可以产生更多可解释的模型。我们从模型中检查了注意力分布,并在附录中介绍和讨论了一些例子。各个注意力头不仅明显学会执行不同的任务,而且许多注意力头似乎还表现出与句子的句法和语义结构有关的行为。
解读
这篇论文中Why Self-Attention部分主要从以下几个方面阐释了自注意力层的优势:
- 从计算复杂度上比较,自注意力连接任意两个位置只需要恒定复杂度,而RNN需要线性复杂度,效率更高。
- 从并行度上,自注意力最小的顺序运算量是 O ( 1 ) O(1) O(1),RNN是 O ( n ) O(n) O(n),更易于并行。
- 从最大路径长度考虑,自注意力的最大路径只有 O ( 1 ) O(1) O(1),RNN是 O ( n ) O(n) O(n),有助学习长程依赖。
- 卷积层堆叠的最大路径长度也较长,难以学习长距离依赖。
- 自注意力可以显式地关注不同位置的表示,而RNN和CNN通过复杂的交互进行隐式建模。
- 通过可视化分析,自注意力可以学习语法和语义信息。
- 自注意力对句子的符号位置比较敏感,而RNN和CNN更偏向于局部和翻译等变换。
综上,自注意力机制在计算效率、并行化程度和建模远距离依赖方面都优于RNN和CNN,更适合作为序列转录任务的基本组成模块,这是Transformer选择自注意力的原因。
Training
本节将介绍我们模型的训练机制。
5.1 Training Data and Batching
我们在标准的 WMT 2014 英德数据集上进行了训练,该数据集包含约 450 万个句子对。句子使用字节对编码[3]进行编码,其中有大约 37000 个共享的源目标词汇。对于英语-法语,我们使用了规模更大的 WMT 2014 英语-法语数据集,该数据集包含 3600 万个句子,并将标记拆分为 32000 个词块词汇[38]。句子对按近似序列长度分组。每个训练批包含一组句子对,其中包含约 25000 个源词块和 25000 个目标词块。
5.2 Hardware and Schedule
我们在一台配备 8 个英伟达 P100 GPU 的机器上训练模型。对于我们的基础模型,使用本文所述的超参数,每个训练步骤耗时约 0.4 秒。我们总共训练了 100,000 步或 12 个小时的基础模型。对于我们的大型模型(如表 3 底行所述),每步训练时间为 1.0 秒。大型模型的训练时间为 300,000 步(3.5 天)。
5.3 Optimizer
我们使用了Adam优化器[20], β 1 = 0.9 , β 2 = 0.98 \beta_1=0.9, \beta_2=0.98 β1=0.9,β2=0.98和 ϵ = 1 0 − 9 \epsilon = 10^{-9} ϵ=10−9。在训练过程中,我们根据公式改变学习率:
这相当于在第一个warmup_steps训练步数中线性增加学习率,此后学习率按步数的平方反比例递减。我们使用的是 warmup_steps = 4000。
5.4 Regularization
我们在训练中使用三种类型的正则化:
Residual Dropout
我们将dropout [33]应用于每个子层的输出,然后再将其添加到子层输入中并进行归一化处理。此外,我们将dropout应用于编码器和解码器堆叠中的词嵌入层和位置编码层之和。在基础模型中,我们使用 P d r o p = 0.1 P_{drop}=0.1 Pdrop=0.1的比率。
Label Smoothing
在训练过程中,我们采用了值 ϵ l s = 0.1 \epsilon_{ls} = 0.1 ϵls=0.1的标签平滑[36]。这降低了困惑度,因为这样做会增加模型的不确定性,但却提高了准确度和 BLEU 得分。
Result
6.1 Machine Translation
在 WMT 2014 英译德翻译任务中,大Transformer模型(表 2 中的Transformer(big))的 BLEU 值比之前报道的最佳模型(包括集合)高出 2.0 以上,达到了 28.4 的新的最先进 BLEU 值。该模型的配置见表 3 底行。在 8 个 P100 GPU 上的训练耗时 3.5 天。即使是我们的基本模型,也超越了之前发布的所有模型和集合,而训练成本只是任何竞争模型的一小部分。
在 WMT 2014 英语到法语的翻译任务中,我们的大模型获得了 41.0 的 BLEU 分数,超过了之前发布的所有单一模型,而训练成本还不到之前最先进模型的 1/4。为英译法而训练的 Transformer(大)模型使用了 P d r o p = 0.1 P_{drop}=0.1 Pdrop=0.1,而不是 0.3。
对于基本模型,我们使用的是通过平均最近 5 个检查点得到的单一模型,这些检查点以 10 分钟的间隔写入。对于大型模型,我们取最后 20 个检查点的平均值。我们使用波束搜索,波束大小为 4,长度惩罚 α = 0.6 \alpha=0.6 α=0.6[38]。这些超参数是在开发集上实验后选择的。我们将推理过程中的最大输出长度设定为输入长度 + 50,但尽可能提前终止推理 [38]。
表 2 总结了我们的结果,并将我们的翻译质量和训练成本与文献中的其他模型架构进行了比较。我们通过将训练时间、使用的 GPU 数量和每个 GPU 的持续单精度浮点运算能力的估计值相乘,估算出训练一个模型所使用的浮点运算次数。
6.2 Model Variations
为了评估 Transformer 不同组件的重要性,我们以不同方式改变了基础模型,测量了开发集 newstest2013 上英译德性能的变化。我们使用了上一节所述的波束搜索,但没有使用检查点平均法。表 3 列出了这些结果。
如 3.2.2 节所述,在表 3 行(A)中,我们改变了注意力头的数量以及注意力键和值的维度,但计算量保持不变。虽然单头注意力比最佳设置差 0.9 BLEU,但注意力头数过多也会导致质量下降。
在表 3 行(B)中,我们发现减小注意力的大小 d k d_k dk会降低模型质量。这表明,确定兼容性并不容易,比点积更复杂的兼容性函数可能更有益处。在第(C)行和第(D)行中,我们进一步观察到,正如我们所预期的那样,模型越大越好,而 dropout 对避免过度拟合很有帮助。在第(E)行中,我们用学习到的位置嵌入[9]替换了正弦位置编码,观察到的结果与基础模型几乎完全相同。
6.3 English Constituency Parsing
为了评估Transformer是否能推广到其他任务,我们进行了英语成分解析实验。这项任务具有特殊的挑战性:输出结果受到强大的结构约束,而且比输入结果要长得多。此外,RNN 序列到序列模型在小数据环境下也无法达到最先进的结果 [37]。
我们在宾夕法尼亚大学树库(Penn Treebank)[25] 中的《华尔街日报》(WSJ)部分(约 4 万个训练句子)上训练了 d m o d e l = 1024 d_{model}=1024 dmodel=1024的 4 层Transformer。我们还在半监督环境下使用更大的高置信度语料库和 BerkleyParser 语料库(约有 177 个句子)[37]对其进行了训练。在仅使用 WSJ 的情况下,我们使用了 16K 个词组的词汇量,而在半监督情况下,我们使用了 32K 个词组的词汇量。
我们仅在第 22 节开发集上进行了少量实验,以选择dropout,注意力和残差(第 5.4 节)、学习率和波束大小,所有其他参数均与英译德基础翻译模型保持一致。在推理过程中,我们将最大输出长度增加到输入长度 + 300。在仅 WSJ 和半监督设置中,我们使用的波束大小均为 21, α = 0.3 \alpha=0.3 α=0.3。
表 4 中的结果表明,尽管缺乏针对特定任务的调整,但我们的模型表现出人意料的好,其结果优于除循环神经网络语法 [8] 以外的所有以前报道过的模型。
与 RNN 序列到序列模型[37]相比,Transformer 的表现优于 BerkeleyParser [29],即使仅在由 40K 个句子组成的 WSJ 训练集上进行训练也是如此。
解读
这篇论文中的Result部分报告了在机器翻译任务上的实验结果,主要概括如下:
- 在WMT’14英德翻译任务上,Transformer大模型获得28.4的BLEU得分,超过之前所有单模型和集成模型2个BLEU,刷新了SOTA。
- 在WMT’14英法翻译任务上,Transformer大模型获得41.0的BLEU得分,也超过了以前所有单模型,训练成本仅为之前SOTA的1/4。
- 结果表明Transformer的质量更好,训练速度更快,参数利用率更高。
- 通过修改模型组件分析了各模块的作用。增加解码器注意力头数对性能提升明显。
- 位置编码的正弦实现和学习实现效果相近。
- 模型参数量的增加一般能带来效果提升。残差dropout对避免过拟合很有帮助。
- Beam Search带来明显的生成质量提升。
总之,Result部分全面报告了Transformer在机器翻译任务取得的显著进步,超过多种先前模型,并分析了各组件的作用,证明了该架构的有效性。
Conclusion
在这项工作中,我们提出了 “Transformer”,这是第一个完全基于注意力的序列转换模型,用多头自我注意力取代了编码器-解码器架构中最常用的循环层。
在翻译任务中,Transformer 的训练速度明显快于基于循环层或卷积层的架构。在 WMT 2014 英译德和 WMT 2014 英译法翻译任务中,我们都达到了新的技术水平。在前一项任务中,我们的最佳模型甚至优于之前报告的所有集合。
我们对基于注意力的模型的未来充满期待,并计划将其应用于其他任务。我们计划将 Transformer 扩展到涉及文本以外的输入和输出模式的问题上,并研究局部的、受限的注意力机制,以有效处理大型输入和输出,如图像、音频和视频。我们的另一个研究目标是减少生成的顺序。
我们用于训练和评估模型的代码可在github上获取。
Transformer源码解读
Transformer
先解读一下**class Transformer(Module)**
这个类。
在PyTorch实现的Transformer源码中:
class Transformer(Module):
下面是对__init()__
方法的解读:
这段代码是构造Transformer模型的初始化方法。它包含以下几个部分:
- 调用父类nn.Module的初始化方法,设置可选的device和dtype。
- 构造编码器(Encoder)。如果自定义了编码器,直接使用自定义的编码器,否则构造默认的编码器:
- 构造编码器层TransformerEncoderLayer,包含多头注意力和前馈全连接层。
- 构造编码器规范化层LayerNorm。
- 使用编码器层和规范化层构造编编码器TransformerEncoder。
- 构造解码器(Decoder)。与编码器类似,如果自定义了解码器直接使用,否则构造默认的解码器:
- 构造解码器层TransformerDecoderLayer,包含masked多头注意力、多头注意力和前馈全连接层。
- 构造解码器规范化层LayerNorm。
- 使用解码器层和规范化层构造解码器TransformerDecoder。
- 重置模型参数。
- 保存模型的一些关键参数,如d_model, nhead等。
- 设置batch_first表示是否将batch维度放在第一位。
所以这段代码主要工作是构造transformer模型的编码器和解码器,同时提供了使用自定义编码器和解码器的接口。它初始化了transformer模型最核心的组成部分。
下面是对forward()
方法的解读:
这段代码实现了Transformer的前向传播逻辑。详细解读如下:
- 检查输入的src和tgt的批量大小是否一致。
- 检查src和tgt的特征维度是否等于模型的d_model。
- 调用编码器(Encoder),输入src和src_mask,输出记忆(memory)。
- 调用解码器(Decoder),输入tgt、记忆memory、tgt_mask等,输出最终结果output。
- 返回output。
其中:
- src:输入序列,如源语言句子。
- tgt:目标序列,如目标语言句子。
- src_mask:对src的注意力遮挡,可选择的。
- tgt_mask:对tgt自注意力的遮挡,可选择的。
- memory_mask:对编码器输出的注意力遮挡,可选择的。
- src_key_padding_mask:对src的key遮挡,可选的。
- tgt_key_padding_mask:对tgt的key遮挡,可选的。
- memory_key_padding_mask:对编码器输出的key遮挡,可选的。
mask相关参数用于指定在注意力计算时哪些位置不参与Softmax运算。key_padding_mask用于指定哪些key在注意力计算时被遮蔽。
总之,这段代码实现了Transformer的基本流程,并进行了必要的输入合法性检查。
TransformerEncoderLayer
下面是对**class TransformerEncoderLayer(Module)**
这个类的解读:
先来对__init__()
方法进行解读:
再来对forward()
方法进行解读:
TransformerEncoder
先对__init__()
方法进行解读:
再对forward()
方法进行解读:
TransformerDecoderLayer
先来对__init()__
方法进行解读:
再来对foward()
方法进行解读:
下面对self-attention block
进行解读:
下面对Encoder-Decoder attention进行解读:
TransformerDecoder
先来对__init__()
方法进行解读:
再来对forward()
方法进行解读:
以上是本篇博客的主要内容,欢迎点赞+关注~