用Hadoop分析金庸人物關系網
--- 用大數據粗略的分析金庸人物關系網
整體結果報告
達到預期目標並完成了選做內容
實驗目標描述:
金庸的江湖
課程設計目標
通過一個綜合數據分析案例:”金庸的江湖--金庸武俠小說中的任務關系挖掘“,來學習和掌握MapReduce程序設計。通過本課程設計的學習,可以體會如何使用MapReduce完成一個綜合性的數據挖掘任務,包括全流程的數據預處理、數據分析、數據后處理等。
涉及的編程技能:
1.Hadoop中使用第三發JAR包輔助分析
2.掌握簡單的MapReduce算法設計:
a) 單詞同現算法;
b) 數據整理與歸一化算法
c) 數據排序(選做)
3.掌握帶有迭代特性的MapReduce算法設計:
a) PageRank算法
b) 標簽傳播(Label Propagation)算法(選座)
...etc 具體見課程設計要求
編寫環境
編寫環境: VMware WorkStation 11 Ubuntu Hadoop 2.7.1 IDEA
測試編譯環境: macOS Sierra 10.12.6 IDEA 2017
實際運行集群: 南京大學計算機系pasa大數據集群
編譯依賴包:
ansj_seg-5.1.3.jar
nlp-lang-1.7.3.jar
hadoop-common-2.7.1.jar
hadoop-dfs-2.7.1.jar
hadoop-mapreduce-client-core-2.7.1.jar
帶依賴打包運行在2.7.1的hadoop集群即可
實驗進展流程圖:
Task | Main |
---|---|
Task1 | 提取人名,清理無關文本(使用已提供的名字冊) |
Task2 | 人名同現統計 |
Task3 | 人物關系圖構建與特征歸一化 |
Task4 | 基於人物關系圖的PageRank計算 |
Task5 | 數據分析:在人物關系圖上的標簽傳播 並根據結果染色 |
Task6 | 分析結果整理 |
實現代碼模塊
Module | Function |
---|---|
數據預處理 | Task1-Task3 |
數據處理-PageRank | Task4 |
數據處理-LPA | Task5 |
代碼注解:
-
Txt2name:
- TransferTask.java: 實現數據與處理方面的mapreduce的job設置,同時將人名列表放入cache中,方便后續的讀取。
- TransferMapper.java: 完成自定義詞典的配置,進行中文分詞操作,提取出其中詞性為Name的分詞(在自定義詞典時,將所有要提取的任務名稱的詞性均置為Name),提取出人名同現鍵對,以此作為reducer函數的輸入。其中<value,key>為輸出,value為人名,key為與其同現過的人名。
- TransferReducer.java: 以mapper的輸出鍵對作為輸入,將同樣的鍵值對進行合並輸出類似於<(a,b),time>的鍵對,以a為主要人物,進行下一步的權重歸一化操作,根據不同鍵值對出現次數的不同,計算權重,將輸出格式設為”人名1 人名2:權重2,人名3:權重3……”,各權重值之和為1。
-
PageRank:
- PageRankDriver.java: 對外接口函數,可通過操作此函數來實現對其余功能的調用,並實現驅動多趟循環的功能
- GraphBuilder.java: 該java文件主要進行初始化操作,輸入的<key,value>為<文本行號,文本內容>,對之進行處理,賦予初始的pr值輸出為key:關系有向圖節點人名,value:帶pr值的從屬人名
- PageRankIter.java: 此java文件包含了pagerank功能函數,,對初始化的pr的值進行迭代工作,參考書上內容后我們設置為迭代次數固定為30次,不對其進行是否收斂的判斷以減少工作量。其中mapper函數的輸入為上一輪迭代的輸出,<key,value>中key為行號,value為每一行的內容,輸出<key,value>中,key為人物名,value為更新后的pr值。將mapper的輸出傳入reducer函數中,輸出新一輪迭代后的文件,格式為:”人名1 PR值 人名2:權重2,人名3:權重3,……”。
- PageRankViewer.java: 主要針對最后一次迭代的結果進行處理,調整其存儲結構,只提取<人名,pr值>,將其輸出到txt文件中,其中FinalRank中存儲最后的PageRank結果。除此之外,在次函數中同時實現了對所有人名pr值的排序工作,將其從大到小一次排序。
-
TagVisual:
- LPADriver.java: 為對外接口函數,調用此函數可以完成對LPA算法的調用,同時為滿足多次迭代操作,在其中設置for循環,實現多趟操作功能。
- GraphBuilder.java: 對分詞后的輸出結果進行處理,將其格式設置為:”人名1 Tag |人名2:權重2:Tag2,人名3:權重3:Tag3,……”,在初始化時每個人物的標簽設置為其本身。
- LPA.java: 此文件實現了LPA算法,將上一次迭代的輸出文件作為下一次的輸入文件,根據權重調整人名1的Tag值,並將其傳送給與其相關的人物,監督其也調整Tag值。
- TagViewer.java: 在LPADriver.java中設置迭代次數為10次,因此TagViewer.java的輸入文件為LPA迭代十次后的輸出文件。首先自定義一個map存儲結構,選取14篇文章中的”主人公”,將其Tag置為114后存入map中。對輸入文件進行提取工作,(人名,Label),其中Label值為114。此Project主要為可視化操作提供文件輸入。
-
Gephi:
- 針對1模塊輸出的txt用python處理得到邊的csv,通過2,3模塊得到的pr和lpa tag處理得到點的csv。導入后稍加調整即可獲得所需圖像
具體模塊內實現細節:
- Module1 沒什么好說的,借助外部jar還是很好寫的
- Module2 這部分的pagerank迭代參照書上MapReduce算法部分,結構相同,也沒什么好說的,關鍵就是處理好map和reduce以及定義好處理的數據格式。實現上還需要注意pagerank的溢出和泄露問題,加個判斷語句即可。
- Module3 這部分debug時間較長細節處會說
Module1:
就不給出詳細描述了,詳見作業設計中
這里僅僅給出應有的輸入輸出的圖片
對於的處理結果可見處理的中間結果
Module2:
PageRank算法部分:
注意預防Rank Sink和Rank Leak
針對這兩個問題,我們采取將表達式變為
pagerank = (double) (1 - 0.8) + 0.8 * pagerank;這種形式可以避免以上的問題(可以說阻尼系數是0.8)
- 一般來講這個松弛系數取0.85,PRsum就是不同貢獻值的和。最后,做一步清理工作,一是只保留人名和PageRank值,二是利用mapreduce自帶的排序,將PageRank值作為主鍵,得到一個從高到低排序的人名表。
數據格式 :
狀態 | 數據格式 |
---|---|
Module1執行后的結果 | 一燈大師 啞梢公:0.00234192037470726,魯有腳:0.00936768149882904,楊康:0.00702576112412178,耶律燕:0...等 |
PageRank初始化 | 一燈大師 1.0 啞梢公:0.00234192037470726,魯有腳:0.00936768149882904,楊康:0.00702576112412178,耶律燕:...等 |
PageRank Mapper 給所有從屬節點發出 | 針對從屬節點pagekey,value=weight*pr |
PageRank Mapper 針對節點本身發出 | 一燈大師 1.0 |啞梢公:0.00234192037470726,魯有腳:0.00936768149882904,楊康:0.00702576112412178,耶律燕:...等 |
PageRank Reduce | 接受處理以上的同一個key的數據,當遇到直接的節點,權值,通過阻尼系數計算后的pagerank值相加,用於更新這個key的新的pr值,而加上|標識符號的串作為下一輪開始的初始串。 |
我們在這里一共迭代了30次(實際上15次基本就穩定了)
Module3
這個部分本來不應該有什么問題的,實際上卻遇到了一個很嚴重的問題
這部分的算法基本參照Wayne1234他們的,我認為他們描述的比較好:
- 標簽傳播(Label Propagation)是一種半監督的圖分析算法,他能為圖上的頂點打標簽,進行圖頂點的聚類分析,從而在一張類似社交網絡圖中完成社區發現(Community Detection)。該任務中,我們用每個聚類的核心人物作為這個類的標簽,比如下面輸出中,“張無忌”實際上是一個標簽名,表示“楊逍”屬於張無忌這個類。
- 實現思路: LPA需要迭代,因此我們采用了和PageRank差不多的方式做了初始化,初始化文件的格式如下:
一燈大師 一燈大師&喬寨主,0.0022988506,喬寨主;華箏,0.0022988506,華箏;……
- 這里,“一燈大師”是key,value中,“&”前面的“一燈大師”表示key對應的標簽,我們的初始化標簽均和自己相同。后面,“喬寨主”是人名,中間數字是它的權重,后一個“喬寨主”是它的標簽,在迭代的時候我們會更新這個標簽,這里初始化的時候使用的是自己的名字。迭代過程中最關鍵的是如何選取新的標簽,遍歷“&”后面的“人名,權值,標簽”列表,我們要統計出那個貢獻值最高的標簽。舉例:
一燈大師 一燈大師&喬寨主,0.0022988506,喬寨主;華箏,0.0022988506,華箏;……
- 假設我們現在正在統計的就是這一行數據,在解析“&”后面的字符串時,除了第一次迭代之外,會出現多個人對應同一個標簽的情況,因此需要一個數組來存儲每個標簽在當前字符串中的總權值,如果該標簽還沒出現,那把該標簽新家進去,該標簽的權值作為總權值的一個初值。比如說現在的這個數組是空的,我發現“喬寨主”的標簽是“喬寨主”,那么這個數組里就應該添加一條記錄:“喬寨主”這個標簽的當前權值是0.00229…,后面還有“喬寨主”的話,直接用那個權值加上當前的“喬寨主”的總權值即可。我們還需要一個數組記錄下這整個字符串中的所有人名,這個后面會用到。現在我們已經有了一組“標簽,權重”的記錄,因此從中挑選權重最高的作為key的標簽就可以了,注意輸出到reduce的時候,為了保持迭代時文件格式一致,應當將這個長字符串一起發送給reduce。我們還有一件事要做,告訴所有和當前key相關的其他key,當前key對應的標簽變了,所以我們輸出遍歷之前記錄了整個字符串中所有人名的數組,以單個人名為key,將更新過的當前的key和標簽作為value也輸出到reduce以供reduce更新。Reduce過程做的工作就是更新長字符串中的標簽,最后按照統一格式寫到文件。最后還有一步清理工作,只保留標簽和人名輸出一個最終文件即可。
這個地方的嚴重BUG
“有些人走着走着就散了”,在我們第一個版本的LPA迭代,一輪迭代后,1283行剩下了1178行,很多影響力大的人物后面的后綴加起來都沒有1,很是奇怪。看了下我們的代碼認為實現沒有問題
先說說我們定義的數據格式在Map和Reduce階段的變化:
- Mapper: 主串 Key newLabel | lineAfterKey(即原有從屬權值信息等)
- Mapper: 通知更改串 Keylink & key : newLabel 其中keylink為從屬人物 key為主標簽 (這里其實有沒有更新都會發送更改信息的)
- Reduce: 拿下主串,記錄下原有從屬權值信息
- Reduce: 根據通知更改串,更改標簽為最新標簽
詳細的執行方式和上面描寫的是一致的,而特別的識別符也是&
后面DeBug修改的是Reduce階段的一個策略
初始的策略是:
Reduce階段,首先取最長的那個基礎串,記錄下他們的權值(用hash記錄)(因為另一種更新串的數據格式是 [ key & 原有key 已更改的新標簽] 采取的被動更新,就是遍歷reduce發過來的所有串,解析到&就更新一個新的子節點加進去。
結果就是有的人走着走着就散了,幾次迭代過后就沒數據了。
后期修改的策略:
Reduce階段,首先取最長的那個基礎串,記錄下他們的權值(用hash記錄)(因為另一種更新串的數據格式是 [ key & 原有key 已更改的新標簽] 采取的主動更新,遍歷主串的從屬串,從解析到&的字符串中更新標簽。這樣就保證了一個都不會少
現在能確保得到結果可是任然不知道初始策略錯在了哪里
這邊的問題其實到現在還沒想清楚,知道為什么的麻煩在issue中告知
Gephi部分的處理
起初在lpa標簽傳播階段沒有分類的,只是把最后每個人對應的標簽打印處理,通過一個簡單的python程序統計下一共多少種,之后給它分類到各個作品里,再寫一個函數重新跑一標簽使自動對應作品標號
Gephi需要的邊和點的數據也不是很困難,通過python可以輕松的處理,關鍵是Gephi顯示的數據有些迷,之前有幾次一直把標簽當做了主要的區別符號輸出,導致了一大片一大片的標簽名散在上面。后來調整里幾下就好了
實驗結果
Gephi總體結果:
更換參數獲得的新聚類圖: 說明我們的標簽傳播算法還是准確的
主要角色:
一些局部圖 鹿鼎記
神雕俠侶
神雕俠侶和射雕英雄傳的交界處
圖中一些連接處 這邊可以看出沒有了其實應該占比較高的說不得什么的,因為在模塊一中考慮了這一點特殊處理了一些只在某些作品中有意義的人物名
更多細節可以參見Gephi文件夾中的gephi文件
優化與總結
- 在分詞部分,主要時間花在了外部依賴jar包的調整上,由於沒有找到助教推薦的版本,采用了最新的版本,但在最新版本中,ansj_seg的函數接口與原版的略有不同。從最后的分詞結果來看,可以認為此次實驗中的分詞工作基本完成,對人物權重進行歸一化處理后,文件結構較為清晰,為之后的功能提供了一個較好的輸入。
- 起初在人物中沒有刪除說不得等可變成常用語句的角色名,導致大漢、漢子、說不得這類詞本來在某部作品中的人物可能到處出現,后來發現還有“農婦”,“胖婦人”“啞巴這種”,妥善的處理方式我認為是在之前加上作品前綴,就不會有滑稽的相關聯了,因為這種角色名不可能是連着幾部作品的。
- LPA標簽傳播部分遇到過一個很大的問題,錯誤處在第一次迭代上,初始化結束之后,第一輪迭代結果人物剩下了1180幾個,而原來1283個,真的是“有些人走着走着就散了”,肯定是reduce出了問題,當時的策略是:map階段,更新節點標簽,同時產生一個通知所有相關節點的數據,在reduce階段,遍歷通知節點添加后綴產生新的字符串。這是一個被動的方式,到了凌晨3點多想到了解決方法,主動更新,也就是我們記錄下所有的后綴,遍歷后綴匹配,強制更新每個后綴的標簽嗎,這樣果然就沒有人散了。
- 大數據集群的運行狀態資源分布有時候也會影響運行結果,舉個例子,15號凌晨的時候有兩個小組和我們一起跑,結果那次運行跑掛了,我們認為是代碼問題,改了半天沒發現錯誤,再次跑一遍就是正確的。
- 本次的結果優化,由於做實驗起初就看到了比較好的方案,所以也沒自己優化,要是編個起初怎么樣,后期怎么樣也沒意思。集群的性能還是很強大的,我們的程序只有map和reduce,如果進一步優化其實可以再謝謝partition之類的?不過不影響最后結果。
Contributer:
ID | Task |
---|---|
Ericyz | 組長,主要完成LPA部分Debug,數據可視化處理,實驗報告總結,Github編寫Wiki。 |
你愛上的我 | 主要完成PageRank–MapReduce算法設計,LPA-MapReduce算法設計。 |
MissAngel | 主要完成小說中文分詞功能,LPA部分Debug,實驗報告撰寫。 |
福克斯四世 | 主要完成小說中文分詞功能,實驗報告完善,jar包編譯調試。 |
Reference:
金庸的江湖,人物關系分析 Jinyong-Novels-Analysis -Wayne1234 標簽算法部分描述很好
基於15本金庸小說的人物關系圖挖掘 MapReduce-JinYongsWorld -sunfvrise 代碼框架結構很好 分成三個模塊
《深入理解大數據》機械工業出版社,黃宜華,苗凱翔
PIG標簽傳播算法和超級英雄們的社區發現