中文维基百科词向量Word2vec实战!

上一篇文章详细学习了Word2vec的含义及实现原理,包含两大语言模型:Cbow、Skip-Gram,以及两大快速训练技巧:Negative sampling、哈夫曼树形式查找Target word。这一篇主要讲基于中文维基百科语料库的Word2vec训练,采用了开源的python gensim包

安装gensim包

Google最初开源的Word2vec是用C++写的,后来陆续出现了很多版本,我们采用的是gensim ,gensim已经被集成在了conda中,所以我们可以方便地利用miniconda进行安装。

1
2
source activate py2k  # 激活python2环境,当然python3也可以的,只不过笔者此处用的python2版本
conda install gensim

获取维基百科语料库

下载

2017版百科语料下载地址为:https://dumps.wikimedia.org/zhwiki/latest/zhwiki-latest-pages-articles.xml.bz2。 下载后是一个压缩包,里面的内容是从网上爬取的“标题–摘要–正文”对,以及其他的html、json标志符等。

语料抽取

Wikipedia Extractor是一个开源的语料抽取工具,语料库压缩包不用在本地解压,直接用Wikipedia Extractor脚本执行:

1
2
3
4
$ git clone https://github.com/attardi/wikiextractor.git wikiextractor
$ cd wikiextractor
$ sudo python setup.py install
$ ./WikiExtractor.py -b 1024M -o extracted zhwiki-latest-pages-articles.xml.bz2 #

其中2000M代表抽取后的预料库最大允许size,因为原始语料库size约1.3G,这里用2000M的话相当于把语料全部抽取到一个文本文件“wiki_00”中。extracted代表抽取后语料存放路径。
最终控制台log输出:INFO: Finished 7-process extraction of 986891 articles in 1393.9s (708.0 art/s),代表全部抽取完成共986891篇文章。
抽取后的内容格式为:每篇文章被一对<doc></doc>包起来,而<doc>中的包含的属性有文章id、url和title属性,如<doc id="xxx" url="https://zh.wikipedia.org/xxx" title="xxx">

繁体字转简体

上一步抽取的中文维基百科语料库比较杂乱,既有繁体字也有简体字,这里需要将其统一变为简体字,采用开源的 OpenCC转换器。ubuntu下使用方法如下:

1
2
$ sudo apt-get install opencc
$ opencc -i wiki_00 -o zh_wiki_00 -c zht2zhs.ini

去除标点

去除标点是因为:训练标点符号意义不大,且标点符号出现次数过多,影响一定训练效率。可以通过正则表达式去除。感谢吴良超的学习笔记提供的去标点程序,使用格式为:

1
python script.py input_file output_file

pre1.py源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys
import re
import io

reload(sys)
sys.setdefaultencoding('utf-8')

def pre_process(input_file, output_file):
multi_version = re.compile(ur'-\{.*?(zh-hans|zh-cn):([^;]*?)(;.*?)?\}-')
punctuation = re.compile(u"[-~!@#$%^&*()_+`=\[\]\\\{\}\"|;':,./<>?·!@#¥%……&*()——+【】、;‘:“”,。、《》?「『」』]")
with io.open(output_file, mode = 'w', encoding = 'utf-8') as outfile:
with io.open(input_file, mode = 'r', encoding ='utf-8') as infile:
for line in infile:
line = multi_version.sub(ur'\2', line)
line = punctuation.sub('', line.decode('utf8'))
outfile.write(line)

if __name__ == '__main__':
if len(sys.argv) != 3:
print "Usage: python pre1.py input_file output_file"
sys.exit()
input_file, output_file = sys.argv[1], sys.argv[2]
pre_process(input_file, output_file)

去除标识符及分词

经过上一步处理,每一篇文章被存放在了doc/doc标签。同时肉眼对比抽取后的句子,每一段落的开头跟标题都是重复的,也需要去除。还有前辈们建议把一篇段落的文章放到一行,但词向量关注的window size`一般大小为3、5,感觉也没这个必要。安装jieba分词,pre2.py代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys
import io
import jieba

reload(sys)
sys.setdefaultencoding('utf-8')

jieba.load_userdict("dict.txt") # 如果有的话加载用户自定义词典


def cut_words(input_file, output_file):
count = 0
a = 1 # 标识符
with io.open(output_file, mode = 'w', encoding = 'utf-8') as outfile:
with io.open(input_file, mode = 'r', encoding = 'utf-8') as infile:
for line in infile:
if a == -1:
a = a*(-1)
continue
line = line.strip()
if len(line) < 1:
continue
# 移除字符串首尾的\r \n 空格等字符,此处希望一篇文章的所有内容位于一行,使得训练预料更规整
if line.startswith('doc'):
if line == 'doc':
outfile.write(u'\n')
count += 1
str = "已完成 %d 篇百科文章转换" % (count)
print str
else:
a = -1
continue
else:
for word in jieba.cut(line):
outfile.write(word + ' ')

if __name__ == '__main__':
if len(sys.argv) < 3:
print "Terminal Format: python script.py input_file output_file"
sys.exit()
input_file, output_file = sys.argv[1], sys.argv[2]
cut_words(input_file, output_file)

最终log输出:已完成 986891 篇百科文章转换,代表全部文章转换完毕。

文本去重

NLP常规操作,这么大的数据量,采用利用哈希表检测重复并去除,pre3.py代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/python
# -*- coding: utf-8 -*-
#程序功能是为了完成判断文件中是否有重复句子
#并将重复句子打印出来

import shutil
res_list = set()
f = open('pre2','r')
w = open('pre3', 'w')
k = 1
index = 0
for line in f.readlines():
index = index + 1
if line in res_list:
k = k + 1
print("Repeat", k, "All:", index, "Percent: %.5f%%" % (100*k/index))
else:
res_list.add(line)
w.write(line)

最终log输出:Repeat', 18649, 'All:', 986884, 'Percent: 1.00000%',重复了1%的语料。

用gensim训练词向量

初始训练

解释下参数含义:
multiprocessing:python中的多线程模块,这里这里采用的是8核CPU进行训练,网络比较简单的情况下速度要比GPU快。
LineSentence:将输入文件转为 gensim 内部的 LineSentence 对象,要求输入的文件的格式为每行一篇文章,对于中文每篇文章经过分词处理。
min_count:低于min_count的词频的词不参与训练;sg:0表示CBOW语言模型,1表示Skip-gram模型;size:词向量维度
iter:迭代的epoch次数,这里着重讲下#此处的iter值会被直接设置到百分比进度条中,而不是日常训练神经网络一样epoch1、epoch2…epoch100

train.py代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/python
# -*- coding: utf-8 -*-

import os, sys
import multiprocessing
import gensim
import logging

reload(sys)
sys.setdefaultencoding('utf-8')

def word2vec_train(input_file, output_file):
logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
sentences = gensim.models.word2vec.LineSentence(input_file)
model = gensim.models.Word2Vec(sentences, size=800, window=5, min_count=3, iter=100,sg=0, workers=multiprocessing.cpu_count())
# model.train(sentences, total_examples=model.corpus_count, epochs = 100) 默认参数,也可以指定
model.save(output_file)

if __name__ == '__main__':
if len(sys.argv) < 3:
print "Terminal Format: python script.py infile outfile"
sys.exit()
input_file, output_file = sys.argv[1], sys.argv[2]
word2vec_train(input_file, output_file)

最终log输出:2018-01-22 19:37:52,179 : INFO : PROGRESS: at 100.00% examples, 122591 words/s, in_qsize 16, out_qsize 0
代表完成训练。

  • 实验环境:ubuntu16.04、 CPU:8核
  1. 迭代5次epoch,耗时1.5小时
  2. 相同参数下,迭代100次epcoh,耗时40小时

增量训练

增量训练可以在新的语料库上,基于原有模型进行叠加训练,而不用从头开始。
train_add.py关键部分代码如下:

1
2
3
4
5
6
def word2vec_train(input_file, output_file):
logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
model = gensim.models.Word2Vec.load('word2vec')
more_sentences = gensim.models.word2vec.LineSentence(input_file)
model.build_vocab(more_sentences, update=True)
model.train(more_sentences, total_examples=model.corpus_count, epochs=model.iter)

上面的代码先加载了一个已经训练好的词向量模型,然后再添加新的文章进行训练,同样新增的文章的格式也要满足每行一篇文章,每篇文章的词语通过空格分开的格式。由于前段时间参加Ai Challenge比赛,正好有1000W行处理后的中文数据可以加入训练。

使用词向量模型

直接上代码了,infer.py如下:

1
2
3
4
5
6
7
8
9
10
#encoding=utf-8
import gensim
from gensim.models import word2vec
model=gensim.models.Word2Vec.load('word2vec')
print "模型加载成功!"
print "----词向量维度------"
print len(model[u'男人'])
print "----跟“滋润”相近的词------"
for i in model.most_similar(u"滋润"):
print i[0],i[1]

Refer:
word2vec实战:获取和预处理中文维基百科(Wikipedia)语料库,并训练成word2vec模型
中文维基百科语料库词向量的训练
不可思议的Word2Vec