【基础整理】attention:浅谈注意力机制与自注意力模型(附键值对注意力 + 多头注意力)


划水休息两天不看论文了 ~ 来重新复习一下基础qaq


以下讲解参考大名鼎鼎的 nndl 邱锡鹏 《神经网络与深度学习》 部分内容(详见第八章,注意力与外部记忆)是对于不太行的初学者也比较友好的一本,当然不能要求一本书既全面又深入,阅读过程还是建议自己去更多地从别的渠道了解细节内容,但个人觉得即使是顺着通读亦对关于深度学习的整体框架搭建蛮有帮助的qaq

同时参考了这篇综述:An Attentive Survey of Attention Models,具体这篇论文的阅读笔记也蛮多的就不找时间再写了(https://www.cnblogs.com/ydcode/p/11040811.html 这篇就写的很好~)



文章目录

    • 1 background
    • 2 整体架构
      • 2.1 计算注意力分布
      • 2.2 计算加权分布
      • 2.3 在 encode-decode 中的应用举例
    • 3 常用注意力机制的变体
      • 3.1 键值对注意力 key-value pair
      • 3.2 多头注意力 Multi-Head Attention
    • 4 自注意力 Self-Attention
      • 4.1 查询 - 键值对注意力模型 Query-Key-Value,QKV
      • 4.2 多头自注意力 Multi-Head Self-Attention
    • 5 summary




1 background


注意力机制本身想要解决的问题很简单,就是算不动了

在整体模型越来越庞大的情况下,大家明显地觉得运算开始吃力了,这样的问题下直观的思想就是考虑如何按重要性更好地将手头有限的计算资源进行分配,以保证更多的计算资源可以分配到确实重要的内容上,而尽量不要太浪费在所谓不重要的内容上。人脑面对海量信息往往利用 注意力 将部分不重要的信息略去而只关注于重要的信息,这里将注意力分为两种:

  • 自上而下的有意识的注意力,也就是 聚焦式注意力(Focus Attention):此时的注意力是确定地有一个目的的,也就是会主动地去关注某一个特定的对象,本身依赖目的,存在预定的任务(举例阅读理解问题,此时给定问题以后,关注的只是和问题相似的文本段落对象,对其他对象可以少输入或不输入模型以节约计算资源)

  • 自下而上的无意识的注意力,也就是 基于显著性的注意力(Saliency Based Attention):也就是说此时并没有预先根据某种目的或任务关注某一个特定的对象,而是单纯地当某一个对象表现出某种特征的时候,比如某一个对象值很大时则转而主动关注这样的对象(举例 max 池化,门控机制)

这里来一个 nndl 中的例子:

一个和注意力有关的例子是鸡尾酒会效应。当一个人在吵闹的鸡尾酒会上和朋友聊天时,尽管周围噪音干扰很多,他还是可以听到朋友的谈话内容,而忽略其他人的声音(聚焦式注意力)。同时,如果背景声中有重要的词(比如他的名字),他会马上注意到(显著性注意力)


从这个思路出发,当前已经进行了较多的尝试,比如最简单的 池化层:最大池化本身也就是选择一个范围内的最大值保存下来,直观理解可以认为就是只关注了值最大的部分而将其他部分内容舍去,以更好地将更宝贵的计算资源放在可能重要的部分

进一步地从当前主流的 encode - decode (编码器 - 解码器)模型来讨论一下 注意力机制 的优势。传统的编码器解码器模型存在以下两个很大的问题:

  • 编码器需要把输入的信息转化为一个定长的序列才能给到解码器进行处理,也就意味着此时容易造成信息的损失(非要塞到一个定长的序列里)
  • 此时输入的序列和最后解码器的输出本身难以作对齐,以翻译问题来说,我输入 Tom chase Jerry,此时由于编码器 RNN 是平等地对每一个词来进行编码操作的,也就是说我最后翻译出的 汤姆 本身是依赖了相同权重的 Tom 、chase、 Jerry 这三个词。但明显 汤姆 的翻译应该更多地依赖 Tom 才对,这就造成了本身输入序列和输出序列的不对齐。这同样也影响了本身神经网络的解释性问题,将所有输入的东西都以相同的权重看待将导致难以解释我最后的结果到底是依赖什么得出的。

注意力机制,也就是尝试 在输入的信息上计算注意力分布 → 从而得到 不同输入信息对应当前任务的重要性分布 = 不同的权重 → 再根据不同的重要性,也就是不同的权重 计算当前输入信息的加权平均,以实现(对重要的,和任务相关的信息赋予更高的权重,而将不重要的信息基本忽略或赋予较低的权重)以更有效率地利用计算资源




2 整体架构


注意力机制 本身只是一种思想,并不依赖某一个特定的模型(虽然总是依赖编码器 - 解码器的模型来解释它),可以简单地总结为两个步骤:

  • 在给定的信息上计算注意力分布(也就是判断什么信息重要,什么信息不重要,分别赋予不同的权重)
  • 根据注意力分布来计算所有输入信息的加权平均


2.1 计算注意力分布


简化问题,考虑此时输入 N 个向量 : [ x 1 , . . . , x N ] [x_1, ... , x_N] [x1,...,xN],我想要从中选出对于我的目标而言比较重要的信息,需要引入我的目标任务的表示,称为 查询向量(query vector),则此时问题可以转换为考察 输入的不同内容和查询向量之间的相关度,一个简单的思路就是通过一个 注意力打分函数 对不同内容进行打分,赋予与我当前任务比较相关的部分更大的权重,再直接地通过一个 softmax 层得到分布,也就是输入信息的不同部分的权重。

这里的注意力打分函数主要有以下几种:

  • 加性模型: s ( x , q ) = v T t a n h ( W x + U q ) s(x,q) = v^T tanh(Wx + Uq) s(x,q)=vTtanh(Wx+Uq)
  • 点积模型: s ( x , q ) = x T q s(x,q) = x^Tq s(x,q)=xTq
  • 缩放点积模型: s ( x , q ) = x T q D s(x,q) = \frac{x^Tq}{\sqrt{D}} s(x,q)=D xTq
  • 双线性模型: s ( x , q ) = x T W q s(x,q) = x^TWq s(x,q)=xTWq

上面的 W U 都是可以学习的参数,D是输入向量的维度

比较常用的就是 点积模型,简单 + 有效

通过每一个输入 x 和 q 计算得分函数 → 再通过 softmax 层,则第 i 个输入 xi 对应的权重也就是 s o f t m a x ( s ( x , q ) ) softmax(s(x,q)) softmax(s(x,q))



2.2 计算加权分布

这里分为两种方式:


软性注意力机制:也就是每一个输入的按各自权重的加权平均
a t t ( X , q ) = ∑ n = 1 N a n x n att(X,q) = \sum_{n=1}^N a_n x_n att(X,q)=n=1Nanxn


硬性注意力机制:也就是只关注某一个向量,此时用某一个向量来直接代替所有输入的信息,而将其他的信息都一概忽略

主要有以下两种形式:

  • 选择权重最大的向量
  • 在注意力分布上进行随机采样(也就是权重越大的向量越容易被采到)

但是注意到,这里硬性注意力机制的最大的问题就是它本身是不可导的(比如选择权重最大实际上就是通过了一个 max 函数),也就是说这一步会导致无法使用反向传播算法,一般只能依赖强化学习进行训练



2.3 在 encode-decode 中的应用举例

在这里举个例子,考虑 seq2seq 框架下的 编码器 - 解码器模型中注意力机制最简单的应用方式

整体如下,此时左边是正常的编码器-解码器模型,右边是加入了注意力模块的
【基础整理】attention:浅谈注意力机制与自注意力模型(附键值对注意力 + 多头注意力)_第1张图片

经过 encode,此时输入 x1 x2 x3 对应的得到三个隐藏状态 h1 h2 h3,考察上一步中解码器已经得到的隐藏状态 s2,可以将 s2 作为查询向量,计算所有的隐藏状态 h1 h2 h3 和 s2 的相关度(这里的得分是通过一个前馈神经网络来学习的) → 得到注意力分布 → 得到 h1 h2 h3 的聚合内容,也就是图中的 c2,再进一步依据模型设计将 c2 用于解码器




3 常用注意力机制的变体


实际使用的时候直接利用最原本的模式的情况是不多的,这里讲几个应用比较广泛的变体:


3.1 键值对注意力 key-value pair

也就是利用 键值对 的方式来输入信息,此时不再是只输入一个 x,而以: ( K , V ) = [ ( k 1 , v 1 ) , ( k 2 , v 2 ) , . . . , ( k N , v N ) ] (K,V) = [(k_1, v_1), (k_2, v_2) , ... , (k_N, v_N)] (K,V)=[(k1,v1),(k2,v2),...,(kN,vN)]
的方式输入 N 组信息。这里满足:

  • 键 = k 用来计算注意力分布
  • 值 = v 用来基于注意力分布最后计算聚合后的信息

其他内容和基本形式是一样的,也就是说此时的聚合信息可以表示为(如果利用软性的信息聚合,打分函数为 s): a t t ( ( K , V ) , q ) = ∑ n = 1 N s o f t m a x ( s ( q , k n ) ) v n att((K,V), q) = \sum_{n=1}^N softmax(s(q, k_n)) v_n att((K,V),q)=n=1Nsoftmax(s(q,kn))vn

工作实质如图所示:
【基础整理】attention:浅谈注意力机制与自注意力模型(附键值对注意力 + 多头注意力)_第2张图片
阶段1 → 通过打分函数,利用查询向量 q,对此时输入的 (键 key)的部分进行打分
阶段2 → 通过 softmax 将不同的打分作归一化处理,得到各个部分的注意力权重,也就是注意力分布
阶段3 → 通过输入的 (值 value)结合注意力分布进行信息聚合

K=V 的时候也就是普通的模型



3.2 多头注意力 Multi-Head Attention

也就是此时存在多个查询 Q: Q = [ q 1 , . . . , q M ] Q = [q_1, ... , q_M] Q=[q1,...,qM]
来通过一种 并行 的方式从输入中搜索需要的信息。直观理解就是解决问题需要很多不同的方面的信息,每一个不同的 qi 考察的都是不同的方面,此时利用不同的 qi 从不同的角度给输入信息的重要性进行打分,再进行某种程度的聚合。

一般不同查询对应得到的聚合信息直接用向量拼接的方式: a t t ( ( K , V ) , Q ) = a t t ( ( K , V ) , q 1 ) ⊕ . . . ⊕ a t t ( ( K , V ) , q M ) att((K,V), Q) = att((K,V), q_1) \oplus ... \oplus att((K,V), q_M) att((K,V),Q)=att((K,V),q1)...att((K,V),qM)




4 自注意力 Self-Attention


考虑当前针对输入序列的编码方式,如果我们需要将输入序列转化为一个定长的序列,此时卷积和循环神经网络均是较好的选择,但是注意到以上两种均只是一种局部编码的方式(循环神经网络本身由于长程依赖问题 / 梯度消失,实际上也只能建模局部的信息)

换一个思路,如果我想要捕捉整个输入序列中的某种长程依赖关系,此时常用的方法包括:

  • 构建深层网络来捕捉关系 → 也就是增加神经网络的层数,但是大家这么干过都知道直接粗暴增加层数是很消耗计算资源的
  • 构造全连接网络 → 但是无法处理变长的序列,也就是说此时输入的序列必须要是等长的 = 输入层的神经元个数

想要模拟全连接神经网络的思路来构建一种更好的,可以处理变长输入序列 + 捕捉长距离关系的模型,可以考虑利用注意力机制来 动态地 生成权重,这也就是 自注意力模型 的主要思路



4.1 查询 - 键值对注意力模型 Query-Key-Value,QKV


考虑到往常的使用频率,这里先介绍一下最常用的 查询 - 键值对注意力模型(Query-Key-Value,QKV),实际上为了增强模型效果,基本不会使用上面广义形式对应的自注意力模型

假设此时的输入序列为 x,则自注意力模型可以分为以下几个步骤:

  • 将输入的 x 线性投影到三个不同的空间,以分别得到查询向量 qi,键向量 ki,值向量 vi
    • 注意这里线性投影的含义就是学习到三个不同的矩阵 Wq Wk Wv: Q = W q X ;     K = W k X ;     V = W v X Q = W_qX; \text{ }\text{ }\text{ } K = W_kX; \text{ }\text{ }\text{ } V = W_vX Q=WqX;   K=WkX;   V=WvX也可以看作是一个简单的前馈神经网络。注意这里因为自注意力模型的打分函数常常使用点积形式,所以这里得到的 qi ,ki,vi 三个结果往往是相同维度的
  • 利用上述得到的查询向量,键向量和值向量,通过上述的键值对注意力机制得到输出。也就是说如果这里得到的查询向量,键向量和值向量分别为: Q = [ q 1 , . . . , q N ] ;   K = [ k 1 , . . . , k N ] ;   V = [ v 1 , . . . , v N ] Q = [q_1,..., q_N]; \text{ } K = [k_1, ... , k_N]; \text{ } V = [v_1, ..., v_N] Q=[q1,...,qN]; K=[k1,...,kN]; V=[v1,...,vN],则此时直接通过键值对注意力机制的方式来计算最后的输出: h n = a t t ( ( K , V ) , q n ) = ∑ j = 1 N s o f t m a x ( s ( k j , q j ) ) v j h_n = att((K,V),q_n) = \sum_{j=1}^N softmax(s(k_j, q_j))v_j hn=att((K,V),qn)=j=1Nsoftmax(s(kj,qj))vj

整体图解如下:
【基础整理】attention:浅谈注意力机制与自注意力模型(附键值对注意力 + 多头注意力)_第3张图片
也就是说,输入三个(这里设 N=3)X 序列,每一个长度为 Dx,则此时得到三个查询向量,每一个查询向量得到一个对应的 (这三个 x 序列的线性组合),则此时得到三个结果,对应拼在一起,不改变输入的 X 的对应 N 这部分的维度,但是将长度 Dx 改变为了 Dv(这里的 Dv 是可以任意设置的,只要通过操作此时从 X 到值 V 的投影操作对应的矩阵 Wv 就可以了)

也就是说通过上述操作,此时可以将不定长的序列 Dx 动态地生成适合的权重并转化为某一个定长 Dv



4.2 多头自注意力 Multi-Head Self-Attention

其实也就是上述的自注意力操作加上了自注意力的思路

比如说此时对应的 x 是 Dx 维度,共 N 个,我每一次通过一个矩阵 Wq 将其投影为一个查询矩阵的时候都可以一次性得到 N 个查询向量,这 N 个查询向量可以保证后续输出的结果还是共 N 个(只是另一个维度由值向量的维度 Dv 决定),这是我们上一节的思路

考虑多头注意力,也就是说此时我们考虑能不能得到多个查询矩阵,以从不同的角度来捕捉 X 的重要情况,则此时我采用多个矩阵 Wqi 将它投影到 m 个不同的空间分别得到 m 个查询矩阵,共 m*N 个向量,再分别计算输出,最后采用向量拼接的方式把它拼起来得到最后的结果,这里思路是十分接近的,就不多说了。




5 summary


简单总结一下注意力机制的几个优点:

  • 直观上了解,它可以同时捕捉全局和局部的联系,也就是说每一个 xi 的对应的权重都是和全局比较后得出的(也就是 softmax 部分)
  • 大大减少了计算时间,直观理解也就是更多地将算力用在了确实需要注意的,与任务更加相关的重要的部分
  • 支持并行处理 ,注意力机制的计算都不依赖一层一层的结果输出,所以可以并行进行操作计算
  • 本身模型很简单(可能只是简单的点积 / 前馈神经网络,最后再加入一层 softmax),也就是说并不会为当前的模型带来太大的负担,本身参数也不多,训练容易
  • 某种程度上或许可以帮助提高神经网络模型的解释性,也就是说通过计算注意力分布,我可以知道我当前的答案或许是更多地来自哪一个输入,从而更好地为未来的结果提供一定的解释

但是可以注意到此时缺点也是很明显的:

  • 将所有的输入(对比到 encode - decode 模型也就是 encoder 部分的 隐藏层 h1 h2 h3)均平行对待,也就是说并没有考虑输入可能存在位置关系(比如 h2 就是在 h1 和 h3 之间输入的),而是并行地计算它们和查询向量 q 的关系,对于 nlp 任务来说本质上是损失了信息的

当然后续的模型大多加入位置信息 embedding 来帮助缓解这个问题(比如 BERT 就是这么干的),所以并不认为这就是 attention 用在 nlp 上的致命伤





理解深度有限,存在错误 / 不足欢迎指出!期待进一步讨论~
转载请注明出处。知识见解与想法理应自由共享交流,禁止任何商用行为!

你可能感兴趣的:(【基础整理】attention:浅谈注意力机制与自注意力模型(附键值对注意力 + 多头注意力))