Lucene 哪怕4.7,其基于分页的搜索效率也是极高,几乎不用开多线程。
在上一篇文章中,我通过用自定义 WordBreakFilter 解决了 CJKBigramFilter 建立过多无用 term 的问题。最终目的都是相对于只有StandardAnalyzer,可以大幅地改善结果排序,更加精确地命中汉语词组。
但是最近又发现一个排序上的错误:以开心
为关键词全文搜索《牛津英语词典》之时,词条“unrip” 竟然排在 “clap” 前面。
生僻词 unrip :
解释很短,完全没有“开心”二字,仅仅是恰好对上了三次“开”字而已
unrip
/ˌʌnˈrɪp/
verb
(unripped, unripping)[with obj.]
rare open by ripping
〈罕〉撕开, 扯开:
he carefully unripped one of the seams.
他小心地拆开其中一条缝。
常用词 clap:
解释很长,且有“开心”二字(也是三次命中,但是是全命中啊!开心、开、心)。就因为解释内容篇幅长,总评分就被 lengthNorm 系数拉低了。
……
……
……
Agnes clapped her hands in glee
阿格尼丝开心地鼓掌
……
……
……
文档误我,说什么要会用searcher.explain(query, id)
,结果这打印出来的东西是给人看的?一大串数字,只有最终的分数对的上,其他都是蒙蔽。
0.07699502 = (MATCH) product of:
0.11549253 = (MATCH) sum of:
0.07321933 = (MATCH) weight(content:开 in 1) [DefaultSimilarity], result of:
0.07321933 = score(doc=1,freq=3.0 = termFreq=3.0
), product of:
0.45505905 = queryWeight, product of:
0.5945349 = idf(docFreq=2, maxDocs=2)
0.76540345 = queryNorm
0.16090071 = fieldWeight in 1, product of:
1.7320508 = tf(freq=3.0), with freq of:
3.0 = termFreq=3.0
0.5945349 = idf(docFreq=2, maxDocs=2)
0.15625 = fieldNorm(doc=1)
0.0422732 = (MATCH) weight(content:心 in 1) [DefaultSimilarity], result of:
0.0422732 = score(doc=1,freq=1.0 = termFreq=1.0
), product of:
0.45505905 = queryWeight, product of:
0.5945349 = idf(docFreq=2, maxDocs=2)
0.76540345 = queryNorm
0.092896074 = fieldWeight in 1, product of:
1.0 = tf(freq=1.0), with freq of:
1.0 = termFreq=1.0
0.5945349 = idf(docFreq=2, maxDocs=2)
0.15625 = fieldNorm(doc=1)
0.6666667 = coord(2/3)
然后百度Lucene评分原理,原来,文章越长、解析出的关键词越多,搜索的时候,评分就会相应地降低。
所以需要继续魔改Lucene-core模块,计算 lengthNorm 的时候强制关键词数量为固定值。(DefaultSimilarity)
@Override
public float lengthNorm(FieldInvertState state) { // 我谢谢你
int numTerms;
if (discountOverlaps)
numTerms = state.getLength() - state.getNumOverlap();
else
numTerms = state.getLength();
numTerms = 10; // 固定值
return state.getBoost() * ((float) (1.0 / Math.sqrt(numTerms)));
}
其实,我认为这个 lengthNorm 应该和总分数分开来算,不要简单得用乘法乘上去,太粗暴了!应该仅在总评分一致、命中关键词数量和频率都一致的情况下,进一步影响排名。不过这个功能太过复杂,暂时还是快刀斩乱麻,返回它一个固定的 lengthNorm 吧?
其他参考资料:长度归一化/omitNorms=false 有什么好处? - 堆栈溢出
lucene - 在单个字段上禁用长度规范化? - Stack Overflow
如果“恰巧”命中更多的单字会怎么样,unrip
还会排在clap
之前吗?还真会…
不过要数量特别大才行,测试中,50个“开”字才堪堪打败一个连起来的“开心”二字。
呵呵,图样图森破,sometimes naïve!