> 会产生"指控"的工具,误报代价不对称——先归一化再开口
一个参考文献核验工具会把解析不出来的 DOI 标成 "likely hallucinated"。 在一轮批量运行里它报了 12 条——全部是假的。
// TABLE_OF_CONTENTS
结论先行¶
一个参考文献核验工具会把解析不出来的 DOI 标成 "likely hallucinated"。 在一轮批量运行里它报了 12 条——全部是假的。
根因是一行正则:它把句末的句号吃进了 DOI。
10.1038/s41586-024-08025-4. ← 末尾那个点是句号,不是 DOI 的一部分
带着句号去解析,Crossref 当然解不出来,于是一篇真实存在的论文被标成"可能是编造的"。
修法是一行:在报 phantom_doi 之前先 strip 尾部标点,再解一次。
为什么¶
这不是一个普通的假阳性。会产生指控的工具,它的误报代价和漏报代价不在同一个数量级。
- 漏报:一条可疑引用没被标出来,下游还有人看。
- 误报:一条正确的引用被标成"可能是幻觉"。如果这个标签进了一份给别人看的文档, 它就是一条不实指控。而现在不少会议明确把"由 LLM 生成的不实指控"列为可处分行为。
所以这类工具的设计原则和普通 linter 相反:宁可漏,不可冤。 具体到实现,就是在"开口指控"之前必须穷尽所有廉价的归一化:
| 归一化 | 为什么必要 |
|---|---|
strip 尾部标点(. , ; ) ]) |
从正文/PDF 抽取时必然带上 |
| 大小写归一(DOI 不区分大小写) | 不同样式输出不一致 |
URL 前缀剥离(https://doi.org/) |
同一个 DOI 的两种写法 |
10.48550/arXiv.* 白名单 |
这是 DataCite 的 DOI,Crossref 本来就解不出 |
| 全角/半角、软连字符、零宽字符 | PDF 抽取产物里常见 |
最后那条里的 10.48550/ 尤其值得单列:它不是解析失败,是问错了后端。
把一个"后端不负责"的情况报成"文献可能不存在",是同一个错误的另一种形态。
补一条同形缺陷:归一化只做了一边¶
另一个工具里的同一个错误,形态更干净。它核对的是"模型读者引用的句子/数字 是不是真的在交给它的那份文本里"。
读者写的是 R=95.10%,正文里写的是 95.10\%(LaTeX 的转义)。
而归一化只作用在读者那一侧,正文侧保留转义。后果不是偶发假阳性:
任何以百分号结尾的数,在任何一篇论文里,都永远不可能匹配上。
一条一边倒的归一化,会让整个语法类别永久失配,而不是让匹配率下降一点。
同一轮里另外三类也都是正常的读者行为:带省略号的引文、
(line 238) 这种位置锚、以及读者引用工具自己输出的那句话。
净结果是一份完全正确的读者报告上报了 4 条,4 条全假——
而这足以让人把已经手工核过的真发现一起丢掉。
修法已经进了代码,连理由都写在 docstring 里:
def _locates(q, nb, bw):
"""Resolved, tolerantly. Readers elide with '...' and trim punctuation, and a
verifier whose false positives land on accurate work is worse than none: it
launders a correct finding into 'not admissible'. Measured at 4 flags out of
4 on one correct report before these three tolerances."""
三道容差里的第三道值得注意:只要有连续 6 个词命中正文就算解析成功。 这是用精度换召回——一句编造的长句只要嵌了 6 个真词就能过关。 在这个代价结构下这个交换是对的:假阳性会销毁一条真发现, 假阴性只是少抓一条编造,而数字那一层还有第二道。 误报和漏报要按后果定价,不是按比例定价。
两条合起来是同一条规则:
两边都归一化,或者两边都不归一化。只做一边,等于给某个类别判了死刑。
怎么用¶
给任何会输出"可疑 / 违规 / 可能是编造"标签的工具加三道闸:
- 归一化在前,判定在后。 判定函数拿到的必须是已归一化的标识符,
而不是从文本里 split 出来的原始串。写一个单独的
normalize_doi()并测它。 - 分开"解析失败"和"不存在"。 后端不负责(DataCite 的 DOI 问 Crossref)、后端挂了、标识符格式不对、 查到了但元数据不匹配——四种,四个标签,四种处置。 见 一个标签盖了三种根因,而三种的处置相反——"可能是幻觉"的三张脸。
- 归一化必须对称。 写一个
normalize(x)同时作用在两侧, 而不是在比较的时候各自处理。不对称的那一侧一旦涉及转义、全角、空白折叠, 失配就是整类而不是个例。 - 指控级别的标签要有一条"反例预算"。 报 N 条之前先手工核 3 条。这一轮如果核过 3 条,12 条假指控一条都发不出去。
顺手一条:假阳性批量出现、而且专挑最著名的那些文献时,坏的是查询端,不是文献。 这是一个很好用的判别法。
出处¶
- 自己的参考文献核验链(
bibguard/ref-check)在一批外部 PDF 上的批量实测, 2026-10;DOI 正则吃句末句号 → 12 条假phantom_doi,其中包含真实可解析的 Nature DOI - 同一轮记录下来的另一条:
10.48550/arXiv.*属 DataCite,应白名单化而非报 FAIL - 单边归一化那一条出自
~/Tools/nonexpert-review的quotes子命令:feedback/里 2026-09-25 的四条回报,修后的_norm_quote/_POSITION/_locates在当前版本里,4/4 这个数写在 docstring 上 - 相关:一天里五次搞错同一类事:核查步骤本身没被核查、引用幻觉藏在作者列表和 venue 里,不在标题里——只比标题的批量校验会漏、 指令写进 prompt 不等于它生效了——加一道"引文必须在原文里"的机械核验