ElasticStack系列之十二 & 搜索结果研究

Posted 星火燎原智勇

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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的向量空间模型,我觉得有本质的区别,前者是概率模型,后者是代数模型。

 

以上是关于ElasticStack系列之十二 & 搜索结果研究的主要内容,如果未能解决你的问题,请参考以下文章

ElasticStack系列之十七 & 大文本搜索性能提升方案

ElasticStack系列之十三 & 联想补全策略

ElasticStack系列之十五 & query cache 引起性能问题思考

ElasticStack系列之十一 & 同步 mysql 数据的实践与思考

ElasticStack系列之十九 & bulk时 index 和 create 的区别

ElasticStack系列之十四 & ElasticSearch5.x bulk update 中重复 id 性能骤降