诚实性审计
为什么需要这一章
本项目上一代的图级分类模型拿到 AUROC = 1.0。 拿 30 张目检干净的隔离图去试,**全部被打 0.999+**。
它学到的不是缺陷,是「这张图来自哪个压缩包」——一代数据里良品 100% 来自 good.rar、
不良 100% 来自其他包,类别与批次完全绑定。模型只要认出批次签名就赢了。
由此得出本项目的核心教训:
监督粒度决定诚实度。
同一批数据、同一个骨干,只改监督信号的粒度,隔离集误报率(quarantine_over_thr0)单调下降:
| 监督形式 | 隔离集误报 |
|---|---|
| 图级 BCE | 1.00 |
| 特征判别器 | 0.92 |
| DINO 线性探针 | 0.85 |
| 双头 cls 分支 | 0.31 |
| 判决头(含梯度隔离) | 0.23 |
| 像素 top-k / 检测框 | 0.00 |
监督越细,越没有空间去学捷径。
所以二代数据上分类模型 AUROC 0.99+ 这件事,必须先证明它不是同一个骗局。 下面四道审计就是干这个的。
前提条件:捷径是否还存在
二代数据的关键改变:批次内良品与不良品混合采集,类别不再与批次绑定。
但「不绑定」不等于「无信息」。先量一下作弊空间还有多大:
python cls_probe.py
**批次指纹强度 = 92.7%**(frozen_probe_*.json 里的 pkg_acc)。
拿模型的冻结特征去预测「这张图属于 11 个包中的哪一个」,准确率 92.7%。 图像里确实带着强烈的批次签名(光照、白平衡、镜头状态逐包不同)。
这一条不是失败项,恰恰相反:它证明作弊是可行的。 如果批次不可分,后面三道审计就没有意义了——通过也说明不了什么。 现在的问题变成:模型有这个机会,它用了没有?
审计一:未见类硬正样本
做法:「碎」这一类(64 张)整类从未参与训练,模型从没见过。 如果模型学的是「缺陷长什么样」,它应该能抓到;如果学的是「批次签名」,它没理由认识。
结果(cls_test_all.csv 的 sui 列):
| 模型 | 未见类「碎」检出 |
|---|---|
bin_mobilenetv3_large_100_mid_320 (A) |
100.0% |
bin_resnet34_mid_320 (A) |
100.0% |
bin_efficientnet_b0_mid_320 (A) |
98.4% |
bin_resnet34_mid_320 (B) |
98.4% |
cls4_resnet34_mid_320 (B) |
98.4% |
cls4_resnet34_mid_320 (A) |
92.2% |
通过。 全榜 40 个可用模型的 sui 中位数 > 0.95。
模型抓的是「这里有东西不对」的通用特征,不是记住的类别模板。
审计二:跨批次泛化(B 套切分)
做法:把 样本_38016_P08262014 整包扣出来当 test。
训练集里完全没有这个批次的任何图(p10_honest.json 里 pkg_overlap = 0)。
如果模型靠批次签名,遇到全新批次应该崩掉。
结果:
| A 套(同批次可见) | B 套(批次全新) | 差值 | |
|---|---|---|---|
bin_resnet34_mid_320 test AUROC |
0.9911 | 0.9862 | −0.0049 |
cls4_resnet34_mid_320 test AUROC |
0.9867 | 0.9825 | −0.0042 |
通过。 掉 0.005,和 AUROC 的标准误(test_se ≈ 0.005)同量级——统计上不可区分。
另外 B 套的 eta2 = 0.000:包身份完全解释不了良品分数的方差。
A 套是 0.05–0.06,也很低。
审计三:冻结特征对照(决定性)
前两道审计有个共同弱点:它们能证明「模型没有完全依赖捷径」, 但证明不了「模型没有部分利用捷径」。第三道补上这个。
做法:冻结骨干,只训探针,比两种:
| 探针 | 用了标签吗 | test AUROC |
|---|---|---|
| 线性探针 | 用了良/不良标签 | 0.9461 |
| 只见良品的 kNN | 完全没用标签 | 0.9634 |
判读逻辑:
如果特征空间里存在一条「批次 → 类别」的捷径,那么给探针看标签,它必然能顺着标签把捷径挖出来, 表现应该显著优于一个压根不知道标签存在的 kNN。
实测反过来:不用标签的 kNN 赢了 0.017。
通过,而且这是最强的一条证据。 拿到标签没有带来任何优势, 说明特征空间里没有可被标签解锁的捷径——良品自己就构成一个紧致的簇, 偏离这个簇的就是缺陷,这是纯粹的异常检测结构。
未见类「碎」上同样:kNN 0.9375 vs 探针 0.7969。没用标签的反而更能抓未见缺陷。
审计四:良品分数分布
做法:看 185 张 test 良品被打了多少分。
结果:169 张分数恰好为 0,只有 16 张非零。
这 16 张逐张目检的结果在 被模型报警的良品.html 里。
它们不是随机误报——多数确实有肉眼可见的异常(漏标的真缺陷、边缘毛刺、附着物)。
这也是「良品标签本身约 3.3% 存疑」这个结论的来源之一。
汇总
| 审计 | 结果 | 判读 |
|---|---|---|
| 批次指纹强度 | 92.7% | 作弊有空间 |
| 未见类「碎」检出 | 92–100% | 学的是通用缺陷特征 ✓ |
| 跨批次泛化 | −0.005 AUROC | 噪声量级,没退化 ✓ |
| 冻结特征对照 | kNN 0.9634 > 探针 0.9461 | 没有可解锁的捷径 ✓✓ |
| 良品分数分布 | 169/185 恰好为 0 | 非零的那些多是真缺陷 ✓ |
四道全过。二代数据上的分类结果是可信的。
但仍需谨慎的地方
审计通过 ≠ 可以直接上产线。三条限制:
审计只覆盖了本数据集的 11 个包。 换产线、换光源、换相机是分布外,审计结论不自动延续。
gwz归一化的抗漂移能力没验证过。 我曾推测灰世界白平衡能扛住色调漂移 (B 通道逐日 15→8→5),但 test 上imnet归一化反而拿了最高 AUROC(0.9976 vsgwz0.9918)。 该推测已撤回。 要证明需要专门的色调漂移压力测试,本项目没做。样本量限制无法用审计绕过。 见
CAVEATS.md。
如果你要在新数据上重做这套审计
最小可行版本,按性价比排序:
- 先跑冻结特征对照(
cls_frozen_probe.py)——一个脚本,最强证据,几分钟 - 留一个批次当 test(B 套那样)——需要重训,但直接回答「换批次灵不灵」
- 找一类不参与训练的缺陷当硬正样本——几乎零成本,只要你有一类可以牺牲
- 看良品分数分布——零成本,且顺带帮你找出漏标
第 1 条最值。如果只做一条,做它。