最近和舍友看完了去年大火的電視劇【人民的名義】,看完覺得里面的人物關(guān)系很有意思,決定對(duì)其分析分析,也順便測(cè)試一下早前使用過(guò)的一些模型(例如word2vec)效果是否能達(dá)到預(yù)期。
1.獲取數(shù)據(jù)
進(jìn)行分析之前首先需要獲取劇情的文本,因?yàn)闆]有看過(guò)小說(shuō),為了盡量接近自己和大部分人所熟悉的劇情,這里爬取從百度百科上的每一集的劇情,針對(duì)已更新的劇情文本進(jìn)行分析。利用python的urllib2(python3.3后改為urllib.request)和BeautifulSoup包可以很快的爬下劇情文本,保存為rmdmy.txt文檔,順便將出現(xiàn)的人物名字也一起爬下來(lái),后面進(jìn)行預(yù)處理和分析中涉及到的分詞、實(shí)體屬性對(duì)齊和社交網(wǎng)絡(luò)分析等都將會(huì)用到。
# -*- coding: utf-8 -*-
"""
@author: wangyao
"""
#改變默認(rèn)工作路徑
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標(biāo)簽
content1 = re.sub(r'<[^>]+>','',content)
f = open('rmdmy.txt','w',encoding= 'utf-8') #直接用open打開會(huì)報(bào)錯(cuò),需要指定編碼方式
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()
文本文件如下所示:
2.文本預(yù)處理
將劇情爬下來(lái)后需要對(duì)文本進(jìn)行預(yù)處理,主要包括分句、分詞、去掉一些特殊符號(hào)和停用詞、實(shí)體對(duì)齊和屬性對(duì)齊等。如果一個(gè)人可能在劇中有不同的名字,這時(shí)候就需要進(jìn)行統(tǒng)一。為了盡量正確的切分一些固定名稱,需要導(dǎo)入自定義詞典,主要包含一些人名、地名和組織名稱等(例如這里需要加入侯亮平,漢東省,漢東大學(xué),山水集團(tuán),大風(fēng)廠等等)。此外,在提取文本特征時(shí)需要去掉一些停用詞,以提高分析的準(zhǔn)確度。經(jīng)過(guò)一系列處理后得到比較干凈的文本分詞結(jié)果,然后就可以在此基礎(chǔ)上進(jìn)行深入的分析。
#文本預(yù)處理
import jieba
jieba.load_userdict('rmdmy_dict.txt')#自定義詞典
stopword = [line.strip() for line in open('StopwordsCN.txt',encoding= 'utf-8').readlines()] #簡(jiǎn)體中文停用詞
fr = open('rmdmy.txt','r',encoding= 'utf-8')
con = [fr.readlines()]
'''
分詞,并去掉特殊字符、詞語(yǔ)
'''
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("。")#按句號(hào)分句
for j in w1:
w2 = re.sub(r',|。|?|:|“|”|!','',j.strip())#去掉特殊字符
#w1 = re.sub(name1,name2,w1) #實(shí)體對(duì)齊
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()
預(yù)處理結(jié)果:
3.人物出現(xiàn)頻次和社交網(wǎng)絡(luò)關(guān)系
先看一下劇中出場(chǎng)次數(shù)較多的關(guān)鍵人物有哪些,根據(jù)之前爬下來(lái)的名字列表,統(tǒng)計(jì)其在文本中出現(xiàn)的次數(shù),通過(guò)matplotlib包畫出出現(xiàn)次數(shù)最多的10個(gè)關(guān)鍵人物如圖所示,可以發(fā)現(xiàn)侯亮平出現(xiàn)次數(shù)最多,共483次,其次李達(dá)康出現(xiàn)了226次,再次是高育良出現(xiàn)了211次,祁同偉202次。整部劇穿插的幾大事件都在圍繞著這四個(gè)人展開,而沙瑞金雖然也是貫穿全劇的重要人物,出場(chǎng)次數(shù)也很多(148次),但是大部分事件里都是出來(lái)打醬油的,所以次數(shù)還低于大風(fēng)廠事件的蔡成功。
#人物出場(chǎng)次數(shù)統(tǒng)計(jì)
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')
#引入字體,否則文字顯示不出來(lái)
#python 字符串轉(zhuǎn)列表 list 出現(xiàn)\ufeff的解決方法(網(wǎng)上)
##文件內(nèi)容 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)
#
##打印結(jié)果如下
#['\ufefflisi']
#lisi
#自己測(cè)試,在notepad++上把編碼從UTF-8編碼模式改成UTF-8無(wú)BOM編碼模式,ufeeff就會(huì)消失
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()
#匹配詞典里的名字和劇本內(nèi)容里的該名字出現(xiàn)的次數(shù)
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('人物出場(chǎng)次數(shù)',fontsize = 14,fontproperties = font_yahei_consolas)
ax.set_yticks(range(10))
ax.set_yticklabels(names,fontsize = 14,fontproperties = font_yahei_consolas)
plt.show()
人物出場(chǎng)頻次條形圖:
再來(lái)看看劇中人物的社交關(guān)系情況,采用以句為單位來(lái)進(jìn)行分析(有時(shí)候也以段落為單位來(lái)識(shí)別人物關(guān)系,但采集的文本每集只有一個(gè)段落,所以不適用),即如果兩個(gè)人同時(shí)出現(xiàn)在一句話中,那說(shuō)明他們之間肯定有某種聯(lián)系。因此可以得到他們的社交網(wǎng)絡(luò)關(guān)系。通過(guò)求得的共現(xiàn)矩陣,使用Gephi畫出下面的社交網(wǎng)絡(luò)關(guān)系圖,圖中邊的粗細(xì)代表關(guān)系的密切程度,邊越粗表示兩人的關(guān)系越密切,而名字的大小可以表示為該人的社交人脈強(qiáng)弱情況。
#匹配詞典里的名字和劇本內(nèi)容里的該名字出現(xiàn)的次數(shù)
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('人物出場(chǎng)次數(shù)',fontsize = 14,fontproperties = font_yahei_consolas)
ax.set_yticks(range(10))
ax.set_yticklabels(names,fontsize = 14,fontproperties = font_yahei_consolas)
#社交網(wǎng)絡(luò)關(guān)系(共現(xiàn)矩陣)
f2 = open('rmdmy_content.txt','r',encoding= 'utf-8')
word = f2.readlines()
name = data1
#name = data1[1:]
#總?cè)藬?shù)
wordcount = len(name)
#初始化128*128值全為0的共現(xiàn)矩陣
cormatrix = [[0 for col in range(wordcount)] for row in range(wordcount)]
#遍歷矩陣行和列
for colindex in range(wordcount):
for rowindex in range(wordcount):
cornum = 0
#如果兩個(gè)人名字在同一句話里出現(xiàn),那么共現(xiàn)矩陣中兩個(gè)人對(duì)應(yīng)的值加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)
#把共現(xiàn)矩陣存進(jìn)excel
social_cor_matrix.to_csv('social_cor_matrix.csv')
social_contact = pd.DataFrame(columns = ['name1','name2','frequency'])
#共現(xiàn)頻率
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.走進(jìn)‘大風(fēng)廠’事件
接下來(lái)重點(diǎn)探索一下我比較感興趣的“大風(fēng)廠”事件,先通過(guò)關(guān)鍵字抓取出相關(guān)劇情,然后使用python的wordcloud包生成詞云,wordcloud可以導(dǎo)入圖片自定義詞云的形狀,非常方便,但是需要注意中文編碼和字體的問(wèn)題,否則生成的詞云會(huì)顯示成亂碼。
#大風(fēng)廠(感興趣的關(guān)鍵字,可能需要加入到詞典中)
text = []
#遍歷每句話
for line in word:
if '大風(fēng)廠' in line:
text.append(line)
#詞頻統(tǒng)計(jì)
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'
#設(shè)置字體格式路徑,不然顯示不了中文,
#可以改成r'C:\Windows\Fonts\xxxx.ttf'來(lái)切換其他字體,這邊我們把文件放在默認(rèn)文件夾中。
#選擇已經(jīng)有的字體,根據(jù)詞頻否則生成圖片的時(shí)候會(huì)報(bào)錯(cuò):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()的結(jié)果相同,但是傳入的數(shù)據(jù)格式不同,
#這個(gè)函數(shù)傳入的是要給字典,key(鍵)為詞,value(值)為出現(xiàn)頻率。
#wc.generate_from_frequencies(dict_dz)
#根據(jù)圖片生成詞云顏色,這里選擇顯眼的顏色
#如果需要黑白灰的詞云顏色就把'#'刪除
#image_colors = ImageColorGenerator(background_Image)
#wc.recolor(color_func = image_colors)
plt.imshow(wc)
plt.axis('off')
plt.show()
wc.to_file('c2.jpg') #保存圖片
生成的詞云圖:
詞云中詞語(yǔ)越大代表和大風(fēng)廠這個(gè)詞相關(guān)度越高,可以看出和大風(fēng)廠關(guān)系最緊密的有:蔡成功,陳巖石,鄭西坡,李達(dá)康,職工股權(quán),侯亮平,高小琴,山水集團(tuán)等
4.1.基于TF-IDF提取關(guān)鍵詞
上面的詞云圖表示了大風(fēng)廠同時(shí)出現(xiàn)頻數(shù)多的詞,但出現(xiàn)頻數(shù)多的詞并不能代表是文本中的關(guān)鍵詞,故使用TF-IDF進(jìn)行關(guān)鍵詞提取。TF-IDF權(quán)重通過(guò)計(jì)算詞頻和逆向文件頻率來(lái)計(jì)算,這里直接利用jieba分詞工具進(jìn)行提取,列出20個(gè)關(guān)鍵詞如下表所示:
【蔡成功,李達(dá)康,鄭西坡,陳巖石,侯亮平,趙東來(lái),沙瑞金,陳清泉,職工,高小琴,山水集團(tuán),尤瑞星,鄭乾,歐陽(yáng)菁,股權(quán),打電話,常成虎,京州,陳海,鄭勝利】
可以發(fā)現(xiàn)與大風(fēng)廠相關(guān)的主要人物都列出來(lái)了,此外,還有一些特別詞,如山水集團(tuán),京州,職工,股權(quán)等,通過(guò)這些關(guān)鍵詞我們可以比較清楚的知道有關(guān)大風(fēng)廠的大部分信息。我們可以推測(cè)出:大風(fēng)廠事件很可能和山水集團(tuán)還有職工股權(quán)糾紛有關(guān),其事件主要涉及到的人是山水集團(tuán)、京州公務(wù)員、大風(fēng)廠職工,各部分人在事件中扮演不同的角色。如果想看到每個(gè)詞具體的TF-IDF權(quán)重也可以利用scikit-learn包進(jìn)行計(jì)算,然后再根據(jù)權(quán)重的大小進(jìn)行重要性排序。
#基于tf-idf提取關(guān)鍵詞
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.運(yùn)用word2vec挖掘語(yǔ)義相似關(guān)系
由于文本分析中也經(jīng)常會(huì)用到word2vec將文本轉(zhuǎn)化為詞向量的形式后來(lái)挖掘詞語(yǔ)語(yǔ)義上的相似關(guān)系。這里我們使用word2vec來(lái)測(cè)試一下模型效果。導(dǎo)入gensim庫(kù)后,將文本轉(zhuǎn)化為模型輸入的形式后進(jìn)行訓(xùn)練,然后就可以得到每個(gè)詞的向量形式。
4.2.1 先簡(jiǎn)單介紹一下word2vec的基本使用:
訓(xùn)練模型的定義:
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)
#參數(shù)解釋:
#其中的sentences是句子列表,而每個(gè)句子又是詞語(yǔ)的列表,即list[list]類型。
#1.sg=1是skip-gram算法,對(duì)低頻詞敏感;默認(rèn)sg=0為CBOW算法。
#2.size是輸出詞向量的維數(shù),值太小會(huì)導(dǎo)致詞映射因?yàn)闆_突而影響結(jié)果,值太大則會(huì)耗內(nèi)存并使算法計(jì)算變慢,一般值取為100到200之間。
#3.window是句子中當(dāng)前詞與目標(biāo)詞之間的最大距離,3表示在目標(biāo)詞前看3-b個(gè)詞,后面看b個(gè)詞(b在0-3之間隨機(jī))。
#4.min_count是對(duì)詞進(jìn)行過(guò)濾,頻率小于min-count的單詞則會(huì)被忽視,默認(rèn)值為5。
#5.negative和sample可根據(jù)訓(xùn)練結(jié)果進(jìn)行微調(diào),sample表示更高頻率的詞被隨機(jī)下采樣到所設(shè)置的閾值,默認(rèn)值為1e-3。
#6.hs=1表示層級(jí)softmax將會(huì)被使用,默認(rèn)hs=0且negative不為0,則負(fù)采樣將會(huì)被選擇使用。
#7.workers控制訓(xùn)練的并行,此參數(shù)只有在安裝了Cython后才有效,否則只能使用單核,anaconda會(huì)自帶Cython。
訓(xùn)練后的模型保存與加載:
model.save(fname)
model = Word2Vec.load(fname)
模型使用
model.most_similar(positive=['woman', 'king'], negative=['man'])
#woman+king-man的詞向量結(jié)果:輸出[('queen', 0.50882536), ...]
model.doesnt_match("breakfast cereal dinner lunch".split())
#輸出'cereal' ,分出不是一類的詞
model.similarity('woman', 'man')
#兩個(gè)詞的相似度,輸出0.73723527
model['computer'] # raw numpy vector of a word
#某個(gè)詞的特征,輸出array([-0.00449447, -0.00310097, 0.02421786, ...], dtype=float32)
4.2.2 分析與‘大風(fēng)廠’相似的詞語(yǔ)關(guān)系
#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)
例如打印出“大風(fēng)廠”的向量如輸出結(jié)果所示。
print (model['大風(fēng)廠'])
我們也可以輸出通過(guò)word2vec生成向量之后,和‘大風(fēng)廠’最相似的向量
#這里我們輸出相似度最高的18個(gè)(本來(lái)取了20個(gè),最后兩個(gè)詞無(wú)特別含義,這里我們topn取18)
for k,s in model.most_similar(positive = ['大風(fēng)廠'],topn=18):
print (k,s)
最后,我們測(cè)試一下word2vec的效果,由于篇幅有限這里就不介紹word2vec原理了,具體可以去看peghoty大神的word2vec系列博客。https://blog.csdn.net/itplus/article/details/37969635
【 需要注意的是:如果使用word2vec建議不要去除停用詞,因?yàn)閣ord2vec用于發(fā)現(xiàn)上下文的關(guān)系,如果去除了停用詞,生成的詞向量可能會(huì)受影響,最后生成的詞向量差異過(guò)小沒有區(qū)分度,這里我們?yōu)榱朔奖氵€是使用去除停用詞的語(yǔ)料,這導(dǎo)致了出現(xiàn)的關(guān)聯(lián)詞向量相似度兩極分化的現(xiàn)象。另外在深度學(xué)習(xí)中也是不需要去除停用詞的,但是去除了模型結(jié)果會(huì)有略微提升】
word2vec模型的優(yōu)點(diǎn)在于不僅考慮了語(yǔ)境信息還壓縮了數(shù)據(jù)規(guī)模。了解word2vec的人都知道,他的強(qiáng)大之處在于可以利用基本代數(shù)公式來(lái)發(fā)現(xiàn)單詞之間的關(guān)系(比如,“國(guó)王”-“男人”+“女人”=“王后”)。所以word2vec生成的詞向量很明顯可以應(yīng)用于:代替詞袋用來(lái)預(yù)測(cè)未知數(shù)據(jù)的情感狀況。
現(xiàn)在這些詞向量已經(jīng)捕捉到上下文的信息,我們來(lái)看一下效果:
計(jì)算一個(gè)詞,該詞的詞向量和詞向量(山水集團(tuán)+大風(fēng)廠-高小琴)最接近。
由于高小琴是山水集團(tuán)的boss,而蔡成功是大風(fēng)廠的boss,那么按照word2vec的原理我們可以自己推理出,這個(gè)詞應(yīng)該是'蔡成功'。
r = model.most_similar(positive = ['山水集團(tuán)','大風(fēng)廠'],negative=['高小琴'],topn=1)
print (r)
結(jié)果為:
[('蔡成功', 0.9983329772949219)]
和預(yù)期結(jié)果一樣,這就是word2vec的強(qiáng)大之處。
另:如果要直觀的看詞向量的效果,在詞向量數(shù)量不是特別多的情況下,可以使用pca或LDA對(duì)詞向量降維,降到兩維度或者三維就可以進(jìn)行可視化。
References:
圖片和文字內(nèi)容爬取自百度百科
[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