先做一个隔离判断:同一张图,分别问“整体场景是什么”和“右下角那行小字写了什么”。如果整体描述稳定正确,换成小字就稳定出错,那么更可能是感知层没有看清,理解层未必有问题。此时直接换更大的模型,成本高,而且未必针对真正的原因。下面的做法把感知这一层单独测一次:用一小批可人工核对的题目、统一的判分口径和可复现的记录,先把“模型没看清”和“模型不聪明”分开。前提是你能拿到原图,能固定模型和请求参数,能保存每次回答;边界是这套测法只能说明感知层是否可疑,不能替代对业务整体效果的评估。
当模型漏掉小字、密集表格、低对比文字时,先做隔离测试再换模型:同一张图分别问整体和局部,确认错误集中在具体区域;用只问“图上写了什么”的最小题目集排除推理干扰;判分按统一规则,与基准中同类感知题对齐;记录逐题结果并重跑验证一致性。补图源、加预处理都无效,且错误集中在同一类视觉特征时,再考虑换模型。
先确认错的是细节而不是整体
这一步的目的是把问题限定在感知层。取一张真实出错的图,用两条提示词分别问,不要混在同一次对话里,避免上文影响回答。提示词可以这样写(把括号内的内容替换成你图上的实际内容):
# 整体问法
请描述这张图的整体内容:画面里有哪些主要对象、大致在做什么。
# 局部问法
只看画面(右下角/表格第二行第三列)这一小块,逐字写出上面的文字,不要解释含义。
对照两组回答时,不要只看“哪句错了”,要指出差异出现的具体位置:是整段文字被漏掉、个别字被替换成形近字、数字和小数点错位,还是行列对应关系错乱。形近字替换和数字错位通常指向分辨率或对比度不足;行列错乱更可能是布局解析的问题,属于感知与结构解析的交界处。如果整体问法也错,比如把主体对象认错,那么问题不止在细节,这时先别继续往下套用这套小题。
构造只考感知的最小题目集
目的是让测试不受推理难度干扰。每道题只问“图上写了什么”,答案必须能在原图上一字一字核对出来。建议选取三类图,每类先取几道,够用就行,不必凑数量:
- 小字图:字号明显偏小、缩放后笔画粘连的标签、票据小字、截图里的注释。
- 密集表格:多行多列、有无框线都行,重点是有单元格文字密集相邻。
- 低对比文字:浅灰字压在浅色底上、深色底上的暗色字、带水印或背景纹理的文字。
选题标准:文字在原图中真实存在且清晰可辨,答案唯一,出题位置可以明确指认(给出坐标或行列号)。排除标准:需要计算、需要外部知识、答案有歧义、以及人眼放大后也读不出来的图,因为人眼读不出就不能算模型答错。也不要把“请判断这张表说明了什么趋势”这类推理题放进这套小题,否则无法区分是看不清还是想不明白。
与基准的感知类题目对齐判分口径
目的是让自己的小题和基准结果能直接对照。做法是统一判分方式,而不是改题去迁就结果。判分采用二值判对错加错误类型标注:答对记 1,答错记 0,并记录错误属于漏字、错字、数字错位、行列错位中的哪一类。文字比对前先做归一化,保证两边规则一致:
def normalize(s: str) -> str:
# 统一全角半角、去首尾空白、合并连续空格、统一大小写
s = unicodedata.normalize("NFKC", s)
s = re.sub(r"\s+", "", s)
return s.strip().lower()
def judge(pred: str, gold: str) -> int:
return 1 if normalize(pred) == normalize(gold) else 0
如果基准里同一类感知题是人工判对错的,那么你的小题也用同一批人、同一份判分说明来判,不要一边用脚本一边用人判。基准的具体子集名称和题目数量,以你手上实际能跑的那份为准,用前先在本地确认它包含哪类感知题、答案格式是什么;不要凭印象写子集名。两边都归一化之后,逐题结果才能放在一起看。
跑一次并保存可复现的结果
目的是让结论能被别人复核。下面是一个通用命令骨架,脚本名、模型名和路径按你的环境替换,参数建议固定 temperature 并写进输出,否则重跑结果不一致时说不清是模型随机还是代码问题。
python run_perception.py \
`--cases` cases.jsonl \
`--model` <你的模型标识> \
`--temperature` 0 \
`--out` runs/$(date +%Y%m%d-%H%M%S).jsonl
# 复核:同一输入重跑一次,逐题比对
diff <(jq -r '.case_id + "\t" + (.verdict|tostring)' runs/a.jsonl) \
<(jq -r '.case_id + "\t" + (.verdict|tostring)' runs/b.jsonl)
每条记录建议包含这些字段:case_id、image_path、region(坐标或行列号)、question、model_answer、gold、verdict、error_type、model、temperature、run_id。同一输入重跑两次,如果 verdict 一致,说明流程本身可复现,可以直接拿结果做判断;如果两次不一致,先查是不是 temperature、图片预处理或缓存没固定,把不稳定的题单独标出来,不要用一次结果下结论。
结果记录表可以直接照这个结构填,逐题看,不要只看总数:
| case_id | 图片类型 | 位置 | gold | model_answer | verdict | error_type |
|---|---|---|---|---|---|---|
| c-001 | 小字 | 右下角标签 | 批号 A7 | 批号 A1 | 0 | 错字 |
| c-002 | 密集表格 | 第2行第3列 | 12.50 | 1250 | 0 | 数字错位 |
| c-003 | 低对比 | 页脚灰色说明 | 仅供内部使用 | (空) | 0 | 漏字 |
顺着结果决定补图源、加预处理还是换模型
判定口径按三条线走。整体答对、局部小字错,按感知问题处理;整体和小字都错,先修图源再看模型;小字能答对,但回答含义或计算错,那属于理解层,换模型的收益要重新算。确认是感知问题后,三种动作按成本从低到高依次试,每次都只重跑感知小题,看对应位置有没有改善:
- 补图源:重新导出原分辨率截图,而不是从压缩过的聊天记录里二次保存。验证方式是同一题集、同一模型只换图片,逐题对比同一 region 的 verdict;如果小字题从 0 变 1,说明之前是图源损耗。
- 加预处理:对出错区域做裁剪放大、提高对比度或灰度二值化,只对指定 region 生效。验证方式是同时看两类题:目标小题是否改善,其余整体题是否被破坏。若预处理后整体描述变差,说明裁剪掉了上下文,需要缩小裁剪范围。
- 换模型或换输入分辨率档位:只在补图源和预处理都无效、且错题集中在同一类视觉特征(比如全部是低对比)时才考虑。验证方式是用同一题集跑新模型,对比逐题差异,而不是看平均分,重点看原来错的那几道有没有针对性改善、原来对的有没有变错。
每一步改动都保留 run_id 和改动说明,这样下次再遇到小字答错时,可以直接翻记录判断是图的问题、前处理的问题,还是模型本身的能力边界,不必从头再猜一遍。