这篇重点学什么
本篇重点:先检查该找的资料是否找到、排在什么位置,再评估回答质量;用 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["失败归因与下一实验"]按顺序看,每一步做了什么
- 冻结查询、语料与相关标注版本,并定义片段或文档单位。
- 执行检索并保留前k排名及失败原因。
- 验证ID唯一与相关等级,分别计算覆盖和位置指标。
- 无正相关标注单独归类,报告有效样本数量。
- 按查询类型查看失败,再比较不同检索配置。
03对照实现代码
OpenJDK 17 GA,jdk-17+35 · DoubleSummaryStatistics.accept / getAverage / getCount
下方为原创 Java 教学片段,调用已核验的 JDK API 说明局部机制;不是该类原样源码,也不实现神经网络。完整程序见算法模块。
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算术模型,正式信息检索库可能采用不同零分母与重复政策;对照资料时必须先对齐这些定义,不能只比较方法名称。
代码先调用谁,再调用谁
- 逐查询读取排名与标注
- 计算已定义指标
- accept汇总有效数值
getCount核对分母- 输出平均与未定义数量
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等于该前缀按固定增益与折扣的总和。
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。程序包含固定输入自测,断言失败时会抛出错误。
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,单独报告资料外行为并定位具体检索失败。
本篇重点加练
标注单位与失败归因决定检索指标是否可解释,重点区分评测数据问题与算法问题。
- 文档和片段标注单位
- 检索失败归因
重点内容会多练几次。先说清楚怎么做,再试试空数据、出错或条件改变时会发生什么。
先自己做,卡住时记下问题,再回来查。问答题写清“为什么、什么时候适用”;动手题留下输入、输出和运行步骤,方便下次重做。
基础必做
- 作业 01原理问答
为6条查询建立小标注集,明确语料版本与相关单位;设计同一文档多个片段被召回时的计分契约。
- 提交内容
- 一份独立书面回答或标注图,包含自造例子、概念边界及判断依据。
- 怎样算完成
- 逐项回应题目要求,区分事实、假设与个人判断。
- 能用自己的例子解释适用条件,记录尚未弄懂的点。
练到的知识点查询、语料与相关标注 · 文档和片段标注单位
- 作业 02代码推演
设某查询相关集合为{a,c,e},返回[b,a,a,d,c],自行定义去重与截断协议,计算不同k下的Hit和Recall并保留推导。
- 提交内容
- 手工推演记录,写明输入约定、中间状态、最终判断;可运行时附程序对照记录。
- 怎样算完成
- 推演包含中间过程,数据形状、单位、顺序或状态前后一致。
- 主动检查至少一个边界或异常条件,能解释手算与运行差异。
练到的知识点Hit@k · Recall@k
- 作业 03原理问答
为一次漏召回设计逐层归因表,明确查询对应的文档、片段与语料版本,列出判断“真的漏了”之前必须确认的事实。
- 提交内容
- 概念或合同说明、关系图与自造案例,明确事实、假设和适用条件。
- 怎样算完成
- 关键概念有明确边界,能用案例检验声明。
- 未获证据支持的假设单独标注。
练到的知识点文档和片段标注单位 · 检索失败归因
综合应用
- 作业 04面试场景
两个系统首个相关结果位置相同,但后续相关等级不同。构造完整排名与分级标签,比较MRR和NDCG各能检验什么。
- 提交内容
- 场景分析与决策记录,包含信息缺口、约束、备选方案和验证计划。
- 怎样算完成
- 方案回应给定约束,列出至少一种替代方案及代价。
- 每项关键结论说明所需证据,不能核实的部分明确标注。
练到的知识点MRR@k · 分级相关性与 NDCG
- 作业 05编程实践
实现检索评测脚本,混合可回答与无答案查询,按失败类型分组报告;记录漏标、过滤与候选截断造成的不可判定情况。
- 提交内容
- 可复现的最小程序或实验材料、自写测试、环境说明与运行记录;题目允许纸面实验时提交完整案例表。
- 怎样算完成
- 提交物按说明可以重现;测试覆盖正常、边界与失败场景。
- 运行证据与断言能核验声明的行为,注明模拟与真实执行范围。
练到的知识点无答案问题单独评估 · 检索失败归因
- 作业 06排错实验
故意让标注按文档ID、输出按片段ID,运行评分并定位异常来源;提交统一前后的输入契约和自写回归测试。
- 提交内容
- 最小故障输入、复现步骤、观测记录、定位依据及回归用例。
- 怎样算完成
- 其他人可按记录复现故障,并区分现象与已验证原因。
- 定位过程有观测依据,回归用例可以再次暴露原问题。
练到的知识点查询、语料与相关标注 · 文档和片段标注单位 · Recall@k
- 作业 07编程实践
编写离线评测审计器,输入混合文档级标签、片段级标签与已删除来源;输出可以评分、需要复核和无法比较的记录,不强行补标签。
- 提交内容
- 可复现的小程序或题目允许的离线实验、自写测试、输入和运行记录。
- 怎样算完成
- 输入、环境和运行过程可重现,测试覆盖声明的重点条件。
- 验证目标由合同确定,模拟结果不冒充真实服务证据。
练到的知识点文档和片段标注单位 · 检索失败归因
- 作业 08排错实验
一个相关文档被拆成十个片段后,指标突然上升。固定查询与原文内容,对照切分前后的计分单位,记录可能改变的分子分母。
- 提交内容
- 最小故障序列、观测记录、原因核对与可再次触发原问题的测试。
- 怎样算完成
- 故障可以稳定重现,现象与已验证原因分开。
- 观测记录足以判断声明的合同是否被违反。
练到的知识点文档和片段标注单位 · 检索失败归因
- 作业 09面试场景
评审人员对同一片段给出不同相关等级。设计保留分歧、复核样本与归因报告的协议,并说明哪些算法改进结论需要暂缓。
- 提交内容
- 场景评审记录,包含约束、备选方案、未决信息和验证计划。
- 怎样算完成
- 方案回应题目约束,取舍能对应到可核验的事实。
- 给出失败或条件变化时的检查方式。
练到的知识点文档和片段标注单位 · 检索失败归因
进阶挑战 · 掌握前置后选做
- 作业 10设计评审
在不同查询类型上比较两个检索器,写出分层报告与最差案例,说明总体均值可能掩盖哪些业务问题。
- 提交内容
- 设计评审文档或对照实验报告,包含前置条件、方案取舍、验收证据与尚未验证的范围。
- 怎样算完成
- 比较使用一致条件与事先声明的标准,记录失败与未知结果。
- 说明方案的适用前提、未覆盖风险及后续验证方法。
练到的知识点Hit@k · MRR@k · 分级相关性与 NDCG
思维拓展
先写出你的猜想,再用例子或实验验证。答案可以不同,但要说清依据、适用情况,以及还有什么没弄懂。
- 作业 11思维拓展 · 反例构造
构造“同一查询的Recall提高,就能证明检索器变好”的反例,要求反例涉及标注单位或语料边界,而非单纯算错公式。
- 提交内容
- 具体反例、原主张的适用前提、复现或逻辑证明及结论边界。
- 怎样算完成
- 反例满足原主张所声称的前提,确实针对其结论。
- 说明一个反例能推翻什么、不能进一步证明什么。
练到的知识点文档和片段标注单位 · 检索失败归因
- 作业 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;对照源码分析理解实际调用边界。