说起来有点讽刺,我最近在做一个表单类文档的 OCR 评测,初版结果漂亮得让人发慌:字符错误率 CER 只有 1.72%。换算一下,相当于每 100 个字符里只错 1.72 个,这种精度放到商用场景里已经属于可以大张旗鼓宣传的数字了。但我把抽检样本翻出来逐条核对的时候,发现了很不安的东西——几个供应商名称、经办人姓名、银行卡号的后四位,全都错了。尤其是人名,几乎从“张三丰”变成了“张四丰”,汉字被悄悄替换成了形近字,视觉上人眼不仔细看根本发现不了,但数据入库以后就是完全不同的两个人。
这就是我这次想聊的核心问题:当你把 VLM(视觉语言模型)从“看图说话”的场景挪到 OCR 的严肃识别场景里,你会发现传统的 CER / ACC 指标根本看不见幻觉。指标好看,不代表结果正确。CER 是一个字符级别的编辑距离指标,它计算的是把识别文本转成真值需要多少次“插入、删除、替换”。它对于单词级的拼写错误很敏感,但它完全无法回答一个问题:这个错,错得有多严重?把“有限公司”识别成“有限责任公司”,CER 会很难看,但这在业务上几乎无所谓;反过来,把人名、编号这类不可纠错的实体字段识别成一个语义完全不同的合法字符串,CER 可能只涨零点几个百分点,但业务上这是重大事故。这篇文章我想把 VLM 转 OCR 这条路踩过的坑、指标失效的机制、以及补救的评测方案完整讲一遍。
1. VLM 转 OCR 的整体设计与方案选型
1.1 为什么要把 VLM 用在 OCR 上
先说清楚我为什么选这条路。传统的 OCR 系统,比如 PaddleOCR、Tesseract、RapidOCR,它们的工作方式本质上是一个多阶段流水线:检测文本区域,逐块裁剪,再过识别模型,最后拼接。这套体系很成熟,识别印刷体、固定模板票据的时候又快又稳。但它在三类场景下明显吃力:版面极度复杂、元素有嵌套关系的表格票据;图文混排中文字和视觉元素相互解释的部分;以及需要语义理解才能正确输出的内容(比如“总金额(大写)”后面跟的到底是“人民币壹仟元整”还是对方手写了“1000元”然后旁边有个印章压住了)。
VLM 是一条完全不同的路线。端到端输入图片、输出文字,模型自己决定看哪里、读什么、怎么组织语言。好处是它对版面干扰的容忍度极高,不需要单独训练检测头,也不需要做复杂的后处理排序。坏处是,它本质上是“根据图像生成文本”,换句话说是在做条件语言模型的采样。于是所有大语言模型会犯的病,它都会犯:幻觉、自由发挥、对不确定区域强行编一个合理内容,尤其是当图片质量不高、字段模糊或者被遮挡的时候,VLM 会选择“圆过去”。
使用 VLM 做 OCR 的典型架构有两种。第一种是轻量接入:用 Qwen2-VL、MiniCPM-V 这类模型,直接给一句“请把这页票据上的所有文字转写出来”,然后把输出文本和真值做比对。第二种是重一点的 Agent 式方案:VLM 只是阅读模块,前面挂版面分析、字段定位,后面挂校验纠错规则,模型的输出只是一个中间结果。我一开始图省事用的第一种,评测指标也是第一种模式下算出来的,结果被 1.72% 这个数字狠狠骗了一次。
1.2 2. 指标失效的机制:CER 不是万能的
CER 的计算是逐字符的编辑距离归一化,它天然有好几个盲区。盲区一,它不区分错误类型。替换、缺失、插入统统等价地拉高错误计数,但业务上它们的杀伤力完全不同。身份证号的最后一位校验码识别错了,CER 可能只加 1/n;但这一位错了,整条数据就废了,甚至可能匹配到另一个人。盲区二,它对“合法但错误”的字符串毫无惩罚。也就是说模型把“李慕白”识别成“李慕青”,两次替换,CER 依然很低,但这两个人毫无关系。模型并没有产生一个不像话的输出,它非常自信地给出了一个像模像样的人名。CER 只知道“不像”,不知道“不对”。
我后来把错误样本做了个分类,发现了一个规律:传统 OCR 的错误大多是“视觉层面的变形”,比如0和O,1和l,8和B混了;而 VLM 的错误是“语义层面的编造”,它会把一个讹字补成一个通顺的词,会跨行重组文本,甚至会在印章把数字盖住的时候自己脑补一个逻辑上说得通的金额。这就是模型幻觉的典型特征。用一个特别糙的类比:传统 OCR 错,是抄写员眼神不好;VLM 错,是抄写员理解力太强看不懂还非要补全。你拿“错字数量”这个标准去考核后者,等于只看作文有没有错别字,不看他有没有谎报军情。
1.3 为啥这项评测卡住了我整整一周
一开始我怀疑是某个模型的权重加载有问题,甚至怀疑是不是推理参数里的 temperature 调太高导致采样随机性过强。排查了很久,最后发现 inference 参数完全正常,temperature 0.1,top_p 0.8,输出就是模型真实水平。然后我怀疑是 prompt 写得不清楚,于是换了几种措辞,比如强调“逐字转写,不要增删改”,CER 确实下降了零点几个点,但人名字段的错误率纹丝不动。真正卡住我的问题是认知上的:我一直把注意力放在“降低 CER”上,所有的优化动作都在围绕字符级差异做文章,而没有意识到核心矛盾是“评测维度选错了”。
后来我换了个思路:把样本里的字段拆出来,按字段类型分别算准确率。这一拆立刻真相大白。公司名、地址这类长文本字段,VLM 表现得近乎完美;人名、证件号、金额数字这类短实体字段,错得一塌糊涂。因为长文本有上下文约束,前后文可以互相纠正;短字符串没有冗余,一旦模型开始沿着错误的视觉特征推理,就得靠瞎猜。猜得准不准,完全取决于训练数据里见过多少个类似字形。
2. 核心细节解析:VLM 做 OCR 的幻觉重灾区
2.1 三类最典型的人名替换错误模式
我把错误样本整理了一遍,归纳出三类高频模式。
第一类,形近字替换,占大约六成。比如“侯”变成“候”,“管”变成“菅”,“未”变成“末”,“干”变成“千”。视觉上用 VLM 的中间层表征来看,这些 token 的视觉embedding确实非常接近,替换之后输出的句子依然通顺。第二类,同音字替换,占两成。比如“张伟”变成“张苇”,“晓明”变成“小明”。这类错误在纯视觉特征层面解释不通,更像是模型在解码过程中把听觉通道的语言先验也带进来了。第三类,多字省略或扩展。比如“欧阳娜娜”变成“欧阳娜”,“爱新觉罗”变成“爱新罗”。这种常见于印章、手写体遮挡导致部分笔画确实不可见,模型选择“跳过忐忑的字符,输出一个有信心的子串”。
最阴间的是第一类中的一个小分支:模型输出的汉字不仅字形相似,而且生成了训练语料里出现频率更高的常见字。我曾经遇到过“徐”被识别成“余”,当时我一度怀疑是检测框漏了半个偏旁,但后来检查原图,“双人旁”在图像里非常清晰。这就是典型的语言先验压过了视觉证据,模型觉得“余”在这里出现概率更高。
2.2 表格、印章、低分辨率都是助燃剂
并不是说 VLM 在所有图片上都会疯。我用完全相同的模型跑清晰印刷体、打印体、扫描件和手机拍摄件,发现场景因素对幻觉概率的影响极大。
表格是最明显的幻觉助燃剂。表格单元格里的文本天然缺乏上下文,尤其是数字、简称这类短内容。模型看到一列表格,很容易聪明地根据列名推断单元格内容,而不是老老实实去读图。举个例子,一张采购清单,列头是“供应商名称”,某一行盖了半个章,模型就直接把上一行的供应商名抄了下来。CER 照样低,因为只是两个字的差异;但上下游数据对账的时候,这就是一笔错账。
印章是第二大类问题来源。公章压住文字的时候,传统 OCR 往往是识别不出来或者乱码,这种错误是显性的,后处理能拦截;VLM 不同,它会主动把印章下的内容猜出来,而且输出的时候完全不加任何“不确定”的信号,结果可信度和普通字段没有任何区别。等你在下游校验规则里发现发票代码对不上,已经晚了。
低分辨率和字体生僻同样是大麻烦。长尾字体、艺术字、异体字,这些样本在训练集里本来就少,模型拿不准的时候就会往常见字上靠。你给它一张像素不高的伞兵绳照片,它敢给你读成“余兵绳”。我会在后文给出一套防住这些幻觉的工程方案,但在此之前,评测体系必须先把这些错误暴露出来。
2.3 为什么 CER 看不见这些错误
说到底,CER 是字符串距离,不是语义距离。它对惩罚“数字变成相似数字”和“人名变成另一个人名”是一视同仁的。我们作一个简单的推演:设某行文本总字符数 50,模型把人名 3 个字全换成了另一个合法名字。CER 大约是 6%(3 次替换除以 50)。如果你用的真值只有 20 个字符,CER 就是 15%。看绝对数值,这似乎已经不难看了?不,问题的关键在于,在没有专门针对实体字段的评测维度的前提下,这个 15% 会被平均分配到所有样本的 CER 里。你看到的 1.72% 是全测试集 500 张图的平均,一个高价值字段的错误被稀释在几百个低风险字段中,完全隐形。
再举一个更直观的例子:如果测试集里 98% 的样本都是印刷体公司名和地址,2% 的样本人名区域被故意擦掉。模型在 98% 的样本上 CER 能做到 1%,在 2% 的样本上人在字段上大面积出错,CER 高达 40%,平均一下总体仅为 1.78%。这个数字没有任何实际意义,但它会让你误判整个系统的健康度。
3. 实操过程:从 1.72% 到定位幻觉源的完整评测实验
3.1 测试集与数据划分的重新设计
我开始重新设计实验时,先做了一件小事:把真值表从“整行文本字符串”改成“字段级 JSON”。一张票据不再是一行话,而是拆成vendor_name、vendor_id、amount、date、bank_card_no等字段。这一步很苦,因为标注量翻了好几倍,但这是整个评测体系重建的地基。
字段级的真值表带来的第一个直接好处是,我可以分别计算每个字段的准确率。这时候之前那个好看的数字立刻现出原形:公司名字段识别准确率 98.6%,而姓名字段只有 91.2%,银行卡号字段 93.5%。注意这里我说的准确率是整字段精确匹配的准确率,不是字符级指标。整字段精确匹配的意思是,一个字都不能错,错一个字就算这个字段失败。这才是业务视角的指标。
数据集划分上也特别讲究。如果想把幻觉问题暴露出来,测试集必须包含大量对抗样本。我重新构建了四类子集:清晰印刷体 300 张、模拟盖章遮挡 100 张、低分辨率扫描件 100 张、手写叠加印刷 50 张。前两类模型表现都不错,真正拉低成绩的是盖章遮挡和低分辨率集。在这里,幻觉率直接干到了 20% 以上。
3.2 用逐字段排查定位幻觉分布
拿到字段级结果之后,我把所有错误样本重新拉出来,写了一个jsonl格式的错误审计文件,每一行是这样子的:
{ "image_id": "ocr_11234", "field": "customer_name", "ground_truth": "詹慕白", "prediction": "詹慕青", "cer_contribution": 0.02, "error_type": "similar_character_hallucination", "confidence": 0.93 }注意看最后一行的 confidence,模型对这个错误输出给出的置信度是 0.93。这是最恐怖的:它在高置信地产生幻觉,而不是犹豫不决地猜测。我后来又统计了一下,所有字段错误里,置信度超过 0.85 的占了 76%。这意味着单纯用概率阈值做后处理过滤几乎无效,你没法通过“置信度低的输出不可信”这个规则来兜底。
之后我做了两次人工分类。第一次按字段归类,第二次按图片状况归类。交叉统计后发现:盖章遮挡场景在“人名、金额”字段上的幻觉集中度是清晰印刷体的 8 倍。这就定位到了需要重点防御的具体场景。
3.3 设定新的基准评测体系
重建评测体系之后,我不再单看 CER 了,而是设定了四个维度同时汇报:字符级 CER(可以和行业基线横向对比)、字段级精确匹配率、实体级语义错误率、幻觉样本占比。
实体级语义错误率是我自己定的一个指标:把预测值里的实体字段单独抽取出来,和真值做比较,若两个字符串代表的是不同实体,则视为一次实体级语义错误。用人名举例,“张伟”和“张苇”代表的是两个不同的人,算错;“张伟”和“张伟”一模一样,算对。这个指标最大的作用是倒逼模型,让它不能靠“读音相同”蒙混过关。
幻觉样本占比指的是所有样本里“输出内容完整但存在语义性编造”的比例。这个指标不一定要压到 0,毕竟印章遮挡等场景下强行猜测难免,但它是衡量系统可信度的核心风向标。我实测下来,一个好的 OCR 系统工程化部署时,这个比例至少要压到 1% 以下,否则下游数据业务根本兜不住。
4. 常见问题与排查技巧实录
4.1 为什么我换了更大更强的 VLM,幻觉反而更明显
我试过把 4B 参数级别的小模型换成 72B 级别的大模型,得到的结论是:整体 CER 确实下降了,但实体幻觉不但没有消失,在某些场景下反而更顽固。原因在于模型容量变大以后,它的“世界知识”更丰富,语言先验更强,对残缺图像的自作聪明程度只会水涨船高。尤其是当推理时采样贪婪度拉满、只取最高概率 token 的时候,模型会坚决地输出一个“更像话”但错误的结果,连丝毫不确定性都不会表现出来。这和直觉是相反的。直觉会告诉你,大模型更聪明,应该更可靠;实际是,大模型更善于编造“合理的假话”。
从工程角度,我把这条经验总结为一个选择标准:如果业务场景是整篇文档的语义理解、摘要、结构化抽取,VLM 越大越好;如果业务场景是精确转写、录入校验、字段归档,模型大小不重要,重要的是你如何限制它的自由发挥空间。大模型在这种场景下不一定是资产,甚至可能是负担。
4.2 如何设计 Prompt 来压制幻觉
Prompt 设计确实有影响,但影响幅度有限。我把多次实验下来最好用的 Prompt 模板写在这里:
请逐字转写这张图片中的所有可见文字。 要求: 1. 只输出图片中真实存在的字符,不要补全看不到的内容; 2. 如果某个区域被遮挡、模糊或不可辨识,用 <UNKNOWN> 标记; 3. 不要根据上下文推测不存在的词; 4. 保留原始换行顺序,不要重新组织排版。其中最关键的是第二条和第四条。加不加这份约束,实测下来在遮挡场景下的幻觉占比从 23% 降到 17%,有效果但根治不了。因为即便你让它输出<UNKNOWN>,很多开源模型的指令遵循能力本来就有限,稍微复杂一点的版面它就回到“自由发挥”的老路。
我更推荐的是配合约束解码来用:把输出空间限定在预设的字段范围内。比如你预先知道某个字段是数字,那就在后处理阶段用正则强制清洗;你预先知道是姓名,就建立一个常见姓氏表,预测结果不在表内且与真值形态差别细微时保守拒识。约束解码是工程兜底,Prompt 只是软引导,两者叠加效果远大于单用任何一个。
4.3 从困扰到突破:一个被我忽略的细节
整个过程里最典型的思维死角是:我默认“评测指标是客观的,模型是固定的”,于是遇到异常指标只会在模型或指标之间来回怀疑。直到我放弃总指标、回到逐字段样本审计,才真正找到问题所在。这个习惯我保留到了后续所有项目里,现在做评测第一件事不是看分,而是先挑 20 张错得最离谱的图亲眼看一遍。
还有一个小细节值得留意:推理时的temperature参数,OCR 任务里应该设置成 0 或接近 0。这个我从一开始就设置了,但不是所有人都会做这件事。有不少 VLM 在部署时保留文本生成任务的默认参数,比如temperature=0.7,这在对话场景没问题,在 OCR 场景就是灾难性随机源,同一张图每次跑结果还不一样。你可以做个简单实验,把 temperature 调到 0.9 跑同一张单据十次,会看到不同的人名版本,这就是把 OCR 任务做成了抽卡游戏。
4.4 常见问题速查表
| 问题现象 | 根因 | 排查思路 | 我的默认处置 |
|---|---|---|---|
| 总指标 CER 低但字段经常错 | 语义级错误被长文本稀释 | 拆分字段级评测 | 重建字段级真值表 |
| 大模型幻觉比小模型严重 | 语言先验过强,视觉证据被覆盖 | 逐样本比对模型输出与真值 | 用约束解码替代纯自由生成 |
| 同图多次推理结果不一致 | temperature 过高 | 查看推理参数配置 | temperature 置 0,关闭随机采样 |
| 盖章区域出现无中生有内容 | 模型在补全视觉缺失区域 | 检测盖章区域并加大遮挡测试占比 | 区域检测 + 强规则后校验 |
| 输出高置信度但错误 | 模型对幻觉毫无自知力 | 置信度阈值过滤无用 | 用二次校验模型或规则兜底 |
| 真值错也会打高模型错误率 | 标注本身存在错别字 | 抽检标注质量 | 双人复核,不一致样本剔除 |
提示:如果你的项目已经跑完了 OCR 评测,建议回头检查测试集中是否有整张图出错率极低、但个别字段连环错的“带上毒的甜蜜样本”。这种样本会像糖衣炮弹一样把整体指标抬得非常好看,却对你的业务毫无贡献。
5. 工程侧的必要防线:评测之外的事
评测体系重建之后,工程侧也必须同步补位。否则你只是“看见了问题”,还没有“解决问题”。这一节专门讲我把幻觉从“指标问题”变成“已解决问题”时用到的几种工程手段。
第一道防线是字段级后处理校验。这个方案不需要改动模型,成本最低。对所有预测字段做约束:数字字段走正则,日期字段格式校验,金额字段范围校验,身份证、银行卡号加校验位。一旦校验失败,强制转人工复核。这套方案能拦截大概 40% 的明显幻觉,尤其是数字类的,因为数字幻觉往往会产生非法格式或非法校验值。人名这类开放集合字段拦不住,因为“张苇”本身合法。
第二道防线是区域锁定。对于固定版面的票据,先用目标检测模型定位出人名区域、金额区域,再把整张图裁剪成若干子区域,分别独立交给 VLM。这个方案的效果立竿见影,因为它割裂了全局上下文的“圆谎空间”。模型不能借助整张图的语义去脑补被遮挡的字段,只能基于一小块子图做转写。实测下来,盖章遮挡场景下的实体级错误率从 20% 降到了 7% 左右。
第三道防线是“转录 + 比对”的二次校验。我用一个更小的专用 OCR 模型(比如 PaddleOCR)在相同区域做一次独立识别,拿两路结果做交叉比对。一致则通过,冲突则回退给人工。VLM 擅长语义理解,传统 OCR 擅长图形匹配,两者恰好互补。这个方法能在成本可控的前提下,把人工复核的量压到极低,同时也为人工标注攒下一批高质量难例,后续可以做模型微调的数据池。
从成本侧看,第一道防线几乎零成本,第二道要准备目标检测模型,第三道要引入第二个 OCR 引擎。我的建议是从第一道开始,逐级叠加,按实际情况评估投入产出。我在实际项目里最后保留的是第二道和第三道叠加,整体推理成本上升约 30%,但实体级错误率从 23.5% 压到了 2.1%。
这也是我特别想在结尾再强调一次的原因:把 1.72% 当作成功,项目上线半年后所有脏数据都会变成定时炸弹;但用上面这套方案把“看起来很好的指标”修正成“真实可信的指标”之后,整个人就踏实了。技术圈现在都在谈模型能力,但真正决定一个 OCR 系统能不能落地的,从来都是这些“看不见的可靠工程细节”。