【Transformer学习笔记】Transformer开山之作: Attention is All you Need


至今,transformer结构的网络可以说已经占据了RNN领域的大半壁江山,在各大RNN竞赛中基本都出现了霸榜的情况。
本文主要作transformer的开山之作《Attention is All you Need》的解读.

一、transformer整体结构

首先先来看一看论文原文中整体结构图。论文中给出的结构可以看成是一个用于机器翻译的Transformer的结构。
transformer结构图
相信大部分第一次看的人肯定都是很懵逼的,因为它和我们之前看的大部分以卷积为基本单位的RNN系列网络不同,他有很多奇奇怪怪的分支箭头。但是相信在完整了解了Transformer的整体结构后,会发现其实这张图十分简洁且清晰(不过还是想吐槽一下这个画的像太极一样的Positional Encoding)。

可以看到,Transformer可以从中间切为两半,左边的那部分是Encoder(编码器),右边那部分是Decoder(解码器)。编码器和解码器都拥有一个输入,但是输入的内容略有不同,编码器部分的输入是待翻译的句子,而解码器的输入是已经翻译了一部分的翻译过后的句子,这个地方有一点绕,本文将在后面进行解释。最后,在解码器最上方还有一个输出层,用来进行输出。

二、why Transformer

事实证明,transformer在nlp的各个领域都能大放异彩,成为了后起之秀。那么transformer到底为什么会比RNN效果要好呢?可以先看看下图:
在这里插入图片描述
图源自于一文搞懂RNN(循环神经网络)基础篇

如图,RNN系的模型中,最基本的结构是一个cell,如图中左侧所示。RNN的计算过程就是输入的数据分时序的反复经过相同的cell结构。
如输入:[我,爱,学,习],这四个字符会按顺序依次投入网络中进行计算。

图中的 X X X表示某个输入的字符,如 【我】 或者是 【爱】, O O O表示的是输出,放在机器翻译的场景中, X X X:【我】输入对应的输出 O O O是英文【i】。 S S S表示的是隐藏层,其中蕴含着本次输入和之前输入的信息。 W W W V V V U U U都是参数,其中 U U U用于对输入进行编码, W W W用于对隐藏层编码, V V V用于进行解码。

RNN的输入通常会取上一个隐藏层的输出 S t − 1 S_{t-1} St1经过 W W W编码作为本层来源于之前信息的输入, X t X_{t} Xt进行 U U U编码后作为本层的信息输入,二者经过融合后得到本隐藏层 S t S_{t} St,经过 V t V_{t} Vt的解码后的到输出 O t O_{t} Ot,而 S t S_{t} St又将继续传递下去。

值得注意的是RNN使用的是一套参数,也就是 W W W V V V U U U在所有的timestep(可以理解为每个输入的字)中是一致的。不难想想RNN来源于上层的 S t − 1 S_{t-1} St1信息在经过后续无数次的循环后可能会有所丢失。通俗意义上讲,对于一个句子而言,后面的部分在翻译的时候很可能就会无法顾及开头的信息,因为信息在传递的过程中丢失了。

【无法顾及全局的信息】是RNN的一大缺点,同时,因为需要进行序列化的一个个输入,导致 【训练和计算时间慢】,是RNN的另一个缺点。

而这两个缺点,在transformer引入了其attention机制进行并行化处理后,都得到了一定程度的解决。

三、Transformer逐层剖析

transformer的整体结构可以大概分为编码器和解码器两部分,本部分将分别介绍这两个主要构件。

1.Encoder 编码器

为了更加直观的说明,还是以【我,爱,学,习】为例。

词嵌入

首先,一般在nlp中输入是one-hot表示的,随后进入word embedding(词嵌入)层进行降维,得到最终的输入。词嵌入可以理解为将字的表示进行降维,从稀疏矩阵转为稠密矩阵,使每个表示字的向量在减少占据的空间的同时具有语义性。
大概转化如下图。

t t t是输入的字的个数,也就是timestep, d i c _ l e n dic\_{}len dic_len是字典长度, d m o d e l d_{model} dmodel对应了论文中的表述,是词嵌入后用来表示每个字的向量的维度。(注意:本文为了简化说明,省略了batch_size)。

在这里插入图片描述

最终输入大小为 t × d m o d e l t \times d_{model} t×dmodel

位置编码

在这里插入图片描述
随后输入将进入位置编码层。那么为什么需要位置编码呢?

之前提到过,transformer的一大特点,它把计算给并行化了。他固然带来了计算速度和全局信息传递上的优势,但是也带来了一个缺点:
整个句子计算同时进行的,所以句子前后的时序信息被抹除了。

比如,本来我们的输入是【我爱学习】,但是时序信息和前后关系莫得了,它便等价于输入【学习爱我】,这显然不是我们想要的。

因此transformer便想出了一个办法解决这个问题:引入位置编码,给每个输入附加上它本来的位置信息。原论文中的公式如下:

P E ( p o s , 2 i ) = s i n ( p o s / 1000 0 2 i / d m o d e l ) PE_{(pos,2i)} = sin(pos/10000^{2i/d_{model}}) PE(pos,2i)=sin(pos/100002i/dmodel)
P E ( p o s , 2 i + 1 ) = c o s ( p o s / 1000 0 2 i / d m o d e l ) PE_{(pos,2i+1)} = cos(pos/10000^{2i/d_{model}}) PE(pos,2i+1)=cos(pos/100002i/dmodel)
公式中的 p o s pos pos表示的是position,也就是具体的某个字符的位置,比如【我】的位置是0,【学】是2。

2 i 2i 2i 2 i + 1 2i+1 2i+1分别表示偶数和奇数的维度。具体而言,之前我们将每个字符表示为了 1 × d m o d e l 1 \times d_{model} 1×dmodel大小的向量,那么 2 i 2i 2i 2 i + 1 2i+1 2i+1就表示的是表示字符的 d m o d e l d_{model} dmodel长的向量中偶数和奇数的位置。

至于为什么采用正余弦表示,论文中的表述是:

we hypothesized it would allow the model to easily learn to attend by relative positions, since for any fixed offset k k k, P E p o s + k PE_{pos+k} PEpos+k can be represented as a linear function of P E p o s PE_{pos} PEpos.

因为对于三角函数而言,存在和差化积公式和积化和差公式,所以论文作者认为这么设计可以使某个位置编码有能力来表示其他位置编码,也就是具有关注相对位置的能力。

论文作者也做了和可学习位置编码的对比试验,发现性能差别不大,但是作者的正余弦版本位置编码可以应用于更长序列(因为是公式产生的),所以作者最后采用了正余弦位置编码。

在完成了位置编码后同样产生了大小为 t × d m o d e l t \times d_{model} t×dmodel的向量,加入到输入的词向量矩阵中进行融合
,得到最终大小为大小为 t × d m o d e l t \times d_{model} t×dmodel的输入。

多头注意力机制层

重点!这部分是transformer注意力机制的灵魂所在!

这一层可以分解为【注意力机制】和【多头】两个重点。

注意力机制

我们的输入是经过位置编码的大小为 t × d m o d e l t \times d_{model} t×dmodel的矩阵(省略了batch_size)。本层中会让输入分别经过大小为 d m o d e l × d k d_{model} \times d_{k} dmodel×dk W k W_{k} Wk d m o d e l × d q d_{model \times d_{q}} dmodel×dq W q W_{q} Wq d m o d e l × d v d_{model \times d_{v}} dmodel×dv W v W_{v} Wv,产生Q(query),K(key),V(value)三个编码后的矩阵。

其中 d q d_q dq, d k d_k dk, d v d_v dv是超参数,为了便于算Q和K两者的点乘,论文中取 d q d_q dq= d k d_k dk

Q和K的大小为 t × d k t \times d_{k} t×dk,V的大小为 t × d v t \times d_{v} t×dv t t t仍然是字符的个数,因此QKV其实都分别是由一个个字符编码后的向量表示组成的矩阵,用于特定的用途。

为了避免混淆,我们使用qkv表示代表每个字符信息的向量,用QKV表示所有向量组成的矩阵

在这里插入图片描述

q对应表示的是字符对应的【用来查询】的信息,它表示的是该字符用于和其他字符进行比对的信息。

k对应的是字符【被比对】的信息,字符将用它来被其他字符进行比对。

v表示的是字符【自身表示】的信息。

论文中使用QKV计算attention的公式为:
A t t e n t i o n ( Q , K , V ) = s o f t m a x ( Q K T d k ) V Attention(Q, K, V) = softmax(\frac{QK^T}{\sqrt{d_k}})V Attention(Q,K,V)=softmax(dk QKT)V

公式中 Q K T QK^{T} QKT得到的是使用每个字符的Q和所有字符(包括自己)的K进行点乘,得到的表示字符之间的相关性的一个数字。比如一个句子【一只狗摔倒了地上,它觉得很疼】中,【它】指的是【地】还是【狗】。如果【它】表示的是【狗】,那么用【它】的q和【狗】的k进行点乘得到的数值应该很大,而【它】的q和【地】的k进行点乘得到的数值应该相对较小。

Q K T QK^{T} QKT最终生成一个大小为 t × t t \times t t×t的矩阵,该矩阵位置 ( i , j ) (i,j) (i,j)上的元素表示的就是使用第 i i i个字符的q和第 j j j个字符的k进行点乘后得到的表示它们相关性的数值,如下图。

在这里插入图片描述
此处的矩阵计算便是transformer结构能够并行计算的关键之处。

生成的 Q K T QK^{T} QKT矩阵还需要再除以了一个 d k \sqrt{d_k} dk ,可以简单理解为调整方差方到1,防止softmax趋向于较大值。

之后 Q K T d k \frac{QK^T}{\sqrt{d_k}} dk QKT将会经过一个 S o f t m a x Softmax Softmax层,注意,此处的 S o f t m a x Softmax Softmax以字符为单位作用的,是对 Q K T d k \frac{QK^T}{\sqrt{d_k}} dk QKT每行作softmax。最后由此得到表示【不同字符】对于【当前字符】的权重值的矩阵。

使用权重值和表示字符信息的V相乘后,就可以得到混入了不同权重的别的字符的信息的当前字符的新表示。

在这里插入图片描述
使用 Q K T QK^{T} QKT乘上V矩阵,得到一个新的用于表示每个字符的信息的大小为 t × d v t \times d_v t×dv矩阵。

多头

注意力机制部分的其实是一个头所作的事情,而多头其实理解起来很简单,就是有很多组不同的 W q W_{q} Wq W k W_{k} Wk W v W_{v} Wv,作用于同一个输入上,产生多组不同的相互隔离的QKV。

至于为什么这么做,抽象的说可能是不同的QKV可能提取出了同一个输入的不同表示,从而增强了模型的表达能力。通俗的说是因为更多的头会引入更多的参数,大部分情况下效果会更好。

如上文所说,每个头最后都会产生一个大小为 t × d v t \times d_v t×dv矩阵作为输出。最后将这些头给整合起来(论文中是直接连接起来了),假设一共有 h h h个头,那么链接后的矩阵大小为 t × h d v t \times hd_v t×hdv,在经过一个大小为 h d v × d m o d e l hd_v \times d_{model} hdv×dmodel W o W_{o} Wo,得到最终大小为 t × d m o d e l t \times d_{model} t×dmodel的输出。

之后还会再进入一个名为【Feed Forward】的层,简单来说它只是把矩阵先升维再降维,增加了一些模型的复杂度。最后的输出大小仍然为 t × d m o d e l t \times d_{model} t×dmodel

可以发现这个输出的大小和经过embedding矩阵后产生的输入大小是一样的,这也是transformer的一大特点:

每块产生的输出和输入大小一样!!

encoder总结

至此encoder部分就已经基本结束了,它其实还采用了残差结构,但是本文就不过多赘述了。值得一提的是encoder模块是多个堆叠的,因为输入和输出是相同的,所以可以进行堆叠。至于为什么使用六个,抽象的说可能是层数越多能够提取越多信息,和卷积越深效果越好的道理一样。通俗的说可能是参数量越大表达能力越强。

2.Decoder 解码器

刚刚encoder产生了一个大小为 t × d m o d e l t \times d_{model} t×dmodel的输入,其实并不会直接得到输出的对应字符,而要和解码层的信息融合后才能产生输出。

解码器输入

transformer比较神奇的一点在于,它的解码器也要接收输入,并且要经过和编码器一样的词嵌入层和位置编码层。

请注意此处的模型是针对于机器翻译的任务,解码器中的输入是【已经翻了的部分字符】。

比如【我爱学习】翻译为 【i, love ,studying】。

在最开始并不存在已经翻译的字符,所以输入是一个【开始字符】,最后输出的是【i】。第二次的输入便是【开始字符, i】,输出【love】,以此类推。

mask机制

但是我们在训练的时候其实输入解码器的是ground truth,也就是已经翻译好了的【开始字符,i, love ,studying】。这样会导致在翻译的时候受到后方信息的影响,比如在翻译【爱】-》【love】的时候,因为多头注意力机制会将所有的输入信息进行融合,这就导致了看到了还没有翻译到的【studying】的信息。

因此论文作者引入了mask机制,通俗的说就是将没翻译到的部分给遮挡住了,抹除了它对翻译的影响。下图为包含mask机制的输入层。

包含mask的输入层
可以发现,它不是在一开始就mask的,而是在softmax前,给需要mask的位置加上很大的一个负数,这样就使得softmax产生的输出趋近于0,这样这些位置的影响就微乎其微了。

双多头注意力机制

在这里插入图片描述

解码器的其实比编码器多了一层多头注意力机制的结构。

第一个加入了mask机制的多头注意力机制层和编码器一样,随后产生的输出将和编码器产生的输出一起进入第二个多头注意力机制层,这里将使用编码器第一个注意力机制曾产生的输出生成V和K,用解码器的输出生成Q。

输出

最后解码器的输出经过全连接和softmax层后产生一个词表大小的输出,来表示概率。

四、其他的

笔者认为有一点需要特殊去提的是,transformer的编码器和解码器输入长度从一开始就已经固定了的,如果输入的句子不够长度则填充特殊字符,如果超过了长度则要进行切割了。
比如将输入长度设定为10,我输入【我爱学习】,则需要填充六个padding符号。在编码器中的多头注意力机制层中,也需要使用类似于解码器中的mask机制来将这些填充字符的影响去除掉。

推荐的博客&视频

一文搞懂RNN(循环神经网络)基础篇
Transformer从零详细解读(可能是你见过最通俗易懂的讲解)

最后,笔者也正在transformer的学习道路上,如果有不正确或者不完整的地方欢迎补充~

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值