AI Research

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加法,数学公式⚠️ 有限
RoPELLaMA, 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 了——这是下一章的内容。


本章总结

  1. Token Embedding 是一个查表操作:用 token ID 在 embedding 矩阵中找到对应的行
  2. 每个 token 被表示为一个 d 维向量(GPT-2: 768 维,GPT-3: 12288 维)
  3. 语义相近的 token 在向量空间中距离更近——这是训练自然学到的
  4. 位置编码告诉模型 token 的顺序,没有它 Transformer 分不清”猫追狗”和”狗追猫”
  5. 现代模型主要使用 RoPE(旋转位置编码),编码相对位置,泛化性更好
  6. 最终输入 = Token Embedding + 位置编码,形状为 [序列长度, 嵌入维度]

下一篇:04. Self-Attention:Transformer 的核心计算 — 模型如何”关注”序列中的关键信息