Java实现TFIDF算法

时间:2022-10-08 11:00:21

算法介绍

最近要做领域概念的提取,TFIDF作为一个很经典的算法可以作为其中的一步处理。

关于TFIDF算法的介绍可以参考这篇博客http://www.ruanyifeng.com/blog/2013/03/tf-idf.html

计算公式比较简单,如下:

 Java实现TFIDF算法

预处理

由于需要处理的候选词大约后3w+,并且语料文档数有1w+,直接挨个文本遍历的话很耗时,每个词处理时间都要一分钟以上。

为了缩短时间,首先进行分词,一个词输出为一行方便统计,分词工具选择的是HanLp。

然后,将一个领域的文档合并到一个文件中,并用“$$$”标识符分割,方便记录文档数。

Java实现TFIDF算法

下面是选择的领域语料(PATH目录下):

Java实现TFIDF算法

代码实现

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));
}
}

运行结果 

测试词为“离退休人员”,中间结果如下:

Java实现TFIDF算法

 最终结果:

Java实现TFIDF算法

结论

可以看到“离退休人员”在养老保险和社保领域,tfidf值比较高,可以作为判断是否为领域概念的一个依据。

当然TF-IDF算法虽然很经典,但还是有许多不足,不能单独依赖其结果做出判断。

很多论文提出了改进方法,本文只是实现了最基本的算法。

如果有其他思路和想法欢迎讨论。