搜索是微型博客的「信息导航」——用户输入关键词,期望「最相关」的结果排在最前。但「最相关」没有唯一答案:含关键词的帖、刚发布的帖、高互动的帖、权威作者发的帖,哪个该排前面?
本文系统讲解:相关性模型(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 构成持续优化闭环。
搜索是微型博客「内容价值」的放大器——排序排对了,好内容被看见,创作者获得反馈,平台形成内容正循环。把相关性、漏斗、反馈三件事做透,搜索就从「能搜」进化为「搜得准」。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。