利用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')

你可能感兴趣的:(机器学习算法笔记,nlp/知识图谱,网络爬虫技术及小工具,python语法糖)