注意力评分函数

加性注意力评分函数

从上述例子中,知道a函数,注意力评分主要是用来衡量,query和key值得相似度。

加性注意力评分的公式:
a(q , k) = tranpose( W(v) ) * tanh( W(q) * q + W(k) * k )

本质上q是形状为 (batch_size, 查询次数,特征值)
本质上k是形状为 (batch_size, kv对数,特征值)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def masked_softmax(X, valid_lens):
"""通过在最后一个轴上掩蔽元素来执行softmax操作"""
# X:3D张量,valid_lens:1D或2D张量
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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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):
print("1 进入的queries和keys的shape")
# queries的形状:(batch_size,查询的个数,num_hidden)
# key的形状:(batch_size,“键-值”对的个数,num_hiddens)
print(queries.shape)
print(keys.shape)

# queries的形状:(batch_size,查询的个数,1,num_hidden)
# key的形状:(batch_size,1,“键-值”对的个数,num_hiddens)
queries, keys = self.W_q(queries), self.W_k(keys)
print("2 经给linear后,queries和keys的size")
print(queries.shape)
print(keys.shape)

## 维度拓展:
# queries的形状:(batch_size,查询的个数,1,num_hidden)
# key的形状:(batch_size,1,“键-值”对的个数,num_hiddens)
print("3 经给维度扩展,queries和keys的size")
queries_unsq = queries.unsqueeze(2)
keys_unsq = keys.unsqueeze(1)
print(queries_unsq.shape)
print(keys_unsq.shape)

# 使用广播方式进行求和
features = queries_unsq + keys_unsq

# features形状
print("4 queries和keys相加得到的features的size")
print(features.shape)

features = torch.tanh(features)
print("5 激活features之后的size")
print(features.shape)

scores = self.w_v(features)
print("6 经给wv之后的scores")
print(scores.shape)

scores = scores.squeeze(-1)
print("7 squeeze(-1)之后的scores")
print(scores.shape)

self.attention_weights = masked_softmax(scores, valid_lens)
print("8 masked_softmax之后的attention_weights")
print(self.attention_weights.shape)


res = torch.bmm(self.dropout(self.attention_weights), values)
print("9 value的shape")
print(values.shape)

print("10 res的shape")
print(res.shape)

return res
1
2
3
4
5
6
7
8
9
queries = torch.normal(0, 1, (2, 4, 20))
keys = 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()
attention(queries, keys, values, valid_lens)

得到的结果是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1 进入的queries和keys的shape
torch.Size([2, 4, 20])
torch.Size([2, 10, 2])
2 经给linear后,queries和keys的size
torch.Size([2, 4, 8])
torch.Size([2, 10, 8])
3 经给维度扩展,queries和keys的size
torch.Size([2, 4, 1, 8])
torch.Size([2, 1, 10, 8])
4 queries和keys相加得到的features的size
torch.Size([2, 4, 10, 8])
5 激活features之后的size
torch.Size([2, 4, 10, 8])
6 经给wv之后的scores
torch.Size([2, 4, 10, 1])
7 squeeze(-1)之后的scores
torch.Size([2, 4, 10])
8 masked_softmax之后的attention_weights
torch.Size([2, 4, 10])
9 value的shape
torch.Size([2, 10, 4])
10 res的shape
torch.Size([2, 4, 4])

可以看到最后的atteion_weight是2, 4, 10,相当于两个批量,4个查询,10个kv对的权重。
最后进行相乘,可以看到value的形状,(batch size, kv对的数目,特征数),其中kv对数目是需要事先确定好的。

本质上就是一个就是一个mlp,多层感知机。
我也不知道为什么这样的mlp可以怎么衡量query和key的相似程度?
只能认为是mlp具有学习功能,可以把相似的query和key联系起来。
我也不知道的理解是对还是错的。

缩放点积注意力评分函数

A点乘B的结果表示 A在B 方向上的投影与|B|的乘积,反映了两个向量在方向上的相似度,结果越大越相似。
这种方式衡量相似度,非常科学,且非常的快速。

Additive attention computes the compatibility function using a feed-forward network with a single hidden layer.
While the two are similar in theoretical complexity, dot-product attention is much faster and more space-efficient in practice,
since it can be implemented using highly optimized matrix multiplication code
意思就是缩放点积注意力评分函数比加性更快,更省内存。

缩放点积注意力公式:
a(q, k) = ( transpose(q) * k ) / sqrt(d)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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):
# queries的形状:(batch_size,查询的个数,num_hidden)
# key的形状:(batch_size,“键-值”对的个数,num_hiddens)
print("1 进入的queries和keys, value的shape")
print(queries.shape)
print(keys.shape)
print(values.shape)

d = queries.shape[-1]
print("2 d的长度")
print(d)

keys_t = keys.transpose(1,2)
print("3 transpose keys之后")
print(keys_t.shape)

# 设置transpose_b=True为了交换keys的最后两个维度
scores = torch.bmm(queries, keys_t) / math.sqrt(d)
print("4 bmm之后的scores的shape")
print(scores.shape)

self.attention_weights = masked_softmax(scores, valid_lens)
print("5 attention_weights的shape")
print(self.attention_weights.shape)

res = torch.bmm(self.dropout(self.attention_weights), values)
print("6 res的shape")
print(res.shape)

return res
1
2
3
4
queries = torch.normal(0, 1, (2, 1, 2))
attention = DotProductAttention(dropout=0.5)
attention.eval()
attention(queries, keys, values, valid_lens)

得到是一个res,( batch size, 查询次数,value特征个数),本质上就是得到了query个value

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import math
import torch
from torch import nn
from d2l import torch as d2l
import pandas as pd

# skip
def masked_softmax(X, valid_lens):
"""通过在最后一个轴上掩蔽元素来执行softmax操作"""
# X:3D张量,valid_lens:1D或2D张量
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)


# ok,self attention,输入q,k,v(query数目,和kv数目一致,d维度也一样)
# 输出得score是q对应每个v的特征加权和
class DotProductAttention(nn.Module):
"""缩放点积注意力"""

def __init__(self, dropout, **kwargs):
super(DotProductAttention, self).__init__(**kwargs)
self.dropout = nn.Dropout(dropout)

def forward(self, queries, keys, values, valid_lens=None):
d = queries.shape[-1]
# 设置transpose_b=True为了交换keys的最后两个维度
scores = torch.bmm(queries, keys.transpose(1, 2)) / math.sqrt(d)
self.attention_weights = masked_softmax(scores, valid_lens)
attention_weights_dropout = self.dropout(self.attention_weights)
## dropout 保持均值不变的情况下,把个别特征值设置为0

# 最关键是Q*K_T是什么意义?
# 设置1个batch,每个batch2个query,一个query有4个特征值, (1,2,4)
# 设置1个batch,每个bacth有2个kv值,每个kv值由4个特征值, (1,2,4)

# query:
# q11,q12,q13,q14
# q21,q22,q23,q24

# key:
# k11,k12,k13,k14
# k21,k22,k23,k24

# key_T
# k11,k21
# k12,k22
# k13,k23
# k14,k24

# Q*K_T/sqrt(d) = (1,2,4) * (1,4,2) = (1,2,2), 也就是每个query对应的key的相似度
# a11=q11*k11 + q12*k12 + q13*k13 + q14*k14 q1和k1
# a12=q11*k21 + q12*k22 + q13*k23 + q14*k24 q1和k2
# a21=q21*k11 + q22*k12 + q23*k13 + q24*k14 q2和k1
# a22=q21*k21 + q22*k22 + q23*k23 + q24*k24 q2和k2

# a11,a12
# a21,a22
# a11是query1 对应 key1的相似度

# AT * Val = (1,2,2) * (1,2,4) = (1, 2, 4)
# value:
# v11, v12, v13, v14
# v21, v22, v23, v24

# a11*v11+a12*v21, a11*v12+a12*v22, a11*v13+a12*v23, a11*v14+a12*v24
# 解读:
# score11 = q1和k1相似度*v1的特征1 + q1和k2相似度*v2的特征1
# score12 = q1和k1相似度*v1的特征2 + q1和k2相似度*v2的特征2
# score13 = q1和k1相似度*v1的特征3 + q1和k2相似度*v2的特征3
# score14 = q1和k1相似度*v1的特征4 + q1和k2相似度*v2的特征4

# a21*v11+a22*v21, a21*v12+a22*v22, a21*v13+a22*v23, a21*v14+a22*v24
# 解读:
# score21 = q2和k1相似度*v1的特征1 + q2和k2相似度*v2的特征1
# score22 = q2和k1相似度*v1的特征2 + q2和k2相似度*v2的特征2
# score23 = q2和k1相似度*v1的特征3 + q2和k2相似度*v2的特征3
# score24 = q2和k1相似度*v1的特征4 + q2和k2相似度*v2的特征4
# 也就是一个score(i,j)第一个索引是代表query(i),第二个索引是代表所有的value在j特征的加权和,

res = torch.bmm(attention_weights_dropout, values)
return res