
摘要:先让每一个分数有出处
此前的教学实验让我们知道指标怎么计算,却没有回答一个更实际的问题:换成公开科研文献,代码中的假设还成立吗?本篇把研究对象换成 SciFact,目标只有一个:从固定数据文件出发,通过 BEIR 官方词项检索链路,留下别人能够检查的候选、分数、版本和失败记录。这里没有神经模型训练;真实目标模型是 Elasticsearch 中的 BM25 排序模型。
本次实际验证覆盖全部五千余篇文档和八百余条开发查询,不是缩小文档池的演示。我们暂不比较稠密模型,也不挑选更好参数。相较上一轮,新增变量是研究对象与真实实现;冻结条件是语料、字段、分词、候选预算和评价口径;新增产物是后续五篇共同使用的检索基线包。
一、复现价值:找回文献不等于核验科学结论
论文报告。 Wadden 等人在 EMNLP 2020 提出 SciFact,任务涉及找到包含证据的摘要、判断支持或反驳关系,并定位依据。本文只复现文献检索这一环,没有运行判断立场或抽取证据的模型,不能把召回率叫作事实核验准确率。原论文
论文报告。 Thakur 等人的 BEIR 工作把不同检索任务纳入统一框架,并指出词项基线仍有竞争力。这给我们的理由是“先建立基线”,而不是预言 BM25 必然超过 Embedding。论文使用的多数据集结论,也不能直接代替本机的一次 SciFact 实验。BEIR,2021,v4
本次实际验证。 文件里有三类实体:语料库 corpus 存文档,queries 存查询,qrels 存查询与相关文档的关系。它们通过字符串 ID 连接,不能拿文件行号当主键。程序核对了重复 ID、相关文档缺失、查询与文档 ID 碰撞,并在加载之后检查评测查询集合没有变小。
二、数据审计:最危险的是看起来熟悉的名称
| BEIR 文件 | 实际数量 | 与原始 SciFact 的对应 |
|---|---|---|
| corpus | 5,183 文档 | 原始文档 ID 与标题一致 |
| train qrels | 809 查询、919 对关系 | 原始 train |
| test qrels | 300 查询、339 对关系 | 原始 dev,并非原始无标签 test |
这是逐 ID 核对后的关系,不能根据文件名猜测。原始测试集不公开标签,官方说明也明确区分开发评测和测试预测。本项目把 BEIR train 当作开发池,BEIR test 留作后续确认。确认集目前只做结构与映射审计,没有检索、挑错例或调参;它并非外部封存盲测,也不保证与开发池在文献层面独立。官方数据说明
更关键的是,两份 qrels 都完全对应原始 cited_doc_ids,并不完全对应 evidence 的文档键。开发池有三百余条声明的 evidence 为空,仍然有检索相关文档。因此“找到了标注文献”不能自动解释成“找到了支持该声明的证据”。这一差别决定了后续错误分类应研究文献匹配,而非擅自评价声明真假。
还有一个真实失败:最初的审计要求摘要逐字相同,直接报错。检查发现,1,055 篇文档与原始摘要句子拼接结果存在纯空白差异;归一化空白后全部一致。我们保留失败日志,并把审计拆成“标题完全相同”“摘要归一化后相同”,检索仍使用 BEIR 原文件,没有悄悄改写输入。
版本也不能只写“最新版”。原始下载地址带有 latest,本次用实际文件 SHA-256 锁定内容,无法证明它永远不变。获取脚本遇到不同字节就停止。许可证以已打开的原始仓库为准:声明与证据标注为 CC BY 4.0,摘要为 ODC-By 1.0,代码为 Apache 2.0;数据卡仍显示另一旧口径,不能混为一谈。压缩包不再分发完整语料。固定版本许可证
三、核心思想与公式:先看字段怎么参与排序
BM25 是按词项匹配打分的检索模型。它奖励查询词出现,抑制同一词反复出现的收益,并考虑文档长度。英文分析器会影响词形和停用词,因此“都叫 BM25”不代表实验等价。本次使用 Elasticsearch 7.17.9 默认参数 k1=1.2、b=0.75,不搜索参数;前者控制词频饱和,后者控制长度归一化强度。官方相似度文档
BEIR 这条链路分别索引标题和摘要,使用 best_fields,两字段的合成关系可以写成:
这里查询为 q、文档为 d,两个标量分别是标题与摘要字段的匹配分数。系数来自固定源码的 tie_breaker,不是我们调出来的权重。它与“标题加空格加摘要后一次打分”不是同一协议,也不是把两个分数简单相加。官方多字段说明

图中相关性标注只进入审计端,不参与索引或检索评分。锁住的确认集没有回到开发流程的箭头。方法图只解释关系,所有数值均来自保存的 JSON 日志。
四、官方代码阅读路线:沿数据流读四处
先看 GenericDataLoader.load:它按 qrels 筛选 queries,因此分母审计必须放在加载之后。再看 BM25Search.index:标题和正文进入不同字段,不能照着函数名自行拼接。接着看 lexical_multisearch:这里决定字段、查询类型、跨分片统计与返回数量。最后看 EvaluateRetrieval.evaluate:它调用 trec_eval 接口并将聚合值保留五位小数,原始逐查询值需要另外保存。
实际运行还碰到两个实现障碍。旧 Elasticsearch 客户端引用了 NumPy 2 已删除的类型别名,固定到 NumPy 1.26.4 后导入通过。当前 BEIR 提交又要求词项检索类实现两个稠密编码抽象接口,导致实例化失败。附带的 compat.py 仅让这两个不适用接口显式抛出错误;索引、查询和评分全部继承原实现,没有另写一个“近似 BM25”。源码地图记录了精确提交、行号与适配边界。
五、最小实验与评测协议
运行前已经保存冻结协议:先用按 ID 排序的五条开发查询做 smoke test,再运行全部 809 条;完整语料始终保持 5,183 篇。没有训练阶段,没有选择最佳种子或参数;种子 82 作为配置记录,词项基线不依赖随机初始化。索引采用单分片,查询批次为 64,每条最多保留 100 个候选。
主指标为归一化折损累积增益 nDCG@10:
此处标注为二元相关性,排在第 i 位的文档相关时 r 为一,否则为零;分母是同一查询的理想排序得分,短列表缺失位置按零计。先逐查询计算,再做宏平均,不能把所有命中文档混成一个总比例。辅助指标 Recall@100 衡量相关文档被找回的比例,MRR@10 衡量首个相关文档名次的倒数。
官方检索会多取一个候选并过滤同 ID,但本数据没有查询文档 ID 碰撞。实际有 808 条返回 101 个结果,一条只返回 27 个。我们保存原始返回,再按分数降序、同分时字符串文档 ID 降序截到 100,与 trec_eval 的同分口径核对;绝不补造未匹配文档。最终保存 80,827 条候选记录,而非假设存在稠密的查询乘文档分数矩阵。
“完整候选”在这里指本次约定预算内的全部返回,不是穷举所有文档得分。未进入返回池的文档没有保存分数,不能事后拿这份文件计算任意深度的召回。未来重排也只能使用同一候选池;若增加检索深度,应登记为新实验。对于并列分数,当前排序规则只约束已返回候选,不能保证跨版本的边界候选集合完全一致。
六、本次实际验证:均值之外保留失败
| 固定开发协议 | 实测结果 |
|---|---|
| nDCG@10 | 0.69427 |
| Recall@100 | 0.93506 |
| MRR@10 | 0.66252 |
| 前十名没有任何相关文档 | 133 / 809 |
| 前百名没有任何相关文档 | 47 / 809 |
这些数值来自本机运行,不是论文报告或排行榜抄录。全部逐查询 nDCG 与 Recall 用独立标量公式重算,与官方接口最大误差约为一乘十的负十六次方。空结果按零计、缺失查询拒绝通过、同分排序和第二名命中也有独立检查。该核对支持“程序按既定口径计算”,不证明标注完备。
CPU 环境为 macOS 26.6 arm64、Python 3.12.14、Lucene 8.11.1。预处理约 0.149 秒,建索引约 1.281 秒,检索约 0.651 秒,评测约 0.066 秒;总入口约 2.867 秒,不包含下载和服务启动。Python 峰值常驻内存约 112.4 MiB;服务堆上限 512 MiB,结束时堆占用快照约 269.8 MiB,服务峰值内存未测量。服务已经过 smoke 预热,所以不能称这些耗时为冷启动性能。
七、失败排查与可检验研究问题
首先查实验故障,再解释模型。网络代理不可达、端口被沙箱拒绝、依赖冲突、抽象类报错都已记录;它们不是检索失败。建索引后显式刷新并检查文档数,也比等待固定秒数更可靠。将来复用索引时,程序逐文档核对存储字段,避免同名索引里混入旧数据。
查询 173 只有 27 个候选,相关文档未出现。这只是可定位的现象,不能立刻命名为“语义鸿沟”。可能解释包括词形处理、专有名词匹配不足,以及标注对应文献与表述差距。尚未完成的人工错误分类明确留待人工核验,本文没有伪造人工审查结论。
作者推断。 下一篇可以检验一个有限假设:冻结同一语料、查询和候选预算后,稠密模型是否主要改善本次漏召回查询,同时损失一部分词项命中查询?需要看逐查询配对差值及新增、丢失文档,不能只看总均值。如果提升只来自更长输入或更多候选,就不能归因于表示能力。此前失败样本可用于开发分析;确认集必须等方案冻结后再评测,查询之间可能共享文献,后续区间估计还要审计分组相关性。
八、源码与复现入口
本篇完整代码已公开在 GitHub,以下入口均固定到提交 cae9252,后续维护不会改变本文引用的版本:
先按 README 安装固定依赖、校验数据并启动本地服务,在本篇源码目录运行 python run.py --cache /path/to/research_cycle6 --out results/my_run。具体实现与配置请通过上述链接查看,无需博客 ZIP 附件。
这是实际验证过的完整开发集入口;加 --limit 5 可试跑。预期产物包括原始候选、TREC runfile、逐查询指标、聚合结果、数据审计、环境与阶段状态。本次执行到真实检索和官方评测,未运行稠密编码、重排、训练或确认集。缓存身份包含模型构建、分析器、数据、配置、代码和依赖版本;本篇没有 prompt 与解码,显式记录为空。源码包排除环境、索引、完整语料及服务发行包,外部资源由固定哈希获取脚本恢复。
代码与文章一起保留为本次实验的完整快照,运行数据和环境放在独立缓存目录。后续若抽取共享模块,应固定版本,并在各篇实验包中包含所需代码;历史文章不能直接依赖不断变化的公共目录。这样既能延续一个研究项目,也能让读者单独下载某一篇并复查当时的结果。
九、总结
第一次真实复现的收获,不只是一个分数,而是知道这个分数究竟衡量什么。划分名称、相关性语义、字段组合和候选数量都会改变解释。保存失败与版本以后,下一篇才有资格问“另一种方法改善了哪些查询”。本篇已经建立可重放的开发基线,下一篇将沿同一协议研究稠密检索带来的逐查询变化。
参考资料
检索与实际访问日期:2026-09-25。论文年份、作者及版本已核对;源码永久链接、许可证和本地映射见 SOURCE_MAP。
- Wadden, Lin, Lo, Wang, van Zuylen, Cohan, Hajishirzi. Fact or Fiction: Verifying Scientific Claims. EMNLP, 2020。
- Thakur, Reimers, Rücklé, Srivastava, Gurevych. BEIR: A Heterogenous Benchmark for Zero-shot Evaluation of Information Retrieval Models. NeurIPS Datasets and Benchmarks, 2021。
- BEIR Authors. 官方代码,固定提交。
- Allen Institute for AI. SciFact 固定提交与数据说明。
- Elastic. 7.17 多字段检索、BM25 默认参数、英文分析器。