天天看點

利用gensim裡word2vec訓練執行個體——分析三國裡人物關系

前言

萬物皆可Embedding

入坑cs224N後看完第二周和相關論文。覺得word2vec非常有意思,将一段具有上下文關系的短文(實體)詞語學習嵌入到語義空間成為一個向量,然後判斷兩個詞語(實體)的相關性。又發現有造好的輪子gensim,何不先做一些簡單又有意思的實驗,再深入的學習。

本來想爬豆瓣使用者曆史記錄,用word2Vec做一個推薦,但最近進入考期,預習刷網課要緊。先埋個伏筆,以後有時間再弄吧,技多不壓身。于是先利用《三國演義》作為語料,練練手gensim,看看embedding能學到啥有趣的結論。

準備語料

利用request和beautifoup解析工具先從某網站上爬下我國小愛不釋手的曆史小說《三國演義》(複習爬蟲技能)。簡單對文本裡标點符号消去并利用

jieba

分好詞,由于小說近似是文言文,分出詞語效果并不好。

def crawl(url = None):
    """
     從http://www.purepen.com/sgyy/爬下《三國演義》到本地txt檔案
    :param url:
    :return:
    """
    print('Waitting for crawling sentence!')
    url  = 'http://www.purepen.com/sgyy/'
    contents = ''
    for num in range(1,121):
        num_str = str(num)
        if len(num_str) == 1 : num_str = '00'+ num_str
        if len(num_str) == 2 : num_str = '0'+ num_str
        urls = url + num_str + '.htm'
        html = requests.get(urls)
        html.encoding = html.apparent_encoding # 保證中文編碼方式的正确
        soup =  BeautifulSoup(html.text,'lxml')
        title  = soup.find(align = 'center').text
        contents += title
        content = soup.find(face = '宋體').text
        contents += content

    with open('三國演義.txt','w') as f:
        f.write(contents)
        
def segment(document_path= '三國演義.txt'):
    """

    :param document_path:
    :return:
    """
    with open(document_path) as f:
        document = f.read()
        document = re.sub('[()::?“”《》,。!·、\d ]+', ' ', document)  # 去标點
        document_cut = jieba.cut(document)
        result = ' '.join(document_cut)
        with open('segement.txt', 'w') as f2:
            f2.write(result)
        print('Segement Endding')        
           

利用 gensim裡word2vec訓練模型

首先安裝gensim是很容易的,使用

"pip install gensim"

即可。但是需要注意的是gensim對numpy的版本有要求,是以安裝過程中可能會偷偷的更新你的numpy版本。而windows版的numpy直接裝或者更新是有問題的。此時我們需要解除安裝numpy,并重新下載下傳帶mkl的符合gensim版本要求的numpy。

 

在gensim中,word2vec 相關的API都在包gensim.models.word2vec中。和算法有關的參數都在類gensim.models.word2vec.Word2Vec中,建議閱讀官方文檔。

算法需要注意的參數有:

  • sentences: 我們要分析的語料,可以是一個清單,或者從檔案中周遊讀出。後面我們會有從檔案讀出的例子。
  • size: 詞向量的次元,預設值是100。這個次元的取值一般與我們的語料的大小相關,如果是不大的語料,比如小于100M的文本語料,則使用預設值一般就可以了。如果是超大的語料,建議增大次元。
  • window:即詞向量上下文最大距離,這個參數在我們的算法原理篇中标記為c

    ,window越大,則和某一詞較遠的詞也會産生上下文關系。預設值為5。在實際使用中,可以根據實際的需求來動态調整這個window的大小。如果是小語料則這個值可以設的更小。對于一般的語料這個值推薦在[5,10]之間。

  • sg: 即我們的word2vec兩個模型的選擇了。如果是0, 則是CBOW模型,是1則是Skip-Gram模型,預設是0即CBOW模型。
  • hs: 即我們的word2vec兩個解法的選擇了,如果是0, 則是Negative Sampling,是1的話并且負采樣個數negative大于0, 則是Hierarchical Softmax。預設是0即Negative Sampling。
  • negative:即使用Negative Sampling時負采樣的個數,預設是5。推薦在[3,10]之間。這個參數在我們的算法原理篇中标記為neg。
  • cbow_mean: 僅用于CBOW在做投影的時候,為0,則算法中的xw

    為上下文的詞向量之和,為1則為上下文的詞向量的平均值。在我們的原理篇中,是按照詞向量的平均值來描述的。個人比較喜歡用平均值來表示xw,預設值也是1,不推薦修改預設值。

  • min_count:需要計算詞向量的最小詞頻。這個值可以去掉一些很生僻的低頻詞,預設是5。如果是小語料,可以調低這個值。
  • iter: 随機梯度下降法中疊代的最大次數,預設是5。對于大語料,可以增大這個值。
  • alpha: 在随機梯度下降法中疊代的初始步長。算法原理篇中标記為η,預設是0.025。
  • min_alpha: 由于算法支援在疊代的過程中逐漸減小步長,min_alpha給出了最小的疊代步長值。随機梯度下降中每輪的疊代步長可以由iter,alpha, min_alpha一起得出。這部分由于不是word2vec算法的核心内容,是以在原理篇我們沒有提到。對于大語料,需要對alpha, min_alpha,iter一起調參,來選擇合适的三個值。

      

    用預處理好的語料,訓練模型:

sentences = word2vec.LineSentence(sentence_path)
 model = word2vec.Word2Vec(sentences, hs=1,min_count=1,window=3,size=100)
           

儲存模型:

# 儲存模型,以便重用
model.save("test_01.model")
 #不能利用文本編輯器檢視,但是儲存了訓練的全部資訊,可以在讀取後追加訓練可以再次讀取訓練
model.wv.save_word2vec_format('test_01.model.txt',binary=False) 
# 将模型儲存成文本格式,但是儲存時丢失了詞彙樹等部分資訊,不能追加訓練
           

追加訓練:

model = gensim.models.Word2Vec.load('/tmp/mymodel')
model.train(more_sentences)
           

加載模型:

model = gensim.models.Word2Vec.load('/tmp/mymodel')
           

模型的使用 - 詞向量分析人物關系

  • 求與某個詞最相關的詞:

    model.most_similar()

舉例:

我們在根據《三國演義》的語料訓練好模型裡測試一下我們的學習得到向量(word vector)學習了哪些英雄豪傑們之間潛在相關性:

首先測試早生華發(最愛)的

周瑜

:

print('Nearest 周瑜:',model.most_similar('周瑜'))

學習到的結果很滿意:

Nearest 周瑜:
 [('孫策', 0.6876850128173828), ('孔明', 0.6875529289245605), ('司馬懿', 0.6733481287956238), ('孟獲', 0.6705329418182373), ('先主', 0.6662196516990662), ('魯肅', 0.6605409383773804), ('孫權', 0.6458742022514343), ('孫夫人', 0.643887996673584), ('姜維', 0.6326993703842163), ('有人', 0.6321758031845093)]
           

相關性排第一是與周瑜有總角之好,後同為江東雙壁的小霸王

孫策

。他曆史上與

周瑜

不僅從小交好,惺惺相惜,長大後一起平定江東亂世,建立功勳。同時迎娶了三國裡著名一對國色天香的姐妹花

大喬,小喬

,傳為一段佳話!

第二是

即生瑜,何生亮

的諸葛亮的字号:孔明。《三國演義》裡一個勁為了凸顯諸葛亮的神機妙算,狂黑周瑜成一個氣量小的小人。同時赤壁大戰,這兩人代表孫,劉陣營出力最多的将領謀士。故這兩人的聯系必然十分緊密。

第三是

司馬懿

,曹魏後期智力,政治最高的謀士,大都督!

後面的

魯肅

孫權

吳老太

,也是和周瑜關系最親密的人物。

其他學習到的結果還有:

Nearest 劉備:, [('東吳', 0.7638486623764038), ('袁紹', 0.6992679238319397), ('劉表', 0.6835019588470459), ('吳侯', 0.6756551265716553), ('司馬懿', 0.6602287888526917), ('曹', 0.6518967747688293), ('曹操', 0.6457493305206299), ('劉玄德', 0.6447073817253113), ('蜀', 0.6380304098129272), ('諸葛亮', 0.6250388026237488)]
Nearest 曹操: [('袁紹', 0.6900763511657715), ('劉備', 0.6457493901252747), ('孫策', 0.6446478962898254), ('司馬懿', 0.6381756067276001), ('吳侯', 0.6193397641181946), ('孫權', 0.6192417144775391), ('蜀', 0.6191484928131104), ('周瑜', 0.6183933019638062), ('東吳', 0.6114454865455627), ('馬超', 0.5959264039993286)]
Nearest 孫策: [('姜維', 0.6926037073135376), ('周瑜', 0.687684953212738), ('鄧艾', 0.687220573425293), ('孫堅', 0.6793218851089478), ('司馬懿', 0.6556568741798401), ('鐘會', 0.6528347730636597), ('郭淮', 0.6527595520019531), ('孔明自', 0.6470344066619873), ('曹操', 0.6446478962898254), ('王平', 0.6399298906326294)]
Nearest 貂蟬: [('卓', 0.7048295140266418), ('允', 0.6404716968536377), ('身', 0.6323765516281128), ('妾', 0.6265878677368164), ('瑜', 0.6257222890853882), ('吳', 0.6242125034332275), ('父', 0.6216113567352295), ('衆官', 0.6189900636672974), ('後主', 0.6172502636909485), ('幹', 0.6154900789260864)]
Nearest 諸葛亮: [('亮', 0.7160214185714722), ('賢弟', 0.7146532535552979), ('子敬', 0.6765022277832031), ('此人', 0.6603602766990662), ('表曰', 0.6592696905136108), ('既', 0.6532598733901978), ('奈何', 0.6503086090087891), ('大王', 0.6495622992515564), ('吾主', 0.6492528915405273), ('玄德問', 0.6449695825576782)]
           
  • 選出集合中不同類的詞語

    比如:

    print(model.wv.doesnt_match(u"周瑜 魯肅 呂蒙 陸遜 諸葛亮".split()))

周瑜,魯肅,呂蒙,陸遜

都是東吳大獎并曆任大都督,隻有

諸葛亮

是蜀國大丞相,輸出結果也是

諸葛亮

。說明我們向量學習到這層關系。

同樣的還有:

print(model.wv.doesnt_match(u"曹操 劉備 孫權 關羽".split())) #關羽不是主公
print(model.wv.doesnt_match(u"關羽 張飛 趙雲 黃忠 馬超 典韋".split())) #典韋不是蜀國五虎将
print(model.wv.doesnt_match(u"諸葛亮 賈诩  張昭 馬超".split()))#馬超是唯一武将
           

當然也不是所有關系都能被學習到,學習結果的好壞也取決于我們訓練語料的數量和品質。《三國演義》小說才1.8Mb,是以無法學習到更多實體之間詳細相關的關系。

完整代碼

#!/usr/bin/env python
# encoding: utf-8
'''
@author: MrYx
@github: https://github.com/MrYxJ
'''

import jieba
import jieba.analyse
from gensim.models import word2vec
import requests
from bs4 import BeautifulSoup
import re

def crawl(url = None):
    """
     從http://www.purepen.com/sgyy/爬下《三國演義》到本地txt檔案
    :param url:
    :return:
    """
    print('Waitting for crawling sentence!')
    url  = 'http://www.purepen.com/sgyy/'
    contents = ''
    for num in range(1,121):
        num_str = str(num)
        if len(num_str) == 1 : num_str = '00'+ num_str
        if len(num_str) == 2 : num_str = '0'+ num_str
        urls = url + num_str + '.htm'
        html = requests.get(urls)
        html.encoding = html.apparent_encoding # 保證中文編碼方式的正确
        soup =  BeautifulSoup(html.text,'lxml')
        title  = soup.find(align = 'center').text
        contents += title
        content = soup.find(face = '宋體').text
        contents += content

    with open('三國演義.txt','w') as f:
        f.write(contents)


def segment(document_path= '三國演義.txt'):
    """

    :param document_path:
    :return:
    """
    with open(document_path) as f:
        document = f.read()
        document = re.sub('[()::?“”《》,。!·、\d ]+', ' ', document)  # 去标點
        document_cut = jieba.cut(document) # 結巴分詞
        result = ' '.join(document_cut)
        with open('segement.txt', 'w') as f2:
            f2.write(result)
        print('Segement Endding')


def train_model(sentence_path ,model_path):
    sentences = word2vec.LineSentence(sentence_path)
    model = word2vec.Word2Vec(sentences, hs=1,min_count=1,window=3,size=100)
    print('Word2Vec Training Endding!')
    model.save(model_path)

def analyse_wordVector(model_path):
    model =  word2vec.Word2Vec.load(model_path)
    print('Nearest 周瑜:',model.most_similar('周瑜'))
    print('Nearest 劉備:,',model.most_similar(['劉備']))
    print('Nearest 曹操:',model.most_similar(['曹操']))
    print('Nearest 孫策:',model.most_similar(['孫策']))
    print('Nearest 貂蟬:',model.most_similar(['貂蟬']))
    print('Nearest 諸葛亮:', model.most_similar(['諸葛亮']))
    print(model.wv.doesnt_match(u"周瑜 魯肅 呂蒙 陸遜 諸葛亮".split()))
    print(model.wv.doesnt_match(u"曹操 劉備 孫權 關羽".split()))
    print(model.wv.doesnt_match(u"關羽 張飛 趙雲 黃忠 馬超 典韋".split()))
    print(model.wv.doesnt_match(u"諸葛亮 賈诩  張昭 馬超".split()))
    print(model.wv.similarity('周瑜','孫策'))
    print(model.wv.similarity('周瑜','小喬'))
    print(model.wv.similarity('呂布', '貂蟬'))


if __name__ == '__main__':
    crawl()
    segment()
    train_model('segement.txt','model1')
    analyse_wordVector('model1')