Transformer

尽管LSTM能够缓解RNN的循环导致的远程信息丢失的问题,但潜在问题任然存在。通过扩展的一系列循环连接向前传递信息会导致相关信息的丢失和训练上的困难。此外,循环网络的固有顺序性质抑制了并行计算资源的使用。transformers:是一种序列处理方法,其消除了循环连接

本文参考资料:

http://jalammar.github.io/illustrated-transformer/

https://blog.csdn.net/longxinchen_ml/article/details/86533005

https://blog.csdn.net/qq_37394634/article/details/102679096

https://www.kancloud.cn/drxgz/slp20201230#/catalog 第九章

1 self-attention

Transformers的关键创新是使用了自注意力(self-attention)层,自注意力允许网络直接从任意大的上下文提取和使用信息,而无需像RNN中那样通过中间的循环连接来传递信息

1.1 从宏观视角看自注意力机制

例如,下列句子是我们想要翻译的输入句子:

The animal didn’t cross the street because it was too tired

这个“it”在这个句子是指什么呢?它指的是street还是这个animal呢?这对于人类来说是一个简单的问题,但是对于算法则不是。

当模型处理这个单词“it”的时候,自注意力机制会允许“it”与“animal”建立联系。

随着模型处理输入序列的每个单词,自注意力会关注整个输入序列的所有单词,帮助模型对本单词更好地进行编码。

如果你熟悉RNN(循环神经网络),回忆一下它是如何维持隐藏层的。RNN会将它已经处理过的前面的所有单词/向量的表示与它正在处理的当前单词/向量结合起来。而自注意力机制会将所有相关单词的理解融入到我们正在处理的单词中。

1.2 从微观视角看自注意力机制

计算自注意力的第一步就是从每个编码器的输入向量(每个单词的词向量)中生成三个向量。也就是说对于每个单词,我们创造一个查询向量、一个键向量和一个值向量。这三个向量是通过词嵌入(即Embedding层)与三个权重矩阵后相乘创建的。

可以发现这些新向量在维度上比词嵌入向量更低。他们的维度是64,而词嵌入和编码器的输入/输出向量的维度是512. 但实际上不强求维度更小,这只是一种基于架构上的选择,它可以使多头注意力(multiheaded attention)的大部分计算保持不变

在上图中,q为查询向量,k为键向量,v为值向量,Wq、Wk、Wv为随机初始化的映射矩阵

什么是查询向量、键向量和值向量?

计算自注意力的第二步是计算得分。假设我们在为这个例子中的第一个词“Thinking”计算自注意力向量,我们需要拿输入句子中的每个单词对“Thinking”打分。这些分数决定了在编码单词“Thinking”的过程中有多重视句子的其它部分。

这些分数是通过打分单词(所有输入句子的单词)的键向量与“Thinking”的查询向量相点积来计算的。所以如果我们是处理位置最靠前的词的自注意力的话,第一个分数是q1和k1的点积,第二个分数是q1和k2的点积。

第三步和第四步是将分数除以8(8是论文中使用的键向量的维数64的平方根,这会让梯度更稳定。这里也可以使用其它值,8只是默认值),然后通过softmax传递结果。softmax的作用是使所有单词的分数归一化,得到的分数都是正值且和为1。

这个softmax分数决定了每个单词对编码当下位置(“Thinking”)的贡献。显然,已经在这个位置上的单词将获得最高的softmax分数,但有时关注另一个与当前单词相关的单词也会有帮助。

第五步是将每个值向量乘以softmax分数(这里的直觉是希望关注语义上相关的单词,并弱化不相关的单词)。第六步是将加权的向量求和,最终的结果z就是x1这个单词的Attention向量。

当同时计算所有单词的Attention时,图示如下:

  1. 将输入词向量转换为Q、K、V.

X矩阵中的每一行对应于输入句子中的一个单词

  1. 直接计算Z

2 Multi-Head Attention

不同的随机初始化映射矩阵Wq,Wk,Wv可以将输入向量映射到不同的子空间,这可以让模型从不同角度理解输入的序列。因此同时几个Attention的组合效果可能会优于单个Attenion,这种同时计算多个Attention的方法被称为Multi-Head Attention,或者多头注意力。

每个“Head”都会产生一个输出向量z,但是我们一般只需要一个,因此还需要一个矩阵把多个合并的注意力向量映射为单个向量。那该怎么做?其实可以直接把这些矩阵拼接在一起,然后用一个附加的权重矩阵Wo与它们相乘。

整个多头注意力机制图示如下:

既然我们已经摸到了注意力机制的这么多“头”,那么让我们重温之前的例子,看看我们在例句中编码“it”一词时,不同的注意力“头”集中在哪里:

当我们编码“it”一词时,一个注意力头集中在“animal”上,而另一个则集中在“tired”上,从某种意义上说,模型对“it”一词的表达在某种程度上是“animal”和“tired”的代表。

然而,如果我们把所有的attention都加到图示里,事情就更难解释了:

3 使用位置编码表示序列的顺序

到目前为止,我们对模型的描述缺少了一种理解输入单词顺序的方法。

为了解决这个问题,Transformer为每个输入的词嵌入添加了一个向量。这里的直觉是,将位置向量添加到词嵌入中使得它们在接下来的运算中,能够更好地表达的词与词之间的距离。

为了让模型理解单词的顺序,我们添加了位置编码向量,这些向量的值遵循特定的模式

如果我们假设词嵌入的维数为4,则实际的位置编码如下:

位置编码向量如何生成?

import numpy as np
import matplotlib.pyplot as plt
# Code from https://www.tensorflow.org/tutorials/text/transformerdef get_angles(pos, i, d_model):
  angle_rates = 1 / np.power(10000, (2 * (i//2)) / np.float32(d_model))
return pos * angle_rates
def positional_encoding(position, d_model):
  angle_rads = get_angles(np.arange(position)[:, np.newaxis],
                          np.arange(d_model)[np.newaxis, :],
                          d_model)
# apply sin to even indices in the array; 2i  angle_rads[:, 0::2] = np.sin(angle_rads[:, 0::2])
# apply cos to odd indices in the array; 2i+1  angle_rads[:, 1::2] = np.cos(angle_rads[:, 1::2])
  pos_encoding = angle_rads[np.newaxis, ...]
return pos_encoding
tokens = 10
dimensions = 64
pos_encoding = positional_encoding(tokens, dimensions)
print (pos_encoding.shape)
plt.figure(figsize=(12,8))
plt.pcolormesh(pos_encoding[0], cmap='viridis')
plt.xlabel('Embedding Dimensions')
plt.xlim((0, dimensions))
plt.ylim((tokens,0))
plt.ylabel('Token Position')
plt.colorbar()
plt.show()

通过对嵌入向量交替的进行正弦与余弦最终构成如下图所示的位置编码矩阵。句子10个词(行),嵌入向量64维。

4 残差模块

在继续进行下去之前,我们需要提到一个编码器架构中的细节:在每个编码器中的每个子层(自注意力、前馈网络)的周围都有一个残差连接,并且都跟随着一个“层-归一化”步骤。

如果我们去可视化这些向量以及这个和自注意力相关联的层-归一化操作,那么看起来就像下面这张图描述一样:

解码器的子层也是这样样的。如果我们想象一个2 层编码-解码结构的transformer,它看起来会像下面这张图一样:

5 解码组件

既然我们已经谈到了大部分编码器的概念,那么我们基本上也就知道解码器是如何工作的了。但最好还是看看解码器的细节。

编码器通过处理输入序列开启工作。顶端编码器的输出之后会变转化为一个包含向量K(键向量)和V(值向量)的注意力向量集 。这些向量将被每个解码器用于自身的“编码-解码注意力层”,而这些层可以帮助解码器关注输入序列哪些位置合适:

接下来的步骤重复了这个过程,直到到达一个特殊的终止符号,它表示transformer的解码器已经完成了它的输出。每个步骤的输出在下一个时间步被提供给底端解码器,并且就像编码器之前做的那样,这些解码器会输出它们的解码结果 。另外,就像我们对编码器的输入所做的那样,我们会嵌入并添加位置编码给那些解码器,来表示每个单词的位置。

自注意力层在解码器中相较于在编码器中有着些许不同:在解码器中,自注意力层只被允许处理输出序列中更靠前的那些位置。在softmax步骤前,它会把后面的位置给隐去(把它们设为-inf)(这就是mask)

这个“编码-解码注意力层”工作方式基本就像多头自注意力层一样,只不过它是通过在它下面的层来创造查询矩阵,并且从编码器的输出中取得键/值矩阵。

sequence mask

sequence mask有各种各样的形式和设计,最常见的应用场景是在需要一个词预测下一个词的时候,如果用self attention 或者是其他同时使用上下文信息的机制,会导致模型”提前看到“待预测的内容,这显然不行,所以为了不泄露要预测的标签信息,就需要 mask 来“遮盖”它。如下图所示,这也是Transformer中Decoder的Masked Multi-Head self-attention使用的Mask机制

除了在decoder部分加入mask防止标签泄露以外,还有模型利用这种填空机制帮助模型学的更好,比如说BERT和ERNIE模型中利用到的Masked LM(MLM)。(注意:BERT模型只有Transformer的Encoder层,是可以学习上下文信息的

6 最终的线性变换和softmax层

解码组件最后会输出一个实数向量。我们如何把浮点数变成一个单词?这便是线性变换层要做的工作,它之后就是Softmax层

线性变换层是一个简单的全连接神经网络,它可以把解码组件产生的向量投射到一个比它大得多的、被称作对数几率(logits)的向量里。

不妨假设我们的模型从训练集中学习一万个不同的英语单词(我们模型的“输出词表”)。因此对数几率向量为一万个单元格长度的向量——每个单元格对应某一个单词的分数。

接下来的Softmax 层便会把那些分数变成概率(都为正数、上限1.0)。概率最高的单元格被选中,并且它对应的单词被作为这个时间步的输出。

这张图片从底部以解码器组件产生的输出向量开始。之后它会转化出一个输出单词。

7 训练部分总结

既然我们已经过了一遍完整的transformer的前向传播过程,那我们就可以直观感受一下它的训练过程。

在训练过程中,一个未经训练的模型会通过一个完全一样的前向传播。但因为我们用有标记的训练集来训练它,所以我们可以用它的输出去与真实的输出做比较。

为了把这个流程可视化,不妨假设我们的输出词汇仅仅包含六个单词:“a”, “am”, “i”, “thanks”, “student”以及 “eos”(end of sentence的缩写形式)。

我们模型的输出词表在我们训练之前的预处理流程中就被设定好。

一旦我们定义了我们的输出词表,我们可以使用一个相同宽度的向量来表示我们词汇表中的每一个单词。这也被认为是一个one-hot 编码。所以,我们可以用下面这个向量来表示单词“am”:

接下来我们讨论模型的损失函数——这是我们用来在训练过程中优化的标准。通过它可以训练得到一个结果尽量准确的模型。

8 损失函数

比如说我们正在训练模型,现在是第一步,一个简单的例子——把“merci”翻译为“thanks”。

这意味着我们想要一个表示单词“thanks”概率分布的输出。但是因为这个模型还没被训练好,所以不太可能现在就出现这个结果。

因为模型的参数(权重)都被随机的生成,(未经训练的)模型产生的概率分布在每个单元格/单词里都赋予了随机的数值。我们可以用真实的输出来比较它,然后用反向传播算法来略微调整所有模型的权重,生成更接近结果的输出。

你会如何比较两个概率分布呢?我们可以简单地用其中一个减去另一个。更多细节请参考交叉熵和KL散度。

交叉熵:https://colah.github.io/posts/2015-09-Visual-Information/

KL散度:https://www.countbayesie.com/blog/2017/5/9/kullback-leibler-divergence-explained

但注意到这是一个过于简化的例子。更现实的情况是处理一个句子。例如,输入“je suis étudiant”并期望输出是“i am a student”。那我们就希望我们的模型能够成功地在这些情况下输出概率分布:

  • 每个概率分布被一个以词表大小(我们的例子里是6,但现实情况通常是3000或10000)为宽度的向量所代表。

  • 第一个概率分布在与“i”关联的单元格有最高的概率

  • 第二个概率分布在与“am”关联的单元格有最高的概率

  • 以此类推,第五个输出的分布表示“”关联的单元格有最高的概率

这展示的是针对一个样本句子模型训练的目标概率分布

在一个足够大的数据集上充分训练后,我们希望模型输出的概率分布看起来像这个样子:

我们期望训练过后,模型会输出正确的翻译。当然如果这段话完全来自训练集,它并不是一个很好的评估指标(参考:交叉验证,链接https://www.youtube.com/watch?v=TIgfjmp-4BA)。注意到每个位置(词)都得到了一点概率,即使它不太可能成为那个时间步的输出——这是softmax的一个很有用的性质,它可以帮助模型训练。

因为这个模型一次只产生一个输出,假设这个模型只选择概率最高的单词,并把剩下的词抛弃,这是其中一种方法(叫贪心解码)。另外一种方法是保留概率最高的两个单词(例如I和a),那么在下一步里,跑模型两次:其中一次假设第一个位置输出是单词“I”,而另一次假设第一个位置输出是单词“me”,并且无论哪个版本产生更少的误差,位置1、2的结果都被保存。然后我们为第二和第三个位置重复这一步骤。这个方法被称作集束搜索(beam search)。在我们的例子中,集束宽度是2(这意味着在任何时候,两个部分假设(未完成的翻译)都保存在记忆中),top_beams也是2(意味着我们返回了两个翻译结果)。这些都是可以提前设定的参数。

9 展望

我希望通过上文已经让你们了解到Transformer的主要概念了。如果你想在这个领域深入,我建议可以走以下几步:阅读Attention Is All You Need,Transformer博客和Tensor2Tensor announcement,以及看看Łukasz Kaiser的介绍,了解模型和细节。

Attention Is All You Need:https://arxiv.org/abs/1706.03762

Transformer博客:https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html

Tensor2Tensor announcement:https://ai.googleblog.com/2017/06/accelerating-deep-learning-research.html

Łukasz Kaiser的介绍:https://colab.research.google.com/github/tensorflow/tensor2tensor/blob/master/tensor2tensor/notebooks/hello_t2t.ipynb

接下来可以研究的工作:

Depthwise Separable Convolutions for Neural Machine Translation

https://arxiv.org/abs/1706.03059

One Model To Learn Them All

https://arxiv.org/abs/1706.05137

Discrete Autoencoders for Sequence Models

https://arxiv.org/abs/1801.09797

Generating Wikipedia by Summarizing Long Sequences

https://arxiv.org/abs/1801.10198

Image Transformer

https://arxiv.org/abs/1802.05751

Training Tips for the Transformer Model

https://arxiv.org/abs/1804.00247

Self-Attention with Relative Position Representations

https://arxiv.org/abs/1803.02155

Fast Decoding in Sequence Models using Discrete Latent Variables

https://arxiv.org/abs/1803.03382

Adafactor: Adaptive Learning Rates with Sublinear Memory Cost

https://arxiv.org/abs/1804.04235

10 几个问题

10.1 为什么 Decoder 需要做 Mask

  • 训练阶段:我们知道 “je suis etudiant” 的翻译结果为 “I am a student”,我们把 “I am a student” 的 Embedding 输入到 Decoders 里面,翻译第一个词 “I” 时

如果对 “I am a student” attention 计算不做 mask,“am,a,student” 对 “I” 的翻译将会有一定的贡献

如果对 “I am a student” attention 计算做 mask,“am,a,student” 对 “I” 的翻译将没有贡献

  • 测试阶段:我们不知道 “我爱中国” 的翻译结果为 “I love China”,我们只能随机初始化一个 Embedding 输入到 Decoders 里面,翻译第一个词 “I” 时:

无论是否做 mask,“love,China” 对 “I” 的翻译都不会产生贡献

但是翻译了第一个词 “I” 后,随机初始化的 Embedding 有了 “I” 的 Embedding,也就是说在翻译第二词 “love” 的时候,“I” 的 Embedding 将有一定的贡献,但是 “China” 对 “love” 的翻译毫无贡献,随之翻译的进行,已经翻译的结果将会对下一个要翻译的词都会有一定的贡献,这就和做了 mask 的训练阶段做到了一种匹配

总结下就是:Decoder 做 Mask,是为了让训练阶段和测试阶段行为一致,不会出现间隙,避免过拟合

10.2 为什么 Encoder 给予 Decoders 的是 K、V 矩阵

我们在讲解 Attention 机制中曾提到,Query 的目的是借助它从一堆信息中找到重要的信息。

现在 Encoder 提供了 (K_e、V_e) 矩阵,Decoder 提供了 (Q_d) 矩阵,通过 “我爱中国” 翻译为 “I love China” 这句话详细解释下。

当我们翻译 “I” 的时候,由于 Decoder 提供了 (Q_d) 矩阵,通过与 (K_e、V_e) 矩阵的计算,它可以在 “我爱中国” 这四个字中找到对 “I” 翻译最有用的单词是哪几个,并以此为依据翻译出 “I” 这个单词,这就很好的体现了注意力机制想要达到的目的,把焦点放在对自己而言更为重要的信息上。

其实上述说的就是 Attention 里的 soft attention机制,解决了曾经的 Encoder-Decoder 框架的一个问题,在这里不多做叙述,有兴趣的可以参考网上的一些资料。早期的 Encoder-Decoder 框架中的 Encoder 通过 LSTM 提取出源句(Source) “我爱中国” 的特征信息 C,然后 Decoder 做翻译的时候,目标句(Target)“I love China” 中的任何一个单词的翻译都来源于相同特征信息 C,这种做法是极其不合理的,例如翻译 “I” 时应该着眼于 “我”,翻译 “China” 应该着眼于 “中国”,而早期的这种做法并没有体现出,然而 Transformer 却通过 Attention 的做法解决了这个问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值