Hanlp分詞例項:Java實現TFIDF演算法

adnb34g發表於2018-11-14


演算法介紹

最近要做領域概念的提取, TFIDF 作為一個很經典的演算法可以作為其中的一步處理。

關於 TFIDF 演算法的介紹可以參考這篇部落格 http://www.ruanyifeng.com/blog/2013/03/tf-idf.html

計算公式比較簡單,如下:

預處理

由於需要處理的候選詞大約後 3w+ ,並且語料文件數有 1w+ ,直接挨個文字遍歷的話很耗時,每個詞處理時間都要一分鐘以上。

為了縮短時間,首先進行分詞,一個詞輸出為一行方便統計,分詞工具選擇的是 HanLp

然後,將一個領域的文件合併到一個檔案中,並用 “$$$” 識別符號分割,方便記錄文件數。

下面是選擇的領域語料( PATH 目錄下):

程式碼實現

package edu.heu.lawsoutput;

import java.io.BufferedReader;

import java.io.BufferedWriter;

import java.io.File;

import java.io.FileReader;

import java.io.FileWriter;

import java.util.HashMap;

import java.util.Map;

import java.util.Set;

 

/**

 * @ClassName: TfIdf

 * @Description: TODO

 * @author LJH

 * @date 2017 11 12 日 下午 3:55:15

 */

 

public class TfIdf {

 

    static final String PATH = "E:\\corpus"; // 語料庫路徑

 

    public static void main(String[] args) throws Exception {

 

        String test = " 離退休人員 "; // 要計算的候選詞

 

        computeTFIDF(PATH, test);

 

    }

 

    /**

    * @param @param path 語料路經

    * @param @param word 候選詞

    * @param @throws Exception

    * @return void

    */

    static void computeTFIDF(String path, String word) throws Exception {

 

        File fileDir = new File(path);

        File[] files = fileDir.listFiles();

 

        // 每個領域出現候選詞的文件數

        Map<String, Integer> containsKeyMap = new HashMap<>();

        // 每個領域的總文件數

        Map<String, Integer> totalDocMap = new HashMap<>();

        // TF = 候選詞出現次數 / 總詞數

        Map<String, Double> tfMap = new HashMap<>();

 

        // scan files

        for (File f : files) {

 

            // 候選詞詞頻

            double termFrequency = 0;

            // 文字總詞數

            double totalTerm = 0;

            // 包含候選詞的文件數

            int containsKeyDoc = 0;

            // 詞頻文件計數

            int totalCount = 0;

            int fileCount = 0;

            // 標記檔案中是否出現候選詞

            boolean flag = false;

 

            FileReader fr = new FileReader(f);

            BufferedReader br = new BufferedReader(fr);

            String s = "";

 

            // 計算詞頻和總詞數

            while ((s = br.readLine()) != null) {

                if (s.equals(word)) {

                    termFrequency++;

                    flag = true;

                }

 

                // 檔案識別符號

                if (s.equals("$$$")) {

                    if (flag) {

                        containsKeyDoc++;

                    }

                    fileCount++;

                    flag = false;

                }

                totalCount++;

            }

 

            // 減去檔案識別符號的數量得到總詞數

            totalTerm += totalCount - fileCount;

            br.close();

            // key 都為領域的名字

            containsKeyMap.put(f.getName(), containsKeyDoc);

            totalDocMap.put(f.getName(), fileCount);

            tfMap.put(f.getName(), (double) termFrequency / totalTerm);

 

            System.out.println("----------" + f.getName() + "----------");

            System.out.println(" 該領域文件數: " + fileCount);

            System.out.println(" 候選詞出現詞數: " + termFrequency);

            System.out.println(" 總詞數: " + totalTerm);

            System.out.println(" 出現候選詞文件總數: " + containsKeyDoc);

            System.out.println();

        }

        

        // 計算 TF*IDF

        for (File f : files) {

 

            // 其他領域包含候選詞文件數

            int otherContainsKeyDoc = 0;

            // 其他領域文件總數

            int otherTotalDoc = 0;

 

            double idf = 0;

            double tfidf = 0;

            System.out.println("~~~~~" + f.getName() + "~~~~~");

 

            Set<Map.Entry<String, Integer>> containsKeyset = containsKeyMap.entrySet();

            Set<Map.Entry<String, Integer>> totalDocset = totalDocMap.entrySet();

            Set<Map.Entry<String, Double>> tfSet = tfMap.entrySet();

 

            // 計算其他領域包含候選詞文件數

            for (Map.Entry<String, Integer> entry : containsKeyset) {

                if (!entry.getKey().equals(f.getName())) {

                    otherContainsKeyDoc += entry.getValue();

                }

            }

 

            // 計算其他領域文件總數

            for (Map.Entry<String, Integer> entry : totalDocset) {

                if (!entry.getKey().equals(f.getName())) {

                    otherTotalDoc += entry.getValue();

                }

            }

 

            // 計算 idf

            idf = log((float) otherTotalDoc / (otherContainsKeyDoc + 1), 2);

 

            // 計算 tf*idf 並輸出

            for (Map.Entry<String, Double> entry : tfSet) {

                if (entry.getKey().equals(f.getName())) {

                    tfidf = (double) entry.getValue() * idf;

                    System.out.println("tfidf:" + tfidf);

                }

            }

        }

    }

 

    static float log(float value, float base) {

        return (float) (Math.log(value) / Math.log(base));

    }

}

 

執行結果

測試詞為 離退休人員 ,中間結果如下:

最終結果:

結論

可以看到 離退休人員 在養老保險和社保領域, tfidf 值比較高,可以作為判斷是否為領域概念的一個依據。當然 TF-IDF 演算法雖然很經典,但還是有許多不足,不能單獨依賴其結果做出判斷。很多論文提出了改進方法,本文只是實現了最基本的演算法。如果有其他思路和想法歡迎討論。

 

 


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

相關文章