目录
- 注意力分数
- 注意力打分函数代码
- 掩蔽softmax操作
- 拓展到高纬度
- Additive Attention(加性注意力)
- 加性注意力代码
- 演示一下AdditiveAttention类
- 该部分总代码
- 注意力权重
- Scaled Dot-Product Attention(缩放点积注意力)
- 缩放点积注意力代码
- 演示一下DotProductAttention类
- 该部分总代码
- 注意力权重
- 总结
注意力分数
注意力打分函数代码
import math
import torch
from torch import nn
from d2l import torch as d2l
掩蔽softmax操作
import torch
from torch import nn
from d2l import torch as d2l
def masked_softmax(X, valid_lens):
"""通过在最后一个轴上遮蔽元素来执行softmax操作"""
if valid_lens is None:
# 如果valid_lens为空,则对X执行softmax操作
return nn.functional.softmax(X, dim=-1)
else:
# shape的形状为(2,2,4)
shape = X.shape
# 判断有效长度是否是一维的
if valid_lens.dim() == 1:
# valid_lens重复两次[2,3]→[2,2,3,3],和x的列数一样
valid_lens = torch.repeat_interleave(valid_lens, shape[1])
else:
# 将valid_lens重塑为一维向量
valid_lens = valid_lens.reshape(-1)
# 在X的最后一个维度(即:列)上进行遮蔽操作
X = d2l.sequence_mask(X.reshape(-1, shape[-1]), valid_lens, value=-1e6)
# 对遮蔽后的X执行softmax操作,并将形状还原为原始形状
return nn.functional.softmax(X.reshape(shape), dim=-1)
print(masked_softmax(torch.rand(2, 2, 4), torch.tensor([2, 3])))
print(masked_softmax(torch.rand(2,2,4), torch.tensor([[1,3],[2,4]])))
拓展到高纬度
Additive Attention(加性注意力)
(拓展到多维)
可学参数:
等价于将key和query合并起来后放入到一个隐藏大小为h输出大小为1的单隐藏层MLP。
它的好处是:key、value、query可以是任意的长度。
加性注意力代码
需要学习三个参数:key_size, query_size, num_hiddens
class AdditiveAttention(nn.Module):
"""加性注意力"""
def __init__(self, key_size, query_size, num_hiddens, dropout, **kwargs):
super(AdditiveAttention, self).__init__(**kwargs)
self.W_k = nn.Linear(key_size, num_hiddens, bias=False)
self.W_q = nn.Linear(query_size, num_hiddens, bias=False)
# 用于生成注意力分数的线性变换
self.w_v = nn.Linear(num_hiddens, 1, bias=False)
self.dropout = nn.Dropout(dropout)
def forward(self, queries, keys, values, valid_lens):
# queries的形状:(batch_size,查询的个数,num_hiddens),key是(batch_size,键的数目,num_hiddens)
# 两者不能直接相加
queries, keys = self.W_q(queries), self.W_k(keys)
# 执行加性操作,将查询和键相加
# queries加一维进去,变成了(batch_size,查询的个数,1,num_hiddens),key在第一维加一个维度,变成了(batch_size,1,键的数目,num_hiddens)
# 最后features变成了(batch_size,number_querys,number_keys,num_hiddens)
features = queries.unsqueeze(2) + keys.unsqueeze(1)
features = torch.tanh(features)
# features的形状:(batch_size,number_querys,number_keys,1)
# 使用线性变换生成注意力分数,并将最后一维的维度压缩掉
scores = self.w_v(features).squeeze(-1)
# 使用遮蔽softmax计算注意力权重
self.attention_weights = masked_softmax(scores, valid_lens)
# 根据注意力权重对values进行加权求和
return torch.bmm(self.dropout(self.attention_weights), values)
演示一下AdditiveAttention类
# queries是一个批量大小为2,1个query,query长度为20
# keys是一个批量大小为2,10个key,key的长度为2
queries, keys = torch.normal(0, 1, (2, 1, 20)), torch.ones((2, 10, 2))
# repeat(2, 1, 1)沿着第一个维度重复两次(共两个)
# values是一个批量大小为2,10个value,value的长度为4
values = torch.arange(40, dtype=torch.float32).reshape(1, 10, 4).repeat(2, 1, 1)
# 第一个样本看前两个,第二个样本看前6个
valid_lens = torch.tensor([2, 6])
# 创建加性注意力对象
attention = AdditiveAttention(key_size=2, query_size=20, num_hiddens=8, dropout=0.1)
attention.eval()
# 调用加性注意力对象的forward方法
print(attention(queries, keys, values, valid_lens))
该部分总代码
import math
import torch
from torch import nn
from d2l import torch as d2l
def masked_softmax(X, valid_lens):
"""通过在最后一个轴上遮蔽元素来执行softmax操作"""
if valid_lens is None:
# 如果valid_lens为空,则对X执行softmax操作
return nn.functional.softmax(X, dim=-1)
else:
# shape的形状为(2,2,4)
shape = X.shape
# 判断有效长度是否是一维的
if valid_lens.dim() == 1:
# valid_lens重复两次[2,3]→[2,2,3,3],和x的列数一样
valid_lens = torch.repeat_interleave(valid_lens, shape[1])
else:
# 将valid_lens重塑为一维向量
valid_lens = valid_lens.reshape(-1)
# 在X的最后一个维度(即:列)上进行遮蔽操作
X = d2l.sequence_mask(X.reshape(-1, shape[-1]), valid_lens, value=-1e6)
# 对遮蔽后的X执行softmax操作,并将形状还原为原始形状
return nn.functional.softmax(X.reshape(shape), dim=-1)
# 加性注意力
class AdditiveAttention(nn.Module):
"""加性注意力"""
def __init__(self, key_size, query_size, num_hiddens, dropout, **kwargs):
super(AdditiveAttention, self).__init__(**kwargs)
self.W_k = nn.Linear(key_size, num_hiddens, bias=False)
self.W_q = nn.Linear(query_size, num_hiddens, bias=False)
# 用于生成注意力分数的线性变换
self.w_v = nn.Linear(num_hiddens, 1, bias=False)
self.dropout = nn.Dropout(dropout)
def forward(self, queries, keys, values, valid_lens):
queries, keys = self.W_q(queries), self.W_k(keys)
features = queries.unsqueeze(2) + keys.unsqueeze(1)
features = torch.tanh(features)
scores = self.w_v(features).squeeze(-1)
self.attention_weights = masked_softmax(scores, valid_lens)
return torch.bmm(self.dropout(self.attention_weights), values)
queries, keys = torch.normal(0, 1, (2, 1, 20)), torch.ones((2, 10, 2))
values = torch.arange(40, dtype=torch.float32).reshape(1, 10, 4).repeat(2, 1, 1)
valid_lens = torch.tensor([2, 6])
attention = AdditiveAttention(key_size=2, query_size=20, num_hiddens=8, dropout=0.1)
attention.eval()
print(attention(queries, keys, values, valid_lens))
注意力权重
# 调用d2l.show_heatmaps函数,显示注意力权重的热图
d2l.show_heatmaps(attention.attention_weights.reshape((1,1,2,10)),
xlabel='Keys', ylabel='Queries')
Scaled Dot-Product Attention(缩放点积注意力)
如果query和key都是同样的长度,q、k∈ R d R^d Rd,那么可以:
向量化版本:(拓展到多维)
缩放点积注意力代码
好处是不需要学习参数
class DotProductAttention(nn.Module):
"""缩放点积注意力"""
def __init__(self, dropout, **kwargs):
super(DotProductAttention, self).__init__(**kwargs)
# Dropout层,用于随机丢弃一部分注意力权重
self.dropout = nn.Dropout(dropout)
def forward(self, queries, keys, values, valid_lens=None):
# 获取查询向量的维度d
d = queries.shape[-1]
# 计算点积注意力得分,并进行缩放
scores = torch.bmm(queries, keys.transpose(1, 2)) / math.sqrt(d)
# 使用遮蔽softmax计算注意力权重
self.attention_weights = masked_softmax(scores, valid_lens)
# 根据注意力权重对values进行加权求和
return torch.bmm(self.dropout(self.attention_weights), values)
演示一下DotProductAttention类
queries = torch.normal(0,1,(2,1,2))
attention = DotProductAttention(dropout=0.5)
attention.eval()
# 调用缩放点积注意力对象的forward方法
attention(queries, keys, values, valid_lens)
该部分总代码
import math
import torch
from torch import nn
from d2l import torch as d2l
def masked_softmax(X, valid_lens):
"""通过在最后一个轴上遮蔽元素来执行softmax操作"""
if valid_lens is None:
# 如果valid_lens为空,则对X执行softmax操作
return nn.functional.softmax(X, dim=-1)
else:
# shape的形状为(2,2,4)
shape = X.shape
# 判断有效长度是否是一维的
if valid_lens.dim() == 1:
# valid_lens重复两次[2,3]→[2,2,3,3],和x的列数一样
valid_lens = torch.repeat_interleave(valid_lens, shape[1])
else:
# 将valid_lens重塑为一维向量
valid_lens = valid_lens.reshape(-1)
# 在X的最后一个维度(即:列)上进行遮蔽操作
X = d2l.sequence_mask(X.reshape(-1, shape[-1]), valid_lens, value=-1e6)
# 对遮蔽后的X执行softmax操作,并将形状还原为原始形状
return nn.functional.softmax(X.reshape(shape), dim=-1)
class DotProductAttention(nn.Module):
"""缩放点积注意力"""
def __init__(self, dropout, **kwargs):
super(DotProductAttention, self).__init__(**kwargs)
# Dropout层,用于随机丢弃一部分注意力权重
self.dropout = nn.Dropout(dropout)
def forward(self, queries, keys, values, valid_lens=None):
# 获取查询向量的维度d
d = queries.shape[-1]
# 计算点积注意力得分,并进行缩放
scores = torch.bmm(queries, keys.transpose(1, 2)) / math.sqrt(d)
# 使用遮蔽softmax计算注意力权重
self.attention_weights = masked_softmax(scores, valid_lens)
# 根据注意力权重对values进行加权求和
return torch.bmm(self.dropout(self.attention_weights), values)
# keys是一个批量大小为2,10个key,key的长度为2
queries = torch.normal(0, 1, (2, 1, 2))
keys = torch.ones((2, 10, 2))
# repeat(2, 1, 1)沿着第一个维度重复两次(共两个)
# values是一个批量大小为2,10个value,value的长度为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)
# 设置为评估模式,不使用dropout
attention.eval()
# 调用缩放点积注意力对象的forward方法
print(attention(queries, keys, values, valid_lens))
注意力权重
# 调用d2l.show_heatmaps函数,显示注意力权重的热图
d2l.show_heatmaps(attention.attention_weights.reshape((1,1,2,10)),
xlabel='Keys', ylabel='Queries')
总结
注意力分数是query和key的相似度,注意力权重是分数的softmax结果。
两种常见的分数计算:
将query和key合并起来进入一个单输出单隐藏的MLP。(加性注意力)
直接将query和key做内积。(缩放点积注意力)