直觉理解
神经网络只能处理数字,文本要先变成向量。最直接的办法是 one-hot 编码:词表有 个词,每个词就是一个长度为 、只有一个位置为 1 的向量。但这种表示又长又稀疏,而且任意两个词的向量都正交,「猫」和「狗」的距离与「猫」和「汽车」一样,看不出任何语义关系。
词嵌入把每个词映射成一个低维(几百到几千维)的稠密向量。向量是在训练中学出来的,语义相近的词向量也相近,向量之间的方向甚至能编码类比关系,经典例子是 。
定义与公式
嵌入矩阵
嵌入层就是一个 的矩阵 ,第 行是 id 为 的词的向量。查一个词的向量,等价于用它的 one-hot 向量乘以嵌入矩阵:
实际实现时不做矩阵乘法,而是直接按 id 取出对应的行(查表),效率高得多。嵌入矩阵和网络的其他参数一样,通过反向传播训练。
与分词的关系
「词」嵌入这个名字沿用自早期按整词建词表的做法。现代语言模型先用分词器把文本切成 token(通常是子词,如 BPE 的产物),再给每个 token 分配一个 id,嵌入层查的是 token 的向量。词表大小、切分粒度都由分词方案决定,详见分词。
Word2Vec 的思想
Word2Vec 由 Mikolov 等人在 2013 年提出,依据的是分布假说:一个词的含义由它周围经常出现的词决定。它训练一个非常浅的网络来完成一个「假任务」,真正要的是训练副产品,也就是嵌入矩阵:
- CBOW:用上下文的词预测中间的词。
- Skip-gram:用中间的词预测它周围的词。
为了避免每次都在整个词表上做 softmax,Word2Vec 使用负采样等技巧:把真实的「中心词–上下文词」对当作正例,随机抽的词对当作负例,训练一个二分类器。之后的 GloVe(2014)从全局词共现统计出发,也得到了类似的词向量。
静态嵌入与上下文相关的表示
Word2Vec 给每个词一个固定的向量,「苹果」在「吃苹果」和「苹果手机」里是同一个向量,无法区分多义词。在 Transformer 中,嵌入层给出的只是初始向量,经过多层注意力机制与上下文交互之后,每个 token 的表示才真正依赖语境。
相似度
两个嵌入向量的接近程度通常用余弦相似度衡量:
代码示例
import torch
import torch.nn as nn
import torch.nn.functional as F
vocab_size, d_model = 10, 4
emb = nn.Embedding(vocab_size, d_model)
ids = torch.tensor([1, 5, 2]).view(1, 3) # 分词器输出的 token id
vecs = emb(ids)
print(vecs.shape) # torch.Size([1, 3, 4])
# 查表等价于 one-hot 乘嵌入矩阵
one_hot = F.one_hot(ids, vocab_size).float()
print(torch.allclose(one_hot @ emb.weight, vecs)) # True
# 余弦相似度衡量两个词向量的接近程度
print(F.cosine_similarity(vecs[0, 0], vecs[0, 1], dim=0))在 AI 中的作用
每个语言模型的第一层都是嵌入层,把 token id 变成向量;输出层则把向量映射回词表上的分数。很多模型让这两层共享同一个矩阵(权重共享),以减少参数。「嵌入」的思想也推广到了句子、图像、音频等各种对象:把它们映射到同一个向量空间,就能用向量相似度做检索和匹配,这是语义搜索和检索增强生成的基础。
相关
- 分词:嵌入层查的 token 从哪里来。
- Transformer:嵌入层之后的上下文建模。
- 语言模型:输入嵌入与输出层。
- 线性代数:向量、点积与余弦相似度。
- 嵌入与检索模型:把整段文本编码成向量用于检索。