ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Word2vec词向量效果差?从数据清洗到参数调优的排查全攻略

Word2vec词向量效果差?从数据清洗到参数调优的排查全攻略

1. 问题定位与整体思路

想说一个比较扎心的场景:你跟着网上的教程把 Word2vec 跑起来了,代码也没报错,模型也训练完了,满心欢喜去算一下"美丽"和"漂亮"的相似度,结果出来一个负数;再做一下经典的词类比测试,"北京-中国+美国"应该得到"华盛顿",结果返回一个莫名其妙的高频词。这时候大部分人第一反应是"我参数是不是调错了",其实根据我自己的实际经历,绝大多数情况下问题根本不在最后的训练参数,而是从头到尾的任何一个环节都可能埋雷。

先厘清一个概念:很多人说的"准确度太低",严格来说在 Word2vec 里并不存在标准的准确度指标。Word2vec 属于无监督表示学习,它没有监督任务里那种"对或错"的标签。我们在实践里常说的"准确度低",实际上是几种不同现象的统称:

  • 语义相似度结果离谱,比如近义词相似度接近 0 甚至为负;
  • 词类比任务(king - man + woman ≈ queen)答对率极低;
  • 把训练好的词向量接进下游分类任务,效果还不如随机初始化的 embedding;
  • 词向量可视化之后一团乱麻,看不出任何语义聚集。

这四种现象对应的原因侧重点是完全不同的。我自己排查的时候习惯于把整个流程拆成四个环节:语料数据、预处理、算法参数、评估方式。注意这个顺序是有讲究的,越是靠前的环节影响力越大,而且越靠前的错误,越容易被后面的"调参"行为掩盖。你反复调 window、调 negative 都没用,往往因为语料本身就是一锅粥。

所以拿到"准确度低"这类问题,先别急着动参数,应该先回答一个问题:你现在最在意的是哪个维度的效果?如果只是想要几个词之间的相似度看起来合理,那重点在语料清洗和停用词处理;如果你要做类比推理测试,那一方面语料要高质,另一方面窗口大小和训练充分度很重要;如果是要跑下游任务,那么词向量的语义空间是否平滑就比单个词的相似度更重要。

我见过不少同学,词向量训得不理想,就疯狂去搜"Word2vec 最全参数清单",把 window 从 5 改到 9,再把 min_count 调低,指望着参数一改脱胎换骨。实际上在做这些操作之前,至少应该先做个快速体检:检查语料的总词数、词典大小、低频词占比、单条文本的平均长度。这一套体检做完,大概率能直接看出训练效果差的根本原因。

2. 数据与预处理:第一道闸门

2.1 语料规模其实是一种"质变"因素

很多人对语料量的感知是很模糊的,总觉得"我有几万条评论/几万字文档,应该够了吧"。这个想法放在普通文本分类里可能勉强能跑,放在 Word2vec 里基本等于没训。

Word2vec 之所以能学到有意义的语义关系,靠的是大量的"共现上下文"信号。比如"苹果"和"香蕉"要能被模型认为相似,语料里就得反复出现它们与"水果、好吃、甜的、削皮"这类词的搭配。如果语料只有几万条,很多词之间的共现关系是稀疏的,模型根本没有机会在这些词之间建立联系。

这里我提供一个快速估算的经验值:想要得到一个语义空间还不错的词向量,语料的有效文本量至少在千万字符这一量级(英文可以稍微少一点,但中文至少要上亿字才保险),这只是针对词典大小在 5 万 - 10 万级别的场景。如果你需要覆盖某个垂直领域的术语,又希望这些低频专业词也有质量,那么语料还得继续加量。

但注意,光有量不够。有些同学的语料是从网上爬来的,里面带着大量 HTML 标签残留、超链接、乱码符号,甚至还有广告信息,这些噪声会把上下文窗口搅得极其混乱。打个比方,你在一个满是弹幕的直播间里听别人聊天,你能听清几个人说话?词向量就是在一堆噪声里试图听话的那个学生。

所以第一步是清洗。我自己常用的顺序是:去除 HTML 标签、统一全半角、剔除乱码字符、按行去重(重复文本会严重影响词频分布)、过滤掉过短的文本。这里单独提一下按行去重,很多爬虫采集到的语料里同一句话会反复出现几十遍,如果不做去重,那几个高频句子就会主导上下文窗口,让模型变成一个"复读机",学出来的词向量缺乏泛化性。

2.2 分词粒度的一致性:中文字词向量最容易踩的坑

中文词向量绕不开分词,而分词粒度的一致性直接决定词向量的质量下限。很多人喜欢用 jieba 直接按最大概率路径切分,然后一股脑丢进去训练,结果你会发现"北京大学"被切成了"北京/大学","机器学习"被切成"机器/学习"。有些词你希望它是一个整体向量,有些词拆开反而更好,这完全取决于你的下游任务。

这个问题的本质是:Word2vec 的输入是词,但"词"的定义对中文来说本身就是模糊的。如果你做的是通用领域,用 jieba 默认词典问题不大;但如果你在某个垂直领域(比如医疗、法律、工业制造),必须加载一份高质量的用户词典,把这些领域的专有名词锁成一个词。

一个容易忽略的点是分词结果的一致性。什么意思?同一个句子在不同位置被切分的结果应该稳定。比如"当人工智能成为主流时智能家居也会普及"这句话,如果 jieba 在部分场景下切出"人工智能",换个地方又切出"人工/智能",那模型会同时学习到两个完全不同的表示,语义空间会被拉散。解决方法是固定 jieba 的词典之后,最好在自定义词典里把所有可能被拆的复合词都加进去,宁可分词粒度偏大,也不要前后不一致。

对英文用户来说,分词问题相对轻,但也要注意词形还原或者词干提取该不该做。举个例子,cook、cooked、cooking 如果拆成三个词,模型虽然能学到一定共性,但每个词的出现频率都会被稀释,词的表示质量会受影响。

2.3 min_count 和 max_vocab_size 的取舍逻辑

min_count 是很多人喜欢乱设的参数。设得太低,低频词全部进入词典,但这个词总共只出现了两三次,上下文信号严重不足,学习出来的向量的稳定性很差。我见过某同学把 min_count 设成 1,训练出来的低频词向量动不动跟其他任意词相似度都特别高,原因就是这些词的上下文噪声太大,模型只能给它们一个"平均的没啥含义"的向量。

反过来,设得太高,词典太小,一些有区分度的中频词会被误杀,同样会造成效果下降。我在实践里的原则是:宁可让低频词被丢掉,也不能让它们在词典里当噪声。因为下游任务碰到这些低频词的时候,你随时可以用 OOV 策略或者加上其他模型去兜底;但如果词典里充满了垃圾低频词,那整个语义空间都会被污染。

还需要留意 gensim 在 4.0 之后对词汇管理的改动:旧版本里 model.wv.vocab 是 dict,新版本里换成了 KeyedVectors,遍历和查询的方式都不一样。如果照着老博客的代码去访问 vocab 会直接报错,需要改用 model.wv.index_to_key。

3. 核心算法与参数调优:每一个参数背后都有原因

3.1 Skip-gram 还是 CBOW?这是个战略选择

gensim 里的 Word2vec 默认用的是 CBOW(Continuous Bag-of-Words),即通过上下文词预测中心词。CBOW 的优势是训练快、对高频词更友好,在语料充足且目标是"通用语义表示"的时候表现很稳定。Skip-gram 则反过来,用中心词预测上下文,它对低频词和稀有语义更友好,但训练开销更大。

如果目标是把词向量用到专业领域的语义相似度计算、词类比这类任务上,Skip-gram 通常是更好的选择。原因在于 Skip-gram 将每个词作为预测的主体,迫使模型更加精细化地理解该词的上下文分布;而 CBOW 会把上下文的词向量做平均/求和,信息被"糊"在一起,低频词的细节很难保留。

但这不意味着 Skip-gram 一定更好。语料量不够大的时候,Skip-gram 会因为参数量太大而欠拟合,训出来的向量可能比 CBOW 还差。所以我的建议是:语料很充足、追求极限语义细粒度就选 sg=1;语料有限、目标只是拿到一个大致可用的初始化 embedding,用默认的 CBOW 就行。

3.2 嵌入维度:100、200 还是 300?不是越大越好。

很多人觉得维度越大信息越丰富,直接拍脑袋设个 500。你先冷静一下:embedding 维度本质上是模型的一层隐藏层宽度,维度越大,可容纳的信息越多,但需要的训练语料也越多。维度太大了但语料不够,学出来的向量是高维空间里的稀疏点,互相之间的距离关系完全没有意义。

我在垂直领域项目中的经验是:

  • 语料 1000 万字符级别:dim 100 足够,甚至 50 都可以;
  • 语料 1 亿字符级别:dim 150-200 比较合适;
  • 语料 10 亿字符级别以上:维度 300 左右会有明显增益。

超出这个范围,收益会急剧递减。另外,下游任务如果只是给 LR、XGBoost 之类的模型当特征,100 维和 300 维差距并不大;但如果你要对接深层神经网络,可以考虑高维一点。

3.3 窗口大小:语义相关性和主题相关性的权衡

window 参数决定训练时考虑的上下文范围。窗口小(比如 2 到 3),模型看到的是紧邻词,学到的语义更"窄"更精准,比如"苹果"和"香蕉"会靠近;窗口大(比如 10 以上),模型会把更远的词也拉进来,学到的更偏向"主题相关",比如"苹果"会跟"公司"、"发布会"离得近。

很多调参教程喜欢把 window 默认设在 5,这适合通用语义。但如果你发现自己训出来的词向量在"近义词"上表现不错,却在"上下位词"或者"类比任务"上一塌糊涂,可以试试把 window 调大,给模型更多共现信息。但注意,window 太大会引入很多噪音词,尤其是中文这种一句话包含大量虚词的场景,过大的窗口反而会让无关词产生虚假关联。

我一般会跑 3 和 7 两组对比,看哪组在下游任务上的效果更合适,而不是凭感觉猜。

3.4 负采样数量和采样阈值:高频词的"拔河"问题

negative 参数默认是 5,它代表着每训练一个正样本,要随机抽取几个负样本。如果语料里高频词特别多(比如中文的"了、的、是"),上下文窗口经常被这些虚词占据,模型很难学到有效信息。这时候除了在预处理里去停用词,另一个手段是调大 sample 参数。

sample 是高频词的下采样阈值,默认是 1e-3,代表词频超过该值的词会被概率性丢弃一部分出现样本。这样做的原理是:一个词出现太多次,它的上下文信号已经足够充分,再继续训练它反而会拖慢训练并占用表示空间。如果你的语料里高频虚词严重干扰语义,把 sample 调成 1e-4 或 5e-5 会更激进地降采样高频词,模型更多聚焦在中频和低频词上,效果往往会有明显提升。

我自己踩过的一个坑:在某个垂直领域语料里,"产品"这个词出现了几万次,导致它在向量空间里变成了一个"万金油",跟任何词都有一定相似度。当时调了 sample 之后,这个问题显著缓解了。

3.5 epochs 和训练充分度:在欠拟合和过拟合之间反复横跳

epochs 这个参数也很容易被忽略。gensim 4.0 之后不叫 iter 了,改叫 epochs。多训练几轮理论上能提高向量质量,但有一个前提—语料要足够多样。如果语料本身风格单一、重复度高,多训练几轮只会让模型把那些重复文本的上下文关系强化成"铁律",泛化能力反而下降。

我自己的经验是:大部分时候 5 到 10 轮就足够。如果 10 轮之后相似度结果还在明显变化,说明语料量或者学习率设置可能有问题。另外发现 gensim 默认的初始学习率是 0.01,语料大的时候可以不调,但如果语料偏小、训练不充分,可以把 alpha 适当调到 0.03 或 0.05 再训练看效果,但记得后期它会线性衰减。

3.6 一个通用的调参起点建议

分享一份我常用的参数起步组合(用于中文通用领域):

  • sg=1(Skip-gram)
  • vector_size=200
  • window=5
  • min_count=5
  • negative=10
  • sample=1e-4
  • epochs=8

这个组合不一定最优,但比较均衡:低频词有一定保护,高频噪声被压制,训练成本适中。拿到一组结果后,优先调的是 sample 和 window,因为这两个参数对语义空间结构的影响最直观。

4. 评估与调试:客观衡量词向量质量

4.1 可视化先探路:TSNE降低维度看聚类

在动手调参之前,先把词向量降维到二维,用 TSNE 画出来看。这一步成本低、见效快,而且能直观暴露问题。

我之前在一个金融语料上训练,一开始画出来的图就是一大坨,所有词挤在一起,没有任何趋势。后来发现原因是语料清洗不干净,很多文本只剩几个孤零零的词,上下文窗口形同虚设。清洗之后重新训练,可视化里不同主题的词明显分成几簇,金融术语、产品名称、情感词各自聚在一起,这时候我才确信模型是真正学到了东西。

可视化不需要追求完美,只要能看到部分语义聚集的迹象,就说明模型本身没有问题;如果全图混沌一片,先别管参数,回去查数据。

4.2 类比测试:一个更严谨的"正确率"指标

如果你想用一个数字来量化词向量质量,词类比测试是最常见的做法。经典问法是:给定三组词的向量关系,如"vec(北京) - vec(中国) + vec(美国)",去找和这个结果向量余弦相似度最高的词,看是不是"华盛顿"。

gensim 里可以用 model.wv.most_similar 配合 positive/negative 参数来完成这个测试。我自己会在验证集上准备 200 到 500 组类比题,算一下答对比例。这个比例通常比单看相似度更可靠。不过要注意,测试集的构造本身就带有主观性:如果类比关系设计的都是"国家-首都""品牌-产品"这种结构清晰的,那答对率高并不代表真实语义空间的全面质量。

4.3 训练集之外的测评:下游任务才是最终裁判

尤其中文学术圈里很流行"词向量准确度"的说法,但如果你最终目的不是类比任务,而是把词向量当作下游任务的一部分,那就必须在下游任务上做验证。比如做情感分类,你可以把训练好的词向量 freeze 住,embedding 层直接初始化,然后看 F1。把这个数值跟随机初始化的 embedding 对比,如果提升微弱,说明词向量的语义信息并没有被有效利用,问题可能出在语料的领域匹配度上。

要不要覆盖到领域外的大规模通用语料,是这里的一个关键选择:如果只有领域内的语料(比如只有医疗文本),词向量会在领域内表现不错,但在通用词的语义关系上一般会偏差。想要取得平衡,建议在领域语料里混入一部分通用语料,混合比例可以在 3:1 到 1:1 之间尝试。

4.4 多组实验对照:养成记录的习惯

调参最忌讳"我今天改了这个,感觉好了点,但不确定是不是因为参数"。我自己每次实验都会把关键信息记录成一个表格,包括语料规模、清洗规则、sg 选择、window、negative、sample、epochs、训练耗时、类比正确率、下游任务指标。这样跑过几组之后,很容易看出哪些参数的改变真正带来了提升,哪些改动只是心理安慰。

比如我之前遇到过一次负采样数量从 5 提高到 20,类比正确率涨了两个百分点,当时很高兴,但后来看记录才发现同一轮里我把语料去重规则也改了,两个变量混淆在一起。后来养成单变量控制的习惯,折腾效率高了很多。

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

5.1 高频但不好用的排错速查表

我把这几年遇到最多的"准确度低"案例汇总成一张速查表,以下内容都是常见实践中容易踩的问题,不代表某特定项目的具体数据,仅供排查参考:

症状常见原因排查与解法
相似度普遍偏高(0.9 以上),词之间分不开高频词噪声主导,或 min_count 过低混入大量噪音词调大 sample 值(如 1e-4),适当提高 min_count
相似度普遍偏低,近义词也不像语料量太少,或语料面过于单一扩充语料,引入领域外通用语料
某些低频词向量像随机初始化该词在语料中出现次数太少提高该词在语料中的覆盖,或降低 min_count 但单独做平滑
类比任务答对率极低训练不充分、窗口太小、CBOW 模型不适合换 sg=1,增大 epochs,适当加大 window
下游任务比随机 embedding 还差领域语料不匹配,或词向量训偏检查清洗和分词,混入更多目标领域语料
同一话题的词聚不在一起窗口过大,模型学到了太多无关共现缩小 window 到 3-5

这张表不解决所有问题,但能帮你把"症状"翻译成"可能的病灶",再针对性地去验证。

5.2 容易被忽略的 API 细节坑

gensim 4.0 之后有一批 API 调整,网上大量老博客的代码没法直接运行。比如:

  • model.vocabulary 已经不存在,需要改成 model.wv.index_to_key;
  • 保存和加载模型建议用 model.save,加载用 Word2vec.load;如果只保存了词向量,用 model.wv.save 配合 KeyedVectors.load_word2vec_format 做兼容;
  • similarity 接口没问题,但如果要对全词典做相似度批量检索,用 model.wv.similar_by_word 比循环 most_similar 快很多。

这些小细节虽然不影响参数层面的"准不准",但确实耽误过不少人的时间,尤其是当训练很久之后才发现代码根本算的是另一个东西。

5.3 关于"再训一次"的误区

还有一个普遍现象:训练完了发现效果不行,直接在已有模型上再调用 train 方法继续训练。这有时候会雪上加霜。因为 gensim 的 Word2vec 对象如果调用 train 传入非初始语料,学习率和语料顺序都会有巨大变化,模型内部的状态并不能简单地继续累积优化。正确的做法是重新初始化一个新的模型,从零开始训练。如果你觉得之前的模型有点苗头但不够好,可以把它的权重作为预训练直接加载,但至少要知道你是在做 fine-tune,不是在"续训"。

5.4 低频词和 OOV 词的最终兜底方案

再优质的语料也无法覆盖所有词。遇到下游任务出现 OOV 词时,我一般会用三种兜底策略:

  • 直接用一个全局平均向量或者随机向量,但效果一般;
  • 利用 FastText 的 n-gram 特性,它在处理 OOV 时比 Word2vec 有天然优势;
  • 组合策略:Word2vec + FastText 两者的向量拼接,这种方式在垂直领域里往往比单独使用任何一种都稳定。

Word2vec 本身给不了你 OOV 词的向量,但 Word2vec 学的语义空间是其他模型可以迁移的基础,把它们组合不是丢人的事,反而很实用。

6. 写在最后的一点体会

说实话,词向量训练这个事,市面上有大量论文和调参宝典,但真正决定成败的还是数据本身。我自己经历过太多"调了两周参数,结果发现是语料里有几万行重复数据"的时刻。技术社区里总有人强调参数多重要,但实际上高频词分布、语料干净程度、分词一致性,这三个基本盘不出问题,模型效果就差不到哪儿去。

把"准确度太低"当作一个需要解剖的现象,而不是一个需要魔法般修复的 bug,是解决问题最关键的心智转变。下次你打算写代码前先花二十分钟推敲数据,可能比你在参数里翻一整天更有收获。先按 3.6 的起点组合跑基线,再用 4.2 的类比测试做个评估,然后对照 5.1 的速查表去调整,这条路子比盲目乱试要高效得多。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进