读完这篇,你能做什么
能比较单路、融合和重排四种方案,并解释正确资料在哪一阶段被找回或丢失。
学习理由、准备要求与知识点
为什么学它
只按关键词可能漏掉换了说法的内容,只按语义又可能错过精确类名。组合检索和重排后,需要实测是否找得更好。
先读这些内容
知识点清单
- 关键词与向量检索互补
- BM25 与向量分数尺度
- RRF 排名融合
- 候选去重
- 双编码器与交叉编码器
- 重排候选边界
- 多路权限过滤一致性
- 重排延迟和输入预算
运行前先确认完整算法使用 Java 17 标准库;JDK 源码基线为 OpenJDK 17 GA。示例是内存中的机制模型,外部服务接入需要另外配置和验证。
01先弄懂它是什么
查技术笔记时,有的问题适合按意思找,有的问题需要精确匹配。例如“事务隔离”可以用语义检索,某个异常类名或配置键则往往适合关键词检索。混合检索让不同方式各找一批候选,再按统一规则合并。它要解决两类查询各自的长处,不能只把两个不同分数随意相加。
两套检索结果不能随手把分数相加,因为它们的“分”可能不是同一种尺子。关键词检索中的 BM25 分数受词频和语料影响,余弦相似度通常处于有限范围,直接加起来可能长期由一边说了算。RRF 换一种办法:只看文档在每张榜单上排第几,再把名次变成贡献后相加。这容易核对,但也看不出原榜第一名究竟领先第二名多少;效果仍需实测,不能保证胜过单种检索。
重排就是先找到一小批候选,再更仔细地比较“这段文字是否回答了这个问题”。双编码器分别把问题和文档变成向量,所以文档向量可以提前算好存起来;交叉编码器通常把问题与某个候选一起处理,让二者在计算中充分交互,但每多检查一个候选都会增加工作量。它只能重排手头已有的候选,救不回从未找来的文档。因此先查有没有召回正确资料,再查正确资料有没有排到前面。
文中这几个词,先认一认
- 候选召回
- 先从大量资料中找出一小批可能相关的内容,后续排序只能在这批候选里选择。
- RRF
- 把结果在不同列表中的名次,转换成倒数贡献后相加,避免直接混用量纲不同的原始分数。
- 交叉编码器
- 把查询和某个候选一起送入模型,计算它们有多相关;通常用来重新排列一批数量有限的候选。
02看看它怎么工作
本题定义每个列表名次从一开始,文档贡献为1/(c+rank),总分为各列表贡献之和。c越大,相邻名次差距相对越小;c必须为正且写入实验配置。文档未出现的通道贡献零,不能把“未出现”擅自视为排在列表末尾。
假设关键词列表A、B,向量列表B、C,c为一:A得二分之一,B得三分之一加二分之一,C得三分之一,因此顺序B、A、C。单路重复ID会让同一文档多次加分,本题直接拒绝;跨路同ID则正是融合对象,需要累加。
融合后先去重再限定重排数量,确保各通道权限过滤一致。重排输入应使用相同版本的候选正文,不能分数来自新文档、展示却读旧文档。最终按分数和稳定ID排序,保留各通道名次与重排版本,便于解释结果变化。
正在加载流程图…
查看流程图源码(Mermaid)
flowchart LR
Q["查询与权限"] --> K["关键词召回"]
Q --> V["向量召回"]
K --> F["RRF融合与去重"]
V --> F
F --> C["有限候选"]
C --> R["联合文本重排"]
R --> E["分阶段评估"]按顺序看,每一步做了什么
- 固定查询集并建立关键词、向量两条单路基线。
- 在相同权限和文档版本下分别召回候选。
- 拒绝单路重复ID,以RRF聚合跨路名次。
- 截取有限候选进入重排,记录输入预算与耗时。
- 分别评价召回覆盖、最终排序和答案支持。
03对照实现代码
OpenJDK 17 GA,jdk-17+35 · TreeMap.merge / comparator
下方为原创 Java 教学片段,调用已核验的 JDK API 说明局部机制;不是该类原样源码,也不实现神经网络。完整程序见算法模块。
Map<String, Double> scores = new TreeMap<>();
for (int i = 0; i < ranking.size(); i++) {
double contribution = 1.0 / (constant + i + 1.0); // 名次从 1 开始。
scores.merge(ranking.get(i), contribution, Double::sum); // 跨通道同ID累计贡献。
}
// TreeMap 按ID管理键,最终还需按融合分数排序。TreeMap使用有序树维护键次序,merge把已有值与新贡献交给合并函数。键顺序与融合分数顺序不同,不能把map迭代次序当最终排名。
选择TreeMap让键管理成本为对数级并方便稳定输出,但RRF的数学规则与容器无关。也可以使用HashMap再排序,只需保持同样的重复和并列规则。
浮点加法受舍入影响,固定通道顺序和计算方式便于复现。若需要严格有理数比较,可保留分数的分子分母,但大整数成本也会上升。
Java片段是原创排名融合实现,RRF定义见Elastic官方文档;它没有实现BM25、Embedding或交叉编码器,这些通道需分别配置与评价。
代码先调用谁,再调用谁
- 读取每路已排序候选
- 检查单路ID唯一
- 按一基名次计算贡献
TreeMap.merge聚合- 按融合分数和ID形成候选序列
04动手写一个例子
多路RRF排名融合
输入若干无重复ID的排名列表与正参数c,输出融合分数降序的ID;分数相等按ID升序。空输入输出空,单路重复拒绝。Java17 Main.java内置固定手算测试。
- 输入 1
列表[A,B]与[B,C],c=1- 输出 1
B,A,C
- 输入 2
列表[B]与[A],c=1- 输出 2
A,B
每条候选只贡献它在当前列表的名次倒数。单路已见集合负责防止重复加分,跨路则允许并应当累加。
全部通道处理完后,每个ID的值就是公式定义的总和;再用分数降序和ID升序比较,形成完整稳定排名。
本算法输出完整融合列表,若只需前k项可换成有界堆;但当前候选量通常已受召回上限控制,全量排序的实现更易检查,是否优化应由测量决定。
为什么这样写是对的处理完前若干候选后,scores[id]恰好等于这些已处理通道名次对该ID的贡献之和,每个ID在单一通道最多计一次。
R为所有列表总长度,U为不同ID数,M为最长单路长度。TreeMap更新O(log U),排序U项;字符串比较的长度成本需另外考虑。
保存为 Main.java,运行 javac --release 17 -encoding UTF-8 Main.java,再运行 java Main。程序包含固定输入自测,断言失败时会抛出错误。
import java.util.*;
public class Main {
static List<String> fuse(List<List<String>> lists,double c){
if(!Double.isFinite(c)||c<=0)throw new IllegalArgumentException("constant");
Map<String,Double> scores=new TreeMap<>();
for(List<String> list:lists){
Set<String> seen=new HashSet<>();
for(int i=0;i<list.size();i++){
String id=list.get(i);if(id==null||!seen.add(id))throw new IllegalArgumentException("duplicate in one list");
scores.merge(id,1.0/(c+i+1.0),Double::sum); // 跨路同ID累加,名次从1开始。
}
}
List<String> ids=new ArrayList<>(scores.keySet());
ids.sort((a,b)->{int x=Double.compare(scores.get(b),scores.get(a));return x!=0?x:a.compareTo(b);}); // 分数降序、ID升序。
return ids; // 不把TreeMap的键序误当融合分数序。
}
static void eq(List<String> e,List<String> a){if(!e.equals(a))throw new AssertionError(a);}
public static void main(String[] args){
eq(List.of("B","A","C"),fuse(List.of(List.of("A","B"),List.of("B","C")),1));
eq(List.of("A","B"),fuse(List.of(List.of("B"),List.of("A")),1));
eq(List.of(),fuse(List.of(),60));
eq(List.of("A","B"),fuse(List.of(List.of("A","B"),List.of()),60));
try{fuse(List.of(List.of("A","A")),1);throw new AssertionError();}catch(IllegalArgumentException expected){}
try{fuse(List.of(),0);throw new AssertionError();}catch(IllegalArgumentException expected){}
System.out.println("PASS 6 tests");
}
}这些特殊情况也要试一试
- 单路重复与跨路重复含义不同,不能一律去掉跨路贡献。
- 空通道贡献零,不制造虚拟名次。
- 参数c为正有限数,固定值要进入实验版本。
进阶选读 · 后续再读:带预算的候选分配
场景关键词、向量和多个领域通道争用有限重排预算。
做法先估计“再给某一路多一个候选名额,可能补回多少有用资料”,这就是这里的边际收益。然后在验证集上比较三种分配办法:每路固定名额、按查询调整名额,以及去重后把空出来的名额补满。还要检查弱势通道是否保留了必要覆盖,避免热门通道占满名额后,少见但重要的查询反而找不到资料。
复杂度若建模为离散预算优化,可用O(mB)等特定动态规划;真实收益有重叠和交互,一般不能用独立收益模型保证全局质量最优。
好处与代价增加候选通常改善覆盖但增加重排成本;融合参数需要与候选数量共同调节,不能孤立比较。
05项目里怎么用
先记录各路原始名次和融合贡献,再记录重排分数。最终结果变差时,能判断相关文档是没召回、融合后被截掉,还是被重排降权。只保存最后五条会丢失这些诊断信息,使所有问题都被归咎于生成模型。
送进重排模型的资料,也必须在当前用户权限内,并满足输入长度限制。候选过长时,直接截断可能删掉决定结论的条件,因此优先使用意思完整的片段,并记录怎样截断。更换模型版本后,还要重新比较阈值和排名表现,不能只改模型名称,就假定原来的经验设置仍然合适。
融合方案必须与单路方案在同一固定集比较,包含代码符号、同义表达、无答案及多版本冲突查询。若关键词已经覆盖精确配置名,向量通道可能带来主题相似噪声;只有分组指标才能看清哪些任务真正受益。
容易踩的坑
- 直接相加BM25与余弦分数,忽略尺度。
- 重排未召回的文档,误以为能凭空补回候选。
- 单路重复ID被多次累计,产生虚假优势。
- 只测无权限过滤的理想数据。
几种做法,怎么选
表格有四列,窄屏可左右滑动查看代价和适用条件。
| 方案 | 优势 | 代价与限制 | 适用条件 |
|---|---|---|---|
| RRF | 无需校准原始分数尺度 | 丢失分数间距信息 | 多种检索器的稳健基线 |
| 归一分数融合 | 可利用分数差距 | 需要稳定校准和分布监控 | 已有代表性标注集 |
| 交叉编码重排 | 更细致比较查询与文本 | 每候选增加延迟与输入成本 | 候选有限且质量要求较高 |
06检验是否读懂了
先自己想、自己写,再展开答案核对。场景题是假设的练习情境;遇到还没学过的内容,可以做完基础后再回来。
先用自己的话解释
01RRF为何不用原始分数?
展开参考答案与推导
不同通道分数尺度不一致,名次提供更通用的融合依据;代价是无法利用原始分数差距。
02重排与召回有何边界?
展开参考答案与推导
重排只能调整已有候选次序,相关资料未进入候选集时必须改召回或数据管道。
03c增大意味着什么?
展开参考答案与推导
相邻名次贡献差异相对变小,多路共同出现的作用可能更突出;实际效果仍需固定集验证。
换个情况,还会处理吗
01相关文档在单路第一但最终消失,怎样查?
展开参考答案与推导
查看各路候选数量、融合贡献和重排截断位置,判断是配额、融合还是重排造成,不直接修改生成提示。
02异常类名检索变差怎么办?
展开参考答案与推导
单独评价精确符号查询,核对关键词分词与字段配置,再调整融合配额;语义通道可能引入近主题噪声。
03同一候选出现两次影响排名吗?
展开参考答案与推导
跨通道出现应累计,一条通道内部重复不应重复加分;用通道ID与文档ID联合检查。
写代码试一试
01手算跨路贡献
- 输入 / 条件
[A,B]和[B,C],c=1- 输出 / 预期
B=5/6,A=1/2,C=1/3
提示 名次从1起。
展开参考答案与推导
B在第一路第二和第二路第一,贡献1/3+1/2;这说明跨路支持可超过单路首位。
02如何输出前k而非全部?
- 输入 / 条件
融合后U项,k远小于U- 输出 / 预期
相同Top-k
提示 使用一致比较器的有界堆。
展开参考答案与推导
先完成贡献聚合,再保留分数最高k项;不能在单路处理中提前丢弃尚可能从其他通道加分的候选。
03去重前截断有什么风险?
- 输入 / 条件
列表前十全是同文档重复- 输出 / 预期
有效候选不足
提示 先定义文档粒度。
展开参考答案与推导
按正确身份去重后再补足授权候选,否则重复项耗尽配额,降低其他资料覆盖。
读完之后,独立完成
课后作业:自己做一遍
本篇共 10 道作业,先完成 8 道基础与综合任务,再按前置知识挑战进阶题。其中包含 1 道思维拓展,试着换个条件,看看原来的办法是否还管用。不附参考答案。
能比较单路、融合和重排四种方案,并解释正确资料在哪一阶段被找回或丢失。
本篇重点加练
多路分数不可直接视为同一尺度,多路权限也需保持一致,重点检验融合前后的数据合同。
- BM25 与向量分数尺度
- 多路权限过滤一致性
重点内容会多练几次。先说清楚怎么做,再试试空数据、出错或条件改变时会发生什么。
先自己做,卡住时记下问题,再回来查。问答题写清“为什么、什么时候适用”;动手题留下输入、输出和运行步骤,方便下次重做。
基础必做
- 作业 01原理问答
为类名精确匹配和自然语言含义查询分别设计样本,说明关键词与向量候选的来源及直接相加分数需要核验的条件。
- 提交内容
- 一份独立书面回答或标注图,包含自造例子、概念边界及判断依据。
- 怎样算完成
- 逐项回应题目要求,区分事实、假设与个人判断。
- 能用自己的例子解释适用条件,记录尚未弄懂的点。
练到的知识点关键词与向量检索互补 · BM25 与向量分数尺度
- 作业 02代码推演
输入两路排名A,B,C与C,A,D,自定RRF参数并推演融合、去重过程,说明同文档多片段时采用什么身份单位。
- 提交内容
- 手工推演记录,写明输入约定、中间状态、最终判断;可运行时附程序对照记录。
- 怎样算完成
- 推演包含中间过程,数据形状、单位、顺序或状态前后一致。
- 主动检查至少一个边界或异常条件,能解释手算与运行差异。
练到的知识点RRF 排名融合 · 候选去重
- 作业 03原理问答
为每一路候选定义包含分数口径、来源与授权依据的记录格式,说明融合器需要核对哪些字段才能安全比较或合并。
- 提交内容
- 概念或合同说明、关系图与自造案例,明确事实、假设和适用条件。
- 怎样算完成
- 关键概念有明确边界,能用案例检验声明。
- 未获证据支持的假设单独标注。
练到的知识点BM25 与向量分数尺度 · 多路权限过滤一致性
综合应用
- 作业 04面试场景
召回200条候选但重排预算仅允许30条。设计双编码器与交叉编码器各自参与的位置,并列出候选被截断的记录要求。
- 提交内容
- 场景分析与决策记录,包含信息缺口、约束、备选方案和验证计划。
- 怎样算完成
- 方案回应给定约束,列出至少一种替代方案及代价。
- 每项关键结论说明所需证据,不能核实的部分明确标注。
练到的知识点双编码器与交叉编码器 · 重排候选边界
- 作业 05编程实践
实现假两路检索与假重排流水线,其中一路混入越权记录且重排超时;测试权限、候选预算和降级响应,提交逐段耗时。
- 提交内容
- 可复现的最小程序或实验材料、自写测试、环境说明与运行记录;题目允许纸面实验时提交完整案例表。
- 怎样算完成
- 提交物按说明可以重现;测试覆盖正常、边界与失败场景。
- 运行证据与断言能核验声明的行为,注明模拟与真实执行范围。
练到的知识点多路权限过滤一致性 · 重排延迟和输入预算
- 作业 06排错实验
将两路分数分别做不同倍数缩放,对照融合前后排名,记录你选择的方法对尺度和重复候选的处理性质。
- 提交内容
- 最小故障输入、复现步骤、观测记录、定位依据及回归用例。
- 怎样算完成
- 其他人可按记录复现故障,并区分现象与已验证原因。
- 定位过程有观测依据,回归用例可以再次暴露原问题。
练到的知识点BM25 与向量分数尺度 · RRF 排名融合 · 候选去重
- 作业 07编程实践
编写离线融合输入检查器,一路候选缺少权限依据、另一路分数方向与声明不符;记录拒绝、保留与后续可评估的候选集合。
- 提交内容
- 可复现的小程序或题目允许的离线实验、自写测试、输入和运行记录。
- 怎样算完成
- 输入、环境和运行过程可重现,测试覆盖声明的重点条件。
- 验证目标由合同确定,模拟结果不冒充真实服务证据。
练到的知识点BM25 与向量分数尺度 · 多路权限过滤一致性
- 作业 08排错实验
两路使用不同时间的权限快照,且同一文档的分数口径发生升级。构造可回放的异常记录,检查融合结果是否还能追溯到一致的判断条件。
- 提交内容
- 最小故障序列、观测记录、原因核对与可再次触发原问题的测试。
- 怎样算完成
- 故障可以稳定重现,现象与已验证原因分开。
- 观测记录足以判断声明的合同是否被违反。
练到的知识点BM25 与向量分数尺度 · 多路权限过滤一致性
进阶挑战 · 掌握前置后选做
- 作业 09设计评审
固定语料与标注,比较单路、融合、融合加重排三条链路,联合报告检索质量、延迟和输入预算。
- 提交内容
- 设计评审文档或对照实验报告,包含前置条件、方案取舍、验收证据与尚未验证的范围。
- 怎样算完成
- 比较使用一致条件与事先声明的标准,记录失败与未知结果。
- 说明方案的适用前提、未覆盖风险及后续验证方法。
练到的知识点关键词与向量检索互补 · 双编码器与交叉编码器 · 重排延迟和输入预算
思维拓展
先写出你的猜想,再用例子或实验验证。答案可以不同,但要说清依据、适用情况,以及还有什么没弄懂。
- 作业 10思维拓展 · 跨域迁移
将多路候选结果的处理规则迁移到多商家商品搜索的纸面设计,比较用户可见范围、排序尺度与去重身份有哪些新的业务约束。
- 提交内容
- 原场景与目标场景的对应表、可迁移的机制、不能迁移的前提及验证设计。
- 怎样算完成
- 逐项核对两种场景的前提,不只比较相似术语。
- 至少一个不能直接迁移的边界有具体检验方法。
练到的知识点BM25 与向量分数尺度 · 多路权限过滤一致性
完成后,再检验一次
给每题标记“独立完成 / 查阅后完成 / 尚未完成”。查过资料或没做出的题,记下卡在哪里。隔一天换一组输入再试,能解释结果和出错原因,再把这个知识点标为掌握。
继续阅读
先看能帮你完成当前练习的部分。例子跑通后,再读更深入的解释。
- 必读参考文档英文
Elasticsearch:Reciprocal rank fusion (新标签页)↗
阅读基于排名的融合公式和参数,用来合并分数尺度不同的检索结果;引擎配置按所用版本核对。
- 实践实践指南英文
Sentence Transformers:Cross-Encoders (新标签页)↗
重点阅读双编码器与交叉编码器的区别,以及先召回再重排流程,理解额外延迟和候选边界。
- 深入参考文档英文
pgvector:向量检索与索引说明 (新标签页)↗
先读距离运算符和精确搜索,再读 HNSW、过滤与索引参数,使用同一数据比较速度和召回。
- 深入参考文档英文
OpenJDK 17 TreeMap 源码 (新标签页)↗
按指定版本定位
TreeMap.merge/ comparator;对照源码分析理解实际调用边界。