跳转至

5 注意力机制介绍

学习目标

  • 了解什么是注意力机制的由来
  • 理解什么是注意力机制
  • 了解常见的注意力类型
  • 了解什么是注意力计算规则以及常见的计算规则
  • 了解什么是注意力机制及其作用
  • 掌握注意力机制的实现步骤

5.1 注意力机制的由来

在认识注意力之前,我们先简单了解下机器翻译任务:

例子:seq2seq(Sequence to Sequence)架构翻译任务

seq2seq模型架构包括三部分,分别是encoder(编码器)、decoder(解码器)、中间语义张量c

  • 编码器:将输入序列编码为一个固定长度的上下文向量(Context Vector->中间语义张量c)。
  • 解码器:基于该上下文向量生成输出序列。

图中表示的是一个中文到英文的翻译:欢迎 来 北京welcome to BeiJing。编码器首先处理中文输入”欢迎 来 北京”,通过GRU模型获得每个时间步的输出张量,最后将它们拼接成一个中间语义张量c;接着解码器将使用这个中间语义张量c以及每一个时间步的隐藏层张量, 逐个生成对应的翻译语言。

早期在解决机器翻译这一类seq2seq问题时,通常采用的做法是利用一个编码器(Encoder)和一个解码器(Decoder)构建端到端的神经网络模型,但是基于编码解码的神经网络存在两个问题:

  • 问题1:如果翻译的句子很长很复杂,比如直接一篇文章输进去,模型的计算量很大,并且模型的准确率下降严重。
  • 问题2:在翻译时,可能在不同的语境下,同一个词具有不同的含义,但是网络对这些词向量并没有区分度,没有考虑词与词之间的相关性,导致翻译效果比较差。

针对这样的问题,注意力机制被提出,最初是为了解决Seq2Seq模型在机器翻译任务中的信息瓶颈长序列问题而提出的。

5.2 注意力机制介绍

注意力机制早在上世纪九十年代就有研究,最早注意力机制应用在视觉领域,后来伴随着2017年Transformer模型结构的提出,注意力机制在NLP,CV相关问题的模型网络设计上被广泛应用。“注意力机制”实际上就是想将人的感知方式、注意力的行为应用在机器上,让机器学会去感知数据中的重要和不重要的部分。

举例说明:当我们看到下面这张图时,短时间内大脑可能只对图片中的“锦江饭店”有印象,即注意力集中在了“锦江饭店”处。短时间内,大脑可能并没有注意到锦江饭店上面有一串电话号码,下面有几个行人,后面还有“喜运来大酒家”等信息。

所以,大脑在短时间内处理信息时,主要将图片中最吸引人注意力的部分读出来了,大脑注意力只关注吸引人的部分, 类似下图所示.

同样的如果我们在机器翻译中,我们要让机器注意到每个词向量之间的相关性,有侧重地进行翻译,模拟人类理解的过程。

5.2.1 什么是注意力机制

用于增强神经网络模型性能的技术

注意力机制(Attention Mechanism)是深度学习中一种重要的技术,它起源于神经科学中的“视觉注意”机制,即人类在处理视觉信息时,能够聚焦于特定区域(例如:某个物体、某个细节等),从而更加高效地处理信息。借用这一思想,注意力机制在深度学习中广泛应用,特别是在处理序列数据(如文本、语音、时间序列等)时,能够帮助模型集中关注输入序列中的关键部分,从而提高模型的性能。

在传统的序列模型(如RNN、LSTM、GRU)中,模型通常通过固定的方式处理输入序列,无法灵活地关注输入序列中的不同部分,这种限制在处理长序列或复杂任务时尤为明显。注意力机制的引入解决了这一问题,它允许模型动态地分配权重,从而更有效地利用输入信息。

注意力机制的核心思想是让模型在处理输入序列时,动态地学习哪些部分应该被重点关注,而不是像传统方法那样对所有部分同等对待。它通过计算每个输入位置的注意力权重,并根据这些权重对输入信息进行加权求和,从而得到一个更加关注重要信息的表示。

注意力机制是一种通用的思想和技术,不依赖于任何模型和数据。换句话说,注意力机制可以用于任何模型和任何模态中。

5.2.2 注意力机制的作用

注意力机制的核心作用是动态地分配权重,从而更好地捕捉输入数据中的重要信息

以下是注意力机制的主要作用:

  • 解决信息瓶颈问题
    • 传统的 Seq2Seq 模型依赖于一个固定长度的上下文向量,导致信息丢失。
    • 注意力机制通过动态地关注输入序列的不同部分,避免了信息瓶颈问题。
  • 捕捉长距离依赖关系
    • 在长序列任务中,传统的 RNN 模型难以捕捉远距离依赖关系。
    • 注意力机制能够直接计算输入序列中任意两个元素之间的关系,从而更好地捕捉长距离依赖。
  • 提高模型的表达能力
    • 注意力机制通过动态分配权重,能够更灵活地处理输入数据。
    • 这种灵活性使得模型能够更好地适应不同的任务和数据分布。
  • 增强模型的可解释性
    • 注意力权重可以直观地反映模型在决策过程中关注了哪些部分。
    • 这种可解释性在NLP和CV任务中尤为重要。

5.3 注意力机制分类

注意力机制通俗来讲就是对于模型的每一个输入项,可能是图片中的不同部分,或者是语句中的某个单词分配一个权重,这个权重的大小就代表了我们希望模型对该部分一个关注程度。这样一来,通过权重大小来模拟人在处理信息的注意力的侧重,有效的提高了模型的性能,并且一定程度上降低了计算量。

深度学习中的注意力机制通常可分为三类: 软注意(全局注意)、硬注意(局部注意)和自注意(内注意)

  • 软注意机制(Soft/Global Attention): 对每个输入项分配的权重为0-1之间,也就是某些部分关注的多一点,某些部分关注的少一点,因为对大部分信息都有考虑,但考虑程度不一样,所以相对来说计算量比较大。
  • 硬注意机制(Hard/Local Attention,[了解即可]): 对每个输入项分配的权重非0即1,和软注意不同,硬注意机制只考虑哪部分需要关注,哪部分不关注,也就是直接舍弃掉一些不相关项。优势在于可以减少一定的时间和计算成本,但有可能丢失掉一些本应该注意的信息。
  • 自注意力机制(Self/Intra Attention,[transformer讲解]): 对每个输入项分配的权重取决于输入项之间的相互作用,即通过输入项内部的”表决”来决定应该关注哪些输入项。和前两种相比,在处理很长的输入时,具有并行计算的优势。

5.3.1 Soft Attention (最常见)

注意力机制是一种通用的思想和技术,不依赖于任何模型,换句话说,注意力机制可以用于任何模型。我们这里只是以文本处理领域的Encoder-Decoder框架为例进行理解。这里我们分别以普通Encoder-Decoder框架以及加Attention的Encoder-Decoder框架分别做对比。

5.3.1.1 普通Encoder-Decoder框架

下图是Encoder-Decoder框架的一种抽象表示方式:

上图图例可以把它看作由一个句子(或篇章)生成另外一个句子(或篇章)的通用处理模型。对于句子对,我们的目标是给定输入句子Source,期待通过Encoder-Decoder框架来生成目标句子Target。Source和Target可以是同一种语言,也可以是两种不同的语言。而Source和Target分别由各自的单词序列构成:

1737772117408

Encoder顾名思义就是对输入句子Source进行编码,将输入句子通过非线性变换转化为中间语义表示C

1737772133224

对于解码器Decoder来说,其任务是根据句子Source的中间语义表示C和之前已经生成的历史信息,\(y_1, y_2…y_{i-1}\)来生成i时刻要生成的单词\(y_i\)

1737772198474

上述图中展示的Encoder-Decoder框架是没有体现出“注意力模型”的,所以可以把它看作是注意力不集中的分心模型。为什么说它注意力不集中呢?请观察下目标句子Target中每个单词的生成过程如下:

1737772218723

其中f是Decoder的非线性变换函数。从这里可以看出,在生成目标句子的单词时,不论生成哪个单词,它们使用的输入句子Source的语义编码C都是一样的,没有任何区别。而语义编码C又是通过对Source经过Encoder编码产生的,因此对于Target中的任何一个单词,Source中任意单词对某个目标单词\(y_i\)来说影响力都是相同的,这就是为什么说图1中的模型没有体现注意力的原因。

5.3.1.2 加Attention的Encoder-Decoder框架

举例说明,为何添加Attention:

比如机器翻译任务,输入Source为:Tom chase Jerry,输出Target为:“汤姆”,“追逐”,“杰瑞”。在翻译“Jerry”这个英文单词的时候,普通Encoder-Decoder框架中,Source里的每个单词对翻译成目标单词“杰瑞”贡献是相同的,很明显这里不太合理,显然“Jerry”对于翻译成“杰瑞”更重要。

如果引入Attention模型,在生成“杰瑞”的时候,应该体现出英文单词对于翻译成当前中文单词不同的影响程度,比如给出类似下面一个概率分布值:(Tom,0.3)(Chase,0.2) (Jerry,0.5)。每个英文单词的概率代表了翻译成当前单词“杰瑞”时,注意力分配模型分配给不同英文单词的注意力大小。

因此,基于上述例子所示, 对于Target中任意一个单词都应该有对应的Source中所有单词的注意力分配概率。而且由于注意力模型的加入,原来在生成Target单词时候的中间语义C就不再是固定的,而是会根据注意力概率变化的C,加入了注意力模型的Encoder-Decoder框架就变成了下图所示:

即生成目标句子单词的过程成了下面的形式:

1737772390686

而每个\(C_i\)可能对应着不同的源语句子单词的注意力分配概率分布,比如对于上面的英汉翻译来说,其对应的信息可能如下:

1737772409226

\(f_2\)函数代表Encoder对输入英文单词的某种变换函数,比如如果Encoder是用的RNN模型的话,这个\(f_2\)函数的结果往往是某个时刻输入后隐藏层节点的状态值;\(g\)代表Encoder根据单词的中间表示合成整个句子中间语义表示的变换函数,一般的做法中,\(g\)函数就是对构成元素加权求和,即下列公式:

1737772430375

\(Lx\)代表输入句子Source的长度, \(a_{ij}\)代表在Target输出第i个单词时Source输入句子中的第j个单词的注意力分配系数, 而\(h_j\)则是Source输入句子中第j个单词的语义编码, 假设\(Ci\)下标i就是上面例子所说的’汤姆’, 那么\(Lx\)就是3, \(h_1\)=f(‘Tom’),\(h_2\)=f(‘Chase’),\(h_3\)=f(‘jerry’)分别输入句子每个单词的语义编码, 对应的注意力模型权值则分别是0.6, 0.2, 0.2, 所以\(g\)函数本质上就是加权求和函数, 如果形象表示的话, 翻译中文单词’汤姆’的时候, 数学公式对应的中间语义表示\(C_i\)的形成过程类似下图:

5.3.1.3 如何得到注意力概率分布

为了便于说明,我们假设Encoder-Decoder框架中,Encoder和Decoder都采用RNN模型,如下图所示:

那么注意力分配概率分布值的通用计算过程如下:

上图中\(h_j\)表示Source中单词j对应的隐藏层节点状态\(h_j\)\(H_i\)表示Target中单词i的隐藏层节点状态,注意力计算的是Target中单词i对Source中每个单词对齐可能性,即\(F(h_j,H_{i-1})\),而函数F可以用不同的方法,然后函数F的输出经过softmax进行归一化就得到了注意力分配概率分布。

上面就是经典的Soft Attention模型的基本思想,区别只是函数F会有所不同。

5.3.1.4 Attention机制的计算过程

其实Attention机制可以看作,Target中每个单词是对Source每个单词的加权求和,而权重是Source中每个单词对Target中每个单词的重要程度。因此Attention的本质思想会表示成下图:

将Source中的构成元素看作是一系列的数据对,给定Target中的某个元素Query,通过计算Query和各个Key的相似性或者相关性,相关性即权重系数;然后对Value进行加权求和,并得到最终的Attention数值。将本质思想表示成公式如下:

1737786772440

深度学习中的注意力机制中提到:Source中的Key和Value合二为一,指向的是同一个东西,也即输入句子中每个单词对应的语义编码,所以可能不容易看出这种能够体现本质思想的结构。因此Attention计算转换为下面3个阶段。

输入由三部分构成:Query、Key和Value。其中,(Key, Value)是具有相互关联的KV对,Query是输入的“问题”,Attention可以将Query转化为与Query最相关的向量表示。

1.Attention的计算主要分3步,如下图所示。

Attention三步计算过程:

  • 第一步:查询向量Query与每个键向量Key进行相似度计算,得到注意力分数(Attention Score);
    • 常用的相似度计算方法包括:
      • 点积 (Dot Product): \(score(Q, K) = Q ⋅ K^T\)
      • 缩放点积 (Scaled Dot Product): \(score(Q, K) = \frac{Q ⋅ K^T}{\sqrt{d_k}}\) (\(d_k\)是键向量的维度)
      • 加性 (Additive): \(score(Q,K)=W*tanh(W_qQ+W_kK)\)
  • 第二步:对Attention Score进行Softmax归一化,得到权值矩阵;
    • \(α = softmax(score(Q, K))\)
  • 第三步:权重矩阵与值向量Value进行加权求和计算,得到上下文向量c。
    • \(context_{vector} = \displaystyle \sum_{i}α_i * V_i\)
2.Query、Key和Value的含义是什么呢?

我们以刚才大脑读图为例:

  • Value可以理解为人眼视网膜对整张图片信息的原始捕捉,不受“注意力”所影响。我们可以将Value理解为像素级别的信息,那么假设只要一张图片呈现在人眼面前,图片中的像素都会被视网膜捕捉到。
  • Key与Value相关联,Key是图片原始信息所对应的关键性提示信息,比如“锦江饭店”部分是将图片中的原始像素信息抽象为中文文字和牌匾的提示信息。
  • 一个中文读者看到这张图片时,读者大脑有意识地向图片获取信息,即发起了一次Query,Query中包含了读者的意图等信息。在一次读图过程中,Query与Key之间计算出Attention Score,得到最具有吸引力的部分,并只对具有吸引力的Value信息进行提取,反馈到大脑中。就像上面的例子中,经过大脑的注意力机制的筛选,一次Query后,大脑只关注“锦江饭店”的牌匾部分。

再以一个搜索引擎的检索为例:

  • 使用某个Query去搜索引擎里搜索,搜索引擎里面有好多文章,每个文章的全文可以被理解成Value
  • 文章的关键性信息是标题,可以将标题认为是Key
  • 搜索引擎用Query和哪些文章的标题Key进行匹配,看看相似度(计算Attention Score)。我们想得到跟Query相关的知识,于是用这些相似度将检索的文章Value做一个加权和,那么就得到了一个新的信息,新的信息融合了相关性强的文章们,而相关性弱的文章可能被过滤掉。

在自然语言中:

  • 查询 (Query):查询向量表示当前要关注的目标,可以把它理解为“我正在寻找什么?”。类似于搜索时的“提问”,用于匹配相关的键(Key)。例如:在机器翻译中,查询向量Query代表的是解码器当前要翻译的目标词语的嵌入向量(解码器的隐藏状态)

  • 键 (Key):键向量表示输入序列每个元素的关键特征,可以把它理解为“所有元素的关键属性是什么?”。它们是用来与 Query进行比较,以确定哪些信息与当前Query最为相关。类似于数据库中的“索引”,用于与查询(Query)计算相关性。例如:在机器翻译中,键向量Key代表的是源语言句子中所有词语的嵌入向量(编码器的隐藏状态)

  • 值 (Value):值向量表示输入序列每个元素的实际信息,可以把它理解为“与当前Query最相关的元素的实际内容是什么?”。这些内容将被加权平均,以产生最终的输出。类似于数据库中的“实际数据”,根据权重加权后生成最终输出。通常与键向量Key相同。例如:在机器翻译的例子中,值向量Value代表的是源语言句子中所有词语的嵌入向量(编码器的输出结果)

5.3.2 Hard Attention

在6.3.1章节我们使用了一种软性注意力的方式进行Attention机制,它通过注意力分布来加权求和融合各个输入向量。而硬性注意力(Hard Attention)机制则不是采用这种方式,它是根据注意力分布选择输入向量中的一个作为输出。这里有两种选择方式:

  • 选择注意力分布中,分数最大的那一项对应的输入向量作为Attention机制的输出。
  • 根据注意力分布进行随机采样,采样结果作为Attention机制的输出。

硬性注意力通过以上两种方式选择Attention的输出,这会使得最终的损失函数与注意力分布之间的函数关系不可导,导致无法使用反向传播算法训练模型,硬性注意力通常需要使用强化学习来进行训练。因此,一般深度学习算法会使用软性注意力的方式进行计算。

5.3.3 Self Attention

自注意力(Self Attention)是Google在transformer模型中提出的,上面介绍的都是一般情况下Attention发生在Target元素Query和Source中所有元素之间。而Self Attention,指的是Source内部元素之间或者Target内部元素之间发生的Attention机制,也可以理解为Target=Source这种特殊情况下的注意力机制。当然,具体的计算过程仍然是一样的,只是计算对象发生了变化而已。

上面内容也有说到,一般情况下Attention本质上是Target和Source之间的一种单词对齐机制。那么如果是Self Attention机制,到底学的是哪些规律或者抽取了哪些特征呢?或者说引入Self Attention有什么增益或者好处呢?仍然以机器翻译为例来说明, 如下图所示:

Attention的发展主要经历了两个阶段:

  • 从上图中可以看到, self Attention可以远距离的捕捉到语义层面的特征(its的指代对象是Law)。
  • 应用传统的RNN、LSTM在获取长距离语义特征和结构特征的时候, 需要按照序列顺序依次计算, 距离越远的联系信息的损耗越大, 有效提取和捕获的可能性越小。
  • 但是应用self-attention时, 计算过程中会直接将句子中任意两个token的联系通过一个计算步骤直接联系起来。

5.4 注意力机制规则

它需要三个指定的输入Q(query), K(key), V(value), 然后通过计算公式得到注意力的结果, 这个结果代表query在key和value作用下的注意力表示。当输入的Q=K=V时, 称作自注意力计算规则(Self Attention);当Q、K、V不相等时,称作一般注意力计算规则

例子:seq2seq架构翻译应用中的Q、K、V解释

seq2seq模型架构包括三部分,分别是encoder(编码器)、decoder(解码器)、中间语义张量c。

图中表示的是一个中文到英文的翻译:欢迎 来 北京 → welcome to BeiJing。编码器首先处理中文输入”欢迎 来 北京”,通过GRU模型获得每个时间步的输出张量,最后将它们拼接成一个中间语义张量c;接着解码器将使用这个中间语义张量c以及每一个时间步的隐藏层张量, 逐个生成对应的翻译语言。

5.4.1 常见的注意力计算规则

  • 将Q,K进行纵轴(1轴)拼接, 做一次线性变化, 再使用softmax处理获得结果最后与V做张量乘法,更简单直接,适合计算资源有限或任务较简单的情况:

    1737788947142

  • 将Q,K进行纵轴(1轴)拼接, 做一次线性变化后再使用tanh函数激活, 然后再进行内部求和,最后使用softmax处理获得结果再与V做张量乘法,引入了非线性变换和内部求和,可捕捉更复杂的依赖关系,但计算复杂度更高:

    1737788955758

  • 将Q与K的转置做点积运算, 然后除以一个缩放系数(防止点积值过大或过小,导致梯度爆炸或消失), 再使用softmax处理获得结果最后与V做张量乘法:

    1737788965117

说明:当注意力权重矩阵和V都是三维张量且第一维代表为batch条数时, 则做bmm运算,bmm是一种特殊的张量乘法运算。

bmm运算演示:

Python
# 如果参数1形状是(b × n × m), 参数2形状是(b × m × p), 则输出为(b × n × p)
input = torch.randn(10, 3, 4)
mat2 = torch.randn(10, 4, 5)
res = torch.bmm(input, mat2)
res.size()

# 输出结果:
torch.Size([10, 3, 5])

5.4.2 加入Attention的两种方式

  • 第一种TensorFlow版本(传统方式),如下图所示:

    上图翻译应用中的Q、K、V解释:

    • 查询张量Q: 解码器每个时间步的输出或者是当前输入的x
    • 键张量K: 解码器上一个时间步的隐藏状态(第1个时间步的隐藏状态=编码器最后1个时间步的隐藏状态)
    • 值张量V: 编码器所有时间步的输出结果(隐藏状态)组合而成
  • 第二种Pytorh版本(改进版),如下图所示:

    上图翻译应用中的Q、K、V解释:

    • 查询张量Q: 解码器每个时间步的输出或者是当前输入的x
    • 键张量K: 编码器所有时间步的输出结果(隐藏状态)组合而成
    • 值张量V: 编码器所有时间步的输出结果(隐藏状态)组合而成
  • 两个版本对比:

    • pytorch版本的是乘型attention,tensorflow版本的是加型attention
      • pytorch将当前输入(input)与所有单元(unit)的隐藏状态(prev_hidden)进行点积并缩放得到score,之后将score经过softmax得到attenion_weights。
      • tensorflow将当前输入(input)与上一个单元(unit)的隐藏状态(prev_hidden)拼接起来线性计算得到score,之后将score经过softmax得到attenion_weights。
    • 乘型注意力(点积注意力)通常用于现代深度学习模型,如Transformer,它计算查询query和键key的点积并进行缩放处理。它计算效率高,能够更好地并行化,适合大规模任务。
    • 加性注意力则通过一个小的神经网络来计算注意力权重,这使得它能捕捉更复杂的关系,但计算上可能更加昂贵,通常用于早期的RNN模型中,如Bahdanau注意力用于神经机器翻译。

5.4.3 加Attention架构解码流程

  • 采用自回归机制,比如:输入“go”来预测“welcome”,输入“welcome”来预测”to”,输入“to”来预测“Beijing”。在输入“welcome”来预测”to”解码中,可使用注意力机制。
  • 查询张量Q:一般可以是“welcome”词嵌入层以后的结果,查询张量Q为生成谁就是谁的查询张量(比如这里为了生成“to”,则查询张量就是“to”的查询张量,请仔细体会这一点)
  • 键向量K:一般可以是上一个时间步的隐藏层输出(加性注意力)或是编码器每个时间步的输出结果组合而成(缩放点击注意力)
  • 值向量V:一般可以是编码器每个时间步的输出结果组合而成
  • 查询张量Q来生成“to”,去检索“to”单词和“欢迎”、“来”、“北京”三个单词的权重分布,注意力结果表示(用 权重分布 乘以 内容V

5.5 什么是深度神经网络注意力机制

注意力机制是注意力计算规则能够应用在深度学习网络的载体, 同时包括一些必要的全连接层以及相关张量处理, 使其与应用网络融为一体。使用自注意力计算规则的注意力机制称为自注意力机制。

深度神经网络中的注意力机制(Attention Mechanism)是一种强大的工具,用于增强模型对输入数据的理解能力。它通过动态地分配权重,使模型能够关注输入数据中的重要部分,从而提升模型的性能。注意力机制广泛应用于自然语言处理(NLP)、计算机视觉(CV)、语音处理和多模态任务等领域。

说明: NLP领域中, 当前的注意力机制大多数应用于seq2seq架构, 即编码器和解码器模型。

请思考:为什么要在深度神经网络中引入注意力机制?

  • RNN等循环神经网络,随着时间步的增长,前面单词的特征会遗忘,造成对句子特征提取不充分。
  • RNN等循环神经网络是一个时间步一个时间步的提取序列特征,效率低下。
  • 研究者开始思考,能不能对32个单词(序列)同时提取事物特征,而且还是并行的,所以引入注意力机制。

5.6 seq2seq架构中注意力机制作用

  • 编码器端的注意力机制
    • 主要解决表征问题, 相当于特征提取过程, 得到输入的注意力表示,一般使用自注意力(self-attention)。
    • 编码器的任务是生成输入序列的表示。在传统架构中,编码器输出一个固定长度的上下文向量。
    • 在引入注意力机制后,编码器输出的是所有时间步的隐藏状态,而不是单一的上下文向量。
    • 这些隐藏状态包含了输入序列的完整信息,为解码器提供了更丰富的上下文。
    • 编码器端Self-Attention主要用于捕捉输入序列的内部依赖关系。
  • 解码器端的注意力机制
    • 能够根据模型目标有效的聚焦编码器的输出结果, 当其作为解码器的输入时提升效果。改善以往编码器输出是单一定长张量, 无法存储过多信息的情况。
    • 解码器的任务是基于编码器的输出生成目标序列。在传统架构中,解码器仅依赖于单一的上下文向量。
    • 在引入注意力机制后,解码器在每个时间步动态地计算注意力权重,并根据这些权重对编码器的隐藏状态进行加权求和,得到一个上下文向量
    • 这个上下文向量反映了当前时间步需要关注的输入序列部分,从而帮助解码器生成更准确的输出。
    • 解码器端Encoder-Decoder Attention主要用于让解码器选择性地关注编码器输出的相关信息。

总结:编码器端与解码器端的注意力机制区别

特点 编码器端的注意力机制 解码器端的注意力机制
机制类型 自注意力机制(Self-Attention) 编码器-解码器注意力(Encoder-Decoder Attention)
查询(Query) 来自输入序列本身的隐藏状态(h_t 来自解码器当前隐藏状态(s_t
键(Key)和 值(Value) 来自输入序列的隐藏状态(h_1, h_2, ..., h_T 来自编码器的每个时间步的隐藏状态(h_1, h_2, ..., h_T
作用 增强输入序列的上下文信息表示 基于解码器的当前状态来选择最相关的编码器信息

5.7 注意力机制应用

  • 主要思路

    • 自定义注意力模块,类似于加性注意力的方法。
    • 通过1轴拼接查询Q和键K后用一个线性层 + softmax计算得到注意力权重。
    • 然后利用注意力权重和值V进行加权求和,得到动态的中间语义张量c
    • 最后将动态的中间语义张量c与原始查询Q进行1轴拼接融合,再经过一次线性变换得到加强的Q作为隐层的输入。
  • 实现步骤

    • 计算注意力权重:通过1轴拼接查询Q与键K后进行线性 + softmax计算得到注意力权重。
    • 加权求和:利用注意力权重对值V进行加权求和,生成动态注意力表示。
    • 结果融合:将原始查询Q和动态注意力表示通过1轴拼接后线性计算,生成加强的Q作为隐层的输入。
  • 代码实现

    常见注意力机制的代码分析:

    Python
    # 任务描述:
    # 有qkv:v是内容比如32个单词,每个单词32个特征,k是32个单词的索引,q是查询张量
    # 我们的任务:输入查询张量q,通过注意力机制来计算如下信息:
    # 1、查询张量q的注意力权重分布:查询张量q和其他32个单词相关性(相识度)
    # 2、查询张量q的结果表示:由一个普通的q升级成一个更强大的q;用q和v做bmm运算
    # 3、注意:查询张量q查询的目标是谁,就是谁的查询张量。 eg:比如查询张量q是来查询单词"我",则q就是我的查询张量
    
    import torch
    import torch.nn as nn
    
    
    # MyAtt类实现思路分析
    # 1 init函数 (self, query_size, key_size, seq_len, hidden_size, output_size)
    # 准备2个线性层 1.注意力权重分布self.attn    2.注意力结果表示和q融合再按照指定维度输出self.attn_combine
    # 2 forward(self, Q, K, V):
    # 求查询张量q的注意力权重分布, attn_weights->[1,1,32]
    # 求查询张量q的注意力结果表示 bmm运算, attn_applied->[1,1,32]
    # q与attn_applied融合,再按照指定维度输出 output[1,1,32]
    # 返回注意力结果表示output:[1,1,32], 注意力权重分布attn_weights:[1,32]
    
    class MyAtt(nn.Module):
        def __init__(self, query_size, key_size, seq_len, hidden_size, output_size):
            super(MyAtt, self).__init__()
            self.query_size = query_size    # 解码器的隐藏状态维度或输入x维度
            self.key_size = key_size    # 解码器的隐藏状态维度 
            # value是编码器每个时间步的输出结果组合而成 (batch_size, seq_len, hidden_size)
            self.seq_len = seq_len    # 注意力权重经过attn线性层的输出维度, value的第2个维度值(词数量)
            self.hidden_size = hidden_size    # value的第3个维度值, 经过隐藏层后的词向量维度
            self.output_size = output_size    # 最终输出的维度, 中间语义张量c维度
    
            # 线性层1:用于计算注意力权重的分布,负责从拼接的Q和K特征中得到注意力权重分布
            # 输入为query和key拼接后的向量,维度为 query_size + key_size,如32+32=64;
            # 输出为seq_len,如32, 和value中句子的词数量对应, 后续要做三维矩阵乘法
            # [1,1,32] * [1,32,32] ---> [1,1,32]
            self.attn = nn.Linear(self.query_size + self.key_size, self.seq_len)
    
            # 线性层2:用于融合查询(Q)和注意力加权后的结果(attn_applied),
            # 负责将原始查询向量Q与注意力机制生成的表示融合后投影到最终的输出空间,
            # 得到加强的Q表示(融合了Q和注意力机制生成的表示)
    
            # 输入为query和value拼接后的向量, 维度为 query_size + hidden_size,如32+32=64;
            # 输出为最终输出维度output_size,如32, 和后续网络层(rnn/lstm/gru)的输入维度一致
            self.attn_combine = nn.Linear(self.query_size + self.hidden_size, output_size)
    
        def forward(self, Q, K, V):
            # 1 求查询张量q的注意力权重分布, attn_weights[1,1,32]
            # [1,1,32],[1,1,32]--> [1,1,32],[1,1,32]->[1,1,64]
            # tmp1 = torch.cat((Q, K), dim=-1)
            # [1,1,64] --> [1,1,32]
            # tmp2 = self.attn(tmp1)
            # tmp3 = torch.softmax(tmp2, dim=-1)
    
            """
            这里注意:这段实现与标准点积注意力不同,其核心思路是先1轴拼接Q和K,
            再通过线性层得到注意力权重。
            也就是说,注意力权重的计算是通过一个学习到的线性变换来决定的,
            而不是直接用Q与K的点积。
    
            torch.cat(tensors=[Q, K, dim=-1)
            拼接向量同时包含了当前查询的特征和对应键的特征,方便后续层捕捉它们之间的关系。
            就好比在评估一本书是否与当前问题相关时,
            你把问题的描述和书籍的特征“放在一起”,再由模型判断它们的匹配程度。
    
            self.attn(torch.cat(tensors=[Q, K, dim=-1))
            线性层将拼接后的向量映射到一个新的空间,输出一个向量。
            线性层的参数(权重和偏置)会在训练过程中自动调整,从而学习到如何将拼接后的联合特征转换为一个表示相关性或兼容性的“得分”。
            对线性映射得到的分数进行softmax归一化,使得输出得分转换为概率分布。
            这样得到的权重能够表示各个位置(或各个特征在这里)的相对重要性。
            """
            attn_weights = torch.softmax(self.attn(torch.cat(tensors=[Q, K], dim=-1)), dim=-1)
    
            # 2 求查询张量Q和V的结果表示 bmm运算, attn_applied[1,1,32]
            # [1,1,32] * [1,32,32] ---> [1,1,32]
            # 这一步的作用相当于用注意力权重对V进行加权求和,生成了注意力机制的输出表示, 动态中间语义张量c。
            attn_applied = torch.bmm(attn_weights, V)
    
            # 3 Q与attn_applied进行1轴拼接融合,再按照指定维度输出 output[1,1,32]
            # 3-1 q与结果表示拼接 [1,1,32],[1,1,32] ---> [1,64]
            # 动态的c->attn_applied主要关注编码器端,反映了解码器当前最关注的编码器信息。
            # Q引入更多的解码器状态信息和特定目标信息。
            # 拼接操作将这两种信息融合到一起,使得后续处理可以同时考虑原始查询与上下文信息。
            output = torch.cat(tensors=[Q, attn_applied], dim=-1)
            # 3-2 shape [1,1,64] ---> [1,1,32]
            # 融合了原始查询和动态c的信息,作为rnn层的输入数据。
            output = self.attn_combine(output)
    
            # 4 返回rnn层的输入数据output:[1,1,32], 注意力权重分布attn_weights:[1,1,32]
            return output, attn_weights
    
    if __name__ == '__main__':
        query_size = 32
        key_size = 32
        seq_len = 32 # 32个单词
        hidden_size = 32 # 32个特征
        output_size = 32
    
        # 解码器每个时间步的输出或输入x
        Q = torch.randn(1, 1, query_size)
        # 解码器上一时间步的隐藏状态
        K = torch.randn(1, 1, key_size)
        # 编码器每个时间步的输出结果组合而成 (batch_size, seq_len, hidden_size)
        V = torch.randn(1, seq_len, hidden_size)    
    
        # 1 实例化注意力类 对象
        myattobj = MyAtt(query_size, key_size, seq_len, hidden_size, output_size)
        # 2 把QKV数据扔给注意机制,求查询张量q的注意力结果表示、注意力权重分布
        output, attn_weights = myattobj(Q, K, V)
        print('查询张量q的注意力结果表示output--->', output.shape, output)
        print('查询张量q的注意力权重分布attn_weights--->', attn_weights.shape, attn_weights)
    

    输出结果:

    Python
    查询张量q的注意力结果表示output---> torch.Size([1, 1, 32]) tensor([[[ 0.1448, -0.2936, -0.2447, -0.0355, -0.1963, -0.2531,  0.3007,
              -0.1721,  0.2715, -0.4715,  0.7442, -0.0779, -0.0868,  0.5419,
               0.2675, -0.0528,  0.3024,  0.3986, -0.5940,  0.0214, -0.2103,
               0.7032, -0.2594, -0.5653, -0.3881,  0.1979, -0.3113, -0.3242,
               0.3027,  0.1695,  0.0868,  0.3664]]], grad_fn=<ViewBackward0>)
    查询张量q的注意力权重分布attn_weights---> torch.Size([1, 1, 32]) tensor([[[0.0250, 0.0592, 0.0369, 0.0194, 0.0173, 0.0211, 0.0251, 0.0073,
              0.0419, 0.0478, 0.0259, 0.0199, 0.0404, 0.0366, 0.0278, 0.0348,
              0.0688, 0.0383, 0.0201, 0.0117, 0.0128, 0.0667, 0.0265, 0.0236,
              0.0279, 0.0200, 0.0340, 0.0674, 0.0311, 0.0230, 0.0304, 0.0113]]],
           grad_fn=<SoftmaxBackward0>)
    

更多有关注意力机制的应用我们将在后续案例中进行详尽的理解分析。

5.8 小结

  • 什么是注意力机制:
    • “注意力机制”实际上就是想将人的感知方式、注意力的行为应用在机器上,让机器学会去感知数据中的重要和不重要的部分。
  • 注意力机制的类别:
    • 深度学习中的注意力机制通常可分为三类: 软注意(全局注意)、硬注意(局部注意)和自注意(内注意)
      • 软注意机制(Soft/Global Attention: 对每个输入项的分配的权重为0-1之间,也就是某些部分关注的多一点,某些部分关注的少一点,因为对大部分信息都有考虑,但考虑程度不一样,所以相对来说计算量比较大。
      • 硬注意机制(Hard/Local Attention,[了解即可]): 对每个输入项分配的权重非0即1,和软注意不同,硬注意机制只考虑那部分需要关注,哪部分不关注,也就是直接舍弃掉一些不相关项。优势在于可以减少一定的时间和计算成本,但有可能丢失掉一些本应该注意的信息。
      • 自注意力机制( Self/Intra Attention): 对每个输入项分配的权重取决于输入项之间的相互作用,即通过输入项内部的”表决”来决定应该关注哪些输入项。和前两种相比,在处理很长的输入时,具有并行计算的优势。
  • 什么是注意力计算规则:
    • 它需要三个指定的输入Q(query), K(key), V(value), 然后通过计算公式得到注意力的结果, 这个结果代表query在key和value作用下的注意力表示. 当输入的Q=K=V时, 称作自注意力计算规则.
  • 常见的注意力计算规则:
    • 将Q,K进行纵轴拼接, 做一次线性变化, 再使用softmax处理获得结果最后与V做张量乘法.
    • 将Q,K进行纵轴拼接, 做一次线性变化后再使用tanh函数激活, 然后再进行内部求和, 最后使用softmax处理获得结果再与V做张量乘法.
    • 将Q与K的转置做点积运算, 然后除以一个缩放系数, 再使用softmax处理获得结果最后与V做张量乘法.
  • 什么是深度学习注意力机制:
    • 注意力机制是注意力计算规则能够应用的深度学习网络的载体, 同时包括一些必要的全连接层以及相关张量处理, 使其与应用网络融为一体. 使自注意力计算规则的注意力机制称为自注意力机制.
  • 注意力机制的作用:
    • 在解码器端的注意力机制: 能够根据模型目标有效的聚焦编码器的输出结果, 当其作为解码器的输入时提升效果. 改善以往编码器输出是单一定长张量, 无法存储过多信息的情况.
    • 在编码器端的注意力机制: 主要解决表征问题, 相当于特征提取过程, 得到输入的注意力表示. 一般使用自注意力(self-attention).
  • 注意力机制实现步骤:
    • 第一步: 根据注意力计算规则, 对Q,K,V进行相应的计算, 得到动态语义张量c.
    • 第二步: 根据第一步采用的计算方法, 如果是加性注意力,则需要将Q与第一步的计算结果再进行拼接; 如果是点积注意力, 一般是自注意力, Q与V相同, 则不需要进行与Q的拼接.
    • 第三步: 最后为了使整个attention机制按照指定尺寸输出, 使用线性层作用在第二步的结果上做一个线性变换, 得到隐层的输入表示.