我用Python挖掘了一哈《權力的游戲》中的人物關系

最近在學習自然語言處理,看完了《Python 自然語言處理》這本書,想做一點實踐性的練習。之前我在知乎上看見有人用機器學習來做過紅樓夢人物關系梳理,于是我便想嘗試著做了下《權力的游戲》中人物關系的梳理

背景知識

  • 詞向量

    在自然語言處理(natural language processing,NLP)領域,首要任務便是將單詞轉換為由數字組成的向量,這樣計算機才可以進行更進一步處理。下面我們簡要介紹一下詞向量的表示方法

  1. 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。

  2. 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庫來訓練得到相關的詞向量。

  1. 安裝gensim庫

    Python庫的安裝都比較簡單,在終端中輸入下面的命令行語句即可

    $ pip3 install gensim
    
  2. 獲取詞向量

    導入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部分內容如下圖所示:

name_list
  • 人物關系分析

    經過前面一系列的操作,我們現在獲得到了《權力的游戲》里大部分人的名字(頻數大于10的人名)以及這些名字的詞向量,接下來我們就可以來分析這些人物之間關系的緊密程度了。

  1. 導入詞典向量文件和人名

    將之前訓練好的模型文件以及保存人名的txt文件導入到代碼中。

    model = word2vec.Word2Vec.load("./model.txt")
    names = open('names.txt')
    
  2. 相似度矩陣

    計算人物兩兩之間的相似度,可以選擇前面所提到的方法來進行計算,在這里我嫌麻煩直接就用了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))
    

    好,這就是我們所需要的相似度矩陣了。

  3. 數據處理以及可視化

    光得到相似度矩陣可不行哇,無法給人直觀的感受,下面我們來對這個矩陣進行一些分析。這個矩陣大概是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的我來說已經很滿意了,剛剛畫出拓撲圖的時候還是躊躇滿志呢。由于筆者水平較低,是一枚剛入門的菜鳥,如有錯誤之處望各位讀者不吝賜教。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容