这份合同到底有没有实体签署过?在扫描档案中检测未签署文件

核心结论: 我们构建了一个面向扫描文档的签名存在性检测器,在 100 页真实 Tobacco800 文件(66 签署 / 34 未签署)上对照专家区域标注评分:精确率 0.981、召回率 0.773、F1 0.864。平凡基线——假设档案里每份合同都签过——F1 0.795,但找出的未签署文件数量是;检测器标出了 34 份中的 33 份,只有 1 个误报。最强的信号出乎意料:OCR 读不出墨水签名,而这个失败本身正是我们检测的对象。所有数字均为定点实测(2026-07-07),提交在 content/signature_results.toml 中,文末给出复现命令。

签名在 OCR 引擎眼中长什么样

这是评测集里的一个真实签名,paddleocr 看到的样子——上世纪 70 年代一封打字机信件底部相邻的两个块:

块文本 OCR 置信度
wm.Hobbr 0.62
Wm. D. Hobbs 1.0

引擎把打字体姓名读得一字不差,却在上方的墨水涂鸦上栽了跟头——输出了一个乱码的低置信度 token。它完全不知道那里有个签名。但这个模式——页面下部、一行读得干干净净的人名、正上方压着一个低置信度乱码块——正是签名在 OCR 输出中留下的可靠痕迹。我们检测的不是签名,是签名的残骸。

谁需要这个

问题来自合同生命周期管理(CLM):企业迁移遗留档案——成千上万份扫描的历史合同——需要知道其中哪些从未真正签署过。档案里躺着一份未签署的合同是法律和审计问题,而默认假设("进了档案就是已签署")对相当大的比例是静默错误的:我们这个真实档案评测集里,100 页中有 34 页根本没有签名。任何文档级质量分数都不会告诉你这件事——我们在上一篇 OCR 遗漏文章里单独测过这个盲区:缺失的签名和任何遗漏一样,不产生任何可供质量信号打分的块。

三个信号,以及一个被刻意否决的

detect_signature(ir) 组合三个块级信号,每个都是从真实 Tobacco800 OCR 输出中设计出来的,以概率或(probabilistic OR)合并为 P(signed) 置信度:

  1. 结尾敬辞——匹配"Sincerely / Regards / Very truly yours / …"的块。遥遥领先的最强信号:73% 的已签署文件带有,未签署文件只有 6%,且精确率近乎完美。一封落了款的信就是签了的信。
  2. 显式签名线索——/s/By:、"duly authorized"、"authorized signature"。
  3. 签名块——上文那个"涂鸦压姓名"的几何模式:页面下部一行打字体人名,正上方压着一个低置信度 OCR token。它能找回没有敬辞的已签署备忘录和表单,在本评测集上误报成本为零。

有一个诱人的信号被刻意否决了:把逐块 OCR 置信度当作通用的遮挡检测器。在真实扫描件上,与签名/印章区域重叠的块置信度确实更低(0.893 vs 0.963)——但未签署的传真和电传和已签署的信件一样噪声大,所以在块粒度上这个信号几乎不区分两者,依赖它反而伤精确率。置信度只有锚定到几何(信号 3)时才有用:姓名行正上方的低置信度有含义;泛泛的低置信度只说明这是张传真。

对照专家真值的结果

真值是 Tobacco800 的 GEDI 区域标注:一页算已签署当且仅当其标注带有签名区域。Tobacco800 对签名的标注是全面的,所以零区域的页面是真正的负例,不是"没标注"。在 100 页上:

精确率 召回率 F1 准确率
"默认都签过"基线 0.660 1.000 0.795
签名检测器 0.981 0.773 0.864(+0.069) 0.840

F1 增量看起来不大,因为在一个大多已签署的语料上基线白拿满分召回。真正要看的是 F1 表不出来的那一列:基线从构造上就找不出任何未签署文件;检测器标出了 34 份中的 33 份,误报 1 个。对档案迁移的那个问题——"把所有可能从未生效的文件都给我列出来"——这就是全部价值所在,而"默认都签过"一分价值也提供不了。

每个预测都带 evidence_block_ids,复核者能看到哪些块触发了判断——和流水线其余部分同样的来源归因纪律:无法追溯到证据的论断,是无法审计的论断。

诚实的局限

自己复现

git clone <repo> && cd contract-rag && uv sync --extra dev
# Tobacco800 TIFF + GEDI XML 区域标注:来自 Illinois CDIP 集合(从不提交到本仓库)
SIGNATURE_DIR=path/to/tobacco800/tiffs SIGNATURE_GT_DIR=path/to/gedi/xml \
  uv run python -m contract_rag.eval.signature

OCR 解析有 IR 缓存,重跑很快。检测器本身是对解析后文档的纯逻辑——用手工构建的 IR 做单元测试,不需要 OCR 也不需要网络。如果你的数字有实质性差异,欢迎开 issue——公开测量的意义正在于此。