10.3 注意力评分函数

发布时间 2023-09-09 23:28:26作者: Ann-

1.torch.bmm()的用法

先说一般的矩阵乘法torch.mm()。torch.mm()用于将两个二维张量(矩阵)相乘,求它们的叉乘结果。如:

 我们创建一个2*3的矩阵A,3*4的矩阵B,它们的值都初始化为均值为0方差为1的标准正态分布,用torch.mm()求它们的叉乘结果:

import torch
from torch import nn
from d2l import torch as d2l

A = torch.normal(0,1,(2,3))
B = torch.normal(0,1,(3,4))
AB = torch.mm(A,B)
print(AB)

输出:

torch.mm()是求一个矩阵乘以一个矩阵的结果,它的两个参数都是二维张量。而torch.bmm()是求一个批量的矩阵乘以一个批量的矩阵的结果,它的两个参数都是三维张量,其中第一维表示了这一批矩阵的数量。如:

A = torch.normal(0,1,(3,2,3))
B = torch.normal(0,1,(3,3,4))
AB = torch.bmm(A,B)
print(AB)

输出:

 注意bmm操作要求两个三维张量维度的第一个参数必须相等:如这里是(3,2,3)的张量和(3,3,4)的张量,它们的第一个参数都是3.

2.掩蔽softmax操作

在有些情况下,并非所有的值都是有意义的,

#@save
def masked_softmax(X, valid_lens):
    """通过在最后一个轴上掩蔽元素来执行softmax操作"""
    # X:3D张量,valid_lens:1D或2D张量
  #如果没有有效长度,即所有值均有效,那么就是直接返回softmax操作的结果 if valid_lens is None: return nn.functional.softmax(X, dim=-1) else: shape = X.shape if valid_lens.dim() == 1: valid_lens = torch.repeat_interleave(valid_lens, shape[1]) else: valid_lens = valid_lens.reshape(-1) # 最后一轴上被掩蔽的元素使用一个非常大的负值替换,从而其softmax输出为0 X = d2l.sequence_mask(X.reshape(-1, shape[-1]), valid_lens, value=-1e6) return nn.functional.softmax(X.reshape(shape), dim=-1)

dim=-1的用法:对于三维张量(C,H,W),做softmax就有三种方式:

1.dim=0就是对每一个维度对应位置的数值构成的向量做softmax运算。

2.dim=1就是对某一个维度的一列数值构成的向量做softmax运算。

3.dim=2就是对某一个维度的一行数值构成的向量做softmax运算。

 

 

这里面的dim=-1相当于dim=2. 那为什么不直接写dim=2?应该是为了这个函数能适应不同维度的X输入,若输入X是3维的,则softmax(X,dim=-1)相当于softmax(X,dim=2),若输入X是2维的,则softmax(X,dim=-1)相当于softmax(X,dim=1)。

torch.repeat_interleave()的用法:

a = torch.arange(6).reshape(2,1,3)
res = torch.repeat_interleave(a,3,dim = 1) #张量a在第1维(行)上重复3遍
print(a)
print(res)
print(a.shape)
print(res.shape)

运行结果:

 看一下这个masked_softmax(X,valid_lens)函数的效果。考虑样本为2个2*4的矩阵,即(2,2,4)的矩阵:

 这里面valid_lens为[2,3]时,意思是:第一个矩阵中的每一行数据都是前2个是有效值,第二个矩阵中的每一行数据都是前3个是有效值。

       valid_lens为[ [1,3],[2,4] ]时,意思是:第一个矩阵中第一行前1个是有效值,第一个矩阵中第二行前3个是有效值,第二个矩阵第一行前2个是有效值,第二个矩阵中第二行是前4个是有效值。

 

3.加性注意力

其中可学习的参数是:

 它等价于将q和k拼接到一起,丢到一个隐藏层大小为h,输出大小为1的单隐藏层MLP中去。Wq、Wk拼接起来就是隐藏层的参数。

(这里的代码没怎么理解,后面再补充)

 

4.缩放点积注意力

两个同维度并且模相等的向量,它们的点积越大,就表示它们越相似。当query和key具有相同的长度d时,我们可以使用缩放点积作为评分函数。假设query和key都遵循均值为0,方差为1的标准正态分布,那么q和k的点积就遵循均值为0,方差为d的正态分布。我们为了确保无论向量长度d是多长,q和k点积的方差仍然是1,将点积再除以√d,得到缩放点积注意力的评分函数:

 这是一个查询q的情况。考虑有n个查询query,m个key-value pair,其中query和key的长度为d,value的长度为v,那么,

的缩放点积注意力为:

 这里面QKτ / √d (维度为n*m) 的第 i 行第 j 列的值就表示第 i 个query对第 j 个key的注意力分数。再做softmax得到的就是注意力权重,再乘以V得到的就是注意力汇聚的结果。

 下面实现的缩放点积注意力使用了dropout进行模型正则化:

#缩放点积注意力
#@save
class DotProductAttention(nn.Module):
    """缩放点积注意力"""
    def __init__(self, dropout, **kwargs):
     #这里的参数暂时没理解 super(DotProductAttention, self).
__init__(**kwargs) self.dropout = nn.Dropout(dropout) # queries的形状:(batch_size,查询的个数,d) # keys的形状:(batch_size,“键-值”对的个数,d) # values的形状:(batch_size,“键-值”对的个数,值的维度) # valid_lens的形状:(batch_size,)或者(batch_size,查询的个数) def forward(self, queries, keys, values, valid_lens=None): d = queries.shape[-1] # keys.transpose(1,2)的意思是将key这个三维张量在1和2这两个维度上转置() scores = torch.bmm(queries, keys.transpose(1,2)) / math.sqrt(d) self.attention_weights = masked_softmax(scores, valid_lens) return torch.bmm(self.dropout(self.attention_weights), values)

下面演示一下这个类。在下面的代码中,我们设计了queries维度为(2,1,2),含义是一个批量有两个矩阵,每个矩阵有1个查询,长度为2,那么,key和value也应该一个批量是2个矩阵。我们设计了key-value pair的数量为10,并且key的长度等同于query的长度,value的长度为4.

queries = torch.normal(0, 1, (2, 1, 2))
keys = torch.ones((2, 10, 2))
# values的小批量,两个值矩阵是相同的
#values的维度被repeat成了(2,10,4)
values = torch.arange(40, dtype=torch.float32).reshape(1, 10, 4).repeat(
    2, 1, 1)
valid_lens = torch.tensor([2, 6])
attention = DotProductAttention(dropout=0.5)
attention.eval()
print(attention(queries, keys, values, valid_lens))
d2l.show_heatmaps(attention.attention_weights.reshape((1, 1, 2, 10)),
                  xlabel='Keys', ylabel='Queries')

运行结果:

 

 总结:

注意力分数是query和key的相似度,是没有被normalize过的,而注意力权重是注意力分数softmax的结果。两种常见的注意力分数计算:

1. 将query和key合并起来进入一个单输出单隐藏层的MLP。(加性注意力)

2.直接将query和key做内积。(点积注意力)