4. 文本向量化进阶:Word2Vec原理(CBOW与Skip-gram)、GloVe与FastText简介、使用Gensim训练词向量、词向量可视化

好,咱们继续深入。上一章我们聊了基础的词袋模型和TF-IDF,说白了,它们都是基于统计的“硬编码”表示。每个词都是一个孤岛,彼此之间没有语义关联。

但现实世界不是这样的。“苹果”和“香蕉”都是水果,“国王”和“王后”有对应关系。我们需要的,是能捕捉这种语义和语法关系的向量表示。

这就是分布式表示(Distributed Representation)的用武之地。它的核心思想很简单:一个词的语义,由它周围的词决定。你想想看,看到“我在吃__”,你大概率会填“苹果”而不是“汽车”。

我个人习惯把Word2Vec看作是这一领域的里程碑。它让词向量训练变得又快又高效。今天我们就把它彻底讲透。

4.1 Word2Vec:两种核心架构

Word2Vec是Google在2013年提出的。它有两种训练模式:CBOW和Skip-gram。理解它们,是理解所有后续模型的基础。

4.1.1 CBOW(Continuous Bag of Words)

核心思想:用上下文词,预测当前词。

举个例子,句子是“我 喜欢 吃 新鲜 的 苹果”。假设窗口大小为2,那么对于目标词“吃”,它的上下文就是[“喜欢”, “新鲜”](忽略“的”这种停用词,或者保留,看预处理策略)。

CBOW的任务就是:给定[“喜欢”, “新鲜”],预测出“吃”。

我在项目中遇到过一个问题:CBOW对高频词很友好,训练速度快。因为它一次利用多个上下文词的信息,相当于“集思广益”。但缺点也很明显,它对低频词的刻画不够精细。

为什么? 因为低频词出现的次数少,上下文信息不充分,CBOW这种“平均”策略会把它的独特性给抹平了。

4.1.2 Skip-gram

核心思想:用当前词,预测上下文词。

还是那个句子。给定目标词“吃”,Skip-gram的任务是预测它周围的词:比如“喜欢”和“新鲜”。

说白了,CBOW是“众星捧月”,Skip-gram是“一夫当关”。

我个人的经验是:如果你的语料库足够大,或者你很关心那些低频词、罕见词,Skip-gram通常是更好的选择。它虽然训练慢一点,但每个词都要独立地去“解释”它的上下文,这让低频词也能学到更丰富的表示。

我的避坑指南: 我曾经在一个法律文书项目里,直接用CBOW训练,结果发现很多专业术语(比如“不可抗力”、“连带责任”)的向量质量很差。后来换成Skip-gram,效果明显提升。嗯,这里要注意:领域专有名词多,优先考虑Skip-gram。

4.2 GloVe与FastText:站在巨人肩膀上

Word2Vec虽然好,但它只利用了局部上下文信息(窗口内的词)。有没有办法利用全局的统计信息呢?

有,GloVe就是干这个的。

4.2.1 GloVe(Global Vectors for Word Representation)

GloVe的核心是共现矩阵。它统计整个语料库中,任意两个词在同一个窗口内共同出现的次数。

举个例子,“苹果”和“水果”共现的次数,肯定比“苹果”和“汽车”多得多。GloVe通过矩阵分解,把这些共现信息压缩到低维向量里。

我个人觉得,GloVe在词类比任务上(比如“国王 - 男人 + 女人 = 王后”)表现非常稳定。因为它有全局统计信息兜底,不会像Word2Vec那样偶尔出现“抽风”的情况。

4.2.2 FastText:解决OOV问题

Word2Vec和GloVe都有一个共同的硬伤:无法处理未登录词(OOV)。如果模型没见过“ChatGPT”这个词,它就不知道它的向量是什么。

FastText的解决方案很巧妙:把词拆成字符级别的n-gram

比如“apple”,拆成3-gram就是:”app”, “ppl”, “ple”。然后,一个词的向量,就等于它所有n-gram向量的和。

这样一来,即使模型没见过“apples”,但它见过“app”和“ple”这些子串,也能拼凑出它的向量。

注意: FastText虽然解决了OOV问题,但模型体积会变大很多。因为每个n-gram都是一个向量。我在移动端部署时,就吃过这个亏。如果资源受限,需要权衡一下。

4.3 使用Gensim训练词向量

理论说完了,咱们来点实际的。Gensim是Python里最流行的主题模型和词向量库。用它训练Word2Vec,代码非常简洁。

先准备数据。假设我们有一个已经分好词的列表:

sentences = [
    [“我”, “喜欢”, “吃”, “苹果”],
    [“他”, “喜欢”, “吃”, “香蕉”],
    [“她”, “喜欢”, “吃”, “草莓”],
    [“苹果”, “是”, “一种”, “水果”],
    [“香蕉”, “是”, “一种”, “水果”]
]

然后,三行代码搞定训练:

from gensim.models import Word2Vec

model = Word2Vec(
    sentences=sentences,
    vector_size=100,   # 向量维度,我一般设100-300
    window=5,          # 上下文窗口大小
    min_count=1,       # 忽略出现次数少于这个值的词
    sg=0,              # 0表示CBOW,1表示Skip-gram
    workers=4          # 并行训练的线程数
)

训练完成后,我们可以直接查看某个词的向量:

vector = model.wv[“苹果”]
print(vector.shape)  # 输出: (100,)
print(vector)        # 输出一个100维的浮点数数组

还可以找最相似的词:

similar_words = model.wv.most_similar(“苹果”, topn=5)
print(similar_words)
# 输出类似: [(“香蕉”, 0.85), (“草莓”, 0.78), (“水果”, 0.72), ...]

嗯,这里要注意:min_count这个参数很关键。设得太小,低频噪音会污染向量;设得太大,稀有词就学不到了。我一般先设成5,然后根据结果调整。

4.4 词向量可视化:让机器“说话”

训练好的词向量是100维或300维的,我们人类没法直接看。所以需要降维到2D或3D,画出来看看。

最常用的降维方法是t-SNE。它擅长把高维空间中的“邻居关系”保留到低维空间。

代码示例:

from sklearn.manifold import TSNE
import matplotlib.pyplot as plt

# 假设我们想可视化前50个词
words = list(model.wv.index_to_key)[:50]
vectors = [model.wv[word] for word in words]

# t-SNE降维到2维
tsne = TSNE(n_components=2, random_state=42, perplexity=30)
vectors_2d = tsne.fit_transform(vectors)

# 画图
plt.figure(figsize=(12, 8))
for i, word in enumerate(words):
    x, y = vectors_2d[i]
    plt.scatter(x, y)
    plt.annotate(word, (x, y), fontsize=12)

plt.title(“词向量可视化 (t-SNE)”)
plt.show()

画出来的图里,语义相近的词会聚在一起。比如“苹果”、“香蕉”、“草莓”会形成一个水果簇,“喜欢”、“吃”会形成另一个动作簇。

我的经验: 可视化是检验词向量质量最直观的方法。如果“苹果”和“电脑”挨在一起,而“苹果”和“香蕉”离得很远,那说明你的语料或者训练参数有问题。我曾经在一个金融语料里,发现“牛市”和“熊市”居然离得很远,后来一查,是分词把“牛”和“市”分开了。这种坑,可视化一眼就能看出来。

好了,这一章的内容就到这里。从Word2Vec的原理,到GloVe和FastText的改进,再到Gensim的实战和可视化,我们一步步把文本向量化这件事给捋清楚了。下一章,我们会把这些向量用起来,做真正的文本摘要。