☰
静态语言模型词向量全解析:从独热编码到Gaussian与Poincaré
2026/10/15 19:19:19 网站建设 项目流程

1. 为什么我们需要“喂”给模型词语的含义

在做自然语言处理的这两年,我踩过最多的坑,其实不是模型选型,也不是调参,而是最基础的一个问题:怎么把文字变成模型能懂的输入。你说“苹果”,模型不知道这是一种水果还是一个手机品牌;你说“银行”,模型不知道你是要去存钱还是站在河边。语言模型再强,第一步也绕不开“词表示”,也就是 Embedding。这个系列的第二篇,我打算把静态语言模型这条线彻底讲透,从最原始的独热编码,一路走到带几何直觉的 Gaussian、Poincaré,把这几个方法摆在一起对比着看,你会发现它们之间根本不是什么替代关系,而是针对不同痛点逐步迭代出来的方案。

静态语言模型这个词,可能有的朋友听着陌生。它的核心意思是:每个词对应一个固定的向量,不管这个词出现在什么上下文里,向量不变。换句话说,这是一种“一次训练、终身使用”的词表示方案,跟后来基于上下文的动态模型(比如基于 Transformer 的那一类)是相对的概念。这篇文章适合刚入门 NLP 的初学者,也适合已经会用 BERT 但想补一补底层演进史的同学。它会解决一个很实际的问题:当你要做一个文本分类、文本相似度、语义检索或者知识图谱项目时,到底该选哪种词向量,背后的原理是什么,坑在哪里。

我先把整条技术线铺开,然后再逐项掰开揉碎。

2. 静态语言模型的整体设计思路:从符号到几何的演进

如果让我用一句话总结这一章要讲的内容,那就是:词表示的发展史,本质上是一部“如何让语义距离变得可计算”的历史。

2.1 这条技术线的核心矛盾

最早期的方法,比如独热编码,想法很直接:每个词用一维表示,词表多大,向量维度就多大。这种表示方式有致命伤——词与词之间完全孤立。“猫”和“狗”的距离,和“猫”与“汽车”的距离,一模一样。因为任何一个独热向量彼此之间的夹角都是 90 度,内积全是 0。这在数学上意味着:模型无法从表示中获取任何词与词之间的关联信息。

后来 TF-IDF 出现,它是基于统计的,每个词的向量由它在文档中的权重构成。这个方案比独热编码强的点是:有了“词在语料中重要性”的区分。但它依然没有解决“语义”问题,因为 TF-IDF 是基于字面共现的,它捕捉不到“电脑”和“计算机”这种同义词关系,这两个词如果不在同一篇文章里出现,它们的向量也毫无关联。

真正的转折点是 word2vec。它把“词的语义”变成了一个可以由神经网络预测任务间接学出来的副产品。核心思想特别朴素:一个词的含义,由它周围的词决定。这句话其实最早是语言学家 Firth 在 1957 年说的,但直到 2013 年,才被 Mikolov 用一套高效的神经网络结构落地成工业级方案。从此,“语义距离”第一次变成了“向量空间中的几何距离”,两个词的相似度可以直接用余弦相似度计算,甚至可以做“国王 - 男性 + 女性 = 女王”这样的向量运算。

2.2 为什么还需要 GloVe、FastText 这些变体

既然 word2vec 这么好,为什么后面还有那么多方法?原因很简单,word2vec 有一个天生的盲区:它只考虑了词的局部窗口共现,没有充分利用全局统计信息。GloVe 想弥补这个缺点,所以它把共现矩阵和词向量的关系用数学方式显式建模。FastText 则从另一个角度切入,它把词拆成字符级别的 n-gram,以此解决生僻词和未登录词(OOV)的问题。

而 Gaussian Embedding 和 Poincaré Embedding 则是两个更有意思的方向。前者不再把词表示成一个点,而是表示成一个高斯分布,用均值和方差一起表示语义和不确定性,这样天然能处理一词多义和词本身的歧义程度。后者干脆把向量空间从欧几里得空间换成了双曲空间,专门用来表达树状、层次化的数据结构,比如 WordNet 这种词义层级,适合有明确上位词/下位词关系的场景。

这一整条线看下来,你会发现每个方法都不是凭空冒出来的,都是为了解决前一个方法某个具体缺陷的补救。理解了这条演进逻辑,后面选型的时候就不容易抓瞎。

3. 先从最基础的谈起:独热编码与 TF-IDF 的实操要点

先别急着跳过这两个“老古董”。虽然现在工业界已经不太直接用它们做词向量了,但它们的思想渗透到几乎所有后续方法中,而且在一些小型项目里依然有实用价值。

3.1 独热编码:最容易理解也最容易“翻车”的方案

独热编码的思路是:假设有一个包含 N 个词的词表,每个词用一个 N 维向量表示,该词对应位置取 1,其他位置取 0。比如词表是 [猫, 狗, 鱼],那么“猫”的向量是 [1, 0, 0],“狗”的向量是 [0, 1, 0]。

这个方案实现起来三行代码搞定:

from sklearn.preprocessing import OneHotEncoder import numpy as np vocab = ["猫", "狗", "鱼"] encoder = OneHotEncoder(sparse_output=False) # 需要做一次 reshape one_hot = encoder.fit_transform(np.array(vocab).reshape(-1, 1)) print(one_hot)

输出就是 3 行 3 列的稀疏矩阵。我估计看到这,有的朋友已经在想这玩意儿的缺点了。没错,它最明显的问题就是维度爆炸。真实语料中词表动辄几十万,独热编码的向量维度就是几十万维。存储开销极大,而且绝大部分位置是 0,训练时计算效率极低。

更关键的是它的语义盲区。我用一个实际场景来展示这个问题:假设我做一个小型的新闻分类器,训练语料里有“电脑”和“计算机”两个词,但“电脑”出现在科技类文章里,“计算机”出现在教育类文章里。如果用独热编码,模型无法知道这两个词是近义词,对于同时包含“电脑”和“计算机”的测试样本,模型只能从统计频次上做判断,而无法进行任何语义层面的泛化。

所以独热编码的适用场景其实非常窄。它只适合词表很小、词与词之间不需要语义关联、并且特征维度可控的情况,比如一些简单的规则匹配系统、或者是作为类别特征输入给树模型。如果是为了做语义任务,建议直接跳过这个方案。

3.2 TF-IDF:让词的重要性变得有区分度

TF-IDF 的英文全称是 Term Frequency-Inverse Document Frequency,中文叫词频-逆文档频率。它的核心观点是:如果一个词在一篇文档中出现得很多,同时在所有文档中出现得很少,那这个词对这篇文档就有很强的代表性。

计算公式分两部分。词频部分 TF:某个词在文档中出现的次数除以该文档的总词数。逆文档频率部分 IDF:log(总文档数 / 包含该词的文档数 + 1)。最终的 TF-IDF 值就是两者的乘积。

举个我实际做过的例子。我在处理一批客服工单分类时,发现“退”“款”这类词在退款类工单中出现频繁,同时在整个工单集里也大量出现,所以仅靠词频无法区分。但像“发票”这个词,只在少量工单里出现,在这些工单里又反复出现,那么“发票”这个特征对工单类别的区分能力就远高于“退”“款”。TF-IDF 通过 IDF 的调节,让“发票”这类信息量大的词获得更高的权重,从而把真正有区分力的特征突显出来。

不过 TF-IDF 的向量依然是高维稀疏的,而且它完全没有上下文信息。“苹果好吃”和“苹果手机好用”两句里的“苹果”,在 TF-IDF 看来是完全相同的词。这个问题在后续的模型里才会被解决。

如果要在 scikit-learn 里快速实现 TF-IDF,是可以直接用现成的 TfidfVectorizer 的:

from sklearn.feature_extraction.text import TfidfVectorizer corpus = [ "这台电脑性能很好", "这台计算机运行速度快", "苹果很好吃", "苹果手机拍照很好" ] vectorizer = TfidfVectorizer(token_pattern=r"(?u)\b\w+\b") X = vectorizer.fit_transform(corpus) print(vectorizer.get_feature_names_out()) print(X.toarray())

从输出可以看到,“电脑”和“计算机”被当作完全独立的特征,它们之间没有任何相似度评分。这在语义任务中是一个硬伤。所以在做短文本相似度或者语义检索类任务时,TF-IDF 只适合作为基线方案,不建议作为最终方案。

4. 真正的转折点:word2vec 原理与训练细节

word2vec 是这篇博文的核心章节。当初我刚开始用 word2vec 的时候,被各种名词绕晕过:CBOW、Skip-gram、负采样、层次 Softmax……后来花了不少时间才把这些概念串起来。现在我用一个能直接落地的思路帮你把它理清。

4.1 CBOW 和 Skip-gram 的直觉

word2vec 自身的结构并不复杂,是一个极简的神经网络。它有两种训练模式。

CBOW(Continuous Bag-of-Words)的做法是:用上下文词预测中心词。比如句子“我 喜欢 吃 苹果”,如果窗口大小为 2,那么给定“我”“喜欢”“吃”“苹果”中的“喜欢”“吃”这两个上下文词(具体取决于窗口设置),去预测中间词。Skip-gram 刚好反过来:用中心词预测上下文词。同样一句话,给定“吃”,去预测“喜欢”和“苹果”。

两种模式各有适用场景。CBOW 的训练速度快,对频繁出现的词效果更好;Skip-gram 的训练速度相对慢一点,但对低频词和语义细节的刻画更好。我在实际做项目时,如果语料规模不大,一般优先选择 Skip-gram,因为它对小语料的适应性更强,训练出来的词向量质量通常比 CBOW 更细腻。

从数学角度看,word2vec 本质上是在做一个分类任务:给定当前词和目标词,预测它们是否真的在同一个上下文窗口中共现。训练完成后,我们扔掉输出层,只保留输入层到隐藏层的权重矩阵,这个矩阵的每一行就是对应词的词向量。

4.2 负采样到底在做一件什么事

这里有个细节特别值得展开说一下。最初的 word2vec 如果直接用完整的 Softmax 做分类,每个训练样本都要对所有词表中的词计算概率,词表是几十万的话,每一步的计算量是不可接受的。负采样(Negative Sampling)的思路是:对于每一个正样本(真实的上下文词对),我们随机从词表中抽一小部分词作为负样本,让模型学会区分“这个上下文词是真的”和“这个上下文词是假的”。

负采样数量是一个关键参数。我一般设置为 5 到 10。设置太少,模型学不到足够的反例信息;设置太多,容易让模型过于关注区分负样本,反而忽略了正样本的特征。gensim 里 Word2Vec 的默认参数是 5,这是一个在通用语料上表现比较平衡的值。如果语料偏小,可以适当调到 10,负样本多一点能提供更多训练信号。

4.3 使用 gensim 训练词向量的实操配置

如果你用的是 Python 生态,gensim 应该是最方便的工具。下面给一个完整的训练示例,注意每个参数的用途我都标注了:

from gensim.models import Word2Vec from gensim.models.word2vec import LineSentence # 准备语料,每行一条句子,也可以自己写一个数据迭代器逐条产出分词后的词列表 sentences = [ ["我", "喜欢", "吃", "苹果"], ["这台", "电脑", "性能", "很", "好"], ["这台", "计算机", "运行", "速度", "快"], # 更多语料... ] model = Word2Vec( sentences=sentences, vector_size=100, # 词向量维度,常用 100-300 window=5, # 上下文窗口大小 min_count=2, # 词频低于 2 的词会被丢弃,减少噪音 sg=1, # 1 表示 Skip-gram,0 表示 CBOW negative=5, # 负采样数量 workers=4, # 并行训练线程数 epochs=5, # 训练轮数 seed=42 # 固定随机种子,保证可复现性 ) # 保存模型 model.save("word2vec.model") # 查看相似词 result = model.wv.most_similar("电脑", topn=10) print(result) # 词向量运算 print(model.wv.most_similar(positive=["电脑", "运行"], negative=["性能"], topn=5))

我在实操中有一个体会:min_count 和窗口大小对词向量质量的影响,往往比维度更明显。如果语料比较干净、体量大,min_count 可以设为 2 甚至 1;如果语料噪音较多,min_count 可以提高到 5 以上,过滤掉那些只在个别文本里出现的低频垃圾词。窗口大小方面,如果做的是短文本任务,窗口可以小一些,比如 3 到 5,因为短文本里邻近词的关系更紧密;如果做长文档类任务,窗口可以适当扩大到 10 左右,捕捉更远的语义关系。

还有一点容易被忽略:训练语料的预处理。中文语料建议先分词,而且分词质量直接影响词向量效果。如果你用的是 jieba,建议自己维护一个自定义词典,把领域专有名词加进去,否则类似“支持向量机”被切成“支持”“向量”“机”,三个词向量拼不出一个完整的语义。我最早做舆情分析项目时,就因为没加自定义词典,模型里“新冠”和“疫情”的词向量距离很远,后来才发现是分词阶段把“新冠肺炎”切碎了。

4.4 word2vec 的局限与 Hidden Bias

word2vec 的效果在当时是划时代的,但它有一个在后续长期被讨论的问题:静态向量无法处理多义词。比如“苹果”,在财经新闻里指公司,在水果话题里指水果,但 word2vec 只能给出一个固定的向量,等于把两种含义硬揉在一起。这个问题在文章后面说 Gaussian Embedding 时会再次提到。

另外,word2vec 模型会继承训练语料中的偏见。比如语料中“医生”和“男性”共现较多,“护士”和“女性”共现较多,那学出来的词向量就带有这种刻板关联。这不是模型本身的问题,是数据的问题,但我们在应用层必须意识到这一点,尤其是在做招聘、信贷等涉及决策的业务场景时,不建议直接带着原始词向量跑模型,而是要做一些去偏(Debias)处理。

5. FastText:用子词信息解决未登录词问题

FastText 这个模型看起来跟 word2vec 很像,网上的教程也经常把它们放在一起讲,但它们的出发点完全不同。word2vec 是纯词级别的,它把每一个词当成一个独立的符号。FastText 在此基础上增加了一个关键设计:把一个词拆成字符级别的 n-gram,并把这些 n-gram 的向量相加作为该词的最终向量。

5.1 为什么子词 n-gram 能派上用场

举个例子,词“unhappiness”。如果词表里没有这个完整词,word2vec 就只能给你一个随机初始化向量或者报错,因为模型从未见过它。FastText 则会把“unhappiness”拆成多个字符级别的 3-gram / 4-gram / 5-gram,比如“unh”“nha”“hap”“app”“ppi”“ine”“ess”等等,然后每个 n-gram 都有自己的向量表示,把这些向量加起来,就得到了“unhappiness”的向量。

这个设计的直接好处是两个:一是未登录词不再是无解的问题,因为只要一个词能和已有词共享字符片段,它就能得到一个有意义的向量;二是形态相近的词,向量也相近。比如“run”、“running”、“runner”,它们共享了很多字符 n-gram,即使语料里“runner”出现次数很少,它依然能跟“run”有较高的相似度。这对于英语等形态丰富的语言来说非常实用。

中文场景可以这么理解:虽然中文不像英文那样有丰富的词形变化,但子词信息对于处理英文专有名词音译、网络新词等场景有额外优势。比如“抖音”和“TikTok”这类词,在中文语料里无法通过字符 n-gram 建立关联,但如果你的语料中同时包含中英文,字符 n-gram 可以在一定程度上把相同序列的字母片段关联起来。

5.2 用 FastText 训练词向量的代码示例

FastText 官方提供了 C++ 版本,同时也提供了 Python 接口。我平时用 gensim 比较多,因为可以直接复用 word2vec 那套 API:

from gensim.models.fasttext import FastText model = FastText( sentences=sentences, vector_size=100, window=5, min_count=1, # FastText 对低频词更友好,min_count 可以设小 sg=1, negative=5, min_n=3, # 最小字符 n-gram,写 2 到 3 均可 max_n=6, # 最大字符 n-gram,通常取 5 或 6 epochs=5, seed=42 )

min_n 和 max_n 这两个参数值得细说。min_n 太小,比如 1,那么单个字符也会作为特征,这对于大多数语言来说噪音太大,因为单字符太常见,不具备区分度;max_n 太大,比如 8,会引入大量长 n-gram,训练速度下降,而且这些长片段出现的频次往往很低,对模型贡献有限。我常用的组合是 min_n=3, max_n=6,对于大多数语言都够用。有一个特殊情况是中文,如果使用的是字级别的 n-gram,min_n 设 2 会比较合适,因为中文双字词是语义的最小单位,单字信息量弱了一些。

5.3 FastText 在分类任务中的应用

除了训练词向量,FastText 本身还自带一个文本分类模型。这个模型结构极简:对句子中的词向量做平均,然后接一个线性分类器。虽然结构简单,但胜在训练快、内存占用小,在数据量不太大的场景下,效果往往能逼近甚至超过一些复杂的深度模型。我曾经在电商评论情感分类项目里用过它,几万条数据,训练只花了几分钟,准确率达到了 86% 左右,在当时的业务场景里已经非常实用。

这里有个值得注意的经验:FastText 的文本分类模型对词顺序完全不敏感,因为它本质上是在做词向量平均。所以在需要理解语序的复杂任务上,比如判断“我喜欢他”和“他喜欢我”的差别,FastText 是会失效的。它更适合那些依赖关键词集合就能做出判断的场景,比如垃圾邮件过滤、商品类目预测、情感倾向判断等。

6. GloVe:在全局统计和局部上下文之间找到平衡

GloVe 全称是 Global Vectors for Word Representation,它跟 word2vec 的关系不是替代,而是互补。如果你已经理解了 word2vec 的本质是“让中心词去预测上下文词”,那么理解 GloVe 的关键就是换一种角度:把共现统计信息直接写入损失函数。

6.1 共现矩阵与词向量的关系

先说共现矩阵。如果我们有一个语料库,词表大小为 V,那共现矩阵就是 V × V 的矩阵,矩阵中的每个元素 P(i, j) 表示词 i 和词 j 在某个上下文窗口内共同出现的次数。这个矩阵的规模是词表大小的平方,对于大规模语料来说,直接存储是不现实的,因此我们不会真的保留这个矩阵,而是把它的统计信息用神经网络建模出来。

GloVe 的核心想法是:假设词 i 和词 j 的词向量分别是 vi 和 vj,它们的点积应该尽可能等于它们共现次数的对数,即:

vi · vj = log(P(i, j))

用通俗的话说:两个词共现次数越高,它们的向量就越接近。这里为什么要用 log?因为词频分布是长尾的,有些词(如“的”“了”)共现次数极高,如果直接用原始数值,低频词的贡献会被淹没。取对数之后,高频词和低频词之间的差异被压缩到合理范围内。

GloVe 还有一个细节体现了它的思想:它会给每个共现次数设置一个权重函数。比如两个词共现次数为 1 的样本,和共现次数为 100 的样本,它们对损失函数的贡献不应该是线性关系。频繁共现的组合权重更高,但也不能过高,否则会被少数极端高频词主导。

6.2 GloVe 与 word2vec 的对比

业内对两者的优劣有过不少讨论。我的体会是:

对比维度word2vecGloVe
训练方式基于预测的神经网络基于统计的最小二乘
信息来源局部窗口上下文全局共现统计矩阵
训练速度较快较慢,需要先构造共现矩阵
低频词表现相对一般相对更好
可扩展性语料较大时表现稳定内存占用随词表平方增长

这说明一个实际问题:如果你的语料很大,但内存资源有限,word2vec 更容易处理;如果你的语料不大,但对低频词的表示质量有要求,GloVe 是一个更好的选择。

6.3 GloVe 训练实操建议

GloVe 官方的实现是用 C 语言写的,训练时需要先构建共现矩阵。如果要快速体验效果,可以直接使用已经预训练好的词向量文件,比如斯坦福发布的基于 Wikipedia 和 Common Crawl 训练的模型。加载方式也很简单:

import gensim from gensim.scripts.glove2word2vec import glove2word2vec # 将 GloVe 格式转换成 word2vec 格式 glove2word2vec("glove.6B.100d.txt", "glove.6B.100d.word2vec.txt") # 用 gensim 加载 model = gensim.models.KeyedVectors.load_word2vec_format("glove.6B.100d.word2vec.txt")

如果自己的语料需要从零训练 GloVe,流程是:预处理文本、构建共现矩阵、执行 GloVe 训练脚本。这里我提醒一个细节:共现矩阵的构建方式很关键。你需要定义窗口大小,并且通常会对离中心词更远的共现词赋予更低的权重。这种距离加权的方式直接影响了共现统计的质量。我实验时发现,窗口大小设为 10 比设为 5 对最终的向量质量有所提升,但提升的幅度有限,反而导致矩阵构建时间翻倍。实际项目中,窗口设置为 8 到 10 就算是一个不错的折中。

7. 更有几何味道的方向:Gaussian Embedding 与 Poincaré Embedding

这两个模型在工业界的应用不如前面几种常见,但它们解决的是更挑剔的问题,值得单独拿出来讲。它们的核心突破是:不再把词表示为欧氏空间中的一个点。

7.1 Gaussian Embedding:用分布表示不确定性

Gaussian Embedding 的思路直观理解是:把一个词表示成高斯分布,也就是一个椭圆体区域,而不仅仅是一个点。每个词由两个参数决定:均值 μ 和方差 Σ。μ 可以理解为这个词最典型的位置,Σ 则表示这个词的语义范围。如果一个词含义明确,比如“钯”这种专业术语,它的方差会比较小,椭圆比较“瘦”;如果一个词含义模糊,比如“bank”这种多义词,它的方差就会比较大,椭圆比较“胖”。

它的直接优势是能刻画词义的不确定性和歧义度。我打个具体的比方:在做医疗文本挖掘时,“心脏”这个词在心脏内科语料中含义清晰,但在综合病历中可能涉及心脏、心情等多种含义,Gaussian Embedding 的方差表示就能把这种差异性体现出来。而 word2vec 只能给“心脏”一个固定的点,无法区分不同上下文中的侧重。

训练 Gaussian Embedding 的损失函数一般基于能量函数或 KL 散度,为了满足对称性,很多实现使用了对称形式的 KL 散度。公式也不复杂:对于一对词 u 和 v,如果它们互为上下文,我们希望它们的分布尽可能接近,同时通过负样本进行对比学习。

我自己在尝试 Gaussian Embedding 时有一个体会:它对随机初始化和学习率比较敏感。如果学习率设置过大,方差值容易崩掉,训练初期就可能发散。建议把方差层的初始化值设小一点,学习率设得比训练 word2vec 时低一个数量级,再配合学习率衰减策略,稳定性会好很多。

7.2 Poincaré Embedding:把层级信息装进双曲空间

Poincaré Embedding 看起来更抽象,但它的直觉其实非常接地气。现实世界中的很多语义关系是树状的:猫属于猫科,猫科属于哺乳动物,哺乳动物属于动物。这种关系如果用欧氏空间表示,很难做到既保留父子之间的深度信息,又保证兄弟节点之间的距离合理。树的高度越高,需要的维度就越大。但在双曲空间中,因为它的几何性质特殊,空间是“越往外越大”的,可以用很小的维度嵌入层数很深的树。

打个比方:欧氏空间像一张纸,你在纸上画一棵树,树的分支越展开,需要的纸面越大;而双曲空间像一个喇叭形的曲面,越靠近边缘“空间越大”,树状结构可以很自然地延展进去。在数学上,双曲空间中的距离公式比较复杂,但它有一个好性质:距离与节点的层级深度、兄弟节点之间的角度都有关系。这个性质使得 Poincaré Embedding 在处理 WordNet、知识图谱、好看点分类体系等有明确上下位关系的场景时效果突出。

训练 Poincaré Embedding 时,损失函数通常是基于距离的排名损失,负样本的采样也很关键。我见过有人在项目中直接用全量随机负样本,效果并不好,后来改成只从非相关节点中采样,并且控制负样本与正样本的比例保持在一定范围内,性能才有明显提升。

7.3 什么时候需要用这两个方向

给出一个选型指南:如果你的任务是普通文本分类、相似度计算、检索,word2vec、FastText、GloVe 已经非常够用,Gaussian 和 Poincaré 的收益不会很明显。但如果你在做知识图谱实体嵌入、上下位词预测、词义消歧,或者你的数据天然带有层级结构,比如电商类目树、生物分类树,那 Poincaré Embedding 值得专门研究。如果你的文本中大量存在多义词,且你希望显式建模这种不确定性,Gaussian Embedding 提供了一个全新的思路。

8. 静态词向量模型选型:一份基于场景的实践建议

说了这么多,最关键的问题来了:实际项目里到底怎么选?我根据自己的项目经验,总结了一份参考指南。

8.1 不同场景下的推荐配置

业务场景首选方案备选方案选型理由
短文本分类(新闻、评论)FastTextword2vec + 平均池化速度快,对 OOV 容忍度高
语义相似度计算word2vec(Skip-gram)GloVe局部上下文信息对相似度更有效
低频词多的领域文本GloVeFastTextGloVe 利用全局统计,低频词相对稳定
多义词较多Gaussian Embedding无(不建议用静态向量)显式建模不确定性
层级结构、知识图谱Poincaré Embedding无(这是专属方案)双曲空间天然适合树状数据
大规模语料、资源有限word2vec(CBOW)FastText训练效率最高

这个表格是我实践经验的浓缩,不是绝对标准,但可以作为初步筛选的依据。

8.2 关于预训练词向量的一个建议

如果你做的是通用领域任务,比如舆情分析、新闻分类,强烈建议直接使用通用语料训练好的词向量,而不是自己从头训。自己训练需要准备大规模语料、分词、清洗、调参,整个流程的复杂度和算力成本往往超过项目本身的一半。只有在领域非常专精,比如医学、法律、金融代码,通用词向量覆盖不好时,才值得用领域语料做增量训练或者在通用词向量基础上微调。

进行增量训练时有一个小技巧:用预训练向量作为初始化,然后用领域语料继续训练,同时把学习率调低到原来的百分之十左右。这样可以保留通用语义信息,又能吸收领域特色。

9. 常见问题与排查技巧实录

最后分享几个我实际踩过的坑和排查思路,每一个都是真实案例的总结。

9.1 训练出的词向量相似度离谱

症状:你查“苹果”的相似词,出来的全是“梨”“香蕉”“水果”之类的还正常,但有时候“苹果”的相似词竟然是“编程”“手机”——这就说明你的语料本身把两种含义混合了,模型找不到一个稳定点,只能取一个中间值。这种时候不要先去调参,先去看语料构成。可以翻一翻训练语料,看看“苹果”这个词在不同句子里的占比。如果两种含义几乎各占一半,静态模型必然无法同时表达两者。解决办法是:要么放弃静态词向量,使用动态模型;要么在预处理阶段就把多义词按照上下文切分成不同的词条,比如“苹果_水果”“苹果_公司”。后者做法很粗暴,但在某些项目里确实能救急。

9.2 结果复现不出来

word2vec 的训练有随机性,因为初始化、负采样、并行训练时的数据顺序都是随机的。如果希望结果可复现,除了设置 seed 之外,还需要注意 gensim 多线程训练在部分版本下即使设了 seed 也不能完全保持一致。一个轻便的解决办法是:对重要实验,用单线程训练并固定所有随机源;对生产环境,不要苛求每次向量一模一样,更稳定的做法是让向量结果保持整体分布一致即可。

9.3 内存爆炸

训练大规模词向量时,最头疼的是内存不足。如果用的语料词表特别大(比如超过 100 万词),并且向量维度是 300,那模型本身的存储就接近上 GB 级别。有几个优化手段我实测有效:一是把不需要训练的词过滤掉,min_count 调高一点;二是使用 gensim 的 KeyedVectors 只保存词向量,不保存网络结构;三是训练时使用磁盘映射,把向量矩阵存储在磁盘上而不是内存中。最后一种方式对超大规模语料几乎是最优解。

9.4 加载预训练模型版本混乱

GloVe 官方提供 50、100、200、300 维等多种版本,word2vec 也有 Google News 等不同版本。加载之后要先验证一下向量维度和词表大小是否一致,否则下游模型输入维度不匹配的报错找半天都定位不到原因。一个简单有效的检查方式是:加载后马上运行一个相似度查询,比如“苹果”最相似的词,如果结果明显是错的,说明文件格式可能解析有问题,别继续进行下游实验。

10. 写在最后的个人体会

这一篇我从独热编码写到 Poincaré Embedding,尽量每个模型的数学直觉都说了。但说句实话,学完这些静态模型,记住它们各自的公式和代码不是最重要的,重要的是建立起一种意识:选词表示方案,本质上是选择你对语义的假设。如果你认为词的含义主要由局部共现决定,选 word2vec;你认为全局统计更重要,选 GloVe;你的数据有很多形态和生僻词,选 FastText;你要表达不确定性,就尝试 Gaussian;你要建模层级关系,就走 Poincaré。没有全能的模型,只有匹配场景的方案。

最后再分享一个小技巧:无论使用哪种词向量,在下游任务前建议做一次向量归一化,这样既能保证不同的模型输出的向量在同一个比较尺度下,也能提升后续聚类或分类算法的稳定性。这一个小细节,能少踩不少坑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询