🎯 课程主题
位置编码(Positional Encoding)——用正余弦三角函数为词嵌入向量注入时序位置信息。
📝 核心知识点
1. 位置编码的必要性
- 概念说明:词嵌入向量只包含语义信息,不含时序顺序信息,需要位置编码补充。
- 关键细节:
- 时间序列任务(如翻译)中,句子有明显的先后顺序
- 词嵌入向量仅有语义信息,无先后顺序信息
- 位置编码 + 词嵌入向量 → 同时包含语义与位置信息
- 位置编码向量维度必须与词嵌入向量相同($1 \times D$),才能做相加融合
2. 位置编码公式
- 概念说明:Transformer 使用 sin 和 cos 三角函数构建位置编码。
- 关键细节:
- $PE$ 表示位置编码向量
- 参数:
- $pos$:当前 token 在序列中的位置(从 0 开始)
- $i$:向量中的维度索引
- $d_{model}$:词嵌入维度 $D$(Transformer 中为 512)
- 偶数位用 $\sin$,奇数位用 $\cos$ 交替计算
- 每个位置的编码是固定的(非可训练参数)
3. 位置编码计算示例
- 概念说明:以 "I love you" 为例说明位置编码计算。
- 关键细节:
- "I" 在位置 0,"love" 在位置 1,"you" 在位置 2
- 计算 "love"($pos=1$)的位置编码:
- 第 0 维(偶数位):$\sin(1 / 10000^{2 \times 0 / 512})$
- 第 1 维(奇数位):$\cos(1 / 10000^{2 \times 1 / 512})$
- 第 2 维(偶数位):$\sin(1 / 10000^{2 \times 2 / 512})$
- ...直到 512 维全部计算完
- 示例:$D=3$ 时,"我是一条狗"(5 个 token)的位置 0 编码为 $[0, 1, 0]$
4. 位置编码矩阵
- 概念说明:整个序列的位置编码构成一个矩阵。
- 关键细节:
- 序列长度 $L$,每个位置编码维度 $D$ → 位置编码矩阵 $L \times D$
- 第 0 行:位置 0 的编码(512 个数值)
- 第 1 行:位置 1 的编码
- ...
- 与词嵌入矩阵 $L \times D$ 逐元素相加
🧮 核心公式与推导
位置编码公式:
$$PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d_{model}}}\right)$$
$$PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d_{model}}}\right)$$
其中:
- $pos$:token 在序列中的位置($0, 1, 2, \ldots$)
- $i$:维度索引($0, 1, 2, \ldots, d_{model}/2 - 1$)
- $d_{model}$:词嵌入维度($D = 512$)
- $10000$:基数常数
物理意义:
- 偶数维度用 $\sin$,奇数维度用 $\cos$,形成不同频率的波形
- 不同位置产生唯一的编码模式
- 相对位置关系可通过三角函数的性质表达($\sin(\alpha + \beta)$ 公式)
融合公式:
$$\text{Input} = \text{Embedding}(X) + PE(X)$$
🏗️ 模型架构与数据流向
维度变化:
- 词嵌入:$L \times D$($L$ 为序列长度,$D=512$)
- 位置编码:$L \times D$
- 融合结果:$L \times D$(逐元素相加)
💻 代码实战
import torch
import math
def positional_encoding(seq_len, d_model):
"""
生成位置编码矩阵
seq_len: 序列长度 L
d_model: 嵌入维度 D (Transformer中为512)
return: [seq_len, d_model] 位置编码矩阵
"""
pe = torch.zeros(seq_len, d_model)
position = torch.arange(0, seq_len).unsqueeze(1).float() # [L, 1]
# 计算分母项: 10000^(2i/d_model)
div_term = torch.exp(
torch.arange(0, d_model, 2).float() *
(-math.log(10000.0) / d_model)
) # [D/2]
# 偶数位用sin, 奇数位用cos
pe[:, 0::2] = torch.sin(position * div_term) # [L, D/2]
pe[:, 1::2] = torch.cos(position * div_term) # [L, D/2]
return pe
# 示例
seq_len = 4 # "我喜欢打篮球" 4个token
d_model = 512 # 嵌入维度
pe = positional_encoding(seq_len, d_model)
print(pe.shape) # torch.Size([4, 512])
# 与词嵌入相加
# embedding: [L, D] + pe: [L, D] → [L, D]
# input = embedding + pe
⚠️ 常见问题与避坑指南
- 位置编码维度必须与词嵌入维度 $D$ 相同,否则无法相加
- 位置编码是固定的(非可训练参数),不随模型训练更新
- 偶数维度用 $\sin$,奇数维度用 $\cos$,不要搞混
- $pos$ 从 0 开始计数
💡 个人总结与延伸
位置编码是 Transformer 处理时序信息的关键,通过三角函数为每个位置生成唯一编码,使模型能区分词的顺序。现代大模型中,位置编码有多种变体:如可学习位置编码(GPT)、旋转位置编码 RoPE(LLaMA、Qwen)、ALiBi 等,但核心目的都是为模型注入位置信息。