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