前言
萬物皆可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')