目录
1、什么是注意力
- 当我们观察事物时,之所以能够很快判断出一种事物是对是错,是因为我们的大脑能够很快把注意力放在事物具有辨识度的部分从而做出判断。而并非是从头到尾观察的观察一遍事物之后才能有判断结果,正是基于这样的理论就产生了注意力机制
2、什么是注意力计算规则
- 它需要三个指定的输入Q(query)、k(key)、V(value),然后通过公式得到注意力的计算结果,这个结果代表 query 在 key 和 value 作用下的表示。通常这个计算表达式是多样的
- Q、K、V的比喻解释
假如有一个问题:给出一段文本,使用一些关键词对它进行描述
为了方便统一正确的答案,这道题可能预先已经给大家写出一些关键字作为提示。其中这些给定的提示词就可以看作为 key,而整个文本信息就相当于是 query。而 value 可以比作你看到这段文本信息之后自己得到的答案信息。
这里假设第一次读完之后脑海中只有这几个关键词,换而言之也就是 value == key,但是随着我们对这段文本信息的不断深入研究,脑海中积累的东西越来越多,并且能够开始对这段文本提取关键信息进行表示。这就是注意力作用的过程,而通过这个过程,我们最终脑海中的 value 会发生明显的变化。根据提示 key 生成了 query 的关键字词表示方法,也就是另外一种特征表示的方法。
一般情况下,key 和 value 默认是相同的,而与 query 是不同的,这是注意力的一般输入形式,但是有一种特殊形式,就是 query 与 key 和 value 相同,这种情况就是自注意力机制。
使用一般注意力机制,是使用不同于给定文本的关键词表示它,而自注意力机制,需要用给定文本自身来表达自己,也就是你需要从给定文本中抽取关键词来描述它,相当于对文本自身的一次特征提取。
- 什么是注意力机制
注意力机制是注意力计算规则能够应用的深度学习网络的载体,除了注意力计算规则以外,还包括一些必要的全连接层以及相关的张量处理,使其余网络应用融为一体,使用自注意力计算规则的注意力机制称为自注意力机制
- 注意力机制在网络中实现的图形表示
3、代码演示
python
x = torch.rand(5,5)
print(x)
mask = torch.zeros(5,5)
print(mask)
y = torch.masked_fill(x,mask==0,-1e9)
print(y)
tensor([[0.2997, 0.7500, 0.0364, 0.2789, 0.1416],
[0.7921, 0.9282, 0.0420, 0.3505, 0.2455],
[0.9167, 0.2907, 0.5283, 0.2175, 0.6954],
[0.1894, 0.1151, 0.5096, 0.2843, 0.2340],
[0.8615, 0.0076, 0.2582, 0.1468, 0.6379]])
tensor([[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.]])
tensor([[-1.0000e+09, -1.0000e+09, -1.0000e+09, -1.0000e+09, -1.0000e+09],
[-1.0000e+09, -1.0000e+09, -1.0000e+09, -1.0000e+09, -1.0000e+09],
[-1.0000e+09, -1.0000e+09, -1.0000e+09, -1.0000e+09, -1.0000e+09],
[-1.0000e+09, -1.0000e+09, -1.0000e+09, -1.0000e+09, -1.0000e+09],
[-1.0000e+09, -1.0000e+09, -1.0000e+09, -1.0000e+09, -1.0000e+09]])
4、注意力代码实现
python
import torch
import torch.nn as nn
import math
from torch.autograd import Variable
# 构建Embedding类来实现文本嵌入层
class Embeddings(nn.Module):
def __init__(self,vocab,d_model):
"""
:param vocab: 词表的大小
:param d_model: 词嵌入的维度
"""
super(Embeddings,self).__init__()
self.lut = nn.Embedding(vocab,d_model)
self.d_model = d_model
def forward(self,x):
"""
:param x: 因为Embedding层是首层,所以代表输入给模型的文本通过词汇映射后的张量
:return:
"""
return self.lut(x) * math.sqrt(self.d_model)
import torch
from torch.autograd import Variable
import math
import torch.nn as nn
class PositionalEncoding(nn.Module):
def __init__(self,d_model,dropout,max_len=5000):
"""
:param d_model: 词嵌入的维度
:param dropout: 随机失活,置0比率
:param max_len: 每个句子的最大长度,也就是每个句子中单词的最大个数
"""
super(PositionalEncoding,self).__init__()
self.dropout = nn.Dropout(p=dropout)
pe = torch.zeros(max_len,d_model) # 初始化一个位置编码器矩阵,它是一个0矩阵,矩阵的大小是max_len * d_model
position = torch.arange(0,max_len).unsqueeze(1) # 初始一个绝对位置矩阵 max_len * 1
div_term = torch.exp(torch.arange(0,d_model,2)*-(math.log(1000.0)/d_model)) # 定义一个变换矩阵,跳跃式的初始化
# 将前面定义的变换矩阵进行奇数、偶数的分别赋值
pe[:,0::2] = torch.sin(position*div_term)
pe[:,1::2] = torch.cos(position*div_term)
pe = pe.unsqueeze(0) # 将二维矩阵扩展为三维和embedding的输出(一个三维向量)相加
self.register_buffer('pe',pe) # 把pe位置编码矩阵注册成模型的buffer,对模型是有帮助的,但是却不是模型结构中的超参数或者参数,不需要随着优化步骤进行更新的增益对象。注册之后我们就可以在模型保存后重加载时,将这个位置编码与模型参数一同加载进来
def forward(self, x):
"""
:param x: 表示文本序列的词嵌入表示
:return: 最后使用self.dropout(x)对对象进行"丢弃"操作,并返回结果
"""
x = x + Variable(self.pe[:, :x.size(1)],requires_grad = False) # 不需要梯度求导,而且使用切片操作,因为我们默认的max_len为5000,但是很难一个句子有5000个词汇,所以要根据传递过来的实际单词的个数对创建的位置编码矩阵进行切片操作
return self.dropout(x)
import numpy as np
import torch
def subsequent_mask(size):
"""
:param size: 生成向后遮掩的掩码张量,参数 size 是掩码张量的最后两个维度大小,它的最后两个维度形成一个方阵
:return:
"""
attn_shape = (1,size,size) # 定义掩码张量的形状
subsequent_mask = np.triu(np.ones(attn_shape),k = 1).astype('uint8') # 定义一个上三角矩阵,元素为1,再使用其中的数据类型变为无符号8位整形
return torch.from_numpy(1 - subsequent_mask) # 先将numpy 类型转化为 tensor,再做三角的翻转,将位置为 0 的地方变为 1,将位置为 1 的方变为 0
python
import math
import torch
from torch import tensor, softmax
def attention(query, key, value, mask = None, dropout = None):
"""
:param query: 三个张量输入
:param key: 三个张量输入
:param value: 三个张量输入
:param mask: 掩码张量
:param dropout: 传入的 dropout 实例化对象
:return:
"""
d_model = query.size(-1) # 得到词嵌入的维度,取 query 的最后一维大小
scores = torch.matmul(query,key.transpose(-2,-1)) / math.sqrt(d_model) # 按照注意力公式,将 query 和 key 的转置相乘,这里是将 key 的最后两个维度进行转置,再除以缩放系数,得到注意力得分张量 scores
if mask is not None:
scores = torch.masked_fill(scores,mask == 0,-1e9) # 使用 tensor 的 mask_fill 方法,将掩码张量和 scores 张量中每一个位置进行一一比较,如果掩码张量处为 0 ,则使用 -1e9 替换
# scores = scores.masked_fill(mask == 0,-1e9)
p_attn = softmax(scores, dim = -1) # 对 scores 的最后一维进行 softmax 操作,使用 F.softmax 方法,第一个参数是 softmax 对象,第二个参数是最后一个维度,得到注意力矩阵
print('scores.shape ',scores.shape)
if dropout is not None:
p_attn = dropout(p_attn)
return torch.matmul(p_attn,value),p_attn # 返回注意力表示
python
# 实例化参数
d_model = 512
dropout = 0.1
max_len = 60 # 句子最大长度
# 输入 x 是 Embedding层输出的张量,形状为 2 * 4 * 512
x = Variable(torch.LongTensor([[100,2,42,508],[491,998,1,221]]))
emb = Embeddings(1000,512) # 嵌入层
embr = emb(x)
print('embr.shape:',embr.shape) # 2 * 4 * 512
pe = PositionalEncoding(d_model, dropout,max_len) # 位置编码
pe_result = pe(embr)
query = key = value = pe_result
attn ,p_attn = attention(query,key,value)
print('attn ',attn)
print('attn.shape ',attn.shape)
print('p_attn ',p_attn)
print('p_attn.shape ',p_attn.shape)
embr.shape: torch.Size([2, 4, 512])
scores.shape torch.Size([2, 4, 4])
attn tensor([[[ 14.6400, 26.9103, 0.0000, ..., 13.9270, -6.5191, 32.4080],
[-34.1280, 19.2188, 19.4858, ..., 15.7109, 10.0917, -8.3122],
[ 20.5220, -67.1927, -21.7284, ..., -21.4821, 15.7020, 22.7759],
[ 17.4563, -19.7163, 10.3354, ..., -27.8732, 40.4710, 43.8462]],
[[ 3.1333, -11.7184, -13.3821, ..., -30.2729, -22.0316, 27.8263],
[ -1.0876, -11.2612, 17.5255, ..., -4.7157, 0.0000, 31.7973],
[ -3.5455, -39.6752, -22.7584, ..., 14.7211, -6.5217, -7.8053],
[ 14.3607, -34.6090, -34.4293, ..., 0.0000, 43.7119, -33.4485]]],
grad_fn=<UnsafeViewBackward>)
attn.shape torch.Size([2, 4, 512])
p_attn tensor([[[1., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 0., 1., 0.],
[0., 0., 0., 1.]],
[[1., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 0., 1., 0.],
[0., 0., 0., 1.]]], grad_fn=<SoftmaxBackward>)
p_attn.shape torch.Size([2, 4, 4])
加入掩码操作
python
# 实例化参数
d_model = 512
dropout = 0.1
max_len = 60 # 句子最大长度
# 输入 x 是 Embedding层输出的张量,形状为 2 * 4 * 512
x = Variable(torch.LongTensor([[100,2,42,508],[491,998,1,221]]))
emb = Embeddings(1000,512) # 嵌入层
embr = emb(x)
print('embr.shape:',embr.shape) # 2 * 4 * 512
pe = PositionalEncoding(d_model, dropout,max_len) # 位置编码
pe_result = pe(embr)
query = key = value = pe_result
mask = torch.zeros(2,4,4)
attn ,p_attn = attention(query,key,value,mask=mask)
print('attn ',attn)
print('attn.shape ',attn.shape)
print('p_attn ',p_attn)
print('p_attn.shape ',p_attn.shape)
embr.shape: torch.Size([2, 4, 512])
scores.shape torch.Size([2, 4, 4])
attn tensor([[[ -9.6782, -4.7182, 10.1894, ..., -14.4487, -19.1498, 17.2041],
[ -9.6782, -4.7182, 10.1894, ..., -14.4487, -19.1498, 17.2041],
[ -9.6782, -4.7182, 10.1894, ..., -14.4487, -19.1498, 17.2041],
[ -9.6782, -4.7182, 10.1894, ..., -14.4487, -19.1498, 17.2041]],
[[ -1.6496, 15.2794, 24.1114, ..., -7.3236, 6.2080, 5.8881],
[ -1.6496, 15.2794, 24.1114, ..., -7.3236, 6.2080, 5.8881],
[ -1.6496, 15.2794, 24.1114, ..., -7.3236, 6.2080, 5.8881],
[ -1.6496, 15.2794, 24.1114, ..., -7.3236, 6.2080, 5.8881]]],
grad_fn=<UnsafeViewBackward>)
attn.shape torch.Size([2, 4, 512])
p_attn tensor([[[0.2500, 0.2500, 0.2500, 0.2500],
[0.2500, 0.2500, 0.2500, 0.2500],
[0.2500, 0.2500, 0.2500, 0.2500],
[0.2500, 0.2500, 0.2500, 0.2500]],
[[0.2500, 0.2500, 0.2500, 0.2500],
[0.2500, 0.2500, 0.2500, 0.2500],
[0.2500, 0.2500, 0.2500, 0.2500],
[0.2500, 0.2500, 0.2500, 0.2500]]], grad_fn=<SoftmaxBackward>)
p_attn.shape torch.Size([2, 4, 4])