一. 自注意力机制之位置编码
1. 位置编码
在处理词元序列时,循环神经网络是逐个的重复地处理词元的,而自注意力则因为并行计算而放弃了顺序操作。为了使用序列的顺序信息,我们通过在输入表示中添加位置编码(positional encoding)来注入绝对的或相对的位置信息。位置编码可以通过学习得到也可以直接固定得到。下面使用基于正弦函数和余弦函数的固定位置编码:
假设输入表示包含一个序列中个词元的维嵌入表示。位置编码使用相同形状的位置嵌入矩阵输出,矩阵第行、第列和列上的元素为:
2. 位置编码实现
import torch
import d2l.torch
from torch import nn
class PositionalEncoding(nn.Module):
"""位置编码"""
def __init__(self,num_hiddens,dropout,max_len=1000):
super(PositionalEncoding,self).__init__()
self.dropout = nn.Dropout(dropout)
# 创建一个足够长的P
self.P = torch.zeros(size=(1,max_len,num_hiddens))
X = torch.arange(max_len,dtype=torch.float32).reshape(-1,1)/torch.pow(1000,torch.arange(0,num_hiddens,2,dtype=torch.float32)/num_hiddens)
self.P[:,:,0::2] = torch.sin(X)
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)
在位置嵌入矩阵 𝐏 中, 行代表词元在序列中的位置,列代表不同维度的位置编码。 如下面图所示我们可以看到位置嵌入矩阵的第 6 列和第 7 列的频率高于第 8 列和第 9 列。 第 6 列和第 7 列之间的偏移量(第 8 列和第 9 列相同)是由于正弦函数和余弦函数的交替。
encoding_dim,num_steps = 32,60
pos_encoding = PositionalEncoding(encoding_dim,0)
pos_encoding.eval()
X = pos_encoding(torch.zeros(size=(1,num_steps,encoding_dim)))
P = pos_encoding.P[:,:X.shape[1],:]
d2l.torch.plot(torch.arange(num_steps),Y=P[0,:,6:10].T,xlabel='Row (position)',figsize=(6,2.5),legend=['Column %d' %d for d in torch.arange(6,10)])
使用热力图显示每一个样本序列的位置编码:
P = P[0,:,:].unsqueeze(0).unsqueeze(0)
d2l.torch.show_heatmaps(P,xlabel='Column(encoding dimension)',ylabel='Row (position)',figsize=(3.5,4),cmap='Blues')
3. 相对位置信息
除了捕获绝对位置信息之外,上述的位置编码还允许模型学习得到输入序列中相对位置信息。这是因为对于任何确定的位置偏移,位置处的位置编码可以线性投影位置处的位置编码来表示。这种投影的数学解释是,令,对于任何确定的位置偏移,任何一对都可以线性投影到:
投影矩阵不依赖于任何位置的索引。
4. 小结
- 为了使用序列的顺序信息,可以通过在输入表示中添加位置编码,来注入绝对的或相对的位置信息。
5. 全部代码
import torch
import d2l.torch
from torch import nn
class PositionalEncoding(nn.Module):
"""位置编码"""
def __init__(self, num_hiddens, dropout, max_len=1000):
super(PositionalEncoding, self).__init__()
self.dropout = nn.Dropout(dropout)
# 创建一个足够长的P
self.P = torch.zeros(size=(1, max_len, num_hiddens))
X = torch.arange(max_len, dtype=torch.float32).reshape(-1, 1) / torch.pow(1000, torch.arange(0, num_hiddens, 2,
dtype=torch.float32) / num_hiddens)
self.P[:, :, 0::2] = torch.sin(X)
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(size=(1, num_steps, encoding_dim)))
P = pos_encoding.P[:, :X.shape[1], :]
d2l.torch.plot(torch.arange(num_steps), Y=P[0, :, 6:10].T, xlabel='Row (position)', figsize=(6, 2.5),
legend=['Column %d' % d for d in torch.arange(6, 10)])
P = P[0, :, :].unsqueeze(0).unsqueeze(0)
d2l.torch.show_heatmaps(P, xlabel='Column(encoding dimension)', ylabel='Row (position)', figsize=(3.5, 4), cmap='Blues')
6. 相关链接
注意力机制第一篇:李沐动手学深度学习V2-注意力机制注意力机制第二篇:李沐动手学深度学习V2-注意力评分函数注意力机制第三篇:李沐动手学深度学习V2-基于注意力机制的seq2seq注意力机制第四篇:李沐动手学深度学习V2-自注意力机制之位置编码注意力机制第五篇:李沐动手学深度学习V2-自注意力机制注意力机制第六篇:李沐动手学深度学习V2-多头注意力机制和代码实现注意力机制第七篇:李沐动手学深度学习V2-transformer和代码实现