03. Embedding:从数字到向量空间
03. Embedding:从数字到向量空间
Token ID 只是一个编号,没有任何语义。Embedding 把这个编号变成一个高维向量,让”意思相近的词”在向量空间中距离更近。
一、为什么不能直接用 ID
上一章把文本变成了 token ID 序列:
"巴黎是法国的首都" → [15432, 8821, 562, 7291, 3847, 1023, 9456]
但这些 ID 是任意分配的编号。ID 15432(“巴”)和 ID 15433 之间没有任何语义关系——可能一个是”巴”,另一个是”@”。
如果直接把 ID 当数字输入网络:
- 模型会认为 ID 15432 和 15433 “很接近”(数值差 1)
- 但 ID 15432 和 ID 8821 “很远”(数值差 6611)
- 这完全是错误的——“巴”和”黎”(经常一起出现)反而应该”接近”
我们需要一种表示方法,让语义关系反映在数值关系中。
二、Token Embedding:查表操作
2.1 Embedding 矩阵
模型维护一个巨大的查找表(Embedding 矩阵):
词汇表大小 V = 50,257
嵌入维度 d = 768 (GPT-2 small)
Embedding 矩阵 W_e 的形状: [V, d] = [50257, 768]
这意味着:词汇表中的每个 token 都对应一个 768 维的向量。
W_e = [
[0.012, -0.034, 0.156, ..., 0.089], ← ID 0 的向量
[0.045, 0.023, -0.067, ..., 0.134], ← ID 1 的向量
...
[0.023, -0.156, 0.891, ..., 0.234], ← ID 15432 ("巴") 的向量
...
] (共 50257 行,每行 768 个数)
2.2 查找过程
给定 token ID,找到对应的行,就是这个 token 的向量表示:
token ID = 15432 ("巴")
embedding = W_e[15432] = [0.023, -0.156, 0.891, ..., 0.234] (768 维)
token ID = 8821 ("黎")
embedding = W_e[8821] = [0.045, -0.128, 0.753, ..., 0.198] (768 维)
这个操作叫 lookup(查表),计算上等价于 one-hot 编码乘以 embedding 矩阵:
one-hot(15432) = [0, 0, ..., 0, 1, 0, ..., 0] (第 15432 位为 1,其余为 0)
one-hot(15432) @ W_e = W_e[15432] (矩阵乘法,但实际实现就是直接查行)
2.3 Embedding 矩阵的参数量
GPT-2 small: 50257 × 768 = 38,597,376 ≈ 38.6M
GPT-3: 50257 × 12288 = 617,558,016 ≈ 617.6M
GPT-4 (推测): 100000 × 12288 ≈ 1.2B
GPT-3 光 embedding 矩阵就有 6 亿参数!
2.4 Embedding 是怎么学到语义的
初始时,embedding 矩阵是随机初始化的。“巴”和”黎”的向量完全没有关系。
但在训练过程中,模型需要从 embedding 向量中提取信息来做预测。如果”巴”后面经常跟”黎”(“巴黎”),模型会调整它们的向量,使得后续的注意力机制能捕捉到这个关系。
训练完成后,语义相近的词自然会聚集在向量空间中相近的位置:
向量空间中的距离(概念示意):
"巴黎" ←近→ "伦敦" ←近→ "东京" (都是首都)
↑ 远
"苹果" ←近→ "橘子" ←近→ "香蕉" (都是水果)
↑ 远
"跑步" ←近→ "游泳" ←近→ "骑车" (都是运动)
经典的向量算术:
king - man + woman ≈ queen
"国王"的向量 - "男人"的向量 + "女人"的向量 ≈ "女王"的向量
这不是刻意设计的——是模型在训练中自然学到的。
三、768 维是什么意思
3.1 从低维直觉理解高维
我们很难想象 768 维的空间。但可以从 2 维和 3 维建立直觉。
2 维空间:每个词用 2 个数字表示,可以画在平面上
"猫" → [1.2, 3.5] 在平面上的一个点
"狗" → [1.5, 3.2] 和"猫"很近
"汽车" → [8.1, 1.3] 离"猫"和"狗"都很远
3 维空间:每个词用 3 个数字表示,可以画在立体空间中
多出来的维度让我们能表达更细微的区别——比如第三个维度可能区分”家养/野生”。
768 维空间:每个词用 768 个数字表示
虽然我们画不出来,但数学是一样的。768 个维度可以编码非常丰富的语义信息:
- 某些维度可能编码”是否是名词”
- 某些维度可能编码”情感正负”
- 某些维度可能编码”是否和食物相关”
- …
实际上,每个维度不会有这么清晰的解释——它们是纠缠在一起的(entangled),由训练自动决定。
3.2 向量相似度:余弦相似度
怎么衡量两个向量”有多接近”?最常用的是余弦相似度:
cosine_similarity(A, B) = (A · B) / (|A| × |B|)
其中 A · B 是点积,|A| 是向量长度(L2 范数)。
A = [1, 2, 3]
B = [2, 4, 6]
A · B = 1*2 + 2*4 + 3*6 = 28
|A| = sqrt(1² + 2² + 3²) = sqrt(14) = 3.74
|B| = sqrt(2² + 4² + 6²) = sqrt(56) = 7.48
cosine_similarity = 28 / (3.74 × 7.48) = 28 / 27.97 = 1.0
结果范围:
- 1.0:方向完全相同(语义非常接近)
- 0.0:正交(无关)
- -1.0:方向完全相反(语义相反)
这就是 RAG(检索增强生成)的基础——把问题和文档都变成向量,用余弦相似度找最相关的文档。
四、位置编码:告诉模型”谁在前谁在后”
4.1 为什么需要位置信息
Embedding 把每个 token 变成了一个向量,但这些向量不包含位置信息。
"猫追狗" → embedding("猫"), embedding("追"), embedding("狗")
"狗追猫" → embedding("狗"), embedding("追"), embedding("猫")
如果模型只看 embedding 向量的集合(不看顺序),这两句话的信息是一样的——但意思完全不同!
Transformer 的注意力机制计算的是所有 token 两两之间的关系,本身不关心顺序。所以必须显式注入位置信息。
4.2 绝对位置编码(GPT-2 使用)
维护另一个 embedding 矩阵,专门编码位置:
位置 Embedding 矩阵 W_p 形状: [最大序列长度 L, 嵌入维度 d]
GPT-2: L = 1024, d = 768 → W_p 形状 [1024, 768]
每个位置(0, 1, 2, …)都有一个独立的 768 维向量。
最终输入 = Token Embedding + 位置 Embedding:
位置 0 的 token "猫" (ID 15432):
token_emb = W_e[15432] = [0.12, -0.34, 0.56, ...]
pos_emb = W_p[0] = [0.01, 0.02, 0.03, ...]
最终输入 = [0.13, -0.32, 0.59, ...] (逐元素相加)
位置 1 的 token "追" (ID 8821):
token_emb = W_e[8821] = [0.15, -0.28, 0.44, ...]
pos_emb = W_p[1] = [0.03, -0.01, 0.05, ...]
最终输入 = [0.18, -0.29, 0.49, ...]
W_p 也是模型参数,在训练中学习。
局限:最大序列长度是固定的(GPT-2 只有 1024)。超出这个长度就无法处理。
4.3 正弦位置编码(原始 Transformer 使用)
原始论文用数学公式直接生成位置编码,不需要学习:
PE(pos, 2i) = sin(pos / 10000^(2i/d))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d))
其中 pos 是位置,i 是维度索引。
直觉:不同维度使用不同频率的正弦/余弦波。低维度用高频波(区分相邻位置),高维度用低频波(区分远距离位置)。
维度 0, 1 (高频): sin(pos/1), cos(pos/1)
维度 2, 3: sin(pos/5.6), cos(pos/5.6)
维度 4, 5: sin(pos/31.6), cos(pos/31.6)
...
维度 766, 767 (低频): sin(pos/10000), cos(pos/10000)
优点:不需要学习,理论上可以外推到任意长度。 缺点:实践中外推效果有限。
4.4 RoPE(旋转位置编码,现代主流)
RoPE(Rotary Position Embedding) 是目前 LLaMA、GLM、DeepSeek 等主流模型使用的方案。
核心思想:不是给输入加上位置向量,而是对注意力计算中的 Q 和 K 向量做旋转变换。
传统方式: input = token_emb + pos_emb (加法)
RoPE 方式: Q_rotated = rotate(Q, position)
K_rotated = rotate(K, position)
然后用旋转后的 Q, K 算注意力
旋转的角度取决于位置:位置 m 的向量被旋转 m × θ 度(不同维度对使用不同的基础角度 θ)。
关键性质:两个位置之间的注意力分数只取决于它们的相对距离,而不是绝对位置。
位置 3 和位置 5 的注意力 = f(5 - 3) = f(2)
位置 10 和位置 12 的注意力 = f(12 - 10) = f(2)
两者一样!因为相对距离都是 2
这使得模型更容易泛化到训练时没见过的更长序列。
4.5 三种方案对比
| 方案 | 使用模型 | 实现方式 | 长度泛化 |
|---|---|---|---|
| 绝对位置 (学习) | GPT-2 | 加法,学习参数 | ❌ 固定长度 |
| 正弦位置 | 原始 Transformer | 加法,数学公式 | ⚠️ 有限 |
| RoPE | LLaMA, GLM, DeepSeek | 旋转 Q/K | ✅ 较好 |
五、从输入到进入 Transformer
把上面的步骤串起来,看看一段文字如何变成 Transformer 可以处理的输入:
原始文本: "巴黎是"
第 1 步 - Token 化:
"巴黎是" → [15432, 8821, 562]
第 2 步 - Token Embedding 查表:
W_e[15432] → [0.12, -0.34, 0.56, ..., 0.23] (768 维)
W_e[8821] → [0.15, -0.28, 0.44, ..., 0.18] (768 维)
W_e[562] → [0.08, 0.11, 0.33, ..., 0.42] (768 维)
第 3 步 - 加上位置编码:
位置 0: [0.12, -0.34, ...] + [0.01, 0.02, ...] = [0.13, -0.32, ...]
位置 1: [0.15, -0.28, ...] + [0.03, -0.01, ...] = [0.18, -0.29, ...]
位置 2: [0.08, 0.11, ...] + [0.05, 0.03, ...] = [0.13, 0.14, ...]
最终输入矩阵 X (形状 [3, 768]):
X = [[0.13, -0.32, ...], ← 位置 0 "巴"
[0.18, -0.29, ...], ← 位置 1 "黎"
[0.13, 0.14, ...]] ← 位置 2 "是"
这个矩阵 X 就是 Transformer 第一层的输入。接下来就进入 Self-Attention 了——这是下一章的内容。
本章总结
- Token Embedding 是一个查表操作:用 token ID 在 embedding 矩阵中找到对应的行
- 每个 token 被表示为一个 d 维向量(GPT-2: 768 维,GPT-3: 12288 维)
- 语义相近的 token 在向量空间中距离更近——这是训练自然学到的
- 位置编码告诉模型 token 的顺序,没有它 Transformer 分不清”猫追狗”和”狗追猫”
- 现代模型主要使用 RoPE(旋转位置编码),编码相对位置,泛化性更好
- 最终输入 = Token Embedding + 位置编码,形状为 [序列长度, 嵌入维度]
下一篇:04. Self-Attention:Transformer 的核心计算 — 模型如何”关注”序列中的关键信息