2026年9月20日
03 Embedding:token变成向量
本质上就是查表。 真正有意思的地方在于,这张表里的数字是怎么来的。
第一步:查表
上次说到,分词器把文本变成一串 token ID,比如"我爱猫"变成 [1234, 567, 8901]。
模型内部有一张 embedding 矩阵,行数等于词表大小,列数等于 dimension。比如词表有 10 万个 token,dimension 是 4096,这张表就是 10 万行 × 4096 列。
把 token 变成向量,就是按 ID 取出对应的那一行:ID 是 1234,就取第 1234 行,得到一个长度为 4096 的向量。没有任何复杂计算。
如果用你在吴恩达课程里学过的线性代数来表述:把 token ID 表示成一个 one-hot 向量(只有第 1234 位是 1,其余全是 0),再乘以这个 embedding 矩阵,结果恰好就是第 1234 行。查表只是这个矩阵乘法的高效实现。
顺便算一下,这张表本身就有 10 万 × 4096 ≈ 4 亿个参数,是模型参数的一部分。
第二步:表里的数字从哪来
这才是关键。训练开始时,这张表里全是随机数。 "猫"和"狗"的向量毫无关联,和"汽车"也一样。
然后,它和模型的其他参数一起参与训练。模型每次预测下一个 token 出错,梯度下降不仅会调整 Transformer 各层的参数,也会调整这次用到的那几行 embedding。
为什么调着调着,语义相近的词就会靠在一起?背后有一个语言学上的观察,叫分布假说:一个词的含义,由它经常出现的上下文决定。"猫"和"狗"经常出现在相似的句子里,比如"我家的__很可爱""给__喂食""带__去看兽医"。训练中,为了让模型在这些位置都能做出好的预测,它们的向量会被推向相似的方向。而"汽车"出现的上下文完全不同,就被推到别的区域。
所以,语义结构不是人设计的,而是从"预测下一个 token"这个任务中自然涌现出来的。 这也印证了之前说的:向量的每个维度没有人为定义的含义,是模型自己学出来的。
一个经典的例子来自早期的 word2vec 研究:训练出的向量甚至能做"语义运算","国王" − "男人" + "女人" 的结果,最接近"女王"的向量。说明向量空间中的某些方向,对应了像"性别"这样的抽象概念。
两个容易忽略的细节
位置信息需要另外加入。 查表只看 ID,不管位置,所以"狗咬人"和"人咬狗"查出来是同一组向量,只是顺序不同。而 Transformer 的注意力机制本身对顺序不敏感,因此模型还要额外注入位置信息。早期做法是给每个位置加一个位置向量,现在主流模型多用一种叫 RoPE 的方法,在注意力计算时编码位置。
查表得到的向量是"静态"的,经过模型各层后才变成"动态"的。 "苹果"这个 token 无论在"吃苹果"还是"苹果手机"里,查表得到的初始向量完全一样。区分这两种含义,靠的是后面的 Transformer 层:通过注意力看到上下文中的"吃"或"手机",逐层修改这个向量。所以经过几十层计算后,同一个"苹果"会变成截然不同的向量。这就是所谓的上下文相关表示,也是 Transformer 比早期 word2vec 强大的关键。
和 RAG 中的 embedding 的区别
这里要区分两个容易混淆的东西。我们今天说的,是大模型内部第一层的 token embedding,对象是单个 token。而做 RAG 时调用的 embedding 模型,是专门训练出来的另一类模型:它把一整段文本送进 Transformer,在最后一层把所有 token 的向量汇总成一个向量,代表整段话的含义,然后用来计算文本之间的相似度。
两者都叫 embedding,原理同源,但用途不同:前者是模型处理文本的起点,后者是一个完整模型的最终输出。做 agent 开发时,你直接打交道的通常是后者。