You are what you eat.
And I'm cooking what I eat! :)
目录
Attention Is All You Need (Transformer) - 论文精读学习笔记背景知识框架:编码器 + 解码器编码器的结构解码器的结构注意力(Attention)Scaled Dot-Product Attetion(缩放点积注意力)和其他注意力的区别多头(mutli-head)掩码多头注意力(Masked Multi-Head Attetion)解码器的多头注意力机制(Multi-Head Attention)三个多头注意力
的使用情况Position-wise MLPembeddings and softmaxPosition encoding为什么使用注意力机制自注意力网络循环神经网络卷积神经网络被限制的自注意力网络训练&结果训练数据和批次Transformer英语翻译德语Transformer英语翻译法语硬件和步长正则化结果:机器翻译任务正文摘要About注意力
Scaled Dot-Product Attention(放缩点乘注意力)自注意力
多头注意力
机制※
Transformer模型架构残差连接前馈网络整体架构与掩码多头注意力嵌入层位置编码自注意力层的优点(why 自注意力层)实验结果总结特别强调:参考博文
提前说明:本系列博文主要是对参考博文的解读与重述(对重点信息进行标记、或者整段摘录加深自己的记忆和理解、融合多个博文的精髓、统合不同的代表性的案例),仅做学习记录笔记使用。与君共享,希望一同进步。
简介:《Attention Is All You Need》是一篇由Google DeepMind团队在2017年发表的论文,该论文提出了一种新的神经网络模型,称为Transformer模型,用于自然语言处理任务。
Transformer和Attention(注意力机制)的关系就是这样的!
Transformer是一个模型;
该模型用了(只用了)Attention机制。
优势:对序列进行更好地建模。
补充 它是主要用来解决RNN系列在在处理长序列数据时存在一些问题,比如难以并行计算和难以捕捉长距离依赖关系,而Transformer模型则通过引入自注意力机制(Self-Attention)来解决这些问题。
补充 该论文还提出了一种新的训练方法,称为“无序列信息的训练(Training without sequence information)”,其基本思想是将输入序列中的每个位置看作独立的词向量,而不考虑它们在序列中的位置信息。通过这种方式,可以避免序列中的位置信息对模型训练的影响,提高模型的泛化性能。
中译英:机器翻译
输入:简将访问非洲
输出(参考基准):
Jane is going to visit Africa.
Jane will visit Africa.
输出(算法模型):
Jane visits the Africa.
BLEU Score:利用参考输出和算法输出计算得到。
早期的处理:
在深度学习时代早期,人们使用RNN(循环神经网络)来处理机器翻译任务。一段输入先是会被预处理成一个token序列。
RNN会对每个token逐个做计算,并维护一个表示整段文字整体信息的状态。根据当前时刻的状态,RNN可以输出当前时刻的一个token。
所谓token,既可以是一个单词、一个汉字,也可能是一个表示空白字符、未知字符、句首字符的特殊字符。
输入:
上一轮的状态(如果有的话);
这一轮的输入token;
这种简单的RNN架构仅适用于输入和输出等长的任务。然而,大多数情况下,机器翻译的输出和输入都不是等长的。
“编码器-解码器”架构:两个部分通过一个状态
来传递信息。把该状态看成输入信息的一种编码的话,前半部分可以叫做“编码器”,后半部分可以叫做“解码器”。
这种架构因而被称为“编码器-解码器”架构。
基于注意力的架构:
每一个输出对每一个输入的权重叫做注意力,注意力的大小取决于输出和输入的相关关系。这种架构优化了编码器和解码器之间的信息交流方式,在处理长文章时更加有效。
注意力:权重(输出 ← 输入)
补充 从上面的图可以看到Transformer的组成为:Encoder + Decoder(参考博文2的行文思路)
对于Encoder而言:
Encoder的作用是将输入序列编码成一个高维向量表示,该向量表示将被输入到Decoder中用于生成输出序列。Encoder包括多个Encoder层,每个Encoder层由两个子层组成:多头自注意力机制和前馈网络。
补充 编码器会将
对于Decoder而言:
Decoder的作用是生成输出序列,它包括多个Decoder层,每个Decoder层由三个子层组成:多头自注意力机制、多头注意力机制和前馈网络。
encoder和decoder的区别:
encoder可以一次看到所有的词,但是decoder只能一个词一个词生成,auto-regressive自回归的模型。
具体来说就是,编码器拿到
↑ 传统的encoder-decoder做法。
↓ Transformer中的encoder-decoder做法。
Transformer是将self-attention、point-wise、fully-connected layers进行堆叠行程encoder和decoder架构。
左边就是编码器,右边就是解码器。
如果是一个中文翻译英文的任务:
Inputs就是中文句子。
shifted right表示解码器在之前时候的一些输出作为此时的输入,一个一个右移。
编码器和解码器都是由若干个Transformer Block组成。
Input Embedding:
表示将输入转换成向量,得到的向量值和Positional Encoding相加。
Nx:
表示有N层,即N个Transformer的block叠在一起,比如ResNet中N个残差块的叠加。
编码器
编码器中,一个Transformer Block由两个子块组成:
第一个子块由Multi-head Attention、Add & Norm组成;
第二个子块由Feed Forward、Add & Norm组成,子块之间使用残差连接,最后使用layer normalization。
两个子块组成了编码器的Transformer Block,N个编码器Transformer Block组成最后的编码器。
解码器
解码器中,一个Transformer Block由三个子块组成:
第一个子块由Masked(掩码)Multi-head Attention、Add & Norm组成;
第二个子块和第三个子块设计上与编码器一致。
但是这里的解码器第二个子块的输入是编码器的输出和解码器第一个子块的输出。(解码器第一个子块的输入一个一个传递的)。
这里的每个子块依旧使用的是残差连接,最后使用layer normalization,三个子块构成了解码器的Transformer Block,N个编码器Transformer Block组成最后的解码器。
编码器就是重复6个前面提到的编码器Transformer Block,所以前面提到的N,它等于6(N=6)。
6个layer进行堆叠,每一个layer都由2个sub-layer组成。
第一个sub-layer就是Multi-head Attention;
第二个sub-layer其实就是一个MLP(simple, position-wise fully connected feed-forward network,名字很长)
每个sub-layer都做残差连接和LayerNorm。公式如下
指self-attetion或者MLP。
关于残差连接
残差连接的前提:输入输出的维度保持完全一样;
所谓残差连接,就是下面的公式:
※
※
这样的设计方式就是残差连接。因此这里的
总结 实际上就是一个数据
在Transformer中,简单起见,固定每一层的输出维度
简单设计:只需调2个参数(
Remark:和CNN、MLP不一样。MLP通常空间维度往下减;CNN空间维度往下减,channel维度往上拉。
这句话没看懂。
关于
含义解读:
Norm的意思就是Normalization,即,归一化。
Layer的意思表示归一化的方式。
两种Norm的区别和联系:
两者相同之处就是:
※ 都需要对当前处理的向量进行相同的归一化操作:所有值减去均值,再除以方差。
两者不同之处就是:
※ LayerNorm是对batch做Norm;
※ BatchNorm是对feature做Norm。
案例 - 待具体化
假设一个Seq(序列)有n个词,一个词有d个feature(这里的d=512)。
※ 所以:
● d是不变的;
● 而n是经常会变的,因为一个句子的长度(即单词、汉字的个数会经常发生变化)。
BatchNorm所做的事情是每一次把每一列(即,每一个特征),在一个小mini-batch里面,将它的均值变成0、方差变为1(即减去向量均值再除以方差),是在这条向量里面算出来它的均值与方差。
※ 在训练的时候,可以做小批量;
※ 在预测的时候,会把一个全局的一个均值给算出来,可认为是整个数据扫描一遍之后,在所有数据上那些平均的均值、方差存起来,预测的时候再使用。
假设一个mini-batch有m个样本,一个样本有n个左右的元素,也就是说每个样本的长度不同。
※ BatchNorm会把每一个列看成一个向量,将这个向量重构成一个均值为0、方差为1的新向量。
● 这个重构的过程,就是:1 先将这个向量求出它的均值与标准差,2 然后将向量所有的元素都减去均值再除以方差。
※ 而LayerNorm就是把一行看成一个向量,然后还是把每一个向量重构成一个均值为0、方差为1的新向量。
● 所以,在实际任务中,LayerNorm可以看做是先将数据进行转置,再放入BatchNorm进行处理,将BatchNorm的处理结果再进行转置就是相当于进行了一次LayerNorm。
demo 左边为BatchNorm的做法;右边为LayerNorm的做法。
疑惑:
什么是batch?什么是mini-batch?
怎样算是对batch做Norm?怎样又算是对feature做Norm?
当然,BatchNorm还会学习一个
但在实际情况中,不管是Transformer还是RNN,输入都是一个三维数据:
一个batch的句子数量;
一个句子词的数量;
一个词的特征数量
这个时候的列变成了一个矩阵,也就是一个sequence,每个sequence都有d个feature,在本文中d=512。
demo
BatchNorm(蓝色):
每次取一个特征,切一块(蓝色线),拉成一个向量,均值为0、方差为1的标准化。
LayerNorm(橙色):
横着切。
LayerNorm为什么用的多?
时序数据中样本长度可能不一样。LayerNorm更稳定,不管样本长还是短,均值和方差是在每个样本内计算。
解码器和编码器主体上比较相似,也是由6个Transformer Block堆叠而成。
但是解码器的Transformer Block是由3个sub-layer组成,然后每一个sub-layer之间全部用残差连接和LayerNorm全都不变。
解码器是auto-regressive(自回归):当前时刻的输入集是之前一些时刻的输出。
做预测时,解码器不能看到之后时刻的输出。但是注意力机制每一次能看完完整的输入,要避免这个情况发生。
我的理解是,解码器是自回归的,所以,它不应该看到当前时刻之后的输入;But,注意力机制比较牛,它能看到每一次的完整输入,这就造成了矛盾:解码器不能看到后面的输入 vs 注意力机制本身具有看到全局输入的功能,咋办呢?要解决这个矛盾呢!
方法 在解码器训练的时候,在预测第
一个注意力函数就是将一个query和一些key-value对映射成一个输出的函数。
输入由大名鼎鼎的Q、K、V组成。
Q:query
K:key,
维 V:value,
维 K和V,一般是键值对的形式存在。
步骤描述:
1 首先,计算query和所有key的点积;
2 点积除以缩放因子(
3 这个结果再经过softmax获得一组权重,这个权重对value进行重构就是注意力机制的结果。
其中,经过softmax计算得到的权重,代表的意义是query和key的相似度。
※ 虽然key-value都没有发生变化,但是随着query的改变,因为权重的分配不一样,导致输出会有不一样,这就是所谓的注意力机制。这个过程的两种表示如下:
图片表示 | 公式表示 |
---|---|
![]() |
Softmax补充说明:
给定一个
传入softmax得到n个非负、求和为1的权重值(归一化权重)。
把softmax得到的权重值与value矩阵
相似度补充说明:
具体计算是说,对每一个query和key做内积,然后把它作为相似度,可以认为两个向量做内积的事情。
如果这两个向量的Norm是一样大的话,内积值越大,表示两个向量的相似度越高;
如果两个向量的内积等于0了,就说明两个向量是相交的,没有相似度。算出来之后再除以
疑惑:这句话没有理解。相交的是垂直的意思吧?那我要这个长度有什么意义呢?
实际计算
计算思路是上面说的思路,但是实际计算的时候不会一个query、一个query的计算,因为运算比较慢。
普遍的计算方法:把多个query写成一个矩阵,并行化运算。
: (矩阵的维度)
: (矩阵的维度)
: (点积运算之后,结果矩阵的维度)
一个query对所有key的内积值,然后再除以
softmax是对每一行的值做softmax,然后对每一行之间是独立的,会得到权重。
Attention的计算就是两次矩阵乘法、并行计算。
Scale Dot-Product Attetion补充说明:
最简单的注意力机制。
query和key的长度是等长的,都等于
query和key也可以不等长,不等长用别的办法计算。
2种常见的注意力机制:
加性的注意力机制
功能 它可以处理你的query和key不等长的情况。
点乘注意力机制(Dot-Product Attention)
本文采用的是(缩放点积注意力机制)
Scaled体现在,除以
选用dot-product的原因:
两种注意力机制其实都差不多。但是点乘实现简单、高效,两次矩阵乘法计算。
Scaled(除以
重要 当值比较大的时候,相对的差距会变大,导致最大值softmax会更加靠近于1,剩下那些值就会更加靠近于0。值就会更加向两端靠拢,算梯度的时候,梯度比较小。softmax会让大的数据更大,小的更小。
因为softmax最后的结果是希望softmax的预测值,置信的地方尽量靠近,不置信的地方尽量靠近0,以保证收敛差不多了。这时候梯度就会变得比较小,那就会跑不动。
在Transformer里面一般用的
单头注意力就是self-Attention,即原始输入
经过self-Attention后生成 向量。 多头注意力,Multi-head,这里的head代表头数,所以这个head等于多少是自己定义的。
重述 这篇论文的注意力机制的运作方式:
1 在原始数据的基础上生成一组Q, K, V向量;
2 再由这组Q, K, V向量生成最后的注意力结果。
而Multi-head的做法,就是生成多组Q, K, V,使用Attention得到多个结果,再将多个结果进行加权平均得到最后的结果。
多头注意力机制的设计灵感来源于CNN的多通道特征提取。
这个过程的两种表示如下:
图片表示 | 公式表示 |
---|---|
![]() |
原始输入
在过程1中,是一组
生成的
Mask就是掩码的意思。
由于Attention机制的缘故,每次都必须看到句子的全部。但是在生成的时候是一个一个生成的:也就是说在
因此,作者设置了一个掩码机制:就是在解码器的内部,输入数据之前,先经过一层Masked Multi-Head Attention。这层网络会将
作用 让
在计算权重的时候,
把
简单理解
可以从一个简单的角度理解,mask就是一个由0和1组成的矩阵,和Attention(scale
在解码器中,不再是使用的自注意力。解码器的
编码器最后一层的输出是n个长度为d的向量;
解码器的(Masked Multi-Head Attention + Add & Norm) 的输出是m个长为d的向量。
多头注意力
的使用情况在Transformer模型中,(在不考虑Transformer Block堆叠的情况下)一共有三种使用多头注意力的情况,分别是①编码器中的注意力层、②解码器的掩码注意力层、③解码器的第二个注意力层。
第①处使用:
编码器包含Self-Attention层。
在self-attention层中,所有的key、value和query来自同一个地方,在这里是编码器中前一层的输出。
编码器中的每个位置都可以关注编码器上一层的所有位置。
第②处使用:
在“编码器-解码器Attention层”。
query来自上面的解码器层,key和value来自编码器的输出。
这允许解码器中的每个位置能关注到输入序列中的所有位置。
这模仿序列到序列模型中,典型的编码器—解码器的Attention机制。
第③处使用:
类似地,解码器中的Self-Attention层允许解码器中的每个位置都关注解码器中直到并包括该位置的所有位置。
我们需要防止解码器中的向左信息流来保持自回归属性。
通过屏蔽softmax的输入中所有不合法连接的值(设置为
前面我们提到一个Transformer Block主要由三部分组成,分别是:
注意力;
前馈神经网络;
这里的前馈神经网络指的就是模型图中的Feed Forward,之前我们提到过实际上它是一个MLP。
残差连接+LayerNorm。
文中的MLP全称是Position-wise Feed-Forward Networks,这里的MLP由两层Linear组成,之间有一层ReLU:
MLP:原文解释 —— applied to each position separately and identically
Point-wise:把一个MLP对每一个词(Position)作用一次,对每个词作用的是同样的MLP
FFN:Linear + ReLU + Linear
单隐藏层的MLP,中间
PyTorch实现:2个线性层。
PyTorch在输入是3D的时候,默认在最后一个维度做计算。
也就是说原始输入有512维,
最简单情况:没有残差连接、没有LayerNorm、Attention单头、没有投影。
Attention实际上是对输入进行了一个加权和,进入point-wise MLP,point-wise MLP对每个输入的点做计算得到输出。
这里的Attention作用实际上是把整个序列的信息抓取出来,做一次汇聚aggregation。
Attention与RNN进行比较
输入、输出方面:
RNN是把上一个时刻的信息输出传入下一个时候做输入。
Transformer通过一个Attention层,去全局的拿到整个序列信息,再用MLP做语义的转换。
相同点:
RNN与Transformer都是用一个线性层 or 一个MLP来做语义空间的转换。
虽然都是NLP任务,但是如何有效的去使用序列的信息是最大的区别。
我们的输入是一个一个的词(=词源=token),需要将词映射成一个向量。
含义 embedding的意思是给任何一个词,学习一个长为
编码器、解码器、最后softmax之前的3个embedding共享权重。 → 训练更简单。
Note:权重*sqrt(
维度大的话,学到的一些权重值就会变小,但之后还需要加上positional encoding(不会随着维度的增加而变化)。
multiply weights by sqrt(
Question 为什么Attention不会有时序信息呢?
Answer 因为Transformer Block的注意力机制是对value进行重构,对value进行重构的权重是query和key之间的距离,和序列信息无关。
根本不看key-value pair在序列哪些地方。一句话把顺序任意打乱之后,Attention出来,结果都是一样的。
问题引入 把一句话的词相对顺序进行了改变,但是结果不变,这岂不是错误?
※ 因此,position encoding的作用就是加入时序信息(所谓时序信息就是先后顺序之间的关系)。
由于我们的模型不包含循环和卷积,为了让模型利用序列的顺序,我们必须注入序列中关于词符相对或者绝对位置的一些信息。为此,我们将“位置编码”添加到编码器和解码器堆栈底部的输入嵌入中。
位置编码和嵌入的维度
有多种位置编码可以选择。
例如,通过学习得到的位置编码和固定的位置编码。
在这项工作中,我们使用不同频率的正弦和余弦函数:
其中,
我们选择这个函数是因为我们假设它允许模型很容易学习对相对位置的关注,因为对任意确定的偏移
我们还使用学习到的位置嵌入进行了试验,发现这两个版本产生几乎相同的结果。
我们选择了正弦曲线,因为它可以允许模型推断比训练期间遇到的更长的序列。
思路:在RNN中将上一个时刻的输出加入到下一时刻的输入中,以此来传递时序信息。假设有一个词,它在位置
计算机表示一个32位的整数:32个bit,每个bit上有不同的值来表示。
一个词在嵌入层表示成一个512维的向量,用另一个512维的向量来表示一个数字,位置信息 1 2 3 4 5 6 7 8 ...
表示一个位置数字信息的值,怎么计算?
周期不一样的sin和cos函数计算 → 任何一个值可以用一个长为512的向量来表示。
这个长为512、记录了时序信息的一个positional encoding,+ 嵌入层相加 → 完成把时序信息加进数据。
Demo 假如有一个英文句子,里面有7个单词。经过embedding后就会变成一个
在前面的内容中,论文主要介绍了模型整体架构,并对每个组件进行了解释。
作者在这部分将进行解释,为什么这样做以及设计理念。
主要解释的是卷积、循环和自注意力使用上的区别,此外,还与受限的自注意力做了对比。
如图所示,一共有3列进行比较。
第一列:计算复杂度,越低越好;
第二列:顺序的计算,越少越好;
顺序的计算指的是你下一步的计算必须要等前面多少步计算完成,在算一个layer的时候,等待的越少,那么并行度就会越高。
第三列:信息从一个数据点到另一个数据点需要走多远,也是越短越好。
一共有自注意力、循环、卷积、自注意力(带限制)这四种网络,分别介绍一下这4种网络的数值是如何计算的。
计算复杂度:
其实说白了就是几个矩阵做运算,其中一个矩阵是你的query的矩阵乘以你的key的矩阵。
query矩阵有
key也是一样的,也是
两个矩阵一乘的话,算法复杂度就是
因为sequential operations就是那么几个矩阵乘法,矩阵里面可以认为并行度是比较高的,所以这个地方是O(1)。
最大长度是从一个点跳到另一个点要走多少步。
在Attention里面,就是一个query可以跟所有的key去做运算,而且输出是跟所有的value的一个加权和;
所以就是说任何query跟任何一个很远的key-value pair,只要一次就能过来,所以这个长度是比较短的。
循环层我们知道,如果你的序列是乘了
在计算复杂度上,与自注意力相比取决于
在循环的时候,是要一步一步做运算,当前时刻的词需要等待前面东西的完成,所以导致一个长为
我们没有特别解释卷积在序列上怎么做。具体做法是它用一个1d的卷积,所以它的kernal就是
卷积的好处是经过一次卷积就完成了,里面的并行度很高,所以卷积做起来,通常比RNN要快一点。
卷积每一个是由一个长为
当我做注意力的时候,我的query只跟我最近的
存在的问题是说:存在两个距离比较长的点,需要走几步才能过来。
一般来说,在实际中用Attention主要是关心特别长的序列,能够把信息揉的比较好一点,所以在实际过程中,self-Attention(restricted)用的不是那么多,搭建都是用最原始的版本。
(ps:如果在语音识别方向,会用到restricted,可以让语音识别变成实时的)
实际中,当你的序列长度和你模型的宽度差不多的时候,而且大家深度都一样的话,基本上三个模型的算法复杂度都是差不多的。
当然,你的Attention和卷积相对来说计算会好一点,另外一个是说Attention在信息的糅合性上会好一点,所以你可以认为这个地方还是能影响一些东西。
所以看上去是说用了self-Attention之后对长数据处理得更好,而且算的不快也不会慢。
但实际上并不是这个样子,Attention对模型得假设做了更少,导致需要更多的数据和更大的模型才能训练出来,达到跟RNN和CNN同样的效果。
所以导致现在基于Transformer的模型呢都是特别大、特别贵。
本文主要使用了2个翻译任务,一个是英语 → 德语;一个是英语 → 法语。接下来分两个部分来介绍一下。
首先谈到了训练数据集和batch是如何处理的,前面谈到了LayerNorm和BatchNorm是分别对Feature和Batch做Norm。
英语翻译德语,用的是标准的WMT2014任务,有4500万个句子对,使用了bpe(byte-pair encoding)来做词嵌入。
大概的思想就是说,不管是英语还是德语,其实一个词里面有很多种变化,什么加ing,什么加es呀,但是直接把每一个词做成一个token的话,会导致字典里面东西比较多,而且一个东西可能会有几种变化形式,做成不一样的词的时候,之间的区别模型是不知道的,bpe相对来说就是把你的那些词根给你提出来,好处是可以将整个字典降的比较小。
Transformer英语翻译德语任务使用的是37000个token的一个字典,而且它是在英语和德语之间是共享的:
就是说不再单独为英语或德语构造一个字典,好处是说我整个编码器和解码器的embedding就可以用一个东西了。
模型也更加简单,也就是之前它所谓的编码器和解码器的embedding是共享权重的。
(ps:词向量,Word embedding,又叫Word嵌入式,自然语言处理中的一组语言建模和特征学习技术的统称,其中来自词汇表的单词或短语被映射到实数的向量。)
这个任务使用了一个更大的数据集,在接下来的硬件和schedule部分,训练使用8个p100的GPU
(三年前google的大量工作还是使用GPU的,但是之后google就让内部员工尽量使用tpu,Transformer里面基本都是比较大的矩阵做乘法,tpu适合处理大的矩阵乘法)
它说我们的base模型使用一个小一点的参数,每一个batch的训练时间是0.4s,一共训练了10w步,在8个GPU上训练了12个小时。就是基本上一台机器训练12个h也是不错的性能。
对于大的模型,一个batch训练需要1秒钟,他一共训练了30w步,一台机器3.5天。这其实也是可承受的范围,但之后所出现的工作很难承受。
在训练器上面使用的是Adam,
学习率是用这个公式算出来的:
有意思的是学习率是根据你模型的宽度的-0.5次方,就是说模型越宽,就是你学习的向量越长的时候。你的学习率要低一点,另外一点是它有个warmup,就是从一个小的值慢慢地爬到一个高的值,最后再跟你的步数按照0.5次方衰减,最后它说我的warmup_step是4000。
有意思的是几乎可以看到这个地方没有东西是可以调的,取决于:
adam对学习率没那么敏感;
作者在一台具有8个NVIDIA P100 GPU的机器上训练Transformer模型。
使用本文描述的超参数的基础模型,每个训练步骤耗时约0.4秒。
基础模型共训练了10万步,约12小时。
对于我们的大模型(如表的底部所示),步长为1.0秒,大模型训练了30万步(3.5天)。
训练期间采用三种正则化:
残差丢弃:我们将丢弃应用到每个子层的输出,在将它与子层的输入相加和规范化之前。此外,在编码器和解码器堆栈中,我们将丢弃应用到嵌入和位置编码的和。对于基本模型,我们使用
Label Smoothing:在训练过程中,我们使用的label smoothing的值为
在WMT 2014英语-德语翻译任务中,大型Transformer模型(表2中的Transformer(big))比以前报道的最佳模型(包括整合模型)高出2.0个BLEU以上,确立了一个全新的最高BLEU分数为28.4。该模型的配置列在表3的底部。训练在8个P100 GPU上花费3.5天。即使我们的基础模型也超过了以前发布的所有模型和整合模型,且训练成本只是这些模型的一小部分。
在WMT 2014英语-法语翻译任务中,我们的大模型的BLEU得分为41.0,超过了之前发布的所有单一模型,训练成本低于先前最先进模型的1/4。英语-法语的Transformer(big)模型使用丢弃率为
对于基础模型,我们使用的单个模型来自最后5个检查点的平均值,这些检查点每10分钟写一次。对于大型模型,我们对最后20个检查点进行了平均。我们使用beam search,beam大小为4,长度惩罚
表2总结了我们的结果,并将我们的翻译质量和训练成本与文献中的其他模型体系结构进行了比较。我们通过将训练时间、所使用的GPU的数量以及每个GPU的持续单精度浮点能力的估计相乘来估计用于训练模型的浮点运算的数量。
当前最好的架构是基于注意力的”encoder-decoder”架构。这些架构都使用了CNN或RNN。这篇文章提出的Transformer架构仅使用了注意力机制,而无需使用CNN和RNN。
注意力
名称:注意力
“注意力”这个名字取得非常不容易理解。
作者说,应该叫:“全局信息查询”。
计算:做1次“注意力”计算
其实就跟去数据库了做了一次查询一样。
Demo:
假设,我们现在有这样一个以人名为key(键),以年龄为value(值)的数据库。
xxxxxxxxxx
61{
2 张三: 18,
3 张三: 20,
4 李四: 22,
5 张伟: 19
6}
情况1(明确的查询):现在,我们有一个query(查询),问所有叫“张三”的人的年龄平均值是多少。
让我们写程序的话,我们会把字符串“张三”和所有key做比较,找出所有“张三”的value,把这些年龄值相加,取一个平均数。
这个平均数是(18+20)/2=19。
情况2(不那么明确的查询):比如,我们可能想查询一下所有姓张的人的年龄平均值。
这次,我们不是去比较key == 张三
,而是比较key[0] == 张
。
这个平均数应该是(18+20+19)/3=19。
情况3(更模糊的查询):模糊到无法用简单的判断语句来完成。
因此,最通用的方法是
把query和key各建模成一个向量。
对query和key之间算一个相似度(比如向量内积);
以这个相似度为权重,算value的加权和。
这样,不管多么抽象的查询,我们都可以把query, key建模成向量,用向量相似度代替查询的判断语句,用加权和代替直接取值再求平均值。
“注意力”,其实指的就是这里的权重。
把这种新方法套入刚刚那个例子里。我们先把所有key建模成向量,可能可以得到这样的一个新数据库:
xxxxxxxxxx
61{
2[1, 2, 0]: 18, # 张三
3[1, 2, 0]: 20, # 张三
4[0, 0, 2]: 22, # 李四
5[1, 4, 0]: 19 # 张伟
6}
假设
key[0]==1
表示姓张。
1 【计算权重】我们的查询“所有姓张的人的年龄平均值”就可以表示成向量
[1, 0, 0]
。用这个query和所有key算出的权重是:收获:
1
个Query和4
个Key进行点乘,即可得到每个Key的权重2 【归一化权重】+3 【计算Value的加权和】之后,我们该用这些权重算平均值了。
ps: 注意,算平均值时,权重的和应该是1。因此,我们可以用softmax把这些权重归一化一下,再算value的加权和。
这样,我们就用向量运算代替了判断语句,完成了数据库的全局信息查询。那三个1/3,就是query对每个key的注意力(即,权重)。
收获:softmax可以归一化权重:让权重的和变成1
符号 | 含义 | 实例(在上一个问题中对应的具体对象) |
---|---|---|
key向量的数组 | ||
value向量的数组 | 而在我们刚刚那个例子里,value都是实数。 实数其实也就是可以看成长度为1的向量。 |
在刚刚的例子中,我们只做了1
次查询,所以公式可以改写为:
其中,query,也就是q则为[1, 0, 0]
。
※ 实际上,我们也可以一次做多组query,把所有的q打包成矩阵Q,就得到了上述的公式:
符号 | 含义 | 实例(在上一个问题中对应的具体对象) |
---|---|---|
key向量的数组 | 每个组成被称为key向量,所有加起来叫做key向量的数组。 | |
value向量的数组 | 而在我们刚刚那个例子里,value都是实数。 实数其实也就是可以看成长度为1的向量。 | |
query和key向量的长度由于query和key要做点乘,这两种向量的长度必须一致。 | value向量的长度倒是可以不一致, 论文里把value向量的长度叫做 在我们这个例子里, key向量示例:[1,2,0];长度为3; query向量:[1,0,0];长度为3。 |
为什么要用一个和
这是因为,softmax在绝对值较大的区域梯度较小,梯度下降的速度比较慢。因此,我们要让被softmax的点乘数值尽可能小。而一般在
简单来说:防止(点乘的值过大 → 导致区域梯度较小 → 梯度下降的速度变慢)
关于注意力的解读:
注意力就是权重;
我们常常称呼的“注意力函数”就是“权重函数”;
“注意力函数”就是计算注意力的函数;
那么“权重函数”就是计算权重的函数 → 也就是计算相似度的函数。
计算相似度的方式
点乘注意力(点乘法)
demo 点乘注意力(利用点乘法计算相似度)
结果为1,表示相似;
结果为0,表示不相似。
加性注意力(另一种常用的注意力函数)
收获:点乘可以计算出2个向量之间的相似性。
注意力
回顾:注意力机制其实就是“全局信息查询”。
补充 具体来说,自注意力机制允许模型同时计算输入序列中所有位置之间的关系权重,进而加权得到每个位置的特征表示。在Transformer模型中,自注意力机制被运用在了Encoder和Decoder两个部分中,分别用于编码输入序列和生成输出序列。
多头自注意力机制(multi-head self-attention)是Transformer模型的核心部分,其作用是从输入序列中学习并计算每个位置与其他位置之间的相关度。具体来说,多头自注意力机制将输入序列中的每个位置看作一个向量,然后对这些向量进行相似度计算,得到每个位置与其他位置之间的相关度。
多头自注意力机制将输入序列分别映射成多个维度相同的向量,然后分别应用自注意力机制,得到多个输出向量,最后将这些输出向量拼接起来,得到最终的向量表示。这种分头处理的方法可以使模型更好地捕捉不同方面的特征,从而提高模型的表现。
自注意力模块
目的:为每一个输入token生成一个向量表示,该表示不仅能反映token本身的性质,还能反映token在句子里特有的性质。
比如:翻译“简访问非洲”这句话时,第三个字“问”在中文里有很多个意思,比如询问、慰问等。
→ 我们想为它生成一个表示,知道它在句子中的具体意思。
而在例句中,“问”字组词组成了“访问”,所以它应该取“询问”这个意思,而不是“慰问”。“询问”就是“问”字在这句话里的表示。
自注意力模块的具体工作流程
输入:3个矩阵
在「注意力」一节中:
Q → query:[1, 0, 0]
K → key向量组成的数组:
V → value向量的数组:
自注意力模块会为每一个token输出一个向量表示
demo 以刚刚那个句子“简访问非洲”为例,看一下自注意力是怎么计算的。
存疑:token的query, key, value究竟是什么算出来的,后文会对此做解释。
自注意力计算过程(举例:“简访问非洲”)
每一个字(token)都有自己的q, k, v。所有字的q, k, v组合起来就成了Q, K, V.
q →
k → (当前字对应的)词性(动词 or 名词)
v → (当前字对应的)嵌入
求解目标:计算
目标过渡:
为了获取
这个问题就是第三个token(“问”)的query向量(
求解过程:
和“问”字组词的字,很可能是一个动词。
※ 恰好:
● 每一个token的key
● 每一个token的value
![]() | ![]() |
---|
这样,我们就可以根据每个字的词性(key),尽量去找动词(和query比较相似的key),求出权重(query和key做点乘,再做softmax),对所有value求一个加权平均,就差不多能回答问题
※ 求权重:
经计算,
因此,最终算出来的
![]() |
---|
准确来说,
小结
※ 从上一节中学习到:注意力其实就是全局信息查询。
※ 而在这一节学习到:注意力的一种应用:通过让一句话中的每个单词去向其他单词查询信息,我们能为每一个单词生成一个更有意义的向量表示。
引子:每个单词的query,key,value是怎么得来的? → Transformer里的另一种机制——多头注意力。
注意力
机制补充 多头注意力机制(multi-head attention)是Decoder中的另一个子层,其作用是计算当前时刻的输入与输入序列之间的关系,并根据这些关系计算出当前时刻的上下文向量表示。
多头注意力机制将输入序列的向量表示与当前时刻的输入向量表示进行相似度计算,得到每个位置与当前时刻输入的相关度。然后,根据这些相关度计算当前时刻的上下文向量表示,用于生成输出序列。与多头自注意力机制类似,多头注意力机制也采用了分头处理的方法,从而更好地捕捉不同方面的特征。
————————————————
※ 首先:在自注意力中,每一个单词的query, key, value应该只和该单词本身有关。
● 因此,这三个向量都应该由单词的词嵌入得到。
※ 另外:每个单词的query, key, value不应该是人工指定的,而应该是可学习的。
● 因此,我们可以用可学习的参数来描述从词嵌入到query, key, value的变换过程。
※ ※ 综上所述:自注意力的输入
其中:
是词嵌入矩阵,也就是每个单词的词嵌入的数组;
是可学习的参数矩阵。
※ 在Transformer中,大部分中间向量的长度都用
● 因此,设输入的句子长度为
▲ 则
▲
▲
就像卷积层能够用多个卷积核生成多个通道的特征一样,我们也用多组
把多头注意力用在自注意力上的公式为:
Transformer似乎默认所有向量都是行向量,参数矩阵都写成了右乘而不是常见的左乘。
ps: 这句话怎样理解呢?左乘很常见吗?
是可学习的参数矩阵。 参数矩阵都写成了右乘,而不是常见的左乘。
其中:
多头注意力模块的输入输出向量的长度都是
因此,
在论文中,Transfomer的默认参数配置如下:
词嵌入的长度、大部分输入/输出向量的长度、多头注意力模块的输入输出向量的长度
输出的个数:自注意力输出的个数
关于❷的解释可以看到第1个例子计算张姓同学的年龄的时候,value是每个人的年龄;18
、20
这种表示年龄的一个数字,可以看成长度为1的向量
例如在第1个例子计算张姓同学的年龄的时候,key就是每个人的姓名的表示;
实际上,多头注意力机制不仅仅可以用在计算自注意力上。推广一下,如果把多头注意力的输入
※
Transformer模型架构![]() | ![]() |
---|
Transformer使用了和ResNet类似的残差连接,即:
设模块本身的映射为
和ResNet不同,Transformer使用的归一化方法是LayerNorm。
残差连接有一个要求:
输入
在Transformer中,包括所有词嵌入在内的向量长度都是
补充 前馈网络(feedforward network)是Encoder层的另一个子层,其作用是对多头自注意力机制的输出向量进行非线性变换。前馈网络由两个线性变换和一个激活函数组成,其中线性变换将输入向量映射到一个高维空间,激活函数将这个高维向量进行非线性变换,最后再将其映射回原始维度。
各组件作用:
线性变换:将输入向量映射到一个高维空间;
激活函数:将这个高维向量进行非线性变化,最后再将其映射回原始维度。
架构图中的前馈网络(Feed Forward)其实就是一个全连接网络。
具体来说,这个子网络由两个线性层组成,中间用ReLU作为激活函数。
中间的隐藏层的维度数记作
早期生成序列的模型 早期基于RNN的序列转换模型在生成序列时一般会输入前
用Transformer生成序列 Transformer使用了encoder-decoder的架构:
对于输入序列
给定
对比:
Transformer默认会并行地输出结果。而在推理时,序列必须得串行生成。
直接调用Transformer的并行输出逻辑会产生非常多的冗余运算量。推理的代码实现可以进行优化。
具体来说,输入序列x会经过N=6个结构相同的层。每层由多个子层组成。
第一个子层是多头注意力层,准确来说,是多头自注意力。
这一层可以为每一个输入单词提取出更有意义的表示。
之后数据会经过前馈网络子层。最终,输出编码结果
得到了
解码器的输入是当前已经生成的序列,该序列会经过一个掩码(masked)多头自注意力层。我们先不管这个掩码是什么意思,暂且把它当成普通的多头自注意力层。它的作用和编码器中的一样,用于提取出更有意义的表示。
接下来,数据还会经过一个多头注意力层。这个层比较特别,它的
为什么会有这样的设计呢?
这种设计来自于早期的注意力模型。如下图所示,在早期的注意力模型中,每一个输出单词都会与每一个输入单词求一个注意力,以找到每一个输出单词最相关的某几个输入单词。用注意力公式来表达的话,
就是输出单词, 就是输入单词。
经过第二个多头注意力层后,和编码器一样,数据会经过一个前馈网络。最终,网络并行输出各个时刻的下一个单词。
Masked
这种并行计算有一个要注意的地方。在输出第t+1个单词时,模型不应该提前知道t+1时刻之后的信息。因此,应该只保留t时刻之前的信息,遮住后面的输入。这可以通过添加掩码实现。添加掩码的一个不严谨的示例如下表所示:
输入 | 输出 |
---|---|
(y1, | y2 |
(y1, y2, | y3 |
(y1, y2, y3, | y4 |
这就是为什么解码器的多头自注意力层前面有一个masked。在论文中,mask是通过令注意力公式的softmax的输入为
每个mask都是一个上三角矩阵。
看完了Transformer的主干结构,再来看看输入输出做了哪些前后处理。
![]() | ![]() |
---|
和其他大多数序列转换任务一样,Transformer主干结构的输入输出都是词嵌入序列。
词嵌入,其实就是一个把one-hot向量转换成有意义的向量的转换矩阵。
在Transformer中,解码器的嵌入层和输出线性层是共享权重的
输出线性层表示的线性变换是嵌入层的逆变换,其目的是把网络输出的嵌入再转换回one-hot向量。
如果某任务的输入和输出是同一种语言,那么编码器的嵌入层和解码器的嵌入层也可以共享权重。
论文中写道:“输入输出的嵌入层和softmax前的线性层共享权重”。这个描述不够清楚。如果输入和输出的不是同一种语言,比如输入中文输出英文,那么共享一个词嵌入是没有意义的。
嵌入矩阵的权重乘了一个
由于模型要预测一个单词,输出的线性层后面还有一个常规的softmax操作。
对上面一段话我的理解是:
现在,Transformer的结构图还剩下一个模块没有读——位置编码。
对比 无论是RNN还是CNN,都能自然地利用到序列的先后顺序这一信息。
然而,Transformer的主干网络并不能利用到序列顺序信息。因此,Transformer使用了一种叫做“位置编码”的机制,对编码器和解码器的嵌入输入做了一些修改,以向模型提供序列顺序信息。
位置编码的意义 所以:Transformer的位置编码,就是为了向模型提供“序列顺序信息”的。
第1句:嵌入层的输出是一个向量数组,即词嵌入向量的序列。
个人理解:
假设是这样的:
,如果它是词嵌入向量的序列。
那么,❶一个词嵌入向量是:
。
(?) 那么一个词嵌入就是:1。
第2句设数组的位置叫
个人理解:
什么叫做“数组的位置
”?
疑惑
什么叫做向量的“某一维
”?
在❶中,已经知道,一个词嵌入向量是:
。
是不是说:像1、2、0这就是向量的第1维度、第2维度、第3维度分别对应的值。
第3句我们为每一个向量里的每一个数添加一个实数编码,这种编码方式要满足以下性质:
【性质1】对于同一个
即,1、2、0这3个维度(
【性质2】对于向量的同一个维度处,不同
不同
疑惑数组的位置被称为
要满足这2种性质,我们可以轻松地(我感觉不轻松,甚至感觉到了压力 :P)设计一种编码:
即,对于每一个位置
举个例子呢?
但是,这种编码不利于网络的学习。所以,我们在设计网络的时候,要考虑它的可学习性,因为这样的话,才能让代码跑起来之后,自己学参数,不用人工调参。
目标 我们更希望所有编码都差不多大小,且都位于0~1之间。
行动 为此,Transformer使用了三角函数作为编码函数。
这种位置编码(Positional Encoding, PE)的公式如下:
另外根据三角函数的和角公式:
本文作者也尝试了用可学习的函数作为位置编码函数。实验表明,二者的表现相当。作者还是使用了三角函数作为最终的编码函数,这是因为三角函数能够外推到任意长度的输入序列,而可学习的位置编码只能适应训练时的序列长度。
与循环层、卷积层做了对比之后,结论如下。
自注意力层是一种和循环层和卷积层等效的计算单元。
它们的目的都是把一个向量序列映射成另一个向量序列,比如说编码器把
论文比较了三个指标:每一层的计算复杂度、串行操作的复杂度、最大路径长度。
前两个指标很容易懂,第三个指标最大路径长度需要解释一下。
最大路径长度:数据从某个位置传递到另一个位置的最大长度。
比如对边长为
的图像做普通卷积操作,卷积核大小3x3,要做 次卷积才能把信息从左上角的像素传播到右下角的像素。
设卷积核边长为
,则最大路径长度 。
如果是空洞卷积的话,像素第一次卷积的感受野是3x3,第二次是5x5,第三次是9x9,以此类推,感受野会指数级增长。这种卷积的最大路径长度是
。
我们可以从这三个指标分别探讨自注意力的好处。
1 首先看序列操作的复杂度。
如引言所写,循环层最大的问题是不能并行训练,序列计算复杂度是
而自注意力层和卷积一样可以完全并行。
2 再看每一层的复杂度。
设
其他架构的复杂度有
3 最后是最大路径长度。
注意力本来就是全局查询操作,可以在
它的信息传递速度远胜卷积层和循环层。
为了降低每层的计算复杂度,可以改进自注意力层的查询方式,让每个元素查询最近的r个元素。本文仅提出了这一想法,并没有做相关实验。
本工作测试了“英语-德语”和“英语-法语”两项翻译任务。使用论文的默认模型配置,在8张P100上只需12小时就能把模型训练完。本工作使用了Adam优化器,并对学习率调度有一定的优化。
模型有两种正则化方式:
1)每个子层后面有Dropout,丢弃概率0.1;
2)标签平滑(Label Smoothing)。
Transformer在翻译任务上胜过了所有其他模型,且训练时间大幅缩短。
论文同样展示了不同配置下Transformer的消融实验结果。
实验A表明:在计算量不变的前提下,需要谨慎地调节h和hk,hv的比例,太大太小都不好。这些实验也说明,多头注意力比单头是要好的。
实验B表明:dk增加可以提升模型性能。作者认为,这说明计算key,value相关性是比较困难的,如果用更精巧的计算方式来代替点乘,可能可以提升性能。
实验C、D表明,大模型是更优的,且dropout是必要的。
如正文缩写,实验E探究了可学习的位置编码。可学习的位置编码的效果和三角函数几乎一致。
动机:为了改进RNN不可并行的问题,这篇工作提出了Transformer这一仅由注意力机制构成的模型。
效果:Transformer的效果非常出色,不仅训练速度快了,还在两项翻译任务上胜过其他模型。
作者也很期待Transformer在其他任务上的应用。对于序列长度比较大的任务,如图像、音频、视频,可能要使用文中提到的只关注局部的注意力机制。由于序列输出时仍然避免不了串行,作者也在探究如何减少序列输出的串行度。
现在来看,Transformer是近年来最有影响力的深度学习模型之一。它先是在NLP中发扬光大,再逐渐扩散到了CV等领域。文中的一些预测也成为了现实,现在很多论文都在讨论如何在图像中使用注意力,以及如何使用带限制的注意力以降低长序列导致的计算性能问题。
我(博文作者)认为,对于深度学习的初学者,不管是研究什么领域,都应该仔细学习Transformer。
在学习Transformer之前,最好先了解一下RNN和经典的encoder-decoder架构,再学习注意力模型。
有了这些基础,读Transformer论文就会顺利很多。读论文时,最重要的是看懂注意力公式的原理,再看懂自注意力和多头注意力,最后看一看位置编码。其他一些和机器翻译任务相关的设计可以不用那么关注。
注意力机制不仅仅是Transformer里有的,其他地方也有。只是Transformer里只用了注意力机制。
补充 经典论文学习:Attention Is All You Need(Transformer) ※ 这篇文章有点对“注意力、自注意力、掩码注意力区分不明的感觉”
补充 Transformer:《Attention is all you need》(论文精读/原理解析/模型架构解读/源码解析/相关知识点解析/相关资源提供)
博文免责声明
本条博文信息主要整合自网络,部分内容为自己的理解写出来的,如有断章截句导致不正确或因个人水平有限未能详尽正确描述的地方,敬请各位读者指正;
引用出处可能没有完全追溯到原始来源,如因此冒犯到原创作者,请联系本人更正/删除;
博文的发布主要用于自我学习,其次希望帮助到有共同疑惑的朋友。