直觉理解

神经网络只能处理数字,文本要先变成向量。最直接的办法是 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 变成向量;输出层则把向量映射回词表上的分数。很多模型让这两层共享同一个矩阵(权重共享),以减少参数。「嵌入」的思想也推广到了句子、图像、音频等各种对象:把它们映射到同一个向量空间,就能用向量相似度做检索和匹配,这是语义搜索和检索增强生成的基础。

相关