当无法人工验证 LLM 提取结果时,可通过「变换输入观察输出是否保持不变」来检测错误,无需 ground truth 标签即可发现异常。
如果你曾经用 LLM 对一堆文档提取某个字段——比如总价、日期、金额——你一定知道那个让人不安的地方:它会对每份文档返回一个答案,而且个个信心满满,偏偏有些是错的。你没法逐一手工核对,因为没有答案正是你选用模型的原因。于是那些错误的答案就这样悄悄流向了最终产品。
我花了两周研究这个问题,最终找到一个有效的办法,思路源自一个很老的想法:变形测试(metamorphic testing)。
诀窍在于:测试那些不应该改变的东西
通常你总能知道一些不应该改变答案的情况:
所以对系统跑两遍——一遍用原始输入,一遍用"理论上无关紧要"的变体——然后比较结果。如果两次输出不一致,系统刚刚自相矛盾了。这是一个 bug,在没有任何标签的情况下就被发现了。
from wobbly import check, Relation, unchanged
import random
def extract_total(receipt):
# your system under test — any input -> output callable
...
rng = random.Random(0)
reorder = Relation(
name="reorder lines => total unchanged",
transform=lambda r: {"lines": rng.sample(r["lines"], len(r["lines"]))},
assertion=unchanged(),
)
report = check(extract_total, receipt, [reorder], samples=20)
print(report.summary())
# BROKE (1 of 5 trials): expected 7.95 to be preserved, got 7.5
No correct total was ever supplied — yet we know one of those answers is wrong.
它真的能抓到问题吗?
我在 535 份真实扫描收据(ICDAR-SROIE)上跑了测试,分两个完全独立的阶段进行:先不看标签盲标有问题的收据,然后才打开标签来给这些标记打分。
在留出数据上,被标记的收据出问题的概率是未被标记的 2.7 倍——整个判断过程没有用任何标签。
诚实的一面
这是一种抽查,不是安全网。召回率很低(在这个数据集上约 8%)——大多数 bug 它会漏掉。但精确率很高(约 75%):它标记的内容大约每 4 个里就有 3 个是真正的错误。所以这是一种零成本、零标签暴露 silent bug 的方式——而不是替代真正的测试集。
一个容易出错的地方
一个检查只有在正确系统能通过它时才有价值。我第一个"调换行顺序"的变换把每行都打乱了——结果连正确的提取器也挂了,因为真实的提取器会把类似 TOTAL 这样的提示词和下一行的数值配对。72 个标记中有 68 个是糟糕变换的副产物,不是真正的 bug。修复方法是把独立的块(头部 / 明细 / 合计)作为整体来打乱。要让设计出的变换只有真正的缺陷才能让它失败。
试试看
小型 Python 库,无依赖,MIT 许可:
pip install wobbly
代码仓库:https://github.com/tarunagarwal1981/wobbly 完整实验(含犯过的错):https://medium.com/towards-artificial-intelligence/your-llm-extracted-10-000-numbers-which-ones-are-wrong-7a5d54050dd3
当没有 ground truth 时,你是怎么检验 LLM 输出的?真心好奇大家在用什么办法。