核心结论: 我们构建了一个面向扫描文档的签名存在性检测器,在 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 中,文末给出复现命令。
这是评测集里的一个真实签名,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) 置信度:
/s/、By:、"duly authorized"、"authorized signature"。有一个诱人的信号被刻意否决了:把逐块 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——公开测量的意义正在于此。