Skip to content

第 2 章 词嵌入:把文字变成向量

本章目标:理解为什么神经网络需要将离散的字 ID 转换为连续向量,以及 nn.Embedding 层的工作原理。


2.1 独热编码的问题

计算机只认识数字。要把"春"这个字输入神经网络,最直接的方法是独热编码(One-Hot Encoding):词表有多少个字,就用多长的向量,只有对应位置为 1,其余全为 0。

假设词表大小为 5(简化示例):

春 → [1, 0, 0, 0, 0]
眠 → [0, 1, 0, 0, 0]
不 → [0, 0, 1, 0, 0]

这有两个严重问题:

  1. 维度爆炸:本项目词表有 2439 个字,每个字就是一个 2439 维的稀疏向量,参数量极大
  2. 无法表达语义相似性:"春"和"夏"都是季节,但它们的独热向量之间的余弦相似度为 0,两者在数学上完全无关

2.2 稠密词嵌入

词嵌入(Word Embedding) 的思路是:用一个低维稠密向量来表示每个字,向量中的每个维度都是可学习的实数。

春 → [0.23, -0.81, 0.44, ..., 0.12]   # 256 维
眠 → [0.11,  0.56, -0.33, ..., 0.87]
不 → [-0.42, 0.29, 0.71, ..., -0.15]

关键性质:训练结束后,语义相近的字在向量空间中会自动靠近。例如"春夏秋冬"四个字的嵌入向量会聚在相近的区域,"山水云月"也会形成另一个簇。

这个性质不是人工设计的,而是模型在最小化预测损失的过程中自发学习到的。


2.3 Embedding 层的本质:一张查找表

nn.Embedding 在实现上非常简单——它就是一个形状为 (vocab_size, embedding_dim) 的矩阵(查找表):

         embedding_dim (256)
         ┌─────────────────┐
字 ID=0  │  0.23 -0.81 ... │  ← "春"的嵌入向量
字 ID=1  │  0.11  0.56 ... │  ← "眠"的嵌入向量
字 ID=2  │ -0.42  0.29 ... │  ← "不"的嵌入向量
  ...    │       ...        │
字 ID=2438│  0.77 -0.13 ... │  ← "<UNK>"的嵌入向量
         └─────────────────┘

前向传播就是查表:给定字 ID,返回对应的行向量。这个操作不涉及矩阵乘法,效率极高。

python
embedding = nn.Embedding(num_embeddings=2439, embedding_dim=256)

# 输入:字 ID 序列,形状 (N, L) = (批大小, 序列长度)
x = torch.LongTensor([[10, 23, 5, 88]])   # shape: (1, 4)

# 输出:对应的嵌入向量,形状 (N, L, E) = (1, 4, 256)
embedded = embedding(x)

2.5 维度详解:N、L、E 各代表什么

这是很多同学最容易混淆的地方。我们用一个具体例子来把三个维度讲清楚。

从一首诗到一个批次

假设我们有两首诗(已去标点),各取前 4 个字:

样本 0:春 眠 不 觉  → ID: [156, 891, 73, 441]
样本 1:山 月 随 人  → ID: [623, 891, 512, 307]

把它们打包成一个 batch,得到形状为 (N=2, L=4) 的整数张量:

x = [[156, 891,  73, 441],    ← 第 0 首诗的 4 个字 ID
     [623, 891, 512, 307]]    ← 第 1 首诗的 4 个字 ID

x.shape = (2, 4)  即 (N, L)

N(batch size)= 2:一次喂给模型的样本数量,两首诗并排放在第 0 轴。
L(sequence length)= 4:每首诗的序列长度,沿第 1 轴展开每个时间步。

经过 Embedding 后多了一个轴

Embedding 层对 x 中每个整数 ID 独立查表,返回对应的 256 维向量:

x.shape     = (N=2, L=4)        ← 输入:整数 ID
embedded.shape = (N=2, L=4, E=256) ← 输出:每个 ID 换成了 256 维向量

用图来理解这个"升维"过程:

输入 x (2×4 个整数)       输出 embedded (2×4×256 个浮点数)

┌──────────────────┐        ┌──────────────────────────────┐
│ 156  891  73  441│  查表  │[0.23,-0.81,...]  ← 位置(0,0) │
│ 623  891 512  307│ ─────▶ │[0.11, 0.56,...]  ← 位置(0,1) │
└──────────────────┘        │      ...                      │
   N=2  L=4                 │[0.45,-0.32,...]  ← 位置(1,3) │
                            └──────────────────────────────┘
                               N=2  L=4  E=256

E(embedding size)= 256:每个字的向量表示有多少维。第 2 轴(最后一轴)就是这个向量本身。

三个轴的物理含义

符号大小含义
第 0 轴Nbatch_size = 32哪一条样本(批次中第几首诗)
第 1 轴Lseq_len = 24哪一个时间步(诗的第几个字)
第 2 轴Eembedding_size = 256向量的第几个维度(嵌入向量本身)

记忆口诀:轴 0 选样本,轴 1 选位置,轴 2 看特征。


2.4 嵌入维度怎么选

嵌入维度 E 是一个需要权衡的超参数:

E 过小E 合适E 过大
向量空间太拥挤,无法区分相似字表达能力与计算量平衡参数量暴增,容易过拟合

一个经验规则:Evocab_size0.25,本项目词表 2439,计算得 24390.257,这只是下界参考。

实践中常用的值是 128、256、512。本项目选择 256,足以区分 2439 个字的语义,同时参数量(2439×25662 万)在可接受范围内。


小结

  • 独热编码:维度爆炸 + 无法表达语义相似性
  • 词嵌入:低维稠密向量,可学习,语义相近的字向量相近
  • nn.Embedding = 可学习的查找表,前向传播就是按 ID 取行
  • 本项目使用 256 维嵌入,参数量约 62 万

基于 MIT 协议发布