出处:http://www.cnblogs.com/huangfox/archive/2012/07/05/2578179.html
MoreLikeThis,相似检索。找出某篇文档的相似文档,常见于“类似新闻”、“相关文章”等,这里完全是基于内容的分析。
1)MoreLikeThis的使用
FSDirectory directory = SimpleFSDirectory.open(
new
File(
"d:/nrtTest2"
));
IndexReader reader = IndexReader.open(directory);
IndexSearcher searcher =
new
IndexSearcher(reader);
//
MoreLikeThis mlt =
new
MoreLikeThis(reader);
mlt.setFieldNames(
new
String[] {
"ab"
});
//用于计算的字段
//
int
docNum =
1
;
// TermFreqVector vector = reader.getTermFreqVector(docNum, "ab");
// System.out.println(vector.toString());
Query query = mlt.like(docNum);
//试图找到与docnum=1相似的documents
System.out.println(reader.document(docNum));
System.out.println(query.toString());
//查看构造的query,后面的就是常规的lucene的检索过程。
TopDocs topDocs = searcher.search(query,
10
);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for
(ScoreDoc sdoc : scoreDocs) {
Document doc = reader.document(sdoc.doc);
System.out.println(doc.get(
"ti"
));
// System.out.println(doc.get("ti"));
}
|
2)MoreLikeThis的源码解读
把MLT运行起来很简单,那么我们进一步看看他是怎么实现的。
关键就是 Query query = mlt.like(docNum); 我们就从他下手。
2.1)like
public
Query like(
int
docNum)
throws
IOException {
if
(fieldNames ==
null
) {
// gather list of valid fields from lucene
Collection<String> fields = ReaderUtil.getIndexedFields(ir);
fieldNames = fields.toArray(
new
String[fields.size()]);
}
return
createQuery(retrieveTerms(docNum));
}
|
filedNames为参与“more like this”运算的字段,在moreLikeThis对象的setFiledNames方法中进行设置。
2.2)retrieveTerms
public
PriorityQueue<Object[]> retrieveTerms(
int
docNum)
throws
IOException {
Map<String,Int> termFreqMap =
new
HashMap<String,Int>();
for
(
int
i =
0
; i < fieldNames.length; i++) {
String fieldName = fieldNames[i];
TermFreqVector vector = ir.getTermFreqVector(docNum, fieldName);
//取出term向量
// 如果当前字段没有存储termVector,那么需要重新计算。其实这里就是分词,并计算term词频的过程,注意他默认使用的是StandardAnalyzer分词器!!!
if
(vector ==
null
) {
Document d = ir.document(docNum);
String text[] = d.getValues(fieldName);
if
(text !=
null
) {
for
(
int
j =
0
; j < text.length; j++) {
addTermFrequencies(
new
StringReader(text[j]), termFreqMap,
fieldName);
}
}
}
else
{
//如果之前保存了termVector那么就方便多了。
addTermFrequencies(termFreqMap, vector);
}
}
|
2.3)addTermFrequencies
由于TermVector中的term和field没有关系,不管是标题还是正文,只要term内容一样就将其频率累加。addTermFrequencies就做这个事情!
把累加的结果存放到termFreqMap中。
private
void
addTermFrequencies(Map<String,Int> termFreqMap,
TermFreqVector vector) {
String[] terms = vector.getTerms();
int
freqs[] = vector.getTermFrequencies();
for
(
int
j =
0
; j < terms.length; j++) {
String term = terms[j];
if
(isNoiseWord(term)) {
continue
;
}
// increment frequency
Int cnt = termFreqMap.get(term);
if
(cnt ==
null
) {
cnt =
new
Int();
termFreqMap.put(term, cnt);
cnt.x = freqs[j];
}
else
{
cnt.x += freqs[j];
}
}
}
|
截止,我们将指定的文档(被匹配文档)按照指定的运算字段,将其term和对应的frequency存放到了map中。在这个过程中,我们看到了一个听起来比较牛逼的操作——去噪!
那么这里怎么判断一个Term是不是噪音呢?
private
boolean
isNoiseWord(String term) {
int
len = term.length();
if
(minWordLen >
0
&& len < minWordLen) {
return
true
;
}
if
(maxWordLen >
0
&& len > maxWordLen) {
return
true
;
}
if
(stopWords !=
null
&& stopWords.contains(term)) {
return
true
;
}
return
false
;
}
|
他判断的标准十分简单,第一:是否是规定的停用词;第二:term长度是否过长或过短,这个范围由minWordLen和maxWordLen控制。
2.4)createQueue
这里的queue应该是一个优先级队列,上一步我们获得了所有<term, frequency>,虽然做了去噪,但是term项目还是太多了,还需要找出相对重要的前N个Term。
private
PriorityQueue<Object[]> createQueue(Map<String,Int> words)
throws
IOException {
// 获取当前index的文档总数。
int
numDocs = ir.numDocs();
FreqQ res =
new
FreqQ(words.size());
// 按照term的得分进行存放
Iterator<String> it = words.keySet().iterator();
while
(it.hasNext()) {
// 对所有term进行遍历
String word = it.next();
int
tf = words.get(word).x;
// 对应term的tf
if
(minTermFreq >
0
&& tf < minTermFreq) {
continue
;
// 和去噪类似,tf太小的term直接过掉。
}
// 对于同一个term,找到df最大的那个字段,存放到topField。
String topField = fieldNames[
0
];
int
docFreq =
0
;
for
(
int
i =
0
; i < fieldNames.length; i++) {
int
freq = ir.docFreq(
new
Term(fieldNames[i], word));
topField = (freq > docFreq) ? fieldNames[i] : topField;
docFreq = (freq > docFreq) ? freq : docFreq;
}
//df太小的term也要直接过掉
if
(minDocFreq >
0
&& docFreq < minDocFreq) {
continue
;
// filter out words that don't occur in enough docs
}
//df太大的term也要直接过掉
if
(docFreq > maxDocFreq) {
continue
;
// filter out words that occur in too many docs
}
//df==0的term也要直接过掉,怎么会有df的term???这里说是index文件的问题
if
(docFreq ==
0
) {
continue
;
// index update problem?
}
//经典的idf、tf又来了
float
idf = similarity.idf(docFreq, numDocs);
float
score = tf * idf;
//将结果存放到优先队列中。
res.insertWithOverflow(
new
Object[] {word,
// the word
topField,
// the top field
Float.valueOf(score),
// overall score
Float.valueOf(idf),
// idf
Integer.valueOf(docFreq),
// freq in all docs
Integer.valueOf(tf)});
}
return
res;
}
|
在这里,我们对每个term进行了打分排序,主要还是通过tf、idf进行计算。
这里他的意思就是:
1.将指定参与运算字段的term的frequency进行累加;(这里对ti、ab字段的tf进行累加)
2.通过df的比较,选取df大的字段作为最终“运算”的字段,但tf为所有字段的累加值。这和我们看普通检索时的打分算法不太一样,普通检索中tf为当前字段的词频。
至于为什么这么做,还得验证!!!
2.5)createQuery
到此我们将term的打分排序拿到了,分值越大的term更能表述整篇document的主要内容!(有没有想到这就类似主题词!!!)
private
Query createQuery(PriorityQueue<Object[]> q) {
BooleanQuery query =
new
BooleanQuery();
Object cur;
int
qterms =
0
;
float
bestScore =
0
;
while
(((cur = q.pop()) !=
null
)) {
Object[] ar = (Object[]) cur;
TermQuery tq =
new
TermQuery(
new
Term((String) ar[
1
], (String) ar[
0
]));
//这里还可以对termquery进行boost的设置。默认为false
if
(boost) {
if
(qterms ==
0
) {
bestScore = ((Float) ar[
2
]).floatValue();
}
float
myScore = ((Float) ar[
2
]).floatValue();
tq.setBoost(boostFactor * myScore / bestScore);
}
//构建boolean query,should关联。
try
{
query.add(tq, BooleanClause.Occur.SHOULD);
}
catch
(BooleanQuery.TooManyClauses ignore) {
break
;
}
qterms++;
if
(maxQueryTerms >
0
&& qterms >= maxQueryTerms) {
//限定参与运算的term的数量
break
;
}
}
return
query;
}
|
这样就根据一篇document和指定字段得到了一个query。这个query作为代表着document的灵魂,将寻找和他类似的documents。
3)实例
被匹配的document为:
Document<stored,indexed,tokenized<an:CN00103249.6>
stored,indexed<ad:20000320> stored,indexed,tokenized,termVector<ab: 本发明涉及一种高级毛料服装洗涤剂。使用该洗涤剂,洗衣服可不用到干洗店,自己在家水洗就行,且洗涤后毛料服装笔挺膨松,抗静电,不缩水,洗涤中不刺激皮肤。此剂主要由去污剂、抗静电剂、防缩剂、表面活性剂与其它助剂配制而成。去污率≥90%,缩水率≤1‰。>
stored,indexed,tokenized,termVector<ti:高级毛料服装洗涤剂>>
计算出来的query为:
ab:毛料 ab:服装 ab:洗涤剂 ab:抗静电 ab:高级 ab:剂 ab:洗涤
计算结果为:
高级毛料服装洗涤剂
抗静电防尘污灭菌广谱洗涤剂及制备方法
一种抗紫外线的织物涂层材料
服装绿色干洗及服装翻新技术
洗碟用柔性含蛋白酶的液体或凝胶洗涤组合物
复合洗霉制剂
洗衣机
一种抗静电合成纤维
实验室专用洗涤剂
液晶相结构型液体洗涤剂及制造工艺
貌似结果不是很相似,那么我们可以试着只用ti做运算,这样从标题看起来比较相似。
还可以对MLT的各项参数进行设置,这里就不在实验了!