[控场AI]
· 13 分钟阅读· 6,714 字

一张调参两张验证:Scribe手写表单识别的诚实调优实验

一张调参两张验证:Scribe手写表单识别的诚实调优实验

一包调参两包检查,修正评分器与静默bug后,完全准确率升至96%。

作者将 Scribe 手写表单识别流水线迁移到一份 14 页、177 个答案的问卷包上,采用一包调参、两包检查的配对实验设计,并规定每次改动必须同时提升调参包且在检查包上成立。实验首先发现最初的“提升”只是评分器把 [] 与空白当作不同答案的约定差异,修正后提升消失。随后通过调试视图定位了两个静默 bug:页面从未被裁剪、勾选网格读错一列。最终在三包 531 个答案上,完全准确率从 93.2% 升到 96.2%,逃过审核的错误从 11 个降到 1 至 2 个,审核负担从 18% 升到约 22%,每包处理时间从 70 分钟降到 37 分钟。作者明确列出三包样本量小、检查包已被三次决策污染等局限,强调真正的留出评估仍待完成。

一张调参两张验证:Scribe手写表单识别的诚实调优实验

在上一篇文章中,我们看到了 Scribe 流水线在从未见过的手写体上识别准确率骤降,而审核闸门依然稳健。这一篇要回答一个更前置的问题:在一份我们可以用来调参的长表单上,调优能走多远?我们又怎么判断这些提升是真实的?

作者把 Scribe 迁移到了一份 14 页的问卷包上,每包 177 个答案,页面上布满勾选网格、是/否下划线、计时任务和测试员手写分数。实验设计很克制:用一包数据调参,另外两包留作检查包。

在三包共 531 个已检查答案上,最终配置的结果是:

  • 96% 的答案完全读对,从 93% 提升而来(仅看两个检查包,从 94.4% 到 96.0%)。
  • 免审核答案中 99.8% 正确,从 97.5% 提升而来。
  • 只有 1 个错误答案逃过了审核,从 11 个降下来(其中 9 个原本出在调参包上)。
  • 约 22% 的答案需要进入审核,从 18% 上升:第二个模型增加了审核负担。
  • 每包处理时间约 47 分钟,从加入第二个模型时的约 70 分钟降下来。后续前缀缓存将其降到约 37 分钟,但多漏掉了一个答案。

这些数字都不计入第一部分设定的目标,因为调参用了其中一包,检查包也在过程中影响了部分决策。

rss source: One Packet to Tune, Two to Check: Tuning Scribe Without Fooling Myself

实验设置:一包调参,两包验证

页面的顺序固定:问卷、一页由测试员评分、若干页计时任务和数字字段。作者为整个包构建了一个 schema:177 个答案字段、89 个关联答案之间的检查,以及一条规则——页面在完整包中的位置决定它是哪一页。

手头有三份填好的副本,从未公开过,所以被测模型不可能在训练中见过这些填写页面(空白表单本身可能是公开的)。

答案键的生成流程是:先由两个独立的 AI 分别读取每页,再由一个裁决过程合并,最后两人(其中一位是作者)逐值审核。第一轮审核改了 603 个值中的 2 个,两位审核者一致率 99.2%(598/603),五个分歧由作者裁定。人工检查预填草稿时可能漏掉草稿本身漏掉的东西,所以答案键是“非常好”而非“完美”。后来因为字段调整,答案数缩减到 531 个。

规则是在第一次实验已经跑完三包之后才定下的:

  • 一包用于调参(基线最难的那个:86% 完全正确,另一包有 94%)
  • 两包留作检查,不进入调参循环,仅用于确认改动
  • 每次比较都成对进行:每个答案在两次运行中或对或错,只有发生变化的答案才计入统计

第四条规则是后来加的:一个改动要保留,必须同时提升调参包并在检查包上成立。

先修正评分器,再相信提升

第一次实验比较了带和不带“阅读技能”的提示词——阅读技能是一些与表单无关的笔记,说明笔迹如何工作(下划线、划掉、手写数字、空框)。在第一个评分包上,带技能的版本看起来好了 1.5 个百分点。

但事实并非如此。模型用 [] 表示“没有勾选”,而答案键用的是空白。两者含义相同,但评分器把它们算作不同,正是这一约定制造了全部“提升”。公平评分后,带技能的版本在该包上反而落后半分,三包合计两个版本相差不超过两个答案。

现在作者会同时检查两种评分方式,并以公平的那种为准;本文所有数字都使用公平评分。这从另一个方向印证了第二部分的教训:在相信提升之前,先确认你的评分器没有变。

McNemar 检验是一种用于配对二分类数据的统计检验,适合本文的实验设计:同一批答案在调参前后各被判定一次,只有判定结果发生翻转的答案才携带信息。它把“由对变错”和“由错变对”的答案数量做成 2×2 列联表,检验两个方向的变化是否对称。相比直接比较两个准确率百分比,McNemar 检验的优势在于它控制了题目本身难度的差异——同一道题在两次运行中的表现天然相关,忽略这种配对结构会高估显著性。本文报告的 p = 0.00006(三包合计 15 修复、0 破坏)和 p = 0.03(仅检查包 6 修复、0 破坏)都来自这一检验。需要注意的是,当同一个修复一次性翻转同一页上的多个答案时,这些答案并不独立,p 值会偏乐观,这也是作者提醒“数字应粗略看待”的原因之一。

看看模型实际看到了什么

准确率数字只能告诉我哪些答案错了,不能告诉我为什么错。于是作者构建了一个调试视图:对每个错误答案,同时显示模型读取的图像片段和答案在页面上的实际位置。

它发现了两个指标无法解释的 bug:

  • 有一个页面从未被裁剪。 查找每个答案框的步骤要求在一次回复中列出页面上所有框。在测试员评分页上,回复触发了长度限制,截断的 JSON 解析失败,导致本该裁剪的 22 个字段全部静默回退到整页。修复方法:每十个一批地请求,并保留截断回复中所有完整的框。
  • 一个网格读错了一列。 勾选网格的每一行在三个列标题之一下勾选。裁剪单行会移除标题,模型数错列,却以十足的信心给出错误答案。修复方法:从整页读取这些行。

第三种模式——在计时任务页的空白行上编造“是”答案——最终发现是裁剪时捕捉到了上一行的圆圈。同样的修复。

仅在调参包上,三轮改动将完全准确率从 85.6% 提升到 89.1%,已接受答案准确率从 90.6% 提升到 98.6%。并非全部来自更好的阅读:中间一轮还把测试员评分页上的所有勾选直接送审,将最差的答案从已接受池中移除,并向阅读技能和 26 个是/否提示中加入了作者在这些表单上注意到的下划线规则(下划线从哪个词开始,就选哪个词)。

那个看起来没问题、实际有问题的改动

框查找修好后,测试员评分页终于被正确裁剪。在调参包上,这一改动毫无变化(前后都是 64%)。作者差点就保留了它。

但在两个检查包上,该页准确率从 94% 暴跌到 86%。裁剪只显示一行勾选的一部分,而旧的 bug 因为从不裁剪,反而隐藏了这个问题。

作者回滚了这个改动,也正是在这时,“必须在检查包上成立”从最终的形式步骤变成了硬性规则。这也意味着检查包现在已经影响了一个决策;后文会详细说明。

只读你真正用到的字段

测试员评分页每个项目有一个勾选,每个部分有一个手写分数。勾选是问题所在:开始时 66 个中错 15 个,一旦把所有勾选送审,每包多出 22 个审核项(约占该页答案的 11%)。手写分数在三包中只错了一个,而且它们是下游实际使用的值。

scored assessment ticks vs scores

一张为本文绘制的合成页面,项目为虚构。橙色框标出逐项勾选(包括一个越过边框、一个被划掉、一个很淡),绿色框标出手写部分分数和总分。

所以 schema 现在只读手写分数,丢弃勾选字段。这改变了任务本身,而不是改进阅读:该页最差的字段退出评分,审核负担每包减少 22 项,每包少 24 次模型调用。为了保持比较诚实,摘要和此后所有比较都在同一个缩减后的 531 个答案集上评分;此前的数字使用原始的 603 个。

加上目前所有修复、尚未加入第二个模型时,三包完全准确率从 93.2% 升到 95.3%(修复 12 个,破坏 1 个),逃过审核的错误答案从 11 降到 4。仅看检查包,提升更小且不显著:94.4% 到 95.5%(修复 5 个,破坏 1 个)。

再次审视第二个模型

第一部分最好的想法是用另一个不同的模型重读每个答案。在这里重新加入后,它让一个更少的错误答案通过(4 到 3),但审核负担从 17% 跳到 27%。

查看其分歧,92% 是误报(60 个中 55 个),其中 42 个是主模型从裁剪图读取、而第二个模型从整页重读的答案。在密集页面上,小模型经常找不到字段。

作者一次尝试一个修复:

  • 也重新检查空白答案。 放弃。调参包审核负担从 27% 升到 36%,每个新标记都是误报:小模型在空行上“看到”了答案。
  • 重读主模型读过的同一张图。 保留。调参包误报从 16 降到 10,没有改变任何答案。

所以,当第二眼是对同一事物的公平重看时,它有用;而用来确认某处为空时,它远没有那么有用。它在这个表单上是否值得保留仍是开放问题:与不含它的调优流水线相比,最终设置让 1 个错误答案通过而不是 4 个,审核负担从 17% 升到 22%。三个答案的差异比没有多,但还不足以证明。

更快,但不能让答案变差

一个包 70 分钟太慢了。作者对速度工作的条件是:任何改动都不能让答案变差,通过成对运行逐答案检查。

超过一半的时间花在读取提示词而不是写回复上:每次整页读取发送约 5,100 个 token,其中约 3,650 个是页面图像。

改动节省时间答案
一页同时读 4 个答案,页面顺序已确定时跳过页面类型检查18%(调参包)177 个中 176 个相同
同时将每个答案后的短笔记限制在 60 字符总计 24%(调参包)2 个改变,都变好
在该页所有整页问题间复用处理后的页面图像(前缀缓存)每包再省 22%20 个错而不是 21 个,但逃过审核 2 个而不是 1 个(第二个模型的运行间噪声)

包处理时间从约 70 分钟降到约 47 分钟,再降到约 37 分钟。

尝试后放弃的:

  • 一次 8 个答案而不是 4 个: 快 1%。服务器已经饱和。
  • 去掉阅读技能: 快 6-11%,但检查包上多一个错误答案逃过审核。一个答案在噪声范围内,但原因很具体:一个是/否问题自身的提示没有提到下划线规则,只从技能中获得。作者保留技能是安全考虑,不是测量到的收益,而修复那个提示现在会变成在检查包上发现的修复。
  • 推测解码(一个小“起草者”提出 token,主模型一步检查):在合成测试图像上单次请求跑到 20-23 token/秒而不是 15,但在流水线中慢了 6.6%。在服务库中,运行中的推测批次在接受新请求前会等待最长回复结束,而且加载起草者后,库拒绝 Scribe 依赖的结构化 JSON 输出。过程中作者发现了库中的一个批处理 bug 并上报上游。

前缀缓存(prefix caching) 指的是在多次请求之间复用相同前缀的计算结果。在本文的流水线里,同一页面上会发起多个整页级问题,每次请求的提示词开头都是同一张处理后的页面图像(约 3,650 个 token)。如果不做缓存,模型需要对这张图反复做视觉编码;启用前缀缓存后,服务端把这段前缀的键值缓存保留下来,后续请求直接复用,只计算新增的文本部分。这就是为什么它能在答题结果几乎不变的前提下再省约 22% 的时间。

推测解码(speculative decoding) 则是另一种加速思路:用一个小而快的“起草者”模型先连续猜出若干 token,再由主模型一次性并行验证这些猜测,接受正确部分、拒绝错误部分。理论上的收益来自把主模型的多步串行生成变成一次并行校验。但它对批处理场景很敏感——当服务端同时处理多个请求时,推测批次往往要等待其中最长的回复结束才能接纳新请求,吞吐反而下降,本文测到的 -6.6% 正是这种情况。它还与结构化 JSON 输出约束存在兼容性问题,这是它在本流水线中不可用的直接原因。

漏网的两个错误

第二部分结尾审计了每个跳过审核的答案,这里是当前设置下的两个。

  • 一个多了一个字母的自由文本答案。 模型给两词答案中的一个词加了复数“s”。意思相同,但精确评分算错,第二个模型也犯了同样的误读,所以没有任何标记。
  • 一个是/否下划线同时划过了两个词。 线从“Yes”下开始,一直延续到“No”下。按阅读技能中的规则,线从哪开始决定答案,所以是 Yes。模型根据线在哪里结束,高置信度地读成了 No。没有缓存时,第二个模型不同意,答案进入审核;有缓存时它同意了,于是漏了过去。

underline spans both

第二个案例的合成重绘:虚构问题和生成的笔迹,为本文绘制。

所以一个表面错误和一个真实翻转,发生在一个人也会犹豫的笔迹上。作者接下来要测试的修复(在新包上而非这些包上):任何横跨两个选项的下划线直接送审。

这个实验不能证明什么

  • 只有三包。 531 个答案足以看到大效应,不足以看到小效应。第二个模型在批处理下每次运行不会给出完全相同的答案,所以本文中任何一两个答案的差异都应视为噪声。
  • 检查包不干净。 schema 和答案键是用三包共同构建的,第一次实验在三包上评分,后续三个决策使用了检查包结果:回滚裁剪、只读手写分数(其背后的勾选错误计数覆盖三包)、保留阅读技能。
  • 写手太少。 三包无法显示在许多写手和测试员之间的表现。
  • 这不是留出评估。 第一部分设定的目标(已接受答案至少 98% 准确率,最多 2% 编造答案逃过审核)只能在私有留出集上测量。这些包不合格,而未调优的基线在检查包上已经高于 98%,所以这更多说明了表单本身,而不是调优。

调参集与留出集(held-out set)的区别 是理解本文结论边界的关键。调参集是开发者可以看到结果并据此修改系统的数据;留出集则在整个开发过程中完全不可见,只在最后评估时使用一次。两者之间的差距就是“乐观偏差”:任何在调参集上做出的选择——哪怕只是“这个改动看起来中性,保留吧”——都会让调参集上的数字系统性地好于真实水平。本文采用一包调参、两包检查的设计,已经比单纯在全部数据上调参严格得多,但检查包仍有三次影响了决策(回滚裁剪、只读手写分数、保留阅读技能),因此检查包也不再是干净的留出集。作者反复强调“这些包不合格”,正是因为第一部分设定的 98% 目标必须在从未参与任何决策的私有留出集上测量,否则测到的只是这套表单加这几个写手的局部表现。

关键收获

  • 调参/检查分离值得做,即使有泄漏。 那个有害改动在调参包上看起来中性。
  • 在相信数字之前,检查评分器,并看看模型实际看到了什么。 第一次“提升”是评分约定;两个最严重的 bug 在作者看到裁剪图之前一直静默。
  • 只读你真正用到的字段,并在同一集合上评分前后。
  • 第二个模型应该获得公平的第二眼,而不是更难的问题。
  • 速度工作不能使答案变差,而且加速只在你实际运行的批处理下才算数。

下一步

如第二部分所计划:私有留出评估——schema 从未见过的新包,由多人填写。第一部分的复选框检测阶段仍在清单上。


技术细节: 同一台机器,主读取器 Qwen3.8-27B(8-bit),第二个模型 Qwen3-VL-8B(8-bit),本地用 mlx-vlm 0.6.15 服务。三包 531 个答案(一包调参,两包检查),公平评分([] = 空白)。完整数据表显示:基线完全准确率 93.2%、已接受准确率 97.5%、逃过审核 11 个、标记率 18.1%;最终配置(含前缀缓存)完全准确率 96.2%、已接受 99.5%、逃过 2 个、标记率约 22%、每包约 37 分钟。成对 McNemar 检验显示调优后的粗体行相对基线修复 15 个、破坏 0 个,p = 0.00006(仅检查包 6/0,p = 0.03)。作者提醒这些数字应粗略看待,因为包含调参包且一个修复常翻转同一页的多个答案。

#手写表单识别#视觉语言模型#模型调优#Scribe#准确率评估#审核闸门#前缀缓存#推测解码
分享:

相关推荐