zoukankan      html  css  js  c++  java
  • ElasticStack系列之十二 & 搜索结果研究

    问题

      使用 ElasticSearch 做搜索 时,比如用户输入 --> 柠檬,搜出来的结果 --> 柠檬汽水,柠檬味牙膏等在前面,真正想要的水果那个 柠檬 在后面。已经在中文分词中加了 柠檬,还是不管用,正常来说 tf、idf 都一样,影响排序的只有 field norms。按道理 “柠檬” 的 field length 最短,那么得分应该最高才对,为什么它没有排在第一位呢?

      我这里补充一下:ElasticSearch5.x 以后使用的相关度算法为 BM25,但他仍然是一种相关性算法,只是对 TF/IDF 的改进。 用 BM25 还是 TF/IDF 和本问题的根源没有关系。

    验证

      为了验证这个结果,我实际测试了一下,过程如下:

      创建一个空索引,使用 ik_max_word 分词器并写入 3 条数据

    PUT testindex/
    {
      "mappings": {
        "logs": {
          "properties": {
            "product": {
              "type": "text",
              "analyzer": "ik_max_word"
            }
          }
        }
      }
    }
    
    PUT testindex/logs/1
    {"product":"柠檬"}
    
    PUT testindex/logs/2
    {"product":"柠檬汽水"}
    
    PUT testindex/logs/3
    {"product":"柠檬味牙膏"}

      查询关键词"柠檬"

    POST testindex/_search
    {
      "query": {
        "match": {
          "product": "柠檬"
        }
      }
    }

      查询结果:

    "hits": {
        "total": 3,
        "max_score": 0.85747814,
        "hits": [
          {
            "_index": "testindex",
            "_type": "logs",
            "_id": "3",
            "_score": 0.85747814,
            "_source": {
              "product": "柠檬味牙膏"
            }
          },
          {
            "_index": "testindex",
            "_type": "logs",
            "_id": "2",
            "_score": 0.80226827,
            "_source": {
              "product": "柠檬汽水"
            }
          },
          {
            "_index": "testindex",
            "_type": "logs",
            "_id": "1",
            "_score": 0.7594807,
            "_source": {
              "product": "柠檬"
            }
          }
        ]
     }

      "柠檬" 居然真分数最低,非常出乎我的意料。于是,我在查询里打开 "explain":true 选项,查看分数是怎么计算的,发现 doc frequency, avgfieldlength 看着都不对。

    问题解释

      简而言之,ElasticSearch 的相关性打分计算是每个 shard 独立做的。一个索引默认 5 个shard,如果像示例里那样,写入的文档比较少,可能这些文档分布在不同的 shard,造成各个 shard 分别计算各自的得分的时候,并没有将这几条文档放在一起产生统计数据。 各自的打分不具有可比性。

      所以,后面我又做了一个测试,删掉这个索引,重新创建一个,将 shard 设置为 1,重新写入同样 3 条文档后再搜索,"柠檬” 是排第一位返回的。

      那么怎么看待这个问题?

      因为 ElasticSearch 是分布式搜索系统,各个shard独立搜索,独立计算该shard上的文档打分,当数据量比较大的情况下,上面说的差异统计上看基本被抹平了,通常没什么问题。但如果索引的文档比较少,不同 shard 之间对同一个搜索关键词的统计数据差异可能就比较大,这种情况下只能使用一个 shard 来解决了。

      故这里就可以解释我最初问题中所说的,通过 explain api 去查看打分的过程时,doc frequency 和 avgfieldlength 这类参与打分的统计值看起来“不正确”。之所为认为它“不正确”,是因为一开始没意识到,ES默认的打分是每个shard单独进行的,并非参考的全局统计数据。

      故在做实际搜索应用的时候,则需要根据应用所针对的领域范围,补充打分需要的其他参考信息来提高搜索准确率。 比如收集用户搜索的常用热词,为热词增加权重,或者为信息添加类别信息,为某些类别提高权重等等。

      另外:

        可以在搜索的时候增加参数:?search_type=dfs_query_then_fetch ,即先分别获得每个分片本地的 IDF ,然后根据结果再计算整个索引的全局 IDF,也是可以实现。不过这种方式代价比较高,适合测试的时候使用,生成环境下还是不建议使用的。

      推荐可以看一篇 github 上的 issue

    总结

      ElasticSearch 这类搜索引擎主要提供 “搜的到” 的功能,而能否 “搜的准”,需要理解 ElasticSearch 能提供的打分机制,并结合领域信息,对信息做提炼或者补充,然后利用这些打分机制去调配,让搜索结果符合应用的需求。但搜的“准不准”,一千个人有一千个理解,做好不是一件容易的事。

      BM25 和 基于tf-idf的向量空间模型,我觉得有本质的区别,前者是概率模型,后者是代数模型。

  • 相关阅读:
    POJ3320 Jessica's Reading Problem
    POJ3320 Jessica's Reading Problem
    CodeForces 813B The Golden Age
    CodeForces 813B The Golden Age
    An impassioned circulation of affection CodeForces
    An impassioned circulation of affection CodeForces
    Codeforces Round #444 (Div. 2) B. Cubes for Masha
    2013=7=21 进制转换
    2013=7=15
    2013=7=14
  • 原文地址:https://www.cnblogs.com/liang1101/p/7642521.html
Copyright © 2011-2022 走看看