
简介在自然语言处理中如何将文本转换为机器可理解的向量表示是核心问题之一。词向量技术通过捕捉词语的上下文共现信息将离散符号映射为稠密向量为文本分类、相似度计算、聚类等任务提供基础特征。Word2Vec作为经典静态词嵌入方法以轻量高效著称Doc2Vec进一步扩展至段落级向量表示。使用维基百科中文语料结合繁简转换、分词等预处理流程借助Gensim工具可训练出适用于中文场景的词向量与文档向量。该方案无需GPU即可获得可解释性强、便于部署的语义表示适用于推荐、检索、去重等工业实践。本文基于一份可完整运行的中文词向量训练项目系统梳理了数据清洗、模型训练与效果评估的全流程并探讨了与预训练模型的异同。 手头这份《中文的word2vec以及doc2vec模型使用维基百度的数据训练 供大家参考.zip》我拆开看了一圈里面就是一套能完整跑通的中文词向量和句向量训练流程维基百科官方dump做语料经过清洗、繁简转换、分词再用gensim同时训练word2vec和doc2vec最后打包了训练脚本、评估代码和模型文件。这个项目对刚接触词向量的朋友尤其友好你不需要自己从零摸索数据预处理直接照着流程跑就能得到一组基于千万级中文句子的词向量和文本向量拿来做文本相似度、聚类、特征工程都行。我过去在好几个项目里都干过类似的事从零搭过中文词向量训练管线也踩过不少坑。这一篇就结合这份zip里的内容把整个项目的设计与实现拆开讲透重点说清楚每一步为什么要这么做、参数怎么调、出了毛病怎么排查顺便把word2vec、doc2vec和现在流行的预训练语言模型之间的关系也理一理。1. 项目背景与整体设计思路拆解1.1 为什么现在还要自己训练word2vec可能有人会问现在都什么年代了BERT、RoBERTa、ChatGLM这些大模型满天飞谁还用word2vec这种“老古董”这个问题我每年都会被问到但实际做工程的时候word2vec和doc2vec依然有它们不可替代的位置。首先是轻量。一个训练好的中文word2vec模型词表10万左右文件大小也就几百MB加载到内存里几百毫秒CPU上跑相似度计算毫无压力。这在很多实时推荐、搜索相关性、日志聚类场景里非常重要——你不能在每个请求里去调一次大模型的接口但你可以随时在内存里查一个词的最近邻。其次是可解释性。word2vec训练出来的向量虽然也是高维空间里的数字但它的语义关系是显式的“国王 - 男人 女人 ≈ 女王”这种类比关系非常直观给业务方解释也好讲。相比之下BERT的cls向量或pooler输出到底代表什么至今都没有一个特别清晰的定论。最后是低资源适配。很多中小型项目根本没有GPU或者只有几台CPU机器。word2vec在纯CPU上训练千万级语料也就几十分钟到几小时完全能接受。而doc2vec可以直接得到任意长度文本的固定维度向量下游接个逻辑回归或者聚类算法就能做文本分类、文章去重这套技术栈整个生命周期都不依赖GPU。1.2 为什么选用维基百科作为训练语料这个项目选用维基百科中文dump我认为是个很正确的选择。可以从下面几个维度看内容覆盖面广维基百科条目涵盖科技、历史、地理、文化、人物、事件等几乎所有领域。用全量中文条目训练出来的词向量通用性很强不会像用某垂直领域语料训出来的那样偏科。文本质量高百科条目的表述相对规范、严谨语法结构完整错别字和口语化表达少。这对word2vec的训练是非常友好的因为模型是从“上下文共现”里学语义的语料越干净学到的向量越稳定。结构化程度好每个条目有标题、正文、分类、链接天然是“文档”级别的划分。这意味着同一份dump既可以用来训练word2vec把全文切成句子每个词学一个向量也可以用来训练doc2vec把一个条目的全文当做一个段落每个段落学一个向量。更新有规律、可复现维基百科会定期打包生成dump文件网上能够找到完整的历史版本。这意味着同一个实验可以用不同日期的数据反复做别人也可以复现你的结果这是很多闭源语料库做不到的。对比一下其他常见语料百度百科虽然体量更大但获取和清洗难度高新闻语料时效性强但领域偏科爬取的网页文本噪声太大。所以如果你只是想训练一份“通用中文词向量”维基百科dump基本是最省心的起点。1.3 整体技术路线拆解我梳理了一下这份项目zip里的完整流程大致可以概括成六步这也是所有中文词向量训练任务的通用链路语料获取下载维基百科中文dump格式为xml.bz2大约1到2GB。文本抽取与清洗解析xml提取每个条目的正文去掉模板、特殊符号、链接标记等噪声输出纯文本。繁简转换因为中文维基百科是繁简混杂的需要统一成简体否则“网络”和“網絡”会被当成两个词。分词使用jieba或pkuseg、LTP等对文本进行分词把连续的汉字序列切成词序列。模型训练分别用gensim的Word2Vec和Doc2Vec训练词向量和文档向量。评估与导出用相似词、类比推理、t-SNE可视化等方式检查模型质量导出成二进制或文本格式供下游使用。每一步看似简单但每一步都有讲究。下面我会把每步的原理和实操细节掰开揉碎讲一遍。2. 核心原理word2vec与doc2vec到底在学什么2.1 Word2Vec的两种训练架构word2vec是Google在2013年提出的一套词嵌入方法核心思想一句话概括一个词的含义由它周围的词决定。它有两种训练架构CBOW和Skip-gram二者正好是镜像关系。CBOWContinuous Bag-of-Words用上下文词预测中心词。比如句子“我喜欢吃[苹果]”CBOW输入“我喜欢吃”和“”输出预测“苹果”。这种方式训练速度快对高频词的表示更准确。Skip-gram反过来用中心词预测上下文词。输入“苹果”预测它前后可能出现哪些词。训练速度慢一些但低频词和稀有词的向量质量更好。在gensim里sg0表示CBOWsg1表示Skip-gram。对于中文这种词汇量巨大的语言如果你想用维基百科语料训练出一个通用词向量我建议用Skip-gram因为中文长尾词多Skip-gram对低频词更友好。如果只是给一个比较大的领域做基础词向量且训练时间紧张CBOW也能用。除了架构还有一个很重要的机制是负采样Negative Sampling。原始softmax需要对词表里所有词计算概率词表几十万甚至上百万计算量太大。负采样的思路是对于每个训练样本不更新所有词的权重只更新目标词的权重和随机选出的几个“负样本”词的权重。这样计算量大幅下降而且负采样本身还能让向量更鲁棒。注意gensim训练时如果加了negative5表示每个正样本会用5个噪声词做负样例。这个值不是越大越好太大的负采样次数反而会影响低频词的表示适中的5到10就够。2.2 Doc2Vec给段落也学一个向量doc2vec也叫Paragraph Vector是word2vec作者Mikolov在2014年提出的扩展它解决的是“一段文本如何变成一个向量”的问题。和word2vec最大的区别在于doc2vec在训练时额外引入了一个段落向量。它有两种训练方式PV-DMDistributed Memory相当于CBOW的文档级扩展。在预测中心词时不仅输入上下文词向量还输入段落向量。每个段落有一个唯一的向量作为整个段落的“主题”信息参与预测。PV-DBOWDistributed Bag of Words相当于Skip-gram的文档级扩展。只用段落向量来预测段落中随机采样的词忽略上下文词。训练快得到的段落向量更偏向于主题/语义层面的表示。用gensim训练doc2vec时dm1是PV-DMdm0是PV-DBOW。在我的实践中PV-DM因为结合了词序信息通常效果更好但训练更慢PV-DBOW快很多得到的向量虽然没有那么精细但对于分类、聚类任务往往已经够用。很多开源项目会直接把两种模型各训一个然后拼接向量效果更稳。这里有一个很多人容易误解的点doc2vec训练出来的“文档向量”不是把文档里所有词向量加总平均得到的而是训练过程中每个段落ID被分配了一个独立的、经过梯度更新优化的向量。所以对于训练集中出现过的文档它有一个“正式”的向量对于训练集中没出现过的文档你需要用infer_vector方法去推断一个新的向量。这个过程等于冻结词向量只更新段落向量让新文档“套”进已有的语义空间里。2.3 关键参数怎么选gensim的Word2Vec和Doc2Vec参数高度一致那些参数看着多真正对结果影响最大的就下面几个。我把自己的经验列成了表格后面也直接照着这个表跑。参数作用我的推荐值说明vector_size向量维度200或300维度越高信息越多但训练慢、占用内存大。中文通用向量建议300小语料或工程上想省内存用200也行window上下文窗口大小5窗口小2-3学到的词更偏向语法窗口大10以上更偏向主题。5是一个平衡点min_count最小词频阈值5词频太低说明样本不足学出来的向量不稳定直接忽略也顺便降词表negative负采样个数55到10效果都不错太高会稀释正样本信号sample高频词下采样率1e-4去掉“的”“了”“是”这类高频无意义词的影响1e-4到1e-5之间常用epochs迭代轮数5到10不是越多越好过拟合后向量会退化。我通常先训5轮看效果不够再加workers并行线程数CPU核数减一不是越多越好线程数太多反而因为锁竞争变慢min_alpha学习率下限0.0001默认就行不用折腾hs是否用层次Softmax0用了负采样就不用hs两者取其一对于doc2vec多一个dm参数决定用PV-DM还是PV-DBOW。还有一个dbow_words如果dm0且dbow_words1会在训练段落向量的同时训练词向量相当于一个模型干两份活。不过我一般还是分开训心里有数。实操心得参数别一上来就追求“最优”。先小规模语料快速跑通检查预处理结果和训练时loss曲线确认流程没问题再上全量语料正式训练。很多新手一上来就全量跑跑了两天发现分词结果不对白白浪费时间。3. 实操过程从维基百科dump到可用的中文词向量3.1 数据下载与预处理第一步这份zip里的第一步是下载中文维基百科的dump文件。我们约定用zhwiki-latest-pages-articles.xml.bz2这个文件它包含所有条目正文体积一般在1.5GB到2GB之间。拿到dump之后第一步是解析xml并抽取正文。这一步其实有两类方案方案一直接用gensim的WikiCorpusgensim内置了WikiCorpus它能把wiki的xml文件直接解析成文本行。我在小语料测试时用过代码写起来很简洁from gensim.corpora.wikicorpus import WikiCorpus wiki WikiCorpus(zhwiki-latest-pages-articles.xml.bz2, dictionary{}) with open(wiki_corpus.txt, w, encodingutf-8) as f: for idx, text in enumerate(wiki.get_texts()): # get_texts()返回的是分词后的词列表适合直接送进word2vec f.write( .join(text) \n) if idx 100000: # 测试时先只处理10万篇 break不过要注意新版gensim的WikiCorpus默认去除了标点并且内部会做一个粗粒度的分词。这个分词结果是基于空格分隔的“词”对于中文来说效果很粗糙直接送进word2vec训练效果会打折扣。所以这个方案只适合快速验证流程。方案二自己解析xml再用jieba处理我实际生产环境中用的是自己写的解析脚本核心逻辑是从dump中提取每个page下的text节点去掉模板、{{}}、[[链接]]、html标签等wikipedia语法元素输出纯文本。这一步用正则就能处理大部分情况import bz2 import re from xml.etree import cElementTree as ET def clean_wiki_text(raw): # 去除{{...}}模板 text re.sub(r\{\{[^{}]*\}\}, , raw) # 去除[[...]]中的链接标记保留中文部分 text re.sub(r\[\[([^\]|]*\|)?([^\]]*)\]\], r\2, text) # 去除html标签 text re.sub(r[^], , text) # 去除注释 text re.sub(r!--.*?--, , text, flagsre.S) return text xml_path zhwiki-latest-pages-articles.xml.bz2 with bz2.open(xml_path, rb) as f: context ET.iterparse(f, events(end,)) for event, elem in context: if elem.tag.endswith(}page): for child in elem: if child.tag.endswith(}revision): for sub in child: if sub.tag.endswith(}text): raw_text sub.text or clean_text clean_wiki_text(raw_text) # 这里可以把clean_text写入文件或者直接送进分词器 print(clean_text) elem.clear()用iterparse流式解析的好处是不会一次性把整个xml加载进内存1.5GB的dump也能在普通机器上处理。清洗后的文本需要过滤掉过短的条目比如只有标题没有正文的以及纯重定向页面。3.2 繁简转换与文本清洗这一步是整个预处理里最影响最终效果的环节之一。中文维基百科有简体条目也有繁体条目如果直接分词“网络”和“網絡”就会被当成两个完全不同的词训练出来的词向量空间里这两个词各占一个位置浪费容量还会破坏后续的相似度计算。标准做法是用OpenCC做繁简转换。OpenCC是一个开源繁简转换库支持多种转换配置比如“繁体到简体”、“简体到繁体”以及带词汇习惯的转换。在Python里的用法很简单# pip install opencc-python-reimplemented import opencc converter opencc.OpenCC(t2s) # 繁体转简体 text_simplified converter.convert(text_original)注意这里的“繁体到简体”不是简单的一对一字替换OpenCC会处理“裡/里”、“軟件/软件”这类词级别的差异能显著提高文本一致性。转换完成后还需要做一轮噪声清洗。我整理的清洗规则大概是这样去除不可见字符去掉零宽空格、控制字符、特殊空白符。去除纯数字串和乱码维基百科里有很多版本号、日期数字串这些对语义学习没帮助。统一标点把中文全角标点统一转为半角或者反着来。这里有个细节如果你最终训练用的是gensim标点会被当成独立token所以最好还是统一成一种风格减少无意义的词表膨胀。去除空段和超短段清洗后如果一行文本长度小于某个阈值比如10个字符直接丢弃不然会给模型带来大量噪声。清洗和繁简转换的顺序我建议是先清洗原始文本去模板、去标签再做繁简转换再统一标点最后分词。这样能避免模板里的繁体字影响转换结果也避免特殊字符干扰分词。3.3 中文分词用jieba加上自定义词典中文和英文不一样词之间没有空格。word2vec的输入要求是“词序列”所以分词是必不可少的一环。在这份项目里用的是jieba分词这也是中文NLP里最普及的工具安装简单速度也够快。import jieba import jieba.analyse jieba.set_dictionary(dict.txt) # 如果不指定就默认词典 def tokenize_sentence(line): # 用精确模式分词 words jieba.lcut(line, cut_allFalse) # 过滤单字和停用词、纯标点 stopwords set() with open(stopwords.txt, r, encodingutf-8) as f: for w in f: stopwords.add(w.strip()) result [] for w in words: w w.strip() if not w: continue if len(w) 1 and w not in 的一是我不有人这在: # 单字通常噪声较大但语气词和常用字可以酌情保留 continue if w in stopwords: continue result.append(w) return result这里有一个我从实际项目里总结出来的经验停用词表别太激进。比如“不”“没”“很”这些词虽然看起来是“虚词”但在语义分析里它们往往包含情感或程度信息。word2vec训练的是“共现”这些词出现在特定上下文里其实能帮助模型学到更细的语义边界。我一般只去掉“的”“了”“呢”“吧”这类纯功能词和标点保留否定词和程度副词。如果你对某个领域有特定词汇比如“深度学习”“神经网络”“机器学习”这些词jieba的默认词典有时候会切错。比如“机器学习”可能被切成“机器/学习”导致训练出来的向量里没有“机器学习”这个完整的词。解决办法是准备一份自定义词典按“词 词频 词性”的格式写入然后jieba.load_userdict(user_dict.txt)。词频可以凭感觉给但最好比jieba默认词频稍高一些否则覆盖不了默认的切分。自定义词典示例机器学习 100000 n 深度学习 80000 n 自然语言处理 50000 n分词完成后把每篇文档变成一个词列表一行一个文档写入文件。这个格式同时可以作为word2vec和doc2vec的输入区别只是doc2vec需要额外标出文档ID。3.4 训练Word2Vec与Doc2Vec模型分词完成之后真正的训练环节其实是最简单的因为gensim已经封装得足够好。关键是选对输入的迭代器和参数。先看word2vec的训练代码from gensim.models import Word2Vec from gensim.models.word2vec import LineSentence # LineSentence直接按行读取每行是一个分词后的句子 sentences LineSentence(wiki_tokenized.txt) model_w2v Word2Vec( sentences, vector_size300, window5, min_count5, negative5, sample1e-4, workers6, epochs5, sg1, # 使用Skip-gram hs0 ) # 保存模型 model_w2v.save(word2vec_zh_300.model) # 也可以导出为文本格式方便其他工具使用 model_w2v.wv.save_word2vec_format(word2vec_zh_300.txt, binaryFalse)再来看doc2vec的训练代码。doc2vec的输入需要是TaggedDocument也就是每个文档要有一个标签文档ID。在这个项目里我直接把每个维基百科条目的ID作为标签from gensim.models.doc2vec import Doc2Vec, TaggedDocument import jieba tagged_docs [] with open(wiki_cleaned.txt, r, encodingutf-8) as f: for idx, line in enumerate(f): words tokenize_sentence(line) if len(words) 5: continue tagged_docs.append(TaggedDocument(words, [idx])) model_d2v Doc2Vec( tagged_docs, vector_size300, window5, min_count5, negative5, sample1e-4, workers6, epochs20, # doc2vec通常需要更多轮次 dm1, # 使用PV-DM dbow_words0 ) model_d2v.save(doc2vec_zh_300.model)这里有一个坑doc2vec在gensim里的epochs默认值是40新版可能是20但词向量通常用5到10轮就够了。因为doc2vec要同时学习词向量和文档向量文档向量每轮只能看到自己文档内部的词训练信号更稀疏所以需要的轮次更多。如果训练时发现infer_vector出来的向量不太稳定同一条文本多次推断结果差异大通常就是epochs不够。注意训练Doc2Vec的时候内存占用是word2vec的好几倍因为每个文档都对应一个向量。一千万篇文章就是一千万个300维向量的内存开销。如果内存吃紧可以选dm0PV-DBOW它不训练词向量只训练文档向量内存省很多。3.5 模型评估不只是“相似词”那么简单训练完模型怎么判断好坏大多数人第一步会玩most_similar看看“苹果”的最近邻是不是“梨”“水果”。这确实是最直观的评估方式但远远不够。我整理了一套多层次的评估方案项目里都写了脚本1) 相似词检查model_w2v.wv.most_similar(苹果, topn10) model_w2v.wv.most_similar(北京, topn10)输出结果看一眼就能知道模型有没有“学进去”。如果“苹果”的最近邻是一堆数字、乱码说明训练数据有问题如果最近邻里有一个“水果”但没有“梨”“香蕉”可能需要调大min_count或者增加训练轮次。2) 类比推理检查# 经典类比北京-中国日本 ≈ 东京 result model_w2v.wv.most_similar(positive[北京, 日本], negative[中国], topn3) print(result)如果这一步能出来“东京”说明模型学到了不错的抽象语义关系。这一步比相似词更高质量能暴露更多问题。3) 文档相似度检查# 对训练集的文档直接用模型里的docvecs v1 model_d2v.docs[0] v2 model_d2v.docs[1] sim np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))也可以用infer_vector对新文档推断向量然后计算相似度from gensim.similarities import Similarity new_doc 自然语言处理是人工智能的一个重要方向 tokens tokenize_sentence(new_doc) vec model_d2v.infer_vector(tokens, epochs50)4) t-SNE可视化把词向量降维到二维然后挑一批水果、国家、职业等类别的词画出来看看同类词是否聚在一起。这一步视觉上很直观适合写报告或者向团队展示。from sklearn.manifold import TSNE import matplotlib.pyplot as plt words [苹果, 梨, 香蕉, 西瓜, 北京, 上海, 东京, 中国, 日本, 医生, 教师] vectors [model_w2v.wv[w] for w in words if w in model_w2v.wv] tsne TSNE(n_components2, random_state0) XY tsne.fit_transform(vectors) plt.figure(figsize(8, 6)) for i, word in enumerate(words): plt.scatter(XY[i, 0], XY[i, 1]) plt.annotate(word, xy(XY[i, 0], XY[i, 1])) plt.show()如果可视化结果里水果类、国家类、职业类能清晰地分成三团说明这个模型质量可以用于生产环境了。如果混成一团那就要回头检查预处理和参数了。4. 常见问题与排查技巧实录4.1 训练速度慢、内存占用高这是新手最常遇到的问题。全量维基百科语料有几亿个词如果直接喂给gensim内存占用很容易超过16GB。我的建议是先用小样本试跑先取10万条文档做一次全流程测试确认代码正确、参数合理再全量跑。不要一上来就全量。降低workers数量gensim的worker线程不是越多越好每个worker会自己持有部分模型副本线程太多内存会翻倍增长。我实测4到6个workers在大多数机器上是甜点区。调整min_count把min_count从3提高到5词表能缩小好几万甚至十几万内存立刻降下来。使用词频过滤在用LineSentence读取时可以先做一遍预统计把高频和低频的词都处理一下但gensim会在训练时自动处理min_count和sample这一步其实没必要。4.2 相似词结果差、全是无关词如果most_similar返回的结果乱七八糟首先怀疑的不是模型参数而是预处理。我自己就遇到过分词时没做繁简转换导致“服务”和“服務”各学各的还有一次是停用词表太激进把“银行”里的“银”和“行”拆开保留了“银行”这个完整词反而消失了。排查顺序应该是检查分词结果随机抽100行文本人工看一遍有没有明显切分错误。检查语料清洗有没有残留的wiki模板、html标签、数字串。如果有word2vec会把“http”“org”“www”这些噪声学得很“好”。检查高频词打印训练后词表里频率最高的50个词如果有一堆意义不明的词说明清洗环节漏了。检查低频词词频特别低的词比如只出现一次的词学出来的向量基本是噪声如果这类词太多需要调大min_count。4.3 分词结果不理想jieba这个库用来做通用分词够用但面对维基百科这种包含大量人名、地名、专业术语的语料时默认词典经常不够。比如“自然语言处理”这个词默认分词会切成“自然/语言/处理”虽然不算错但它不是一个完整的语义单元下游做检索或特征工程时就不方便。解决方式增加自定义词典把领域常见词加进去词频写高一点。对有特殊需求的文本可以考虑用pkuseg或LTP替换jieba它们在专业领域分词的准确率更高但速度会慢一些。也可以试试“词性过滤”分词后只保留名词、动词、形容词丢到代词、副词、助词。这样能降低词表规模也能让向量更聚焦在语义实体上。但如果你的下游任务需要情感分析副词和形容词就不能丢。4.4 doc2vec生成的新文档向量不稳定用infer_vector推断新文档向量时如果多次运行得到的结果差别很大通常有这几个原因epochs太小infer_vector默认是5我建议调到50以上让新文档的向量充分收敛。语料中相似文档太少如果训练语料和你的新文档领域差别很大模型没见过类似的词语组合推断结果自然不稳定。alpha值太大infer_vector里的alpha参数默认0.1如果新文档比较长可以在推断前调低alpha比如0.01避免后期震荡。想验证infer_vector的稳定性可以对同一篇文档反复推断10次然后计算这10个向量之间的平均相似度。能稳定在0.95以上说明推断逻辑没问题。4.5 常见问题速查表现象可能原因解决方案相似词全是无关词语料清洗不干净重点检查模板、html、特殊符号是否残留“苹果”的近邻没有水果类训练语料不够或epochs太少增加训练轮次或降低min_count保留更多低频词模型文件太大词表太大调大min_count删除高频停用词训练时间过长语料太大、向量维度过高小语料试跑vector_size降到200workers调优doc2vec推断结果不稳定epochs太小或alpha偏高infer_vector里增加epochs到50alpha降到0.01两个明显同义的词向量差异大繁简未统一用OpenCC统一成简体后再分词内存溢出语料太大、词表太大调大min_count分批训练换PV-DBOW5. 扩展与对比从word2vec到预训练语言模型5.1 Word2Vec和FastText的对比很多读者会问同样是词向量word2vec和FastText到底选哪个这个问题也经常被挂在网上搜索。我直接说结论FastText是word2vec的增强版它在word2vec的基础上额外引入了字符级的n-gram信息。通俗解释就是word2vec把每个词当成一个独立的原子符号无法处理词形变化和罕见词。中文里可能不明显但英文里“play”“played”“playing”会被当成三个完全独立的词浪费了词根信息。而FastText会把“playing”拆成“play”“lay”“yin”“ing”等字符n-gram这样即使一个词在训练语料中从没出现过只要它有熟悉的字符片段也能拼出一个合理的向量。对于中文FastText的n-gram还能在某种程度上缓解分词错误的影响。比如“机器/学习”被切开后FastText的字符n-gram依然能捕捉到“机器”和“学习”之间的字符重叠信息。实测下来在同一份维基语料上FastText训练出的中文词向量在低频词上的表现一般优于word2vec。但代价是模型文件更大、训练更慢。如果需要压缩模型给线上用word2vec仍然是首选。5.2 从词向量到RoBERTa中文预训练模型现在国内很多论文和项目里都在用RoBERTa中文预训练模型。它和word2vec完全是两个层次的东西word2vec学的是“静态词向量”一个词在任何上下文里都是同一个向量不管它前后是什么词。比如“苹果”在“苹果手机”和“红富士苹果”里向量是一样的。RoBERTa这类预训练语言模型学的是“上下文相关表示”同一个词在不同上下文里会得到不同的向量。这样“苹果”在手机语境里会偏向“品牌”在水果语境里会偏向“水果”语义消歧能力更强。那是不是word2vec就该被淘汰了不是。如果你要做的是文本分类、聚类、相似度检索而且数据量不大、没有GPUword2vec/doc2vec反而是更实际的选择。RoBERTa模型参数量动辄上亿没有GPU根本跑不动而且对中文长文本直接输入也有长度限制。很多工程实践里的做法是用word2vec做候选召回再用RoBERTa做精排这样兼顾效率与效果。这份项目里用维基百科训练的word2vec/doc2vec可以作为RoBERTa等大模型方案的“轻量替代”也可以作为其前置特征。我在实际项目里常用的一种组合是把word2vec词向量拼接到文本的统计特征里作为分类模型的特征输入把doc2vec向量用于文章去重和相似文章召回。这两者的效果虽然不如大规模预训练模型那么惊艳但在资源有限时性价比极高。5.3 模型融合把多个模型的结果拼起来“模型融合”这个关键词在NLP项目里也很常见。单独一个word2vec可能在某些语义关系上表现得不错但在另一些关系上就一般。一个很实用的技巧是同时训练CBOW和Skip-gram两个模型然后对同一组词向量做拼接或加权平均。因为两种架构关注的语义模式不同融合后的向量往往更鲁棒。类似的思路也可以用在doc2vec上分别训练PV-DM和PV-DBOW两个模型然后拼接文档向量。拼接后的维度翻倍600维但包含的信息更全面下游分类准确率通常能有小幅提升。还有一种融合是word2vec向量和TF-IDF特征的融合把一段文本的doc2vec向量和词级别的TF-IDF加权word2vec向量拼在一起作为文本的最终表示。这个做法在文本相似度任务里效果很不错。import numpy as np def get_weighted_vector(words, model_w2v, idf_dict): vec np.zeros(model_w2v.vector_size) total_weight 0 for w in words: if w in model_w2v.wv and w in idf_dict: weight idf_dict[w] vec weight * model_w2v.wv[w] total_weight weight if total_weight 0: vec / total_weight return vec5.4 增量训练与领域微调另外一个很实用的扩展是“增量训练”。如果你在维基百科的通用语料上训练好了词向量但下游任务里有很多领域专有词比如医疗、法律、金融词汇与其从头训练不如在已有模型基础上继续训练。gensim里可以直接用build_vocab和train方法在已有模型上继续训练model_w2v Word2Vec.load(word2vec_zh_300.model) # 注意继续训练时新词的min_count阈值可能需要重新考虑 new_sentences LineSentence(domain_corpus.txt) model_w2v.build_vocab(new_sentences, updateTrue) model_w2v.train(new_sentences, total_examplesmodel_w2v.corpus_count, epochs5)但这里有一个容易踩的坑继续训练时如果学习率保持和初始训练一样大容易把已有模型的结构破坏掉导致原有的通用语义丢失。解决办法是在继续训练时把学习率调低比如初始训练时alpha0.025增量训练时改成alpha0.002。这样模型能在保留原有语义的基础上吸收新领域的知识。实操心得增量训练不是“越多越好”。如果在领域语料上训太多轮模型的词向量会逐渐“忘掉”通用语料里的知识泛化能力下降。我的经验是控制在3到5轮并且每一轮都在一个固定的评估集上做相似词检查一旦发现“北京”的近邻开始出现领域噪声词就停止训练。写在最后的一点体会这份项目我前后跑过好几遍每次从维基百科dump开始处理到最终拿到可用的中文词向量大概要花大半天时间其中数据清洗和分词占了七成精力真正的模型训练反而是最轻松的。很多人拿到开源项目就急着跑模型结果因为预处理环节没做好训出来的向量一塌糊涂还以为是代码写错了。其实在NLP这个领域数据质量决定模型上限这个道理对word2vec适用对BERT这类大模型也一样适用。如果你打算在自己的项目里用这套方案我的建议是先别急着追求“最好”的参数踏踏实实把预处理管线跑通把清洗、繁简转换、分词这几个环节的质量做扎实然后再去调参数。等你对这套流程熟了之后会发现自己对“词向量到底是什么”的理解也加深了一层。那时候你再去看BERT、RoBERTa这些更复杂的模型会有一种豁然开朗的感觉——它们背后学习语义的基本逻辑都和word2vec一脉相承。最后再分享一个小技巧维基百科dump本身会持续更新如果你想在未来对比不同版本数据训练出来的模型效果记得保存一份训练时用的预处理脚本和参数配置。这一点看似不起眼但等你三个月后再回来重新训练时会庆幸自己当初留下了这份记录。本文还有配套的精品资源点击获取