产品

BM25 实用详解 - 第 1 部分:分片如何影响 Elasticsearch 中的相关性评分

本文是关于相似度排序(相关性)的Practical BM25三篇系列文章中的第一篇。下一篇文章的链接见文末。

背景

在 Elasticsearch 5.0 中,我们将 Okapi BM25 切换为默认的相似度算法,该算法用于对与查询相关的结果进行评分。在本篇博客中,我不会过多讨论 BM25 与其他衡量指标的对比,但如果您想了解 BM25 的理论依据,可以前往观看 Elastic{ON} 2016 大会上关于 BM25 揭秘 (BM25 Demystified) 的演讲。相反,我将为您介绍(并希望能揭开)BM25 的实际用法,包括涵盖可用的参数以及影响评分的因素。

请记住,本篇博文主要针对那些对文本文档进行评分的用户。也就是说,它真正专注于帮助我们的搜索用户。如果您正在索引日志或指标,并按照某些明确的元数据/数字顺序(如时间戳)返回排序结果,那么本篇博文可能主要仅供您参考了解。

了解分片如何影响评分

由于我希望您能跟着一起操作,我们需要首先解决的问题之一是了解拥有超过 1 个分片如何影响评分,因为 Elasticsearch 默认每个索引使用 5 个主分片。让我们先创建一个名为“people”的索引。我在此处提供的设置将是默认设置(因此无需定义),但我还是会明确写出,以便进行演示。我将在此处使用我名字(“Shane Connelly”)的变体,但如果您正在跟着操作,请随意将其替换为您选择的名字。

PUT people
{
  "settings": {
    "number_of_shards": 5,
    "index" : {
        "similarity" : {
          "default" : {
            "type" : "BM25"
          }
        }
    }
  }
}

现在,让我们添加一个文档并对其进行搜索。首先,我们只添加我的名字:

PUT /people/_doc/1
{
  "title": "Shane"
}
GET /people/_doc/_搜索
{
    "query": {
        "match": {
             "title": "Shane"
         }
      }
}

此时您会得到 1 个命中结果,得分为 0.2876821。我们稍后将深入探讨该评分是如何得出的,但首先让我们看看当我们添加更多包含我全名不同变体的文档时会发生什么。

PUT /people/_doc/2
{
  “title”:“Shane C”
}
PUT /people/_doc/3
{
  “title”:“Shane Connelly”
}
PUT /people/_doc/4
{
  "title": "Shane P Connelly"
}

现在,再次执行相同的搜索:

GET /people/_doc/_搜索
{
    "query": {
        "match": {
             "title": "Shane"
         }
      }
}

此时,您确实应该得到 4 个命中结果,但如果您查看分数,可能会感到困惑。文档 1 和文档 3 的分数均为 0.2876821,但文档 2 的分数为 0.19856805,文档 4 的分数为 0.16853254。这种情况经常会让新用户感到困惑。文档 2 和文档 3 非常相似——它们都有 2 个词,且都匹配“shane”,但文档 2 的分数要低得多。您可能会开始认为“C”的评分与“Connelly”的评分有所不同,但实际上这与文档在分片中的分布方式有关。

提醒一下,Elasticsearch 会将文档划分为 分片,每个分片都保存一部分数据。如果我们查看:

GET /_cat/分片/people?v

如果您执行此操作,将会看到分片 2 有 2 个文档,而分片 3 和 4 中只有 1 个文档(分片 0 和 1 尚无任何文档)。这意味着术语“shane”在这些不同分片中的总出现次数不同,而这正是导致本例中得分差异的根本原因。默认情况下,Elasticsearch 会按分片计算得分。

用户开始将少量文档加载到索引中,并询问“为什么文档 A 的分数比文档 B 高/低”,有时答案是用户设置的分片与文档的比例相对较高,导致分数在不同分片之间出现偏差。有几种方法可以使跨分片的得分更加一致:

  1. 您加载到索引中的文档越多,分片的词项统计数据就会变得越规范。当文档数量足够多时,您可能就不会注意到每个分片中词项统计数据以及评分的细微差异。
  2. 您可以减少分片数量,以降低词频统计偏差。例如,如果我们已在索引设置中将 number_of_shards 设置为 1,则会得到非常不同的分数。我们将看到文档 1 的分数为 0.13245322,文档 2 和文档 3 的分数均为 0.105360515,文档 4 的分数为 0.0874691。设置不同数量的主分片会有一些权衡,这在我们的定量集群规模调整网络研讨会中进行了讨论。
  3. 您可以在请求中添加 ?搜索_type=dfs_query_then_fetch,它会首先收集分布式词项频率(DFS = Distributed Frequency 搜索),然后使用这些频率计算分数。实际上,这返回的分数与仅有 1 个分片时的分数相同。请查看使用和不使用 “搜索_type” 参数时结果有何不同:
    GET /people/_doc/_搜索?搜索_type=dfs_query_then_fetch
    {
        "query": {
            "match": {
                 "title": "Shane"
             }
          }
    }
    这与设置 number_of_shards=1 的效果相同。您可能会问:“如果这能产生更准确的分数,为什么不默认开启呢?”答案是,它在处理过程中增加了一次额外的往返操作来收集所有统计数据,而对于某些用例(评分准确性不如速度重要的场景)来说,这种往返操作是不必要的。此外,当分片中有足够的数据时,统计数据会变得非常接近,从而也使得往返操作变得不再必要。如果您有足够的数据,search_type=dfs_query_then_fetch 通常仅在分片之间的数据持续分布不均时才需要,例如在使用某些 自定义路由 的情况下。

好了,现在我们已经了解了分片如何影响评分(以及如何进行调整)。接下来,我们将研究 BM25 算法,看看不同的变量是如何发挥作用的。

继续阅读本系列:第 2 部分:BM25 算法及其变量