可审计科研文献检索实验封面

摘要:先让每一个分数有出处

此前的教学实验让我们知道指标怎么计算,却没有回答一个更实际的问题:换成公开科研文献,代码中的假设还成立吗?本篇把研究对象换成 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,两字段的合成关系可以写成:

s(q,d)=max⁡(st,sa)+0.5min⁡(st,sa).s(q,d)=\max(s_t,s_a)+0.5\min(s_t,s_a).

这里查询为 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:

nDCG@10(q)=∑i=110ri/log⁡2(i+1)IDCG@10(q).\mathrm{nDCG@10}(q)=\frac{\sum_{i=1}^{10}r_i/\log_2(i+1)}{\mathrm{IDCG@10}(q)}.

此处标注为二元相关性,排在第 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。

  1. Wadden, Lin, Lo, Wang, van Zuylen, Cohan, Hajishirzi. Fact or Fiction: Verifying Scientific Claims. EMNLP, 2020。
  2. Thakur, Reimers, Rücklé, Srivastava, Gurevych. BEIR: A Heterogenous Benchmark for Zero-shot Evaluation of Information Retrieval Models. NeurIPS Datasets and Benchmarks, 2021。
  3. BEIR Authors. 官方代码,固定提交。
  4. Allen Institute for AI. SciFact 固定提交与数据说明。
  5. Elastic. 7.17 多字段检索、BM25 默认参数、英文分析器。