美文网首页PythonPython之路
python+nlp+Gephi 分析电视剧【人民的名义】

python+nlp+Gephi 分析电视剧【人民的名义】

作者: wong小尧 | 来源:发表于2018-01-16 12:31 被阅读588次

    最近和舍友看完了去年大火的电视剧【人民的名义】,看完觉得里面的人物关系很有意思,决定对其分析分析,也顺便测试一下早前使用过的一些模型(例如word2vec)效果是否能达到预期。

    1.获取数据

    进行分析之前首先需要获取剧情的文本,因为没有看过小说,为了尽量接近自己和大部分人所熟悉的剧情,这里爬取从百度百科上的每一集的剧情,针对已更新的剧情文本进行分析。利用python的urllib2(python3.3后改为urllib.request)和BeautifulSoup包可以很快的爬下剧情文本,保存为rmdmy.txt文档,顺便将出现的人物名字也一起爬下来,后面进行预处理和分析中涉及到的分词、实体属性对齐和社交网络分析等都将会用到。

    # -*- coding: utf-8 -*-
    """
    @author: wangyao
    """
    #改变默认工作路径
    import os 
    os.chdir(r"C:\Users\wangyao\Desktop\人民的名义")
    
    ##爬取百度百科剧情
    import urllib.request
    from bs4 import BeautifulSoup
    import re
    import pandas as pd
    url = "https://baike.baidu.com/item/%E4%BA%BA%E6%B0%91%E7%9A%84%E5%90%8D%E4%B9%89/17545218"
    
    import sys
    import importlib
    importlib.reload(sys)
    
    response = urllib.request.urlopen(url)
    con = response.read()
    #使用beautifulsoup中的html解析器
    cont = BeautifulSoup(con,"html.parser")
    content = cont.find_all('ul',{'id':'dramaSerialList'})
    content = str(content)
    ##去掉HTML标签
    content1 = re.sub(r'<[^>]+>','',content) 
    f = open('rmdmy.txt','w',encoding= 'utf-8') #直接用open打开会报错,需要指定编码方式
    f.write(content1)
    f.close()
    
    #爬取名字
    f = open('rmdmy_name.txt','a',encoding= 'utf-8')
    name_content = cont.find_all("dl",attrs={"class": "info"})
    for i in name_content:
        name_d = i.get_text().strip().split(u'\n')[0]
        name = name_d.split(u'\xa0')[2]
        #加decode()byte和str才能相加
        f.write(name.encode('utf-8').decode()+'\n')
    
    f.close()
    

    文本文件如下所示:


    rmdmy.txt rmdmy_name.txt

    2.文本预处理

    将剧情爬下来后需要对文本进行预处理,主要包括分句、分词、去掉一些特殊符号和停用词、实体对齐和属性对齐等。如果一个人可能在剧中有不同的名字,这时候就需要进行统一。为了尽量正确的切分一些固定名称,需要导入自定义词典,主要包含一些人名、地名和组织名称等(例如这里需要加入侯亮平,汉东省,汉东大学,山水集团,大风厂等等)。此外,在提取文本特征时需要去掉一些停用词,以提高分析的准确度。经过一系列处理后得到比较干净的文本分词结果,然后就可以在此基础上进行深入的分析。

    #文本预处理
    import jieba
    jieba.load_userdict('rmdmy_dict.txt')#自定义词典
    stopword = [line.strip() for line in open('StopwordsCN.txt',encoding= 'utf-8').readlines()] #简体中文停用词
    
    fr = open('rmdmy.txt','r',encoding= 'utf-8')
    con = [fr.readlines()]
    '''
    分词,并去掉特殊字符、词语
    '''
    fw = open('rmdmy_content.txt','w',encoding= 'utf-8')
    for i in con[0]:
        #if len(i.decode('utf-8'))<=10:
        if len(i)<=10:
            pass
        else:
            w1 = i.split("。")#按句号分句
            for j in w1:
                w2 = re.sub(r',|。|?|:|“|”|!','',j.strip())#去掉特殊字符
                #w1 = re.sub(name1,name2,w1) #实体对齐
                w3 = list(jieba.cut(w2))#分词
                w4 = [w for w in w3 if w not in stopword]#去掉停用词
                outstr = ''
                for word in w4:
                    outstr +=word
                    outstr +=' '
                fw.write(outstr.strip().encode('utf-8').decode())
                fw.write('\n')
    fw.close()
    

    预处理结果:

    rmdmy_content.txt

    3.人物出现频次和社交网络关系

    先看一下剧中出场次数较多的关键人物有哪些,根据之前爬下来的名字列表,统计其在文本中出现的次数,通过matplotlib包画出出现次数最多的10个关键人物如图所示,可以发现侯亮平出现次数最多,共483次,其次李达康出现了226次,再次是高育良出现了211次,祁同伟202次。整部剧穿插的几大事件都在围绕着这四个人展开,而沙瑞金虽然也是贯穿全剧的重要人物,出场次数也很多(148次),但是大部分事件里都是出来打酱油的,所以次数还低于大风厂事件的蔡成功。

    #人物出场次数统计
    import numpy as np
    import scipy as sp
    import matplotlib.pyplot as plt
    
    import matplotlib.font_manager as fm
    font_yahei_consolas = fm.FontProperties(fname = 'simsun.ttc') 
    #引入字体,否则文字显示不出来
    
    #python 字符串转列表 list 出现\ufeff的解决方法(网上)
    ##文件内容 lisi
    #lock = open("lock_info.txt", "r+",encoding="utf-8")
    #lock_line = lock.readline()
    #lock_list = lock_line.split(",")
    #print(lock_list)
    # 
    #y = lock_line.encode('utf-8').decode('utf-8-sig')
    #print(y)
    # 
    ##打印结果如下
    #['\ufefflisi']
    #lisi
    #自己测试,在notepad++上把编码从UTF-8编码模式改成UTF-8无BOM编码模式,ufeeff就会消失
    
    
    with open('rmdmy_dict.txt',encoding= 'utf-8') as f1:
        data1 = f1.readlines()
    with open('rmdmy_content.txt',encoding= 'utf-8') as f2:
        data2 = f2.read()
        
    #匹配词典里的名字和剧本内容里的该名字出现的次数
    count = []
    for name in data1:
        count.append([name.strip(),data2.count(name.strip())])
    count1 = []
    for i in count:
        if i not in count1:
            count1.append(i)
    count = count1
    
    count.sort(key = lambda x:x[1])
    ay,ax = plt.subplots()
    numbers = [x[1] for x in count[-10:]]
    names = [x[0] for x in count[-10:]]
    ax.barh(range(10),numbers,color=['peru','coral'],align = 'center')
    ax.set_title('人物出场次数',fontsize = 14,fontproperties = font_yahei_consolas)
    ax.set_yticks(range(10))
    ax.set_yticklabels(names,fontsize = 14,fontproperties = font_yahei_consolas)
    plt.show()
    

    人物出场频次条形图:


    人物出场次数

    再来看看剧中人物的社交关系情况,采用以句为单位来进行分析(有时候也以段落为单位来识别人物关系,但采集的文本每集只有一个段落,所以不适用),即如果两个人同时出现在一句话中,那说明他们之间肯定有某种联系。因此可以得到他们的社交网络关系。通过求得的共现矩阵,使用Gephi画出下面的社交网络关系图,图中边的粗细代表关系的密切程度,边越粗表示两人的关系越密切,而名字的大小可以表示为该人的社交人脉强弱情况。

    #匹配词典里的名字和剧本内容里的该名字出现的次数
    count = []
    for name in data1:
        count.append([name.strip(),data2.count(name.strip())])
    count1 = []
    for i in count:
        if i not in count1:
            count1.append(i)
    count = count1
    
    count.sort(key = lambda x:x[1])
    ay,ax = plt.subplots()
    numbers = [x[1] for x in count[-10:]]
    names = [x[0] for x in count[-10:]]
    ax.barh(range(10),numbers,color=['peru','coral'],align = 'center')
    ax.set_title('人物出场次数',fontsize = 14,fontproperties = font_yahei_consolas)
    ax.set_yticks(range(10))
    ax.set_yticklabels(names,fontsize = 14,fontproperties = font_yahei_consolas)
    
    
    #社交网络关系(共现矩阵)
    f2 = open('rmdmy_content.txt','r',encoding= 'utf-8')
    word = f2.readlines()
    name = data1
    #name = data1[1:]
    #总人数
    wordcount = len(name) 
    
    #初始化128*128值全为0的共现矩阵
    cormatrix = [[0 for col in range(wordcount)] for row in range(wordcount)] 
    #遍历矩阵行和列  
    for colindex in range(wordcount):
        for rowindex in range(wordcount):
            cornum = 0
            #如果两个人名字在同一句话里出现,那么共现矩阵中两个人对应的值加1
            for originline in word:
                if name[colindex].strip() in originline and name[rowindex].strip() in originline:
                    cornum += 1
            cormatrix[colindex][rowindex] = cornum
    
    cor_matrix = np.matrix(cormatrix)
    for i in range(len(name)):
        cor_matrix[i,i] = 0
    social_cor_matrix = pd.DataFrame(cor_matrix, index = name,columns = name)
    #把共现矩阵存进excel
    social_cor_matrix.to_csv('social_cor_matrix.csv')
    
    social_contact = pd.DataFrame(columns = ['name1','name2','frequency'])
    #共现频率
    for i in range(0,len(name)):
        for j in range(0,len(name)):
            if i<j and cormatrix[i][j] > 0:
                social_contact.loc[len(social_contact),'name1'] = name[i]
                social_contact.loc[len(social_contact)-1,'name2'] = name[j]
                social_contact.loc[len(social_contact)-1,'frequency'] = cormatrix[i][j]
    
    social_contact.to_excel('social_contact.xlsx',index = False)
    

    社交情况:


    社交网络

    4.走进‘大风厂’事件

    接下来重点探索一下我比较感兴趣的“大风厂”事件,先通过关键字抓取出相关剧情,然后使用python的wordcloud包生成词云,wordcloud可以导入图片自定义词云的形状,非常方便,但是需要注意中文编码和字体的问题,否则生成的词云会显示成乱码。

    #大风厂(感兴趣的关键字,可能需要加入到词典中)
    text = []
    #遍历每句话
    for line in word:
        if '大风厂' in line:
            text.append(line)
    
    #词频统计
    dict_dz = {}
    for i in text:
        dz1 = i.split(' ')
        for w in dz1:
            w1 = w.strip()
            if dict_dz.__contains__(w1) :
                dict_dz[w1] += 1
            else:
                dict_dz[w1] = 1     
                                
    #生成text
    text1 = ''
    for i in text:
        dz2 = i.split(' ')
        for w in dz2:
            text1 =text1 +' '+ w
                    
    #生成词云图
    from wordcloud import WordCloud,STOPWORDS,ImageColorGenerator
    #读取背景图片信息保存为array
    background_Image = plt.imread('c1.jpg')
    font = 'msyh.TTF'  
    
    #设置字体格式路径,不然显示不了中文,
    #可以改成r'C:\Windows\Fonts\xxxx.ttf'来切换其他字体,这边我们把文件放在默认文件夹中。
    #选择已经有的字体,根据词频否则生成图片的时候会报错:OSError: cannot open resource
    
    wc = WordCloud(background_color = 'white',mask = background_Image,
                   max_words = 2000,stopwords = STOPWORDS,font_path = font,
                   max_font_size = 80,random_state = 42,scale = 1.5).generate(text1)
    
    #这种方法和上面直接generate()的结果相同,但是传入的数据格式不同,
    #这个函数传入的是要给字典,key(键)为词,value(值)为出现频率。
    #wc.generate_from_frequencies(dict_dz) 
    
    #根据图片生成词云颜色,这里选择显眼的颜色
    #如果需要黑白灰的词云颜色就把'#'删除
    #image_colors = ImageColorGenerator(background_Image)
    #wc.recolor(color_func = image_colors)
    plt.imshow(wc)
    plt.axis('off')
    plt.show()
    wc.to_file('c2.jpg') #保存图片
    

    生成的词云图:


    wordcloud

    词云中词语越大代表和大风厂这个词相关度越高,可以看出和大风厂关系最紧密的有:蔡成功,陈岩石,郑西坡,李达康,职工股权,侯亮平,高小琴,山水集团等

    4.1.基于TF-IDF提取关键词

    上面的词云图表示了大风厂同时出现频数多的词,但出现频数多的词并不能代表是文本中的关键词,故使用TF-IDF进行关键词提取。TF-IDF权重通过计算词频和逆向文件频率来计算,这里直接利用jieba分词工具进行提取,列出20个关键词如下表所示:

    【蔡成功,李达康,郑西坡,陈岩石,侯亮平,赵东来,沙瑞金,陈清泉,职工,高小琴,山水集团,尤瑞星,郑乾,欧阳菁,股权,打电话,常成虎,京州,陈海,郑胜利】

    可以发现与大风厂相关的主要人物都列出来了,此外,还有一些特别词,如山水集团,京州,职工,股权等,通过这些关键词我们可以比较清楚的知道有关大风厂的大部分信息。我们可以推测出:大风厂事件很可能和山水集团还有职工股权纠纷有关,其事件主要涉及到的人是山水集团、京州公务员、大风厂职工,各部分人在事件中扮演不同的角色。如果想看到每个词具体的TF-IDF权重也可以利用scikit-learn包进行计算,然后再根据权重的大小进行重要性排序。

    #基于tf-idf提取关键词
    from jieba import analyse
    tfidf = analyse.extract_tags
    #analyse.set_stop_words('stop_words.txt') #使用自定义停用词集合
    
    text_dz = ''
    for l in text:
        text_dz += l
        text_dz += ' '
    keywords = tfidf(text_dz,topK=20)
    print (keywords)
    

    4.2.运用word2vec挖掘语义相似关系

    由于文本分析中也经常会用到word2vec将文本转化为词向量的形式后来挖掘词语语义上的相似关系。这里我们使用word2vec来测试一下模型效果。导入gensim库后,将文本转化为模型输入的形式后进行训练,然后就可以得到每个词的向量形式。

    4.2.1 先简单介绍一下word2vec的基本使用:

    训练模型的定义:

    from gensim.models import Word2Vec  
    model = Word2Vec(sentences, sg=1, size=100,  window=5,  min_count=5,  negative=3, sample=0.001, hs=1, workers=4)  
    #参数解释:
    #其中的sentences是句子列表,而每个句子又是词语的列表,即list[list]类型。
    #1.sg=1是skip-gram算法,对低频词敏感;默认sg=0为CBOW算法。
    
    #2.size是输出词向量的维数,值太小会导致词映射因为冲突而影响结果,值太大则会耗内存并使算法计算变慢,一般值取为100到200之间。
    
    #3.window是句子中当前词与目标词之间的最大距离,3表示在目标词前看3-b个词,后面看b个词(b在0-3之间随机)。
    
    #4.min_count是对词进行过滤,频率小于min-count的单词则会被忽视,默认值为5。
    
    #5.negative和sample可根据训练结果进行微调,sample表示更高频率的词被随机下采样到所设置的阈值,默认值为1e-3。
    
    #6.hs=1表示层级softmax将会被使用,默认hs=0且negative不为0,则负采样将会被选择使用。
    
    #7.workers控制训练的并行,此参数只有在安装了Cython后才有效,否则只能使用单核,anaconda会自带Cython。
    

    训练后的模型保存与加载:

    model.save(fname)  
    model = Word2Vec.load(fname)  
    

    模型使用

    model.most_similar(positive=['woman', 'king'], negative=['man'])  
    #woman+king-man的词向量结果:输出[('queen', 0.50882536), ...]  
      
    model.doesnt_match("breakfast cereal dinner lunch".split())  
    #输出'cereal' ,分出不是一类的词 
      
    model.similarity('woman', 'man')  
    #两个词的相似度,输出0.73723527  
      
    model['computer']  # raw numpy vector of a word  
    #某个词的特征,输出array([-0.00449447, -0.00310097,  0.02421786, ...], dtype=float32)  
    
    4.2.2 分析与‘大风厂’相似的词语关系
    #word2vec
    import gensim
    sentences = []
    for line in word:
        if line.strip() != '':
            sentences.append(line.strip().split(' '))
    
    model = gensim.models.Word2Vec(sentences,size = 100,window = 5,min_count = 5,workers = 4)        
    

    例如打印出“大风厂”的向量如输出结果所示。

    print (model['大风厂'])
    
    向量图

    我们也可以输出通过word2vec生成向量之后,和‘大风厂’最相似的向量

    #这里我们输出相似度最高的18个(本来取了20个,最后两个词无特别含义,这里我们topn取18)
    for k,s in model.most_similar(positive = ['大风厂'],topn=18):
        print (k,s)
    
    关联词向量

    最后,我们测试一下word2vec的效果,由于篇幅有限这里就不介绍word2vec原理了,具体可以去看peghoty大神的word2vec系列博客。https://blog.csdn.net/itplus/article/details/37969635
    需要注意的是:如果使用word2vec建议不要去除停用词,因为word2vec用于发现上下文的关系,如果去除了停用词,生成的词向量可能会受影响,最后生成的词向量差异过小没有区分度,这里我们为了方便还是使用去除停用词的语料,这导致了出现的关联词向量相似度两极分化的现象。另外在深度学习中也是不需要去除停用词的,但是去除了模型结果会有略微提升

    word2vec模型的优点在于不仅考虑了语境信息还压缩了数据规模。了解word2vec的人都知道,他的强大之处在于可以利用基本代数公式来发现单词之间的关系(比如,“国王”-“男人”+“女人”=“王后”)。所以word2vec生成的词向量很明显可以应用于:代替词袋用来预测未知数据的情感状况。
    现在这些词向量已经捕捉到上下文的信息,我们来看一下效果:
    计算一个词,该词的词向量和词向量(山水集团+大风厂-高小琴)最接近。
    由于高小琴是山水集团的boss,而蔡成功是大风厂的boss,那么按照word2vec的原理我们可以自己推理出,这个词应该是'蔡成功'。

    r = model.most_similar(positive = ['山水集团','大风厂'],negative=['高小琴'],topn=1)
    print (r)
    

    结果为:

    word2vec运算结果.png

    [('蔡成功', 0.9983329772949219)]
    和预期结果一样,这就是word2vec的强大之处。

    另:如果要直观的看词向量的效果,在词向量数量不是特别多的情况下,可以使用pca对词向量降维,降到两维度或者三维就可以进行可视化。

    References:

    图片和文字内容爬取自百度百科
    [1]http://blog.csdn.net/AlanConstantineLau/article/details/72146213
    [2]http://blog.csdn.net/xiemanr/article/details/72796739
    [3]https://www.zhihu.com/question/21268129
    [4]http://blog.csdn.net/u010309756/article/details/67637930
    [5]https://www.cnblogs.com/mjiang2017/p/8431977.html
    [6]https://blog.csdn.net/itplus/article/details/37969635
    [7]https://radimrehurek.com/gensim/models/word2vec.html

    相关文章

      网友评论

      • 羽恒:您好请教一下 您的人物字典是怎么做的 目的是什么?
        wong小尧:@羽恒 文章中有爬取名字的代码,或者直接复制粘贴到文本中也可以。
        羽恒:@wong小尧 您可以教我一下 怎么制作人物词典吗?
        wong小尧:@羽恒 我猜你的意思是:为什么需要用人物词典。如果不把人物名字做成一个词典的话 用切词去切分文本的时候有可能无法成功把人物名字切取出来,比如蔡成功本来应该是一个单独的词,由于没有人名词典可能会被分成“蔡”&“成功”两个词,无法把词语切取出来来就无法进一步分析

      本文标题:python+nlp+Gephi 分析电视剧【人民的名义】

      本文链接:https://www.haomeiwen.com/subject/kqajoxtx.html