Transformer模型中的位置编码(Positional Encoding)原理与实现
字数 1371 2025-11-03 08:33:37
Transformer模型中的位置编码(Positional Encoding)原理与实现
描述
在Transformer模型中,由于自注意力机制本身不包含序列顺序信息,模型无法区分输入序列中元素的先后顺序。位置编码(Positional Encoding)被引入来解决这一问题,它通过向输入嵌入向量添加位置信息,使模型能够感知序列的顺序。位置编码必须适应不同长度的序列,并保持一定的泛化能力。
解题过程
-
位置编码的需求
- 自注意力机制是排列不变的(permutation-invariant),即打乱输入顺序后输出不变。
- 为了让模型理解序列顺序(如时间顺序、语法结构),需要显式注入位置信息。
-
位置编码的设计原则
- 唯一性:每个位置有唯一的编码。
- 确定性:编码应在不同长度的序列中保持一致(例如位置1的编码始终相同)。
- 泛化性:编码需支持训练时未见过的序列长度。
- 连续性和有界性:相邻位置的编码应平滑变化,且数值范围可控。
-
正弦和余弦位置编码公式
Transformer论文采用正弦和余弦函数的组合:
\[ PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right) \]
\[ PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right) \]
- \(pos\):位置索引(从0开始)。
- \(i\):编码向量的维度索引(\(0 \leq i < d_{\text{model}}/2\))。
- \(d_{\text{model}}\):嵌入向量的维度(如512)。
-
公式的直观解释
- 频率变化:不同维度对应不同的波长(从\(2\pi\)到\(2\pi \cdot 10000\)),低维度(小\(i\))频率高,高维度频率低。
- 线性关系:每个维度对应一个旋转向量,位置\(pos\)的编码可通过线性变换从\(pos-k\)的编码推导(利于模型学习注意力中的相对位置)。
- 正弦和余弦交替:确保每个位置编码唯一,且能通过线性变换捕捉相对位置。
-
位置编码与输入嵌入的结合
- 输入嵌入向量\(X \in \mathbb{R}^{n \times d_{\text{model}}}\)(\(n\)为序列长度)。
- 位置编码矩阵\(P \in \mathbb{R}^{n \times d_{\text{model}}}\)通过加法合并:
\[ X' = X + P \]
- 加法操作允许模型同时处理语义和位置信息。
-
其他位置编码方法
- 学习式位置编码:将位置编码作为可学习参数(如BERT)。优点是可适应数据分布,但缺乏外推性。
- 相对位置编码:直接建模元素间的相对距离(如Transformer-XL、T5),更适合长序列。
-
代码实现示例(Python)
import torch import torch.nn as nn class PositionalEncoding(nn.Module): def __init__(self, d_model, max_len=5000): super().__init__() pe = torch.zeros(max_len, d_model) pos = torch.arange(0, max_len).unsqueeze(1) # (max_len, 1) div_term = torch.exp(torch.arange(0, d_model, 2) * (-torch.log(torch.tensor(10000.0)) / d_model)) pe[:, 0::2] = torch.sin(pos * div_term) # 偶数维度 pe[:, 1::2] = torch.cos(pos * div_term) # 奇数维度 self.register_buffer('pe', pe.unsqueeze(0)) # (1, max_len, d_model) def forward(self, x): return x + self.pe[:, :x.size(1)] -
位置编码的局限性与发展
- 正弦编码在长序列外推时可能失效(如序列长度远超训练时的\(max_len\))。
- 后续研究提出旋转位置编码(RoPE)、相对位置编码等改进方法,提升泛化能力。
通过以上步骤,位置编码使Transformer能够有效利用序列顺序信息,成为自然语言处理任务的核心组件。