【NLP】《Attention Is All You Need》的阅读笔记

背景

在深度学习领域,如果你连Transformer都不知,那就太out了。现如今基于Transformer的模型,如Bert在NLP的下游的很多任务中都达到了sota。而这个Transformer就出自于这篇论文。虽然是2017年发表的,但是已经被称作是非常经典的论文了。下面主要是捡一些主要的来看吧。原文地址:Attention Is All You Need
当然本文也参考了这些文章:6 - Attention is All You NeedThe Illustrated Transformer,没有这些文章我理解的也没有那么快。

介绍

之前的文章中已经介绍了attention,注意力帮助提高神经机器翻译等应用到性能。但传统的基于RNN和attention的神经网络,速度还是慢了点。谷歌根据这个现状提出了Transformer–一种利用注意力提高训练速度的模型,这里的attention就是Self-Attention结构,Transformer一个巨大的优点是:模型在处理序列输入时,可以对整个序列输入进行并行计算,不需要按照时间步循环递归处理输入序列。实验结果,不仅提高了训练速度,效果还也得到了比较大的提升。现在就来看看吧。

1. 基本结构

原文中的模型的结构如下图:
在这里插入图片描述
不过我们可以先进行抽象,慢慢细化来看。

我们首先将模型看作一个单独的黑盒子。在机器翻译应用程序中,它将采用一种语言的句子,并输出另一种语言的翻译。

在这里插入图片描述
我们再看看下面这种进一步细化的结构,其中包括编码器部分和解码器部分来联结他们。
在这里插入图片描述
编码组件是一堆编码器(图中六个堆叠在彼此的顶部–数字六没有什么神奇之处,人们肯定可以尝试其他排列方式)。解码组件是相同数量的解码器的堆砌。
在这里插入图片描述
下面再进一步的细化,编码器在结构上都是相同的(但它们不共享权重)。每一层分为两个子层:
在这里插入图片描述
编码器的输入首先通过一个self-attention层——一个帮助编码器在编码特定单词时查看输入句子中其他单词的层。

self-attention层的输出反馈给前馈神经网络(Feed Forward Neural Network, FFNN)。需要注意编码器的输入:假设一个序列用 X = w 1 , w 2 , ⋯   , w n X=w_1,w_2,\cdots,w_n X=w1,w2,,wn表示,第一个encoder输入,则是其对应的embedding之后的结果,有: X = x 1 , x 2 ⋯   , x n X=x_1,x_2\cdots, x_n X=x1,x2,xn,其中 x i ∈ R d x_i\in \mathbb{R}^d xiRd,即每个词用d维的向量表示,经过第一个encoder之后的输出则是:经过self-attention和FFNN之后的向量,其维度与 x i x_i xi相同。

解码器也有编码器的两个层,但在这两层之间有一个注意层,帮助解码器关注输入句子的相关部分。

在这里插入图片描述

2 张量在模型结构中的使用

现在我们已经看到了模型的主要部分,让我们开始看看各种向量/张量,以及它们如何在这些组件之间流动,从而将经过训练的模型的输入转化为输出。

与NLP应用程序的一般情况一样,我们首先使用词嵌入算法将每个输入字转换为向量。假如将每个词转成词向量的维度为4维,实际的代码维度通常是256或者512等。假设以下图表示:
在这里插入图片描述

在输入序列中嵌入单词后,每个单词都会流经编码器的两层。如下:
在这里插入图片描述
在这里,我们开始看到Transformer的一个关键属性,即每个位置的字在编码器中通过自己的路径流动。在self-attention层中,这些路径之间存在依赖关系。然而,前馈层没有这些依赖关系,因此在流经前馈层时,可以并行执行各种路径。

接下来,我们将把示例切换到一个较短的句子,并查看编码器的每个子层中发生了什么。

正如已经提到的,编码器接收向量列表作为输入。
在这里插入图片描述
它通过将这些向量传递到“self-attention”层,然后传入前馈神经网络,然后将输出向上发送到下一个编码器来处理。

3 详细了解self-attention

让我们首先看看如何使用向量计算self-attention,然后继续看它是如何实际实现的——使用矩阵,深刻理解数据的流向。

计算self-attention的第一步是从编码器的每个输入向量中创建三个向量(在本例中,输入是每个单词的嵌入)。因此,我们为每个单词创建一个查询向量、一个键向量和一个值向量。这些向量是通过将嵌入乘以我们在训练过程中训练的三个矩阵来创建的。

这些新向量的维数小于嵌入向量。它们的维数为64,而嵌入和编码器输入/输出向量的维数为512,当然图中显示的是4。

那么问题来了,query,key,value向量到底是什么呢,使用的意义是什么?提前透漏:他们是三个参数矩阵,是需要学习的。
假如Thinking、Machines这两个单词经过Embedding后得到向量是 x 1 , x 2 x_1,x_2 x1,x2,那么 q 1 = x 1 W Q , q 2 = x 2 W Q q_1=x_1W^Q,q_2=x_2W^Q q1=x1WQ,q2=x2WQ,同理可得 k 1 , k 2 , v 1 , v 2 k_1,k_2,v_1,v_2 k1,k2,v1,v2,attention的计算逻辑:query和key计算attention得分,然后根据attention得分进行加权求和(在seq2seq中的attention是两个语句之间的,现在是计算当前语句内各词语之间的相似得分,然后再使用softmax,所以就起名叫self-attention吗?)。具体如何计算 self-attention如下:

计算self-attention的第二步是计算分数,当然第一步是获取对应的q,k,v向量值了。假设我们正在计算例子中第一个单词“thinking”的self-attention。我们需要根据这个词对输入句子的每个词进行评分。当我们在某个位置对单词进行编码时,分数决定了对输入句子其他部分的关注程度。

在这里插入图片描述

分数是通过将各个单词的query vector和key vector 的点积与来评分的。操作事例如下:如果我们处理位置1的单词的self-attention,第一个分数将是q1和k1的点积。第二个分数就是q1和k2的点积了。
在这里插入图片描述

第三步和第四步是将分数除以8(论文中使用的key向量维度的平方根64。这会有更稳定的梯度,当然也可以是其他可能的值,但这是默认值),然后通过softmax操作传递结果。Softmax将分数标准化,使其全部为正值,加起来等于1。

在这里插入图片描述
此softmax分数确定每个单词在此位置的表达。很明显,这个位置上的单词将具有最高的softmax分数,但有时关注与当前单词相关的另一个单词会很有用。

第五步是将每个value vector乘以softmax分数(准备将它们相加)。这里的直觉是保持我们想要关注的单词的完整值,并忽略不相关的单词(例如,将它们乘以0.001这样的小数字)。

第六步是对加权值向量求和。这将在该位置(对于第一个单词)生成self-attention层的输出,即 z 1 z_1 z1
在这里插入图片描述
self-attention计算到此结束。结果向量是我们可以发送到前馈神经网络的向量。然而,在实际实现中,这种计算是以矩阵形式进行的,以加快处理速度。现在我们来看一下,我们已经看到了单词级计算的直觉。在实际的实现过程中使用矩阵计算更加方便。

4.self-attention的矩阵计算

第一步是计算Query、Key和Value 的矩阵。我们通过将Embedding值打包到矩阵X中,并将其乘以我们训练的权重矩阵( W Q W^Q WQ W K W^K WK W V W^V WV)来实现这一点。
在这里插入图片描述
最后,由于我们处理的是矩阵,我们可以将第二步到第六步浓缩成一个公式来计算self-attention层的输出。
在这里插入图片描述

5 多头注意力机制

论文中通过添加一种称为“多头”注意力机制,进一步细化了self-attention层。这从两个方面提高了注意层的性能:

  1. 它扩展了模型关注不同位置的能力。在上面的例子中, z 1 z_1 z1包含了一些其他的编码信息,但是它仅仅被当前词所支配。如果我们翻译一句话,比如“The animal didn’t cross the street because it was too tired”,我们会想知道“it”指的是哪个词,这时多头注意力机制就会有所帮助。
  2. 它为注意力层提供了多个“表示子空间”。正如我们接下来将要看到的,对于多头注意力,我们不仅有一组,而且有多组Query/Key/Value 权重矩阵(Transformer使用八个注意力,因此每个编码器/解码器有八组)。这些集合中的每一个都是随机初始化的。在训练之后,每一组注意力权重( W Q , W K , W V W^Q,W^K,W^V WQ,WK,WV)将输入词嵌入(或来自较低编码器/解码器的向量)投影到不同的表示子空间。

在这里插入图片描述
如果我们做上面所述的同样的self-attention计算,只需使用不同的权重矩阵进行八次不同的计算,我们最终得到八个不同的Z矩阵。
在这里插入图片描述
前馈层则不需要八个矩阵——它需要一个矩阵(每个单词对应一个向量)。这时就需要一种方法把这八个元素压缩成一个矩阵。然后将它们乘以一个额外的权重矩阵 W O W^O WO进行一次变换。
在这里插入图片描述
这几乎就是多头self-attention的全部内容。我意识到这是相当多的矩阵。让我试着把它们放在一个视觉上,这样我们就可以在一个地方看到它们

在这里插入图片描述
既然我们已经谈到了多头注意力,那么让我们回顾一下之前的示例,看看在我们的示例语句中对单词“it”进行编码时,不同的注意力头集中在哪里:
在这里插入图片描述

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

到目前为止,模型中缺少的一件事是解释输入序列中单词顺序的方法。

为了解决这个问题,Transformer 在每个输入嵌入中添加一个向量。这些向量遵循模型学习的特定模式,这有助于确定每个单词的位置,或序列中不同单词之间的距离。这里的直觉是,将这些值添加到Embedding中可以在Embedding投影到Q/K/V向量和点积注意力间提供嵌入向量之间有意义的距离。

在这里插入图片描述
如果我们假设嵌入的维度为4,那么实际的位置编码将如下所示:
在这里插入图片描述
论文中位置编码的设计如下:
P E ( p o s , 2 i ) = sin ⁡ ( p o s / 1000 0 2 i / d m o d e l ) P E ( p o s , 2 i + 1 ) = cos ⁡ ( p o s / 1000 0 2 i / d m o d e l ) \mathbf{PE}(pos, 2i) = \sin (pos/10000^{2i/d_{model}})\\ \mathbf{PE}(pos, 2i + 1) = \cos(pos/10000^{2i/d_{model}}) PE(pos,2i)=sin(pos/100002i/dmodel)PE(pos,2i+1)=cos(pos/100002i/dmodel)
其中pos表词的位置, d m o d e l d_{model} dmodel表示位置编码向量维度, i ∈ [ 0 , d m o d e l ) i\in[0, d_{model}) i[0,dmodel)代表位置 d m o d e l d_{model} dmodel维向量的第 i i i维。

7 残差连接

在继续之前我们需要提到的编码器架构中的一个细节是,每个编码器中的每个子层(self-attention,ffnn)在其周围都有一个残差连接,然后是一个层进行归一化步骤。
在这里插入图片描述
如果我们要可视化与 self attention 相关的向量和 layer-norm 操作,它看起来像这样:
在这里插入图片描述
这也适用于解码器的子层。如果我们想一个由 2 个堆叠编码器和解码器组成的 Transformer,它看起来像这样:
在这里插入图片描述

8 解码器

现在我们已经涵盖了编码器方面的大部分概念,我们基本上知道解码器的组件是如何工作的。让我们来看看它们是如何协同工作的。

编码器首先处理输入序列。然后将顶部编码器的输出转换为一组注意向量 K 和 V。这些将由每个解码器在其“编码器-解码器注意力”层中使用,这有助于解码器专注于输入序列中的适当位置:

请添加图片描述

以下步骤重复该过程,直到出现一个特殊符号,表明转换器解码器已完成其输出。每一步的输出在下一个时间步被馈送到底部的解码器,解码器就像编码器一样冒泡它们的解码结果。就像我们对编码器输入所做的那样,我们将位置编码嵌入并添加到这些解码器输入中,以指示每个单词的位置。
请添加图片描述
解码器中的自注意力层的操作方式与编码器中的方式略有不同:

  1. 在解码器里,Self Attention 层只允许关注到输出序列中早于当前位置之前的单词。具体做法是:在 Self Attention 分数经过 Softmax 层之前,屏蔽当前位置之后的那些位置(将attention score设置成-inf)。
  2. 解码器 Attention层是使用前一层的输出来构造Query 矩阵,而Key矩阵和 Value矩阵来自于编码器最终的输出。

9 线性层和softmax

解码器堆栈输出一个浮点向量。我们如何把它变成一个词?这是最后一个线性层的工作,后面是一个 Softmax 层。线性层是一个简单的全连接神经网络,它将解码器堆栈产生的向量投影到一个更大的向量中,称为 logits 向量。

假设我们的模型知道从训练数据集中学习到的 10,000 个独特的英语单词(我们模型的“输出词汇”)。这将使 logits 向量有 10,000 个单元格宽——每个单元格对应一个唯一单词的分数。这就是我们如何解释模型的输出,然后是线性层。然后,softmax 层将这些分数转化为概率(全部为正,全部加起来为 1.0)。选择概率最高的单元格,并生成与其关联的单词作为该时间步的输出。

在这里插入图片描述

模型训练

在训练期间,未经训练的模型将通过完全相同的前向传递。但是由于我们是在一个带标签的训练数据集上训练它,我们可以将它的输出与实际正确的输出进行比较。

为了可视化,假设我们的输出词汇表只包含六个单词(“a”、“am”、“i”、“thanks”、“student”和“<eos>”(“end of sentence”的缩写))
在这里插入图片描述
一旦我们定义了我们的输出词汇表,我们就可以使用一个相同宽度的向量来表示我们词汇表中的每个单词。这也称为 one-hot 编码。因此,例如,我们可以使用以下向量表示单词“am”:

在这里插入图片描述

损失函数选取

假设我们正在训练我们的模型。假设这是我们在训练阶段的第一步,我们正在通过一个简单的例子来训练它——将“merci”翻译成“thanks”

这意味着,我们希望输出是一个概率分布,表示“thanks”这个词。但由于这个模型还没有经过训练,所以这还不太可能发生。
在这里插入图片描述
那我们要怎么比较两个概率分布呢?:我们可以简单的用两组概率向量的的空间距离作为loss(向量相减,然后求平方和,再开方),当然也可以使用交叉熵(cross-entropy)]和KL 散度(Kullback–Leibler divergence)。

解码

但请注意,这是一个过于简单的示例。更现实的是,我们会使用一个比一个词长的句子。例如——输入:“je suis étudiant”,预期输出:“i am a student”。这真正意味着,我们希望我们的模型能够连续输出概率分布,其中:

  • 每次输出的概率分布都由一个大小为 vocab_size 的向量表示(在示例中为 6,但更实际的是一个数字,例如 30,000 或 50,000)
  • 第一个概率分布在与单词“i”相关的单元格中具有最高概率
  • 第二个概率分布在与单词“am”相关的单元格中具有最高概率
  • 依此类推,直到第五个输出分布指示“”符号,它也有一个来自 10,000 个元素词汇表的单元格与之关联。
    在这里插入图片描述
    在足够大的数据集上训练模型足够的时间后,我们希望生成的概率分布如下所示:
    在这里插入图片描述

现在,因为模型一次产生一个输出,我们可以假设模型正在从该概率分布中选择具有最高概率的单词并丢弃其余的单词。这是一种方法(称为贪婪解码),除此之外还有一种叫做beam search:每个时间步保留k个最高概率的输出词,然后在下一个时间步,根据上一个时间步保留的k个词来确定当前应该保留哪k个词。假设k=2,第一个位置概率最高的两个输出的词是”I“和”a“,这两个词都保留,然后根据第一个词计算第2个位置的词的概率分布,再取出第2个位置上2个概率最高的词。对于第3个位置和第4个位置,我们也重复这个过程。这种方法称为集束搜索(beam search)。

总结

上面的内容就是transformer的核心内容了。前前后后内容也不少,需要我们有一定的基础。 如果不懂,这么好的论文是值得回去多看几遍的。因为其是NLP划时代模型BERT的基础。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

智绘山河

你的鼓励可能解决你下一个问题

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值