第 22 章 - 你不能错过的Elasticsearch核心知识点-BM25相关性评分算法(进阶)

414 阅读5分钟

前言

上一章介绍了 Elasticsearch 的读写优化技巧。本章将深入探讨与 Elasticsearch 相关的 BM25 相关性评分公式。

我们将全面解析 BM25 如何在查询时影响文档评分。BM25 是 Elasticsearch 的核心内容之一,内容可能较为难懂,请耐心阅读。

分片对 Elasticsearch 相关性评分的影响

默认情况下,文档的评分是基于当前分片进行计算的。即,同一个 term 在不同的分片上评分会不一致。
我们来看下面这个例子。

创建一个 test_22 索引,并将分片数设置为 3

PUT /test22
{
  "settings": {
    "number_of_shards": 3
  }
}

紧接着,我们写入 3 条数据

POST _bulk
{ "index": { "_index": "test_22", "_id": "1" } }
{"name": "hello es"}
{ "index": { "_index": "test_22", "_id": "2" } }
{"name": "hello php"}
{ "index": { "_index": "test_22", "_id": "4" } }
{"name": "hello world p"}

OK,现在让我们来搜索 hello

GET test_22/_search
{
  "query": {
    "match": {
      "name""hello"
    }
  }
}

结果可能会让你感到意外。
文档1的得分为0.2876821,文档2得分为0.19856803,文档4得分为0.16853255。
是不是感到困惑?文档1和文档2的结构相似,为什么文档1的得分更高呢?而文档4仅仅多了一个 P,它的得分却是最低的?

文档1为什么比文档2得分更高?
因为文档1 在分片2上,文档2、文档4在分片1上。这就是我上述所说的,得分是基于文档所在分片计算的。这与逆文档词频有关,下文会介绍。
查询参数最外层,添加 "explain": true,即可看到文档所在分片。

GET test_22/_search
{
  "explain"true, 
  "query": {
    "match": {
      "name""hello"
    }
  }
}

文档2为什么比文档4得分更高?
文档2和文档4在同一个分片中,但是文档2的平均长度小于文档4。因此,文档2的得分比文档4更高。

简单来说:在一句话中出现 hello 的权重要高于在一段话中出现 hello 的权重。

这里提到的平均长度是以 term 为维度进行计算的。例如,文档2的平均长度为2,而文档4的平均长度为3。

BM25 算法和它的变量

上面提到的例子根源于 BM25 公式。接下来,我将重点介绍 BM25 公式的相关内容。 ES 的评分使用以下公式:

inIDF(qi)f(qi,D)(k1+1)f(qi,D)+k1(1b+bfieldLenavgdl)\sum ^{n}_{i} {I}DF({q}_{i})\frac {f({q}_{i},D)\, *\, (k1\, +1)} {f({q}_{i},D)+k1*(1-b+b*\frac {fieldLen} {avgdl})}

接下来,我将逐一介绍公式中的每个参数。

  • 表示当前查询的第几个term。例如,我们搜索 hello world,它包含 2 个termhello 对应 ,world 对应 。
  • 逆文档词频 (Inverse Document Frequency),其计算公式为: :当前分片所查询字段有值的文档数量 :在当前分片中,命中该term查询的文档数量 如果一个 term 出现在大多数文档中,那么该 term 的得分也就越低。就像在做毕业设计时,你们班50个人的论文都是XXX管理系统,而只有你的论文是关于人工智能,那么你的论文更有可能被评为优秀论文。
    用上面的例子来说明:对于文档1的得分就是:。对于文档2得分就是:。
  • 这里的 avgFieldLen 指的是所有文档 term 的平均数量,fieldLen 则是指当前文档 term 的数量。就比如,在一篇300多页的文档中提到 term,跟在微博短短一句话提到的 term,肯定是后者的权重会更高。
  • 用于调整 文档长度() 带来的影响。默认值为 0.75,取值范围为[0,1]
    当 = 0 时,BM25 不考虑文档长度,即所有文档被视为相同长度。 当 = 1 时,文档长度的影响最大,较长的文档会受到更多惩罚。 一般来说,b 的推荐值在 0.5 到 0.75 之间,这样可以较好地平衡文档长度的影响。 例如,我们上面提到的文档2、文档4,他们都在同一个分片,仅因为文档4的长度大于文档2,文档4的得分就比文档2低。
  • 、 、 同时存在于分子、分母因此需要放在一起看。 表示 term 出现在文档中的频率,频率越高,文档得分越高。 控制 term 的饱和度,默认值为 1.2,该值通常在 [0.5,2] 之间。 当 越大时,词频的影响会增强。不过当并不会无限增强,而是当达到某一临界值时,就会停止增长。

效果应用

可以看到,我们能调整的值只有 、。下面我带大家写几个例子,通过调整、 直观的感受一下。

将 值设置为 0

通过将 值设置为0,文档的长度将不对算分产生影响。

PUT test_22_1
{
  "settings": {
    "number_of_shards": 3,
    "index": {
      "similarity": {
        "default" : {
          "type" : "BM25",
          "b": 0,
          "k1": 1.2
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "text"
      }
    }
  }
}

写入文档

PUT test_22_1/_doc/1
{
  "name""hello es"
}
PUT test_22_1/_doc/2
{
  "name""hello php"
}
PUT test_22_1/_doc/4
{
  "name""hello world p"
}

查询 hello

GET test_22_1/_search
{
  "query": {
    "match": {
      "name""hello"
    }
  }
}

可以看到,现在文档2、文档4的分数一样了。

将 设置为0

设置为0,文档长度、词频将不对算分产生影响。

PUT test_22_2
{
  "settings": {
    "number_of_shards": 3,
    "index": {
      "similarity": {
        "default" : {
          "type" : "BM25",
          "b": 0.75,
          "k1": 0
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "text"
      }
    }
  }
}

写入文档

PUT test_22_2/_doc/1
{
  "name""hello es"
}
PUT test_22_2/_doc/2
{
  "name""hello php"
}
PUT test_22_2/_doc/4
{
  "name""hello world hello"
}

查询文档

GET test_22_2/_search
{
  "query": {
    "match": {
      "name""hello"
    }
  }
}

可以看到文档2、文档4的分数还是一致。

总结

  1. 文档评分是基于文档所在的分片进行计算的。
  2. 在同一个分片中,1个 term 出现在大多数文档中,那么该 term 的得分就会低,反之则高。
  3. 一个文档中,如果 term 出现的频率越高,它的得分就会越高,频率达到某个阈值后,得分将不再增加。
  4. 用于调整文档长度对评分的影响,默认为 0.75,取值范围 [0,1]。
  5. 用于调整文档词频对评分的影响,默认为 1.2,该值通常在 [0.5,2] 之间。
  6. 、 大多数情况下,我们不需要修改它。