4. 实现位置编码(正弦)
任务说明
本案例实现Transformer中的正弦位置编码(Sinusoidal Positional Encoding)。给定序列长度 seq_len 和模型维度 d_model,生成一个形状为 [seq_len, d_model] 的编码矩阵,其中每个位置的值由不同频率的正弦/余弦函数确定。该编码将被添加到词嵌入上,使模型感知词序信息。
实现方案
位置编码公式如下:
PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))
其中 pos 为位置索引(0到seq_len-1),i 为维度索引(0到d_model/2-1)。偶数维度使用正弦,奇数维度使用余弦。实现采用NumPy向量化计算,避免逐元素循环。
代码实现
import numpy as np
def sinusoidal_positional_encoding(seq_len: int, d_model: int) -> np.ndarray:
"""
生成正弦位置编码矩阵。
参数:
seq_len: 序列长度(位置数量)
d_model: 模型维度(必须为偶数)
返回:
shape = [seq_len, d_model] 的 float32 数组
"""
assert d_model % 2 == 0, "d_model 必须为偶数"
# 位置索引: [seq_len, 1]
pos = np.arange(seq_len, dtype=np.float32).reshape(seq_len, 1)
# 维度索引 i: [1, d_model // 2]
i = np.arange(d_model // 2, dtype=np.float32).reshape(1, -1)
# 角度频率: 10000^(2i/d_model)
angle_rates = 1.0 / np.power(10000.0, (2 * i) / d_model)
# 角度矩阵: [seq_len, d_model // 2]
angles = pos * angle_rates
# 偶数维度用 sin,奇数维度用 cos
pe = np.zeros((seq_len, d_model), dtype=np.float32)
pe[:, 0::2] = np.sin(angles)
pe[:, 1::2] = np.cos(angles)
return pe
# 示例:生成 seq_len=5, d_model=8 的位置编码
if __name__ == "__main__":
pe = sinusoidal_positional_encoding(seq_len=5, d_model=8)
print("位置编码矩阵 shape:", pe.shape)
print(pe)
关键设计说明
- 维度约束:要求
d_model为偶数,确保每个位置都能被均分为正弦/余弦两组。 - 向量化计算:通过广播机制一次性生成所有位置和维度的角度值,避免Python循环,计算效率高。
- 相对位置感知:正弦编码的线性性质使得
PE(pos+k)可以表示为PE(pos)的线性函数,这有助于模型学习相对位置关系。 - 数值范围:输出值域为 [-1, 1],与词嵌入相加后不会导致数值溢出。
落地与验证
运行方式:将上述代码保存为 positional_encoding.py,在Python环境中执行 python positional_encoding.py(需安装NumPy)。
验证方法:
- 形状验证:输出矩阵shape应为
(5, 8)。 - 数值验证:手动计算
PE(0, 0) = sin(0) = 0,PE(0, 1) = cos(0) = 1,PE(1, 0) = sin(1/10000^(0)) = sin(1) ≈ 0.8415。观察输出首行应为[0, 1, 0, 1, 0, 1, 0, 1](因为所有偶数维在pos=0时sin(0)=0,奇数维cos(0)=1)。 - 维度对应验证:检查第0列(偶数维)全为正弦值,第1列(奇数维)全为余弦值。
判定标准:程序无报错运行,输出矩阵形状正确,且首行数值与上述手动计算结果一致,即视为实现正确。