周五晚上公司团建,大家在操场排成一条长队玩“摸黑找朋友”。你闭着眼,只能靠手摸衣料辨认熟人。你摸到帽衫、风衣、T恤,却完全不知道谁站在谁前头。若有人告诉你“小王排在队伍第 100 位”,队伍一打乱,这信息就废了;但若有人告诉你“小王就在你前面 5 步”,无论队伍怎么移动,你都能轻松定位。 现代的智能翻译、对话模型处理文字时,也面临着同样的困扰。它天生擅长辨认“内容”——哪个词跟哪个词意思相近——却对“顺序”非常迟钝。为了补上位置感,学者们先后设计了不同的“位置编码”方案。其中,相对位置编码更接近人类“在小王前面 5 步”的直觉:让模型直接感知词与词之间相隔多远、谁前谁后,而不是死记硬背“某个词排在第几位”。 这篇文章从排队找朋友的故事出发,带你一步步看懂相对位置编码的原理、公式、优势与工程局限。

岗位关联

掌握相对位置编码,你就能在长文本 NLP 任务中正确选择位置方案,让模型对序列关系更敏感,从容应对长文档、代码、对话等场景。

目录
  1. 可视化演示
  2. 注意力盲区
  3. 绝对位置编码
  4. 相对距离构造
  5. 注意力打分改造
  6. 平移等变性
  7. 复杂度与边界
  8. 经典模型应用
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示Token间相对距离感知、位置偏置注入注意力矩阵,以及相对编码在长序列外推中的优势。

本节配有交互动效(进入视口后自动加载,约 3KB)。

相对距离感知:Token间位置关系编码 红色=选中Token,感知与其他Token的相对距离k 粉色=右侧Token(k>0) 绿色=左侧Token(k<0) 相对距离 k = j - i(query i, key j)

注意力盲区

摸黑找朋友时,你只能凭衣料质感猜人,却不知道谁站在队伍前面、谁站在后面。你心中对“顺序”一无所知。这正是没有位置信息时注意力机制的处境:它擅长辨认内容,对位置却一片漆黑。

语言模型的注意力机制,本质上就是“内容匹配”。第 i 个词的查询向量会与第 j 个词的键向量做点积,谁相似度高,谁就获得更多注意力。可这套机制只认“长得像不像”,不认“排前还是排后”。“猫追老鼠”和“老鼠追猫”两个句子的词完全一样,没有位置信息时,注意力给出的打分几乎相同,语义却完全相反。

用公式看得更清楚。对任意两个位置 i、j 的 token,注意力得分是:

得分完全由内容决定。换个语序,token 集合一样,得分就一样——好比摸黑找朋友只凭手感:内容对了,方位全错。要让模型理解“猫”在“追”之前还是之后,必须给注意力补上位置这一课。

eij=qi⊤kj\displaystyle e_{ij} = q_i^{\top} k_j
  1. 查询向量把当前词的内容变换成“我想找什么”。
    qi=xiWQ\displaystyle q_i = x_i W^Q
  2. 键向量把目标词的内容变换成“我是什么”。
    kj=xjWK\displaystyle k_j = x_j W^K
  3. 点积打分两个向量做内积,越相似得分越高。
    eij=qi⊤kj\displaystyle e_{ij} = q_i^{\top} k_j

绝对位置编码

先看最直观的解决方案:给队伍里每个人都发一个编号牌,1 号、2 号、3 号……你只要记住朋友的编号就能定位他。这就是绝对位置编码的思路。

绝对位置编码把每个位置变成一个固定的位置向量,加到词向量上。每个 token 在进入注意力之前,已经带上了“我在第几位”的信息。最初的 Transformer 模型采用正弦余弦函数构造位置向量,公式如下:

这种编码有两个特点:不同位置向量彼此可区分;位置之间隐含平滑的相似关系。但它的致命弱点是“绝对”——训练时见过的最大位置是 512,碰到 1024 个 token 的输入就束手无策。更关键的是,注意力打分时并不直接知道两个 token 差了几步,位置信息被“揉碎”在向量里。就像队伍集体向右挪了 5 个位置,朋友的编号瞬间全变。编号牌方案能定位,但经不起队伍变化。

PE(pos,2i)=sin⁡(pos100002i/dmodel),PE(pos,2i+1)=cos⁡(pos100002i/dmodel)\displaystyle PE(pos, 2i) = \sin\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right), \quad PE(pos, 2i+1) = \cos\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right)
  1. 位置编号pos 表示 token 在句子里的绝对序号。
    pos=0,1,2,…\displaystyle pos = 0, 1, 2, \ldots
  2. 波长分母10000^{2i/d} 让不同维度拥有不同频率,低频负责大距离区分。
    pos100002i/dmodel\displaystyle \frac{pos}{10000^{2i/d_{\text{model}}}}
  3. 奇偶交替偶数维用正弦,奇数维用余弦,相邻位置的向量既不同又平滑。
    PE(pos,2i)=sin⁡(⋅),PE(pos,2i+1)=cos⁡(⋅)\displaystyle PE(pos, 2i) = \sin(\cdot), \quad PE(pos, 2i+1) = \cos(\cdot)

相对距离构造

换个思路:不用管朋友是第几号,只需要知道自己往前走几步、往后退几步能碰到他。对任意两个 token 的位置 i 和 j,它们的相对距离就是 i−j。正数表示 j 在 i 的某一侧,负数表示另一侧。

但序列可能长达成千上万步,记住每一种距离不现实。于是引入裁剪操作,把相对距离限制在一个窗口内:

裁剪之后,真正需要学习的只有 $2k+1$ 个相对位置向量(从 −k 到 k),每个向量的维度等于模型维度。这张表叫可学习相对位置表,所有 token 对共用。好比你不必认识队伍里每个人,只要记住前后各 3 步的邻伴特征就行。远处一刀切粗分、近处细分的感知方式,让模型既能感知位置,又不被长距离的细枝末节淹没。

dij=clip(i−j,−k,k)=max⁡(−k,min⁡(k,i−j))\displaystyle d_{ij} = \text{clip}(i - j, -k, k) = \max(-k, \min(k, i - j))
  1. 计算偏移量用查询位置减去目标位置。
    i−j\displaystyle i - j
  2. 钳制范围把偏移量压到 [-k, k] 内,超出窗口的统一按边界算。
    max⁡(−k,min⁡(k,i−j))\displaystyle \max(-k, \min(k, i-j))
  3. 得到相对序号裁剪后得到的值就是查表用的距离序号。
    dij∈[−k,k]\displaystyle d_{ij} \in [-k, k]

注意力打分改造

现在把这些相对位置融进注意力计算。想象摸黑找朋友时,你手上多了一个“距离感应器”:摸到一个人,脑中自动叠加“他离我几步”的提示,再判断像不像朋友。相对位置编码做的是同一件事——在打分时把距离向量加到键上,让查询在判断相似度时自动带上位置感。

以经典相对位置注意力为例,打分公式改造为:

除了打分阶段,前向加权阶段也会在 value 端叠加相应的相对位置向量:$z_i = \sum_j \alpha_{ij}(v_j + r^{V}_{i-j})$,让输出也感知位置对齐。公式中,$r$ 与 $k$ 相加的意义是:键端内容向量里嵌入了“距离提示”,查询询问时既比较内容、也比较方位,真正做到“看在眼里,记在心里”。

eij=qi⊤(kj+ri−j)dk\displaystyle e_{ij} = \frac{q_i^{\top}(k_j + r_{i-j})}{\sqrt{d_k}}
  1. 查表取距离向量按裁剪后的相对距离 $i-j$,从可学习表中取出向量。
    ri−j∈Rdk\displaystyle r_{i-j} \in \mathbb{R}^{d_k}
  2. 叠加到键端把“距离提示”加到目标词的键向量上,让内容与方位一起参与匹配。
    kj+ri−j\displaystyle k_j + r_{i-j}
  3. 缩放点积与查询做内积,再除以 $\sqrt{d_k}$ 防止数值过大。
    qi⊤(kj+ri−j)dk\displaystyle \frac{q_i^{\top}(k_j + r_{i-j})}{\sqrt{d_k}}

平移等变性

整条队伍向右挪了 50 步,你与朋友之间仍然隔着 3 个人。相对位置编码最大的魔力——平移等变性——就在于此。

因为注意力得分依赖的是两个位置之差 i−j,而不是绝对位置,所以当序列整体平移 c 步后,任意两点的相对距离不变,注意力模式也不变。数学上可以这样表达:

这意味着模型学到的是“距离模式”而不是“位置模式”。“第 100 步和第 105 步之间的距离感”与“第 5 步和第 10 步之间”完全一致,所以模型能更好地外推到比训练长度更长的序列。就像演唱会现场,不管人潮整体涌向舞台几米,你找朋友的方式依旧是“往前三步,再往左两步”。队形整体平移,找法不变。

ei+c, j+c=f(xi+c, xj+c, (i+c)−(j+c))=f(xi+c, xj+c, i−j)\displaystyle e_{i+c,\, j+c} = f\big(x_{i+c},\, x_{j+c},\, (i+c)-(j+c)\big) = f\big(x_{i+c},\, x_{j+c},\, i-j\big)
  1. 序列平移所有 token 下标同时增加 c。
    i→i+c,j→j+c\displaystyle i \to i+c, \quad j \to j+c
  2. 距离不变新下标相减后偏移量仍然相同。
    (i+c)−(j+c)=i−j\displaystyle (i+c)-(j+c) = i-j
  3. 模式随迁只要平移后内容特征保持一致,注意力得分就保持不变。
    ei+c, j+c=eij\displaystyle e_{i+c,\, j+c} = e_{ij}

复杂度与边界

大脑不是无限记忆卡。你不可能记住整条队伍一万人的面孔,更不可能为每一对人存一条“距离感觉”。相对位置编码的关键工程价值,就是把记忆负担限制在距离窗口内。

完整相对位置矩阵是 $n \times n$,而使用裁剪后,只需维护一张很小的共享表:

但要注意边界:注意力计算本身仍是平方级——每个 token 对都要算一次相关性,相对位置表只是让“参数规模”可控。对于特别长的序列,还需要窗口截断、分块等策略协同:近距精确、远距粗略。T5 模型甚至把距离映射到对数间隔的“桶”里,进一步压缩参数。所以相对位置编码不是万能钥匙,它让“距离记忆”变得便宜,但注意力计算的固有开销依旧存在,工程上仍需量体裁衣。

位置表大小=(2k+1)×dk\displaystyle \text{位置表大小} = (2k+1) \times d_k
  1. 距离区间宽度从 −k 到 k,共有 2k+1 种距离。
    2k+1\displaystyle 2k+1
  2. 每个距离一维向量每种距离对应一个 $d_k$ 维的可学习向量。
    dk\displaystyle d_k
  3. 总量与序列长度无关表大小只由窗口和维度决定,不随 n 增长。
    (2k+1)×dk\displaystyle (2k+1) \times d_k

经典模型应用

演唱会组织者手里有一张“区块关系表”:不必记住一万名观众的编号,只要知道各区块的邻近关系,就能高效引导入座。这正是 T5、Transformer-XL 等模型使用相对位置编码的方式。

Transformer-XL 在分段循环中,用相对位置编码解决跨片段位置错乱的问题,让模型能处理更长的文本;T5 则用相对位置分桶,把不同距离映射到有限桶位,兼顾参数效率与距离感知。T5 近距离采用精确桶,远距离采用对数桶:

这种设计让模型在长文本摘要、代码生成、长对话等场景中,既保持位置敏感度,又不被参数规模拖垮。从排队找朋友到模型处理百万 token,道理相通:真正重要的是“你离我几步”,而不是“你是第几号”。相对位置编码把这一人类直觉注入注意力机制,让 AI 在长长的语义队伍里,也能清晰感知彼此的前后左右。

b(d)={min⁡(d,8)0≤d≤88+⌊log⁡2d8⌋+1d>8\displaystyle b(d) = \begin{cases} \min(d, 8) & 0 \le d \le 8 \\ 8 + \left\lfloor \log_2 \frac{d}{8} \right\rfloor + 1 & d > 8 \end{cases}
  1. 近距离精确距离在 8 以内时,每个距离独占一个桶,精确区分。
    min⁡(d,8)\displaystyle \min(d, 8)
  2. 远距离对数粗分距离超过 8 时按对数刻度合并,远处的桶越来越宽。
    ⌊log⁡2d8⌋\displaystyle \left\lfloor \log_2 \frac{d}{8} \right\rfloor
  3. 桶号汇总近处精确桶加上远处对数桶,得到最终桶编号。
    b(d)\displaystyle b(d)

AI 应用场景

长文档摘要

阶段:模型训练与推理

解决的问题:传统 Transformer 用绝对位置编码时,训练长度往往只有 512 或 1024 个 token,遇到数万字的法律合同、技术文档时位置外推迅速失效,摘要质量断崖式下跌。相对位置编码让注意力只关心“相隔多远”,长文档中每个句子都能平等地感知上下文位置关系。

具体案例:金融科技公司处理上万字的招股说明书,采用相对位置编码的模型在 8K token 输入下,摘要 ROUGE 分数比绝对位置模型提升约 8%,原因是相对距离窗口让核心条款无论出现在文档何处都能被稳定定位。

代码补全助手

阶段:预训练与微调

解决的问题:代码中的逻辑依赖常跨越数十行,“第 40 行的函数调用依赖第 12 行的 import”。绝对位置编码记住了“某一段代码在第几行”,却难以泛化到行数偏移;相对位置编码让模型感知“那行代码距离当前补全位置往前 28 行”,补全更精准。

具体案例:IDE 插件团队为代码模型切换相对位置编码后,跨行引用类补全的准确率提升 12%,用户连续补全 100 行以上代码时错误率明显下降,因为每行都能感知与上下文的相对行距。

Python 演示

python48 行
import numpy as np  class RelativePositionalAttention:    def __init__(self, d_model, max_dist=2):        self.d_model = d_model        self.max_dist = max_dist        self.WQ = np.random.randn(d_model, d_model) * 0.02        self.WK = np.random.randn(d_model, d_model) * 0.02        self.WV = np.random.randn(d_model, d_model) * 0.02        self.rel_tbl = np.random.randn(2 * max_dist + 1, d_model) * 0.02     def clip_dist(self, i, j):        d = i - j        return max(-self.max_dist, min(self.max_dist, d))     def forward(self, X):        n, d = X.shape        Q = X @ self.WQ        K = X @ self.WK        V = X @ self.WV         logits = np.zeros((n, n))        for i in range(n):            for j in range(n):                rel = self.clip_dist(i, j)                rv = self.rel_tbl[rel + self.max_dist]                logits[i, j] = Q[i] @ (K[j] + rv) / np.sqrt(d)         weights = np.exp(logits)        weights /= weights.sum(axis=1, keepdims=True)         out = np.zeros((n, d))        for i in range(n):            for j in range(n):                rel = self.clip_dist(i, j)                rv = self.rel_tbl[rel + self.max_dist]                out[i] += weights[i, j] * (V[j] + rv)        return out, weights  if __name__ == "__main__":    np.random.seed(0)    X = np.random.randn(6, 8)    layer = RelativePositionalAttention(8, max_dist=2)    out, att = layer.forward(X)    print("输出形状:", out.shape)    print("注意力矩阵:\n", np.round(att, 3))
目的:从零实现一个带相对位置编码的单头注意力层,演示“键端叠加距离向量”与“值端叠加距离向量”的完整计算流程。
  1. self.rel_tbl = np.random.randn(2 * max_dist + 1, d_model) * 0.02初始化可学习相对位置表,覆盖距离范围 [−k, k]。
  2. rel = self.clip_dist(i, j)计算并裁剪相对距离,超出窗口的按边界距离处理。
  3. rv = self.rel_tbl[rel + self.max_dist]通过偏移量查表,得到当前 token 对的距离向量。
  4. logits[i, j] = Q[i] @ (K[j] + rv) / np.sqrt(d)把距离向量叠加到键向量上,再与查询做缩放点积。
  5. weights /= weights.sum(axis=1, keepdims=True)对每行做 softmax 归一化,得到注意力权重。
  6. out[i] += weights[i, j] * (V[j] + rv)加权求和时在值端也叠加距离向量,让输出保留位置对齐信号。

常见误区

误区相对位置编码就是把“第几个位置”这样的整数数字直接加进模型输入里,模型自己会理解数字大小。

正解位置编码是把位置映射成高维向量,再与内容向量做加和或叠加,模型从向量模式中提取位置感。单个整数只有数值大小,无法表达“向左 2 步”和“向右 2 步”的方向差异,也缺少高维空间里相邻位置渐变的平滑结构。就像说“朋友在 3”完全没有方向感,要说“朋友在右 3”才有意义。

为何常见:位置在数学上天然是数字,初学者容易把“编码”理解为“传数字进去”。

误区相对位置编码是绝对位置编码的完全升级版,任何任务都应该无脑替换成相对位置编码。

正解二者各有擅长。绝对位置编码能提供“句子开头”“段首”等全局参照系,相对位置编码擅长局部距离感知。实践中不少模型采用两者混合或叠加方案。队伍里你既需要“全场第 100 位”这种绝对坐标做全局调度,也需要“我前 5 位”这种相对描述做局部找人。

为何常见:看到很多新模型都在用相对位置编码,便认为绝对位置编码被淘汰了,忽略了任务场景差异。

误区只要把绝对位置换成相对位置,输入多长都没问题,甚至连注意力计算都变快了。

正解相对位置编码改善了位置外推性,但注意力计算本身仍是平方级的:每个 token 对都要算一次相似度。裁剪只是限制了“距离感知范围”,并没有减少参与计算的 token 对数量。处理超长序列仍需窗口注意力、稀疏注意力或分块策略协同。

为何常见:把“位置外推能力”和“计算复杂度”两个维度混为一谈,以为位置问题解决了,复杂度问题也顺带解决。

误区相对位置编码只记录“隔了几步”,至于左边右边、前面后面不重要,模型反正只关心远近。

正解方向和距离同样关键。“前面 2 个”和“后面 2 个”在语义上完全不同,所以位置表必须覆盖负距离到正距离的完整区间,共 2k+1 项。注意力打分时,r_{+2} 和 r_{−2} 是两个不同的向量,模型借此区分左右。

为何常见:听别人说“相对距离”时只注意了数字,忽略了符号背后的方向语义。

面试题

Q1. 在未加任何位置信息时,自注意力模型对「猫追老鼠」和「老鼠追猫」的态度是?

单选题 · easy

  • A.无法分辨词序先后← 正确答案
  • B.完全分辨相反语义
  • C.仅识别名词动词
  • D.自动加入位置线索

答案:A。未加位置信息时,自注意力对 token 集合具有置换不变性,“猫追老鼠”和“老鼠追猫”的词袋完全相同,注意力打分几乎一样,模型分辨不出谁先谁后。选项 B 说反了,C 是错误的功能描述,D 更是无中生有。

Q2. 绝对位置编码采用正弦/余弦函数的主要好处是?

单选题 · easy

  • A.位置向量唯一且平滑
  • B.显著加快计算速度
  • C.减少模型参数量← 正确答案
  • D.直接编码语义角色

答案:C。正弦/余弦函数让不同位置获得唯一且平滑的向量表示,相邻位置向量保持渐变,模型既能区分位置又能感知位置间的连续关系。B、C 与函数形式无关,D 是词向量而非位置编码的职责。

Q3. 相对位置构造中,clip(i−j, −k, k) 里的 k 表示什么?

单选题 · easy

  • A.最大感知距离窗口
  • B.序列的实际长度← 正确答案
  • C.模型隐藏层维度
  • D.注意力头数量

答案:B。k 是相对距离裁剪窗口的边界,把 i−j 限制在 [−k, k] 内,超出窗口的远距离 token 统一使用边界距离向量。它不是序列长度、模型维度或注意力头数。

Q4. 相对位置编码通常注入到哪些计算环节?

多选题 · mid

  • A.注意力打分时的 key 端← 正确答案
  • B.加权输出时的 value 端← 正确答案
  • C.词表映射嵌入层
  • D.损失函数反向传播

答案:AB。相对位置向量一方面叠加到 key 端参与注意力打分,另一方面叠加到 value 端参与加权输出,让位置信息既影响“看哪里”也影响“取什么”。词表映射和损失函数不属于位置编码注入的目标环节。

Q5. 相对位置编码的“平移等变性”是指?

单选题 · mid

  • A.模型权重随平移变化
  • B.所有输出向量不发生改变
  • C.训练损失函数持续上升
  • D.序列平移模式随队而迁← 正确答案

答案:D。平移等变性指整个序列向前或向后移动 c 步后,任意两个 token 的相对距离不变,注意力模式随队形整体迁移。模型权重不会因平移改变,输出向量也会随内容位置同步变化,损失更不会因此上升。

Q6. 与绝对位置编码相比,相对位置编码的优势有哪些?

多选题 · mid

  • A.增强长序列外推能力← 正确答案
  • B.位置表大小与长度无关← 正确答案
  • C.彻底免去注意力计算
  • D.能感知方向和距离← 正确答案

答案:ABD。相对位置编码直接建模 token 间的方向和距离,位置表参数只取决于窗口大小,不随序列长度增长,因此对长序列外推更友好。但它并不能免除注意力本身的平方级计算,C 错误。

Q7. 一张相对位置表的参数规模是?

单选题 · mid

  • A.序列长度乘模型维度
  • B.窗口大小乘序列长度
  • C.两倍k加一乘以维度← 正确答案
  • D.序列长度平方乘维度

答案:C。相对位置表覆盖从 −k 到 k 共 2k+1 种距离,每种距离对应一个 d_k 维向量,因此总参数为 (2k+1)×d_k。其余选项混入了序列长度,不符合共享位置表的定义。

Q8. T5 相对位置分桶策略的核心思想是?

单选题 · hard

  • A.近处精细远处对数粗分← 正确答案
  • B.所有距离共享一个桶
  • C.将距离映射为词向量
  • D.用卷积替代注意力

答案:A。T5 对 8 以内距离分配精确桶,对更远距离按对数刻度合并成宽桶,既保留近距离的精细位置感,又让远距离泛化更稳定。B 会导致位置信息完全丢失,C、D 与分桶策略无关。

Q9. 相对位置编码不能解决哪些问题?

多选题 · hard

  • A.所有注意力平方级开销← 正确答案
  • B.任意长无限上下文← 正确答案
  • C.词向量中语义歧义← 正确答案
  • D.改善序列外推能力

答案:ABC。相对位置编码只改进位置建模,若不加窗口或稀疏策略,注意力打分仍是 O(n²);它也无法凭空支持无限上下文,更不负责消除词向量本身的语义歧义。D 恰恰是它擅长的能力,不应入选。

Q10. 当相对距离 |i−j| 超出裁剪窗口 k 时,模型在注意力计算中如何处理距离向量?

单选题 · extreme

  • A.该位置向量直接置零
  • B.用插值构造全新向量
  • C.改用绝对位置向量
  • D.仍用边界距离向量← 正确答案

答案:D。裁剪操作把超出窗口的距离统一映射到最近的边界值,即使用距离 k 或 −k 对应的向量参与计算。这样既控制参数规模,又保证远距离 token 对仍是“可感知但粗略”的关系,而不是直接丢弃位置信息。