2. 从文字到向量:分词与嵌入
这是什么:分词(Tokenization) = 把一句话切成一个个小片段;嵌入(Embedding) = 给每个片段在一张大表里查出一行固定长度的数字,叫词向量。
为什么用:模型只认识数字。想让大模型接龙,第一步就是"话变数字"。上一章用点积算的"相似度",算的对象也正是这些向量——先把话变成向量,注意力才有东西可比。
如何用:本章把"猫吃鱼了"切成词、编码成 id、再到查表里领出一行行向量,看数字如何顶替文字登场。
第一步:分词——把句子切成查字典的"字条"
模型出厂自带一本词表(vocabulary):表里每行是一个它认识的小片段,配一个编号。把句子拿去词表里查号,碰上词表没收的词就用一个兜底片段<未知>顶上:
import numpy as np
vocab = {"<未知>": 0, "猫": 1, "吃": 2, "鱼": 3, "了": 4} # 假装的小词表
sentence = ["猫", "吃", "鱼", "了"]
ids = [vocab.get(w, 0) for w in sentence] # 查表编码,查不到就给 0(<未知>)
print(ids)
你会看到:
[1, 2, 3, 4]
| 代码 | 作用 |
|---|---|
vocab = {...} |
词表:片段 → 编号,真实模型有几万到几十万行 |
vocab.get(w, 0) |
查字典:查不到返回默认值 0,即兜底片段 <未知> |
| 列表推导式 | 一行把整句切成 id 序列,"猫吃鱼了"变成 [1,2,3,4] |
第二步:嵌入——每个 id 领一行自己的数字
id 只是个编号,还带不来"猫和狸花猫很像"这种信息。真正的钥匙是嵌入矩阵(embedding matrix):每行对应一个 id,存的就是这个片段的向量——矩阵是训练出来的,谁和谁常一起出现,向量方向就挨得近:
E = np.array([
[0.0, 0.0, 0.0], # <未知>
[0.9, 0.1, 0.0], # 猫
[0.2, 0.8, 0.3], # 吃
[0.1, 0.3, 0.9], # 鱼
[0.4, 0.6, 0.8], # 了
])
vectors = E[ids] # 按 id 逐行取出,4 个词各领一行向量
print(vectors)
print(vectors.shape)
你会看到:
[[0.9 0.1 0. ]
[0.2 0.8 0.3]
[0.1 0.3 0.9]
[0.4 0.6 0.8]]
(4, 3)
| 代码 | 作用 |
|---|---|
E = np.array([...]) |
嵌入矩阵:行数 = 词表大小,列数 = 向量维度(示意 3 维,真实是几百到几千维) |
E[ids] |
NumPy 的花式索引:用 id 列表一次取出 4 行 |
vectors.shape |
(4, 3):整句变回了 4×3 的纯数字矩阵 |
记忆口诀:话切成片段,片段查编号;编号领向量,数字才登场。
第三步:还差一件事——位置
到目前为止嵌入只回答了"是哪个词",没回答"排在第几"。"猫追狗"和"狗追猫"的词向量一模一样,含义却相反。Transformer 的解法不是词表,而是一套专门的位置编码——第 5 章再拆。
动手改
- 在
sentence里塞一个词表没有的词(如["猫", "啃", "鱼"]),看 id 序列怎么变出兜底的 0 - 把嵌入矩阵的维度从 3 改到 5(每行补两个数),观察
shape的输出 - 手算
E[1]和E[2]的点积,再改大第一维的差异,看点积怎么变
下一章:注意力机制,让每个词学会"看该看的人" 🐸