位置编码介绍
-
RNN与Transformer的对比:
-
RNN(循环神经网络)的结构包含了序列的时序信息,这意味着它能够利用序列中元素的顺序来理解句子的含义。
-
Transformer模型在设计时完全去掉了序列的时序信息,这可能导致模型无法区分不同顺序的句子。例如,"他欠我100万"和"我欠他100万"在没有时序信息的情况下,意思完全相反。
-
-
位置编码的作用:
-
为了解决时序信息丢失的问题,Transformer的作者引入了位置编码(Positional Encoding)。
-
位置编码为每个token提供了位置信息,使得模型能够区分不同位置的token。
-
-
位置信息的类型:
-
绝对位置信息:直接表示token在序列中的位置,如a1是第一个token,a2是第二个token等。
-
相对位置信息:表示token之间的相对位置,如a2在a1的后面一位,a4在a2的后面两位等。
-
不同位置间的距离:表示不同位置token之间的距离,如a1和a3差两个位置,a1和a4差三个位置等。
-
-
位置编码的要求:
-
提供位置信息:位置编码为模型提供了每个token在序列中的位置信息,这对于理解序列的顺序至关重要。
-
保持一致性:在不同序列长度的情况下,位置编码能够保持不同序列中token的相对位置/距离一致。
-
处理长序列:位置编码可以表示模型在训练过程中从未见过的句子长度,即长度外推问题。
-
位置编码的实现

这种编码方式能够为每个位置生成一个唯一的向量,并且这些向量在不同位置之间是可区分的,从而为模型提供了必要的位置信息。
位置编码的数学原理
-
需要一个有界且连续的函数:
-
位置编码需要一个函数,该函数对于不同的时间步(或位置)t 能够生成不同的值,同时这些值需要是有界的(即在某个范围内)。
-
正弦函数(sin)是一个满足这些条件的简单函数。
-
-
频率设置:
-
为了确保不同位置的编码尽可能不重复,频率 wi 被设置得非常低。
-
这样设计可以减少不同位置编码重合的可能性。
-
-
位置编码公式:

代码实现
在实际的代码实现中,位置编码通常如下所示:
import torch
import torch.nn as nn
import math
class PositionalEncoding(nn.Module):
def __init__(self, d_model, max_len=5000):
super(PositionalEncoding, self).__init__()
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
pe = pe.unsqueeze(0).transpose(0, 1)
self.register_buffer('pe', pe)
def forward(self, x):
x = x + self.pe[:x.size(0), :]
return x