Appearance
词嵌入
神经网络只认数字,不认文字。词嵌入(Word Embedding) 的任务,就是把人类语言里的「词 / 子词 / 字符」翻译成一串串可以被计算的向量,并且让意思相近的词,向量也相近。
为什么需要嵌入
模型的输入必须是张量(数字矩阵)。最朴素的做法是 One-Hot:
词表 = [猫, 狗, 鱼, 书]
猫 → [1,0,0,0]
狗 → [0,1,0,0]
鱼 → [0,0,1,0]
书 → [0,0,0,1]One-Hot 有两个致命问题:
- 维度爆炸:词表有 5 万个词,每个向量就得 5 万维,且绝大多数是 0(稀疏)。
- 语义缺失:任意两个 One-Hot 向量的点积都是 0,「猫」和「狗」的距离,与「猫」和「书」的距离完全一样——模型看不出谁和谁更像。
稠密向量:让距离有意义
词嵌入用低维稠密向量(如 512、768 维)表示一个词,每个维度都可以理解为某种「隐含特征」的强度:
维度含义(示意,实际由模型自己学出来)
动物性 可爱度 水生 ...
猫 → [ 0.9 , 0.8 , 0.1 ]
狗 → [ 0.9 , 0.7 , 0.0 ]
鱼 → [ 0.6 , 0.2 , 0.9 ]
书 → [ 0.0 , 0.1 , 0.0 ]
向量距离: 猫≈狗 > 猫~鱼 > 猫≠书经典的「向量算术」直观展示了这种语义结构:
king - man + woman ≈ queen
北京 - 中国 + 法国 ≈ 巴黎嵌入是「查表」,而且是学出来的
实现上,词嵌入就是一张巨大的查找表(Embedding Matrix):
嵌入矩阵 E 的形状 = [词表大小 V, 嵌入维度 d_model]
第 0 行 → 词表第 0 个词的向量
第 1 行 → 词表第 1 个词的向量
...
输入 token id = 5 → 取 E[5] 这一行向量,就是它的嵌入关键在于:这张表的每一行都是训练时学出来的,不是人工设定。模型为了把下游任务做好,会自动把语义相近的词在向量空间里拉近。
随机初始化 E ──训练(反向传播)──► 有意义的语义空间 E在 PyTorch 里就一层
python
import torch
import torch.nn as nn
V = 10000 # 词表大小
d_model = 512 # 嵌入维度(Transformer 的"宽度")
# 一张可学习的查找表
embedding = nn.Embedding(V, d_model)
ids = torch.tensor([[5, 128, 7, 0]]) # 一句话: 4 个 token
x = embedding(ids) # 形状 [1, 4, 512]
print(x.shape) # torch.Size([1, 4, 512])- 输入:一串 token id(整数)。
- 输出:
[batch, 序列长度, d_model]的浮点张量——这就是后续所有注意力运算的起点。
一个必须搞清的量:d_model
d_model(原始论文取 512)是 Transformer 的「主干道宽度」,贯穿全流程:
token id ──嵌入──► 向量(维度 d_model)
│
位置编码(同样 d_model)└──► 相加,仍是 d_model
│
注意力/FFN 全程都在 d_model 空间里流动记住:嵌入层输出必须是 d_model 维,才能和位置编码相加、才能进入注意力。这是各模块能「串起来」的前提。
词、子词还是字符?
「一个词对应一个向量」在现代大模型里其实是简化的说法。真实情况有三种切分粒度:
| 粒度 | 例子 | 优缺点 |
|---|---|---|
| 字符级 | h,e,l,o | 词表极小,但序列很长、语义稀疏。适合 NanoGPT 练手 |
| 词级 | hello,world | 直观,但遇到没见过的词(OOV)就抓瞎,词表巨大 |
| 子词级(主流) | un,##believ,##able | 兼顾词表大小与覆盖率,是 BERT/GPT 的实际做法 |
子词的切分由分词器(Tokenizer) 完成,嵌入层则负责把切好的 token id 变成向量。二者是上下游关系:
文本 ──Tokenizer──► token 序列 ──Embedding查表──► 向量序列
(见 Transformers库/分词器) (本页)详见 分词器。
权重共享的小技巧
在 GPT 这类模型里,输入嵌入矩阵和最后输出投影矩阵常常共享同一份权重(tied embeddings):既省参数,又让「一个词作为输入的含义」和「作为输出被预测的含义」保持一致。这一点我们会在 搭建NanoGPT 里实际用到。
小结
- 嵌入把离散符号变成低维稠密向量,让「语义相似 = 向量相似」。
- 本质是一张可学习的查找表,
nn.Embedding一行搞定。 - 输出维度必须为
d_model,这是所有后续模块的接口。 - 现代模型多用子词切分,分词与嵌入是两步。
下一步
有了向量,但还不知道「谁在第几位」。下一页给向量打上顺序戳 → 位置编码。