为什么 Transformer 需要位置信息,以及绝对位置编码的方案与局限
配套代码
我们先来看一个有趣的事实:标准的 Self-Attention 完全不关心输入的顺序。
回顾 Attention 的计算过程:
𝑋 𝑊 𝑉 V=XW V 。如果我们打乱输入 𝑋 X 中 token 的顺序,每个 token 的 𝑄 Q、𝐾 K、𝑉 V 向量本身不会改变,只是它们在序列中的位置变了。而 Attention 计算的是所有 token 之间的两两点积,这个操作对顺序是不敏感的。
换句话说,“我 喜欢 猫” 和 “猫 喜欢 我” 对 Attention 来说是一样的。这显然不行,语言的意义高度依赖词序。
这就是为什么我们需要位置编码:在输入中注入位置信息,让模型知道每个 token 在序列中的位置。
最直接的思路是:给每个位置分配一个固定的向量,加到 token 的 embedding 上。
原始 Transformer 论文(Vaswani et al., 2017)使用了基于正弦和余弦函数的位置编码:
其中 𝑝 𝑜 𝑠 pos 是位置索引,𝑖 i 是维度索引,𝑑 d 是 embedding 维度。
import torch
def sinusoidal_pe(max_len, d_model):
"""Sinusoidal 位置编码"""
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len).unsqueeze(1).float()
# 频率项:10000^(-2i/d)
div_term = 10000.0 ** (torch.arange(0, d_model, 2).float() / d_model)
pe[:, 0::2] = torch.sin(position / div_term) # 偶数维度
pe[:, 1::2] = torch.cos(position / div_term) # 奇数维度
return pe # Shape: (max_len, d_model)
这个设计有几个巧妙之处: