《今生今世》是渣男胡蘭成所寫的一部自傳體小說揪荣。今天我們就來分析一下在他所寫的自傳中的人物關系圖譜雅任,分析一下胡蘭成到底和多少女人有關系吱抚。
代碼奉上
# -*- coding: utf-8 -*-
#@author: Yuhao Zhang
#2018.5.30
import jieba
import codecs
from collections import defaultdict
from pandas import DataFrame
import pandas as pd
#路徑設置昔驱。
#文檔介紹
#jsjs.txt是胡蘭成寫的小說《今生今世》。
#person.txt是一個語料庫洛史,里面放了很多該小說的角色名稱惯殊。
#synonymous_dict.txt是角色的別名
#其他兩個文件是存放程序計算所得各個人物關系之間的邊的權(quán)重
TEXT_PATH = 'jsjs.txt'
DICT_PATH = 'person.txt'
SYNONYMOUS_DICT_PATH = 'synonymous_dict.txt'
SAVE_NODE_PATH = 'node.csv'
SAVE_EDGE_PATH = 'edge.csv'
#類的初始化
class RelationshipView:
def __init__(self, text_path, dict_path, synonymous_dict_path):
self._text_path = text_path
self._dict_path = dict_path
self._synonymous_dict_path = synonymous_dict_path
self._person_counter = defaultdict(int)
self._person_per_paragraph = []
self._relationships = {}
self._synonymous_dict = {}
def generate(self):
self.count_person()
self.calc_relationship()
self.save_node_and_edge()
def synonymous_names(self):
with codecs.open(self._synonymous_dict_path, 'r', 'utf-8') as f:
lines = f.read().split('\n')
for l in lines:
self._synonymous_dict[l.split(' ')[0]] = l.split(' ')[1]
return self._synonymous_dict
def get_clean_paragraphs(self):
new_paragraphs = []
last_paragraphs = []
with codecs.open(self._text_path, 'r', 'utf-8') as f:
paragraphs = f.read().split('\r\n')
paragraphs = paragraphs[0].split('\u3000')
for i in range(len(paragraphs)):
if paragraphs[i] != '':
new_paragraphs.append(paragraphs[i])
for i in range(len(new_paragraphs)):
new_paragraphs[i] = new_paragraphs[i].replace('\n', '')
new_paragraphs[i] = new_paragraphs[i].replace(' ', '')
last_paragraphs.append(new_paragraphs[i])
return last_paragraphs
def count_person(self):
paragraphs = self.get_clean_paragraphs()
synonymous = self.synonymous_names()
print('start process node')
with codecs.open(self._dict_path, 'r', 'utf-8') as f:
name_list = f.read().split(' 10 nr\n')
for p in paragraphs:
jieba.load_userdict(self._dict_path)
poss = jieba.cut(p)
self._person_per_paragraph.append([])
for w in poss:
if w not in name_list:
continue
if synonymous.get(w):
w = synonymous[w]
self._person_per_paragraph[-1].append(w)
if self._person_counter.get(w) is None:
self._relationships[w] = {}
self._person_counter[w] += 1
return self._person_counter
def calc_relationship(self):
print("start to process edge")
for p in self._person_per_paragraph:
for name1 in p:
for name2 in p:
if name1 == name2:
continue
if self._relationships[name1].get(name2) is None:
self._relationships[name1][name2] = 1
else:
self._relationships[name1][name2] += 1
return self._relationships
def save_node_and_edge(self):
excel = []
for name, times in self._person_counter.items():
excel.append([])
excel[-1].append(name)
excel[-1].append(name)
excel[-1].append(str(times))
data = DataFrame(excel, columns=['Id', 'Label', 'Weight'])
data.to_csv('node.csv', encoding='gbk')
excel = []
for name, edges in self._relationships.items():
for v, w in edges.items():
if w > 3:
excel.append([])
excel[-1].append(name)
excel[-1].append(v)
excel[-1].append(str(w))
data = DataFrame(excel, columns=['Source', 'Target', 'Weight'])
data.to_csv('edge.csv', encoding='gbk')
print('save file successful!')
if __name__ == '__main__':
v = RelationshipView(TEXT_PATH, DICT_PATH, SYNONYMOUS_DICT_PATH)
v.generate()
先將代碼全部貼出來,大家復制粘貼即可運行也殖,接下來我再慢慢得一行一行仔細講解代碼邏輯土思。
先把程序運行效果圖貼出來。
設計思想
整個程序的實現(xiàn)過程是這樣的忆嗜。
首先己儒,我們預先準備好語料庫。(里面含有小說主人公姓名以及別名霎褐,這樣做的目的降低了程序的難度址愿,不然還要涉及到知識圖譜的實體識別。有關知識圖譜的東西我最近在看冻璃,以后會寫這方面博客)
然后我們將這篇將近三十萬字的小說按段落分開响谓,對每一段進行單獨分析损合,對兩個實體之間的邊的權(quán)重進行計算。具體的說娘纷,它的權(quán)重是如何計算的呢嫁审?比如第一段我們結(jié)合語料庫發(fā)現(xiàn)里面有三個胡蘭成,一個張愛玲赖晶,一個周佛海律适。那么我們就給胡蘭成和張愛玲之間的邊權(quán)重更新為3,張愛玲和周佛海之間更新為1遏插,胡蘭成和周佛海之間更新為3捂贿。然后將每個段落的每兩個點之間的權(quán)重加和,最后寫入excel表中保存為csv格式胳嘲。這是為了方便使用Gephi可視化厂僧。
當然同時我們也進行了節(jié)點大小的計算,其實就是單純計算這個實體名字在文中出現(xiàn)的次數(shù)了牛。比如胡蘭成出現(xiàn)了5752次颜屠,那么它在可視化圖中的大小你可以理解為5752個單位這么大。
代碼詳解
我只講我認為最難懂的部分鹰祸,一些我認為簡單的如果你不懂可以私信評論我都可以甫窟,我一般會很快回復。
我認為最難懂的就是count_person()函數(shù)中的一部分蛙婴,當然這一部分也可以說是精華粗井。
for p in paragraphs:
jieba.load_userdict(self._dict_path)
poss = jieba.cut(p)
self._person_per_paragraph.append([])
for w in poss:
if w not in name_list:
continue
if synonymous.get(w):
w = synonymous[w]
self._person_per_paragraph[-1].append(w)
if self._person_counter.get(w) is None:
self._relationships[w] = {}
self._person_counter[w] += 1
return self._person_counter
首先在一個將小說每段作為一個字符串元素的列表paragraphs中循環(huán),也就是說p是小說中的每一段敬锐。
然后在加載了語料庫之后背传,對該段進行結(jié)巴分詞呆瞻,并將該段分詞結(jié)果存在poss列表中台夺。如果語料庫中的實體在poss中可以找的到,那么我們就從存儲別名字典中依據(jù)這個實體來找他的唯一名字(因為有可能是別名痴脾,我們要統(tǒng)一換成真正的姓名進行加權(quán))颤介。
get()是字典這個類型所有的屬性,在該段代碼中體現(xiàn)為赞赖,判斷該字典中有無含有以w為key的元素滚朵。
大家也許會疑惑,為何self._person_counter是一個字典呢前域?這源于程序一開頭的一行代碼辕近。
self._person_counter = defaultdict(int)
想要理解這個defaultdict的作用,將我下面貼出來的這幾行代碼敲進去試試就知道了匿垄。它其實起到一個給字典的值設置一個默認值的快捷方式移宅。
from collections import defaultdict
person = defaultdict(int)
test_list = ['胡蘭成', '張愛玲', 'Bob', 'Bob', 'Nick', '胡蘭成']
for p in test_list:
person[p] += 1
print(person)
可視化
可視化我是用到了Gephi這個軟件归粉。
PS下載這個軟件需要用到java組件,需要下載一個jre漏峰,聽過來人一句勸告糠悼,千萬不要下載最新的那個10的那個版本。
然后就是Gephi簡單的使用教程浅乔。放幾張網(wǎng)圖倔喂,大家學習一下。