连载中 8/20

评分:_score 到底怎么算出来的

2026-08-14 · 1011 阅读 · 0 评论 · 0 赞

运营的怒火:爆款为什么排后面

搜索上线后,老王收到运营的连环质问:搜「燕麦」,月销一万的「燕麦拿铁」排第二,月销三百的「燕麦酥」排第一——这排序是认真的吗?打开结果一看,每条文档都带着一个 _score。这个分数不是魔法,是算法:ES 默认用 BM25 给每条命中文档打分。想干预排序,先得看懂它在算什么。

BM25 的三个因素,说人话版

BM25 公式长得吓人,但拆开就三个直觉:

第一,词频 TF,但会饱和。「燕麦」在文档里出现 3 次比出现 1 次分高,但第 3 次的增益远不如第 2 次——出现得越多越不稀奇,分数增长越来越钝。控制饱和度的旋钮叫 k1。这个设计天然反作弊:把关键词在页脚堆一百遍,也涨不了多少分。

第二,逆文档频率 IDF,越稀有越值钱。「燕麦」在十万件商品里只有三百件含它,信息量大,分高;「的」「大杯」哪儿都有,信息量为零,分低。搜「燕麦 大杯」,命中的文档主要靠「燕麦」拉开分差。

第三,字段长度归一,短字段命中更值钱。同样命中「拿铁」,在 8 个字的商品名里命中,比在 800 字的商品详情里命中更说明问题。控制强度的是 b 参数。这就是「燕麦酥」能反超的原因:它的名字短,「燕麦」两个字在标题里占比高,长度归一把它抬了上去。

用 explain 亲眼看打分

与其背公式,不如让 ES 把计算过程摊开。加一个 explain 参数,或者用 explain 接口:

GET /product/_explain/1024
{
  "query": { "match": { "name": "燕麦" } }
}
返回的 description 里能看到:
score = tf(频率饱和) x idf(稀有度) x 字段长度归一
每一步都有数值,谁抬的分谁拖的分一目了然

排查「为什么它排前面」的问题,explain 是第一工具。老王看完就懂了:燕麦酥赢在字段短,不是算法针对爆款。

调 k1 和 b?不如交给 function_score

第一反应是调 BM25 参数让爆款上来,先劝退:k1、b 是全局旋钮,牵一发动全身,调完所有查询的排序都变,属于玄学调参。业务诉求(销量高的靠前、新品加权)不该塞进相关性算法,该用 function_score,把相关性分和业务分明明白白合在一起:

GET /product/_search
{
  "query": {
    "function_score": {
      "query": { "match": { "name": "燕麦" } },
      "functions": [
        {
          "field_value_factor": {
            "field": "sales", "modifier": "log1p", "factor": 1
          }
        },
        {
          "filter": { "term": { "tags.keyword": "新品" } },
          "weight": 2
        }
      ],
      "score_mode": "sum",
      "boost_mode": "multiply"
    }
  }
}

逐块解释:functions 里每个函数算出一个业务分。field_value_factor 拿销量字段开方——用 log1p 修正是关键,直接拿原始销量当分,月销一万的商品分数会爆炸,一立方把 10000 变成约 4.6,曲线立刻温和。score_mode 说的是多个业务分怎么合(sum 相加、multiply 相乘、max 取大),boost_mode 说业务分和相关性分怎么合(multiply 相乘是最常用组合:相关性为前提,业务分做放大器)。

还有两招常用招式:random_score 加随机扰动,让搜索页每次刷新略有变化,用户感觉商品丰富;衰减函数(gauss)能实现「离我越近越靠前」「越新越靠前」这类平滑衰减。运营想插广告位、想置顶单品,也都是在 function_score 里加 filter 加 weight,规则清晰可回滚。

小结

评分三句话:BM25 算的是词频饱和、稀有度加成、短字段红利;看不懂排序先 explain,让 ES 自己交代;业务加权别动 BM25 参数,function_score 的 filter 加 weight 干净利落。排序不满意,八成不是算法错,是业务规则还没写进去。

排序理顺了,搜索页还差最后一块拼图:结果里关键词不高亮、用户打错字搜不到东西。下一篇讲进阶查询:高亮、拼写纠错与多字段搜索,把搜索页的用户体验一次配齐。

503

10 年全栈工程师 · 503咖啡馆主理人

#Elasticsearch#评分#BM25#function_score#排序

评论 (0)

相关推荐

连载中 12/20

排查四件套:jstack、jmap、jstat、jcmd 的实战分工

jstack 看线程在干什么,jmap 看堆里装了什么,jstat 看运行时在变什么,jcmd 是统一入口。四把刀各管一段,配合着用没有查不动的现场。

#jstack#jmap#jstat#jcmd#排查工具
2026-09-16 · 5 阅读 · 0 评论 · 0 赞
连载中 11/20

GC 日志:把回收过程翻译成人话

一行 GC 日志里塞着七种信息:谁触发的、收了哪、停了多久、活了哪些。加上 -Xlog 配置,再加上日志分析工具,GC 不再是只能盯监控曲线的黑盒。

#GC日志#Xlog#日志分析#GC监控#Full GC排查
2026-09-16 · 13 阅读 · 0 评论 · 0 赞
连载中 10/20

ZGC:亚毫秒停顿是怎么炼成的

百 G 大堆停顿不到一毫秒,靠的是把搬家全部挪到并发阶段——着色指针让引用自带状态,读屏障让搬运中的对象依然可访问。代价是吞吐与内存,收益是停顿与堆大小解耦。

#ZGC#着色指针#读屏障#亚毫秒停顿#分代ZGC
2026-09-15 · 8 阅读 · 0 评论 · 0 赞