transformer nlp 标注 transformers nlp_信息编码


Transformer是由谷歌团队在Attention Is All You Need这篇论文中提出,其后可谓红遍大江南北,到目前为止仿佛有种此生不识Transformer,就称英雄也枉然的感觉。

而该模型的延展性能力有十分强大,其"变形”应用随处可见。就拿语言模型举例,前有 OpenAI GPT拿Transformer Decoder作为语言模型,后有11项全能的BERT拿Transformer Encoder作为语言模型,不愧于变形金刚这个名号

模型代码的话,建议阅读tensorflow/models仓库里Transformer的官方实现:
代码不长,结构整洁,注释齐全,两个字:真香


tensorflow/modelsgithub.com

transformer nlp 标注 transformers nlp_transformer的组成部分_02


本文试图在讲述Transformer结构的同时将个人感性理解相添加进来,同时将代码中含有的额外的实现细节一并写入。话不多说进入正题


1. Transformer 与 Sequence to Sequence

  • Transformer是端到端的Seq2Seq结构,其包含两个组成部分:Encoder 和 Decoder,

Encoder负责理解源文,Decoder负责产出译文。如图1.1


transformer nlp 标注 transformers nlp_Mask_03

图1.1

其Transformer Encoder 和 Transformer Decoder结构分别如下图:


transformer nlp 标注 transformers nlp_多层transformer结构_04

图1.2 Transformer 结构

  • Transformer是可以堆叠的(图1.2中的N× 就是堆叠N个的意思),其思路是越高层的Transformer能够学习到越高级更抽象的信息(类似CNN的堆叠)


transformer nlp 标注 transformers nlp_Mask_05

图1.3 transformer 预测

在论文中堆叠了6个。其中第六层的 Transformer Encoder Output 作为部分输入传递给每一层的 Transformer Decoder里(具体后面在Transformer Decoder部分会进步展开)

无论是Transformer Encoder, 还是Transformer Decoder,它们都由两个小模块组成,分别是:

  • Multi-Head Attention Layer(Decoder会对它微调以符合逐字翻译出译文的特点)
  • Feed-Forward Layer

(小模块的作用会在下面的Transformer Encoder部分展开)


2. Transformer Encoder

笔者通过Transformer Encoder来介绍各个小模块的结构及功能,首先来看下Encoder的结构:


transformer nlp 标注 transformers nlp_transformer nlp 标注_06

图2.1

在这之前首先要介绍是论文中连接个各个小模块的一个关键技巧:Add & Norm 操作

2.1 Add & Layer Normalization 操作

Add操作借鉴了ResNet模型的结构,其主要作用是使得transformer的多层叠加而效果不退化

Layer Normalization操作对向量进行标准化,可以简化学习难度

论文中Add & Layer Normalization 操作的结构为:



在tensorflow/models的代码实现中与论文略有不同:



(其中


就是前面说的小模块)


笔者感觉后者(式2.2)的确更合理,这样可以使得之后在Multi-Head Attention Layer中Scaled Dot-Product Attention的scale操作的假设与实际情况更接近(后面会在Scaled Dot-Product Attention还会提到)

2.2 Multi-Head Attention Layer

首先看下Multi-Head Attention Layer的结构图:


transformer nlp 标注 transformers nlp_transformer的组成部分_07

图2.2

从图中可以看到Multi-head体现为是由h个Scaled Dot-Product Attention组成。

为了把结构说的尽可能明白,这里先不做Multi-head,把Scaled Dot-Product Attention拿出来单独讲

2.2.1 Scaled Dot-Product Attention

Encoder里的Scaled Dot-Product Attention可以理解为对token向量进行信息提炼的过程,其结构图如下:


transformer nlp 标注 transformers nlp_transformer nlp 标注_08

图2.3

这里为了更清晰的阐述,先解释Scaled Dot-Product Attention如何对一个token向量进行提炼,再拓展到其同时并行地对每个token向量提炼信息

2.2.1.1 Scaled Dot-Product Attention 对某一token向量进行提炼

笔者的感性理解:Scaled Dot-Product Attention 可以抽象为一个简答的单轮问答过程。比如你想要了解一个人,于是你准备了一个想要了解他的问题,然后你不仅对他问这个问题,你还对其他可能了解他的人问同样的问题,你根据得到的所有回答汇总出一个答案,这个答案使得你更了解了他
Scaled Dot-Product Attention与上面的生活例子唯一的不同就是:每个token的回答都是事先就自己准备好了,你得自己去看各个token的回答是否匹配你问的那个问题,不匹配就扔掉,匹配就收集起来,最后将所有收集的回答汇总得到的答案就是这个token提炼好的新向量

这里笔者以提炼第一个位置的token向量举例,展示Scaled Dot-Product Attention的具体步骤:

1.



2. 每个位置的token向量(包括第一位置的token自己)都会事先准备好自己的回答(key, value) (这里准备好的回答是属于一次生成,到处使用~~,即之后轮到提炼第二个位置的token向量时,参考的回答还是这套回答集





这里笔者感性的将value理解为回答的完整内容,而key理解为回答的摘要

这里可以发现每个token向量在准备各自回答的时候,是不知道query内容的,即(key, value)的生成与query是解耦的,这也是后面可以并行对每个token向量同时提炼的基础(2.2.1.2)

3.




式2.6中:


为点积操作,


为query向量的维度,


为相关性系数


这里除以


的原因由于softmax(2.7)是一个会饱和的激活函数,为避免输入进入饱和区域,所以对


的结果进行标准化(论文中假设


为相互独立的


的向量,这个可以由Layer Normalization 去接近。则



的向量)


4.


进行汇总得到最终答案,即经过信息提炼后第一位置的token的新向量



关于在图2.3中Scaled Dot-Product Attention的Mask操作:

因为训练时基本是使用mini batch的方式,这就需要对token数量较少的sequence用<PAD>在尾部填充使得batch里的每个句子长度相同
在Encoder环节去除<PAD>对句子中其他token的影响是在Scaled Dot-Product 结束后紧跟一个mask操作(即对<PAD>的score减去一个极大值---e.g. 1E+9,使得softmax输出的<PAD>token的相关性系数接近 0)

对于没有<PAD>填充的句子则Mask操作就可以忽略,所以Mask操作是optional的

<PAD>不会对其他token向量的信息提炼产生影响

对<PAD>向量再怎么信息提炼还是<PAD>向量

2.2.1.2 Scaled Dot-Product Attention 并行对每个token向量同时提炼

若每个位置上token依次使用Scaled Dot-Product Attention进行信息提炼,即是串行。

上面已经分析出每个token的所准备的回答(key, value)是事先就准备好的,query和


是解耦的。


因此可以同时对每个token向量都生成一个与该token向量相对应的问题(


),将这些问题都汇聚起来组成矩阵一起问 (式2.8),公式如下“




的结果即是各token向量经过提炼后的新向量集



上面操作和2.2.1.1中展示的单例情况完全一样,只是这里式多个一起算所以用的是矩阵操作

2.2.2 Multi-Head Attention

上面Scaled Dot-Product Attention例子已经实现了将每个token所对应的1个query并行计算,从而达到同时对每个token向量进行信息提炼。

Multi-Head更近了一步:可以实现将每个token所对应的h个queries并行计算,从而达到同时对每个token向量进行多方面的信息提炼。


transformer nlp 标注 transformers nlp_信息编码_09

图2.4

Multi-Head思路:从token向量的不同子空间来提取多个问题(Scaled Dot-Product Attention),而每个位置的token向量也是在其子空间内事先准备好回答(key, value),这样h个


,就对应了h组



最后将各个子空间得到的最终答案进行拼接(concat),再经过一个线性变换映射到该token向量原来的空间里。该操作对应的公式如下:



关于Multi-Head我的臆想是:对一个token的不同子空间的提问,就比如我对它提了多个问题(比如有些问题是关于指代关系的, 有些是关于句法的,有些是关于语义的等等),而这些问题只是要提取token的部分信息所以使用子空间。同时既然是对其包含的一部分信息进行提问的话,那用token的部分信息来回答也是显得顺理成章

2.3 Feed-Forward Layer

这里就是将Multi-Head Attention得到的提炼好的向量再投影到一个更大的空间(论文里将空间放大了4倍)在那个大空间里可以更方便地提取需要的信息(使用Relu激活函数),最后再投影回token向量原来的空间


transformer nlp 标注 transformers nlp_Mask_10

图2.5

对应的公式:



这个可以借鉴SVM来理解:SVM对于比较复杂的问题通过将特征其投影到更高维的空间使得问题简单到一个超平面就能解决。这里token向量里的信息通过Feed Forward Layer被投影到更高维的空间,在高维空间里向量的各类信息彼此之间更容易区别,即使像ReLU这样的弟中弟也可以完成提取信息的任务。

3. Position Embedding

其实到目前为止为了方便叙述,一直没有提及一个问题:transformer 无法将token的次序位置信息学习到并编码进向量中。

如果细看为什么大多数Seq2Seq都使用RNN去建模,最主要的原因:通过将token按次序依次将其传入RNN,RNN可以隐含的将token所在的次序位置信息编码进其隐藏状态里

而从Multi-Head Attention结构可以看出来token向量的信息提取是通过Attention机制完成的,无法像RNN一样去隐含的将次序位置信息编码进去

那总不能撒手不管了咯,Google团队的解决方法就是压根不打算在Transformer模块里去学习次序位置信息~( ̄▽ ̄)~*,而是在还没开始前就把这个问题给干掉,即token向量还没进入Transformer Encoder / Decoder 的时候就将次序位置信息编码成向量并直接加到token向量上去,如公式3.1



(WE: word embedding, PE: position embedding)

位置信息使用下面公式进行编码:



其中


为token在sequence里的位置,


为Embedding的维度,


为Embedding中的某一对应维度。


维度的三角函数系中去,每个位置pos对应到这个三角函数系中的一个点。


通过三角函数的和差化积操作后可以被

线性表示

在论文也提及他们希望该编码不仅能编码绝对位置信息,还能学习到相对位置信息,这么看感觉还是有那么点意思的。


4. Transformer Decoder

首先给出Transformer Decoder的结构:


transformer nlp 标注 transformers nlp_transformer nlp 标注_11

图4.1

Transformer Decoder由三个模块组成:

  • Masked Multi-head Attention Layer
  • Enc-Dec Multi-head Attention Layer
  • Feed-Forward Layer

这里看似引出了两个新的Attention Layer,大家不要慌其实这两个Attention Layer和Transformer Encoder里的Multi-head Attention layer结构是一摸一样,只是在某些操作上略有不同(之后会展开)

由于在训练时,我们已经知道需要翻译的译文内容,而在实际翻译时,译文是无法知晓的。所以Transformer Decoder在训练阶段与实际翻译阶段的操作略有不同(原理相同)

为叙述更可能清晰这里将其分开来讲,这里先从实际翻译阶段开始讲述原理,后在training部分再说明其不同之处

4.1 Transformer Decoder in translation

大致流程:在translate阶段,最开始都从同一个起始token开始(这里使用<S>来表示该起始符),之后每次向后预测一个token,如果预测的token为<E>的话就结束翻译。一般情况下还会定义一个最长翻译长度,只要超出这个长度,即使<E>还没出现,也会被强制完成翻译

Transformer Decoder的翻译过程遵循自回归的特性(即在已知翻译出


个token的情况下,预测第


个位置的token)


这里以图1.3中翻译knowledge is power为例(如果不想翻回去看的话,可以看下图4.2)

在t-1时刻已经预测出了 这个token,图中显示的是在t时刻预测token 紧接的next token是哪个。


transformer nlp 标注 transformers nlp_transformer nlp 标注_12

图4.2

Transformer Decoder预测next token可以理解为两步走:

  1. 首先询问之前已经预测出的每个译文token(即[知 识 就 是 力]),了解next token应该具有哪些信息(由Masked Multi-head Attention layer负责)
  2. 再问下已经信息提炼好的源文(即Encoder outputs)进步了解next token应该具有哪些信息(由Enc-Dec Multi-head Attention layer负责)

下面笔者依次进行展开:

4.1.1 Masked Multi-head Attention Layer

在这个模块里query的提问对象是之前所有time step已经预测出的各token向量,这里还是拿图4.2说话,具体步骤:

a. 由前一time step预测出来的


(即

)来生成一个关于next token的 h个问题

(h即是head的个数),同时再生成


对应的

h个回答

,注意这里


的回答和query仍是解耦的


b. 将a.中


(力)生成的

h个回答

添加进之前已经预测得到的译文token向量所对应的回答集合A里


,用a.得到的queries(


)去提问A里的每个token向量,随后步骤和Encoder里的Multi-head Attention Layer一样


在translate阶段 Masked Multi-head Attention Layer中的Mask并没有作用(mask作用主要是为decoder在training阶段服务的),即Masked Multi-head Attention Layer在training和translate阶段的行为略有不同(就比如dropout在训练和预测阶段行为不同)

向量笔者感性地理解为经过咨询已经译出的每个译文token的意见,初步了解next token应该具有哪些信息

4.1.2 Enc-Dec Multi-head Attention Layer

在这个模块里query的提问对象是已经对源文各token提炼好信息的Encoder outputs,具体流程:

a. 由4.1.1得到的输出


向量生成关于next token的新的

h个问题


b. 由Encoder outputs中每个源文token


向量通过线性变换生成其对应的回答集


,将a.得到的queries(


)向回答集


提问,随后步骤和Encoder里的Multi-head Attention Layer一样


其中:Encoder outputs里<PAD>的回答仍会在Enc-Dec Multi-head Attention Layer中子结构Scaled Dot-Product Attention的Softmax操作之前被mask掉, 即源文中的<PAD>也不会在对翻译next token产生影响

向量笔者感性地理解为经过咨询源文的每个token的意见,进步知道这下一个该译出的token应该具有哪些信息

c. 最后的Feed-Forward Layer和Transformer Encoder里的Feed-Forward Layer完全一样

如此经过多层Transformer Decoder后,将最后一层得到的结果向量通过一个线性变换映射到译文的词库大小,用softmax去找哪个译文token和该向量最匹配就得到了next token的预测了。

注:上面例子展示的是transformer Decoder在翻译中的步骤,而在实际翻译中并不会使用这么vanilla的操作,基本会使用beam search方法来降低逐字翻译而产生的exposure bias。因为这个技巧和Transformer无直接关联,本文就不做介绍了,感兴趣的可以自行深入了解

4.2 Transformer Decoder in training

Transformer Decoder在训练阶段和翻译阶段其原理是一样的,只是使用了一个巧妙的技巧实现了并行训练(Transformer Decoder在翻译阶段每次只向后预测一个token,是串行)

在training阶段由于已经事先知道了正确的译文内容,因此可以同时模拟已知预测出前


个译文token情况下,训练decoder去预测第


个译文token是哪个的任务(


),如图4.3


transformer nlp 标注 transformers nlp_多层transformer结构_13

图4.3

(图4.3是为了更直观的方式来展现该技巧要达到的效果所以将Mask操作表现为在transformer decoder外部。实际Mask操作是发生在Transformer Decoder的Masked Multi-head Attention Layer里)

图中每个模拟中必须对该模拟环境中该时刻应该“未知”的译文token 进行mask操作(就比如4.1的例子在图4.3里就是把 给mask掉)

Masked Multi-head Attention Layer里的mask操作和Transformer Encoder里隐去<PAD>的Mask操作非常像:

具体:将模拟中不该出现的“未来”token在Scaled Dot-Product Attention的softmax操作之前,对这些“未来”token对应的score减去一个极大数,使得这些“未来”token在预测next token是发挥不了作用

这里可以感性地理解学生在做练习卷的时候不把对应的答案泄露给学生

除此之外,在后面的Enc-Dec Multi-head Attention Layer 和 Feed Forward Layer里Transformer Decoder表现与翻译阶段并无不同,只是通过矩阵操作将Transformer Decoder预测逐个位置的译文token的任务变成了并行版本。

到此整个Transformer的介绍就结束啦


后语

一开始计划写这篇文章的时候本打算10个小时内搞定的,后来发现越写越难写,想要做到深入浅出真的非常难,若是简单地将公式放上来,劈里啪啦地讲一通,估计连自己看都会懵,更何谈分享?于是乎就加入了些自己的理解,后来越加越多= = ,到最后洋洋洒洒写了23,000+字,为了更便于理解还画了很多图,最后总耗时接近60个小时,这也是我不太写这类文章的原因,太耗时间和精力了,如果随便写写的话,自己这关又过不去。

大家若仍有什么想不明白的,强烈建议去读 tensorflow/models仓库里Transformer的代码实现。文章总归难以涵盖所有,而代码却可以,有些问题笔者回答不了,而代码却可以。

最后如果你觉得这篇文章写的还不错,是你需要的那款,帮你更好的认识了这位变形金刚,你可以通过点赞或者打赏来鼓励一下笔者^_^


修改日志

2019-01-09

  • 修正了Enc-Dec Multi-head Attention Layer中Encoder outputs描述的勘误

2018-12-31

  • 修改了 Transformer Decoder 中的Masked Multi-head Attention Layer & Enc-Dec Multi-head Attention Layer的描述,使其更易于理解