引言

本文从Seq2Seq模型的思想开始,一步一步剖析Transformer的原理。

Transformer是什么

Transformer是一种seq2seq模型,那什么是seq2seq。

Seq2Seq是什么

李宏毅自然语言处理——Transformer_聊天机器人

seq2seq模型就是输入是一个序列,输出也是一个序列的模型。输入的序列长度是可变的,同时输出序列的长度是由机器决定的。

Seq2Seq的应用

常见的应用由语言识别、机器翻译、语言翻译(输入的是英文语言,输出的直接是中文文字)。

语音翻译的应用场景还挺多,比如将粤语翻译成中文。像粤语这种语言很多时候是没有对应的文字的,所以直接将粤语语音训练成中文文字,是一个很好的想法,只要把粤语语音对应的中文文字一起喂给模型“硬Train一发”就可以了。

李宏毅自然语言处理——Transformer_Transformer_02

反过来还可以合成语音,比如输入文字,合成台湾语言。

一个重要的应用是用于训练聊天机器人。

李宏毅自然语言处理——Transformer_归一化_03

除了聊天机器人,seq2seq模型还可以实现很多自然语言处理应用。

李宏毅自然语言处理——Transformer_Seq2Seq_04

很多自然语言处理的任务都可以看成是QA(问答)任务,比如问机器这段评价是正面还是负面的,机器给出的回答是 正面的。

但是为不同的任务设置不同的模型肯定是更好的,这里就不展开了。

还有一些任务,可能你觉得和seq2seq无关,其实也是可以用seq2seq来实现,比如句法分析。

李宏毅自然语言处理——Transformer_Transformer_05

但是句法分析的输出是一种树状结构,看起来不像一个序列啊。但是可以把树状结构强行看成是序列。

李宏毅自然语言处理——Transformer_Transformer_06

比如多加一些括号,把树状结构转换成一段字符串就可以了!

李宏毅自然语言处理——Transformer_聊天机器人_07

还可以处理多标签分类问题,就是一个样本可能同时属于多个类别的问题。

那这种任务的输出如何转换成序列呢? 还是用seq2seq硬Train一发,让机器自己决定输出几个类型。

Seq2Seq结构

李宏毅自然语言处理——Transformer_语音识别_08

通常Seq2Seq模型会分成两部分,一个是Encoder另一个是Decoder。

现在最流行的Seq2Seq模型就是本文的主角——Transformer。

李宏毅自然语言处理——Transformer_Seq2Seq_09

我们后面会讲,先来看一下Seq2Seq模型中Encoder部分。

Encoder

李宏毅自然语言处理——Transformer_Seq2Seq_10

Encoder(编码器)要做的事情就是给一排向量,输出一排向量。

像RNN、CNN等都能完成这个任务。而在Transformer中用的是Self-attention来实现的。

李宏毅自然语言处理——Transformer_Transformer_11

Transformer的Encoder乍看起来非常复杂,不要灰心,我们一步一步来理解它。

李宏毅自然语言处理——Transformer_Seq2Seq_12

首先它是由很多个Block组成的,每个Block都是输入一排向量,输出一排向量。最下面的Block的输出又可以作为它上面那个Block的输入,这样叠加起来。最上面一个Block就会输出最终的向量序列。

李宏毅自然语言处理——Transformer_语音识别_13

就像叠“罗汉”一样。

而每个Block里面是由好几层组成的。

李宏毅自然语言处理——Transformer_Seq2Seq_14

在Transformer的Encoder里面,每一个Block做的事情是,一排向量,输入到Self-attention中,然后得到另外一排向量,然后喂给全连接网络(FC),再得到Block的输出。

其实上面是一个简化版的描述,实际上在Transformer里面,做的事情要更复杂。

李宏毅自然语言处理——Transformer_聊天机器人_15

在输入到Self-attention后,得到一排向量。然后Transformer加入了一种设计,把Transformer得到的输出(李宏毅自然语言处理——Transformer_归一化_16)再加上原来的输入向量(李宏毅自然语言处理——Transformer_Seq2Seq_17),得到一个新的向量。其实这种架构就是残差连接(residual connection)。

得到新的向量之后,再做一个归一化,这里用的是层归一化(​​Layer Normalization​​​)。它做的事情是,计算输入向量的均值李宏毅自然语言处理——Transformer_聊天机器人_18和标准差李宏毅自然语言处理——Transformer_归一化_19,然后用公式李宏毅自然语言处理——Transformer_语音识别_20就得到新的输出。

然后才把这个输出当成FC的输入,然后这里也有残差的架构。

李宏毅自然语言处理——Transformer_聊天机器人_21

把FC的输出和输入加起来,再做一个层归一化,才是这个Block的输出。

现在我们再回过头来看下面这个架构。

李宏毅自然语言处理——Transformer_语音识别_22

首先是根据输入得到输入的嵌入向量,然后加上Positional Encoding,以获得位置的信息,然后输入给一个Multi-Head Self-attention,然后上面黄色的“Add & Norm”是残差(Residual)+层归一化(Layer norm)的意思。然后得到的输出经过FC,再做一次残差+层归一化才是这个Block的输出。

然后这个Block会叠加李宏毅自然语言处理——Transformer_Transformer_23次。

可能你会想,为什么Transformer非要这样设计呢?不这样设计不行吗?

李宏毅自然语言处理——Transformer_聊天机器人_24

不这样设计也是可以的,刚才讲的设计是原始论文(Attenion is all you need)的设计。

就有人在​​On Layer Normalization in the Transformer Architecture​​​中提出了一种不同的设计,上图左边是原始的设计,右边是他的改动,他把残差+层归一化后喂给FC的顺序改成,先计算残差得到一个向量李宏毅自然语言处理——Transformer_归一化_16,然后用李宏毅自然语言处理——Transformer_归一化_16向量做层归一化喂给FC,把FC的输出加上李宏毅自然语言处理——Transformer_归一化_16做残差。这种结果据说还比较好。

又有人在​​PowerNorm: Rethinking Batch Normalization in Transformers​​中提出据说比层归一化更强大的归一化方式。

Decoder

本小节我们介绍Transformer的Decoder。

Decoder有两种,一种是Autoregressive Decoder,另一种是Non-autoregressive (NAT)

李宏毅自然语言处理——Transformer_Transformer_28

Autoregerssive Decoder

以语音识别为例,输入机器学习的声音信号,输出是一排向量。

然后,我们引入Decoder,它读入Encoder的输入,

李宏毅自然语言处理——Transformer_归一化_29

那Decoder这里如何产生一段文字呢,首先需要给它一个特殊的开始符号。接下来,Decoder就可以输出一个向量

李宏毅自然语言处理——Transformer_Seq2Seq_30

这个向量的大小和词典大小一样,如果加上Softmax就可以看成是输出每个词汇的概率。

李宏毅自然语言处理——Transformer_Transformer_31

然后一般取概率最高(GreedySearch)的当成此刻的输出,这里是“机”,然后将这个“机”输入到Decoder中,此时Decoder根据前面的两个输入又生成一个输出,取最最大概率对应的单词,假设是“器”,接着把“器”再输入到Decoder,又会产生一个新的单词,依此类推。

李宏毅自然语言处理——Transformer_Transformer_32

从上面的图示可以看到,Decoder的输出很依赖于它的输入,而它的输入又上上一步的输出。如果某出错了,比如输出了“气”,那么可能会导致后面的结果都是错的。这既是一步错,步步错。这个问题我们先不探讨。

我们来看一下Transfomer中Decoder内部的结构。

李宏毅自然语言处理——Transformer_Seq2Seq_33

看起啦Decoder也是很复杂,我们上面学习过Encoder,我们把它们放到一起,比较一下异同。

李宏毅自然语言处理——Transformer_归一化_34

我们会发现,如果盖住Decoder中间那一块,其实它们两的差别不大。

有一个稍微不一样的地方是,Encoder那里是Multi-Head Attention,而Decoder是Masked Multi-Head Attention。

加了这个Masked是什么意思呢?

李宏毅自然语言处理——Transformer_归一化_35

我们原来的Self-attention的输出都是看过所有的输入得到的,那么加了Masked的意思就是现在不能看右边的输入。

李宏毅自然语言处理——Transformer_Seq2Seq_36

就是产生李宏毅自然语言处理——Transformer_归一化_37的时候,只能看到输入李宏毅自然语言处理——Transformer_聊天机器人_38,不能看到李宏毅自然语言处理——Transformer_语音识别_39

产生李宏毅自然语言处理——Transformer_聊天机器人_40的时候,只能看到李宏毅自然语言处理——Transformer_Seq2Seq_41,不能看到李宏毅自然语言处理——Transformer_Transformer_42

产生李宏毅自然语言处理——Transformer_聊天机器人_43的时候不能看到李宏毅自然语言处理——Transformer_Seq2Seq_44

产生李宏毅自然语言处理——Transformer_Seq2Seq_45的时候,可以看到所有的输入李宏毅自然语言处理——Transformer_语音识别_46

李宏毅自然语言处理——Transformer_Seq2Seq_47

具体做法就是,在产生李宏毅自然语言处理——Transformer_聊天机器人_40的时候,只用李宏毅自然语言处理——Transformer_归一化_49李宏毅自然语言处理——Transformer_归一化_50计算attention,而不管李宏毅自然语言处理——Transformer_语音识别_51

那为什么需要加Masked呢?

这其实很直观,假设把Decoder当成生成模型的时候,它只能看到它生成过的东西,未生成的当然是未知的、看不到的。

就和Seq2Seq中Decoder采用的是从左到右的单向RNN而不是双向RNN一个道理。

下面我们来看另一个关键的问题,就是Decoder必须自己决定输出的序列长度。

李宏毅自然语言处理——Transformer_聊天机器人_52

比如,当它产生了“习”之后,可能接着将它作为输入,又会产生一个“惯”,还有可能继续生成下去。

这有点像是在做成语接龙。

为了让它能停下去,输入给它一个特别的符号,让它输出了这个符号后,就停止了。

李宏毅自然语言处理——Transformer_语音识别_53

这个特别的符号就是结束字符,除了开始字符,我们还要添加结束字符。

实际可能需要填充字符和未知字符

李宏毅自然语言处理——Transformer_聊天机器人_54

那么我们希望在把“习”作为输入的时候,Decoder就要输出结束符号。

整个就是Autoregerssive Decoder的原理。

Non-autoregressive

李宏毅自然语言处理——Transformer_语音识别_55

它和Autoregressive(简称AT)的Decoder的区别是什么呢

李宏毅自然语言处理——Transformer_归一化_56

AT现输入开始字符,然后输出一些词汇,最后输出结束字符。

而NAT Decoder它不是一次产生一个词汇,而是一次产生整个句子的词汇。它的输入是一排开始字符,然后一次输出一排词汇,就结束了。

那NAT如何决定输出的长度呢?有一些做法,比如:

  • 另外用一个模型来预测输出长度
  • 输出很长的序列,而忽略结束字符后面的所有输出

NAT的优点是可以并行化,因为一次就可以产生所有的输出。

显然,NAT这种想法是有了Self-attention之后才有的。

但是NAT的Decoder的表现往往不如AT,所以现在是一个热门的主题,很多人想让它的表现和AT一样好,这样子利用它可以并行化的特性,可以极大地加快生成的效率。

Encoder如何传递信息给Decoder

接下来我们要探讨上面盖住的那一块。

李宏毅自然语言处理——Transformer_语音识别_57

这一部分叫作Cross attention,它是连接Transformer中Encoder和Decoder的桥梁。

李宏毅自然语言处理——Transformer_语音识别_58

放一个高清大图,我们看的清楚一点。仔细看的话,可以看到,这一块有两个输入来自Encoder,一个输入来自Decoder。

下面介绍下这个模块的原理。

李宏毅自然语言处理——Transformer_归一化_59

假设给定Encoder一排向量,得到一排输出,然后输入开始字符到Masked Self-attention,得到一个输出。然后

接下来把这个向量乘上一个矩阵,得到query 李宏毅自然语言处理——Transformer_Transformer_60

李宏毅自然语言处理——Transformer_聊天机器人_61

然后把这个李宏毅自然语言处理——Transformer_Transformer_60与Encoder三个输出得到的李宏毅自然语言处理——Transformer_Transformer_63做运算,得到了经过Softmax之后的李宏毅自然语言处理——Transformer_Transformer_64

李宏毅自然语言处理——Transformer_语音识别_65

然后用李宏毅自然语言处理——Transformer_Transformer_64李宏毅自然语言处理——Transformer_聊天机器人_67得到的李宏毅自然语言处理——Transformer_Transformer_68做加权后,得到一个李宏毅自然语言处理——Transformer_归一化_69。整个步骤就叫Cross attention。然后加上后面的过程,假设产生最终输出“机”,

李宏毅自然语言处理——Transformer_语音识别_70

然后把“机”当成Decoder的输入,后面的过程是一样的。

这就是Cross attention的原理。其实这种技术并不是Transformer那篇论文首创的。

李宏毅自然语言处理——Transformer_归一化_71

​Listen, attend and spell​​这篇论文就提出了Cross Attention,那时还没有Self-attention。主要是讨论语音识别的。

也有人尝试不同的Cross Attention方式。

李宏毅自然语言处理——Transformer_归一化_72

李宏毅自然语言处理——Transformer_归一化_73

​Layer-Wise Cross-View Decoding for Sequence-to-Sequence Learning​

在这篇论文中,作者提出并不一定都是将Encoder最上面那层的输出连接到Decoder的某一层,他们做了各种各样的尝试。

Transformer如何训练

我们已经学习了Transformer中的Encoder、Decoder以及它们是如何联动的。

本小节,我们将探讨整个Transformer模型是如何训练的。

李宏毅自然语言处理——Transformer_Transformer_74

即输入一个序列,如何训练,得到另一排输出序列的。

李宏毅自然语言处理——Transformer_归一化_75

如何训练呢,如果是做语言识别。首先我们要收集训练数据,我们要有一大堆语言和对应的文字数据。

那如何让机器做到语音识别呢?

我们知道,当输入“机器学习”这段语音,模型输出的第一个中文应该是“机”。所以当我们把开始符号输入给Decoder时,它的第一个输出应该要与“机”越接近越好。

如何让输出和“机”越接近越好?

目标词“机”会用one-hot向量来表示,而我们的输出经过Softmax之后,是一个概率分布,我们希望这个概率分布和这个one-hot向量越接近越好。

就变成计算它们之间的cross entroy(交叉熵),希望这个cross entroy的值越小越好。

李宏毅自然语言处理——Transformer_Seq2Seq_76

现在,我们已经知道输出是“机器学习”这四个字,加上一个结束符。

我们希望Decoder的输出和这四个字的one-hot向量越接近越好,然后每个Decoder的输出都会和它的目标词的ont-hot向量计算交叉熵,这里加上结束符就会得到5个交叉熵。

我们希望这5个交叉熵越小越好,这样我们就确定了损失函数,就可以梯度下降。

这里还有一个技巧我们要知道,就是我们可以在训练的时候,给Decoder看正确答案。比如输入开始符之后,不管Decoder的输出是什么,强制输入“机”这个字进去,然后期望Decoder输出“器”。当然接着也可以不管它输出什么, 强制输入“器”进去,期望它输出“学”…

这个技巧叫Teacher Forcing。

下面我们看一些其他的技巧。

训练的技巧

Copy Mechanism

有些任务是希望Decoder从输入的序列里面复制出一些东西出来。

比如在聊天机器人会话中,

李宏毅自然语言处理——Transformer_聊天机器人_77

用户输入“你好,我是xxx”,我们当然期望机器人输出“xxx你好…”。

“xxx”这里指的是姓名,这个姓名,如果机器能直接从输入中拿到,那么整个聊天过程就更加自然。

李宏毅自然语言处理——Transformer_归一化_78

来自​​Get To The Point: Summarization with Pointer-Generator Networks​

或者说在自动摘要任务中,让机器读入一篇文章,产生这篇文章的摘要,也需要模型能够复制一些输入信息。

​Incorporating Copying Mechanism in Sequence-to-Sequence Learning​​可以参考这篇论文,了解如何使用Seq2Seq模型实现Copying Mechanism。

Guided Attention

虽然Seq2Seq模型处理序列数据还不错,但有时会犯非常低级的错误

李宏毅自然语言处理——Transformer_语音识别_79

比如在语音合成,让机器合成四次、三次、两次、一次“发财”的语音,前面的都没问题,而在合成一次“发财”语音的时候,合成出的结果省略掉了“发”这个语音。

那么怎么办呢,机器产生了漏字现象,可能是有一些输入机器没有看到,我们希望强迫机器看过输入的每一个东西。

这种方法就是Guided Attention,主要应用于语音合成、语音识别等任务。

李宏毅自然语言处理——Transformer_聊天机器人_80

以语音合成为例,输入是一串文字,因为我们阅读顺序是从左到右,我们也希望机器先看左边的词汇,产生声音;

再看中间的词汇,产生声音;最后看右边的词汇,产生声音。

如果在语音合成时,你发现机器看字的顺序是乱的,比如它先看最后面,再看最前面,最后看的是中间。显然此时有些问题,而Guided Attention做的事情就是强迫机器由左向右进行Attention。

Beam Search

一个很重要的技巧就是束搜索(Beam Search)。

李宏毅自然语言处理——Transformer_聊天机器人_81

以一个只产生两个单词的Decoder为例,Decoder做的事情就是,每次决定取那个单词。假设决定选A,然后把A当作输入,再决定要选那个单词作为输出…

我们上面讲的过程,每次都是选概率(经过Softmax)最大的那个一个,这种策略也有一个名字,叫作贪心解码(Greedy Decoding)。比如上图的红色箭头的路径就是一个例子。

类似贪心搜索策略,每次选择当前最佳,并不见得整体就是最好。我们不妨假设,第一次选择0.4的B,也许接下来输出B的可能性就大增,变成0.9;然后接下来,第三个步骤是B的可能性也是0.9。就是上图绿色箭头的路径。

整体来看的话,红色路径出现的概率是:李宏毅自然语言处理——Transformer_语音识别_82

而绿色路径出现的概率是: 李宏毅自然语言处理——Transformer_聊天机器人_83

这样看来绿色的路径生成的结果更加好。

那我们要如何找到最好的路径呢?

一种方法是暴力搜索所有可能的路径,但是不太现实。

此时,就可以用到Beam Search来找到一个估测是较好的结果。后面会写一篇文字详细探讨Beam Search。

但是Beam Search也并不是总是有效的

李宏毅自然语言处理——Transformer_Seq2Seq_84

上面这篇文字就指出在文本生成中,Beam Search的方法可能每那么有用,还每有纯粹采用随机采用效果好。

如何优化评估指标

李宏毅自然语言处理——Transformer_Seq2Seq_85

以BLEU score为例,它是拿Decoder预测出来的句子和真实句子进行比较,算出得分。

我们训练的时候,最小化的是交叉熵,并且每个词汇是分开考虑的。

而最小化交叉熵不一定能最大化BLEU score。在评估的时候,我们不是选择交叉熵最小的模型,而是选择BLEU score最高的模型。

那我们能不能在训练的时候就去最小化BLEU score的负值,但是BLEU score本身很复杂,它是不能微分的。

此时李教授教大家一个口诀:“遇到优化方法无法解决的问题,用增强学习硬Train一发就对了”。

​Sequence Level Training with Recurrent Neural Networks​

Scheduled Sampling

李宏毅自然语言处理——Transformer_Seq2Seq_86

我们刚才提到过,训练和测试是不一致的。

测试的时候,Decoder看到的是自己的输出,也即是可能会看到一些错误的东西;

但是在训练的时候,Decoder看到的是完全正确的东西。

这个不一致的现象叫作Exposure bias,假设Decoder在训练的时候永远只看到正确的东西,那么在测试的时候,有一个错,就会一步错,步步错。

要解决这个问题,有一个可以尝试的方向即使给Decoder的输入加一些错误的东西,它反而会学到更好。

李宏毅自然语言处理——Transformer_Transformer_87

这个技巧就是​​Scheduled Sampling​​,但是如果应用到Transfomer上的话,其实会影响它并行化的能力。

Transformer就会有一些应用Scheduled Sampling特别的技巧,具体可以参考:

​Scheduled Sampling for Transformers​

​Parallel Scheduled Sampling​

如果你看到这里,那么Transformer就了解完了,接下来可以考虑看原始论文,以及拿这个模型来做一些实现。