基於共現網路的人物關係圖的繪製
源實驗用的是《釜山行》的中文劇本,其實我們換成其他任何一部小說,結果也會變得超級有趣。這裡為了還原實驗效果,數據集仍然不變。
- 《釜山行》是一部喪屍災難片,我們使用python對《釜山行》劇本的人物進行關係提取操作,最終利用Gephi軟體對提取的人物關係繪製人物關係圖。這是其中一段劇本:
秀安的卧室 晚上 石宇推開秀安的卧室門,秀安躲在被子里正在和媽媽通著電話。 秀安:我自己一個人能坐火車。為什麼,媽媽你來車站接我不就行了嗎。 石宇故意敲敲房門提醒秀安他回來了,然後開了房間的燈,看到床上秀安躲在被子里的形狀。 秀安:媽媽,掛了。 秀安從被子里鑽出來,生著石宇的氣。 石宇坐到床邊:沒關係,接著打吧。 秀安:已經掛了。 石宇:媽媽說你想去釜山。 秀安默認。 石宇:秀安,爸爸最近事可多了。下周好像可以。秀安不能理解一下嗎? 秀安沒回答,抗議著。 石宇想到自己還有禮物,遞給了秀安:你肯定以為我忘了吧。生日快樂。 秀安拿著禮物發愣。 石宇:愣著幹嗎,快點拆開啊。 秀安拆開禮物,發現是一個 wii 遊戲機,又沉默了。 石宇:怎麼了,不喜歡嗎? 秀安看向書桌,石宇順光過去,依然桌上有一台 wii 遊戲了。 石宇這才發現自己失誤了,有點懊惱。 秀安:這次是兒童節收到的。 秀安很失落,石宇也很愧疚。 石宇:那……別的,你有沒有什麼想要的。 秀安:釜山。我想去找媽媽。明天。 石宇:剛才不是說了嘛,等爸爸有時間了,下次。 秀安:不行,明天。每次你都說下一次,明擺著又是騙我。 石宇想說什麼,秀安打斷了他。 秀安:我不會佔用爸爸時間的。我自己一個人能去。 石宇無奈的表情。
- 通過NLPIR分詞系統獲取的實體名稱,效果很差,考慮棄用; 通過jieba分詞系統獲取人物名稱,結果為空;獲取實體名稱,效果很差,考慮棄用; 貼上部分結果:
[(車廂, 98), (喪屍, 87), (人, 47), (廁所, 33), (常務, 30), (火車, 29), (珍熙, 29), (屍變, 24), (門, 22), (電話, 17), (乘客, 15), (隧道, 14), (列車, 14), (玻璃, 13), (少女, 13), (金, 11), (乘務長, 11), (站台, 11), (列車長, 10), (媽媽, 10), (座位, 9), (婆婆, 8), (節車廂, 8), (艙門, 8), (火車頭, 8), (乘務員, 7), (時候, 7), (隊員, 7), (手機, 7), (火車站, 6), (棒球, 6), (棒球隊, 6), (目光, 6), (頭, 6), (視頻, 6), (鐵軌, 6), (眾人, 6), (駕駛室, 5), (內, 5), (手, 5), (對面, 5), (列車員, 5), (榮國, 4), (人群, 4), (土, 4), (表情, 4), (繩子, 4), (大家, 4), (車門, 4), (信號, 4), (旅客, 4), (電視, 4), (車載, 4), (有點, 4), (司機, 4), (過道, 4), (石宇, 4), (床上, 4), (狀態, 3), (卧室, 3), (結果, 3), (代理, 3), (區, 3), (貨車, 3), (車窗, 3), (腿, 3), (畫面, 3), (對講機, 3), (傷心, 3), (海英, 3), (被子, 3), (大叔, 3), (神色, 3), (問題, 2), (臉色, 2), (身體, 2), (防線, 2), (時尚, 2), (汽車, 2), (房間, 2), (對準, 2), (剎車, 2), (鋼化, 2), (路, 2), (關門, 2), (時, 2), (眼神, 2), (成群, 2), (目標, 2), (公路, 2), (洞口, 2), (棒球棒, 2), ......]
- 然後就迷茫了.. 這找出來的東西和想像中的完全不一樣啊... 只好回去看電影。看完電影,很輕鬆的就把人物找齊了... 然後我們把結果放到一個列表中:
[石宇, 盛京, 珍熙, 露宿者, 尚華, 秀安, 金常務, 列車長, 金代理, 組長, 石宇的媽媽, 乘務長, 海英, 少女, 隊員, 榮國, 士兵, 組長, 奶奶, 老婆婆, 土婆婆, 鍾吉, 喪屍]
- 這之後,我們在文中找共現關係。共現關係,在我看來就是在一段話中前後有關聯的實體之間的關係。為了確定這種關係,我們可以按小節、段落或者句子劃分結構來找這種關係。方便起見,我們在一個句號內,尋找實體之間的關係。
# calc relationsrelations = {}f = (x for x in codecs.open(fushan.txt, r, utf-8))for line in f: line = line.strip() if not line: continue segment = line.split(。) for content in segment: # put into set relation = set() for name in names: if name in content: relation.add(name) if len(relation) <= 1: continue # process set to list temp = [] for x in relation: temp.append(x) relation = temp # process relations for index, i in enumerate(relation): for j in relation[index + 1:]: key = str(i) + + str(j) if not relations.get(key): relations[key] = 1 else: relations[key] += 1relations = sorted(relations.items(), key=lambda x: x[1], reverse=True)
[(石宇 秀安, 52), (石宇 尚華, 38), (石宇 喪屍, 28), (盛京 尚華, 19), (石宇 盛京, 18), (珍熙 榮國, 17), (喪屍 尚華, 16), (石宇 榮國, 16), (石宇 石宇的媽媽, 12), (秀安 露宿者, 10), (尚華 榮國, 10), (盛京 秀安, 9), (金常務 喪屍, 9), (喪屍 榮國, 7), (盛京 喪屍, 7), (石宇 露宿者, 7), (秀安 喪屍, 6), (盛京 露宿者, 6), (少女 海英, 6), (秀安 尚華, 6), (石宇 金代理, 6), (秀安 盛京, 6), (組長 海英, 6), (露宿者 喪屍, 5), (珍熙 隊員, 5), (金常務 乘務長, 4), (列車長 金常務, 4), (土婆婆 鍾吉, 3), (隊員 榮國, 3), (石宇 金常務, 3), (乘務長 海英, 3), (珍熙 金常務, 3), (金常務 榮國, 3), (金代理 組長, 3), (秀安 石宇的媽媽, 3), (珍熙 乘務長, 3), (金常務 秀安, 3), (隊員 少女, 2), (珍熙 少女, 2), (露宿者 尚華, 2), (乘務長 喪屍, 2), (珍熙 石宇, 2), (金常務 盛京, 2), (石宇 組長, 2), (秀安 榮國, 2), (珍熙 喪屍, 2), (金常務 土婆婆, 1), (盛京 榮國, 1), (盛京 石宇, 1), (榮國 石宇, 1), (喪屍 海英, 1), (露宿者 土婆婆, 1), (少女 榮國, 1), (土婆婆 喪屍, 1), (盛京 土婆婆, 1), (乘務長 鍾吉, 1), (秀安 奶奶, 1), (土婆婆 盛京, 1), (盛京 士兵, 1), (喪屍 士兵, 1), (土婆婆 秀安, 1), (土婆婆 石宇, 1), (少女 喪屍, 1), (秀安 土婆婆, 1), (石宇 海英, 1), (喪屍 鍾吉, 1), (露宿者 榮國, 1), (組長 喪屍, 1), (隊員 喪屍, 1), (珍熙 鍾吉, 1), (列車長 喪屍, 1), (金常務 石宇, 1), (列車長 乘務長, 1), (喪屍 露宿者, 1), (秀安 石宇, 1), (土婆婆 榮國, 1), (金常務 鍾吉, 1)]
- 我們可以通過上述結果,計算每一個實體的「重量」,或者說,重要度。想像一下將上述元組拆開為 i[0], i[1],將後者的值加到前者的兩個實體中。這樣就可以計算出節點的權。在圖(數據結構)中,我們經常將重要度描述為度中心性,具體可以劃分為帶權或不帶權的度中心性。
- 這之後,我們利用networkx繪製網路。
g = networkx.Graph()edge_list = [x[0] for x in relations]node_list = []for info in sorted(names_count.items(), key=lambda x: x[1]): node_list.append(info[0])for node in node_list: g.add_node(node)for index, i in enumerate(edge_list): g.add_edge(i.split( )[0], i.split( )[1])print(Nodes:, g.number_of_nodes())print(g.nodes())print(Edges:, g.number_of_edges())print(g.edges(),
)
- 利用networkx內置演算法,進行小型社區尋找。
# calc comscoms = []for k in range(2,10): long_list = list(networkx.k_clique_communities(g, k)) for i in long_list: if (1 < len(i) < 10) and (i not in coms): coms.append(i)coms = sorted(coms, key=lambda x:len(x), reverse=True)coms_count = len(coms)print(Communities finds : , coms_count)for i in coms: print(i)print(
)###########################Communities finds : 4frozenset({土婆婆, 金常務, 石宇, 秀安, 露宿者, 榮國, 尚華, 盛京, 喪屍})frozenset({隊員, 少女, 榮國, 喪屍, 珍熙})frozenset({乘務長, 金常務, 鍾吉, 喪屍, 珍熙})frozenset({海英, 組長, 石宇, 喪屍})
- 這裡插播一句... 那個啥,突然就覺得igraph要比networkx好用... networkx只有幾個可憐巴巴的函數,連力引導布局演算法和一些常見的社區劃分函數都不能實現,真是心慌。
- 最後,自己找的事,就自己解決吧... 我們依靠python的一些特徵,還是可以很快的完成作圖的,如下:
plt.figure(figsize=(20, 8))plt.subplot(121)networkx.draw(g, pos=networkx.spring_layout(g, k=0.5, iterations=150), labels={x:x for x in g.nodes() if x in g.nodes()}, node_size=[100 * math.sqrt(dict(names_count).get(x)) for x in g.nodes()], width_=[(dict(relations).get(str(x[1]) + + str(x[0]))) / 6 if (dict(relations).get(str(x[1]) + + str(x[0]))) else (dict(relations).get(str(x[0]) + + str(x[1]))) / 6 for x in g.edges()], alpha=0.8, arrows=False, )plt.title(基於共現網路的電影演員關係圖, fontsize=24)plt.axis(on)# calc colorsprint(各個節點顏色劃分 : )color_enum = [w, y, r, b]color_list = {}print(dict([(node, color_enum[coms.index(x)]) for x in coms for node in g.nodes() if node in x]),
)# plotplt.subplot(122)networkx.draw(g, pos=networkx.spring_layout(g, k=0.5, iterations=20), labels={x:x for x in g.nodes() if x in g.nodes()}, node_color=[dict([(node, color_enum[coms.index(x)]) for x in coms for node in g.nodes() if node in x]).get(node) if dict([(node, color_enum[coms.index(x)]) for x in coms for node in g.nodes() if node in x]).get(node) else w for node in g.nodes()], # jet hot reds cmap=plt.cm.jet, node_size=[100 * math.sqrt(dict(names_count).get(x)) for x in g.nodes()], width_=[(dict(relations).get(str(x[1]) + + str(x[0]))) / 6 if (dict(relations).get(str(x[1]) + + str(x[0]))) else (dict(relations).get(str(x[0]) + + str(x[1]))) / 6 for x in g.edges()], alpha=0.8, arrows=False, )plt.title(社區關係圖, fontsize=24)plt.axis(on)input(
)plt.show()
- 稍微擴展一下,我們都計算了社區和節點質量,那我們還可以畫小型社區,通過這種結構,我們可以大致的了解電影人物中那幾個關係較為緊密... 。
- 不過通過觀察,發現這個劃分演算法對這個圖不太適用,那我們還是導出數據用gephi繪製美觀高效,最後的結果如下:
- 一些用到的材料和源代碼在這裡可以下載:鏈接:http://pan.baidu.com/s/1slbDGc9 密碼:6afd
推薦閱讀:
※Gephi在社交網路數據分析中的應用探討|鏑次元沙龍分享系列
※介紹用Gephi進行數據可視化
※Gephi繪製微博轉發圖譜:以「@老婆孩子在天堂」為例
※數據可視化工具Gephi在社交網路數據分析中的運用| 沙龍分享
※gephi安裝好了,為何打不開?