Elasticsearch 检索模型解析:BM25

849 阅读3分钟

什么是 BM25?

BM25(Best Matching 25)是一种改进的 TF-IDF 排序算法,在 Elasticsearch 中默认作为相关性计算模型。相比 TF-IDF,BM25 更好地考虑了:

  • 词频饱和(Term Frequency Saturation)
  • 文档长度归一化(Document Length Normalization)

BM25 的基本公式拆解

最终得分的计算可以表示为:

score(freq) = boost × idf × tf

其中三部分分别是:


1. TF 子公式(词频饱和处理)

tf = freq / (freq + k1 * (1 - b + b * (dl / avgdl)))
  • freq: 查询词在当前文档中的出现次数
  • dl: 当前文档的长度(token 数)
  • avgdl: 所有文档的平均长度
  • k1: 调节词频饱和程度的参数(默认 1.2)
  • b: 控制文档长度归一化的程度(默认 0.75)

这个公式体现了“频率越高,得分越高,但增长逐渐变缓”的特性。


2. IDF 子公式(逆文档频率)

idf = log(1 + (N - n + 0.5) / (n + 0.5))
  • N: 索引中总文档数量
  • n: 包含查询词的文档数量

IDF 越大说明词越稀有,越能区分文档,得分越高。


3. Score 完整公式

假设 freq = 3boost = 1.0,则:

score(freq=3) = idf × (3 / (3 + k1 * (1 - b + b * (dl / avgdl))))

参数含义与影响分析

参数一:k1(TF 饱和调节器)

  • 典型取值:1.2(Elasticsearch 默认)
  • 意义:决定词频增长的“速度”
k1 值影响
较小TF 饱和快,词频影响小(更关注是否包含)
较大TF 饱和慢,词频影响大(更关注出现次数)

固定 b,观察 k1 的变化效果:

  • k1 → 0,公式趋于布尔匹配(是否包含)
  • k1 → ∞,公式趋于线性 TF 权重(TFIDF 形式)

推荐:在语料差异大时适当提高 k1 会强化词频的区分能力。

补充:BM25 有一个上限,文档里出现 5 到 10 次的词会比那些只出现一两次的对相关度有着显著影响。但是如图 TF/IDF 与 BM25 的词频饱和度 所见,文档中出现 20 次的词几乎与那些出现上千次的词有着相同的影响。

这就是 非线性词频饱和度(nonlinear term-frequency saturation)  。

截屏2025-07-17 10.27.52.png


参数二:b(文档长度归一化调节器)

  • 典型取值:0.75(Elasticsearch 默认)
  • 意义:是否惩罚/奖励过长或过短的文档
b 值影响
0忽略文档长度,类似 TF-IDF
1完全应用文档长度归一化

固定 k1,观察 b 的变化效果:

  • b=0:对所有文档一视同仁(不管文档长短)
  • b=1:长文档的 TF 会被显著缩小(被惩罚)
  • 适当调低 b 可防止短文档因词频密度高而得分过高

推荐:长文档较多的场景(如新闻、文档库)可适当增加 b,防止评分偏差。


总结

参数变化评分变化趋势(定性)场景建议
↑ k1提升高 TF 文档得分,增强 TF 区分度关键词重复较多的文档
↓ k1TF 区分度降低,趋于布尔匹配简短文档,高相关词命中
↑ b长文档得分下降,短文档得分上升需要防止“水文”得分高的场景
↓ b忽略长度影响,仅依赖 TF文档长度差异不大

Elasticsearch 中如何使用 BM25?

BM25 是默认的相似度算法。你也可以在 mapping 中手动设置:

PUT my_index
{
  "settings": {
    "similarity": {
      "default": {
        "type": "BM25",
        "k1": 1.2,
        "b": 0.75
      }
    }
  },
  "mappings": {
    "properties": {
      "content": {
        "type": "text"
      }
    }
  }
}

可以按字段指定,只需在映射中为不同字段选定即可:

PUT /my_index
{
  "mappings": {
    "doc": {
      "properties": {
        "title": {
          "type":       "string",
          "similarity": "BM25" 
        },
        "body": {
          "type":       "string",
          "similarity": "default" 
        }
      }
  }
}

配置相似度算法和配置分析器很相似,自定义相似度算法可以在创建索引时指定

PUT /my_index
{
  "settings": {
    "similarity": {
      "my_bm25": { 
        "type": "BM25",
        "b":    0 
      }
    }
  },
  "mappings": {
    "doc": {
      "properties": {
        "title": {
          "type":       "string",
          "similarity": "my_bm25" 
        },
        "body": {
          "type":       "string",
          "similarity": "BM25" 
        }
      }
    }
  }
}

目前,Elasticsearch 不支持更改已有字段的相似度算法 similarity 映射,只能通过为数据重新建立索引来达到目的。修改 k1b 后需要 重建索引 并重新导入数据。

参考文档

www.elastic.co/guide/cn/el… www.elastic.co/guide/cn/el…