3、可学习位置编码(Learned):BERT等模型使用的可学习位置编码原理、训练方式与局限性
聊完绝对位置编码,咱们来看看另一种更“偷懒”的做法——可学习位置编码。
说白了,就是我不给你设计什么正弦余弦公式了。我直接让模型自己去学一套位置向量。你想想看,这多省事?
BERT 用的就是这套方案。我记得当年第一次看到 BERT 论文时,心里还嘀咕:这玩意儿真能学出位置信息来?后来自己动手跑了一遍,嗯,还真行。
3.1 核心原理:把位置当成参数
可学习位置编码的思路非常直接:
- 假设最大序列长度是
max_len,比如 512 - 每个位置对应一个 d 维的向量
- 那我们就搞一个形状为
[max_len, d]的矩阵 - 这个矩阵里的每个值,都是可训练的参数
用代码表示就是:
import torch.nn as nn
max_len = 512
d_model = 768
# 这就是可学习位置编码
self.position_embeddings = nn.Embedding(max_len, d_model)
# 使用时,生成位置索引
position_ids = torch.arange(max_len).unsqueeze(0) # [1, 512]
position_embeds = self.position_embeddings(position_ids) # [1, 512, 768]
然后呢?直接把词嵌入和位置嵌入加起来,送进 Transformer 里。就这么简单。
关键点:可学习位置编码本质上就是一个 Embedding 层。它和词嵌入的区别在于——词嵌入的索引是词在词表中的 ID,而位置嵌入的索引是词在句子中的位置序号。
3.2 训练方式:端到端学习
训练过程没什么特别的。就是正常的反向传播。
位置编码矩阵的梯度会随着训练不断更新。每个位置最终学到什么样子,完全取决于数据。
我做过一个实验:把训练好的 BERT 位置编码可视化出来。你会发现:
- 相邻位置的向量比较相似
- 距离越远,向量差异越大
- 有些维度对位置敏感,有些维度不敏感
说白了,模型自己学会了“距离越近越相关”这个直觉。
我的经验:如果你自己训练一个模型,建议把位置编码的初始化范围设小一点,比如 N(0, 0.02)。我一开始用大了,结果训练初期位置编码的噪声把词嵌入的信息都淹没了,收敛特别慢。
3.3 局限性:三个绕不开的坑
可学习位置编码虽然简单好用,但坑也不少。我一个个说。
3.3.1 无法处理超长序列
这是最要命的限制。
你训练时设了 max_len=512,那模型就只能处理 512 个 token。来了个 600 的序列怎么办?
有人会说:那我设大一点不就行了?比如设成 2048。
问题是:
- 参数数量暴增:2048 × 768 = 157 万个参数
- 大部分位置在训练时根本没见过几次
- 没见过的位置,学出来就是随机噪声
我曾经试过把 BERT 的 max_len 从 512 扩到 1024,然后做微调。结果 512 之后的位置编码基本没怎么更新,效果很差。
避坑指南:如果你需要处理长文本,别用可学习位置编码。要么用 ALBERT 那种参数共享的方案,要么直接上 RoPE。
3.3.2 缺乏外推能力
外推能力,说白了就是:训练时没见过 600 的位置,推理时能不能处理好?
可学习位置编码的回答是:不能。
为什么?因为每个位置的参数是独立的。位置 512 和位置 600 之间没有任何约束关系。模型没见过位置 600,那它的嵌入就是随机初始化的值。
你想想看,这多尴尬?
相比之下,正弦编码和 RoPE 都有明确的数学公式,可以计算出任意位置的编码。这就是外推能力。
3.3.3 参数冗余
这个点可能很多人没注意到。
可学习位置编码的参数量是 max_len × d。对于 BERT-base 来说,就是 512 × 768 ≈ 40 万参数。
但你真的需要这么多参数吗?
我做过一个消融实验:把位置编码的维度从 768 压缩到 128,然后投影回 768。效果几乎没有下降。这说明什么?说明位置编码的信息冗余度很高。
说白了,你用 40 万个参数去学一个本来可以用几十个参数表达的东西。浪费。
3.4 知识体系总览
下面这张图帮你理清可学习位置编码的核心逻辑:
3.5 什么时候该用它?
说了这么多缺点,那可学习位置编码是不是一无是处?
也不是。它有两个明显的优点:
- 实现简单:一行代码搞定,不用设计复杂的公式
- 灵活性强:模型可以根据数据自适应地学习位置关系
我个人建议的使用场景:
- 任务的最大序列长度固定且较短(比如 128 或 256)
- 你不想折腾复杂的编码方案
- 做 baseline 实验,快速验证想法
但如果你要做长文本、大模型、或者需要外推能力,那还是老老实实用 RoPE 吧。
一句话总结:可学习位置编码是“用参数换简单”,适合短序列、快速实验。但它的天花板很低,长序列场景下基本没法用。
公众号:蓝海资料掘金营,微信deep3321