静态语言模型词向量全解析:从独热编码到Gaussian与Poincaré 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_outputFalse) # 需要做一次 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某个词在文档中出现的次数除以该文档的总词数。逆文档频率部分 IDFlog(总文档数 / 包含该词的文档数 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_patternr(?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 自身的结构并不复杂是一个极简的神经网络。它有两种训练模式。CBOWContinuous 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( sentencessentences, vector_size100, # 词向量维度常用 100-300 window5, # 上下文窗口大小 min_count2, # 词频低于 2 的词会被丢弃减少噪音 sg1, # 1 表示 Skip-gram0 表示 CBOW negative5, # 负采样数量 workers4, # 并行训练线程数 epochs5, # 训练轮数 seed42 # 固定随机种子保证可复现性 ) # 保存模型 model.save(word2vec.model) # 查看相似词 result model.wv.most_similar(电脑, topn10) print(result) # 词向量运算 print(model.wv.most_similar(positive[电脑, 运行], negative[性能], topn5))我在实操中有一个体会min_count 和窗口大小对词向量质量的影响往往比维度更明显。如果语料比较干净、体量大min_count 可以设为 2 甚至 1如果语料噪音较多min_count 可以提高到 5 以上过滤掉那些只在个别文本里出现的低频垃圾词。窗口大小方面如果做的是短文本任务窗口可以小一些比如 3 到 5因为短文本里邻近词的关系更紧密如果做长文档类任务窗口可以适当扩大到 10 左右捕捉更远的语义关系。还有一点容易被忽略训练语料的预处理。中文语料建议先分词而且分词质量直接影响词向量效果。如果你用的是 jieba建议自己维护一个自定义词典把领域专有名词加进去否则类似“支持向量机”被切成“支持”“向量”“机”三个词向量拼不出一个完整的语义。我最早做舆情分析项目时就因为没加自定义词典模型里“新冠”和“疫情”的词向量距离很远后来才发现是分词阶段把“新冠肺炎”切碎了。4.4 word2vec 的局限与 Hidden Biasword2vec 的效果在当时是划时代的但它有一个在后续长期被讨论的问题静态向量无法处理多义词。比如“苹果”在财经新闻里指公司在水果话题里指水果但 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 那套 APIfrom gensim.models.fasttext import FastText model FastText( sentencessentences, vector_size100, window5, min_count1, # FastText 对低频词更友好min_count 可以设小 sg1, negative5, min_n3, # 最小字符 n-gram写 2 到 3 均可 max_n6, # 最大字符 n-gram通常取 5 或 6 epochs5, seed42 )min_n 和 max_n 这两个参数值得细说。min_n 太小比如 1那么单个字符也会作为特征这对于大多数语言来说噪音太大因为单字符太常见不具备区分度max_n 太大比如 8会引入大量长 n-gram训练速度下降而且这些长片段出现的频次往往很低对模型贡献有限。我常用的组合是 min_n3, max_n6对于大多数语言都够用。有一个特殊情况是中文如果使用的是字级别的 n-grammin_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é。没有全能的模型只有匹配场景的方案。最后再分享一个小技巧无论使用哪种词向量在下游任务前建议做一次向量归一化这样既能保证不同的模型输出的向量在同一个比较尺度下也能提升后续聚类或分类算法的稳定性。这一个小细节能少踩不少坑。