算法介绍
概念
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