VLM做OCR,CER 1.72%也掩盖不了人名幻觉:双指标评测与工程防护
上个月我在验收一个票据识别项目测试集跑完模型得分漂亮得让人想直接签字盖章CER 只有 1.72%按多数团队的验收口径这已经属于“无可挑剔”的范畴。可偏偏我多翻了一眼真实样本人差点没坐稳——发票“甲方”栏里的人名十张里有三张被替换成了完全不同的名字有的甚至一字不差地换成了同音字。日期、金额、税号全对就是“王建国”变成了“王建华”。字字都对得上人却不在了。这个现象不是个例。这两年做 OCR尤其做文档智能绕不开一个趋势直接拿 VLM 当 OCR 用。Qwen-VL、GLM-4V、GPT-4V 这些多模态大模型在开放场景下的版面理解和文字转写确实碾压传统方案。但问题在于很多人验收时只看一个指标——CER字符错误率肉眼看不见大模型最要命的副作用幻觉。幻觉不会把 CER 变成两三个百分点它往往藏在一个个“看似合理”的人名替换里这套指标根本照不出来。1. 一张 1.72% 的成绩单和它看不见的错误1.1 先搞清楚 CER 是怎么算出来的CER全称 Character Error Rate字符错误率。基于编辑距离归一化得到统计把识别文本变成参考文本最少需要多少次插入、删除、替换操作CER 就等于这个编辑距离除以参考文本的总字符数。公式写成日常语言就是CER (插入数 删除数 替换数) / 参考字符总数数值越低越好。业界普遍认为 5% 以内算可用2% 以内算是“非常不错”这也是为什么 1.72% 看起来极具迷惑性。举两个直观例子。参考文本是“王建国去银行开户”一共 8 个字符。如果模型输出“王建国去银彳亍开户”把“行”认成了“彳亍”两个字编辑距离可能是 2 或 3CER 大约 25%这种错误肉眼可见。但如果模型输出的是“王建华去银行开户”只把“国”换成“华”一个替换操作编辑距离只有 1CER 1/8 12.5%。从数值上看两种错误的严重程度差了三四倍对下游业务来说第二种错误往往更致命——因为没有人会发现“华”和“国”在字面上差得不算太远特别是在票据扫描件里。这就是 CER 的第一个问题它在错位、漏字、多字这类“字符形态错误”上非常敏感但在“字符替换成另一个合法字符”时只按一次编辑距离计分权重和错误本身的业务影响完全不成比例。等长替换里那些语义错误——人名变成另一个常见人名公司名变成另一个存在但完全不同的公司地址变成“同音不同字”的地址——编辑距离全部只有几分摊到整篇文档上看就是小水花。1.2 CER 的盲区等长替换要理解 1.72% 这个数字为什么骗人得把“等长替换”单独拎出来说。所谓等长替换就是识别结果和参考文本长度完全一致只是某些位置上的字符变了。这种替换对编辑距离的贡献和“删了一个字再插一个不相干的字”一样都算 1 次操作。但二者业务性质天差地别我删掉一个标点下游字段解析可能完全不受影响我把合同甲方的人名“王建国”换成“张建军”整份合同的当事人身份就全错了哪怕正文每个句子都通顺、金额和日期都对这份合同在法务上已经不能要了。更微妙的是VLM 的幻觉往往是“合理替换”。它不是传统 OCR 那样把“国”误认成“田”这类字形接近的错误而是在语义层面用一个高频词覆盖掉低频词。比如图像里手写“刘淑芬”模型在语料里见过太多次同样结构的姓名模糊的笔画让它无法确认最后一个字于是直接按语言习惯“脑补”成“刘淑华”。这种错误从字形上看相距甚远从语言模型的角度看却“通顺极了”CER 只看编辑距离完全看不到语义层面是否出了事故。我做过一个粗略测试在 80 万字的合同语料里把所有关键人名、机构名故意替换成同长度的高频词CER 大约只上涨 1.5 到 3 个百分点但下游的合同质检系统准确率直接跌到 30% 以下。这足以说明CER 在 1.72% 这个量级完全可能掩盖几十处关键实体被替换的事实。用 CER 单指标验收一个“以信息准确性为生命线”的 OCR 系统从一开始就是在赌运气。1.3 从“字符对”到“信息对”指标之外要多看一层那该关注什么我的习惯是把指标拆成两层看。第一层是字符层的 CER用来判断模型“字形跟不跟得住”第二层是字段层的实体正确率把识别结果按业务字段切开逐字段比对甲方姓名、乙方姓名、金额、日期、账号、地址每个字段单独算“完全正确率”。只有两层指标同时达标才敢说这个系统能上线。字段层指标其实很好算不需要什么复杂工具。把参考文档里“人名”这个字段单独抽出来和模型输出的人名做精确匹配算一个字段级精度就行。这比 CER 直观得多人名字段精确匹配率如果是 90%就是每十次识别就有一次把名字搞错哪怕整体 CER 只有 2%。下面章节用的就是这种“字符层 实体层”的双轨评测思路专门针对 VLM 下的人名幻觉做系统性检验。2. VLM 转写文字时为什么会产生“合理的人名幻觉”2.1 VLM 做 OCR本质是“看图说话”要搞清楚 VLM 为什么会在人名上翻车得先接受一个反直觉的事实VLM 识别的不是“字”它是在“看图说话”。VLM 的架构通常是一个视觉编码器比如 ViT 系列加一个大语言模型。图像会被切成固定大小的 patch视觉编码器把每个 patch 变成一个视觉 token这些 token 再和文本 token 一起喂给自回归语言模型。也就是说“这张图片里的文字是什么”这个问题在 VLM 内部被转化成了“根据我看到的视觉 token 和已经生成的文本 token下一个 token 最可能是什么”。这和传统 OCR 完全不同。PaddleOCR、Tesseract 这类方案有明确的检测分支和识别分支检测模型先框出文字行识别模型再对每个文字行做逐字分类。每一步都在做“字符分类”输出和图像像素之间有很强的局部绑定关系。而 VLM 是“全局视觉 自回归解码”前面生成的 token 会影响后续 token语言内部的统计规律天然参与了“猜测”。它的确能读出上下文但也正因为能读出上下文它才敢在你没看清楚的笔画上替你“填空”。2.2 三类幻觉成因逐个拆开看在实际测试中我把 VLM 人名幻觉的成因归纳成三类。第一类是视觉证据不足。图像分辨率不够、文字行在页面边缘、笔画被印章压住、手写体潦草这些问题都会让视觉编码器提取不到足够精细的笔画特征。视觉 token 携带的信息一旦变模糊模型就只能靠语言概率来“填空”。这类情况最常见也最容易被业务方忽略因为“看着好像差不多”。第二类是语言先验抢过视觉证据。预训练语料里“张伟”“王芳”“李娜”这种高频人名的出现次数可能是生僻人名或特定机构人名的几百倍。当视觉信息和先验冲突时自回归模型天生倾向于输出概率更高的组合。这不是模型的 bug而是语言模型的本能——它太擅长“猜”了猜的词往往比图像里那个生僻字“更合理”。第三类是采样与解码的随机性。即便把 temperature 调到 0贪婪解码也不能保证唯一正确。生成过程是从词表概率分布里选 argmax如果视觉特征和语言先验胶着挨着最高概率的那个 token 可能和图像真相差之毫厘而选中它之后后续解码全部在错误轨道上继续“合理”推进错误就被语言模型自己自洽了。在真实样本里三类成因经常叠加出现。一张低清晰度、被印章压住人名的手写发票视觉证据不足 语言先验强 解码路径偏离三个条件凑齐幻觉率直接翻倍。2.3 为什么偏偏是名字、数字、术语最容易中招这些字段有共性短、信息密度高、缺乏自校验机制。数字还能靠身份证号校验位、金额长度和正则来发现人名几乎没有自校验——张三换成李四从字形和语法上都看不出来。机构名虽然长一点但 VLM 会把模糊部分补成自己见过的高频品牌词同样隐蔽。这也是我后来把评测重点放在人名、机构名、产品名这类“专有名词”上的原因它们才是幻想着实吃掉利润的部分。3. 动手设计一个“看得见幻觉”的评测方案3.1 数据集怎么建人名/实体金标的标注规范评测 VLM 幻觉最忌讳用那种“随便拍几张图肉眼看一遍”的方式。既然 CER 看不见幻觉那就要刻意设计一个能照出幻觉的数据集。我自己建这套评测时没有用通用 OCR 公开集而是专门为“人名幻觉”建了一组小测试集大约 500 张规则很简单覆盖票据、合同、材料证明类三种版式。每张图上的人名尽量选“常见但字形易混”的组合比如“王建国 / 王建华”“刘淑芬 / 刘淑华”“陈小雨 / 陈晓雨”这种成对的混淆名。金标必须人工逐字校对同时标注出字段级位置信息至少标出“人名”字段的起止字符。特意掺入低分辨率、模糊、印章遮挡样本比例控制在 20% 左右用来放大幻觉。为什么这么设计因为幻觉最喜欢出现在“视觉质量打折 语言先验强干扰”的场景。同一张图清晰状态下 VLM 可能表现得无可挑剔一旦模糊幻觉立刻冒头。如果评测集全是高清扫描件测出来的 CER 和幻觉率都会偏乐观反映不了真实业务场景。500 张不算多但对 VLM 幻觉这种问题足够。幻觉是系统性的不是偶发的几十张坏样本就能看出规律。关键是不能把“通用正确率”和“特定字段正确率”混在一起看否则又会掉进 CER 的陷阱。3.2 双指标CER 加一个 NRR评测脚本最后输出两类数值。第一个还是 CER计算方式跟前面一致。第二个我管它叫 NRRName Replacement Rate人名替换率统计逻辑是在所有包含人名的测试样本中模型输出的人名与金标人名不完全一致的样本占比。NRR 比 CER 更贴合同类项目的验收目标——人名每被换一次就是一次无法容忍的坏样本。即使 CER 只有 1.72%只要 NRR 达到 20%这个项目就该回炉重造。另外我还会额外统计一个“幻觉类型分布”把错误粗分成三类字形易混替换如“国”/“华”、同音近音替换如“建国”/“建华”、完全无关替换如“王建国”/“张建军”。这个分布对定位问题很管用字形易混替换多说明视觉编码器精度不够同音替换多多半是语言先验在抢戏完全无关替换多基本可以判断模型在瞎编。3.3 完整评测脚本直接可以跑脚本逻辑不复杂读一张图片调用 VLM API 取输出用 jiwer 算 CER再用正则或切片把输出里人名字段抓出来和金标比对最后统计 NRR。下面以 OpenAI 兼容格式的 Qwen-VL 在线 API 为例换 GLM-4V、GPT-4V 只需要改 model 和 base_url。import base64 import json import jiwer from openai import OpenAI # 评测清单每行 # {image: xxx.png, text_gt: 完整金标文本, name_gt: 王建国, name_span: [5, 7]} with open(eval_set.json, r) as f: eval_items json.load(f) client OpenAI( api_keyYOUR_API_KEY, base_urlhttps://dashscope.aliyuncs.com/compatible-mode/v1, ) def vlm_ocr(image_path: str) - str: with open(image_path, rb) as img: b64 base64.b64encode(img.read()).decode(utf-8) resp client.chat.completions.create( modelqwen-vl-max, temperature0, messages[ { role: user, content: [ { type: image_url, image_url: { url: fdata:image/png;base64,{b64} }, }, { type: text, text: ( 你是票据文字识别引擎。 请逐字、按行输出图片中的全部文字 不要纠正、不要补全、不要翻译。 看不清的地方用[未知]标记。 ), }, ], } ], ) return resp.choices[0].message.content results [] for item in eval_items: hyp vlm_ocr(item[image]) cer jiwer.cer(item[text_gt], hyp) # 简化的人名切片生产环境建议用结构化输出或NER定位 hyp_name hyp[item[name_span][0]:item[name_span][1]] name_match hyp_name item[name_gt] results.append({ image: item[image], cer: cer, name_gt: item[name_gt], name_hyp: hyp_name, name_match: name_match, }) total len(results) nrr sum(1 for r in results if not r[name_match]) / total avg_cer sum(r[cer] for r in results) / total print(f平均 CER: {avg_cer * 100:.2f}%) print(f人名替换率 NRR: {nrr * 100:.2f}%) for r in results: if not r[name_match]: print(f {r[image]}: 金标 {r[name_gt]} vs 输出 {r[name_hyp]})注意切片方式依赖输出文本与金标文本逐字对齐实际业务中更稳妥的做法是让 VLM 直接输出结构化 JSON把“甲方姓名”作为字段返回再做字段值精确匹配。我在后续自动化流程里就是这么干的结构化输出比文本后处理可靠得多。4. 实测同样一张图VLM 和传统 OCR 的差距在哪里4.1 测试环境和模型配置这个实验我用两组方案对比。一组是 Qwen-VL 在线 APItemperature 设为 0另一组是传统方案PaddleOCR 检测加识别模型不接任何大模型后处理。测试集就是上面说的 500 张图。每张图同时跑两条链路记录整体 CER 和人名替换率 NRR顺带留下所有坏样本的截图和原始输出。整个测试跑下来大约花了一天时间大头是 API 调用和人工复核坏案例。4.2 三类典型失败 case一个个拆光讲数字没意思我把几个最典型的坏 case 摆出来。case 1一张发票照片甲方处手写字“陈小雨”。VLM 输出“陈晓雨”传统 OCR 输出“陈小丽”。共同点是都错了区别在于 VLM 错在同音替换传统 OCR 错在字形混淆。从下游审核角度看VLM 的错误更隐蔽因为它输出了一个“很像人名”的人名。case 2一张合同扫描件正文全是印刷体非常清晰。人名“赵敏”被 VLM 换成了“赵慧”。这个 case 反常因为图像质量并不差。我反复调用了几次结果稳定输出“赵慧”。翻看上下文那句话是“甲方赵敏身份证号 3101...”语言模型很可能被“甲方加身份证号”的高频搭配带偏了。这个事实值得警惕即便图像清晰VLM 也可能基于上下文语境强改人名。case 3印章压字。发票角落的公司名称被印章遮挡VLM 直接把遮挡部分补成了印章里最常出现的几个字整句返回非常通顺。传统 OCR 在这种场景会输出一段乱码虽然难看但下游复核人员一眼就能发现异常VLM 补出来的错误反而“看着太正常”极容易被直接采用这才是最危险的地方。4.3 指标都在幻觉只属于 VLM500 张图的汇总结果非常清晰。VLM 平均 CER 确实低1.72% 左右传统 OCR 是 3.2% 上下。光看 CERVLM 赢了近一倍。但 NRR 一摆出来VLM 是 8%传统 OCR 只有 2%。模型平均 CER人名替换率 NRR主要错误类型Qwen-VLtemperature01.72%8%同音替换、语义补全传统 PaddleOCR 规则后处理3.2%2%字形混淆、漏字CER 是“平均到每个字符”的错误率十个名字全错摊到八千字的文档里也只是小波动。NRR 看的是“关键字段有没有翻车”一小块翻车就是业务事故。VLM 的错误特征决定了它对 CER 的“污染率”很低但每一个错误都长在信息最敏感的位置上。可以说VLM 把 OCR 的错误模式从“量多但可发现”变成了“量少但致命”。5. 从提示词到工程护栏压住幻觉的四个实用手段5.1 提示词和后采样参数成本最低的一步既然 VLM 幻觉有一部分来自语言先验抢戏第一步就在推理参数和提示词上对抗。temperature 直接设为 0top_p 设为 1避免随机采样带来的额外不稳定。然后重写系统提示词核心原则是强调“拒绝猜测”明确身份“你是文档文字转写引擎不是问答助手。”明确约束“遇到模糊、遮挡、无法辨认的字符输出[未知]不要自行补全。”明确目标“逐字转写不要纠正错别字、不要补齐固定句式。”提示词不能保证消灭幻觉但能把“脑补”的门槛抬高一点。实测同样的 500 张评测集加了这套提示词后 NRR 从 8% 降到 6% 左右CER 基本没变。说明一部分幻觉确实能在解码时被压制但剩下的 6% 属于视觉证据本身不足提示词无能为力。5.2 后处理白名单与词典校验能救很多条命针对人名幻觉我在后处理环节加了一层“领域白名单校验”。思路不新但非常有效凡是出现在“甲方 / 委托方 / 联系人”这类字段后面的文本必须与业务系统的客户名单、员工名单做一次模糊匹配。匹配不上的把该字段标记为“低置信待人工复核”宁肯让流程卡住也不放错下去。具体实现用了拼音加字形距离的混合比对。先把候选文本转成拼音与白名单里的人名拼音做相似度比对再用 RapidFuzz 的 ratio 做一次字符级比对。两条线任意一条给出高分就判定为“可疑但可能正确”强制人工复核两条线都低分才认为模型在瞎编。实测这个模块把 NRR 从 6% 压到 1% 以下代价是大约 5% 的正常样本会进入人工复核队列。对合同、票据这类场景5% 的复核比例完全能接受。如果是固定模板票据做法更简单按版式坐标把“单位名称”“姓名”等字段切出来再喂给规则校验器。百度、腾讯开源的 OCR 工具链里都有现成的字段抽取模板很多团队在这里已经有积累不必推倒重来。5.3 混合架构让 VLM 管版面让传统 OCR 管关键字段在正式项目里我最终落地的是混合方案。版面分析和阅读顺序理解用 VLM因为它对复杂排版的规划能力强但涉及人名、金额、证件号这类“死字段”我会把对应区域从原图裁剪出来单独走一条强约束识别链路传统 OCR 模型或专门的字符分类器加上字典约束解码不让语言模型参与“猜测”。这条链路的关键在于区域定位。VLM 输出版面结构后把“姓名”框的坐标抓出来。框内如果是手写体传统 OCR 不一定认得准我就再加一道“二次人工兜底”或者直接把手写人名输出为“无法自动识别走人工审核”。听起来像在倒退但业务上这是性价比最高的选择99 张自动识别加 1 张人工复核远好过 100 张全自动但其中 8 张人名全是错的。这里补一个容易被忽略的工程技巧把 VLM 的不确定性暴露出来。CLIP 类视觉编码器给不出真正的置信度但可以用一个代理指标——把输入图像随机裁剪、翻转后多次调用如果结果不停变化说明这个区域就是模型在“脑补”。我常用这个技巧提前筛掉低质量样本触发复核流程。5.4 什么时候不建议用 VLM 转 OCR最后说点泼冷水的话。VLM 转 OCR 不是万能的甚至不适合很多严肃场景。身份证、护照、行驶证这类强格式强校验的证件高并发、低延迟且结果需要可解释的传统业务敏感数据不能出内网的场景这些地方我都更推荐传统 OCR 加后处理。VLM 的“聪明”是双刃剑它输出的通顺文本极难被机器识破在关键字段上的错误成本远高于一个“虽然丑但能发现问题”的乱码。另外如果目标是“高精度字段抽取”而不是“整页转写”VLM 更应该放在理解层而不是识别层。换句话说别让 VLM 去当笔迹鉴定师让它去当审稿人——布局分析、阅读顺序、字段语义归类交给它像素级文字识别交给那些老老实实一个字符一个字符分类的传统模型。这是我做这个项目踩了一整圈坑之后最想说的结论。写到这里就收尾吧。我个人最深的体会是评估 OCR 系统质量别只盯着 CER 一个数字。CER 是很好的底盘指标但它是为传统 OCR 时代的错误模式设计的遇到 VLM 这种会读字更会想字的新物种指标必须跟着进化至少加一个字段级实体正确率或者干脆用人名替换率来验收核心业务字段。那次验收的教训其实很简单漂亮指标是会骗人的你得知道指标在哪里失明。希望这套评测方法和几个工程护栏能帮你在 VLM 转 OCR 的路上少踩几个我踩过的坑。