動手實踐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等領域相關的原創優質技術文章:

看小編這麼辛苦,關注一個唄:)

相關文章
相關標籤/搜索