tf-idf

tf-idf的具体实现,代码稍后上传到github

在进行文本特征选择的时候,有很多种方法,tf-idf是比较简单常用的一种。
tf-idf选择出的是有区分度的词,对选特征词有很大的帮助。

TF-IDF(term frequency–inverse document frequency)是一种用于资讯检索与资讯探勘的常用加权技术。

 TFIDF的主要思想是:如果某个词或短语在一篇文章中出现的频率TF高,并且在其他文章中很少出现,则认为此词或者短语具有很好的类别区分能力,适合用来分类。

假如一篇文件的总词语数是100个,而词语”蜜蜂”出现了3次,那么”蜜蜂”一词在该文件中的词频就是3/100=0.03。
一个计算文件频率 (DF) 的方法是测定有多少份文件出现过”蜜蜂”一词,然后除以文件集里包含的文件总数。
所以,如果”蜜蜂”一词在2份文件出现过,而文件总数是8份的话,其逆向文件频率就是 lg(8/2)=2。最后的TF-IDF的分数为 0.03 * 2 = 0.06。

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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package com.xxxx.xxxx.xxxx.featureselection;


import com.xxxx.xxxx.xxxx.util.SegUtil;
import com.xxxx.xxxx.odk.common.collection.MapUtil;
import com.xxxx.xxxx.odk.common.file.CharsetUtil;
import com.xxxx.xxxx.odk.common.file.FileUtil;
import com.xxxx.xxxx.odk.common.string.StringUtil;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.util.*;

/**
* @date: 2018-06-04 15:37
*/
@Slf4j
public class TfIdf {

/**
*
* @param filePaths 所有文本的绝对路径
* @return
*/
public List<Map.Entry<String, Double>> getFeature(List<String> filePaths){
return MapUtil.sortByValue(getTfIdf(filePaths));
}

/**
* 获取所有词语的tf-idf值
*
* @param docs
*/
public Map<String, Double> getTfIdf(List<String> filePaths) {

// <文件路径, <词语, 词频>>
Map<String, Map<String, Integer>> allFilesTd = tfAllFiles(filePaths);

// 猜测的词语大小,用于初始化map,避免内存拷贝
int guessWordCount = allFilesTd.size() << 3;
// <词, 词频>
Map<String, Integer> wordAndFrequeryMap = new HashMap<>(guessWordCount);
// <词, 出现该词的文件个数>
Map<String, Integer> wordAndFileCountMap = new HashMap<>(guessWordCount);
guessWordCount = 0;

// 循环文件
for (Map.Entry<String, Map<String, Integer>> bigEntry : allFilesTd.entrySet()) {
// 词 词频
Map<String, Integer> wordAndCountMap = bigEntry.getValue();
for (Map.Entry<String, Integer> entry : wordAndCountMap.entrySet()) {
String word = entry.getKey();
Integer wordFrequery = entry.getValue();

/** 处理 <词, 词频> map */
if (wordAndFrequeryMap.containsKey(word)) {
wordAndFrequeryMap.put(word, wordAndFrequeryMap.get(word) + wordFrequery);
} else {
wordAndFrequeryMap.put(word, wordFrequery);
}

/** 处理 <词, 出现该词的文件个数> map */
if (wordAndFileCountMap.containsKey(word)) {
wordAndFileCountMap.put(word, wordAndFileCountMap.get(word) + 1);
} else {
wordAndFileCountMap.put(word, 1);
}

} // end for
} // end for

// 所有词的个数
int allWordCount = wordAndFrequeryMap.size();
// 文档总数
int allDocCount = filePaths.size();
// 指定大小,避免内存拷贝
// wordCount / 0.75 = wordCount / 4 * 3 = wordCount >> 2 / 3
Map<String, Double> wordTfidfMap = new HashMap<>(wordAndFrequeryMap.size() >> 2 * 3);

/** 计算TF-IDF */
for (Map.Entry<String, Integer> entry : wordAndFileCountMap.entrySet()) {
String word = entry.getKey();
// 为了避免除数为0 ,对于分母为0的+1,其他的不作处理(避免一个词在所有文档里出现),出现该词语的文件个数 + 1
int wordfileCount = entry.getValue() == 0 ? 1 : entry.getValue();
// tf * idf = (词频 / 词语总数) * ( log(文档总数/出现该词语的文件个数) )
wordTfidfMap.put(word, 1.0 * wordAndFrequeryMap.get(word) / allWordCount * Math.log(allDocCount / wordfileCount));
}

if(log.isDebugEnabled()){
/** 打印词和词频 */
List<Map.Entry<String, Integer>> wordAndFrequeryList = MapUtil.sortByValue(wordAndFrequeryMap);
StringBuilder sbwf = new StringBuilder();
for(Map.Entry<String, Integer> entry : wordAndFrequeryList){
sbwf.append(entry.getKey());
sbwf.append(" : ");
sbwf.append(entry.getValue());
sbwf.append("\r\n");
}
wordAndFrequeryList.clear();
wordAndFrequeryList = null;
log.debug("打印词和词频集合\r\n{}", sbwf);

/** 打印词和包含该词的文档数 */
List<Map.Entry<String, Integer>> wordAndFileCountList = MapUtil.sortByValue(wordAndFileCountMap);
StringBuilder sbwc = new StringBuilder();
for(Map.Entry<String, Integer> entry : wordAndFileCountList){
sbwc.append(entry.getKey());
sbwc.append(" : ");
sbwc.append(entry.getValue());
sbwc.append("\r\n");
}
wordAndFileCountMap.clear();
wordAndFileCountMap = null;
log.debug("打印 词 和 包含该词的文档数 集合\r\n{}", sbwc);
}

allDocCount = 0;
allDocCount = 0;
wordAndFileCountMap.clear();
wordAndFileCountMap = null;
wordAndFrequeryMap.clear();
wordAndFrequeryMap = null;

return wordTfidfMap;
}


/**
* 统计所有文件
*
* @param filePaths
* @return <文件路径, <词语, 词频>>
*/
public Map<String, Map<String, Integer>> tfAllFiles(List<String> filePaths) {

Map<String, Map<String, Integer>> allTf = new HashMap<>(1 << 20);
for (String filePath : filePaths) {
// <文件路径, <词语, 词频>>
allTf.put(filePath, tfOneFile(filePath));
}
return allTf;
}

/**
* 统计一个文件里的词和词频<br>
* 读文件 分词 统计
*
* @param filePath
* @return <词, 词频>
*/
public Map<String, Integer> tfOneFile(String filePath) {
String str = null;
try {
str = FileUtil.readFile2String(filePath);
} catch (IOException e) {
log.error("读文件出错 {}", e.toString());
}
return SegUtil.segAndFilter(str);
}

}

References

[1] 《数学之美》 吴军
[2] 中文文本分类中的特征选择研究
[3] 基于TFIDF的文本特征选择方法
[4] 改进的χ^2统计文本特征选择方法
[5] TF-IDF理解及其Java实现