这篇文章,主要是自己学习的一个记录,如果不自己写下来,之前搞懂的东西,很快也会忘掉。还是得踏踏实实的慢慢来啊(🐷)
下面的是transformer中的encoding的代码的一部分,这部分代码加上了位置编码,利用sin和cos来巧妙的表示位置信息。
这里为什么说是巧妙呢?
因为位置信息可以随着输入文本的长度变得很长,如果只用1234这样的顺序,很难有效表示位置关系。
而利用sin和cos,可以表示非常多的位置关系。为了能表示更多的位置关系,这里将sin和cos中除以10000。
代码如下
class PositionalEncoding(nn.Module):
def __init__(self, d_model, dropout=0.1, max_len=5000):
super(PositionalEncoding, self).__init__()
self.dropout = nn.Dropout(p=dropout)
pe = torch.zeros(max_len, d_model)
#生成0向量,生成矩阵pe[max_len, d_model]
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
#torch.arange(0, max_len)生成张量维度为(max_len,),可以看作是一个包含max_len个元素的行向量,
#unsqueeze(1)表示在维度=1的地方插入一个新的维度
#torch.squeeze()表示对数据维度进行压缩,去掉维数为1的维度,比如一行或者一列这种。squeeze(a)表示将
#a中所有为1的维度删除,不为1的维度没有影响
#torch.unsqueeze(a,N)表示在a中指定位置N加上一个维数为1的维度
print(torch.arange(0, max_len, dtype=torch.float))
print(position)
div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
#torch.arange(0, d_model, 2)这一步表示生成一个0-d_model维度的张量,然后以步长为2递增赋值
print(position.shape)
print(div_term.shape)
pe[:, 0::2] = torch.sin(position * div_term)
#对pe[:, 0::2]用法做出解释。:在切片中表示对整个轴进行选择,[:,...]表示对所有行进行选择
#...表示可以有任意数量的维度;0::2是对列进行选择的部分,0表示从第0行开始,2表示以步长为2进行选择
pe[:, 1::2] = torch.cos(position * div_term)
#这里position[max_len,1],div_term[model/2,]这里利用广播机制
#(即每个张量复制之前存在的元素然后进行维度扩展),position*div_term.shape=[max_len,model/2]
pe = pe.unsqueeze(0).transpose(0, 1)
#这里,给第0维加入维度,然后将0和1维进行交换
self.register_buffer('pe', pe)
#将pe注册为模型的缓冲区
def forward(self, x):
"""
x: [seq_len, batch_size, d_model]
"""
x = x + self.pe[:x.size(0), :]
#将张量和位置编码相加,即加入位置信息
return self.dropout(x)
将 pe 注册为模型的缓冲区有以下作用:
- 保持状态:通过将 pe 注册为模型的缓冲区,它会被保存在模型的状态中。这意味着在训练过程中,每次前向传播时都可以使用相同的位置编码,而不必重新计算或传递它。
- 共享内存:注册为缓冲区后,pe 可以在模型的不同方法之间共享内存。这对于需要在多个方法中使用的常量、权重或其他参数非常有用。
- 参数持久化:当保存和加载模型时,注册为缓冲区的内容也会被自动保存和加载。这确保了在重新加载模型后,缓冲区的内容仍然是一致的。
总之,通过将 pe 注册为模型的缓冲区,我们可以方便地使用和管理位置编码,并使其成为模型的一部分,而不必手动传递它或重新计算它。