🐸 青蛙编程网
首页 / AI / Transformer 原理 / 5. 位置编码

5. 位置编码:谁在前谁在后

这是什么:位置编码(Positional Encoding) = 给每个位置的词向量加上一行代表"我排第几"的数字,让全靠注意力平行处理的模型感知语序。

为什么用:第 2 章的嵌入矩阵只认词不认顺序——"猫追狗"和"狗追猫"查出的向量一模一样,意思却完全相反。注意力又天生"不分先后",必须专门把顺序的信息补进去。

如何用:本章手搓经典的三角函数位置编码,看每个位置的向量如何随位置滑出波纹。

编码要点:像一组指针

Transformer 用一组不同频率的正弦/余弦波给位置编号:偶数维放 sin、奇数维放 cos,且波长随维度下标递减——低维波长短,像秒针,转眼就能区分相邻位置;高维波长长,像日历,负责撑起远距离的分辨。

import numpy as np

T, d_model = 4, 6                    # 句长 4 个词,向量 6 维
pos = np.arange(T)[:, None]          # 位置:0, 1, 2, 3(列向量)
i = np.arange(d_model)[None, :]      # 维度下标:0 ~ 5(行向量)

angle = pos / (10000 ** (2 * (i // 2) / d_model))   # 各位置各维度的相位
pe = np.where(i % 2 == 0, np.sin(angle), np.cos(angle))  # 偶维 sin、奇维 cos

print(np.round(pe, 2))

你会看到:

[[ 0.    1.    0.    1.    0.    1.  ]
 [ 0.84  0.54  0.05  1.    0.    1.  ]
 [ 0.91 -0.42  0.09  1.    0.    1.  ]
 [ 0.14 -0.99  0.14  0.99  0.01  1.  ]]
代码 作用
pos[:, None] / i[None, :] 广播前的形状调整:一行位置 × 一列维度,相除成 4×6 相位表
10000 ** (2 * (i // 2) / d_model) 波长公式:i 越大波长越长,像是"秒针表 + 时钟表 + 日历"齐上阵
np.where(i % 2 == 0, sin, cos) 偶数列放 sin、奇数列放 cos,合起来就是正负一来回摆动
输出第 1 列 sin(位置):0、0.84、0.91、0.14,一眼看出"随位置划过弧线"

加上词向量只一步:H = 词向量 + 位置编码。两个形状相同、按位相加,位置信息便"贴"在了每个词的内容之上。

现状一句:RoPE 已经接班

这套三角函数编码是论文原版写法;如今主流大模型多用 RoPE(旋转位置编码),把"位置"改造成对 Q/K 向量的旋转,长文本外推更稳。但思路同源:都要解决"注意力天生不知道顺序"这一点——手算一遍正弦版,再理解 RoPE 就顺了。

记忆口诀:偶维 sin 奇维 cos,波长随维慢慢长;低频秒针分近处,高频日历辨远方。

动手改

  1. 把 d_model 改成 8,观察新增两列的波形频率变化
  2. 手算 pos=2、i=0 这格的相位:2 / (10000 ** 0) = 2,再对照输出里的 0.91——正是 sin(2) ≈ 0.909
  3. 把 T 改到 10,看第 1 列 sin(位置) 怎么循环起来

下一章:BERT:看懂句子的编码器,配齐同一图纸的另一半玩法 🐸

📥 下载本章代码

ZIP 内含本章全部代码文件与说明,解压即用,与教程内容同步生成。