Python 结巴分词 + Word2Vec利用维基百科训练词向量
结巴分词是一个跨语言的中文分词器,整体效果还算不错,功能也够用,这里直接用Python了,其他主流语言版本均有提供。
Word2Vec,起源于谷歌的一个项目,在我刚开始接触的时候就关注到了他的神奇,大致是通过深度神经网络把词映射到N维空间,处理成向量之后我们终于可以在自然语言处理上方便的使用它进行一些后续处理。
Python的gensim
库中有word2vec
包,我们使用这个就可以了,接下来我们就对维基百科进行处理,作为训练集去训练。(包地址:http://radimrehurek.com/gensim/models/word2vec.html)
本文参考:http://www.52nlp.cn/中英文维基百科语料上的word2vec实验
处理
使用维基百科的数据很方便,一是Wiki给我们提供了现成的语料库(听说是实时更新的),虽然中文体积不大,但比起自己爬来方便了不少。
如果使用英文那就更棒了,非常适合作为语料库。
当然只是说在通用的情况下,在专业词汇上,经过测试效果比较一般(考虑到专业词库有专业wiki,以及中文词条本身也不太多)。
首先,我们把Wiki处理成Text格式待处理的文本,这一步在本文参考中有现成的代码。
process_wiki_data.py
#!/usr/bin/env python # -*- coding: utf-8 -*- # process_wiki_data.py 用于解析XML,将XML的wiki数据转换为text格式 import logging import os.path import sys from gensim.corpora import WikiCorpus if __name__ == '__main__': program = os.path.basename(sys.argv[0]) logger = logging.getLogger(program) logging.basicConfig(format='%(asctime)s: %(levelname)s: %(message)s') logging.root.setLevel(level=logging.INFO) logger.info("running %s" % ' '.join(sys.argv)) # check and process input arguments if len(sys.argv) < 3: print(globals()['__doc__'] % locals()) sys.exit(1) inp, outp = sys.argv[1:3] space = " " i = 0 output = open(outp, 'w',encoding='utf-8') wiki = WikiCorpus(inp, lemmatize=False, dictionary={}) for text in wiki.get_texts(): output.write(space.join(text) + "\n") i = i + 1 if (i % 10000 == 0): logger.info("Saved " + str(i) + " articles") output.close() logger.info("Finished Saved " + str(i) + " articles")
logger
比print
更规范,过去没有用过相关的,不太会用,其实用起来还是蛮方便的,这里暂时就先不介绍了。
Wiki的处理函数在gensim
库中有,通过处理我们可以发现,最终效果是变成一行一篇文章并且空格分隔一些关键词,去掉了标点符号。
执行:python process_wiki.py zhwiki-latest-pages-articles.xml.bz2 wiki.zh.text
,等待处理结果。
处理中文繁体转简体
Wiki中文语料中包含了很多繁体字,需要转成简体字再进行处理,这里使用到了OpenCC工具进行转换。
(1)安装OpenCC
到以下链接地址下载对应版本的OpenCC,下载的版本是opencc-1.0.1-win32。
https://bintray.com/package/files/byvoid/opencc/OpenCC
另外,资料显示还有python版本的,使用pip install opencc-python进行安装,未实践不做赘述。
(2)使用OpenCC进行繁简转换
进入解压后的opencc的目录(opencc-1.0.1-win32),双击opencc.exe文件。在当前目录打开dos窗口(Shift+鼠标右键->在此处打开命令窗口),输入如下命令行:
opencc -i wiki.zh.txt -o wiki.zh.simp.txt -c t2s.json
则会得到文件wiki.zh.simp.txt,即转成了简体的中文。
(3)结果查看
解压后的txt有900多M,用notepad++无法打开,所以采用python自带的IO进行读取。Python代码如下:
import codecs,sys f = codecs.open(‘wiki.zh.simp.txt‘,‘r‘,encoding="utf8") line = f.readline() print(line)
分词
下一步,分词,原文中用的似乎有些复杂,结巴分词的效果其实已经不错了,而且很好用,这里就用结巴分词处理一下。本身而言结巴分词是不去掉标点的,但是由于上一步帮我们去掉了,所以这里我们比较省力(不然的话原本准备遍历去掉,根据词性标注标点为x
)。
我的Python还是不太6,所以写的代码比较难看OTZ,不过效果是实现了,处理起来比较慢,我觉得readlines
里的参数可以更多一点。
这里下面处理完了之后用map
处理,拼接list并且使用utf-8
编码,此外,保证一行一个文章,空格分隔(这是后续处理函数的规定)。
这里分词没开多线程,不过后来发现瓶颈似乎在读取的IO上。
#!/usr/bin/env python #-*- coding:utf-8 -*- import jieba import jieba.analyse import jieba.posseg as pseg def cut_words(sentence): #print sentence return " ".join(jieba.cut(sentence)).encode('utf-8') f = open("wiki.zh.text",encoding='utf8') target = open("wiki.zh.text.seg", 'wb') # print 'open files' line = f.readlines(100000) while line: curr = [] for oneline in line: #print(oneline) curr.append(oneline) ''' seg_list = jieba.cut_for_search(s) words = pseg.cut(s) for word, flag in words: if flag != 'x': print(word) for x, w in jieba.analyse.extract_tags(s, withWeight=True): print('%s %s' % (x, w)) ''' after_cut = map(cut_words, curr) # print lin, #for words in after_cut: #print words target.writelines(after_cut) # print 'saved 100000 articles' line = f.readlines(100000) f.close() target.close()
训练
最后就能愉快的训练了,训练函数还是参考了原文:
#!/usr/bin/env python # -*- coding: utf-8 -*- # train_word2vec_model.py用于训练模型 import logging import os.path import sys import multiprocessing from gensim.corpora import WikiCorpus from gensim.models import Word2Vec from gensim.models.word2vec import LineSentence if __name__ == '__main__': program = os.path.basename(sys.argv[0]) logger = logging.getLogger(program) logging.basicConfig(format='%(asctime)s: %(levelname)s: %(message)s') logging.root.setLevel(level=logging.INFO) logger.info("running %s" % ' '.join(sys.argv)) # check and process input arguments if len(sys.argv) < 4: print (globals()['__doc__'] % locals()) sys.exit(1) inp, outp1, outp2 = sys.argv[1:4] model = Word2Vec(LineSentence(inp), size=400, window=5, min_count=5, workers=multiprocessing.cpu_count()) # trim unneeded model memory = use(much) less RAM #model.init_sims(replace=True) model.save(outp1) model.save_word2vec_format(outp2, binary=False)
这里用了一个个LineSentence
函数,官方文档:http://radimrehurek.com/gensim/models/word2vec.html
文档这么说:
Simple format: one sentence = one line; words already preprocessed and separated by whitespace.
简单的格式:一句话 = 一行,预处理过并且用空白符分隔。
这里我们一篇文章等于一行。
执行训练:python train_word2vec_model.py wiki.zh.text.jian.seg wiki.zh.text.model wiki.zh.text.vector
,训练速度也还可以。
之后我们就可以根据这个进行Word2Vec相关操作了:
In [1]: import gensim In [2]: model = gensim.models.Word2Vec.load("wiki.zh.text.model") In [3]: model.most_similar(u"足球") Out[3]: [(u'\u8054\u8d5b', 0.6553816199302673), (u'\u7532\u7ea7', 0.6530429720878601), (u'\u7bee\u7403', 0.5967546701431274), (u'\u4ff1\u4e50\u90e8', 0.5872289538383484), (u'\u4e59\u7ea7', 0.5840631723403931), (u'\u8db3\u7403\u961f', 0.5560152530670166), (u'\u4e9a\u8db3\u8054', 0.5308005809783936), (u'allsvenskan', 0.5249762535095215), (u'\u4ee3\u8868\u961f', 0.5214947462081909), (u'\u7532\u7ec4', 0.5177896022796631)] In [4]: result = model.most_similar(u"足球") In [5]: for e in result: print e[0], e[1] ....: 联赛 0.65538161993 甲级 0.653042972088 篮球 0.596754670143 俱乐部 0.587228953838 乙级 0.58406317234 足球队 0.556015253067 亚足联 0.530800580978 allsvenskan 0.52497625351 代表队 0.521494746208 甲组 0.51778960228 In [6]: result = model.most_similar(u"男人") In [7]: for e in result: print e[0], e[1] ....: 女人 0.77537125349 家伙 0.617369174957 妈妈 0.567102909088 漂亮 0.560832381248 잘했어 0.540875017643 谎言 0.538448691368 爸爸 0.53660941124 傻瓜 0.535608053207 예쁘다 0.535151124001 mc刘 0.529670000076 In [8]: result = model.most_similar(u"女人") In [9]: for e in result: print e[0], e[1] ....: 男人 0.77537125349 我的某 0.589010596275 妈妈 0.576344847679 잘했어 0.562340974808 美丽 0.555426716805 爸爸 0.543958246708 新娘 0.543640494347 谎言 0.540272831917 妞儿 0.531066179276 老婆 0.528521537781 In [10]: result = model.most_similar(u"青蛙") In [11]: for e in result: print e[0], e[1] ....: 老鼠 0.559612870216 乌龟 0.489831030369 蜥蜴 0.478990525007 猫 0.46728849411 鳄鱼 0.461885392666 蟾蜍 0.448014199734 猴子 0.436584025621 白雪公主 0.434905380011 蚯蚓 0.433413207531 螃蟹 0.4314712286 In [12]: result = model.most_similar(u"姨夫") In [13]: for e in result: print e[0], e[1] ....: 堂伯 0.583935439587 祖父 0.574735701084 妃所生 0.569327116013 内弟 0.562012672424 早卒 0.558042645454 曕 0.553856015205 胤祯 0.553288519382 陈潜 0.550716996193 愔之 0.550510883331 叔父 0.550032019615 In [14]: result = model.most_similar(u"衣服") In [15]: for e in result: print e[0], e[1] ....: 鞋子 0.686688780785 穿着 0.672499775887 衣物 0.67173999548 大衣 0.667605519295 裤子 0.662670075893 内裤 0.662210345268 裙子 0.659705817699 西装 0.648508131504 洋装 0.647238850594 围裙 0.642895817757 In [16]: result = model.most_similar(u"公安局") In [17]: for e in result: print e[0], e[1] ....: 司法局 0.730189085007 公安厅 0.634275555611 公安 0.612798035145 房管局 0.597343325615 商业局 0.597183346748 军管会 0.59476184845 体育局 0.59283208847 财政局 0.588721752167 戒毒所 0.575558543205 新闻办 0.573395550251 In [18]: result = model.most_similar(u"铁道部") In [19]: for e in result: print e[0], e[1] ....: 盛光祖 0.565509021282 交通部 0.548688530922 批复 0.546967327595 刘志军 0.541010737419 立项 0.517836689949 报送 0.510296344757 计委 0.508456230164 水利部 0.503531932831 国务院 0.503227233887 经贸委 0.50156635046 In [20]: result = model.most_similar(u"清华大学") In [21]: for e in result: print e[0], e[1] ....: 北京大学 0.763922810555 化学系 0.724210739136 物理系 0.694550514221 数学系 0.684280991554 中山大学 0.677202701569 复旦 0.657914161682 师范大学 0.656435549259 哲学系 0.654701948166 生物系 0.654403865337 中文系 0.653147578239 In [22]: result = model.most_similar(u"卫视") In [23]: for e in result: print e[0], e[1] ....: 湖南 0.676812887192 中文台 0.626506924629 収蔵 0.621356606483 黄金档 0.582251906395 cctv 0.536769032478 安徽 0.536752820015 非同凡响 0.534517168999 唱响 0.533438682556 最强音 0.532605051994 金鹰 0.531676828861