信息检索的历史发展过程中,出现了很多的模型,模型就是实际问题的抽象。如布尔模型、向量模型。
布尔模型以集合的布尔运算为基础,查询效率高,但只能适用简单搜索问题,同时还要求用户要按照要求拼凑查询串,只能用于特定领域。
向量模型把查询串和文档都是为词所构成的多维向量,查询与文档的相关性用向量间的夹角来表示。向量计算不适用于大规模数据。
TF-IDF(Term Frequency Inverse Doucument Frequency)模型,中文意思是:词频-逆文档频率。
TF-IDF核心思想是“给定的查询串q和文档d的匹配度问题”,这个在搜索引擎中使用广泛。
TF(Term Frequency),即词频,一个词q在文中出现的次数(tf = len(q)/len(all)),一般来说,一个词在一篇文章中出现的次数越多,则这篇文章的意思就越有可能与这个词相关,但是有些词,如“的”、“是”,在文中出现的次数一般都很高,但都没有实际的意义。所以,词频可以衡量一个词与文的相关性,但不能只用它。
IDF(Inverse Doucument Frequency),逆文档频率,即文档总数n与词q所出现文件数n1的比值的对数log(n/n1)。其思想是:词q如果在某篇文章中出现,而在其他文章中很少出现,则q具有很好的区分性,能大致代表出现较多的那篇文章。
最后TF-IDF的值就是:tf*idf,用来表示查询串与文档d的匹配度。这个匹配度,可以从概率的角度来理解,即用户通过查询串q,期望获得文档d的概率。在结果展示中,按从大到小的顺序排列即可,当然,实际中可能还有更多的问题要考虑。
下面是我用Java的实现:
package cn.zhf.cluster;
import java.io.*;
import java.util.*;
import org.junit.Test;
/**
* tf-idf实现类
* 如果是中文,其中input中的txt文件需要先用空格分好词
* 此类只是简单实现,还有很多问题
* @author zhaohuafei
*
*/
public class TFIDF {
public static final String FILE = "E:/input2/input2.txt";// 单个文件地址
public static final String FILES = "E:/input2/";// 文档所在目录
/**
* Term Frequency - Inverse Document Frequency 给定串在给定文件列表中某一文件的 逆文档频率
*
* @param term
* @param file
* @param files
* @return
*/
public double tfidf(String term, File file, File[] files) {
int tf = tf(term, file);
double idf = idf(term, files);
return tf * idf;
}
/**
* Term Frequency of a term(word) in a file
*
* @param term
* @param file
* @return
*/
public int tf(String term, File file) {
if (tfmap(file).get(term) == null)
return 0;
return tfmap(file).get(term);
}
/**
* Inverse Document Frequency the number of documents that a term appears
*
* @param term
* @list the documents that this term appears
* @return
*/
public double idf(String term, File[] files) {
List<Map<String, String>> list = idfmap(files);
int count = 0;// term出现的文件数
for (int i = 0; i < list.size(); i++) {
Map<String, String> map = list.get(i);
for (Map.Entry<String, String> m : map.entrySet()) {
if (m.getValue().equals(term))
count++;
}
}
return Math.log(files.length / count);
}
/**
* All Term Frequency Map in a file
*
* @param file
* @return
*/
public Map<String, Integer> tfmap(File file) {
Map<String, Integer> map = new HashMap<String, Integer>();
List<String> list = segment(file);
for (String term : list)
map.put(term, !map.containsKey(term) ? 1 : map.get(term) + 1);
return map;
}
/**
* All Inverse Document Frequency as a map
*
* @param files
* @return map<filename,term>
*/
public List<Map<String, String>> idfmap(File[] files) {
Map<String, Integer> maptf = new HashMap<String, Integer>();
List<Map<String, String>> list = new LinkedList<Map<String, String>>();
for (int i = 0; i < files.length; i++) {
maptf = tfmap(files[i]);
for (Map.Entry<String, Integer> m : maptf.entrySet()) {
String key = m.getKey();
Map<String, String> map = new HashMap<String, String>();
map.put(files[i].getName(), key);
list.add(map);
}
}
return list;
}
/**检索方法
* 返回(文档名,tfidf值)
*
* @param query
* 查询串
* @return <文档名,tfidf值>,按tfidf值排序
*/
public Map<String, Double> search(String query, File[] files) {
Map<String, Double> map = new HashMap<String, Double>();
for (int i = 0; i < files.length; i++) {
int tf = tf(query, files[i]);
if (tf == 0)
continue;
double idf = idf(query, files);
double tfidf = tf * idf;
map.put(files[i].getName(), tfidf);
}
map = sortMapByValueAsc(map);
return map;
}
/**
* map按值排序 asc
*
* @param map
* @return
*/
public static <K, V extends Comparable<? super V>> Map<K, V> sortMapByValueAsc(
Map<K, V> map) {
List<Map.Entry<K, V>> list = new LinkedList<Map.Entry<K, V>>(
map.entrySet());
Collections.sort(list, new Comparator<Map.Entry<K, V>>() {
@Override
public int compare(Map.Entry<K, V> o1, Map.Entry<K, V> o2) {
return (o2.getValue()).compareTo(o1.getValue());
}
});
Map<K, V> ret = new LinkedHashMap<K, V>();
for (Map.Entry<K, V> m : list) {
ret.put(m.getKey(), m.getValue());
}
return ret;
}
/**
* 简单的分词,假设句子都是以空格隔开的 TODO
*
* @param file
* @return
*/
public List<String> segment(File file) {
List<String> list = new ArrayList<String>();
try {
BufferedReader br = new BufferedReader(new InputStreamReader(
new FileInputStream(file), "gbk"));
String tmp = null, str = null;
while ((str = br.readLine()) != null) {
tmp += str;
}
String[] s = tmp.split("\\s");
for (int i = 0; i < s.length; i++)
list.add(s[i].trim());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return list;
}
// 以下均为测试
@Test
public void test3() {
int tf = tf("的", new File(FILE));
System.out.println("TF = " + tf);
Map<String, Double> map = search("的", new File(FILES).listFiles());
for (Map.Entry<String, Double> m : map.entrySet()) {
System.out.println(m.getKey() + "-->" + m.getValue());
}
}
public void test2() {
File file = new File(FILES);
File[] files = file.listFiles();
List<Map<String, String>> list = idfmap(files);
for (int i = 0; i < list.size(); i++) {
Map<String, String> map = list.get(i);
for (Map.Entry<String, String> m : map.entrySet()) {
System.out.println(m.getKey() + "-->" + m.getValue());
}
}
double tfidf = tfidf("的", new File(FILE), new File(FILES).listFiles());
System.out.println("count = " + tfidf);
}
public void test1() {
File file = new File(FILE);
List<String> list = segment(file);
for (String str : list)
System.out.println(str);
}
public void test() {
File file = new File(FILES);
File[] files = file.listFiles();
for (int i = 0; i < files.length; i++) {
Map<String, Integer> map = tfmap(files[i]);
for (Map.Entry<String, Integer> m : map.entrySet()) {
System.out.println(m.getKey() + "-->" + m.getValue());
}
}
}
}