目录
- 自注意力和位置编码
- 自注意力
- 位置编码
- 绝对位置信息
- 相对位置信息
- 代码实现
- 导入模块
- 自注意力
- 位置编码
自注意力和位置编码
自注意力
自注意力池化层将xi当作key, value, query来对序列特征得到yi
与CNN、RNN进行比较:
最长路径:信息从序列前端的某个位置传递到末端的某个位置的路径
self-attention在长句子中虽然计算复杂度很好,但能很快地抓取距离很远的信息(适合处理较长的序列,付出了计算复杂度的代价,计算量超大)
位置编码
与CNN/RNN不同,自注意力并没有记录位置信息
在处理词元序列时,循环神经网络是逐个的重复地处理词元的, 而自注意力则因为并行计算而放弃了顺序操作。 为了使用序列的顺序信息,我们通过在输入表示中添加位置编码来注入绝对的或相对的位置信息。 位置编码可以通过学习得到也可以直接固定得到。
想法:不改变本身的自注意力机制,将位置信息加入输入中去。
接下来,我们描述的是基于正弦函数和余弦函数的固定位置编码:
不同的列周期不一样
在位置嵌入矩阵P中, 行代表词元在序列中的位置,列代表位置编码的不同维度。 在下面的例子中,我们可以看到位置嵌入矩阵的第6列和第7列的频率高于第8列和第9列。 第6列和第7列之间的偏移量(第8列和第9列相同)是由于正弦函数和余弦函数的交替。
绝对位置信息
为了明白沿着编码维度单调降低的频率与绝对位置信息的关系, 让我们打印出0,1,…,7的二进制表示形式。 正如我们所看到的,每个数字、每两个数字和每四个数字上的比特值 在第一个最低位、第二个最低位和第三个最低位上分别交替。
0的二进制是:000
1的二进制是:001
2的二进制是:010
3的二进制是:011
4的二进制是:100
5的二进制是:101
6的二进制是:110
7的二进制是:111
在二进制表示中,较高比特位的交替频率低于较低比特位, 与下面的热图所示相似,只是位置编码通过使用三角函数在编码维度上降低频率。 由于输出是浮点数,因此此类连续表示比二进制表示法更节省空间。
相对位置信息
除了捕获绝对位置信息之外,上述的位置编码还允许模型学习得到输入序列中相对位置信息。 这是因为对于任何确定的位置偏移δ,位置i+δ处 的位置编码可以线性投影位置i处的位置编码来表示。
小结:
- 在自注意力chi话层将xi当作key,value和query来对序列抽取特征
- 卷积神经网络和自注意力都拥有并行计算的优势,而且自注意力的最大路径长度最短。但是因为其计算复杂度是关于序列长度的二次方,所以在很长的序列中计算会非常慢。(完全并行、最长序列为1,长序列计算复杂度高)
- 为了使用序列的顺序信息,我们可以通过在输入表示中添加位置编码,来注入绝对的或相对的位置信息。
代码实现
导入模块
import torch
from torch import nn
from d2l import torch as d2l
from matplotlib import pyplot as plt
自注意力
根据定义的注意力池化函数f。 下面的代码片段是基于多头注意力对一个张量完成自注意力的计算, 张量的形状为(批量大小,时间步的数目或词元序列的长度,d)。 输出与输入的张量形状相同。
num_hiddens, num_heads = 100, 5
attention = d2l.MultiHeadAttention(num_hiddens, num_hiddens, num_hiddens,
num_hiddens, num_heads, 0.5)
attention.eval()
其中MultiHeadAttention结构如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jZIwXg71-1644311711181)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220208154003164.png)]
batch_size, num_queries, valid_lens = 2, 4, torch.tensor([3, 2])
X = torch.ones((batch_size, num_queries, num_hiddens))
attention(X, X, X, valid_lens).shape
位置编码
class PositionalEncoding(nn.Module):
"""位置编码"""
#num+hiddens:向量长度 max_len:序列最大长度
def __init__(self, num_hiddens, dropout, max_len=1000):
super(PositionalEncoding, self).__init__()
self.dropout = nn.Dropout(dropout)
# 创建一个足够长的P : (1, 1000, 32)
self.P = torch.zeros((1, max_len, num_hiddens))
#本例中X的维度为(1000, 16)
X = torch.arange(max_len, dtype=torch.float32).reshape(
-1, 1) / torch.pow(10000, torch.arange(0, num_hiddens, 2, dtype=torch.float32) / num_hiddens)
self.P[:, :, 0::2] = torch.sin(X) #::2意为指定步长为2 为[start_index : end_index : step]省略end_index的写法
self.P[:, :, 1::2] = torch.cos(X)
def forward(self, X):
X = X + self.P[:, :X.shape[1], :].to(X.device)
return self.dropout(X)
encoding_dim, num_steps = 32, 60
pos_encoding = PositionalEncoding(encoding_dim, 0)
pos_encoding.eval()
X = pos_encoding(torch.zeros((1, num_steps, encoding_dim)))
P = pos_encoding.P[:, :X.shape[1], :]
d2l.plot(torch.arange(num_steps), P[0, :, 6:10].T, xlabel='Row (position)',
figsize=(6, 2.5), legend=["Col %d" % d for d in torch.arange(6, 10)])
绘图如下所示: