注意力机制介绍
1.注意力机制由来,以及解决什么问题
- 机器翻译任务
例子:seq2seq架构翻译任务
- seq2seq 模型架构包括三个部分:编码器(encoder),解码器(decoder),中间语义张量C
- 图中表示中文翻译:欢迎来北京—> welcome to beijing,首先编码器 通过embedding把每个token转换为词向量,在通过GRU模型获取每个时间步的输出张量最后拼接为中间语义张量c;输入_GO通过解码器将使用中间语义张量c以及每一个时间步的隐层张量,组个生成对应的翻译语言,直到输出_EOS翻译结束.
- 早期在解决机器翻译这一类seq2seq时,通常利用一个编码器(encoder)以及一个解码器(decoder)构建端到端的神经网络模型,但基于编码解码存在两个问题:
- 问题1:如果翻译的句子很长很复杂,模型计算量很大,并且模型准确率下降严重.
- 问题2:在翻译时,可能在不同的语境下,同一个词具有不同含义,网络对这些词向量没有区分度,没有考虑词与词之间的相关性,导致翻译效果比较差.
2.什么是注意力机制
- 伴随着transformer模型架构的提出,注意力机制在NLP,CV相关问题的模型被广泛使用,注意力机制就是将人的感知方式,注意力行为应用在机器上,让机器去学习数据中重要和不重要的部分.
- 在机器翻译时,让机器注意到每个词向量之间的相关性,有侧重的进行翻译,模拟人类理解过程.
3.注意力机制分类及如何实现
介绍:
就是对于模型的的每一个输入项,可能是图片中不同部分,又或者是语句中某个单词分配的权重,这个分配的权重大小就希望模型地该部分一个关注程度.通过调整权重大小 来模拟在处理信息的注意力侧重,有效提高模型的性能,并且一定程度上降低计算量.
- 深度学习中注意力机制通常可分为三类:软注意(全局注意),硬注意(局部注意),自注意(内注意)
- 软注意机制 (Soft-Attention):对每个输入项的分配权重为0-1之间,对部分关注对一点,对部分关注少一点,对大部分信息考虑,但考虑层度不一样,计算量比较大.
- 硬注意机制(Hard-Attention):对每个输入项分配的权重非0即1,硬注意只考虑部分需要关注,不需要关注的部分直接舍弃,有可能丢失一些本应该注意的信息,优势在于可以减少时间与计算成本.
- 自注意机制(Self-Attention):对每个输入项分配的权重取决于输入项之间相互作用,通过输入项内部"表决"来决定应该关注那些输入项,与前两者相比,在处理很长的输入时,具有并行计算的优势.
3.1 Soft Attention(常见):软注意
注意力机制:是一种通用的思想与技术,不依赖于任何模型;注意力机制可用于任何模型
以文本领域的Encoder-Decoder框架,以普通Encoder-Decoder框架以及加Attention的Encoder-Decoder框架分别做对比.
3.1.1普通Encoder-Decoder框架
- 如图所示
- 上图图例可以看作由一个句子(或篇章)生成另外一个句子(或篇章)的通用处理模型,我们给定输入句子Source,通过Encoder-Decoder框架来生成目标句子Target,Source和Target可以使用同种语言,也可以使用不同语言,Source和Target分别由各自单词序列构成:
S o u r c e = < X 1 , X 2 . . . X m > T a r g e t = < y 1 , y 2 . . . , Y n > Source = <X_1,X_2...X_m>\\Target = <y_1,y_2...,Y_n> Source=<X1,X2...Xm>Target=<y1,y2...,Yn>
- encoder就是对输入句子Source进行编码,将句子通过非线性变换转换(拼接)成中间语义表示C:
C = F ( X 1 , X 2 . . . X m ) C = F(X_1,X_2...X_m) C=F(X1,X2...Xm)
- decoder根据句子Source的中间语义表示C和之前生成的历史信息y1,y2…yi-1来生成i时刻要生层的单词yi:
y i = G ( C , y 1 , y 2 . . . y i − 1 ) y_i = G(C,y_1,y_2...y_{i-1}) yi=G(C,y1,y2...yi−1)
- 上述模型没有出现注意力模型,看作注意力不集中分心模型,Target中每个单词生成过程如下:
y 1 = f ( C ) y 2 = f ( C , y 1 ) y 3 = f ( C , y 1 , y 2 ) y_1 = f(C)\\ y_2 = f(C,y_1)\\ y_3 = f(C,y_1,y_2) y1=f(C)y2=f(C,y1)y3=f(C,y1,y2)
- f为decoder非线性变换函数,再生成目标单词时,使用的输入句子语义编码C都是一样的,因此对输入token对目标token影响力都是相同的,这是不合理的,模型将无法捕捉输入序列的复杂结构和token之间的依赖关系.
3.1.2加入Attention的Encoder-Decoder框架
举例说明,为何添加Attention:
在机器翻译任务时,输入scoure:Tom chase Jerry ,输出target为:"汤姆’‘,"追逐’‘,’’ 杰瑞’'时,在翻译Jerry这个单词时,普通Encoder-Decoder框架中,source里的每个单词对杰瑞贡献相同,很明显不太合理,显然Jerry对翻译成"杰瑞"更重要.
-
引入Attention机制,在生成 杰瑞 时,应该体现英文单词翻译成中文单词不同影响程度比如给出类似给概率分布值:(Tom,0.3)(Chase,0.2) (Jerry,0.5),每个单词概率分布概率代表翻译成单词 杰瑞 时的概率,注意力分配给不同英文单词的注意力大小.
-
对于target任意单词都有对应的source中单词注意力分配概率,由于Attention加入,在生成target单词时中间语义c不在固定,会根据注意力变化在翻译时得到不同的中间语义C
-
生成目标句子单词过程形式:
y 1 = f 1 ( C 1 ) y 2 = f 2 ( C 2 ) y 3 = f 3 ( C 3 ) y_1 = f_1(C_1)\\ y_2 = f_2(C_2)\\ y_3 = f_3(C_3) y1=f1(C1)y2=f2(C2)y3=f3(C3)
- 每个Ci对应着不同的原语句单词注意力概率分布,对于上面英译汉翻译来说,其对应信息可能如下:
C T o m = g ( 0.6 ∗ f 2 ( T o m ) , 0.2 ∗ f 2 ( C h a s e ) , 0.2 ∗ f 2 ( T e r r y ) ) C C h a s e = g ( 0.2 ∗ f 2 ( T o m ) , 0.7 ∗ f 2 ( C h a s e ) , 0.1 ∗ f 2 ( T e r r y ) ) C J e r r y = g ( 0.3 ∗ f 2 ( T o m ) , 0.2 ∗ f 2 ( C h a s e ) , 0.5 ∗ f 2 ( T e r r y ) ) C_{Tom} = g(0.6 * f_2(Tom),0.2 * f_2(Chase),0.2 * f_2(Terry))\\ C_{Chase} = g(0.2 * f_2(Tom),0.7 * f_2(Chase),0.1 * f_2(Terry))\\ C_{Jerry} = g(0.3 * f_2(Tom),0.2 * f_2(Chase),0.5 * f_2(Terry)) CTom=g(0.6∗f2(Tom),0.2∗f2(Chase),0.2∗f2(Terry))CChase=g(0.2∗f2(Tom),0.7∗f2(Chase),0.1∗f2(Terry))CJerry=g(0.3∗f2(Tom),0.2∗f2(Chase),0.5∗f2(Terry))
- f2函数代表Encoder对输入英文单词的某种变换函数,比如如果使用RNN模型f2函数结果往往代表某个时刻输入后隐藏层节点状态值;g代表Encoder根据单词中间表示合成整个句子中间语义表示得到变换函数,一般做法中,g函数就是对构成元素加权求和,即公式:
C i = ∑ j = 1 L x a i j h j C_i = \sum_{j=1}^{L_x}a_{ij}h_j Ci=j=1∑Lxaijhj
-
Lx代表输入句子长度source的长度,a_ij代表Target输出第i个单词source输入句子中的第j个单词的注意力分配系数,而hj 表示source输入句子中第j个单词的语义编码,假设Ci下标i就是上面例子所说的汤姆,那么Lx就是3.
h1=f(‘Tom’), h2=f(‘Chase’),h3=f(‘jerry’)分别输入句子每个单词的语义编码,对应的注意力模型权重值分别是:0.6,0.2,0.2,所以g函数本质上就是加权求和函数.类似下图
3.1.3如何理解注意力概率分布
- 为方便说明我们假设Encoder-Decoder框架中,都采用RNN模型,如下图所示
- 注意力分配概率分布值计算过程如下:
- 图中hi表示Source中单词对应隐藏层节点状态hj,Hi表示Target中单词i隐藏层节点状态,注意力计算Target中单词Source对每个单词对其的可能性,即F(hi,Hi-1),后经过softmax归一化得到注意力分配概率.
3.1.4 Attention机制本质思想
Attention机制可以看作,Target中每个单词对Source每个单词的加权求和,而权重是Source中每个单词对Target的每个单词重要程度.因此,Attention本质思想表示如下图;
- 将Source中构成元素看作一系列数据对,给定Target中某个元素Query和各个Key的相关性或者相似性即权重系数;对Value 进行加权求和,并得到最终Attention数值,公式如下:
A t t e n t i o n ( Q u e r y , S o u r c e ) = ∑ i = 1 L x S i m i l a r i t y ( Q u r e y , K e y i ) ∗ V a l u e i Attention(Query,Source) =\sum_{i=1}^{L_x} Similarity(Qurey,Key_i) * Value_i Attention(Query,Source)=i=1∑LxSimilarity(Qurey,Keyi)∗Valuei
- 深度学习中注意力机制中提到:Source中的Key和Value合二为一,指的是同一个东西,也即是输入句子中每个单词对应的语义编码,因此Attention计算转换为下面3个阶段.
- 输入由三部分组成:Query,Key和Value,其中(Key,Value)是具有相互关联的K对,Query是输入的’问题’,attention将Query转换成与Query相关向量表示
- Attention计算分为3步,如图所示;
- Attention 3步计算过程(举例说明)
- 第一步:Query和Key进行相似度计算,得到Attention Score;
- 第二步:对Attention Score进行Softmax归一化,得到权值矩阵;
- 第三步:权重矩阵与value进行加权求和
3.2 Hard Attention(硬注意)
- 在之前使用软性注意力的方式进行Attention机制,它通过注意力分布来加权融合各个输入向量,而硬性注意力机制不采用这种方式,它是根据注意力分布选择输入向量中的一个作为输出,有两种方式:
- 选择注意力分布中:分数最大的那一项对应的输入向量作为Attention机制输出
- 根据注意力机制分步进行随机采样,采样结果为Attention机制输出
硬注意力机制通过以上两种方法选择Attention的输出,这会使得最终的损失函数与注意力之间函数关系不可导,导致无法使用反向传播算法训练模型,硬性注意力通常需要使用强化学习来训练,因此,一般深度学习算法会使用软性注意力的方式进行计算
3.3 Self Attention:自注意
上面介绍Attention发生在Target元素Query 和Source中所有元素元素之间,而self Attention指的是Source内部元素之间或者Target内部元素之间发生的Attention机制,也可以理解为Target= source这种特殊情况下的注意力机制.
-
允许模型在处理序列数据时,不仅仅关注当前的元素,还能关注序列的其他元素,计算元素之间相似性,从而更好的捕捉长距离依赖关系自注意机制是Transfromer模型的核心部分,极大提高了模型的性能.
-
Attention发展主要经历两个阶段:
从上图可以看到,self Attention可以远距离捕捉到语义层面的特征(its 代指对象 Law)
应用传统的RNN,LSTM,在获取长距离语义特征和结构特征时候,需要按照序列顺序依次计算,距离越远的联系信息损耗越大,有效提取和捕获 的可能性越小
在self Attention,计算时,会直接将句子中任意两个token的联系通过一个计算步骤直接联系起来
注意力机制介绍2
1.注意力机制规则
- 需要三个指定的输入Q(query),K(key),V(value),通过公式计算得到注意力结果,这个结果代表query在key和value作用下注意力表示输入的Q=K=V时,称作自注意计算规则;当Q,K,V不想等时,称为一般注意力计算规则
例子:seq2seq架构翻译应用中的Q K V解释
- 模型架构包括三个部分:encoder(编码器),decoder(解码器),中间语义张量C
- 机器翻译加入Attention方式有两种:
- 第一种为tensorfiow(传统版本):
- 解释
查询张量Q:解码器每一步输入或者是当前输入的x
键张量K:编码部分每个时间步结果组合而成
值张量:编码每个时间步结果组合而成
介绍:
'欢迎来北京' 经过分词得到长度为3的词表,每个token经过GRU(门控制单元结构)输K=V(1,3)
的向量后与Q(预测结果上个隐藏状态hn或初始化的s0)并行计算k*q的转置得到分数值(1,3)的source经过softmax计算每个token在预测时的概率值后,与V相乘并按位相加输出得到长度为(1,3)向量后,与解码器经过GRU输出前的值拼接得到(1,6)向量输入到下一个GRU得到下个预测值.
- 第二种Pytorch版本:
- 查询张量Q: 解码器每一步的输出或者是当前输入的x
- 键张量K: 解码器上一步的隐藏层输出
- 值张量V:编码部分每个时间步输出结果组合而成
介绍:
输入'欢迎来北京'经过GRU输出按行拼接得到(3,3)矩阵,把编码器输出的隐藏状态K当做解码器输入部分,后解码翻译得到的Welcome转换为q(1,3)向量与解码器GRU输出隐藏层K拼接得到(1,6)后经过线性层(6,3)得到(1,3)后经过softmax计算得到预测下个结果的概率值与V(3,3)矩阵点积得到(1,3)向量后与Welcome转换的(1,3)向量拼接输入到下一个GRU来翻译下一个结果.
2.常见的注意计算规则
- 将Q,K进行纵轴拼接,做一次线性变换,在使用softmax处理后获得结果与V做张量乘法
A t t e n t i o n ( Q , K , V ) = S o f t m a x ( L i n e a r ( [ Q , K ] ) ) . V Attention(Q,K,V) = Softmax(Linear([Q,K])).V Attention(Q,K,V)=Softmax(Linear([Q,K])).V
- 将Q与K的转置做点积运算,然后除以一个缩放系数,在使用softmax处理后获得结果最后与V做张量
A t t e n t i o n ( Q , K , V ) = S o f t m a x ( Q . K T d k ) . V Attention(Q,K,V) = Softmax(\frac{Q.K_T}{\sqrt{d_k}}).V Attention(Q,K,V)=Softmax(dkQ.KT).V
- 为什么要使用深度神经网络中的引入注意力机制?
1.RNN等循环神经网络,随着时间步的增长,前面单词特征会遗忘,造成句子特征提取不充分
2.RNN等循环神经网络是一个时间步一个时间步的提取序列特征,效率低下
3.映入注意力机制进行并行计算
3.注意力机制作用
- 在解码器端注意力机制:能够根据模型目标有效聚焦编码器输出结果当做解码器的输入提升效果,改善以往编码器输出的单一长张量,无法存储过多特征问题
- 在编码器端注意力机制:主要解决表特征问题,相当于特征提取过程,得到输入注意力表示,一般使用自注意力
- 注意机制在网络中实现图形表示
4.注意力机制实现步骤;
4.1步骤
- 第一步:根据注意计算规则,对Q,K,V进行相应计算
- 第二步:根据第一步采用的计算方法, 如果是拼接方法,则需要将Q与第二步的计算结果再进行拼接, 如果是转置点积, 一般是自注意力, Q与V相同, 则不需要进行与Q的拼接.
- 第三步:最后为了使整个attention机制按照指定尺寸输出, 使用线性层作用在第二步的结果上做一个线性变换, 得到最终对Q的注意力表示.
4.2代码实现
任务描述:
有QKV:v是内容比如32个单词,每个单词64个特征,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
import torch.nn.functional as F
# MyAtt类实现思路分析
# 1 init函数 (self, query_size, key_size, value_size1, value_size2, output_size)
# 准备2个线性层 注意力权重分布self.attn 注意力结果表示按照指定维度进行输出层 self.attn_combine
# 2 forward(self, Q, K, V):
# 求查询张量q的注意力权重分布, attn_weights[1,32]
# 求查询张量q的注意力结果表示 bmm运算, attn_applied[1,1,64]
# q 与 attn_applied 融合,再按照指定维度输出 output[1,1,32]
# 返回注意力结果表示output:[1,1,32], 注意力权重分布attn_weights:[1,32]
class MyAtt(nn.Module):
# 32 32 32 64 32
def __init__(self, query_size, key_size, value_size1,value_size2, output_size):
super(MyAtt, self).__init__()
self.query_size = query_size
self.key_size = key_size
self.value_size1 = value_size1
self.value_size2 = value_size2
self.output_size = output_size
# 线性层1 注意力权重分布
self.attn = nn.Linear(self.query_size + self.key_size, self.value_size1)
# 线性层2 注意力结果表示按照指定维度输出层 self.attn_combine
self.attn_combine = nn.Linear(self.query_size+self.value_size2, output_size)
def forward(self, Q, K, V):
# 1 求查询张量q的注意力权重分布, attn_weights[1,32]
# [1,1,32],[1,1,32]--> [1,32],[1,32]->[1,64]
# [1,64] --> [1,32]
# tmp1 = torch.cat( (Q[0], K[0]), dim=1)
# tmp2 = self.attn(tmp1)
# tmp3 = F.softmax(tmp2, dim=1)
attn_weights = F.softmax( self.attn(torch.cat( (Q[0], K[0]), dim=-1)), dim=-1)
# 2 求查询张量q的结果表示 bmm运算, attn_applied[1,1,64]
# [1,1,32] * [1,32,64] ---> [1,1,64]
attn_applied = torch.bmm(attn_weights.unsqueeze(0), V)
# 3 q 与 attn_applied 融合,再按照指定维度输出 output[1,1,64]
# 3-1 q与结果表示拼接 [1,32],[1,64] ---> [1,96]
output = torch.cat((Q[0], attn_applied[0]), dim=-1)
# 3-2 shape [1,96] ---> [1,32]
output = self.attn_combine(output).unsqueeze(0)
# 4 返回注意力结果表示output:[1,1,32], 注意力权重分布attn_weights:[1,32]
return output, attn_weights
if __name__ == '__main__':
query_size = 32
key_size = 32
value_size1 = 32 # 32个单词
value_size2 = 64 # 64个特征
output_size = 32
Q = torch.randn(1, 1, 32)
K = torch.randn(1, 1, 32)
V = torch.randn(1, 32, 64)
# V = torch.randn(1, value_size1, value_size2)
# 1 实例化注意力类 对象
myattobj = MyAtt(query_size, key_size, value_size1, value_size2, 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)
- 输出结果
查询张量q的注意力结果表示output---> torch.Size([1, 1, 32]) tensor([[[ 0.3135, -0.0539, 0.0597, -0.0046, -0.3389, -0.1238, 1.0385,
0.8896, -0.0268, -0.0705, -0.8409, 0.6547, 0.5909, -0.6048,
0.6303, -0.2233, 0.7678, -0.3140, 0.3635, -0.3234, -0.1053,
0.5845, 0.1163, -0.2203, -0.0812, -0.0868, 0.0218, -0.0597,
0.6923, -0.1848, -0.8266, -0.0614]]], grad_fn=<UnsqueezeBackward0>)
查询张量q的注意力权重分布attn_weights---> torch.Size([1, 32]) tensor([[0.0843, 0.0174, 0.0138, 0.0431, 0.0110, 0.0308, 0.0608, 0.0216, 0.0101,
0.0406, 0.0462, 0.0111, 0.0349, 0.0065, 0.0383, 0.0526, 0.0151, 0.0193,
0.0294, 0.0632, 0.0322, 0.0072, 0.0294, 0.0388, 0.0135, 0.0443, 0.0594,
0.0332, 0.0117, 0.0168, 0.0293, 0.0344]], grad_fn=<SoftmaxBackward0>)