Transformer中的位置编码PE(position encoding)
1.提出背景
transformer模型的attention机制并没有包含位置信息,即一句话中词语在不同的位置时在transformer中是没有区别的
2.解决背景
给encoder层和decoder层的输入添加了一个额外的向量Positional Encoding(PE),与embedding维度一致
embedding维度:
- 句子:词向量的维度大小,如512
- 图片:通道数
实际应用中,会对位置信息向量如[1,2,…,16]先进行位置编码,位置编码的维度可以先保持与句子编码维度一致,如512。再通过一个线性层,将维度降为需要的通道数,最后再进行信息合并。
3. 创建一个位置编码器PE
需要的输入设置
- 序列的最大长度 max_seq_len,如1000
- 编码向量的维度 d_model,如512或128
主体逻辑
- 计算PE矩阵
- 重新定义嵌入层:将嵌入层的权重替换为PE(不可训练)
PE为二维矩阵,大小跟输入embedding的维度一样,行表示词语,列表示词向量;pos 表示词语在句子中的位置;dmodel表示词向量的维度;i表示词向量的位置。因此,上述公式表示在每个词语的词向量的偶数位置添加sin变量,奇数位置添加cos变量,以此来填满整个PE矩阵,然后加到input embedding中去,这样便完成位置编码的引入了。
参考:https://blog.csdn.net/qq_34771726/article/details/102918440
class PositionalEncoding(nn.Module):
def __init__(self, max_seq_len: int, d_model: int):
super().__init__()
# Assume d_model is an even number for convenience
assert d_model % 2 == 0 # 为了编码方便
# ---1.计算PE矩阵
# 位置编码二维矩阵PE的大小: [max_seq_len, d_model]
pe = torch.zeros(max_seq_len, d_model) # 初始化为零矩阵
# 行:i向量 [0,1,2,..., 999] 表示每个时间步t
i_seq = torch.linspace(0, max_seq_len - 1, max_seq_len)
# 列:j向量 [0,2,4,6,8] 表示偶数位
j_seq = torch.linspace(0, d_model - 2, d_model // 2) #(0, 8, 5)
# 生成网格数据: 2个矩阵[1000, 5]
pos, two_i = torch.meshgrid(i_seq, j_seq)
pe_2i = torch.sin(pos / 10000**(two_i / d_model)) # 偶数位sin
pe_2i_1 = torch.cos(pos / 10000**(two_i / d_model)) # 奇数位cos
# stack拼接到第2个维度[0,1,2],在把3维重塑为2维
pe = torch.stack((pe_2i, pe_2i_1), 2).reshape(max_seq_len, d_model)
# ---2.定义嵌入层
self.embedding = nn.Embedding(max_seq_len, d_model) # 定义了一个嵌入层
self.embedding.weight.data = pe # 使用位置编码计算好的嵌入矩阵对其进行初始化
self.embedding.requires_grad_(False) # 将其参数设为不可训练
def forward(self, t):
# 调用嵌入层方法
# t表示抽取的时间点向量: [32, 43, 85, 31, 86, 90, 67, 61, 50, 33, 87, 48, 31, 48, 48, 93]
return self.embedding(t)
4.调用位置编码器PE
- 对输入的位置向量,如[1,2,…,16]. 先经过PE编码为词向量长度: [16, 1, 128]
- 与原图x拼接,即x+t
- 先经过一个线性层,将词向量维度转换为与图片通道数一致
- 与原图相加拼接
‘’‘
对输入时间点的位置编码
’‘’
# 1. 设置位置编码器PE
max_seq_len = 1000 # 最大序列长度
d_model = 128 # 编码向量的维度
pe = PositionalEncoding(max_seq_len, d_model)
pe
# 2.随机抽取时间点
n_steps = 100
batch_size = 16
t1 = torch.randint(0, n_steps, (batch_size, )) # 随机抽取16个时间点
# 3.对时间点进行PE编码
p1 = pe(t1)
p1 # 得到位置编码结果[1000, 128]
将编码后的时间与原图进行拼接
‘’‘
将编码后的时间与原图进行拼接
’‘’
pe_dim = 128 # 词向量维度
channel = 1 # 图片通道数C
n = 16 # 图片批量大小(也就是上面时间t的步数)
# 1.先经过一个线性层,将词向量维度转换为与图片通道数一致
# 设置映射空间层
pe_linear = nn.Sequential(nn.Linear(pe_dim, channel),
nn.ReLU(),
nn.Linear(channel, channel))
# 将128的词词向量维度转换为1的图片通道数
pe_v = pe_linear(p1).reshape(n, -1, 1, 1) # (1, 128) -> (1,1) -> (1,1,1)
# pe_linear: 降维 128 -> 1 通道数
# reshape: 整理维度 [n, C, 1, 1]
# 2.将整理后的位置编码与图片连接
# 原图
x = torch.randn(1, 28, 28)
# 拼接
x + pe_v
# [16, 1, 28, 28]
# 16: 数据批量大小(时间点的个数 - batch_size)
# 1: 通道数
# 28*28: 图片大小