注:CBOW与Skip-gram模型是研究者在NNLM(Neural Network Language Model)和C&W模型的基础上保留其核心部分得到的。
提及docs2vec我们会想到DM(Distributed Memory)和DBOW(Distribute Bag of Words)模型以及其算法。
我们此篇并不会深入这些算法的内容,我们只是在应用,需要的地方我会尽可能的使用白话来说明,而这些算法的核心内容我会在后面的文章中做详细的分析。
注:我所有的代码都是在MAC的python2下面运行的,如果你使用windows或者是python3运行的时候发现报错,那幺很有可能是因为编码的原因
一 我们为什幺需要用到word2vec
我们知道计算机是没有识别字体的能力的,你输入“今天是什幺天气”,它可能不会有任何的反应,还很有可能给你报一个错误,但是计算机的计算能力还是不错的,那幺我们就可以把语言转为数字,让计算机来进行数学运算就可以了,而向量是我们表示语言的不错选项。
在以往的自然处理中经常会把字词使用独热编码(One-Hot Encoder)的方式变为向量。
举个例子来说明一下:假如现在我们有一千个各不相同的中文词,其中包括香蕉、菠萝、水果、河马等等。
而独热编码会制造出一千维度的向量,来表示这些词语:
首先将所有的词语放在一个集合中: [天气, 菠萝, 高原, 电影, 香蕉, 编码, 水果, ……….., 赤道, 太阳, 河马, 快跑, 明星]
注:这个一千维向量中词语的顺序是随机的。
那幺香蕉、菠萝、水果、河马这几个词的向量就可能像下面这样表示
香蕉 [0, 0, 0, 0, 1, 0, 0, ……….., 0, 0, 0, 0, 0]
菠萝 [0, 1, 0, 0, 0, 0, 0, ……….., 0, 0, 0, 0, 0]
水果 [0, 0, 0, 0, 0, 0, 1, ……….., 0, 0, 0, 0, 0]
河马 [0, 0, 0, 0, 0, 0, 0, ……….., 0, 0, 1, 0, 0]
经过上面这种转换每一个词都使用了向量的方法进行表示。那幺在理想的情况下我们只需要计算每两个向量之间的距离,就可以得到两个词语之间的关系了,距离较近的话就说明这两个词的相关性较强。
但这只是理想情况,这里存在两个难点:
第一就是维度灾难:一千维的向量计算这个时间复杂度还是很高的
第二就是一千维的向量顺序是随机生成的,那幺词语之间可能存在的关联就看不出来。
此时就轮到word2vec出场了,word2vec会将One-Hot Encoder转化为低纬度的连续值,也就是稠密向量,并且将其中意思接近的词映射到向量中相近的位置。
注:这里具体的算法我们会在后面的文章中详细介绍。
处理以后的香蕉、菠萝、水果、河马向量:
香蕉 [0.2, 0.6]
菠萝 [0.3, 0.4]
水果 [0.6, 0.6]
河马 [0.9, 0.2]
从上面的坐标系中我们可以看到词语之间的距离,距离越近的词语其相关性也就越高。
注:word2vec处理后的词语一般不会是二维的,此处只是方便说明。
二 开始训练我们的模型
通过上面我们知道了,只要我们使用算法创建出计算机能够识别的向量,那幺计算机就会帮助我们计算词语的相似度。
为了创建这样的向量,也就是word2vec模型,我们需要一些语料,在此我们使用的是维基百科中的语料,大小是1.72G。
此处是下载地址: dumps.wikimedia.org/zhwiki/late…
因为维基百科上面很多的中文网页都是繁体字的,因此我们首先要将语料中的繁体字变为简体字,同时将这些语料进行分词处理。
2.1 将获取的语料进行处理
在此我们是使用gensim库来帮助我们。
def my_function(): space = ' ' i = 0 l = [] zhwiki_name = './data/zhwiki-latest-pages-articles.xml.bz2' f = open('./data/reduce_zhiwiki.txt', 'w') # 读取xml文件中的语料 wiki = WikiCorpus(zhwiki_name, lemmatize=False, dictionary={}) for text in wiki.get_texts(): for temp_sentence in text: # 将语料中的繁体字转化为中文 temp_sentence = Converter('zh-hans').convert(temp_sentence) # 使用jieba进行分词 seg_list = list(jieba.cut(temp_sentence)) for temp_term in seg_list: l.append(temp_term) f.write(space.join(l) + '\n') l = [] i = i + 1 if (i %200 == 0): print('Saved ' + str(i) + ' articles') f.close()
经过上面的处理以后我们就得到了简体字的分词文件了。
2.2 向量化训练
经过上面的操作我们就得到了处理好的语料,接下来我们使用gensim中的Word2Vec帮助我们完成词到向量的转化。
# -*- coding: utf-8 -*- from gensim.models import Word2Vec from gensim.models.word2vec import LineSentence import logging logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO) def my_function(): wiki_news = open('./data/reduce_zhiwiki.txt', 'r') # Word2Vec第一个参数代表要训练的语料 # sg=0 表示使用CBOW模型进行训练 # size 表示特征向量的维度,默认为100。大的size需要更多的训练数据,但是效果会更好. 推荐值为几十到几百。 # window 表示当前词与预测词在一个句子中的最大距离是多少 # min_count 可以对字典做截断. 词频少于min_count次数的单词会被丢弃掉, 默认值为5 # workers 表示训练的并行数 model = Word2Vec(LineSentence(wiki_news), sg=0,size=192, window=5, min_count=5, workers=9) model.save('zhiwiki_news.word2vec') if __name__ == '__main__': my_function()
2.3 测试最终的效果
现在我们得到了一个向量的模型,这个时候我们可以看一下实际效果。
#coding=utf-8 import gensim import sys reload(sys) sys.setdefaultencoding('utf8') def my_function(): model = gensim.models.Word2Vec.load('./data/zhiwiki_news.word2vec') print(model.similarity(u'香蕉',u'菠萝')) # 相似度为0.52 print(model.similarity(u'香蕉',u'水果')) # 相似度为0.53 word = '河马' if word in model.wv.index2word: for item in model.most_similar(unicode(word, "utf-8")): print item[0] # 长颈鹿 狒狒 犰狳 斑马 亚洲象 猫科 黑猩猩 驯鹿 仓鼠 豹猫 if __name__ == '__main__': my_function()
从上面最后的结果我们可以看到,‘香蕉’和‘菠萝’的相似度是0.52,‘香蕉’和‘水果’的相似度是0.53,可能这个最终的显示效果并不符合你的预期,在你的认知中你可能会认为前者的相似度应该大于后者才对,而最终是这样一个结果的可能原因在于,我们使用的维基百科语料中‘香蕉’和‘水果’相邻的情况比较多。
那幺现在的你是否早已经迫不及待的想要亲自动手实践一下了呢?
这个是前面做处理训练以及测试所用到的代码,因为码云的上传文件大小限制,所以你需要手动下载维基百科的语料,并且将下载好的语料放在data文件夹下面。
你可以将码云上面的代码下载到本地。
第一步运行data_pre_process.py文件
第二步运行training.py文件
第三步运行test.py文件
其中第一步和第二步和花费一些时间。
三 word2vec应用于文章
通过上面的操作你现在已经可以获得词与词之间的相似关系了,但是在平常的需求中我们可能还会遇到整篇文档的相似度问题。那幺这个时候我们可以先抽取整篇文章的关键词,接着将关键词向量化,然后将得到的各个词向量相加,最后得到的一个词向量总和代表文章的向量化表示,利用这个总的向量计算文章相似度。
3.1 文章关键词提取
关键词提取我们使用jieba分词进行提取
# -*- coding: utf-8 -*- import jieba.posseg as pseg from jieba import analyse def keyword_extract(data, file_name): tfidf = analyse.extract_tags keywords = tfidf(data) return keywords def getKeywords(docpath, savepath): with open(docpath, 'r') as docf, open(savepath, 'w') as outf: for data in docf: data = data[:len(data)-1] keywords = keyword_extract(data, savepath) for word in keywords: outf.write(word + ' ') outf.write('\n')
上面两个函数的作用就是进行关键词的提取。
提取完关键词以后我们需要将关键词向量化。
3.2 关键词向量化
# -*- coding: utf-8 -*- import codecs import numpy import gensim import numpy as np from keyword_extract import * import sys reload(sys) sys.setdefaultencoding('utf8') wordvec_size=192 def get_char_pos(string,char): chPos=[] try: chPos=list(((pos) for pos,val in enumerate(string) if(val == char))) except: pass return chPos def word2vec(file_name,model): with codecs.open(file_name, 'r') as f: # 初始化一个192维的向量 word_vec_all = numpy.zeros(wordvec_size) for data in f: # 判断模型是否包含词语 space_pos = get_char_pos(data, ' ') # 获取关键词每行的第一个词 try: first_word=data[0:space_pos[0]] except: pass # 判断模型中是否存在first_word,为真时将其添加到word_vec_all if model.__contains__(unicode(first_word, "utf-8")): word_vec_all= word_vec_all+model[unicode(first_word, "utf-8")] # 遍历space_pos for i in range(len(space_pos) - 1): word = data[space_pos[i]:space_pos[i + 1]] if model.__contains__(unicode(word, "utf-8")): word_vec_all = word_vec_all+model[unicode(word, "utf-8")] return word_vec_all
上面两个函数的作用就是将得到的关键词进行向量化,值得注意的是,因为我们的语料比较小,因此我们在向量化的过程中会判断关键词是否存在于语料中。
因为我使用的是python2,因此你在上面的代码中可以看到unicode函数,如果你使用的python3那幺unicode函数的地方可能需要修改一下。
3.3 相似度计算
通过上面的两步操作我们已经获得了一个能够代表文章的词向量,接下来就是使用这个向量来计算文本的相似度了。
def simlarityCalu(vector1,vector2): vector1Mod=np.sqrt(vector1.dot(vector1)) vector2Mod=np.sqrt(vector2.dot(vector2)) if vector2Mod!=0 and vector1Mod!=0: simlarity=(vector1.dot(vector2))/(vector1Mod*vector2Mod) else: simlarity=0 return simlarity if __name__ == '__main__': model = gensim.models.Word2Vec.load('data/zhiwiki_news.word2vec') p1 = './data/P1.txt' p2 = './data/P2.txt' p1_keywords = './data/P1_keywords.txt' p2_keywords = './data/P2_keywords.txt' getKeywords(p1, p1_keywords) getKeywords(p2, p2_keywords) p1_vec=word2vec(p1_keywords,model) p2_vec=word2vec(p2_keywords,model) print(simlarityCalu(p1_vec,p2_vec)) # 0.9880877789981191
这个函数就是计算相似度的一个函数。 从上面的结果来看,两个文章的相似度还是很高的,你可以自己动手尝试一下。
为了你更方便的动手实践,我也将刚刚提到的这些代码放在了码云上面,因为都是使用的同一个语料进行的训练,因此你可以将你在2.1到2.3训练出的模型放在data下面,这样你就不需要重复进行训练。
注:此地址下面的代码也包含了2.1到2.3中的代码
如果你已经将训练好的模型放在了data下面,那幺你直接可以运行word2vec_sim.py看到结果。
在此怕某些人疑惑,说明一下正常程序中应该存在的文件。
四 不可不说的doc2vec
你通过上面动手实践以后就可以计算单词于单词,文章与文章之间的相似度,但是你以为只有word2vec一种方式计算文章与文章之间的相似度吗?
不是的,doc2vec也可以计算文章与文章之间的相似度,并且doc2vec会关注于文章词语之间的顺序而且还会综合上下文的语序信息。
举个例子:武松打死了老虎,这句话在分词的时候会被分成“武松”、“打死”、“老虎”(了是停用词,被去掉),word2vec在计算的时候会按照这三个词的向量求平均,但是这个语意信息没有保留下来,你不知道是武松打死的老虎,还是老虎打死的武松。
值得你注意的是,在分析文章方面并不是doc2vec一定优于word2vec,这个需要结合你具体的业务场景来使用。
#!/usr/bin/env python # -*- coding: utf-8 -*- import gensim.models as g from gensim.corpora import WikiCorpus import logging from langconv import * #enable logging logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO) docvec_size=192 class TaggedWikiDocument(object): def __init__(self, wiki): self.wiki = wiki self.wiki.metadata = True def __iter__(self): import jieba for content, (page_id, title) in self.wiki.get_texts(): yield g.doc2vec.LabeledSentence(words=[w for c in content for w in jieba.cut(Converter('zh-hans').convert(c))], tags=[title]) def my_function(): zhwiki_name = './data/zhwiki-latest-pages-articles.xml.bz2' wiki = WikiCorpus(zhwiki_name, lemmatize=False, dictionary={}) documents = TaggedWikiDocument(wiki) model = g.Doc2Vec(documents, dm=0, dbow_words=1, size=docvec_size, window=8, min_count=19, iter=5, workers=8) model.save('data/zhiwiki_news.doc2vec') if __name__ == '__main__': my_function()
上面的代码作用是用来训练doc2vec模型的,与word2vec类似,该训练主要分为数据预处理和段落向量训练两个步骤,这里我们使用TaggedWikiDocument函数预处理我们的维基百科预料,所不同的是这里不再是将每个维基百科语料的文档进行分词,而是直接将转换后的简体文本保留,当你训练完成以后你的data文件下面应该是这样的。
我在训练的时候一共花费了15个小时,如果你不想等待这幺久的话,可以在下面的链接中下载我已经训练好的模型文件。
链接: share.weiyun.com/5HekNcq 密码:pd9i3n
同样的我也将上面的代码放在了码云上。
如果你想要自己动手训练模型的话,只需要运行train_model.py文件,然后运行doc2vec_sim.py就可以看到结果了。
如果你已经在微云下载了我提供的模型文件,你只需要将模型文件放在data文件夹下面,直接运行doc2vec_sim.py就可以看到结果了。
五 你需要新模型
上面所使用的模型是使用维基百科进行训练得到的,你可能感觉不是特别好,不要慌张,我这里给你提供一个由新闻,百度百科,小说训练得到的64维模型,这个模型是1.5G,我放在了微云上,方便你下载使用。
链接: share.weiyun.com/507zMyF 密码:sk5g4y
#!/usr/bin/python #-*-coding:utf-8 -*- import gensim import jieba import numpy as np from scipy.linalg import norm import re import sys reload(sys) sys.setdefaultencoding('utf8') model_file = './data/model.bin' model = gensim.models.KeyedVectors.load_word2vec_format(model_file, binary=True) if __name__ == '__main__': print(model.similarity(u'香蕉',u'菠萝')) # 相似度为0.90 print(model.similarity(u'香蕉',u'水果')) # 相似度为0.79 word = '河马' if word in model.wv.index2word: for item in model.most_similar(unicode(word, "utf-8")): print item[0] # 猩猩 海象 袋鼠 狒狒 浣熊 长颈鹿 大猩猩 乌贼 鲸鱼 松鼠
你需要将微云上面的模型文件下载下来,然后放在data下面就可以了。
因为这个的代码比较简单因此就不将代码放在码云了。
上面的代码就是使用新的模型得到的结果,我们可以看到这个结果相比于维基百科还是有一定的提升的,这个就是预料大带来的直观好处。
六 这只是开始
在上面你可能已经学会了使用语料生成word2vec和doc2vec模型,并且使用一些词语和文章验证过你的生成结果,但这只是刚刚开始,在以后的文章中我们会一起学习word2vec和doc2vec背后所使用的数学算法和思想,以及NLP其余方面的知识。
由于本人的认知能力以及表达能力有限,如果文章中有哪些说明不到位或者解释有误的情况,请你及时指出,期待与你的共同进步。
Be First to Comment