自然語言分析加關係網路 - 半自動分析倚天屠龍記

關鍵詞: Word2vec, Networkx, Jieba, 自然語言, 向量話,相似,關係網路,倚天屠龍記

最近在了解到,在機器學習中,自然語言處理是較大的一個分支。存在許多挑戰。例如: 如何分詞,識別實體關係,實體間關係,關係網路展示等。

我用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 npimport pandas as pdimport jiebaimport 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].valuescorpus = "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 resultdef 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 timesfull_wf={"word":[],"flag":[]}possible_name = yttlj["sentence"].apply(extract_name)#tmp_w,tmp_fdf_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)# tokeningyttlj["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 parametersnum_features = 300 # Word vector dimensionality min_word_count = 20 # Minimum word count num_workers = 4 # Number of threads to run in parallelcontext = 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 word2vecmodel_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.Namedef check_nshow(x): nshow = yttlj["sentence"].str.count(x).sum() #print(x, nshow) return nshowER["nshow"]=ER["tmp"].apply(check_nshow)ER = ER.drop(["tmp"],axis=1)count = 0for 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 nximport matplotlib.pyplot as pltimport pandas as pdimport numpy as npimport pygraphvizfrom networkx.drawing.nx_agraph import graphviz_layout

此處為提取張無忌的不同屬性的關係和相似性

def fill_node(G,ER,topn=0.1,nearn=5,index_filter=[]): color_ER ={"張無忌":"b", "張教主":"b", "無忌哥哥":"r", "張公子":"g", "無忌":"r"} color_ER.setdefault("b") if len(index_filter) ==0: mask = ER.nshow>ER.nshow.quantile(1-topn) ER_highshow = ER[mask] maxshow = ER_highshow.nshow.max() indexes = ER_highshow.index else: maxshow = ER.nshow.max() ER_highshow = ER indexes = index_filter for index in indexes: #print(index) size = (ER_highshow.loc[index,"nshow"] ) G.add_node(index,weight=size) #print(index,size) importance = size #print(iters) small_columns = ER_highshow.loc[index,:].sort_values().tail(nearn).index for col in small_columns: if col !="nshow": relation = ER_highshow.loc[index,col] if relation >0: check_index = index in ["張無忌","張教主","無忌哥哥","無忌","張公子"] check_col = col in ["張無忌","張教主","無忌哥哥","無忌","張公子"] if check_index or check_col: if check_index: label = color_ER[index] else: label = color_ER[col] else: label="b" G.add_edge(index,col,weight= relation,lable=label) returndef scaler_degree(top=20): node =[v.setdefault("weight",0) for k,v in G.node.items()] from sklearn.preprocessing import minmax_scale scaled_degree = np.round(minmax_scale(node,(0,top))**2) return scaled_degree.tolist()def filter_nodes(counter=50): Nodes = G.node filtered_nodes={} for k,v in Nodes.items(): value = v.setdefault("weight",0) if value> counter: filtered_nodes[k]=k else: filtered_nodes[k]="" #print(filtered_nodes) return filtered_nodes

展示

plt.subplots(figsize=(10,10)) ER = pd.read_hdf("ER.h5","ER")G=nx.Graph() fill_node(G,ER,nearn=10,index_filter=["張無忌","無忌","無忌哥哥","張教主","張公子"])d = nx.degree(G)pos = graphviz_layout(G, prog=twopi, args=)elarge=[(u,v) for (u,v,d) in G.edges(data=True) if d[weight] >0.99999] esmall=[(u,v) for (u,v,d) in G.edges(data=True) if d[weight] <=0.96] edge_red=[(u,v) for (u,v,d) in G.edges(data=True) if d[lable] =="r"] edge_green=[(u,v) for (u,v,d) in G.edges(data=True) if d[lable] =="g"] edge_blue=[(u,v) for (u,v,d) in G.edges(data=True) if d[lable] =="b"] # nodes nx.draw_networkx_nodes(G,pos,node_size=scaler_degree() ) # edges nx.draw_networkx_edges(G,pos,edgelist=elarge, width_=1,alpha=0.2,edge_color=b,stylex=dashed) nx.draw_networkx_edges(G,pos,edgelist=esmall, width_=1,alpha=0.2,edge_color=b,stylex=dashed)nx.draw_networkx_edges(G,pos,edgelist=edge_blue, width_=1,alpha=0.2,edge_color=b) nx.draw_networkx_edges(G,pos,edgelist=edge_green, width_=4,alpha=0.5,edge_color=g,)nx.draw_networkx_edges(G,pos,edgelist=edge_red, width_=4,alpha=0.5,edge_color=r,) nx.draw_networkx_labels(G,pos, font_size= 10,font_family=sans-serif) nx.draw_networkx_labels(G,pos,labels=filter_nodes(1000), font_size= 22,font_family=sans-serif) # labels標籤定義 plt.axis("off") plt.show() # display

主動分享吧,這將是生活工作中最好的投資!

王勇

2018年3月17日

招攬機器學習和自然語言落地業務,歡迎聯繫。


推薦閱讀:

機器學習入門筆記4
Spark隨機森林模型在互聯網金融上的應用
自動調參、ROC——2017.03.13
從建立清晰的步驟開始——Machine Learning Project Checklist
VC眼中的人工智慧!

TAG:自然語言處理 | 機器學習 | Python |