混合检索与 RRF 是什么
混合检索指同时跑两路召回:向量检索负责语义相近,关键词检索负责精确命中(专有名词、编号、错误码、函数名)。两路各取前若干条后需要融合,RRF(倒数排名融合)只看排名不看分数,把每条结果的名次换算成分值再相加,天然避开两路分数量纲不同的问题,工程上比加权求和省事得多。
基本信息
| 为什么混合 | 语义召回与字面匹配互补,专有名词场景更稳 |
|---|---|
| RRF 公式 | 分值等于各列表中排名倒数的累加,常数项通常取六十 |
| 输入 | 若干条已按相关性排序的结果列表 |
| 输出 | 融合后的统一排序,可再送重排模型精排 |
| 优势 | 不需要分数归一化,各路权重不必拍脑袋 |
| 可选增强 | 融合后再上重排模型,效果通常还能再提升一截 |
两路召回的分工
| 检索方式 | 强项 | 弱项 |
|---|---|---|
| 向量检索 | 语义相近、同义改写、跨语言 | 精确编号与稀有名词容易漏 |
| 关键词检索 | 错误码、型号、人名、专有名词 | 换种说法就检索不到 |
| 融合排序 | 兼顾两类命中并统一排名 | 需要保证候选池够宽 |
| 重排模型 | 对融合结果做精细打分 | 增加一次推理开销 |
典型使用场景
- 技术文档问答:既要理解语义又要精确命中错误码与参数名
- 法律与制度检索:条文编号与措辞必须精确匹配
- 商品搜索:型号、品牌词与自然语言描述混合查询
- 日志排障:错误码与语义描述并存,单路召回都不够
- 多语言知识库:跨语言语义检索配合原文关键词命中
RRF 融合实现
RRF 融合实现
def rrf(rank_lists, k=60):
score = {}
for lst in rank_lists: # 每个列表按相关性降序排列
for rank, doc_id in enumerate(lst, start=1):
score[doc_id] = score.get(doc_id, 0.0) + 1.0 / (k + rank)
return sorted(score, key=score.get, reverse=True)
fused = rrf([dense_top50, bm25_top50])[:20] # 两路各取 50 条再融合
# 候选池越宽融合收益越大,只取 top5 融合几乎没有意义落地要点
| 要点 | 做法 | 原因 |
|---|---|---|
| 候选池宽度 | 每路多取,融合后再截断 | 池子太浅时融合没有可选空间 |
| 常数项取值 | 从常用默认值起步再微调 | 影响头部结果的权重分配 |
| 去重 | 按文档标识合并同一内容的不同片段 | 避免同一文档霸占多个名次 |
| 重排 | 融合后送重排模型 | 进一步提升前几条的精度 |
| 评测 | 用同一评测集对比单路与融合 | 确认收益真实存在 |
融合后的前几名会送进生成环节,所以候选池的宽度与去重策略直接影响答案质量,值得单独调。
只取各路前几条做融合是常见无效实现:候选池太浅,融合前后的结果几乎没有差别,白加了一层复杂度。