Lucene 之又一奇葩设定:lengthNorm 导致文章越长,排名越后

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

深入理解Lucene默认打分算法

lucene score explain 评分解释说明


如果“恰巧”命中更多的单字会怎么样,unrip还会排在clap之前吗?还真会…

不过要数量特别大才行,测试中,50个“开”字才堪堪打败一个连起来的“开心”二字。

呵呵,图样图森破,sometimes naïve!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值