系列 · NLP 技术前沿 · 第 2 篇

自然语言处理(二):词向量与语言模型

深入理解 Word2Vec、GloVe 和 FastText 如何将词语转化为捕获语义的向量。掌握数学原理,用 Gensim 训练自己的词嵌入,理解嵌入与语言模型的关系。

$$\vec{\text{king}} - \vec{\text{man}} + \vec{\text{woman}} \approx \vec{\text{queen}}$$

自然语言处理(NLP)的发展轨迹因此发生了根本性转变,进入了“表示学习”的时代。本文将沿着这条脉络展开:从独热编码为何失效,到 Word2Vec 如何通过浅层神经网络打开新思路;从 GloVe 如何利用全局统计信息,到 FastText 如何借助子词 n-gram 处理未见过的词;最终,词嵌入与催生它们的语言模型联系起来,揭示两者之间的深层关系。

自然语言处理(二):词向量与语言模型 — 章节概览图


你将学到什么#

自然语言处理(二):词向量与语言模型 — 章节小结图

  • 独热编码的局限性以及稠密嵌入如何突破这一限制
  • Skip-gram 和 CBOW:两种从局部上下文中学习词向量的方法
  • 负采样:让大规模词表训练变得可行的关键技巧
  • GloVe:通过全局共现统计信息学习高质量词向量
  • FastText:借助子词信息处理罕见词和未登录词问题
  • 词嵌入与语言模型之间的联系,以及为什么神经语言模型越大效果越好
  • 使用 Gensim 训练、评估和可视化词嵌入的完整流程

前置知识第 1 部分 (文本预处理基础)、基本线性代数知识(如点积和矩阵乘法),以及对 softmax 和随机梯度下降的基本理解


从稀疏到稠密:为什么词嵌入如此重要#

独热编码的痛点#

$$\text{cat} = [1, 0, 0, 0, \ldots], \quad \text{dog} = [0, 1, 0, 0, \ldots]$$

这种方法存在三个致命缺陷:

  • 过于稀疏。当 $V = 50{,}000$ 时,每个向量中 99.998% 的元素都是零。存储和计算资源几乎完全浪费在这些无意义的零上。
  • 缺乏语义相似性。任意两个不同的独热向量之间的点积恒为零。模型无法判断“猫”和“狗”更接近,还是“猫”和“量子”更接近——在它看来,所有不同的词都同样遥远。
  • 难以泛化。如果在线性分类器中使用独热编码,每个类别需要 $V$ 个独立权重。即使模型学会了“movie”是正面情感,这对“film”也毫无帮助。

词嵌入的优势#

$$\text{cat} = [0.21, -0.34, 0.78, \ldots], \quad \text{dog} = [0.18, -0.29, 0.71, \ldots]$$

这样一来,$\text{cat} \cdot \text{dog} \gg \text{cat} \cdot \text{quantum}$ ,语义相近的词能够共享参数,下游模型所需的标注数据量大幅减少,甚至可以少一两个数量级。那么问题来了:这些向量中的数字是怎么生成的?

分布假设的核心思想#

本文提到的所有方法都基于语言学家 J. R. Firth 提出的一个核心观点:“一个词的意义由它所在的上下文决定。” 换句话说,经常出现在相似上下文中的词往往具有相似的含义。

  • “The cat sat on the mat” 和 “The dog sat on the mat”
  • “The king ruled the kingdom” 和 “The queen ruled the kingdom” 如果我们训练一个模型,让它根据词预测上下文(或者反过来),那些成功完成任务的参数自然会捕捉到分布规律。而词嵌入正是这种预测任务的副产品。

认真对待这一假设,回报非常丰厚:训练好的词嵌入能够将语义关系编码为向量空间中近似恒定的方向,这正是经典的类比算术得以成立的原因。

词嵌入把语义关系编码成空间中近似恒定的方向

Word2Vec:从局部上下文学词嵌入#

Word2Vec (Mikolov 等, 2013)是首个能够以低成本在数十亿词上训练出高质量词嵌入的工具。它有两种模式——Skip-gramCBOW——都基于单隐藏层的神经网络,且没有非线性激活函数。这种极简设计正是它的核心亮点。

Skip-gram:用目标词预测上下文#

给定一个目标词,模型的任务是预测其固定窗口内的每个上下文词。例如,在句子 “the quick brown fox jumps” 中,如果窗口大小为 2,目标词 “brown” 会生成以下 4 个正样本对:

1
(brown, the)   (brown, quick)   (brown, fox)   (brown, jumps)

模型的结构分为三层:独热输入、嵌入查找矩阵 $W \in \mathbb{R}^{V \times d}$ 、输出矩阵 $W' \in \mathbb{R}^{d \times V}$ 加上 softmax。由于输入是独热向量,嵌入层的操作退化为查表,时间复杂度仅为常数级。

Skip-gram 架构:独热输入、嵌入查表、对整个词表做 softmax

$$J = \frac{1}{T}\sum_{t=1}^{T} \sum_{-m \le j \le m,\, j \neq 0} \log P(w_{t+j} \mid w_t)$$ $$P(c \mid w) = \frac{\exp(\mathbf{v}_w^\top \mathbf{v}'_c)}{\sum_{i=1}^{V} \exp(\mathbf{v}_w^\top \mathbf{v}'_i)}.$$

一个值得注意的细节是:$\mathbf{v}_w$ 来自输入矩阵 $W$ (目标词嵌入),而 $\mathbf{v}'_c$ 来自输出矩阵 $W'$ (上下文嵌入)。训练完成后,大多数流程只保留 $W$ ,但 GloVe 后续提出,结合两个矩阵的效果更佳。

为什么有效? 如果“猫”和“狗”都能预测出“坐”、“垫子”、“跑”,梯度下降会将 $\mathbf{v}_{\text{猫}}$$\mathbf{v}_{\text{狗}}$ 推向相似的方向,否则它们无法生成相似的输出分布。分布相似性被强制转化为几何相似性。

CBOW:用上下文预测目标词#

$$\mathbf{h} = \frac{1}{2m} \sum_{j=-m,\, j \neq 0}^{m} \mathbf{v}_{w_{t+j}}, \qquad P(w_t \mid \text{上下文}) = \mathrm{softmax}(W'\mathbf{h}).$$

两者的实际差异如下:

  • Skip-gram 每个中心词生成 $2m$ 个训练样本,因此罕见词会被“训练” $2m$ 次,在低频词上的表现更好,但速度较慢。
  • CBOW 通过平均平滑上下文,训练更快,对高频词的嵌入质量略高。

在 Web 级语料上训练通用词嵌入时,默认选择是“Skip-gram + 负采样”。

Softmax 瓶颈#

无论是 Skip-gram 还是 CBOW,都面临同一个问题: softmax 分母需要在整个词表上求和。当 $V = 100{,}000$ 、训练样本达到数十亿时,每一步梯度的计算代价是 $O(Vd)$ ,根本跑不动。 Word2Vec 的第一招就是——干脆不计算这个 softmax。

负采样:让训练飞起来#

$$J = \log \sigma(\mathbf{v}_w^\top \mathbf{v}'_c) + \sum_{i=1}^{k} \mathbb{E}_{n_i \sim P_n}\!\left[\log \sigma(-\mathbf{v}_w^\top \mathbf{v}'_{n_i})\right]$$

其中 $\sigma$ 是 sigmoid 函数。几何上看,梯度会把目标词和真实上下文拉近,同时把目标词和 $k$ 个无关词推开。

负采样:把 1 个正样本拉近,把 k 个随机负样本推开

两个关键工程细节:

  • 噪声分布。 Word2Vec 从 $P_n(w) \propto f(w)^{0.75}$ 采样,$f(w)$ 是词的语料频率。 0.75 指数的作用是“压平”分布:如果不加指数,“the” 被采样的概率大约是 “zebra” 的 100 倍,梯度会被高频词淹没。加上指数后,罕见词作为负样本的频率提升,信息量更高。
  • 速度提升。取 $k = 5$$15$ ,每步计算代价从 $O(Vd)$ 降到 $O((k+1)d)$ 。对于 $V = 10^5$$k = 10$ 的典型设置,加速约四个数量级——这正是 Word2Vec 能在单机上处理数十亿词的根本原因。

另一个常用技巧是频率下采样:训练时按概率丢弃一部分高频词。负采样和下采样这两个看似简单的工程优化,让 Word2Vec 的训练时间从“周”缩短到“小时”。

GloVe:全局矩阵分解#

Word2Vec 采用滑动窗口的方式处理语料,始终只能看到局部信息,而无法捕捉全局结构。相比之下,GloVe(Pennington 等, 2014)选择了完全不同的思路:先一次性构建完整的词-词共现矩阵,然后对其进行低秩分解。

为什么选择共现比值?#

假设我们有两个目标词 “ice” 和 “steam”,并观察它们与一些探测词的共现概率:

探测词$P(\text{word} \mid \text{ice})$$P(\text{word} \mid \text{steam})$比值
solid$\gg 1$
gas$\ll 1$
water$\approx 1$
fashion$\approx 1$

直接看概率时,它其实包含了两部分信息:探测词与目标词的相关性,以及探测词本身的出现频率。而通过计算比值,可以有效去除频率的影响,只保留相关性信息。 GloVe 的核心思想正是让词嵌入直接建模这种比值关系。

GloVe 的目标函数#

$$\mathbf{w}_i^\top \tilde{\mathbf{w}}_j + b_i + \tilde{b}_j \;\approx\; \log X_{ij}.$$ $$J = \sum_{i,j=1}^{V} f(X_{ij}) \left(\mathbf{w}_i^\top \tilde{\mathbf{w}}_j + b_i + \tilde{b}_j - \log X_{ij}\right)^2,$$ $$f(x) = \begin{cases} (x/x_{\max})^\alpha & \text{if } x < x_{\max} \\ 1 & \text{otherwise} \end{cases}$$

(论文中取 $x_{\max} = 100$$\alpha = 0.75$ 。)这个权重函数的作用有两点:一是限制高频词对(如 “the the”)对损失的主导作用;二是降低稀疏对的影响,因为这些对的计数往往带有较大的统计噪声。

从本质上讲,这就是一次低秩分解:将一个 $V \times V$ 的对数共现矩阵,用一个 $V \times d$ 的词矩阵和一个 $d \times V$ 的上下文矩阵的乘积来近似。

GloVe 把对数共现矩阵分解为词向量矩阵与上下文向量矩阵的乘积

官方发布的 GloVe 向量实际上是 $\mathbf{w}_i + \tilde{\mathbf{w}}_i$ ——即输入向量与上下文向量相加的结果。实验表明,这种方式的效果略优于单独使用其中之一。

GloVe 与 Word2Vec 的对比#

对比维度Word2VecGloVe
处理语料的方式局部窗口(在线)全局共现矩阵
优化方法负采样 + SGD加权最小二乘(实践中用 AdaGrad)
内存需求较低(无需存储全局统计)共现矩阵可能非常庞大
优势支持流式处理;在小语料上表现更稳健在类比任务上稍占优势;结果可复现

实际应用中,两者的词嵌入质量相差无几,选择时主要取决于你的语言或领域是否有现成的预训练向量集。

FastText:子词嵌入的力量#

Word2Vec 和 GloVe 为每个单词分配一个独立的向量,这种方法在实际应用中会遇到两个棘手问题:训练数据中从未见过的新词,以及像德语、土耳其语这样形态丰富的语言——一个词根可能衍生出几十种变形。FastText(Bojanowski 等, 2017)通过将每个单词表示为字符 n-gram 的集合,巧妙地解决了这两个难题。

它是如何工作的?#

以单词 “where” 为例, FastText 在其两端添加边界标记 <>,然后提取所有长度为 3 到 6 的字符 n-gram,同时还将完整单词作为一个特殊标记:

  • 3-gram:<whwhehererere>
  • 4-gram:<whewherhereere>
  • 5-gram 和 6-gram:类似
  • 完整单词:<where>
$$\mathbf{v}_w = \sum_{g \in G(w)} \mathbf{z}_g$$

训练过程与 Word2Vec 的 Skip-gram 模型结合负采样方法几乎完全一致,唯一的区别是输入的“词向量”被替换为上述 n-gram 嵌入的加和。

FastText:每个词的向量是其字符 n-gram 嵌入之和,天然支持未登录词

子词嵌入的优势在哪里?#

  • 未登录词的处理。推理时遇到像 “wherever” 这样的新词也不用担心,它与 “where” 和 “ever” 共享了多个 n-gram (如 whehereere),即使这个词从未出现在训练集中,求和后的向量依然能反映其语义。
  • 形态丰富的语言。在土耳其语、德语或芬兰语中,一个名词可能有几十种屈折变化。 FastText 通过共享 n-gram,在不同形式之间传递信息,罕见的形式也能从常见形式中“借用”语义。
  • 拼写错误的鲁棒性。即使是拼错的 “teh”,它仍然与 “the” 共享多个 n-gram,最终生成的向量位置大致正确,不会偏离太远。

当然,这种设计也有代价:模型体积更大(需要为每个 n-gram 存储嵌入,而不是每个单词),训练速度也会稍慢一些。对于词表固定且简单的英语,提升可能有限;但在形态复杂的语言中,效果则非常显著。

应用场景推荐使用 FastText推荐使用 Word2Vec / GloVe
形态复杂语言(如德语、土耳其语、芬兰语)
用户生成内容(含大量拼写错误和口语化表达)
推理阶段需要处理未登录词
词表固定且干净的英语都可是(模型更小)
内存资源极其有限

可视化结果#

词嵌入如果真的能够捕捉到分布式语义信息,那么将其降维到二维空间后,应该能够清晰地看到语义相关的词汇聚集在一起。尽管降维算法(如 t-SNE 或 UMAP)本身对词义一无所知,但当你用它们处理几百个训练好的词向量时,结果几乎总是符合预期:动物类词汇抱团出现,国家类词汇形成独立的区域,而与皇室相关的词汇则会聚在另一个角落。

t-SNE 投影:同一语义场的词自然聚成一团

这在实际工作中是一个非常实用的调试工具。如果你训练出来的词嵌入无法让明显相关的词汇形成聚类,那问题很可能出在数据质量、分词方式或者超参数设置上——完全不用等到运行下游任务评估就能发现问题所在。

语言模型与词嵌入#

语言模型的核心任务是为一段词语序列分配一个概率值。要训练出一个优秀的语言模型,关键在于让相似的上下文能够共享统计信息,而这种共享能力正是词嵌入所提供的。

N-gram 语言模型#

$$P(w_t \mid w_{t-n+1}, \ldots, w_{t-1}) = \frac{\text{count}(w_{t-n+1}, \ldots, w_t)}{\text{count}(w_{t-n+1}, \ldots, w_{t-1})}.$$

然而,这种方法面临一个严峻的问题:数据稀疏性。假设词汇表大小为 5 万, 4-gram 模型可能涉及 $50{,}000^4 \approx 6 \times 10^{18}$ 种不同的上下文组合,其中绝大多数在任何语料库中都从未出现过。尽管 Kneser-Ney、 Witten-Bell 和改进版回退等平滑技术能在一定程度上缓解这一问题,但它们无法在语义相似的上下文之间共享信息。例如,“the cat ate”和“the kitten ate”仍然是完全独立的两个统计单元。

神经语言模型#

$$\mathbf{h} = \tanh\left(W_h \cdot [\mathbf{v}_{w_{t-n+1}}; \ldots; \mathbf{v}_{w_{t-1}}] + \mathbf{b}_h\right), \quad P(w_t \mid \text{上下文}) = \mathrm{softmax}(W_o \mathbf{h} + \mathbf{b}_o).$$

由于 $\mathbf{v}_{\text{cat}} \approx \mathbf{v}_{\text{kitten}}$ ,像“the cat ate”和“the kitten ate”这样的句子会生成几乎相同的隐藏状态,从而使模型能够泛化到从未见过的组合。

实际效果令人震撼。随着语料规模的增长, n-gram 模型的困惑度很快趋于平稳——因为计数已经无法支撑进一步提升;而神经语言模型则持续优化,尤其是基于 Transformer 的语言模型表现更为突出:

困惑度 vs. 语料规模:n-gram 趋于平稳,神经/Transformer 语言模型持续改进

核心结论来了:词嵌入是语言模型扩展的关键。没有词嵌入,每个新上下文都是一个需要单独估计的统计单元;有了词嵌入,每个新上下文都能从模型已经学到的几何结构中获益。

Word2Vec 是简化版语言模型#

回顾起来, Word2Vec 实际上是一个大幅简化的神经语言模型:

  • Skip-gram 对应的语言模型是:用目标词独立预测周围的每一个词。
  • CBOW 对应的语言模型是:用一组上下文词预测中心词。

这些简化设计——浅层网络、去掉非线性激活、忽略词序、用负采样替代完整的 softmax——全都为了一个目标:快速训练,充分覆盖,从而让词嵌入的几何结构在海量数据中稳定下来。完整的语言模型则在其基础上更进一步。

静态嵌入 vs. 上下文嵌入(预告)#

Word2Vec、 GloVe 和 FastText 生成的都是静态嵌入——无论“bank”出现在“river bank”还是“bank account”中,它的向量始终保持不变。这是它们的主要局限。而下一波模型——ELMo、 BERT、 GPT——生成的是上下文嵌入,每次出现都会根据上下文生成一个独特的向量。这部分内容我们将在第 5 和第 6 部分 深入探讨。

用 Gensim 实战训练#

安装依赖#

1
pip install gensim numpy matplotlib scikit-learn

训练 Word2Vec 模型#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from gensim.models import Word2Vec
from gensim.utils import simple_preprocess

sentences = [
    "the cat sat on the mat",
    "the dog sat on the log",
    "cats and dogs are animals",
    "the quick brown fox jumps over the lazy dog",
    "a cat and a dog are playing in the garden",
]
tokenized = [simple_preprocess(s) for s in sentences]

# 使用 Skip-gram 和负采样方法
model = Word2Vec(
    sentences=tokenized,
    vector_size=100,   # 嵌入向量的维度 d
    window=5,          # 上下文窗口大小 m
    min_count=1,       # 忽略出现次数低于此值的词
    sg=1,              # 1 表示使用 Skip-gram,0 表示 CBOW
    negative=5,        # 每个正样本对应 k 个负样本
    ns_exponent=0.75,  # 负采样的经典指数 0.75
    epochs=100,
    workers=4,
    seed=42,
)

print(f"向量形状: {model.wv['cat'].shape}")            # (100,)
print(model.wv.most_similar('cat', topn=3))
print(f"猫和狗的相似度: {model.wv.similarity('cat', 'dog'):.4f}")

这个语料库太小了,无法生成有意义的词向量。这段代码主要是为了展示 API 的用法,而不是一个实际的训练方案。在真实场景中,建议使用至少几千万 token 的大规模语料,或者直接加载预训练的词向量。

训练 FastText 模型#

Gensim 的 FastText API 设计得与 Word2Vec 几乎一致:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from gensim.models import FastText

model_ft = FastText(
    sentences=tokenized,
    vector_size=100,
    window=5,
    min_count=1,
    sg=1,
    min_n=3,           # 字符级 n-gram 的最小长度
    max_n=6,           # 字符级 n-gram 的最大长度
    epochs=100,
    seed=42,
)

# 即使 'kitty' 没有出现在语料中,FastText 也能为其生成向量
print(model_ft.wv['kitty'].shape)

加载预训练的词向量#

在生产环境中,使用基于数十亿 token 训练的预训练词向量,通常比自己在小规模领域语料上训练的效果更好:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import gensim.downloader as api

# Google News Word2Vec(300 万词,300 维)
w2v = api.load('word2vec-google-news-300')

# Wikipedia + Gigaword 上训练的 GloVe(40 万词,300 维)
glove = api.load('glove-wiki-gigaword-300')

# 带子词信息的 FastText(100 万词,300 维)
fasttext = api.load('fasttext-wiki-news-subwords-300')

print(w2v.most_similar('computer', topn=5))

测试类比推理#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def analogy(model, a, b, c):
    """求解类比关系:a : b :: c : ?"""
    try:
        result = model.most_similar(positive=[b, c], negative=[a], topn=1)
        return result[0][0]
    except KeyError:
        return "词汇表外(OOV)"

print(analogy(w2v, 'man', 'woman', 'king'))     # -> queen
print(analogy(w2v, 'France', 'Paris', 'Italy')) # -> Rome

使用 t-SNE 可视化词向量#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import numpy as np
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE

words = list(w2v.index_to_key[:200])
vectors = np.array([w2v[w] for w in words])

projection = TSNE(n_components=2, random_state=42, perplexity=20).fit_transform(vectors)

plt.figure(figsize=(12, 8))
plt.scatter(projection[:, 0], projection[:, 1], s=12, alpha=0.6)
for i, word in enumerate(words):
    plt.annotate(word, projection[i], fontsize=8)
plt.title('词向量可视化(t-SNE)')
plt.tight_layout()
plt.savefig('embeddings_tsne.png', dpi=150)

评估词嵌入#

当你手头有了一组向量,可以通过三种评估方法来判断它们是否靠谱。

内在评估 1:类比任务#

Google 类比数据集(Google Analogy Dataset)包含 19,544 道题目,形式类似于“Athens 之于 Greece,就像 Beijing 之于?”。如果你用一个十亿词规模的语料训练了一个 300 维的 Word2Vec 或 GloVe 模型,通常可以达到 60% 到 80% 的 top-1 准确率。这算是一个不错的健全性检查,但需要注意的是,类比任务的准确率对数据集的选择比较敏感,可能会因数据集不同而波动。

内在评估 2:词相似度#

像 WordSim-353 和 SimLex-999 这样的数据集收集了人类对词对相似度的评分。你可以用你的嵌入模型计算每对词的余弦相似度,然后与人类评分进行对比,得出 Spearman 秩相关系数 $\rho$ 。一般来说,高质量的嵌入模型在这个指标上能达到 $\rho \approx 0.6$$0.8$ ,具体分数取决于所用的数据集。

外在评估:下游任务#

最终唯一重要的评估标准是:这些嵌入能不能在实际任务中发挥作用,比如情感分类、命名实体识别(NER)或者信息检索?当标注数据有限时,预训练的嵌入通常能带来 2 到 10 个百分点的性能提升——而且任务数据越少,嵌入带来的收益往往越大。

快速检查#

在做任何正式评估之前,先手动检查几个词的最近邻结果。比如,“cat” 的邻居应该包括 “dog”、“kitten”、“feline”,最多再加个 “pet”。如果看到的是 “the”、“and”、“is” 这样的高频词,那很可能是忘了对高频词进行下采样处理。这种问题在跑基准测试之前就能发现,早发现早解决。

如何选择嵌入维度#

维度 $d$适用场景
50 – 100小规模数据集(少于 100 万 token),下游任务模型较简单
100 – 300中等规模数据集,通用型嵌入;实际应用中最常用的范围
300 – 1000大规模数据集(超过 10 亿 token),对嵌入质量要求较高的场景

随着维度的增加,收益会迅速递减:从 50 提升到 100 通常能带来显著效果,但从 300 提高到 600 对大多数任务来说几乎没什么提升。建议初始选择 100 或 300,只有在下游任务评估表明需要更高维度时,再考虑调整。

多语言现实#

Word2Vec 和 GloVe 模型最初是基于英文 Wikipedia 训练的,但当你切换到其他语言时,这些模型的核心假设会在三个方面遇到挑战。

分词方式决定了词汇表的构成。英文天然以空格作为分词依据,而中文却没有这样的便利。如果你直接拿未经分词的中文文本去跑 Word2Vec,最终得到的会是每个汉字一个向量,而不是每个词语一个向量。比如,“苹果”和“果汁”都包含“果”这个字,但它们的意思完全不同——字符级别的向量会把这些语义信号混为一谈。因此,在训练之前,记得先用 Jieba 或 PKUSeg 这样的工具对中文进行分词处理。

词频分布的特性差异显著。中文常用汉字大约有 5000 个,但多字词的数量却能达到几万甚至更多。 Word2Vec 中用于负采样的公式 $f(w)^{0.75}$ 是根据英文的 Zipf 分布曲线调优的。然而在中文、日文或韩文(CJK)语料中,我发现将指数降到 0.5 能稍微提升稀有词的向量质量,因为这些语言的长尾分布更重,低频词的占比更高。

FastText 提供了一种部分解决方案。 FastText 将词语拆解为字符 n-gram,在土耳其语、芬兰语等形态丰富的语言中表现出色。对于中文来说,这种拆解的效果相对有限——毕竟汉字本身已经是语素了——但它在处理未登录词(OOV)的专有名词时依然有用。例如,“蔡徐坤”可能从未完整出现在你的 Wikipedia 训练语料中,但它的每个字都出现过, FastText 仍然能够生成一个合理的向量。

总结一下:直接使用基于英文预训练的 Word2Vec 模型并不合适。建议针对目标语言重新训练模型,或者使用 FastText 提供的多语言子词嵌入模型,比如 cc.zh.300.bin

静态嵌入的失败场景#

以下是一些 Word2Vec/GloVe 明显失效的例子,以及对问题的深入剖析。

多义词混淆。比如,“苹果发布了新机型”和“我吃了一个苹果”,这两句话中的“苹果”会被静态嵌入压缩成同一个向量,而且通常更偏向高频含义(比如水果)。在 WiC (Words in Context)基准测试中, GloVe 的准确率只有 58%,而 BERT 则能达到 70% 以上。这 12 个百分点的差距,正是静态嵌入无法解决多义词问题的代价。

反义词成了“邻居”。“热”和“冷”经常出现在极其相似的上下文中,比如“屋里很 X”“今天感觉 X”。结果它们的向量在空间中距离非常近。在预训练的 GloVe 模型中,vec("hot")vec("cold") 的余弦相似度大约是 0.55——在某些模型版本里,这个值甚至比 vec("hot")vec("warm") 的相似度还高。分布式语义模型把“能填同一个空”误认为“意思相同”,导致这种尴尬局面。

否定信息被忽略。“我不喜欢这个”和“我喜欢这个”,用 bag-of-vectors 表示时几乎没什么区别。如果靠平均词向量来做句子分类,遇到这种输入肯定挂掉。要解决这个问题,要么引入序列模型,要么换成上下文编码器。

领域漂移问题。比如,在新闻语料上训练的 Word2Vec,“transformer”会被理解成“变压器”。但在机器学习论文里,它是一种架构。问题是向量已经固定了,不重新训练就没法修正。

这些问题就是为什么大家一有算力,就毫不犹豫转向上下文嵌入的原因。

总结#

  • 嵌入表达的是分布语义。如果两个词经常出现在相似的上下文中,它们在嵌入空间中的位置也会接近。这种几何邻域关系是下游模型泛化能力的基础。
  • Word2Vec、 GloVe 和 FastText 是三种殊途同归的方法。 Word2Vec 通过滑动窗口捕捉局部上下文; GloVe 通过对全局共现矩阵进行分解建模; FastText 则将词拆解为字符级别的 n-gram。尽管实现路径不同,但它们生成的嵌入质量旗鼓相当。
  • 负采样让训练变得高效。它用对 $k$ 个随机负样本的二分类任务替代了完整的 softmax 计算,同时通过 $f(w)^{0.75}$ 的噪声分布平滑词频,从而大幅降低计算复杂度。
  • 嵌入让语言模型突破扩展瓶颈。没有嵌入时, n-gram 统计方法在语料规模增大后很快遇到天花板;而有了嵌入,神经语言模型可以通过学习到的几何结构不断优化,因为每个新上下文都能从已有的嵌入中“借力”。
  • 静态嵌入有其固有的局限性。比如,“bank”这个词无论出现在“河岸”还是“账户”旁边,都只有一个固定的向量。接下来的两波技术浪潮——ELMo、 BERT 和 GPT (第 5、 6 部分)——通过引入上下文相关的动态嵌入解决了这一问题。

词嵌入为神经网络自然语言处理打开了一扇大门。当词可以像数学向量一样进行加减运算和聚类分析时,从 RNN 到 Transformer 的各种架构才得以蓬勃发展。在下一篇文章中,我们将沿着这条线索继续探讨序列建模的相关内容。

本系列

NLP 技术前沿 12 篇

  1. 01 自然语言处理(一):NLP 入门与文本预处理
  2. 02 自然语言处理(二):词向量与语言模型 当前
  3. 03 自然语言处理(三):RNN 与序列建模
  4. 04 自然语言处理(四):注意力机制与 Transformer
  5. 05 自然语言处理(五):BERT 与预训练模型
  6. 06 自然语言处理(六):GPT 与生成式语言模型
  7. 07 自然语言处理(七):提示工程与 In-Context Learning
  8. 08 自然语言处理(八):模型微调与 PEFT
  9. 09 自然语言处理(九):大语言模型架构深度解析
  10. 10 自然语言处理(十):RAG 与知识增强系统
  11. 11 自然语言处理(十一):多模态大模型
  12. 12 自然语言处理(十二):前沿技术与实战应用

读有所得?

GitHub 关注我 → 新文周更

GitHub