filter-inspection / docs /AUDIT.md
daipath's picture
加入完整文档集:复现指南、逐文件说明、结果汇总、诚实性审计、已知边界
0e9808f verified
|
Raw
History Blame Contribute Delete
6.7 kB
# 诚实性审计
## 为什么需要这一章
本项目上一代的图级分类模型拿到 **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+ 这件事,必须先证明它不是同一个骗局。**
下面四道审计就是干这个的。
---
## 前提条件:捷径是否还存在
二代数据的关键改变:**批次内良品与不良品混合采集**,类别不再与批次绑定。
但「不绑定」不等于「无信息」。先量一下作弊空间还有多大:
```bash
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 | 非零的那些多是真缺陷 ✓ |
**四道全过。二代数据上的分类结果是可信的。**
---
## 但仍需谨慎的地方
审计通过 ≠ 可以直接上产线。三条限制:
1. **审计只覆盖了本数据集的 11 个包。** 换产线、换光源、换相机是分布外,审计结论不自动延续。
2. **`gwz` 归一化的抗漂移能力没验证过。** 我曾推测灰世界白平衡能扛住色调漂移
(B 通道逐日 15→8→5),但 test 上 `imnet` 归一化反而拿了最高 AUROC(0.9976 vs `gwz` 0.9918)。
**该推测已撤回。** 要证明需要专门的色调漂移压力测试,本项目没做。
3. **样本量限制无法用审计绕过。** 见 [`CAVEATS.md`](CAVEATS.md)。
---
## 如果你要在新数据上重做这套审计
最小可行版本,按性价比排序:
1. **先跑冻结特征对照**`cls_frozen_probe.py`)——一个脚本,最强证据,几分钟
2. **留一个批次当 test**(B 套那样)——需要重训,但直接回答「换批次灵不灵」
3. **找一类不参与训练的缺陷当硬正样本**——几乎零成本,只要你有一类可以牺牲
4. **看良品分数分布**——零成本,且顺带帮你找出漏标
第 1 条最值。**如果只做一条,做它。**