注意力机制介绍
一.前言
本章节主要是了解什么是注意⼒计算规则以及常⻅的计算规则,了解什么是注意⼒机制及其作用以及掌握注意⼒机制的实现步骤.
二.注意力概念
我们观察事物时,之所以能够快速判断⼀种事物(当然允许判断是错误的), 是因为我们⼤脑能够很快把注意⼒放在事物最具有辨识度的部分从⽽作出判断,⽽并⾮是从头到尾的观察⼀遍事物后,才能有判断结果. 正是基于这样的理论,就产⽣了注意⼒机制.
三.注意力计算规则
它需要三个指定的输⼊Q(query), K(key), V(value), 然后通过计算公式得到注意⼒的结果, 这个结果代表 query在key和value作⽤下的注意⼒表示. 当输⼊的Q=K=V时, 称作⾃注意⼒计算规则.
Q, K, V的⽐喻解释:
假如我们有⼀个问题: 给出⼀段⽂本,使⽤⼀些关键词对它进⾏描述! 为了⽅便统⼀正确答案,这道题可能预先已经给⼤家写出了⼀些关键词作为提示.其中这些给出的提示就可 以看作是key, ⽽整个的⽂本信息就相当于是query,value的含义则更抽象,可以⽐作是你看到这段⽂本信息后,脑⼦ ⾥浮现的答案信息, 这⾥我们⼜假设⼤家最开始都不是很聪明,第⼀次看到这段⽂本后脑⼦⾥基本上浮现的信息就只有提示这 些信息, 因此key与value基本是相同的,但是随着我们对这个问题的深⼊理解,通过我们的思考脑⼦⾥想起来的 东⻄原来越多, 并且能够开始对我们query也就是这段⽂本,提取关键信息进⾏表示. 这就是注意⼒作⽤的过程, 通过 这个过程, 我们最终脑⼦⾥的value发⽣了变化, 根据提示key⽣成了query的关键词表示⽅法,也就是另外⼀种特征表示⽅法.
刚刚我们说到key和value⼀般情况下默认是相同,与query是不同的,这种是我们⼀般的注意⼒输⼊形 式, 但有⼀种特殊情况,就是我们query与key和value相同,这种情况我们称为⾃注意⼒机制,就如同我们 的刚刚的例⼦, 使⽤⼀般注意⼒机制,是使⽤不同于给定⽂本的关键词表示它. ⽽⾃注意⼒机制, 需要⽤给定⽂本⾃身来表达⾃⼰,也就是说你需要从给定⽂本中抽取关键词来表述它, 相当于对⽂本⾃身 的⼀次特征提取.
四.常见的注意力计算规则
将Q,K进⾏纵轴拼接, 做⼀次线性变化, 再使⽤softmax处理获得结果最后与V做张量乘法.
将Q,K进⾏纵轴拼接, 做⼀次线性变化后再使⽤tanh函数激活, 然后再进⾏内部求和, 最后使⽤ softmax处理获得结果再与V做张量乘法.
将Q与K的转置做点积运算, 然后除以⼀个缩放系数, 再使⽤softmax处理获得结果最后与V做张量乘法.
说明:当注意⼒权重矩阵和V都是三维张量且第⼀维代表为batch条数时, 则做bmm运算.bmm是⼀种 特殊的张量乘法运算.
bmm运算演示:
# 如果参数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])
五.什么是注意力机制
注意⼒机制是注意⼒计算规则能够应⽤的深度学习⽹络的载体, 同时包括⼀些必要的全连接层以及相关 张量处理, 使其与应⽤⽹络融为⼀体. 使⽤⾃注意⼒计算规则的注意⼒机制称为⾃注意⼒机制.
说明: NLP领域中, 当前的注意⼒机制⼤多数应⽤于seq2seq架构, 即编码器和解码器模型.
六.注意力机制的作用
在解码器端的注意⼒机制: 能够根据模型⽬标有效的聚焦编码器的输出结果, 当其作为解码器的输⼊时 提升效果. 改善以往编码器输出是单⼀定⻓张量, ⽆法存储过多信息的情况.
在编码器端的注意⼒机制: 主要解决表征问题, 相当于特征提取过程, 得到输⼊的注意⼒表示. ⼀般使⽤ ⾃注意⼒(self-attention).
注意⼒机制在⽹络中实现的图形表示:
七.注意力机制实现步骤
7.1 步骤
第⼀步: 根据注意⼒计算规则, 对Q,K,V进⾏相应的计算.
第⼆步: 根据第⼀步采⽤的计算⽅法, 如果是拼接⽅法,则需要将Q与第⼆步的计算结果再进⾏拼接, 如 果是转置点积, ⼀般是⾃注意⼒, Q与V相同, 则不需要进⾏与Q的拼接.
第三步: 最后为了使整个attention机制按照指定尺⼨输出, 使⽤线性层作⽤在第⼆步的结果上做⼀个线 性变换, 得到最终对Q的注意⼒表示.
7.2 代码实现
常⻅注意⼒机制的代码分析:
import torch
import torch.nn as nn
import torch.nn.functional as F
class Attn(nn.Module):def __init__(self, query_size, key_size, value_size1, value_size2,output_size):"""初始化函数中的参数有5个, query_size代表query的最后⼀维⼤⼩key_size代表key的最后⼀维⼤⼩, value_size1代表value的导数第⼆维⼤⼩,value = (1, value_size1, value_size2)value_size2代表value的倒数第⼀维⼤⼩, output_size输出的最后⼀维⼤⼩"""super(Attn, self).__init__()# 将以下参数传⼊类中self.query_size = query_sizeself.key_size = key_sizeself.value_size1 = value_size1self.value_size2 = value_size2self.output_size = output_size# 初始化注意⼒机制实现第⼀步中需要的线性层.self.attn = nn.Linear(self.query_size + self.key_size, value_size1)# 初始化注意⼒机制实现第三步中需要的线性层.self.attn_combine = nn.Linear(self.query_size + value_size2, output_size)def forward(self, Q, K, V):"""forward函数的输⼊参数有三个, 分别是Q, K, V, 根据模型训练常识, 输⼊给Attion机制的张量⼀般情况都是三维张量, 因此这⾥也假设Q, K, V都是三维张量"""# 第⼀步, 按照计算规则进⾏计算,# 我们采⽤常⻅的第⼀种计算规则# 将Q,K进⾏纵轴拼接, 做⼀次线性变化, 最后使⽤softmax处理获得结果attn_weights = F.softmax(self.attn(torch.cat((Q[0], K[0]), 1)), dim=1)# 然后进⾏第⼀步的后半部分, 将得到的权重矩阵与V做矩阵乘法计算,# 当⼆者都是三维张量且第⼀维代表为batch条数时, 则做bmm运算attn_applied = torch.bmm(attn_weights.unsqueeze(0), V)# 之后进⾏第⼆步, 通过取[0]是⽤来降维, 根据第⼀步采⽤的计算⽅法,# 需要将Q与第⼀步的计算结果再进⾏拼接output = torch.cat((Q[0], attn_applied[0]), 1)# 最后是第三步, 使⽤线性层作⽤在第三步的结果上做⼀个线性变换并扩展维度,得到输出# 因为要保证输出也是三维张量, 因此使⽤unsqueeze(0)扩展维度output = self.attn_combine(output).unsqueeze(0)return output, attn_weightsif __name__ == '__main__':query_size = 32key_size = 32value_size1 = 32value_size2 = 64output_size = 64attn = Attn(query_size, key_size, value_size1, value_size2, output_size)Q = torch.randn(1, 1, 32)K = torch.randn(1, 1, 32)V = torch.randn(1, 32, 64)out = attn(Q, K, V)print(out[0])print(out[1])
八.总结
注意力机制比较难理解,会在接下来的章节中案例中进⾏详尽的理解分析. 期待大家的点赞关注加收藏。