进入注意力机制之前,有一个基础构件需要看清楚:嵌入层。
第 09 章节简要提过,嵌入层是一张 65,536 × 1,536 的查找表——给一个 token ID,返回一行 1,536 个数字。这一章节把它讲透:这张表怎么初始化、怎么被训练、训练之后学到了什么。
查表的过程
嵌入层没有任何复杂的计算。它就是一个二维数组(矩阵),每一行对应一个 token:
行 0: [0.023, -0.156, 0.891, ..., 0.334] ← token 0 的向量
行 1: [0.712, 0.045, -0.223, ..., 0.567] ← token 1 的向量
行 2: [...]
...
行 65535: [-0.134, 0.678, 0.012, ..., -0.891] ← token 65535 的向量
当 tokenizer 把一段文本编码成 token ID 序列 [3847, 2190, 1523, 892, 4401] 之后,嵌入层做的就是:
output = []
for token_id in [3847, 2190, 1523, 892, 4401]:
output.append(embedding_table[token_id]) # 去第 token_id 行取向量
五次查表,得到五个 1,536 维向量。排起来就是一个 5×1,536 的矩阵。
这个操作等价于一种特殊的矩阵乘法:把 token ID 转成 one-hot 向量(在 ID 对应的位置放 1,其余全是 0),然后乘以嵌入矩阵。但实际实现中没有人真的去做这个乘法——直接查表比乘法快得多。
训练前:随机噪声
训练开始之前,这张表的每一个数字都是随机初始化的——通常是从均值为 0、标准差很小的正态分布中采样的随机数。
这意味着训练开始时:
- 「猫」和「狗」的向量没有任何相似性——它们只是两组随机数字。
- 「的」和「了」的向量也没有任何关系。
- 所有 token 在 1,536 维空间中的分布像一团均匀的噪声,没有结构。
模型在这个阶段对语言一无所知。
训练中:梯度推动向量移动
训练过程中发生了什么?
回顾第 01 章节的学习循环:模型预测下一个词 → 跟正确答案比较 → 计算 loss → 根据 loss 调整参数。嵌入表的每一个数字都是参数,都会在这个过程中被调整。
具体来说,当模型在某个位置预测错了,梯度会从 loss 一路反向传播到嵌入层(反向传播是第 25 章节的主题),告诉嵌入层:「这个 token 的向量需要往某个方向移动一点」。
经过 200 亿个 token 的训练,每个 token 的向量都被推了无数次。最终结果是:
- 经常出现在相似上下文中的词,向量被推到了相近的位置。「猫」「狗」「兔子」经常出现在「养了一只__」「__跑过来」这样的语境中,它们的向量逐渐靠近。
- 功能相似的词也会靠近。「但是」「然而」「不过」都是转折连词,向量相近。
- 含义无关的词被推到远离彼此的位置。「猫」和「经济」几乎不会出现在相似的上下文中,向量越推越远。
没有人给模型标注了「猫和狗是动物」——这个知识完全是从上下文的统计规律中涌现出来的。
训练后:有结构的空间
训练结束后,65,536 个 token 在 1,536 维空间中不再是一团无序的噪声,而是形成了有意义的结构。
虽然我们无法直接观察 1,536 维空间,但可以用降维可视化工具(比如 t-SNE 或 UMAP)把高维向量投影到二维平面上来观察。在典型的词向量可视化中,你会看到:
- 动物词聚成一簇
- 数字词聚成一簇
- 国家名聚成一簇
- 动词、名词、形容词各自有偏向的区域
- 中文词和对应的英文词在空间中的相对位置相似
这就是嵌入层学到的东西:一张把离散的 token ID 映射到连续高维空间的地图,地图上的距离和方向编码了语义关系。
嵌入层的参数量
65,536 × 1,536 = 100,663,296,约 1 亿个参数。
这 1 亿个参数占了 Sophia 总参数量(约 10.1 亿)的大约 10%。它们没有做任何「计算」——只是存储了每个 token 的向量表示。但这张表是模型理解语言的第一步:如果嵌入向量学得不好,后面的 28 层 Transformer 拿到的输入就是混乱的,再多的计算也难以补救。
还有一个细节:Sophia 的输出层(把 1,536 维向量映射回 65,536 维、生成对每个词的预测概率的那一层)和嵌入层共享同一张表——这种做法叫权重绑定(weight tying)。共享权重既节省了参数(不用存两份 1 亿参数的表),也让输入和输出的语义空间保持一致。
下一章节进入本单元的核心:自注意力。一个 token 的向量怎么「看到」前面其他 token 的向量,怎么根据上下文修正自己的表示。