Lucene系列三:相关度排名

前面已经对Lucene的基本内容有一个相关的了解,特别是反向索引 ,接下来将重点介绍下相关度排名。

1. 什么是相关度排名

直观理解搜索结果精准由高到低

如:要查询 苍老师、tony、火锅有关的新闻:
含有三个关键字(相关度最高)的新闻排前面,含两个关键字(相关度次之)排次之,含一个关键字 的排次次之。

当然,这是由一个算法完成的。

2. 如何设计相关性模型

利用出现次数来建立模型

规则1: 统计出现次数,根据次数从高到低排

{{1,5},{5,3},{12,1},{8,1}}:文章1出现5次,文章5出现3次,文章12出现1次,文章8出现1次。问题:标题中出现苍老师,与新闻的内容中出现苍老师,哪个是专门写苍老师的相关度高些?

规则2:加入权重,标题权重10,内容权重1,计算权重得分,按高-低排序

{{1,23},{12,10},{5,3},{8,1}},总结:利用出现次数来建立模型这个相关性模型很简单。有时排序会不是很准确。

复杂的相关性计算模型有:

  • TF/IDF和字段长度准则(长度越长,相关性越低),即检索词频率【检索词在该字段出现的频率】/反向文档频率【检索词在索引中出现的频率】,具体参参阅es-search
  • 向量空间模型
  • 贝叶斯概率模型,如:BM25

3. lucene相关度排序

官方介绍,在Lucene 7.4中,我们打分的对象是文档。文档是字段的集合。每个字段都有关于如何创建和存储 (tokenizedstored)的语义。重要的是要注意,Lucene评分在Fields上工作,然后组合结果返回文档。这是很重要的,因为两个文档具有相同的内容,但是由于长度规范化,一个文档在两个字段中具有内容,而另一个文档在一个字段中具有内容,所以对于相同的查询,可能返回不同的分数。lucene 为查询出来的结果(document)都打一个分,得分越高,排列的顺序越靠前,当然,我们也可以自己去干预。

  • 相关度得分

    • 相关度得分是在查询时根据查询条件实进计算出来的;
    • 如果索引库数据不变,查询条件不变,查出的文档得分也不变;
  • 注意事项

    • 排序我们可以通过设置这个权重值来影响他的得分;
    • 得分跟命中的次数有关系;
    • 这个得分跟文档出现的搜索关键字的次数或者位置有关系...
  • 提高分数

    • Lucene允许通过包装 BoostQuery影响查询的各个部分的得分贡献,也就是通过设置boost值。

4. Boost

上面提到过Boost它影响着索引阶段和搜索阶段。在索引阶段设定文档的Document.setBoost和域的Field.setBoost值,这些值是在索引阶段就写入索引文件的,存储在标准化因子(.nrm)文件中,一旦设定,除非删除此文档,否则无法改变。如果不进行设定,则Document Boost和Field Boost默认为1。在6.4版本中已经不再支持了。

5. Similarity

Similarity是非常重要的接口,它的实现非常多。ClassicSimilarity曾经是Lucene/Solr默认评分公式,但是从lucene-6.0开始已经改成BM25Similarity了。

5.1 ClassicSimilarity

它是TFIDFSimilarity的实现类,它的计算公式:tf * idf * boost * lengthNorm

  • tf ,查询的词在文档中出现的次数的平方根
  • idf ,表示反转文档频率,观察了一下所有的文档都一样,所以那就没什么用处,不会起什么决定作用
  • boost ,激励因子,值越大代表越重要,score得分越高
  • lengthNorm,一个域中包含的Term总数越多,也即文档越长,此值越小,文档越短,此值越大,公式:(1.0 / Math.sqrt(numTerms))
@Test
public void go_explain() throws IOException {
    Directory directory = new RAMDirectory();
    IndexWriterConfig config = new IndexWriterConfig();
    config.setUseCompoundFile(true);
    IndexWriter writer = new IndexWriter(directory, config);

    String feildName="title";
    Field f1 = new TextField(feildName, "bc bc", Field.Store.YES);
    Field f2 = new TextField(feildName, "ab bc", Field.Store.YES);
    Field f3 = new TextField(feildName, "ab bc cd", Field.Store.YES);
    Field f4 = new TextField(feildName, "abc", Field.Store.YES);

    Document doc1 = new Document();
    Document doc2 = new Document();
    Document doc3 = new Document();
    Document doc4 = new Document();
    doc1.add(f1);
    doc2.add(f2);
    doc3.add(f3);
    doc4.add(f4);
    writer.addDocument(doc1);
    writer.addDocument(doc2);
    writer.addDocument(doc3);
    writer.addDocument(doc4);
    writer.close();

    IndexReader reader = DirectoryReader.open(directory);
    IndexSearcher searcher = new IndexSearcher(reader);
    searcher.setSimilarity(new ClassicSimilarity());//切换算法
    TermQuery q = new TermQuery(new Term(feildName, "bc"));
    //Weight weight = q.createWeight(searcher, true, 3); //不要再这样使用,否则导致TFIDFSimilarity不生效
    BoostQuery bq=new BoostQuery(q,2f);
    TopDocs topDocs = searcher.search(bq, 10);
    System.out.println("totalHits:" + topDocs.totalHits + ",MaxScore" + topDocs.getMaxScore());
    for (int i = 0; i < topDocs.scoreDocs.length; i++) {
        ScoreDoc scoreDoc = topDocs.scoreDocs[i];
        Document doc = searcher.doc(scoreDoc.doc);
        System.out.print(doc.get(feildName));
        System.out.print("               ");
        System.out.print(scoreDoc.score);
        System.out.print("               ");
        System.out.println(searcher.explain(bq, i));
    }
    reader.close();
    directory.close();
}

这里思考一个问题:searcher.explain(bq, i)中的i默认是从0开始,是随意写的吗?其实不是的,它就是文档编号,为什么呢?具体可参阅我之前的分享的系统架构及核心模块

totalHits:3,MaxScore1.9999999
bc bc               1.9999999               1.9999999 = weight(title:bc in 0) [ClassicSimilarity], result of:
  1.9999999 = score(doc=0,freq=2.0), product of:
    2.0 = boost
    0.99999994 = fieldWeight in 0, product of:
      1.4142135 = tf(freq=2.0), with freq of:
        2.0 = termFreq=2.0
      1.0 = idf, computed as log((docCount+1)/(docFreq+1)) + 1 from:
        3.0 = docFreq
        3.0 = docCount
      0.70710677 = fieldNorm(doc=0)

ab bc               1.4142135               1.4142135 = weight(title:bc in 1) [ClassicSimilarity], result of:
  1.4142135 = score(doc=1,freq=1.0), product of:
    2.0 = boost
    0.70710677 = fieldWeight in 1, product of:
      1.0 = tf(freq=1.0), with freq of:
        1.0 = termFreq=1.0
      1.0 = idf, computed as log((docCount+1)/(docFreq+1)) + 1 from:
        3.0 = docFreq
        3.0 = docCount
      0.70710677 = fieldNorm(doc=1)

ab bc cd               1.1547005               1.1547005 = weight(title:bc in 2) [ClassicSimilarity], result of:
  1.1547005 = score(doc=2,freq=1.0), product of:
    2.0 = boost
    0.57735026 = fieldWeight in 2, product of:
      1.0 = tf(freq=1.0), with freq of:
        1.0 = termFreq=1.0
      1.0 = idf, computed as log((docCount+1)/(docFreq+1)) + 1 from:
        3.0 = docFreq
        3.0 = docCount
      0.57735026 = fieldNorm(doc=2)

这里解析下,记录f1(bc bc),score(1.9999999)=tf (1.4142135)* idf(1.0) * boost(2.0) * lengthNorm(0.70710677),termFreq(2)是词频代表出现了2次

5.2 BM25Similarity

这里注释掉代码中“切换算法”那一行即可,从下面结果中可以看出,BM25的idf和tf都做了一些变形,特别是tf公式(它叫tfNorm),还加入了两个经验参数k1和b(主要用来调整精准度),默认取K1=1.2,b=0.75。

totalHits:3,MaxScore0.38258293
bc bc               0.38258293               0.38258293 = weight(title:bc in 0) [BM25Similarity], result of:
  0.38258293 = score(doc=0,freq=2.0 = termFreq=2.0
), product of:
    2.0 = boost
    0.13353139 = idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:
      3.0 = docFreq
      3.0 = docCount
    1.4325581 = tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:
      2.0 = termFreq=2.0
      1.2 = parameter k1
      0.75 = parameter b
      2.3333333 = avgFieldLength
      2.0 = fieldLength

ab bc               0.28363907               0.28363907 = weight(title:bc in 1) [BM25Similarity], result of:
  0.28363907 = score(doc=1,freq=1.0 = termFreq=1.0
), product of:
    2.0 = boost
    0.13353139 = idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:
      3.0 = docFreq
      3.0 = docCount
    1.0620689 = tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:
      1.0 = termFreq=1.0
      1.2 = parameter k1
      0.75 = parameter b
      2.3333333 = avgFieldLength
      2.0 = fieldLength

ab bc cd               0.23911436               0.23911436 = weight(title:bc in 2) [BM25Similarity], result of:
  0.23911436 = score(doc=2,freq=1.0 = termFreq=1.0
), product of:
    2.0 = boost
    0.13353139 = idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:
      3.0 = docFreq
      3.0 = docCount
    0.89534885 = tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:
      1.0 = termFreq=1.0
      1.2 = parameter k1
      0.75 = parameter b
      2.3333333 = avgFieldLength
      3.0 = fieldLength

总结,搜索引擎中会提供一种、或多种实现供选择使用,电商网站中的搜索相关性计算会考虑更多,更复杂。这里提问你一个问题lucene怎样去实现“猜你喜欢”?我的另一篇中给出解答。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值