算法介紹
概念
tf-idf(term frequency–inverse document frequency)是一種用于資訊檢索與資訊探勘的常用加權技術。tf-idf是一種統計方法,用以評估一字詞對于一個文件集或一個語料庫中的其中一份文件的重要程度。字詞的重要性隨著它在文件中出現的次數成正比增加,但同時會隨著它在語料庫中出現的頻率成反比下降。tf-idf加權的各種形式常被搜尋引擎應用,作為文件與用戶查詢之間相關程度的度量或評級。除了tf-idf以外,因特網上的搜尋引擎還會使用基于連結分析的評級方法,以確定文件在搜尋結果中出現的順序。
原理
在一份給定的文件里,詞頻(termfrequency,tf)指的是某一個給定的詞語在該文件中出現的次數。這個數字通常會被歸一化(分子一般小于分母區別于idf),以防止它偏向長的文件。(同一個詞語在長文件里可能會比短文件有更高的詞頻,而不管該詞語重要與否。)
逆向文件頻率(inversedocumentfrequency,idf)是一個詞語普遍重要性的度量。某一特定詞語的idf,可以由總文件數目除以包含該詞語之文件的數目,再將得到的商取對數得到。
某一特定文件內的高詞語頻率,以及該詞語在整個文件集合中的低文件頻率,可以產生出高權重的tf-idf。因此,tf-idf傾向于過濾掉常見的詞語,保留重要的詞語。
tfidf的主要思想是:如果某個詞或短語在一篇文章中出現的頻率tf高,并且在其他文章中很少出現,則認為此詞或者短語具有很好的類別區分能力,適合用來分類。tfidf實際上是:tf*idf,tf詞頻(termfrequency),idf反文檔頻率(inversedocumentfrequency)。tf表示詞條在文檔d中出現的頻率(另一說:tf詞頻(termfrequency)指的是某一個給定的詞語在該文件中出現的次數)。idf的主要思想是:如果包含詞條t的文檔越少,也就是n越小,idf越大,則說明詞條t具有很好的類別區分能力。如果某一類文檔c中包含詞條t的文檔數為m,而其它類包含t的文檔總數為k,顯然所有包含t的文檔數n=m+k,當m大的時候,n也大,按照idf公式得到的idf的值會小,就說明該詞條t類別區分能力不強。(另一說:idf反文檔頻率(inversedocumentfrequency)是指果包含詞條的文檔越少,idf越大,則說明詞條具有很好的類別區分能力。)但是實際上,如果一個詞條在一個類的文檔中頻繁出現,則說明該詞條能夠很好代表這個類的文本的特征,這樣的詞條應該給它們賦予較高的權重,并選來作為該類文本的特征詞以區別與其它類文檔。這就是idf的不足之處.
最近要做領域概念的提取,tfidf作為一個很經典的算法可以作為其中的一步處理。
計算公式比較簡單,如下:
預處理
由于需要處理的候選詞大約后3w+,并且語料文檔數有1w+,直接挨個文本遍歷的話很耗時,每個詞處理時間都要一分鐘以上。
為了縮短時間,首先進行分詞,一個詞輸出為一行方便統計,分詞工具選擇的是hanlp。
然后,將一個領域的文檔合并到一個文件中,并用“$$$”標識符分割,方便記錄文檔數。
下面是選擇的領域語料(path目錄下):
代碼實現
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
|
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算法雖然很經典,但還是有許多不足,不能單獨依賴其結果做出判斷。
以上就是本文關于java實現tfidf算法代碼分享的全部內容,希望對大家有所幫助。如有不足之處,歡迎留言指出。
原文鏈接:http://www.cnblogs.com/justcooooode/p/7831157.html