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。
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实现