位置编码
如何理解LLM sin cos位置编码中,依据三角函数的基本性质,可以得知个位置编码是第个位置编码的线性组合,这就意味着位置编码中蕴涵着单词之间的距离信息
理解这个问题的关键在于三角函数的和角公式。让我们一步步拆解:
1. 三角函数位置编码公式
对于 Transformer 中的位置编码,第 pos 个位置、第 i 个维度的编码为:
其中 d 是模型维度。
2. 核心数学性质:和角公式
三角函数的关键性质:
3. 推导线性组合关系
令:
α = pos / 10000^{2i/d}β = k / 10000^{2i/d}
那么:
4. 矩阵形式表示
将这两个公式写成矩阵形式:
这就是一个旋转矩阵!
5. 物理意义解读
5.1 距离信息的编码
β只依赖于 相对位置 k 和 维度 ipos+k的位置编码可以由pos的位置编码线性变换得到- 变换矩阵的系数 (
cosβ,sinβ) 只与相对距离 k 有关
这意味着:模型可以学习到相对位置关系,而不仅仅是绝对位置。
5.2 实际例子
假设我们想计算位置 10 和位置 13 之间的关系:
- 相对距离 k = 3
- 对于每个维度 i,都有一个旋转角度
- 位置 13 的编码 = 旋转矩阵 × 位置 10 的编码
5.3 为什么这是"线性组合"
因为:
# pos+k 的编码可以由 pos 的编码线性表示
PE_pos_k = a * PE_pos_even + b * PE_pos_odd
其中 a, b 是常数(对固定 k 和 i 是固定的)。
6. 可视化理解
对于固定的维度 i:
- 每个位置编码是单位圆上的一个点
- 位置 ,其中
- 相对距离 k 对应旋转角度
- 所有位置都在同一个圆上,只是角度不同
不同维度的不同"频率":
# 低频维度(i 小)
ω_small = 1/10000^{2*0/512} ≈ 1.0 # 缓慢变化
# 高频维度(i 大)
ω_large = 1/10000^{2*255/512} ≈ 1/10000 ≈ 0.0001 # 快速振荡
7. 在注意力机制中的作用
在注意力计算中,会包含位置编码的信息:
由于 位置编码 是 位置编码 的线性函数,模型可以学会:
- 相对位置模式:如"动词后面常跟名词"(距离1)
- 句法结构:如主谓一致(固定距离关系)
- 局部依赖:相邻词有强关联
8. 与可学习位置编码的对比
| 特性 | 三角函数编码 | 可学习编码 |
|---|---|---|
| 外推性 | ✅ 好(可计算任意位置) | ❌ 差(只训练过固定长度) |
| 相对位置 | ✅ 内置(三角公式) | ❌ 需要显式学习 |
| 距离信息 | ✅ 明确编码 | ❌ 隐含在参数中 |
| 长序列 | ✅ 支持(周期性) | ❌ 可能过拟合 |
9. 现代变体:RoPE(旋转位置编码)
RoPE 直接将这个思想扩展到复数域:
这样 Q·K 内积自然包含相对位置信息:
相对位置 k 只影响相位,不改变模长。
10. 实际验证代码
import numpy as np
def sinusoidal_position_encoding(pos, d_model=512):
"""计算sinusoidal位置编码"""
position = np.arange(pos)[:, np.newaxis]
div_term = np.exp(np.arange(0, d_model, 2) * -(np.log(10000.0) / d_model))
pe = np.zeros((pos, d_model))
pe[:, 0::2] = np.sin(position * div_term) # 偶数维度
pe[:, 1::2] = np.cos(position * div_term) # 奇数维度
return pe
# 验证线性关系
d_model = 512
pos = 10
k = 3
i = 10 # 看第10个维度对(实际是第20、21维)
pe = sinusoidal_position_encoding(pos + k + 1, d_model)
# 提取两个维度
dim_even = 2*i
dim_odd = 2*i + 1
# 计算β
div_term = np.exp(np.arange(0, d_model, 2) * -(np.log(10000.0) / d_model))
beta = k * div_term[i]
# 验证公式
pe_pos_even = pe[pos, dim_even]
pe_pos_odd = pe[pos, dim_odd]
pe_pos_k_even = pe[pos + k, dim_even]
pe_pos_k_odd = pe[pos + k, dim_odd]
# 根据公式计算
calc_even = pe_pos_even * np.cos(beta) + pe_pos_odd * np.sin(beta)
calc_odd = pe_pos_odd * np.cos(beta) - pe_pos_even * np.sin(beta)
print(f"实际值: [{pe_pos_k_even:.6f}, {pe_pos_k_odd:.6f}]")
print(f"计算值: [{calc_even:.6f}, {calc_odd:.6f}]")
print(f"误差: [{abs(pe_pos_k_even - calc_even):.6f}, {abs(pe_pos_k_odd - calc_odd):.6f}]")
总结
核心洞见:三角函数位置编码通过和角公式,使得相对位置关系被编码为线性变换。这意味着:
- 位置 pos+k 的编码 = 线性矩阵 × 位置 pos 的编码
- 变换矩阵只依赖于相对距离 k,与绝对位置 pos 无关
- 模型可以学习到"距离为k的两个词之间的关系"
- 这种编码具有外推性,可以处理比训练时更长的序列
这正是为什么原始 Transformer 论文说这种编码可以让模型"轻松学习到相对位置信息"。后续的 RoPE、ALiBi 等位置编码方法都是基于类似的洞见发展而来。