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

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

P0 · 必会主线L2 · 进阶

单独测量检索召回与排序质量

用固定测试题检查相关资料有没有找到、排得够不够靠前,以及资料外的问题怎样处理。

本篇目录 · 按需要跳转

这篇重点学什么

本篇重点:先检查该找的资料是否找到、排在什么位置,再评估回答质量;用 Recall / MRR / NDCG 按样本类别分别检查,定位遗漏资料的原因。

开始读正文 ↓
学过相近内容?查看哪些可以跳过

两条路线有些基础相同。已经学会的部分可以跳过,另一条路线只用来补你当前需要的内容。

检索与记忆

共同基础检索、证据和上下文选择相通;使用检索器与训练检索器有不同验收目标。

另一条路线补充什么?

分别为查询和文档生成向量(双编码器),用负样本和对比损失学习区分相关资料,再检查内容选择策略及对新数据的效果。

读完这篇,你能做什么

能计算并解释 Recall@3 和 MRR@3,单独报告资料外行为并定位具体检索失败。

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

为什么学它

先单独检查相关资料有没有找到、排在哪里,才能分清问题出在检索,还是模型读到资料后解释错了。

先读这些内容

知识点清单

  • 查询、语料与相关标注
  • 文档和片段标注单位
  • Hit@k
  • Recall@k
  • MRR@k
  • 分级相关性与 NDCG
  • 无答案问题单独评估
  • 检索失败归因

运行前先确认完整算法使用 Java 17 标准库;JDK 源码基线为 OpenJDK 17 GA。示例是内存中的机制模型,外部服务接入需要另外配置和验证。

01先弄懂它是什么

想知道检索有没有改进,需要一组能反复使用的测试题。每条用例应保存查询、固定语料版本和哪些文档相关,必要时再标出相关程度。先决定按整篇文档还是按片段评分;否则切分方式一变,同一篇文档的几个片段就可能被重复当成多个独立正确答案,分数也失去可比性。

可以按实际问题选择指标:Hit@k 看前 k 项里有没有至少一项相关;Recall@k 看已标注的相关项找回多少;MRR@k 看第一个相关结果排多靠前;NDCG 则结合相关程度和位置,评价整体排序。它们分别回答不同问题,所以报告里应说明具体用了哪个指标,避免统称为一个含糊的“检索准确率”。

“没有标出相关资料”不等于“资料里确实找不到答案”:可能是标注没完成,也可能是资料确实不足。如果都把 Recall 记为 0,就会把不同问题混在一起。本篇在没有正相关标注时把指标记为“未定义”,不硬填 0。实际评估还要另外检查:没有答案时能否拒答,以及是否检索出了不相关资料(误召回)。

文中这几个词,先认一认

Qrels
记录某个查询与哪些资料相关、相关到什么程度的标注,用作评测参考,而不是模型自己生成的答案。
位置折扣
同样相关的结果,排得越后,排序得分贡献越小。本篇使用 log2(rank+1) 计算折扣。
宏平均
先为每个可评查询单独算指标,再取平均,让每个查询占相同权重。

还有陌生词?到术语小抄里看一个具体例子 ↗

02看看它怎么工作

令正相关集合有A与B,排名X、A,k为二:Hit为一,Recall为二分之一,首相关在第二名所以MRR为二分之一。若只看Hit会忽略B完全没找到;若任务只需一个正确来源,MRR又更接近用户尽快看到证据的体验。

NDCG 可以分两步看:先按“越相关、越靠前,得分越高”的规则算 DCG。若某项相关等级是 rel、名次是 rank,本题给它的分数为 (2的rel次方减一)/log2(rank+1),再把各项相加。

随后算一个理想成绩:从全部相关标注中挑出最好的前 k 项,按最佳顺序排列,再用同样规则评分。实际 DCG 除以这个理想 DCG,才得到归一化分数。不能只把已经搜到的项目重新排好来当理想成绩,否则没找回的重要资料不会被计入差距,结果可能虚高到满分。

比较实验前,先固定 k 和标注版本,并约定遇到重复 ID、未定义指标时怎么处理。重复 ID 不能重复增加召回率;本题会直接拒绝含重复 ID 的排名。检查输入后再计算,报告中同时给出参与平均的有效查询数,以及指标未定义的查询数。只写平均分而不写这些数量,可能把用不同样本算出的结果误当成可直接比较。

正在加载流程图…

图中状态只描述本篇可执行机制;业务调用仍需由外层应用负责。
查看流程图源码(Mermaid)
flowchart LR
  Q["固定查询与标注"] --> R["检索排名和失败"]
  R --> V["唯一性与等级检查"]
  V --> M["覆盖和排序指标"]
  M --> G["有效数及分组统计"]
  G --> F["失败归因与下一实验"]

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

  1. 冻结查询、语料与相关标注版本,并定义片段或文档单位。
  2. 执行检索并保留前k排名及失败原因。
  3. 验证ID唯一与相关等级,分别计算覆盖和位置指标。
  4. 无正相关标注单独归类,报告有效样本数量。
  5. 按查询类型查看失败,再比较不同检索配置。

03对照实现代码

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

OpenJDK 17 GA,jdk-17+35 · DoubleSummaryStatistics.accept / getAverage / getCount

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

核心机制代码 · java
DoubleSummaryStatistics stats = new DoubleSummaryStatistics();
for (OptionalDouble metric : perQueryMetrics) {
    if (metric.isPresent()) stats.accept(metric.getAsDouble()); // 未定义值不伪装成零。
}
OptionalDouble mean = stats.getCount() == 0 ? OptionalDouble.empty()
        : OptionalDouble.of(stats.getAverage()); // 同时报告有效样本数。

DoubleSummaryStatistics收集计数、和及极值,getAverage在没有样本时返回零。这个库默认值不等于业务上的“零分”,调用方需要先看getCount区分未测。

accept接收数值并不验证它是否适合当前评价范围;NaN或无穷可能污染统计。应用应在计算阶段保证有限有效结果,再送入汇总器。

统计器只完成数值聚合,不知道查询是否独立、标注是否完整或数据是否泄漏。评价设计的正确性不能由一个平均函数保证。

本篇指标实现是原创Java算术模型,正式信息检索库可能采用不同零分母与重复政策;对照资料时必须先对齐这些定义,不能只比较方法名称。

代码先调用谁,再调用谁

  1. 逐查询读取排名与标注
  2. 计算已定义指标
  3. accept汇总有效数值
  4. getCount核对分母
  5. 输出平均与未定义数量

04动手写一个例子

Recall、MRR与NDCG联合计算

输入唯一ID排名、0至3级相关标注和正k,返回Hit/Recall/MRR/NDCG;没有正相关标注则返回空。Java17程序固定增益公式并运行自测。

输入 1
gold A=3,B=1;rank X,A;k=2
输出 1
Hit=1,Recall=0.5,MRR=0.5
输入 2
无正相关标注
输出 2
未定义

先计数等级大于零的相关项,构造理想等级降序列表。未标注候选本题按不相关处理,但评价报告必须说明这一假设以及标注覆盖范围。

扫描前k排名,累计正相关命中数,第一次命中时记录倒数名次,再按固定公式累加DCG。排名唯一性在全输入检查,避免截断掩盖重复数据。

用理想等级的前k项计算IDCG。存在正相关且k为正时分母大于零,再计算归一结果;不把无答案或未标注问题强行挤进这个比率。

为什么这样写是对的扫描前i名后,hitCount等于其中唯一正相关项数,MRR保留首相关倒数位置,DCG等于该前缀按固定增益与折扣的总和。

时间 O(n+r log r)额外空间 O(n+r)

n为排名长度,r为标注数。重复检查O(n),理想等级排序O(r log r),指标扫描O(min(n,k));保存已见ID和理想等级列表。

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

Main.java · 完整可运行程序 · Java
import java.util.*;
public class Main {
    record Metrics(double hit,double recall,double mrr,double ndcg){}
    static double gain(int grade,int rank){return (Math.pow(2,grade)-1)/(Math.log(rank+1)/Math.log(2));}
    static Optional<Metrics> score(List<String> ranked,Map<String,Integer> grades,int k){
        if(k<1)throw new IllegalArgumentException("k");
        Set<String> seen=new HashSet<>();for(String id:ranked)if(id==null||!seen.add(id))throw new IllegalArgumentException("duplicate rank");
        List<Integer> ideal=new ArrayList<>();
        for(int g:grades.values()){if(g<0||g>3)throw new IllegalArgumentException("grade");if(g>0)ideal.add(g);}
        if(ideal.isEmpty())return Optional.empty(); // 未定义不能写成零分或满分。
        ideal.sort(Comparator.reverseOrder());double dcg=0,idcg=0,mrr=0;int hits=0;
        for(int i=0;i<Math.min(k,ranked.size());i++){
            int g=grades.getOrDefault(ranked.get(i),0);
            if(g>0){hits++;if(mrr==0)mrr=1.0/(i+1);} // 只保留首个相关位置。
            dcg+=gain(g,i+1);
        }
        for(int i=0;i<Math.min(k,ideal.size());i++)idcg+=gain(ideal.get(i),i+1); // 理想序列来自全部标注。
        return Optional.of(new Metrics(hits>0?1:0,(double)hits/ideal.size(),mrr,dcg/idcg));
    }
    static void near(double e,double a){if(!Double.isFinite(a)||Math.abs(e-a)>1e-12)throw new AssertionError(a);}
    public static void main(String[] args){
        Map<String,Integer> gold=Map.of("A",3,"B",1);
        Metrics x=score(List.of("X","A"),gold,2).orElseThrow();near(1,x.hit());near(.5,x.recall());near(.5,x.mrr());
        near(1,score(List.of("A","B"),gold,2).orElseThrow().ndcg());
        near((1+7/(Math.log(3)/Math.log(2)))/(7+1/(Math.log(3)/Math.log(2))),score(List.of("B","A"),gold,2).orElseThrow().ndcg());
        near(0,score(List.of(),gold,2).orElseThrow().recall());
        if(score(List.of("A"),Map.of(),2).isPresent())throw new AssertionError();
        try{score(List.of("A","A"),gold,2);throw new AssertionError();}catch(IllegalArgumentException expected){}
        System.out.println("PASS 6 cases");
    }
}

这些特殊情况也要试一试

  • 有相关标注但空排名是已定义零召回。
  • 无正相关标注返回未定义,需要另测无答案行为。
  • IDCG使用全部标注,不只使用检索命中的子集。
进阶选读 · 后续再读:不完整标注与置信区间

场景人工标注昂贵,多种检索器发现的候选集合不同。

做法汇集多路候选进行盲标,记录未判定项;按查询族分组重采样估计指标区间,并报告标注覆盖和分歧。

复杂度一次指标计算如本题;B轮自助采样约需O(Bq)聚合,q为查询数。独立性不成立时应按组采样。

好处与代价更多标注提高比较可信度,但成本上升;只标单个系统的结果会对该系统产生评价偏差。

05项目里怎么用

建立覆盖精确术语、同义表达、跨段落、多版本和无答案的查询集,样本来自明确授权资料并做必要脱敏。开发集用于调参,测试集留作最终比较;反复看测试失败再调参数会使测试集逐渐变成开发集。

除了总体结果,还要按任务类型和权限范围分别统计。例如平均 Recall 上升了,但查询某些关键异常类名时,找回的相关资料反而更少。风险高的那组查询应单独设合格门槛。超时和检索失败也要记录;只给成功返回的请求算质量,不能据此宣称整个系统变好了。

错误归因先看资料是否存在,再看切分、编码、过滤、近似索引和融合。评测结果指导下一步实验,而不是直接指向某一个组件。每次只改变可解释的配置组合,并保留具体失败排名与来源版本。

容易踩的坑

  • 把Hit、Recall、MRR都叫同一个准确率。
  • 只用命中项计算理想NDCG分母。
  • 没有标注就填零,与真实无答案混淆。
  • 删除超时样本后报告更好的平均质量。

几种做法,怎么选

表格有四列,窄屏可左右滑动查看代价和适用条件。

方案优势代价与限制适用条件
Hit与MRR贴近尽快找到一项证据忽略多项证据覆盖单答案导航型查询
Recall关注必要资料覆盖不充分反映前后次序综合问答候选召回
NDCG结合等级与位置需要分级标注及公式一致多相关程度的排序评价

06检验是否读懂了

先自己想、自己写,再展开答案核对。场景题是假设的练习情境;遇到还没学过的内容,可以做完基础后再回来。

先用自己的话解释

01Hit为一能推出Recall为一吗?

展开参考答案与推导

不能。至少找到一项只满足Hit,可能还有多项相关资料未找到。

02无正相关标注为什么不直接算零?

展开参考答案与推导

分母不存在且原因可能是未标注或真正无答案;应独立分类,避免把评价未知伪装成失败。

03NDCG理想序列如何建立?

展开参考答案与推导

按全部有效相关标注的等级降序,取同样k范围计算IDCG,不只重排已命中的候选。

换个情况,还会处理吗

01总体分数上升但配置键查询退化怎么办?

展开参考答案与推导

按查询类型查看指标与失败列表,设置关键子集门槛,再调整关键词通道或融合;平均值不能替代业务约束。

02新系统召回未标注文档被算错,怎么处理?

展开参考答案与推导

补充盲标并记录标注版本,比较双方在同一更新标注下的结果;不要只为新系统挑有利项目。

03测试集越调越好意味着泛化改善吗?

展开参考答案与推导

未必,反复用测试反馈调参产生泄漏;保留新的独立评估集或按时间/案例族切分。

写代码试一试

01两个相关只找到第二名一个,指标如何?

输入 / 条件
gold A/B;rank X,A;k2
输出 / 预期
Hit1,Recall0.5,MRR0.5

提示 覆盖和首位置分开。

展开参考答案与推导

命中数一除相关总数二得Recall;首相关rank2的倒数是0.5。

02空排名与空标注有什么不同?

输入 / 条件
有gold但rank空;gold空
输出 / 预期
前者Recall0,后者未定义

提示 区分结果缺失与参考缺失。

展开参考答案与推导

有参考时检索没找到可评价为零;没有正参考时比率无定义,不能用相同默认值。

03如何测试重复ID不加分?

输入 / 条件
rank A,A,gold A/B
输出 / 预期
拒绝

提示 在指标计算前检查唯一性。

展开参考答案与推导

先扫描全排名集合,重复时报输入错误;若产品采用去重政策,也必须固定去重后名次定义。

读完之后,独立完成

课后作业:自己做一遍

本篇共 12 道作业,先完成 9 道基础与综合任务,再按前置知识挑战进阶题。其中包含 2 道思维拓展,试着换个条件,看看原来的办法是否还管用。不附参考答案。

这次练什么

能计算并解释 Recall@3 和 MRR@3,单独报告资料外行为并定位具体检索失败。

本篇重点加练

标注单位与失败归因决定检索指标是否可解释,重点区分评测数据问题与算法问题。

  • 文档和片段标注单位
  • 检索失败归因

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

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

基础必做

  1. 作业 01原理问答

    为6条查询建立小标注集,明确语料版本与相关单位;设计同一文档多个片段被召回时的计分契约。

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

    练到的知识点查询、语料与相关标注 · 文档和片段标注单位

  2. 作业 02代码推演

    设某查询相关集合为{a,c,e},返回[b,a,a,d,c],自行定义去重与截断协议,计算不同k下的Hit和Recall并保留推导。

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

    练到的知识点Hit@k · Recall@k

  3. 作业 03原理问答

    为一次漏召回设计逐层归因表,明确查询对应的文档、片段与语料版本,列出判断“真的漏了”之前必须确认的事实。

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

    练到的知识点文档和片段标注单位 · 检索失败归因

综合应用

  1. 作业 04面试场景

    两个系统首个相关结果位置相同,但后续相关等级不同。构造完整排名与分级标签,比较MRR和NDCG各能检验什么。

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

    练到的知识点MRR@k · 分级相关性与 NDCG

  2. 作业 05编程实践

    实现检索评测脚本,混合可回答与无答案查询,按失败类型分组报告;记录漏标、过滤与候选截断造成的不可判定情况。

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

    练到的知识点无答案问题单独评估 · 检索失败归因

  3. 作业 06排错实验

    故意让标注按文档ID、输出按片段ID,运行评分并定位异常来源;提交统一前后的输入契约和自写回归测试。

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

    练到的知识点查询、语料与相关标注 · 文档和片段标注单位 · Recall@k

  4. 作业 07编程实践

    编写离线评测审计器,输入混合文档级标签、片段级标签与已删除来源;输出可以评分、需要复核和无法比较的记录,不强行补标签。

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

    练到的知识点文档和片段标注单位 · 检索失败归因

  5. 作业 08排错实验

    一个相关文档被拆成十个片段后,指标突然上升。固定查询与原文内容,对照切分前后的计分单位,记录可能改变的分子分母。

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

    练到的知识点文档和片段标注单位 · 检索失败归因

  6. 作业 09面试场景

    评审人员对同一片段给出不同相关等级。设计保留分歧、复核样本与归因报告的协议,并说明哪些算法改进结论需要暂缓。

    提交内容
    场景评审记录,包含约束、备选方案、未决信息和验证计划。
    怎样算完成
    • 方案回应题目约束,取舍能对应到可核验的事实。
    • 给出失败或条件变化时的检查方式。

    练到的知识点文档和片段标注单位 · 检索失败归因

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

  1. 作业 10设计评审

    在不同查询类型上比较两个检索器,写出分层报告与最差案例,说明总体均值可能掩盖哪些业务问题。

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

    练到的知识点Hit@k · MRR@k · 分级相关性与 NDCG

思维拓展

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

  1. 作业 11思维拓展 · 反例构造

    构造“同一查询的Recall提高,就能证明检索器变好”的反例,要求反例涉及标注单位或语料边界,而非单纯算错公式。

    提交内容
    具体反例、原主张的适用前提、复现或逻辑证明及结论边界。
    怎样算完成
    • 反例满足原主张所声称的前提,确实针对其结论。
    • 说明一个反例能推翻什么、不能进一步证明什么。

    练到的知识点文档和片段标注单位 · 检索失败归因

  2. 作业 12思维拓展 · 开放探究

    研究评测单元选择对结论的敏感性:预先定义文档级与片段级两套协议,在同一小语料上比较系统排名,并报告无法统一的语义。

    提交内容
    可证伪假设、预先声明的比较协议、小规模证据与限制说明。
    怎样算完成
    • 假设存在可被否定的条件,协议在观察结果前声明。
    • 包含不支持假设的可能结果及不确定性说明。

    练到的知识点文档和片段标注单位 · 检索失败归因

完成后,再检验一次

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

继续阅读

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

  • 必读参考文档英文

    Sentence Transformers:Evaluation (新标签页)↗

    查阅 InformationRetrievalEvaluator 的 queries、corpus、相关标注与指标,建立可重跑的检索评测。

  • 实践参考文档英文

    scikit-learn:模型评估与评分 (新标签页)↗

    按任务查阅 precision、recall、排序指标和聚合方法,先明确分母及样本范围再报告分数。

  • 深入实践指南英文

    Spring AI:Retrieval Augmented Generation (新标签页)↗

    阅读检索、查询增强、过滤和空上下文处理,按阶段设计带证据与明确失败出口的 RAG 流程。

  • 实践实践指南中文

    Milvus 中文:先按词找原文,再检查检索结果 (新标签页)↗

    先读“BM25 实现”“插入文本数据”“执行全文搜索”。纸上圈出示例查询与三条文本重合的词,写出期望返回的原文;已有相容测试环境时再记录实际排名。换成中文前先核对分析器。首轮跳过其他语言 SDK、索引调优与混合检索;相关性分数不是真实性概率。 中文正文公开可读;代码连接数据库服务,不能当成只靠 Python 标准库的例子。未启动服务或验证 SDK 兼容性;不同文档版本需单独核对。

  • 实践实践指南英文

    Sentence Transformers:相关资料有没有进前几条 (新标签页)↗

    只看 InformationRetrievalEvaluator 的 queries、corpus、relevant_docs。准备两个问题、五条虚构材料,每题至少标一个相关 ID;先固定标注,再列前三条结果,交出找到和漏掉的相关 ID 及 Recall@3。首轮跳过整库下载、其他评估器和调参;资料外问题另测,标注不完整也会限制结论。 API 正文公开可读。完整示例会加载模型和数据集。该评估器只纳入相关 ID 集合非空的问题,不能替代资料外问题的拒答测试;示例分数不代表自有数据效果。

  • 实践实践指南英文

    Sentence Transformers:先找候选,再重新排队 (新标签页)↗

    先看流程图及 Retrieval、Re-Ranker 两节。画五张候选卡,先移走其中唯一能支持答案的一张,再尝试只重排剩余卡;交出候选缺失和排序靠后两类失败记录。首轮跳过整套 Wikipedia 向量化与模型下载,不承诺换上重排器就能提高每个任务的效果。 文档与图公开可读。代码需要相应库、模型或语料,完整 Wikipedia 示例并非轻量本地练习。重排分数不应直接当成答案正确概率。

  • 深入参考文档英文

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

    按指定版本定位 DoubleSummaryStatistics.accept / getAverage / getCount;对照源码分析理解实际调用边界。

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