动手实践word2vec和doc2vec模型

咱们在处理文本的时候有可能会遇到文本类似度计算或者找出文本近似表达的诉求,本篇文章是从实际操做的角度出发,手动训练出word2vec和doc2vec模型,来计算文本的类似度前端

当咱们说起word2vec的时候,可能不少人都会联想到CBOW(Continuous Bag of-Words)、Skip-gram模型以及其算法。

注:CBOW与Skip-gram模型是研究者在NNLM(Neural Network Language Model)和C&W模型的基础上保留其核心部分获得的。
python

说起docs2vec咱们会想到DM(Distributed Memory)和DBOW(Distribute Bag of Words)模型以及其算法。

咱们此篇并不会深刻这些算法的内容,咱们只是在应用,须要的地方我会尽量的使用白话来讲明,而这些算法的核心内容我会在后面的文章中作详细的分析。

注:我全部的代码都是在MAC的python2下面运行的,若是你使用windows或者是python3运行的时候发现报错,那么颇有多是由于编码的缘由
git

一 咱们为何须要用到word2vec

咱们知道计算机是没有识别字体的能力的,你输入“今天是什么天气”,它可能不会有任何的反应,还颇有可能给你报一个错误,可是计算机的计算能力仍是不错的,那么咱们就能够把语言转为数字,让计算机来进行数学运算就能够了,而向量是咱们表示语言的不错选项。算法

在以往的天然处理中常常会把字词使用独热编码(One-Hot Encoder)的方式变为向量。windows

举个例子来讲明一下:假如如今咱们有一千个各不相同的中文词,其中包括香蕉、菠萝、水果、河马等等。微信

而独热编码会制造出一千维度的向量,来表示这些词语:app

首先将全部的词语放在一个集合中: [天气, 菠萝, 高原, 电影, 香蕉, 编码, 水果, ..........., 赤道, 太阳, 河马, 快跑, 明星]函数

注:这个一千维向量中词语的顺序是随机的。
学习

那么香蕉、菠萝、水果、河马这几个词的向量就可能像下面这样表示测试

  • 香蕉 [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,可能这个最终的显示效果并不符合你的预期,在你的认知中你可能会认为前者的类似度应该大于后者才对,而最终是这样一个结果的可能缘由在于,咱们使用的维基百科语料中‘香蕉’和‘水果’相邻的状况比较多。

那么如今的你是否早已经火烧眉毛的想要亲自动手实践一下了呢?

gitee.com/wangtao_it_…

这个是前面作处理训练以及测试所用到的代码,由于码云的上传文件大小限制,因此你须要手动下载维基百科的语料,而且将下载好的语料放在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下面,这样你就不须要重复进行训练。

gitee.com/wangtao_it_…

注:此地址下面的代码也包含了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

一样的我也将上面的代码放在了码云上。

gitee.com/wangtao_it_…

若是你想要本身动手训练模型的话,只须要运行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其他方面的知识。

因为本人的认知能力以及表达能力有限,若是文章中有哪些说明不到位或者解释有误的状况,请你及时指出,期待与你的共同进步。

欢迎关注"腾讯DeepOcean"微信公众号,每周为你推送前端、人工智能、SEO/ASO等领域相关的原创优质技术文章:

看小编这么辛苦,关注一个呗:)

相关文章
相关标签/搜索