==================转自===============
http://blog.sina.com.cn/s/blog_6cc084c90100ojhr.html
========================================
下图是用Lucene开发的搜索页面做一次搜索时的结果:
分析搜出来的文档,第8个和第9个文档标题没有出现关键字,而第10个文档标题出现了关键字。一般来说标题出现关键字的文档往往是人们比较关心的文档,第10个文档理应排在前两个之前。而且第8个文档全篇都是用户的评论,一般来说它的重要性应该低于其他类似新闻的文档,如果考虑文档重要性的差异,例如加入网页的PageRank,搜索的结果应该更好。下面就上面两个问题作改进。
(1)提高对标题域的重视。
之前的搜索只搜索正文域,而没有考虑标题域。可以使用MultiFieldQueryParser来解析搜索词,对多个域进行搜索。相关的代码如下:
String[] fields = { "title", "contents" };
BooleanClause.Occur[] clauses = { BooleanClause.Occur.SHOULD,
BooleanClause.Occur.SHOULD };
Query query = MultiFieldQueryParser.parse(Version.LUCENE_30,
queryWord, fields, clauses, analyzer);
这样就可以同时在title域和contents域中对关键字进行搜索。
此外,还可以通过增加title域的boost值来改进Score算法,例如将标题域的权重设大一些,也可以改变文档的score,提高对title域的重视。
(2)加入PageRank值。
各个网页的PageRank值在Heritrix抓下来时已经进行了计算,计算结果和链接信息分别保存在两个文本文档中。建立索引阶段需要将PageRank值读入。我所用的方法是先分别读入网页URL和对应的PageRank值,存储在BerkeleyDB中,key值为URL换算的long值(换算的函数直接使用了Heritrix的createKey函数,感谢Heritrix),data值即为PageRank值。之后处理每一个网页时,将它的路径换算成long值(本地存放路径与URL有不同,在比较之前要进行处理),在BerkeleyDB中查询网页对应的PageRank。
Lucene中通过改变Score值控制排序结果一般有3种方法,包括通过改变文档Boost值,使用自定义的sort对象排序以及定制自己的评分系统。分别如下:
1)改变文档Boost值。
下图为Lucene的评分公式:
改变文档的Boost值可以改变文档的score。可以直接将PageRank值作为文档的Boost值,或者处理后赋给Boost值。局限性是PageRank值只能作为Score的一个乘法因子,灵活性不够。
2)自定义的sort对象。
这里使用的是Lucene3.02版本,自定义sort对象的方法跟之前的版本有点小不同,需要实FieldComparatorSource和FieldComparator接口。FieldComparatorSource功能是返回一个用来排序的comparator。这个接口只定义了一个方法newComparator,用来生成FieldComparator对象。生成的FieldComparator对象可作为指定文档域的排序比较器。FieldComparator对象需要实现setScorer,compare,compareBottom,copy,setBottom,setNextReader等方法,如果需要用到文档的score值,还需要实现setScorer方法。实现的代码如下:
public class AddPagerankConparatorSource extends FieldComparatorSource {
public FieldComparator newComparator(String fieldName,
int numHits,int sortPos, boolean reversed) throws IOException {
return new AddPagerankComparator(fieldName, numHits);
}
private class AddPagerankComparator
extends FieldComparator {
private float[] pagerank;
private float[] scores;
private float[] values;
private float bottom;
private Scorer scorer;
String fieldName;
public AddPagerankComparator(String fieldName, int numHits){
values = new float[numHits];
this.fieldName = fieldName;
scores = new float[numHits];
}
public void setScorer(Scorer scorer) {
this.scorer = new ScoreCachingWrappingScorer(scorer);
}
public int compare(int slot1, int slot2) {
if (values[slot1] > values[slot2])
return -1;
if (values[slot1] < values[slot2])
return 1;
return 0;
}
public int compareBottom(int doc) throws IOException {
float score = scorer.score();
if (bottom > revisedScore(score, doc))
return -1;
if (bottom < revisedScore(score, doc))
return 1;
return 0;
}
private float revisedScore(float score, int n) hrows IOException {
float cof1 = ??;
float cof2 = ??;
return cof1*score + cof2*pagerank[n];
}
public void copy(int slot, int doc) throws IOException {
scores[slot] = scorer.score();
values[slot] = revisedScore(scores[slot], doc);
}
public void setBottom(int slot) {
bottom = values[slot];
}
public void setNextReader(IndexReader reader, int docBase)
throws IOException {
final TermEnum enumerator = reader.terms(new Term(fieldName, ""));
pagerank = new float[reader.maxDoc()];
if (pagerank.length > 0) {
TermDocs termDocs = reader.termDocs();
try {
if (enumerator.term() == null) {
throw new RuntimeException("no terms infield"+ fieldName);
}
do {
Term term = enumerator.term();
if (term.field()!= fieldName)
break;
termDocs.seek(enumerator);
while (termDocs.next()){
String prScrStr = term.text();
pagerank[termDocs.doc()] = Float.parseFloat(prScrStr);
}
} while (enumerator.next()); // #9
} finally {
termDocs.close();
}
}
}
public Comparable value(int slot) {
return new Float(values[slot]);
}
}
}
其中revisedScore(float score, int n)方法获取加入pagerank值后的score值,其值的计算公式为
newscore = a*score + b*pagerank
系数a,b的取值需要进行多次实验比较才能取到较好的值。
在查询时需要新建一个AddPagerankConparatorSource的sort对象,并在search中调用,相关代码如下:
Sort sort = new Sort(new SortField("pagerank",new AddPagerankConparatorSource()));
ScoreDoc[] hits = searcher.search(query, null, 10 * hitsPerPage,
sort).scoreDocs;
使用自定义排序的方法在改动不大的情况下可以较为灵活的更改文档的评分方式,在这里Lucene评出的Score值和pagerank值是线性的关系,感觉较乘法合理。
3) 定制自己的评分系统
Lucene计算评分涉及到几个核心的类/接口:Similarity、Query、Weight、Scorer、Searcher,由它们或其子类来完成评分的计算。可以实现一个 Similarity 换掉默认的DefaultSimilarity,但它仅限于Scorer、Weight 计算好的因子值再加工。要想对评分有更强的控制力,要实现一套 Query、Weight、Scorer。这种方法灵活性更强,但实现难度很大,目前还没有实现这种方法加入pagerank值。
经过上面改进后搜索结果如下图所示: