连载中 8/20

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

2026-08-14 · 1016 阅读 · 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)

热门推荐

连载中 11/22

主从搭建实操:从零配出一主两从

光讲原理不过瘾?手把手搭一主两从:my.cnf 六个参数、复制账号、GTID、CHANGE REPLICATION SOURCE TO、SHOW REPLICA STATUS 验收,附翻车排查清单。

#MySQL#主从复制#GTID#主从搭建#高可用
2026-05-07 · 10101 阅读 · 0 评论 · 0 赞
连载中 16/22

连接池:HikariCP 参数与连接风暴

连接池不是越大越好:8 核机器配 1000 连接反而更慢的数学原理,HikariCP 四个必调参数,maxLifetime 与 wait_timeout 的隐形陷阱。

#MySQL#连接池#HikariCP#maxLifetime#连接风暴
2026-05-10 · 9873 阅读 · 0 评论 · 0 赞
连载中 4/16

缓存穿透:恶意 ID 打穿 MySQL 的四道防线

请求的数据在缓存和数据库里都不存在时,缓存形同虚设。聊聊参数校验、空值缓存、布隆过滤器、限流熔断四道防线的原理与组合打法。

#Redis#缓存穿透#布隆过滤器#高可用
2026-05-16 · 9294 阅读 · 21 评论 · 287 赞