看一個博主(亞當-adam)的關於hanlp關鍵詞提取算法TextRank的文章,仍是很是好的一篇實操經驗分享,分享一下給各位須要的朋友一塊兒學習一下!程序員
TextRank是在Google的PageRank算法啓發下,針對文本里的句子設計的權重算法,目標是自動摘要。它利用投票的原理,讓每個單詞給它的鄰居(術語稱窗口)投同意票,票的權重取決於本身的票數。這是一個「先有雞仍是先有蛋」的悖論,PageRank採用矩陣迭代收斂的方式解決了這個悖論。本博文經過hanlp關鍵詞提取的一個Demo,並經過圖解的方式來說解TextRank的算法。算法
1//長句子函數
2 String content = "程序員(英文Programmer)是從事程序開發、維護的專業人員。" +學習
3 "通常將程序員分爲程序設計人員和程序編碼人員," +編碼
4 "但二者的界限並不很是清楚,特別是在中國。" +spa
5 "軟件從業人員分爲初級程序員、高級程序員、系統" +設計
6 "分析員和項目經理四大類。";blog
最後提取的關鍵詞是:[程序員, 程序, 分爲, 人員, 軟件]element
下面來分析爲何會提取出這5個關鍵詞開發
第一步:分詞
把content 經過一個的分詞算法進行分詞,這裏採用的是Viterbi算法也就是HMM算法。分詞後(固然首先應把停用詞、標點、副詞之類的去除)的結果是:
[程序員, 英文, Programmer, 從事, 程序, 開發, 維護, 專業, 人員, 程序員, 分爲, 程序, 設計, 人員, 程序, 編碼, 人員, 界限, 並不, 很是, 清楚, 特別是在, 中國, 軟件, 從業人員, 分爲, 程序員, 高級, 程序員, 系統分析員, 項目經理, 四大]
第二步:構造窗口
hanlp的實現代碼以下:
Map<String, Set<String>> words = new TreeMap<String, Set<String>>();
Queue<String> que = new LinkedList<String>();
for (String w : wordList)
{
if (!words.containsKey(w))
{
words.put(w, new TreeSet<String>());
}
// 複雜度O(n-1)
if (que.size() >= 5)
{
que.poll();
}
for (String qWord : que)
{
if (w.equals(qWord))
{
continue;
}
//既然是鄰居,那麼關係是相互的,遍歷一遍便可
words.get(w).add(qWord);
words.get(qWord).add(w);
}
que.offer(w);
}
這個代碼的功能是爲分個詞構造窗口,這個詞先後各四個詞就是這個詞的窗口,如詞分詞後一個詞出現了屢次,像[程序員],那就是把每次出現取一次窗口,而後把各次結果合併去重,最後結果是:程序員=[Programmer, 專業, 中國, 人員, 從業人員, 從事, 分爲, 四大, 開發, 程序, 系統分析員, 維護, 英文, 設計, 軟件, 項目經理, 高級]。最後造成的窗口:
1 Map<String, Set<String>> words =
2
3 {Programmer=[從事, 開發, 程序, 程序員, 維護, 英文], 專業=[人員, 從事, 分爲, 開發, 程序, 程序員, 維護], 中國=[從業人員, 分爲, 並不, 清楚, 特別是在, 程序員, 軟件, 很是], 人員=[專業, 分爲, 並不, 開發, 清楚, 界限, 程序, 程序員, 維護, 編碼, 設計, 很是], 從業人員=[中國, 分爲, 清楚, 特別是在, 程序員, 軟件, 高級], 從事=[Programmer, 專業, 開發, 程序, 程序員, 維護, 英文], 分爲=[專業, 中國, 人員, 從業人員, 特別是在, 程序, 程序員, 系統分析員, 維護, 設計, 軟件, 高級], 四大=[程序員, 系統分析員, 項目經理, 高級], 並不=[中國, 人員, 清楚, 特別是在, 界限, 程序, 編碼, 很是], 開發=[Programmer, 專業, 人員, 從事, 程序, 程序員, 維護, 英文], 清楚=[中國, 人員, 從業人員, 並不, 特別是在, 界限, 軟件, 很是], 特別是在=[中國, 從業人員, 分爲, 並不, 清楚, 界限, 軟件, 很是], 界限=[人員, 並不, 清楚, 特別是在, 程序, 編碼, 很是], 程序=[Programmer, 專業, 人員, 從事, 分爲, 並不, 開發, 界限, 程序員, 維護, 編碼, 英文, 設計], 程序員=[Programmer, 專業, 中國, 人員, 從業人員, 從事, 分爲, 四大, 開發, 程序, 系統分析員, 維護, 英文, 設計, 軟件, 項目經理, 高級], 系統分析員=[分爲, 四大, 程序員, 項目經理, 高級], 維護=[Programmer, 專業, 人員, 從事, 分爲, 開發, 程序, 程序員], 編碼=[人員, 並不, 界限, 程序, 設計, 很是], 英文=[Programmer, 從事, 開發, 程序, 程序員], 設計=[人員, 分爲, 程序, 程序員, 編碼], 軟件=[中國, 從業人員, 分爲, 清楚, 特別是在, 程序員, 很是, 高級], 很是=[中國, 人員, 並不, 清楚, 特別是在, 界限, 編碼, 軟件], 項目經理=[四大, 程序員, 系統分析員, 高級], 高級=[從業人員, 分爲, 四大, 程序員, 系統分析員, 軟件, 項目經理]}
第三步:迭代投票
每一個詞最後的投票得分由這個詞的窗口進行屢次迭代投票決定,迭代的結束條件就是大於最大迭代次數這裏是200次,或者兩輪以前某個詞的權重小於某一值這裏是0.001f。看下代碼:
Map<String, Float> score = new HashMap<String, Float>();
//依據TF來設置初值
for (Map.Entry<String, Set<String>> entry : words.entrySet()){
score.put(entry.getKey(),sigMoid(entry.getValue().size()));
}
System.out.println(score);
for (int i = 0; i < max_iter; ++i)
{
Map<String, Float> m = new HashMap<String, Float>();
float max_diff = 0;
for (Map.Entry<String, Set<String>> entry : words.entrySet())
{
String key = entry.getKey();
Set<String> value = entry.getValue();
m.put(key, 1 - d);
for (String element : value)
{
int size = words.get(element).size();
if (key.equals(element) || size == 0) continue;
m.put(key, m.get(key) + d / size * (score.get(element) == null ? 0 : score.get(element)));
}
max_diff = Math.max(max_diff, Math.abs(m.get(key) - (score.get(key) == null ? 0 : score.get(key))));
}
score = m;
if (max_diff <= min_diff) break;
}
System.out.println(score);
return score;
}
投票的原理拿Programmer=[從事, 開發, 程序, 程序員, 維護, 英文],這個詞來講明,Programmer最後的得分是由[從事, 開發, 程序, 程序員, 維護, 英文],這6個詞依次投票決定的,每一個詞投出去的分數是和他自己的權重相關的。
1、投票開始前每一個詞初始化了一個權重,score.put(entry.getKey(),sigMoid(entry.getValue().size())),這個權重是0到1之間,公式是
1 //value是每一個詞窗口的大小
2 public static float sigMoid(float value) {
3 return (float)(1d/(1d+Math.exp(-value)));
4 }
這個函數的公式和圖像以下,由於value必定是大於0的,因此sigMod值屬於(0,1)
初始化後的分詞是:{特別是在=0.99966466, 程序員=0.99999994, 編碼=0.99752736, 四大=0.98201376, 英文=0.9933072, 很是=0.99966466, 界限=0.99908894, 系統分析員=0.9933072, 從業人員=0.99908894, 程序=0.99999774, 專業=0.99908894, 項目經理=0.98201376, 設計=0.9933072, 從事=0.99908894, Programmer=0.99752736, 軟件=0.99966466, 人員=0.99999386, 清楚=0.99966466, 中國=0.99966466, 開發=0.99966466, 並不=0.99966466, 高級=0.99908894, 分爲=0.99999386, 維護=0.99966466}
進行迭代投票,第一輪投票,[Programmer, 專業, 中國, 人員, 從業人員, 從事, 分爲, 四大, 開發, 程序, 系統分析員, 維護, 英文, 設計, 軟件, 項目經理, 高級]依給次*程序員*投票,得分以下:
[Programmer]給[程序員]投票後,[]程序員]的得分:
[專業]給[程序員]投票
這樣[Programmer, 專業, 中國, 人員, 從業人員, 從事, 分爲, 四大, 開發, 程序, 系統分析員, 維護, 英文, 設計, 軟件, 項目經理, 高級]依次給[程序員]投票,投完票後,再給其它的詞進行投票,本輪結束後,判斷是否達到最大迭代次數200或兩輪之間分數差值小於0.001,若是知足則結束,不然繼續進行迭代。
最後的投票得分是:{特別是在=1.0015739, 程序員=2.0620303, 編碼=0.78676623, 四大=0.6312981, 英文=0.6835063, 很是=1.0018439, 界限=0.88890904, 系統分析員=0.74232763, 從業人員=0.8993066, 程序=1.554001, 專業=0.88107216, 項目經理=0.6312981, 設計=0.6702926, 從事=0.9027207, Programmer=0.7930236, 軟件=1.0078223, 人員=1.4288887, 清楚=0.9998723, 中國=0.99726284, 開發=1.0065585, 並不=0.9968608, 高級=0.9673803, 分爲=1.4548829, 維護=0.9946941},分數最高的關鍵詞就是要提取的關鍵詞