前言
上一章介绍了 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 的评分使用以下公式:
接下来,我将逐一介绍公式中的每个参数。
- 表示当前查询的第几个
term。例如,我们搜索hello world,它包含 2 个term:hello对应 ,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个 term 出现在大多数文档中,那么该 term 的得分就会低,反之则高。
- 一个文档中,如果 term 出现的频率越高,它的得分就会越高,频率达到某个阈值后,得分将不再增加。
- 用于调整文档长度对评分的影响,默认为 0.75,取值范围 [0,1]。
- 用于调整文档词频对评分的影响,默认为 1.2,该值通常在 [0.5,2] 之间。
- 、 大多数情况下,我们不需要修改它。