qinghuan
← 查找资料并核对引用 / 全部笔记

单元 15 / 查找资料并核对引用

P1 · 重点提升L2 · 进阶

组合关键词、向量检索与重排

结合关键词与语义检索,既找准类名和配置键,也找得到换了说法的资料,再重新排序。

本篇目录 · 按需要跳转

读完这篇,你能做什么

能比较单路、融合和重排四种方案,并解释正确资料在哪一阶段被找回或丢失。

学习理由、准备要求与知识点

为什么学它

只按关键词可能漏掉换了说法的内容,只按语义又可能错过精确类名。组合检索和重排后,需要实测是否找得更好。

先读这些内容

知识点清单

  • 关键词与向量检索互补
  • 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["分阶段评估"]

按顺序看,每一步做了什么

  1. 固定查询集并建立关键词、向量两条单路基线。
  2. 在相同权限和文档版本下分别召回候选。
  3. 拒绝单路重复ID,以RRF聚合跨路名次。
  4. 截取有限候选进入重排,记录输入预算与耗时。
  5. 分别评价召回覆盖、最终排序和答案支持。

03对照实现代码

OpenJDK 17 TreeMap 源码 (新标签页)↗

OpenJDK 17 GA,jdk-17+35 · TreeMap.merge / comparator

下方为原创 Java 教学片段,调用已核验的 JDK API 说明局部机制;不是该类原样源码,也不实现神经网络。完整程序见算法模块。

核心机制代码 · java
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或交叉编码器,这些通道需分别配置与评价。

代码先调用谁,再调用谁

  1. 读取每路已排序候选
  2. 检查单路ID唯一
  3. 按一基名次计算贡献
  4. TreeMap.merge聚合
  5. 按融合分数和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在单一通道最多计一次。

时间 O(R log U+U log U)额外空间 O(U+M)

R为所有列表总长度,U为不同ID数,M为最长单路长度。TreeMap更新O(log U),排序U项;字符串比较的长度成本需另外考虑。

保存为 Main.java,运行 javac --release 17 -encoding UTF-8 Main.java,再运行 java Main。程序包含固定输入自测,断言失败时会抛出错误。

Main.java · 完整可运行程序 · Java
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 与向量分数尺度
  • 多路权限过滤一致性

重点内容会多练几次。先说清楚怎么做,再试试空数据、出错或条件改变时会发生什么。

先自己做,卡住时记下问题,再回来查。问答题写清“为什么、什么时候适用”;动手题留下输入、输出和运行步骤,方便下次重做。

基础必做

  1. 作业 01原理问答

    为类名精确匹配和自然语言含义查询分别设计样本,说明关键词与向量候选的来源及直接相加分数需要核验的条件。

    提交内容
    一份独立书面回答或标注图,包含自造例子、概念边界及判断依据。
    怎样算完成
    • 逐项回应题目要求,区分事实、假设与个人判断。
    • 能用自己的例子解释适用条件,记录尚未弄懂的点。

    练到的知识点关键词与向量检索互补 · BM25 与向量分数尺度

  2. 作业 02代码推演

    输入两路排名A,B,C与C,A,D,自定RRF参数并推演融合、去重过程,说明同文档多片段时采用什么身份单位。

    提交内容
    手工推演记录,写明输入约定、中间状态、最终判断;可运行时附程序对照记录。
    怎样算完成
    • 推演包含中间过程,数据形状、单位、顺序或状态前后一致。
    • 主动检查至少一个边界或异常条件,能解释手算与运行差异。

    练到的知识点RRF 排名融合 · 候选去重

  3. 作业 03原理问答

    为每一路候选定义包含分数口径、来源与授权依据的记录格式,说明融合器需要核对哪些字段才能安全比较或合并。

    提交内容
    概念或合同说明、关系图与自造案例,明确事实、假设和适用条件。
    怎样算完成
    • 关键概念有明确边界,能用案例检验声明。
    • 未获证据支持的假设单独标注。

    练到的知识点BM25 与向量分数尺度 · 多路权限过滤一致性

综合应用

  1. 作业 04面试场景

    召回200条候选但重排预算仅允许30条。设计双编码器与交叉编码器各自参与的位置,并列出候选被截断的记录要求。

    提交内容
    场景分析与决策记录,包含信息缺口、约束、备选方案和验证计划。
    怎样算完成
    • 方案回应给定约束,列出至少一种替代方案及代价。
    • 每项关键结论说明所需证据,不能核实的部分明确标注。

    练到的知识点双编码器与交叉编码器 · 重排候选边界

  2. 作业 05编程实践

    实现假两路检索与假重排流水线,其中一路混入越权记录且重排超时;测试权限、候选预算和降级响应,提交逐段耗时。

    提交内容
    可复现的最小程序或实验材料、自写测试、环境说明与运行记录;题目允许纸面实验时提交完整案例表。
    怎样算完成
    • 提交物按说明可以重现;测试覆盖正常、边界与失败场景。
    • 运行证据与断言能核验声明的行为,注明模拟与真实执行范围。

    练到的知识点多路权限过滤一致性 · 重排延迟和输入预算

  3. 作业 06排错实验

    将两路分数分别做不同倍数缩放,对照融合前后排名,记录你选择的方法对尺度和重复候选的处理性质。

    提交内容
    最小故障输入、复现步骤、观测记录、定位依据及回归用例。
    怎样算完成
    • 其他人可按记录复现故障,并区分现象与已验证原因。
    • 定位过程有观测依据,回归用例可以再次暴露原问题。

    练到的知识点BM25 与向量分数尺度 · RRF 排名融合 · 候选去重

  4. 作业 07编程实践

    编写离线融合输入检查器,一路候选缺少权限依据、另一路分数方向与声明不符;记录拒绝、保留与后续可评估的候选集合。

    提交内容
    可复现的小程序或题目允许的离线实验、自写测试、输入和运行记录。
    怎样算完成
    • 输入、环境和运行过程可重现,测试覆盖声明的重点条件。
    • 验证目标由合同确定,模拟结果不冒充真实服务证据。

    练到的知识点BM25 与向量分数尺度 · 多路权限过滤一致性

  5. 作业 08排错实验

    两路使用不同时间的权限快照,且同一文档的分数口径发生升级。构造可回放的异常记录,检查融合结果是否还能追溯到一致的判断条件。

    提交内容
    最小故障序列、观测记录、原因核对与可再次触发原问题的测试。
    怎样算完成
    • 故障可以稳定重现,现象与已验证原因分开。
    • 观测记录足以判断声明的合同是否被违反。

    练到的知识点BM25 与向量分数尺度 · 多路权限过滤一致性

进阶挑战 · 掌握前置后选做

  1. 作业 09设计评审

    固定语料与标注,比较单路、融合、融合加重排三条链路,联合报告检索质量、延迟和输入预算。

    提交内容
    设计评审文档或对照实验报告,包含前置条件、方案取舍、验收证据与尚未验证的范围。
    怎样算完成
    • 比较使用一致条件与事先声明的标准,记录失败与未知结果。
    • 说明方案的适用前提、未覆盖风险及后续验证方法。

    练到的知识点关键词与向量检索互补 · 双编码器与交叉编码器 · 重排延迟和输入预算

思维拓展

先写出你的猜想,再用例子或实验验证。答案可以不同,但要说清依据、适用情况,以及还有什么没弄懂。

  1. 作业 10思维拓展 · 跨域迁移

    将多路候选结果的处理规则迁移到多商家商品搜索的纸面设计,比较用户可见范围、排序尺度与去重身份有哪些新的业务约束。

    提交内容
    原场景与目标场景的对应表、可迁移的机制、不能迁移的前提及验证设计。
    怎样算完成
    • 逐项核对两种场景的前提,不只比较相似术语。
    • 至少一个不能直接迁移的边界有具体检验方法。

    练到的知识点BM25 与向量分数尺度 · 多路权限过滤一致性

完成后,再检验一次

给每题标记“独立完成 / 查阅后完成 / 尚未完成”。查过资料或没做出的题,记下卡在哪里。隔一天换一组输入再试,能解释结果和出错原因,再把这个知识点标为掌握。

继续阅读

先看能帮你完成当前练习的部分。例子跑通后,再读更深入的解释。

打开资料库,找下一份学习材料 ↗