最近在學習自然語言處理,看完了《Python 自然語言處理》這本書,想做一點實踐性的練習。之前我在知乎上看見有人用機器學習來做過紅樓夢人物關系梳理,于是我便想嘗試著做了下《權力的游戲》中人物關系的梳理
背景知識
-
詞向量
在自然語言處理(natural language processing,NLP)領域,首要任務便是將單詞轉換為由數字組成的向量,這樣計算機才可以進行更進一步處理。下面我們簡要介紹一下詞向量的表示方法
-
One-Hot representation
這是最簡單的一種詞向量的表示方式。其基本思想便是用一個很長很長的向量來表示一個單詞,向量的長度便是字典的長度,該向量由0和1組成,有且僅有一個1,其他全為0,1的位置便是該詞在詞典中的順序。
例如,在某個詞典中,'like'的詞向量可表示為[0,0,0,1,0,0,...],'hate'的詞向量可表示為[0,1,0,0,0,0,...]。可以根據以上向量可知,like在詞典中處于第四個位置,hate在詞典中處于第二個位置。
這種向量表示法的優點是直觀,易于理解。缺點也是很明顯的。首先,會出現維數災難,詞典的長度通常是上萬的,一篇簡單的文檔,例如新聞,也都是上千上萬的單詞,如果用這種方式來表達一篇文檔很容易都達到極高的維度,同時也浪費了很多存儲空間。再者就是這種表示方法假設了任意兩個詞之間都是孤立的存在,無法表達語義相近的兩個詞之間的關系,比如hate與dislike。
-
Distributed Representation
這種表示方法最早是由Hinton提出來的,它克服了one-hot表示方法的缺點。其基本思想是,通過對某種語言中的詞語進行訓練,獲得一個普通向量(例如[0.0268,-0.234,0.5263,...]這種形式,分布較為均勻),其維數通常為50-100(相比于one-hot可是大大減少了)。
利用這種表示方法,就可以計算不同詞之間的相似度,可利用常用的距離計算公式或者相似度計算公式來進行計算。
訓練生成詞向量通常采用的是神經網絡算法,例如RNN(循環神經網絡)。
在本項目中我們將采用gensim庫中的word2vec來訓練并獲得所需要的詞向量。
-
相似度計算
在得到每個詞的向量過后,計算兩個詞之間的相似度就很方便了。常見的計算相似度的方法有Euclidean距離,向量余弦相似度,Jaccard方法。這個比較簡單,不在此贅述了
-
名字識別
對《權力的游戲》小說中人物的識別我采用的策略是:這個詞不是處在一句話的首位;正則表達式“[A-Z][a-z]+”,即首字母大寫,后面的字母小寫;利用nltk中的詞性標注,如果是人名應該是屬于名詞。
利用這種方法可以正確識別大部分的名字,也會將一些地名認為是人物名字,當然由于我找到的這個權力的游戲的txt文本中存在一些排版錯誤,也可能會影響到實體識別。
實驗過程
-
完整代碼
項目的github地址:可查看完整代碼
-
實驗材料
《Game of Thrones》txt文本資源的下載。
-
利用gensim獲取詞向量
前面提到,這里我利用Python的gensim庫來訓練得到相關的詞向量。
-
安裝gensim庫
Python庫的安裝都比較簡單,在終端中輸入下面的命令行語句即可
$ pip3 install gensim
-
獲取詞向量
導入gensim中的word2vec
from gensim.models import word2vec
讀取game_of_thrones.txt,對它進行分詞,并且識別出相關的人物名稱。在我的代碼中創建了一個類BuildDict來完成這項工作,以下是該類的的部分代碼
class BuildDict(object): def __init__(self,filename): self.filename = filename def get_tokens_and_names(self): ''' :return:tokens and names from the file ''' file = open(self.filename) tokens = [];names = [] for line in file: token,name = self.get_tokens_and_names_per_line(line) tokens.append(token) names.extend(name) return tokens,names
get_tokens_and_names()這個方法就是處理文本,將文本切割成一個一個的單詞,返回tokens和names。
如果文檔很多,gensim庫提供了一次讀一個文檔的方法,由于這里只有一個文檔,并且文檔并不大,所以未使用該方法。如果有興趣可以看一看gensim的官方文檔。
在得到tokens和names過后,下面便進行訓練了,利用之前導入的word2vec里的Word2Vec類。將訓練的結果保存在一個文件中,方便后面使用。
sentences,names= BuildDict("./game_of_thrones.txt").get_tokens_and_names() #train and get word vector model = word2vec.Word2Vec(sentences,min_count=1) model.save("./model.txt") #compute names' frequency and save the list freq = nltk.FreqDist(names) name_list = [w for w in set(names) if freq[w]>10] f = open('names.txt','w') for i in name_list: f.write(i+'\n')
model文件有亂碼,在這就不展示了。得到的names.txt部分內容如下圖所示:
-
人物關系分析
經過前面一系列的操作,我們現在獲得到了《權力的游戲》里大部分人的名字(頻數大于10的人名)以及這些名字的詞向量,接下來我們就可以來分析這些人物之間關系的緊密程度了。
-
導入詞典向量文件和人名
將之前訓練好的模型文件以及保存人名的txt文件導入到代碼中。
model = word2vec.Word2Vec.load("./model.txt") names = open('names.txt')
-
相似度矩陣
計算人物兩兩之間的相似度,可以選擇前面所提到的方法來進行計算,在這里我嫌麻煩直接就用了model對象的similarity方法得到相似度。
sim = model.similarity(name_row,name_column)
最終得到了一個相似度的對稱矩陣,并且對角線上的數字都是1,由于所有人物來自同一個文檔,毫無疑問他們之間的相似度都比較大,粗略觀察了一下,我所得到的相似度最小的也有0.65左右,這樣不利于我們分析他們的親疏關系。于是我對這個進行了歸一化處理
from sklearn import preprocessing name_mat = preprocessing.MinMaxScaler().fit_transform(np.mat(name_similarity_array))
好,這就是我們所需要的相似度矩陣了。
-
數據處理以及可視化
光得到相似度矩陣可不行哇,無法給人直觀的感受,下面我們來對這個矩陣進行一些分析。這個矩陣大概是196*196的規模,還是比較大的,下面我們對它進行降維處理。目前我采用的降維方法有主成分分析(PCA)以及矩陣的奇異值分解(SVD)這兩種,兩種方法得到的結果差不太多.
- PCA
from sklearn import decomposition pca = decomposition.PCA(n_components=3) pca.fit(name_mat) U = pca.transform(name_mat)
得到的三維圖像如下圖:
PCA- SVD
import numpy as np U,S,V = np.linalg.svd(name_mat)
得到三維圖像如下圖:
SVD
得到的三維圖像效果并不是很好,全部都聚集在一堆去了,也難怪,這都是一個文檔的人物,無論怎樣關系還是很大的。人物關系圖嘛,用網絡拓撲圖來表示肯定比這個三維圖像更加直觀。正好,Python就有這么一個庫,可以繪制網絡拓撲圖,這個庫便是networkx。
安裝networkx$pip3 install networkx
用network繪制拓撲圖的代碼如下
def visulize2(name_mat,name_list,name,layer = 2,accurate = 0.9): ''' :param name_mat: :param name_list: :param name: the center of the graph :param layer: decide the layer number of the graph :param accurate: the similarity between the center with next layer :return: ''' graph = nx.Graph() add_element(graph,name_mat,name_list,name,layer,accurate) nx.draw(graph, node_size=100, node_color='g', with_labels=True, font_size =8,alpha = 0.5,font_color='b',edge_color = 'gray') plt.show() def add_element(graph,name_mat,name_list,name,layer,accurate): ''' add element to the graph,including node and edge :param graph: :param name_mat: :param name_list: :param name: :param layer: :param accurate: :return: ''' graph.add_node(name) if layer == 1:return pos = name_list.index(name) for i in range(len(name_list)): if name_mat[pos, i] >= accurate: graph.add_node(name_list[i]) graph.add_edge(name, name_list[i]) add_element(graph,name_mat,name_list,name_list[i],layer-1,accurate)
你只需要向visualize2方法傳入相似度矩陣,名字列表,以及作為中心點的人物的名字,即可繪制出該人物與和他相似度大于0.9的人物之間的關系圖。你也可以自定義layer和accurate的值,繪制更復雜的拓撲圖。
下面是layer = 2,accurate = 0.9,name = 'Snow'的關系圖:
Snow's Relationship下面是layer = 3,accurate = 0.95,name = 'Daenerys'的關系圖
Daenerys's Relationship雖然關系還是很復雜,但是不是很直觀了呢!
總結
本項目中還是存在很多的不足,比如不能完全正確識別人物名字,人物間區分度不高(相似度都比較大),不過作為才學兩周nlp的我來說已經很滿意了,剛剛畫出拓撲圖的時候還是躊躇滿志呢。由于筆者水平較低,是一枚剛入門的菜鳥,如有錯誤之處望各位讀者不吝賜教。