搜索排序优化:BM25、召回-排序-重排与中文分词实战

全面讲解微型博客的搜索排序优化:相关性排序(BM25/TF-IDF)、新鲜度与权威性加权、召回-粗排-精排-重排四阶段漏斗、中文分词与查询改写、搜索日志与点击反馈、以及搜索效果的评估与迭代。

搜索是微型博客的「信息导航」——用户输入关键词,期望「最相关」的结果排在最前。但「最相关」没有唯一答案:含关键词的帖、刚发布的帖、高互动的帖、权威作者发的帖,哪个该排前面?

本文系统讲解:相关性模型(BM25)、四阶段排序漏斗、中文分词与查询改写、排序特征与效果评估。

一、相关性排序:从 TF-IDF 到 BM25

1.1 核心问题

关键词「Go 并发」
  帖 A:标题含「Go 并发」2 次,正文 1 次
  帖 B:正文含「Go 并发」10 次但很长
  谁更相关?→ 要看词频、文档长度、词在全库的稀有度

1.2 TF-IDF

TF = 词在文档中出现次数(频度越高越相关)
IDF = log(N / df)(词越稀有越有区分度,如「并发」优于「的」)
得分 = Σ TF(t,d) × IDF(t)
局限:未考虑文档长度归一化

1.3 BM25(现代标准)

BM25 对 TF 做饱和化、对长度做归一化:
  score = Σ IDF(t) × [tf·(k1+1)] / [tf + k1·(1-b+b·dl/avgdl)]
  k1 ≈ 1.2(词频饱和)
  b ≈ 0.75(长度归一化强度)

- 词频有上限(多一次词频收益递减)
- 长文档惩罚(同样的词出现,短文档更相关)

1.4 混合:BM25 + 新鲜度 + 互动

最终分 = BM25 相关性 × α
       + 新鲜度(时间衰减)× β
       + 互动热度(赞/转/评)× γ
       + 作者权威 × δ

参数通过搜索日志回归 / 人工调优

一句话总结:BM25 用「词频饱和 + 长度归一 + 稀有度」给出稳定的相关性基线,再叠加新鲜度与热度——排序永远是多信号加权,而非单一相关性。

二、四阶段排序漏斗

2.1 漏斗结构

召回(Recall)    → 粗排(Coarse)    → 精排(Fine)    → 重排(Re-rank)
候选 10000        → 候选 500          → 候选 100        → 最终 50
(快、宽)          (轻量打分)        (精细模型)      (多样性/去重)

2.2 各阶段职责

阶段速度模型/方法输出
召回毫秒级倒排索引 + 前缀匹配 + 同义词候选池
粗排十毫秒线性加权(BM25 + 新鲜度 + 热度)截断候选
精排百毫秒学习排序模型(GBDT/DNN)Top 排序
重排毫秒级多样性、去重、已看过滤最终结果

2.3 粗排示例

# 粗排:可解释的线性打分
def coarse_score(doc, query, now):
    bm25 = bm25_score(doc, query)
    freshness = exp(-age_hours(doc, now) / 72.0)   # 3 天半衰
    hot = log1p(doc.likes + doc.reposts + doc.comments)
    return 1.0 * bm25 + 0.8 * freshness + 0.5 * hot

2.4 精排模型

特征分组:
  - 相关性:BM25、词覆盖、位置
  - 质量:长度、图片/视频、完整度
  - 互动:赞/转/评/收藏、互动率
  - 作者:粉丝数、发布历史、权威分
  - 新鲜:发布距今、最后互动时间
  - 个性化:与你历史行为的相似度

模型:GBDT(XGBoost/LightGBM)或浅层 DNN,pairwise 训练

一句话总结:排序漏斗用「宽召回 + 轻粗排 + 重精排 + 重排收尾」四段,把「又要快又要准又要多样」的冲突拆解到各阶段——每个阶段只做它擅长的事。

三、中文分词与查询理解

3.1 为什么中文分词难

「区块链技术」→ 词典可分:区块链 | 技术
「南京市长江大桥」→ 歧义:南京市/长江大桥 vs 南京/市长/江大桥
中文无空格边界 → 必须分词才能建索引与匹配

3.2 分词方案

- 词典分词(jieba/IK):快、可控、可加自定义词典
- 统计分词(HMM/CRF):处理未登录词
- 语义分词(BERT 细粒度):精确但重
- 混合:词典优先 + 未登录词兜底

微型博客常用:jieba / IK + 自定义词表(平台术语、人名、话题)

3.3 索引与匹配

建索引:把帖子分词 → 每个词写入倒排(token → post list)
查询:query 分词 → 多词查倒排 → 合并打分

同义词/近义:查询改写「手机」→「手机|移动电话」
拼音/纠错:query 纠错「Go 语方」→「Go 语言」

3.4 分词一致性陷阱

- 索引分词与查询分词必须一致(同一分词器)
- 全半角、大小写、繁简统一
- 数字/英文/符号的 token 策略

一句话总结:中文搜索的第一关是「分词」——词典 + 自定义词表 + 纠错改写,保证查询与索引在同一 token 空间,才能谈相关性。

四、搜索日志与点击反馈

4.1 埋点

每次搜索记录:
  query、用户、结果列表、点击了第几条、停留时长
  → 构成「搜索日志」

4.2 点击率反馈

CTR = 点击 / 曝光
  结果排序越好 → 前列 CTR 越高
  可用点击数据做:排序调参、特征回填、模型训练样本

4.3 评估指标

指标含义
CTR@K前 K 位点击率
首点位置用户第一次点击的排名(越靠前越好)
无结果率query 无结果的占比(可优化召回)
深度点击翻页点击 → 前列不满意
长尾 query无法匹配 → 需同义词/改写

一句话总结:搜索好不好,靠「日志说话」——点击率、首点位置、无结果率是排序质量的直接证据,也是调参与迭代的数据源。

五、效果评估与迭代

5.1 离线评估

人工标注(相关性分级)→ 计算 NDCG@K
  NDCG 同时考虑「相关性」与「位置」:前列放高相关才得分高
  离线跑模型 → 对比基线 NDCG 是否提升

5.2 在线评估(A/B)

- 新排序版本灰度 → 对照组对比
- 观察 CTR、深度点击、用户留存
- 指标显著提升 → 全量

5.3 迭代闭环

搜索日志 → 分析坏例(用户点了第 5 条而非第 1 条)
        → 定位原因(相关/新鲜/热度/分词)
        → 调整权重或特征
        → 离线验证 + A/B → 上线
        → 继续收集日志

一句话总结:搜索优化是「日志 → 坏例 → 调参 → A/B → 上线」的持续闭环——没有评估就没有迭代,没有坏例分析就不知道往哪调。

六、工程落地要点

6.1 架构

用户 query
  → 查询服务(分词 + 改写)
  → 召回(倒排索引 / Elasticsearch)
  → 粗排(线性权重,批量)
  → 精排(模型服务)
  → 重排(多样性)
  → 结果

缓存:
  热门 query 结果缓存(短 TTL)
  个性化部分不缓存或按用户分桶

6.2 降级

- 精排模型超时 → 用粗排结果直接返回
- ES 不可用 → 降级 PostgreSQL 全文检索(tsvector)
- 热门 query → 缓存兜底

6.3 性能预算

- 整体 P99 < 200ms
- 召回 < 20ms、粗排 < 30ms、精排 < 100ms、重排 < 30ms
- 模型预加载 + 批处理

一句话总结:搜索工程 = 架构分层 + 缓存 + 降级 + 性能预算——把召回排序拆成可独立超时的服务,才能保证「慢一点可以、挂了不行」。

七、总结

微型博客搜索排序的优化路径可以概括为:先相关性、再漏斗、后反馈。BM25 给相关性基线,新鲜度/热度/权威叠加成全信号;四阶段漏斗把快与准拆解到召回-粗排-精排-重排;中文分词与改写解决「query 理解」;搜索日志与点击反馈提供评估与迭代素材;离线 NDCG 与在线 A/B 构成持续优化闭环。

搜索是微型博客「内容价值」的放大器——排序排对了,好内容被看见,创作者获得反馈,平台形成内容正循环。把相关性、漏斗、反馈三件事做透,搜索就从「能搜」进化为「搜得准」。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「miniblog」更多文章

  1. 速率限制与防滥用:令牌桶、滑动窗口与分布式限流
  2. 通知系统:通知类型、聚合去重、多端同步与推送架构
  3. 评论与互动系统:评论树、@提及、点赞转发与互动计数一致性