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)。

验证方法:

  1. 形状验证:输出矩阵shape应为 (5, 8)。
  2. 数值验证:手动计算 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)。
  3. 维度对应验证:检查第0列(偶数维)全为正弦值,第1列(奇数维)全为余弦值。

判定标准:程序无报错运行,输出矩阵形状正确,且首行数值与上述手动计算结果一致,即视为实现正确。

正弦位置编码生成流程 seq_len = 5 d_model = 8 pos = [0,1,2,3,4] shape: [5,1] angle_rates[i] = 1/10000^(2i/8) angles = pos × rates shape: [5,4] 偶数维: sin(angles) → pe[:, 0::2] 奇数维: cos(angles) → pe[:, 1::2] PE 矩阵 [5, 8] 行=位置, 列=维度 pos=0: [0, 1, 0, 1, 0, 1, 0, 1] pos=1: [0.84, 0.54, 0.10, 0.99, ...] (示例值,实际以运行输出为准)