档案数字化系统研发源码手记:置信度过滤——把 97% 识别率变成真正可交付的成果

一、"识别率 97%"为什么不能直接交付

很多档案数字化项目验收时有个经典场面:乙方说"我们识别率 97%",甲方拿一页档案指着一个错字问"这个怎么错了",场面尴尬。

识别率是统计指标,客户看的是具体文件。 97% 意味着每 100 个字错 3 个——一份 500 字的文件就有 15 个错字,如果这些错字进了检索库,客户搜"张三"搜出"张兰",信任瞬间崩塌。

所以我们的做法是:OCR 出来不是终点,置信度过滤 + 分级处理才是真正可交付的起点。

二、置信度是什么,为什么它"虚高"

OCR 模型输出的置信度(score)是"模型对自己预测的自信程度",但它有系统性偏差:

  • 模型对"训练集里常见的字形"自信过高,哪怕这个字在档案里是错的(比如把"已"识别成"己");
  • 长文本行尾部的置信度普遍偏低,因为上下文衰减;
  • 模糊/断笔/浅色字迹,模型有时"自信地错"——置信度 0.9 但字是错的。

所以我们不能只信 score,要构建多维度可信度评估

三、三级置信度策略:我们的核心做法

class OcrConfidenceGate:
    """OCR 结果可信度分级门控"""
    def __init__(self, lexicons=None):
        self.lexicons = lexicons or {}   # 类型化词库:人名、地名、术语、档号模式

    def evaluate(self, text, score, field_type=None):
        """综合评估一条识别结果的可信度,返回 (level, reasons)"""
        reasons = []
        # 维度1:模型置信度
        model_conf = score
        # 维度2:字符级稳定性(对同一区域多次识别/或词典对比)
        stability = self._char_stability(text)
        # 维度3:词库/规则校验
        lex_conf = 1.0
        if field_type and field_type in self.lexicons:
            lex_conf = self._lexicon_match(text, field_type)
        if field_type == "archive_no":
            lex_conf = 1.0 if self._regex_archive_no(text) else 0.0

        # 综合分
        total = 0.5 * model_conf + 0.3 * stability + 0.2 * lex_conf
        if total >= 0.9:
            return "auto", reasons          # 全自动入库
        elif total >= 0.7:
            return "sample_check", reasons  # 抽检
        else:
            return "manual", reasons        # 人工录入

    def _char_stability(self, text):
        # 实践:对检测框做轻微扰动重识别,比对一致性
        return 1.0  # 简化示意

    def _lexicon_match(self, text, field_type):
        # 命中词库最高分 / 编辑距离惩罚
        return 0.8

    def _regex_archive_no(self, text):
        import re
        # 档号格式示例:全宗号-门类-年度-案卷号-件号
        return re.fullmatch(r'[A-Z0-9]{1,6}-\w{1,4}-\d{4}-\d{1,6}(-\d{1,4})?', text) is not None

三个核心维度:

  1. 模型置信度(占 50%):基础,但不可全信;
  2. 稳定性(占 30%):对检测框做轻微平移/缩放扰动重新识别,两次结果一致的才可信。这个技巧简单但极其有效——识别错字往往对扰动敏感;
  3. 词库/规则(占 20%):人名、地名、术语词库校验 + 档号格式正则。关键字段(档号、人名、日期)的格式校验权重可以单独提到 50% 以上,因为它们决定检索质量。

四、分级处理:三类结果三种去向

级别 综合分 处理方式 占比(实测)
auto(自动入库) ≥0.9 直接进检索库,无需人工 78%
sample_check(抽检) 0.7~0.9 按 10% 比例抽检,抽检合格则该批次放行 15%
manual(人工) <0.7 全部转人工录入队列 7%

(占比来自 2000 页留底样张实测,不同档案类型波动,发布前请替换为你们的数据。)

关键点:auto 和 manual 之间不是线性的,抽检层是成本/质量的调节阀。 项目工期紧就放宽抽检比例,验收严格就收紧。这一层给项目经理留了调节空间,不用动不动改模型。

五、为什么"检索命中率"比"识别率"更值得看

识别率高不代表搜得到。我们内部有两个指标:

  • 识别率:单字准确率,宣传用;
  • 关键字段召回率:档号、人名、单位、日期这些"检索入口字段"的准确率,这才是质量命脉

实测:单纯优化识别率到 97%,关键字段召回率可能只有 93%;用了置信度过滤 + 词库校验后,识别率维持 96.8%,但关键字段召回率提到 96.9%——检索体验提升一个档次。

给团队的建议:汇报时两个数字都要报,但内部考核只认关键字段召回率。 这决定了系统在客户那里的真实口碑。

六、踩坑记录

坑 1:置信度过滤误杀高置信错字。 有些错字模型"自信地错"(score 0.93 但错了),稳定性扰动识别也一致地错。这时词库/规则维度是最后的防线——比如档号格式正则,能拦下几乎所有"自信地错"的档号。

坑 2:词库膨胀导致误判。 人名库太大、含重名时,词库匹配反而把正确识别改成词库里的其他名字。我们的解决:词库匹配只做"候选排序",不做"强制替换",除非候选置信度碾压(>0.15 差距)。

坑 3:抽检是"比例抽检"还是"分批抽检"。 我们一开始按总量 10% 随机抽,发现问题批次可能整批漏检。改成按批次(比如每天加工的批次)抽检,批次不合格整批回退重新处理——这让问题能追溯、能闭环。

七、小结

  • 识别率是宣传指标,关键字段召回率才是质量命脉
  • 三级置信度策略(模型分 + 稳定性 + 词库规则)把 97% 的识别率变成可交付的质量;
  • 抽检层是成本调节阀,按批次抽检才能追溯闭环;
  • 宁可多标"人工复核",不把错字放进检索库。
posted on 2026-09-03 15:58  程序员李铁牛  阅读(4)  评论(0)    收藏  举报