Transformers位置编码详解:绝对与相对机制比较
在现代自然语言处理系统中,一个看似简单却至关重要的问题长期存在:如何让完全并行的神经网络理解“顺序”?
Transformer 模型自 2017 年问世以来,凭借其强大的注意力机制取代了传统的 RNN 和 CNN 结构,成为 NLP 领域的基石。但正因为它摒弃了递归结构,也就失去了对输入序列天然的“时序感知”。如果不对位置信息进行建模,“猫追狗”和“狗追猫”在模型眼里可能并无区别。
为解决这一根本性问题,位置编码(Positional Encoding)应运而生——它虽不显眼,却是 Transformer 能够真正“读懂句子”的关键所在。从最初的正弦函数到如今复杂的相对偏置设计,位置编码的演进折射出我们对语言结构理解的深化。
绝对位置编码:赋予每个位置唯一身份
最早的解决方案很直观:既然模型不知道词的位置,那就直接告诉它每个 token 在第几位。这就是绝对位置编码的核心思想。
原始 Transformer 使用了一种基于正弦和余弦函数的固定编码方式:
$$
PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{\frac{2i}{d_{\text{model}}}}}\right), \quad
PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{\frac{2i}{d_{\text{model}}}}}\right)
$$
这个公式乍看复杂,实则巧妙。通过将不同频率的三角波叠加,使得每个位置都获得一个独一无二的向量表示。低频部分覆盖长距离变化,高频捕捉局部细节,整体呈现出平滑且可外推的特性。
更重要的是,这种编码是无参数的。相比于后来常见的可学习嵌入表(learned positional embedding),它不需要占用额外的模型容量,也避免了过拟合特定长度训练数据的风险。即使面对比训练时更长的序列,也能通过函数计算生成新的编码,具备良好的泛化能力。
实际实现中,通常会预先生成一个最大长度的位置编码矩阵,并作为常量缓冲区注册到模型中:
import torch import torch.nn as nn import math class AbsolutePositionalEncoding(nn.Module): def __init__(self, d_model: int, max_len: int = 5000): super().__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) # shape: [1, max_len, d_model] self.register_buffer('pe', pe) def forward(self, x): # x shape: [batch_size, seq_len, d_model] x = x + self.pe[:, :x.size(1)] return x这段代码中的div_term控制着频率衰减的速度,确保高维通道变化缓慢,形成多尺度的时间表征。而register_buffer的使用则保证该张量随模型一起被移动至 GPU,但不会参与梯度更新,提升推理效率。
不过,绝对编码也有明显短板。例如,当整个句子发生平移(如从位置 1–10 移动到 101–110),尽管语义关系未变,模型仍需重新学习这些“新位置”的含义。此外,在超长文本任务中,固定周期性的正弦编码可能无法有效区分遥远位置。
这些问题促使研究者转向另一种更贴近人类认知的方式——相对位置建模。
相对位置编码:关注“你离我有多远”
人类理解语言时,往往并不依赖绝对坐标,而是关注词语之间的相对距离。“因为……所以……”这类逻辑连接词的有效性,与其相隔多少个词有关,而非它们出现在句首还是句尾。
相对位置编码正是基于这一洞察。它不再为每个位置分配独立向量,而是在注意力计算过程中动态引入两个 token 之间的偏移信息。
以经典的 additive 方法为例,注意力得分不再只是 $ q_i^\top k_j $,而是增加一个依赖于相对距离 $ i-j $ 的偏置项:
$$
A_{ij} = \frac{q_i^\top k_j}{\sqrt{d_k}} + b_{i-j}
$$
这里的 $ b_{i-j} $ 是一个可学习的向量,代表从位置j到i的影响强度。由于只涉及有限范围内的相对偏移(如 ±16),参数量远小于完整的绝对位置嵌入表。
这种设计带来了几个显著优势:
- 平移不变性更强:无论上下文整体位于序列何处,只要相对关系一致,注意力行为就保持稳定;
- 更适合长序列建模:配合循环机制(如 Transformer-XL 中的 segment 循环),可以跨越多个片段捕捉长期依赖;
- 内存更高效:只需维护一个小规模的偏置查找表,尤其适合移动端或低资源场景。
下面是其实现示例:
import torch import torch.nn as nn class RelativePositionBias(nn.Module): def __init__(self, num_heads: int, max_relative_position: int): super().__init__() self.num_heads = num_heads self.max_relative_position = max_relative_position self.relative_attention_bias = nn.Embedding(2 * max_relative_position + 1, num_heads) def forward(self, length_q, length_k): range_q = torch.arange(length_q) range_k = torch.arange(length_k) distance = range_q[:, None] - range_k[None, :] # [Lq, Lk] distance = torch.clamp(distance, -self.max_relative_position, self.max_relative_position) distance += self.max_relative_position # 映射到非负索引 bias = self.relative_attention_bias(distance) # [Lq, Lk, H] return bias.permute(2, 0, 1) # [H, Lq, Lk]在这个模块中,distance矩阵记录了所有 query 和 key 之间的相对位置差,经过截断和偏移后用于查表。最终得到的偏置会被加到原始注意力分数上,从而引导模型关注特定距离范围内的交互。
值得注意的是,相对编码并不会改变输入表示本身,而是深度集成在注意力机制内部。这意味着它的作用更加精细、灵活,但也增加了实现复杂度——必须修改标准注意力的计算流程。
实际应用中的权衡与选择
在真实系统架构中,位置编码的选择直接影响模型的整体行为。以下是一个典型的处理流程对比:
使用绝对编码的流程(如 BERT)
Input Tokens → Token Embedding → + Positional Encoding → LayerNorm → Multi-Head Attention在这种模式下,位置信息从一开始就与词义融合在一起。模型需要通过自注意力间接推断出“I”和“learning”之间相隔两个词的事实。这种方式简单通用,适用于大多数标准 NLP 任务,但在处理文档级文本或代码等超长序列时表现受限。
使用相对编码的流程(如 Transformer-XL)
Q, K, V from previous layer → Compute QK^T → Retrieve Relative Bias based on position offset → Add bias to attention scores → Softmax + Dropout → Output这里的位置信息是动态注入的。更重要的是,结合缓存机制,模型能够感知跨 segment 的相对距离(如当前词与 512 步前的词之间的关系)。这使得它在语言建模、代码补全等需要长期记忆的任务中表现出色。
工程实践中的常见考量
| 问题 | 设计建议 |
|---|---|
| 是否应使用可学习编码? | 对于领域特定任务(如源代码、DNA 序列),可学习编码往往优于固定函数;但对于通用语言模型,正弦式编码更具外推优势。 |
| 最大长度如何设置? | 若采用可学习编码,需预设最大长度。超过时可通过线性插值扩展位置索引(如 LLaMA 系列的做法)。 |
| 如何优化显存使用? | 将位置编码注册为 buffer 存放于 GPU 显存,避免重复创建;对于相对偏置,限制最大偏移范围可大幅减少内存占用。 |
| 是否可以混合使用? | 是的。Swin Transformer 等视觉模型采用“相对位置偏差”而非向量加法,进一步解耦位置与内容信息,值得借鉴。 |
实际上,近年来的趋势正逐步从“编码”转向“偏差”——即不再将位置信息当作输入的一部分,而是作为注意力机制的调节因子。这种方式不仅提升了灵活性,也增强了模型对局部结构的敏感性。
写在最后:位置编码的未来方向
回顾位置编码的发展历程,我们可以看到一条清晰的技术演进路径:
从静态赋值 → 动态建模 → 偏置调控
早期的绝对编码解决了“有没有顺序”的问题,而相对编码则深入到了“关系有多近”的层面。今天,已有越来越多的工作尝试更细粒度的位置感知方式,例如:
- 旋转位置编码(RoPE):通过旋转向量隐式编码相对位置,已被 LLaMA、ChatGLM 等主流大模型广泛采用;
- ALiBi(Attention with Linear Biases):不使用任何显式位置编码,而是根据相对距离施加线性惩罚,实现无限外推;
- XPos:在 RoPE 基础上引入位置缩放,进一步提升长序列建模能力。
这些创新表明,位置建模已不再是简单的附加组件,而是深刻影响模型归纳偏置的核心机制之一。
在工程层面,借助 PyTorch-CUDA 这类高度集成的深度学习环境,开发者可以快速验证不同位置编码方案的效果。无论是通过 Jupyter Notebook 可视化编码向量分布,还是在多卡集群上测试相对编码在长文本任务中的性能增益,现代工具链都极大降低了实验门槛。
可以说,位置编码虽小,却承载着我们对语言本质的理解。它的每一次进化,都在推动 Transformer 向更智能、更鲁棒的方向迈进。