☰
中文维基百科语料实战:word2vec与doc2vec一次跑通
2026/10/8 4:56:24 网站建设 项目流程

简介:这份资源提供中文word2vec与doc2vec预训练模型,基于维基百科与百度百科语料训练,面向自然语言处理初学者及需要快速验证中文语义任务的开发者。模型可用于语义相似度计算、关键词提取、文本分类等基础场景,帮助使用者跳过耗时的语料采集与训练环节,直接进入应用与调优阶段。压缩包共7个文件,以txt说明文档、Python脚本、md项目说明及license、gitignore等配置为主,整体约3KB,体量轻便,便于快速浏览与二次开发。其中Python脚本承担模型加载与处理逻辑,说明文档交代数据来源与使用方式,依赖清单则方便复现运行环境。目前已有81人学习下载,适合作为中文文本表征的入门参考,也可为后续更复杂的语义分析任务提供基础工具与排错思路。

1. 中文 word2vec 与 doc2vec:用维基百科语料把词向量和文档向量一次跑通

很多人第一次接触词向量,是拿英文的text8或者enwik9跑了个 demo,king - 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}/**/*", recursive=True): with open(path, 'r', encoding='utf-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', encoding='utf-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', encoding='utf-8') as f, \ open('wiki_zh_seg.txt', 'w', encoding='utf-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( sentences=sentences, vector_size=200, # 词向量维度 window=5, # 上下文窗口 min_count=5, # 低于此频次的词丢弃 workers=8, # 并行线程数 sg=1, # 1=skip-gram, 0=CBOW epochs=5, # 训练轮数 negative=5, # 负采样个数 sample=1e-3, # 高频词下采样阈值 ) model.save('word2vec_zh.model') print('词表大小:', len(model.wv))

LineSentence是内存友好的读取方式,它不会一次性把整个文件读进内存,而是按行流式喂给模型,几个 GB 的语料也能跑。vector_size=200是中文的常用值,100 偏小、300 偏大,200 在效果和存储之间比较平衡。window=5是通用默认,如果你做的是短文本相似度,可以降到 3;做主题相关任务可以升到 8。

min_count=5是过滤低频词,中文维基里大量只出现一两次的人名、地名,留着只会增加噪声。sg=1用 skip-gram,它对低频词更友好,代价是训练慢一些;如果语料特别大、追求速度,可以改sg=0用 CBOW。negative=5和sample=1e-3是负采样和高频词下采样,这两个参数一般不用动,默认值就是经过大量实践验证的。

3.2 关键参数怎么调:一张对照表

参数不是拍脑袋定的,下面这张表是我在不同任务里反复试出来的经验值,可以直接对照你的场景改。

参数常用值调大后的影响适用场景
vector_size200表达力强但存储和计算上升通用;300 用于大语料
window5捕获更远语义,但主题漂移短文本用 3,主题任务用 8
min_count5词表变小,低频词丢失语料大用 10,语料小用 3
sg1skip-gram 慢但低频词好通用推荐 1
epochs5轮数多易过拟合语料大 3~5,语料小 10
negative5负采样多,训练慢一般不动

调参的核心逻辑是:语料越大,参数可以越「宽松」;语料越小,越要收紧min_count和epochs。我见过有人拿几十 MB 的语料跑vector_size=300、epochs=20,结果向量过拟合,相似词全是高频虚词,这就是参数和语料规模不匹配。

3.3 训练完怎么验证:相似词、类比、可视化

模型存下来只是第一步,得验证它到底学到了什么。最直接的方法是看相似词。

from gensim.models import Word2Vec model = Word2Vec.load('word2vec_zh.model') # 看「计算机」的相似词 for word, score in model.wv.most_similar('计算机', topn=10): print(word, round(score, 3)) # 类比任务:国王 - 男人 + 女人 ≈ ? result = model.wv.most_similar(positive=['国王', '女人'], negative=['男人'], topn=3) print(result)

most_similar返回的是余弦相似度最高的词。如果「计算机」的邻居里出现「电脑」「软件」「程序」这类词,说明向量质量不错;如果全是「的」「了」这种虚词,那基本是语料清洗或min_count出了问题。类比任务国王 - 男人 + 女人期望得到「王后」这类词,中文上不一定每次都准,但能大致反映语义空间的线性结构。

提示:验证时优先看中频词的相似结果,高频词(如「中国」)和极低频词(只出现几次的)都不能代表整体质量。

4. 训练 doc2vec:把整篇文档压成一个向量

word2vec 给的是词向量,但很多任务要的是「一篇文档一个向量」,比如新闻聚类、相似文章推荐、文档检索。doc2vec 就是干这个的,它在 word2vec 的基础上多学一个「段落向量」,让每篇文档也有自己的表示。这一章讲清楚它的两种训练模式和落地细节。

4.1 PV-DM 与 PV-DBOW:两种模式怎么选

doc2vec 有两种训练模式。PV-DM(分布式记忆)在预测词的时候,同时用上下文词和段落向量,效果通常更好,但训练慢;PV-DBOW(分布式词袋)只用段落向量预测句中的随机词,训练快,对短文档更友好。

在gensim里,dm=1是 PV-DM,dm=0是 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', encoding='utf-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( documents=docs, vector_size=200, window=5, min_count=5, workers=8, dm=1, # PV-DM epochs=20, # doc2vec 需要更多轮数 negative=5, ) model.save('doc2vec_zh.model')

TaggedDocument(words, [i])里的[i]是文档的唯一标签,用整数索引最省事。注意epochs=20,doc2vec 比 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, epochs=50) # 找最相似的已训练文档 similar = model.dv.most_similar([vec], topn=5) 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。解决办法是全程用encoding='utf-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, update=True) model.train(domain_sentences, total_examples=model.corpus_count, epochs=5) model.save('word2vec_zh_domain.model')

build_vocab(update=True)是关键,它把领域新词加进词表而不重置已有向量。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_clusters=10, random_state=42).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))

不归一化直接点积,长度长的向量会占便宜,相似度排序就失真了。

最后说个我自己的习惯:每次训练完,我都会固定用一组「探针词」(比如「计算机」「经济」「医学」)跑一遍相似词,把结果存成文本,下次换参数时对比。这样调参不是凭感觉,而是有据可查。中文词向量这件事,语料清洗的功夫占七成,训练只占三成,把清洗做扎实,后面的效果自然就出来了。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询