中文维基百科语料实战:word2vec与doc2vec一次跑通
简介这份资源提供中文word2vec与doc2vec预训练模型基于维基百科与百度百科语料训练面向自然语言处理初学者及需要快速验证中文语义任务的开发者。模型可用于语义相似度计算、关键词提取、文本分类等基础场景帮助使用者跳过耗时的语料采集与训练环节直接进入应用与调优阶段。压缩包共7个文件以txt说明文档、Python脚本、md项目说明及license、gitignore等配置为主整体约3KB体量轻便便于快速浏览与二次开发。其中Python脚本承担模型加载与处理逻辑说明文档交代数据来源与使用方式依赖清单则方便复现运行环境。目前已有81人学习下载适合作为中文文本表征的入门参考也可为后续更复杂的语义分析任务提供基础工具与排错思路。1. 中文 word2vec 与 doc2vec用维基百科语料把词向量和文档向量一次跑通很多人第一次接触词向量是拿英文的text8或者enwik9跑了个 demoking - man woman ≈ queen一出来就觉得很神奇然后想换中文试试结果卡在第一步——语料从哪来、怎么清洗、word2vec和doc2vec到底该选哪个、训练完怎么验证有没有效果。这个标题讲的正是这条链路用中文维基百科的公开数据训练出中文的 word2vec 词向量和 doc2vec 文档向量把「词」和「文档」两个粒度的表示一次做出来。它适合两类人一类是想给自己的检索、聚类、推荐、文本分类任务补一个中文语义底座又不想直接调云 API 的工程师另一类是学生和转行者需要一个能完整复现、能讲清楚每一步在干什么的中文 NLP 入门项目。下面我按自己实际跑过的顺序把语料获取、清洗、训练、验证、踩坑全部拆开讲参数给到能直接抄的程度。2. 语料从哪来中文维基百科的获取与清洗链路中文维基百科是整个方案里最现实的选择体量够大几百万条目级别、覆盖领域广、纯文本、可离线处理而且有现成的 dump 文件可以下载。但它的原始格式是带大量 MediaWiki 标记的 XML直接丢给word2vec训练出来的向量基本没法用所以清洗这一步的投入往往比训练本身还大。这一章把「拿到数据 → 变成干净句子」这条链路讲透。2.1 为什么选维基百科而不是新闻或论坛语料先说选型理由。新闻语料时效性强、领域偏窄论坛语料口语化严重、噪声大且版权不清晰而维基百科是百科体句子结构完整、术语密度高对训练通用词向量最友好。它的另一个好处是「可复现」——你和我下载的是同一份 dump清洗脚本一致结果就能对齐这在排查问题时非常关键。常见做法是下载zhwiki-latest-pages-articles.xml.bz2这个 dump体积在几个 GB 量级解压后更大。注意不要用「仅摘要」或者「仅标题」的版本那些数据量太小训出来的向量对低频词几乎没有表示能力。下载完成后整条链路是解压 → 抽取正文 → 去掉 wiki 标记 → 繁体转简体 → 分句 → 分词 → 训练。2.2 用 WikiExtractor 抽取正文并去标记第一步是把 XML 里的正文抽出来。业界最常用的是WikiExtractor它能把 MediaWiki 标记、模板、表格、引用这些噪声去掉只留纯文本段落。# 安装 WikiExtractor pip install wikiextractor # 从 dump 抽取正文输出到 extracted 目录 # --json 让每条目输出为一行 JSON方便后续处理 # --no-templates 去掉模板内容减少噪声 python -m wikiextractor.WikiExtractor zhwiki-latest-pages-articles.xml.bz2 \ -o extracted \ --json \ --no-templates这段命令的逻辑是WikiExtractor逐条读取 XML 中的page解析出标题和正文剥离[[链接]]、{{模板}}、ref等标记最后按--json格式每条目输出一行。--no-templates很关键维基的模板里塞了大量导航、信息框内容不去掉会污染语料。参数上-o指定输出目录抽取结果会按AA、AB这样的子目录分片存放方便并行处理。抽取完成后你会得到一堆 JSON 行文件。接下来要做的第一件事是繁体转简体因为中文维基里繁体内容占比不低不统一会导致「電腦」和「电脑」被当成两个词。import json import glob from opencc import OpenCC cc OpenCC(t2s) # 繁体转简体 def iter_docs(extracted_dir): for path in glob.glob(f{extracted_dir}/**/*, recursiveTrue): with open(path, r, encodingutf-8) as f: for line in f: line line.strip() if not line: continue try: obj json.loads(line) except json.JSONDecodeError: continue yield obj.get(title, ), obj.get(text, ) with open(wiki_zh_clean.txt, w, encodingutf-8) as out: for title, text in iter_docs(extracted): text cc.convert(text) # 繁转简 text text.replace(\n, ) # 段落内换行合并 if len(text) 50: # 太短的条目丢弃 continue out.write(text \n)这段脚本做了三件事遍历抽取目录、逐行解析 JSON、繁转简并合并换行。OpenCC(t2s)是繁转简的标准配置别用t2s.json之外的奇怪配置。len(text) 50这个阈值是过滤掉消歧义页、重定向页这类几乎没有正文的条目具体数值可以按你的语料规模调我一般设在 50 到 100 之间。2.3 分句与分词决定向量质量的两个细节清洗完的文本还是一整篇word2vec需要的是「句子」级别的输入因为它的上下文窗口不会跨句。所以要先分句再分词。import re import jieba def split_sentences(text): # 按中文标点切句保留语义边界 parts re.split(r[。\n], text) return [p.strip() for p in parts if len(p.strip()) 5] with open(wiki_zh_clean.txt, r, encodingutf-8) as f, \ open(wiki_zh_seg.txt, w, encodingutf-8) as out: for line in f: for sent in split_sentences(line): words jieba.lcut(sent) # 过滤空白和单字符噪声 words [w for w in words if len(w.strip()) 0] if len(words) 3: continue out.write( .join(words) \n)分句用正则按。切这是中文最常见的句末标点。分词用jieba.lcut它是精确模式适合词向量训练。这里有个血泪经验不要用jieba.cut的搜索引擎模式那个模式会把长词再切碎训出来的词表里全是碎片反而降低质量。len(words) 3是丢掉过短的句子避免噪声。分词后你会得到一个每行一句、词间空格的文本文件这就是word2vec和doc2vec的标准输入格式。到这一步语料准备才算真正完成。3. 训练 word2vec参数怎么设、模型怎么存、效果怎么验语料就绪后训练本身其实很快真正决定成败的是参数。gensim是中文社区用得最多的库它的Word2Vec接口成熟、文档全、坑也相对少。这一章把训练、保存、加载、验证四步讲清楚参数给到能直接用的程度。3.1 gensim 训练 word2vec 的最小可用脚本先上能跑的代码再逐参数解释。from gensim.models import Word2Vec from gensim.models.word2vec import LineSentence # LineSentence 按行读取内存友好适合大语料 sentences LineSentence(wiki_zh_seg.txt) model Word2Vec( sentencessentences, vector_size200, # 词向量维度 window5, # 上下文窗口 min_count5, # 低于此频次的词丢弃 workers8, # 并行线程数 sg1, # 1skip-gram, 0CBOW epochs5, # 训练轮数 negative5, # 负采样个数 sample1e-3, # 高频词下采样阈值 ) model.save(word2vec_zh.model) print(词表大小:, len(model.wv))LineSentence是内存友好的读取方式它不会一次性把整个文件读进内存而是按行流式喂给模型几个 GB 的语料也能跑。vector_size200是中文的常用值100 偏小、300 偏大200 在效果和存储之间比较平衡。window5是通用默认如果你做的是短文本相似度可以降到 3做主题相关任务可以升到 8。min_count5是过滤低频词中文维基里大量只出现一两次的人名、地名留着只会增加噪声。sg1用 skip-gram它对低频词更友好代价是训练慢一些如果语料特别大、追求速度可以改sg0用 CBOW。negative5和sample1e-3是负采样和高频词下采样这两个参数一般不用动默认值就是经过大量实践验证的。3.2 关键参数怎么调一张对照表参数不是拍脑袋定的下面这张表是我在不同任务里反复试出来的经验值可以直接对照你的场景改。参数常用值调大后的影响适用场景vector_size200表达力强但存储和计算上升通用300 用于大语料window5捕获更远语义但主题漂移短文本用 3主题任务用 8min_count5词表变小低频词丢失语料大用 10语料小用 3sg1skip-gram 慢但低频词好通用推荐 1epochs5轮数多易过拟合语料大 3~5语料小 10negative5负采样多训练慢一般不动调参的核心逻辑是语料越大参数可以越「宽松」语料越小越要收紧min_count和epochs。我见过有人拿几十 MB 的语料跑vector_size300、epochs20结果向量过拟合相似词全是高频虚词这就是参数和语料规模不匹配。3.3 训练完怎么验证相似词、类比、可视化模型存下来只是第一步得验证它到底学到了什么。最直接的方法是看相似词。from gensim.models import Word2Vec model Word2Vec.load(word2vec_zh.model) # 看「计算机」的相似词 for word, score in model.wv.most_similar(计算机, topn10): print(word, round(score, 3)) # 类比任务国王 - 男人 女人 ≈ ? result model.wv.most_similar(positive[国王, 女人], negative[男人], topn3) print(result)most_similar返回的是余弦相似度最高的词。如果「计算机」的邻居里出现「电脑」「软件」「程序」这类词说明向量质量不错如果全是「的」「了」这种虚词那基本是语料清洗或min_count出了问题。类比任务国王 - 男人 女人期望得到「王后」这类词中文上不一定每次都准但能大致反映语义空间的线性结构。提示验证时优先看中频词的相似结果高频词如「中国」和极低频词只出现几次的都不能代表整体质量。4. 训练 doc2vec把整篇文档压成一个向量word2vec 给的是词向量但很多任务要的是「一篇文档一个向量」比如新闻聚类、相似文章推荐、文档检索。doc2vec 就是干这个的它在 word2vec 的基础上多学一个「段落向量」让每篇文档也有自己的表示。这一章讲清楚它的两种训练模式和落地细节。4.1 PV-DM 与 PV-DBOW两种模式怎么选doc2vec 有两种训练模式。PV-DM分布式记忆在预测词的时候同时用上下文词和段落向量效果通常更好但训练慢PV-DBOW分布式词袋只用段落向量预测句中的随机词训练快对短文档更友好。在gensim里dm1是 PV-DMdm0是 PV-DBOW。我的经验是文档较长超过 100 字用 PV-DM短文本或追求速度用 PV-DBOW。中文维基的条目普遍较长所以默认用 PV-DM。4.2 用 TaggedDocument 组织数据并训练doc2vec 的输入需要给每篇文档打一个标签gensim用TaggedDocument来封装。from gensim.models.doc2vec import Doc2Vec, TaggedDocument from gensim.models.word2vec import LineSentence # 这里假设每篇文档已经按行存好每行是一篇分词后的文档 # 实际使用时按你的语料结构调整 docs [] with open(wiki_zh_seg.txt, r, encodingutf-8) as f: for i, line in enumerate(f): words line.strip().split() if len(words) 10: # 太短的文档跳过 continue docs.append(TaggedDocument(words, [i])) model Doc2Vec( documentsdocs, vector_size200, window5, min_count5, workers8, dm1, # PV-DM epochs20, # doc2vec 需要更多轮数 negative5, ) model.save(doc2vec_zh.model)TaggedDocument(words, [i])里的[i]是文档的唯一标签用整数索引最省事。注意epochs20doc2vec 比 word2vec 需要更多轮数才能收敛因为段落向量是从零开始学的。len(words) 10是过滤过短文档太短的文档学不出有意义的段落向量。4.3 文档向量的推理与相似文档检索训练完之后新文档怎么得到向量doc2vec 提供了infer_vector。from gensim.models.doc2vec import Doc2Vec model Doc2Vec.load(doc2vec_zh.model) # 对新文档推理向量 new_doc 人工智能 是 计算机科学 的 一个 分支.split() vec model.infer_vector(new_doc, epochs50) # 找最相似的已训练文档 similar model.dv.most_similar([vec], topn5) print(similar)infer_vector的epochs建议设大一点50 左右因为推理时只优化这一个向量轮数少了不稳定。model.dv.most_similar是在文档向量空间里找最近邻返回的是文档标签和相似度。这里有个容易翻车的点推理用的分词方式必须和训练时完全一致训练用 jieba 精确模式推理也得用同一套否则向量对不上。5. 避坑与排查中文语料训练最容易翻车的 5 个地方这一章是我自己踩过的坑也是周围人问得最多的。每条按「现象 → 原因 → 解决」写照着排查基本能覆盖 90% 的问题。现象一相似词结果全是「的」「了」「是」这类虚词。原因是高频词没有被下采样或者min_count设得太低虚词在语料里出现次数极高主导了训练。解决办法是把sample保持在1e-3甚至更低如1e-4同时把min_count提到 5 以上。如果还不行检查分词是不是把标点也切进去了。现象二训练时报内存不足MemoryError。原因是用了list一次性加载全部语料或者workers开太多导致内存翻倍。解决办法是用LineSentence流式读取workers设成 CPU 核数的一半到全部之间别超过核数。语料特别大时可以先用min_count过滤一遍再训练。现象三doc2vec 推理出来的向量每次都不一样。这是正常现象infer_vector有随机初始化。但如果差异特别大说明epochs太小。解决办法是把推理的epochs提到 50 到 100并在推理前调用model.random.seed(42)固定随机种子保证可复现。现象四繁体转简体后出现乱码或丢字。原因是OpenCC配置不对或者文件编码不是 UTF-8。解决办法是全程用encodingutf-8读写OpenCC用t2s标准配置。如果语料里有大量异体字可以在繁转简后再做一次全角转半角。现象五模型文件特别大加载慢。原因是vector_size和词表都很大gensim默认保存了完整的训练状态。解决办法是只保存词向量部分model.wv.save(word2vec.kv)加载时用KeyedVectors.load(word2vec.kv)体积能小一半以上加载也快。注意排查问题时先用一小部分语料比如 10 万行跑通全流程确认没问题再上全量这样能省下大量等待时间。6. 进阶技巧让中文词向量真正能用在业务里跑通基础流程只是起点真正落地时还有几个技巧能让向量质量上一个台阶。这一章讲三个我常用的方法都是能直接加进现有脚本的。第一个是领域语料增量训练。通用维基词向量对专业术语的表示往往不够好比如医疗、法律、金融领域。做法是加载已训练好的模型用领域语料继续训练from gensim.models import Word2Vec from gensim.models.word2vec import LineSentence model Word2Vec.load(word2vec_zh.model) domain_sentences LineSentence(domain_seg.txt) # 增量训练注意 total_examples 要设对 model.build_vocab(domain_sentences, updateTrue) model.train(domain_sentences, total_examplesmodel.corpus_count, epochs5) model.save(word2vec_zh_domain.model)build_vocab(updateTrue)是关键它把领域新词加进词表而不重置已有向量。total_examples要设成领域语料的句子数设错了训练会提前结束或报错。第二个是用向量做文本聚类的验证。把 doc2vec 向量喂给 KMeans看聚类结果是否符合直觉这是检验文档向量质量最直接的方式from sklearn.cluster import KMeans import numpy as np vectors np.array([model.dv[i] for i in range(len(model.dv))]) kmeans KMeans(n_clusters10, random_state42).fit(vectors) print(kmeans.labels_[:20])如果聚类出来的簇在语义上明显混杂说明文档向量质量不够回去检查分词和epochs。第三个是向量归一化后再算相似度。gensim的most_similar默认做了归一化但你自己算余弦相似度时别忘了from numpy.linalg import norm def cosine(a, b): return np.dot(a, b) / (norm(a) * norm(b))不归一化直接点积长度长的向量会占便宜相似度排序就失真了。最后说个我自己的习惯每次训练完我都会固定用一组「探针词」比如「计算机」「经济」「医学」跑一遍相似词把结果存成文本下次换参数时对比。这样调参不是凭感觉而是有据可查。中文词向量这件事语料清洗的功夫占七成训练只占三成把清洗做扎实后面的效果自然就出来了。希望帮到你。本文还有配套的精品资源点击获取