自然語言處理工具hanlp關鍵詞提取圖解TextRank演算法

adnb34g發表於2019-02-20


 

看一個博主(亞當 -adam)的關於hanlp關鍵詞提取演算法TextRank的文章,還是非常好的一篇實操經驗分享,分享一下給各位需要的朋友一起學習一下!

TextRank是在Google的PageRank演算法啟發下,針對文字里的句子設計的權重演算法,目標是自動摘要。它利用投票的原理,讓每一個單詞給它的鄰居(術語稱視窗)投贊成票,票的權重取決於自己的票數。這是一個“先有雞還是先有蛋”的悖論,PageRank採用矩陣迭代收斂的方式解決了這個悖論。 本博文透過 hanlp關鍵詞提取的一個Demo,並透過圖解的方式來講解TextRank的演算法。

 

1 //長句子

2        String content = "程式設計師(英文Programmer)是從事程式開發、維護的專業人員。" +

3                 "一般將程式設計師分為程式設計人員和程式編碼人員," +

4                 "但兩者的界限並不非常清楚,特別是在中國。" +

5                 "軟體從業人員分為初級程式設計師、高階程式設計師、系統" +

6                 "分析員和專案經理四大類。";

 

最後提取的關鍵詞是: [程式設計師, 程式, 分為, 人員, 軟體]

 

  下面來分析為什麼會提取出這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},分數最高的關鍵詞就是要提取的關鍵詞

 

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31524777/viewspace-2636423/,如需轉載,請註明出處,否則將追究法律責任。

相關文章