Python分析倚天屠龙记,你get到了没?
最近在了解到,在机器学习中,自然语言处理是较大的一个分支。存在许多挑战。例如: 如何分词,识别实体关系,实体间关系,关系网络展示等。
我用Jieba + Word2vec + NetworkX 结合在一起,做了一次自然语言分析。语料是 倚天屠龙记。 之前也有很多人用金庸的武侠小说做分析和处理,希望带来一些不同的地方。截几张图来看看:
所有人物的相似图连接。
关系同上。展示形式为多中心结构
以张无忌的不同身份为中心的网络关系图。
这次分析的不一样之处主要是:
1、Word2Vec的相似度结果 – 作为后期社交网络权重
2、NetworkX中分析和展示
上面两个方法结合起来,可以大幅减少日常工作中阅读文章的时间。 采用机器学习,可以从头到尾半自动抽取文章中的实体信息,节约大量时间和成本。 在各种工作中都有利用的场景, 如果感兴趣的朋友,可以联系合作。
先来看看,用Word2Vec+NetworkX 可以发现什么。
一、分析结果
实体的不同属性(张无忌的总多马甲)
张无忌,无忌,张教主,无忌哥哥,张公子。同一个张无忌有多个身份,不同身份又和不同的人联系,有不一样的相似度。
先来看看图:
无忌哥哥是过于亲密的名字,一般不喊。好似和这个词相似度高的都是比较奇怪的角色。
无忌是关系熟了以后,平辈或者长辈可以称呼的名字。还有周姑娘,殷姑娘等
张无忌是通用的名字,人人可以称呼 和马甲联系密切。
张公子是礼貌尊称。 例如,黄衫女子,汝阳王等
张教主是头衔。既要尊重,也表示其实不太熟,有时还有些敌意。 例如: 朱元璋
注:
1、图是Networkx 基于Word2vex画出来了,上面的描述是我的人工分析。
2、赵敏不在上面的网络关系图中。Word2Vec计算出来 张无忌和赵敏 相似度不太高。有些出乎我的意料。 仔细回忆一下,当年看此书时,突然就发现二人在一起了,显得比较突兀。推想起来,书中世界二人成婚了,如果变成现实世界,二人关系比较悬。
二、实现过程
主要步骤:
准备语料
倚天屠龙记 小说的文本文件
自定义分词词典 (小说中的人物名,网上有现成的,约180个)
停用词表
准备工具
Python Pandas, Numpy,Scipy(标准库)
Jieba(中文分词)
Word2vec (单词向量化工具,可以计算单词之间的详细度)
Networks(网络图工具,用于展示复杂的网络关系
数据预处理
文本文件转发成utf8(pandas)
文本文件分句,分词(Jieba)
文本文件分句,分词, 分析词性,主要是人名(Jieba)
更新自定义词典,重新分词(整个过程需要几遍,直至满意)
手工少量删除(分词出来的人名误判率不高,但是还是存在一些。例如:赵敏笑道,可以被识别的 一个叫 赵敏笑的人。 这部分工作还需要手工做。 除非有更好的分词工具,或者可以训练的分词工具,才能解决这一问题。
Word2Vec 训练模型。这个模型可以计算两个人之间的相似度
采用300个维度
过滤词频小于20
滑动窗口 为20
下采样:0.001
生成实体关系矩阵。
网上没找找到现成库,我就自己写了一个。
N*N 维度。 N是人名数量。
用上面WordVec的模型来,填充实体关系矩阵
NetworkX 生成网络图
节点是人名
边是两个节点之间的线条。也就是两个人之间的关系。
三、部分代码实现
初始化
import numpy as np import pandas as pd import jieba import jieba.posseg as posseg %matplotlib inline
数据分词,清洗
renming_file = "yttlj_renming.csv" jieba.load_userdict(renming_file) stop_words_file = "stopwordshagongdakuozhan.txt" stop_words = pd.read_csv(stop_words_file,header= None ,quoting= 3 ,sep= " " )[ 0 ].values corpus = "yttlj.txt" yttlj = pd.read_csv(corpus,encoding= "gb18030" ,header= None ,names=[ "sentence" ]) def cut_join(s): new_s=list(jieba.cut(s,cut_all= False )) #分词 #print(list(new_s)) stop_words_extra = set ([ "" ]) for seg in new_s: if len(seg)== 1 : #print("aa",seg) stop_words_extra.add(seg) #print(stop_words_extra) #print(len(set(stop_words)| stop_words_extra)) new_s = set (new_s) - set (stop_words)-stop_words_extra #过滤标点符号 #过滤停用词 result = "," .join(new_s) return result def extract_name(s): new_s=posseg.cut(s) #取词性 words=[] flags=[] for k,v in new_s: if len(k)> 1 : words.append(k) flags.append(v) full_wf[ "word" ].extend(words) full_wf[ "flag" ].extend(flags) return len(words) def check_nshow(x): nshow = yttlj[ "sentence" ].str.count(x).sum() #print(x, nshow) return nshow # extract name & filter times full_wf={ "word" :[], "flag" :[]} possible_name = yttlj[ "sentence" ].apply(extract_name) #tmp_w,tmp_f df_wf = pd. DataFrame (full_wf) df_wf_renming = df_wf[(df_wf.flag== "nr" )].drop_duplicates() df_wf_renming.to_csv( "tmp_renming.csv" ,index= False ) df_wf_renming = pd.read_csv( "tmp_renming.csv" ) df_wf_renming.head() df_wf_renming[ "nshow" ] = df_wf_renming.word.apply(check_nshow) df_wf_renming[df_wf_renming.nshow> 20 ].to_csv( "tmp_filtered_renming.csv" ,index= False ) df_wf_renming[df_wf_renming.nshow> 20 ].shape #手工编辑,删除少量非人名,分词错的人名 df_wf_renming=pd.read_csv( "tmp_filtered_renming.csv" ) my_renming = df_wf_renming.word.tolist() external_renming = pd.read_csv(renming_file,header= None )[ 0 ].tolist() combined_renming = set (my_renming) | set (external_renming) pd. DataFrame (list(combined_renming)).to_csv( "combined_renming.csv" ,header= None ,index= False ) combined_renming_file = "combined_renming.csv" jieba.load_userdict(combined_renming_file) # tokening yttlj[ "token" ]=yttlj[ "sentence" ].apply(cut_join) yttlj[ "token" ].to_csv( "tmp_yttlj.csv" ,header= False ,index= False ) sentences = yttlj[ "token" ].str.split( "," ).tolist()
Word2Vec 向量化训练
# Set values for various parameters num_features = 300 # Word vector dimensionality min_word_count = 20 # Minimum word count num_workers = 4 # Number of threads to run in parallel context = 20 # Context window size downsampling = 1e-3 # Downsample setting for frequent words # Initialize and train the model (this will take some time) from gensim.models import word2vec model_file_name = 'yttlj_model.txt' #sentences = w2v.LineSentence('cut_jttlj.csv') model = word2vec. Word2Vec (sentences, workers=num_workers, size=num_features, min_count = min_word_count, window = context, sample = downsampling ) model.save(model_file_name)
建立实体关系矩阵
entity = pd.read_csv(combined_renming_file,header= None ,index_col= None ) entity = entity.rename(columns={ 0 : "Name" }) entity = entity.set_index([ "Name" ],drop= False ) ER = pd. DataFrame (np.zeros((entity.shape[ 0 ],entity.shape[ 0 ]),dtype=np.float32),index=entity[ "Name" ],columns=entity[ "Name" ]) ER[ "tmp" ] = entity. Name def check_nshow(x): nshow = yttlj[ "sentence" ].str.count(x).sum() #print(x, nshow) return nshow ER[ "nshow" ]=ER[ "tmp" ].apply(check_nshow) ER = ER.drop([ "tmp" ],axis= 1 ) count = 0 for i in entity[ "Name" ].tolist(): count += 1 if count % round(entity.shape[ 0 ]/ 10 ) == 0 : print ( "{0:.1f}% relationship has been checked" .format( 100 *count/entity.shape[ 0 ])) elif count == entity.shape[ 0 ]: print ( "{0:.1f}% relationship has been checked" .format( 100 *count/entity.shape[ 0 ])) for j in entity[ "Name" ]: relation = 0 try : relation = model.wv.similarity(i,j) ER.loc[i,j] = relation if i!=j: ER.loc[j,i] = relation except : relation = 0 ER.to_hdf( "ER.h5" , "ER" )
NetworkX 展示人物关系图
import networkx as nx import matplotlib.pyplot as plt import pandas as pd import numpy as np import pygraphviz from networkx.drawing.nx_agraph import graphviz_layout
来源:PY学习网:原文地址:https://www.py.cn/article.html