NLP学习笔记(八)注意力机制(Attention)

234 阅读6分钟

「这是我参与2022首次更文挑战的第29天,活动详情查看:2022首次更文挑战」。

导语

本文为我之前在CSDN平台上的一篇博客记录。原链接为:NLP学习笔记(八)注意力机制(Attention)

注意力机制(Attention)

这节课我们学习注意力机制(Attention),它可以大幅度提升机器翻译的效果。

Revisiting Seq2Seq Model(复习Seq2seq模型)

我们首先来复习一下上节课的内容。 在这里插入图片描述

seq2seq模型由一个编码器Encoder和一个解码器Decoder组成。 Encoder输出最后一个状态向量hmh_m作为对之前全部输入的总结。Decoder RNN的初始状态s0=hms_0=h_m,通过hmh_m,Decoder就知道了这句话的信息。然后Decoder就像一个文本生成器一样,逐字进行翻译。 在这里插入图片描述

可惜Seq2seq模型有一个明显的缺陷就是当输入句子很长时,Encoder会记不住完整的句子,Encoder最后一个状态可能会漏掉一些信息。

如果拿Seq2seq模型做机器翻译,会得到这样的结果。 在这里插入图片描述

横轴是句子长度,纵轴是BLUE score是评价机器翻译好坏的标准,值越高说明越准确。 从上图可以看到,如果不用Attention,大概20个词后,BLUE score就会往下降。用Attention后,即使句子很长也不会明显下降。

Attention for Seq2Seq Model(在Seq2seq模型上应用Attention)

在这里插入图片描述 Attention是2015的ICLR上的这篇论文提出的,使用Attention后,Decoder每次在更新状态时都会再看一眼Encoder的所有状态,这样就不会遗忘。Attention还可以告诉Decoder应该关注Encoder的哪个状态,这也是Attention名字的由来。Attention可以大幅度提高准确率,但缺点是计算量很大。

下面简要介绍Attention的原理。 在这里插入图片描述

在Encoder结束工作之后,Attention和Decoder同时开始工作。

在这里插入图片描述 Decoder的初始状态是Encoder的最后一个状态s0=hms_0=h_m,我们同时保留Encoder的所有状态,然后计算s0s_0和每一个状态的相关性。我们用

αi=align(hi,s0)\alpha_i = align(h_i, s_0)

来表示Encoder第i个状态和Decoder第0个状态的相关性。将结果记为αi\alpha_i,被称为权重Weight。 在这里插入图片描述

Encoder有m个状态,所以一共算出m个α\alpha,它们都是介于0到1之间的实数并且加和为1。下面我么来看一下具体怎么计算α\alpha

有很多方法可以用来计算hih_is0s_0的相关性。第一种方法如下:

在这里插入图片描述 我们首先把hih_is0s_0做concatenation得到一个更大的向量。然后求矩阵W和这个向量的乘积,得到一个向量。再把双曲正切函数应用到向量的每一个元素上,把每一个元素值都压缩到-1到+1之间。双曲正切函数的输出还是一个向量。最后计算向量v和刚算出来的向量的内积。两个向量的内积是个实数,记为α~i\widetilde{\alpha}_i

这里的矩阵W和向量v都是参数,需要从训练数据里学习。

在这里插入图片描述

算出这些α~1,α~2,,α~m\widetilde{\alpha}_1, \widetilde{\alpha}_2, \cdots, \widetilde{\alpha}_m之后,对他们进行Softmax变换。把输出结果记为α1,,αm\alpha_1, \cdots, \alpha_m。这种计算方法是Attention第一篇论文中所提出的。在这之后,有其他很多论文提出了很多计算Attention的方法。

下面介绍一个更流行的方法。 在这里插入图片描述 输入还是hih_is0s_0向量,第一步是分别用两个参数矩阵WKW_KWQW_Q对两个输入做线性变换。得到kik_iq0q_0两个向量。这两个参数矩阵要从训练数据中学习。

第二步是计算这两个向量的内积,把结果记为α~i\widetilde{\alpha}_i。得到m个结果。

第三步是对得到的结果做Softmax变换,把输出结果记为α1,,αm\alpha_1, \cdots, \alpha_m

这种方法被Transformer模型采用。

在这里插入图片描述

以上讲了两种计算hih_is0s_0的相关性的方法,随便使用哪种方法都会得到m个α\alpha值,这些α\alpha被称为权重,每个α\alpha对应一个hh。利用这些权重α\alpha,我们可以对这些状态向量求加权平均,结果称为context vector记为c0c_0。每一个Context vector cc都会对应一个状态ss。这里c0c_0对应s0s_0

Decoder读入向量xx^{\prime},然后需要把状态更新为s1s_1。那么具体该如何计算呢?我们先来回顾一下,假如不用Attention,那么Simple RNN是这样更新状态的。 在这里插入图片描述 新的状态s1s_1是旧的状态s0s_0和输入x1x^{\prime}_1的函数。公式如图所示,把s0s_0x1x^{\prime}_1做concatenation,然后乘到参数矩阵AA^{\prime}上,加上偏置Intercept,然后做双曲正切变换后得到状态s1s_1。Simple RNN在更新状态时只需要知道旧的状态s0s_0和输入x1x^{\prime}_1,而并不会去看Encoder的状态。

而使用Attention后,则会用到Context vector c0c_0在这里插入图片描述 在计算过程中需要把s0s_0x1x^{\prime}_1c0c_0做concatenation。之后计算得到状态s1s_1。而c0c_0是知道Encoder的所有状态的,这样一来就解决了长句子遗忘的问题。

下一步我们跟之前一样的计算方式来计算c1c_1

在这里插入图片描述 注意:这里的α\alpha是需要重新计算得到的。而不能用上一轮的值。

有了α\alpha之后就可以计算c1c_1,也是用同样的加权平均。

在这里插入图片描述

之后计算s2s_2在这里插入图片描述

以此类推,全部计算。

在这里插入图片描述

在计算过程中,我们需要计算很多的α\alpha,思考一下,为了完成计算所有的c,一共计算了多少α\alpha呢? 在这里插入图片描述

想要计算一个Context vector cc,需要把Decoder的当前状态输出ss和Encoder所有m个状态做对比来计算出m个权重α1,,αm\alpha_1, \cdots, \alpha_m。而Decoder每一步都会计算m个α\alpha,所以假设Decoder一共运行了t步后,共计算出了mtm*t个权重。所以Attention的时间复杂度是mtm*t,也就是Encoder与Decoder状态数量的乘积。这个时间复杂度是很高的。

Attention避免遗忘,大幅度提高了准确率。但是代价是巨大的计算。下面举个例子来说明权重α\alpha的实际意义。

在这里插入图片描述

上图下面是Encoder,输入为英语。上面是Decoder,输出为法语。Attention会把Encoder每个状态和Decoder每个状态作对比。得到两者相关性,也就是权重α\alpha。在图中,用线连接Encoder和Decoder的每个状态,每条线给与一个权重α\alpha,粗线表示α\alpha很大,细的表示很小。

在这里插入图片描述

例如,图中法语“zone”和英语“area”有很粗的线相连,这条线有很直观的解释。法语里的“zone”就是英语的“area”,在翻译时,Decoder都会看一遍所有的Encoder的状态,而Attention则告诉Decoder重点关注哪些部分。这也是Attention名字的由来。

Summary(总结)

在这里插入图片描述 标准的Seq2seq在面对长句子时会忘记之前的状态。而使用Attention之后,每次Decoder都会再看一遍Encoder的所有信息,并不会遗忘。

除了解决遗忘问题,Attention还可以告诉Encoder重点关注哪个单词。

Attention可以大幅度提高 翻译准确度。但缺点是时间复杂度太高了。