Elasticsearch BM25 原理:为什么相关度排序靠谱

搜索"后端工程师",返回的结果凭什么排成这样?Elasticsearch 默认的 _score 排序用的是 BM25 算法。这篇文章把它讲透:三个因子、两个参数、以及和向量检索怎么配合。

一、BM25 是什么

BM25 是词频相关度算法的改进版,ES 5.0 起替代老的 TF-IDF 成为默认。核心思想:文档和查询越"相关",分越高。由三个因子组成:

s c o r e = I D F × T F _ n o r m × ( )

二、三个因子逐个拆

因子 1:IDF(逆文档频率)——这个词有多稀有

I N n D F = = = l n ( 1 + ( N - n + 0 . 5 ) ( n + 0 . 5 ) )

直觉

  • “Elasticsearch"出现在 100 篇里 → 稀有 → IDF 高 → 匹配了很加分
  • “的"出现在 90 万篇里 → 常见 → IDF 接近 0 → 匹配了不加分

这就是为什么搜索"简历 杭州"时,“杭州"比"简历"更能区分文档(简历到处都有,杭州更稀有)。

因子 2:TF 归一化(词频)——出现次数,但有上限

T f d a k b F r l v 1 _ e g n q d o l r m = = = = = = ( f r e q × ( 1 k . 0 1 2 . 7 + 5 1 ) ) ( f r e q + k 1 × ( 1 - b + b × d l / a v g d l ) )

关键设计:词频有饱和

  • 出现 1 次 → 3 次:分涨得明显
  • 出现 20 次 → 50 次:分几乎不涨

为什么:一篇文章提"Go"50 次不一定比提 10 次的相关性高 5 倍——可能只是废话多。饱和防止长文靠堆词刷分

因子 3:长度归一化——短文更"值钱”

dl/avgdl:文档越长,TF 归一化衰减越多。

直觉:100 字的摘要里出现"Redis”,比 5000 字的论文里出现"Redis"更能说明主题相关——同样的词,在短文里的信息密度更高

三、两个参数:k1 和 b

k1:词频饱和速度(默认 1.2)

  • 调大(1.5-2.0):词频对分数影响更久(多出现更加分)→ 适合长文档主题判定
  • 调小(0.5-1.0):出现几次就饱和 → 适合短文本、关键词精准匹配

b:长度归一化强度(默认 0.75)

  • 调大(接近 1):长度惩罚更狠 → 短文优先
  • 调小(接近 0):长度影响小 → 长文也能排前面

调参实战

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 简历搜索场景:简历长短差异大,b 要调小,防止长简历全被压下去
{
  "index": {
    "similarity": {
      "default": {
        "type": "BM25",
        "k1": 1.4,
        "b": 0.55
      }
    }
  }
}

调参前提:先建评测集(50-100 条标注好的"哪个结果该排前”),调参后跑评测对比——不评测的调参都是玄学。

四、BM25 的局限(为什么需要向量检索)

BM25 是词汇匹配:查询词和文档词必须字面重合。

" " B M 2 5 " " " " " " " "

这是 BM25 的天花板:理解不了语义。所以生产上:

= B M 2 5 R e r a n k T o p K

BM25 管"字面对得上",向量管"意思靠得近"——两者是互补不是替代。这就是我在 RAG 工程化里混合检索的依据。

五、相关度调优的实战套路

1 2 3 4 . . . . - - - - B M 2 5 R e r 线 a s n b t k o p b w g o e k r / - 1 d r s e r a n k e r

总结

BM25 的核心认知:

  1. 三个因子:IDF 管稀有度、TF 管词频(有饱和)、长度归一化管信息密度
  2. 两个参数:k1 调饱和速度,b 调长度惩罚——先建评测再调参
  3. 天花板:词汇匹配,理解不了语义
  4. 出路:BM25 + 向量 + Rerank 三件套

相关度排序没有银弹,BM25 是那个"默认就挺靠谱"的基线——理解它,你才知道什么时候该调它,什么时候该加向量。