Elasticsearch BM25 搜不准?别硬抄 k1=1.2、b=0.75,我拿 5 万条中文短文本调成 0.8/0.35

🔑 关键词:BM25,k1,b,Elasticsearch,搜索排序

📖 摘要:从 5.2 万条本地生活短文本的踩坑出发,讲清 BM25 的 k1 和 b 怎么影响中文搜索排序,给出可复现的调参步骤、评估指标和参数网格。

先说结论:Elasticsearch 默认的 k1=1.2、b=0.75 不是圣旨

图片

我去年帮朋友调一个本地生活搜索,索引 5.2 万条商户,字段就 titletagsdescription。搜“朝阳 修车”,前 10 里有 4 个是“朝阳轮胎”,还有 2 个是“修车工具批发”。我第一反应是分词,换了 ik_smartik_max_word,甚至加了同义词,NDCG@10 只从 0.39 爬到 0.43。后来才发现,问题不在分词,在 BM25 的 b。Lucene/Elasticsearch 默认 b=0.75,意思是长度归一化挺狠。可中文短文本标题长度差别很小,一个 6 字标题和一个 12 字标题,按英文段落逻辑去归一化,反而把“朝阳”这种高频地域词权重搅乱了。

我最后的参数不是 1.2/0.75,而是 0.8/0.35

图片

那轮网格搜索我跑了 5 组 k1:0.6、0.8、1.0、1.2、1.5;5 组 b:0、0.25、0.35、0.5、0.75。评估用 rank_eval,指标看 NDCG@10、MRR@10、Recall@50。5.2 万条里抽了 800 条 query,人工标了 3 档相关性。最优组合是 k1=0.8、b=0.35,NDCG@10 从默认的 0.41 到 0.58,MRR@10 从 0.36 到 0.52。注意,这不是说 Elasticsearch 错了,而是默认值来自英文长文档的传统,短文本、强字段权重的场景要自己试。

BM25 到底在惩罚什么,很多人没搞清

图片

BM25 公式里,k1 管词频饱和,b 管长度归一化。TF-IDF 里一个词出现 10 次就线性加分,BM25 不会,它会在某个点后饱和。k1=1.2 表示饱和比较慢,适合长文档;中文短文本里关键词重复少,k1=1.2 有时让一个“修车”刷太高。b=0.75 则让长文档被压得更狠。短文本标题里,长度差异可能只是“修车”和“朝阳区修车补胎”,b 太大,后者不一定占优。我自己的判断是,中文短文本先把 b 拉到 0.25 到 0.45 之间试,k1 从 0.6 到 1.0 之间试,别一上来就 2.0。

具体怎么调:5 步,别一上来就换向量

第一步,先把数据导出成 query, doc_id, label 格式,至少 500 条 query,每 query 标 10 个 doc。第二步,在 settings 里给字段设 similarity

图片

similarity:
  my_bm25:
    type: BM25
    k1: 0.8
    b: 0.35

第三步,multi_match 里给字段权重:title^3tags^2description^1。第四步,用 _rank_eval 跑网格,别只看前 3 条,看 NDCG@10 和 Recall@50。第五步,线上 AB 至少 7 天,看 CTR、CVR、无结果率。如果无结果率涨了,先降 b,再降 k1

图片

和 TF-IDF、BM25+、向量检索怎么选

TF-IDF 适合教学,线上基本别裸用,因为它没有词频饱和,长文档容易被堆词。BM25 是搜索的默认基线,Elasticsearch 7.x 之后默认就是 BM25。BM25+ 多了 delta,对长文档更友好,常见 delta=1.0,但你得自己实现或找插件。BM25F 分字段加权,比 multi_match 的 boost 更细,适合标题、作者、标签差异大的场景。向量检索适合语义召回,但成本高,5.2 万条还能扛,500 万条就要算账。我的观点:先调 BM25,再上混合检索,别拿向量模型给脏数据擦屁股。

图片

一个小坑:别把 k1b 当成万能旋钮

k1 调到 2.0 以上,词频饱和太慢,标题党关键词容易霸榜;b=0 完全不归一化,长文档可能占便宜;b=1 又惩罚太狠。我们还试了 k1=0.6、b=0.25,NDCG@10 只有 0.54,比最优低 0.04。另一个坑是字段长度:titledescription 放在同一个字段里,长度统计会混,最好分开建字段。最后,如果你的 query 里经常有“附近”“24小时”这种意图词,BM25 调参救不了,得加规则或向量召回。我的数据是本地生活,不代表你的电商、法律、论文库。你先拿 1000 条 query 跑一遍,再决定抄不抄。

🏷️ 标签: