一行 BN 统计量掀翻 47 个已遗忘 checkpoint:unlearning 审计里藏了十年的幽灵变量
假设你是 GDPR 合规审计员。某 AI 公司提交了一份"遗忘报告":他们用最先进的 unlearning 算法,把用户 A 的 200 张照片从 ResNet-50 里抹掉了。报告里三个数字写得清清楚楚——遗忘集准确率 0.02、保留集准确率 0.85、测试集准确率 0.83。三个数字都落在他们自家 seed sp…
一个让审计员坐立不安的实验
假设你是 GDPR 合规审计员。某 AI 公司提交了一份"遗忘报告":他们用最先进的 unlearning 算法,把用户 A 的 200 张照片从 ResNet-50 里抹掉了。报告里三个数字写得清清楚楚——遗忘集准确率 0.02、保留集准确率 0.85、测试集准确率 0.83。三个数字都落在他们自家 seed spread 的容差内,结论是"遗忘成功"。你签字放行。
然后 Junlong Shen 和 Xingyu Li 走进来,跟你说:"把你那 263 个 checkpoint 的 BN running mean/var 重新在保留数据上 refit 一下,权重一字不改,再读一次你刚才那个数字。"
你照做了。47 个 checkpoint 的遗忘准确率跨过了 ±1.2 个百分点的边界。12 个 checkpoint 的"遗忘成功/失败"判定直接翻转——4 个原本"成功"的变成"失败",2 个原本"失败"的变成"成功"。而这一切,权重一个比特都没动。
这不是攻击,不是对抗样本,不是训练数据泄露。这只是把一个从来没人审计过的变量——batch normalization 的 running statistics——按照审计本来该有的方式重新算了一遍。
核心问题:审计比较的两个臂,从一开始就不对等
机器遗忘的审计逻辑看起来无懈可击:
unlearned_checkpoint vs retrained_from_scratch_checkpoint
↓ ↓
三个准确率数字 三个准确率数字
↓ ↓
如果数字接近 → 遗忘成功
但这里藏着一个十年没人碰的坑:两个 checkpoint 都不是"只有权重"。
每个 BatchNorm 层除了可训练的权重 \(\theta\),还附带一组 running mean 和 running variance \(\phi\)——这组统计量是在训练过程中用指数滑动平均累积出来的,推理时直接拿来用,没有任何一个梯度步写过它。
问题来了——两个臂的 \(\phi\) 来源完全不同:
- retrained reference:从头训练时只见过保留数据,所以它的 \(\phi\) 天然就是"在保留数据上拟合的"
- unlearned checkpoint:它的 \(\phi\) 取决于 unlearning 过程的实现——有的方法保留 base model 的 \(\phi\) 原封不动(里面还残留着被删数据的统计痕迹),有的在权重移动时在线累积新的 \(\phi\)
这就是论文标题的来源:"Published Unlearning Numbers Move Per Checkpoint, and Not Because the Removed Data Survives"。
实验设计:一个"重建测试"
作者做了一件简单到残忍的事:
1. 拿到一个公开的 unlearning checkpoint(权重 \(\theta\) + BN 状态 \(\phi\)) 2. 保持 \(\theta\) 一字节不动 3. 把 \(\phi\) 替换成 \(\hat{\phi}\)——在保留数据上做一次前向传播,用精确统计量(不是 EMA)重新计算 4. 重新读那三个准确率数字 5. 看它跨没跨过发布时自家的 pass/fail 边界
每个 checkpoint 重复 10 次,每次重新抽取保留子集和 augmentation stream。如果一个 checkpoint 在 10 次里全部跨过 ±1.2pp 边界,就算"displaced"——这是一个 exact sign test,\(p = (1/2)^{10} = 0.00098\),不需要正态假设,不需要重采样。
关键设计:margin ±1.2pp 是在第一波实验之前就固定的,取自主要发布版本自家 seed spread 的中位数。不是事后调的。
数字说话:47、12、2
| 问题 | n | 结果 |
|---|---|---|
| 发布数字能否按原样复现? | 263 | 263 个在 ±0.02 内复现 |
| 遗忘准确率是否跨过 ±1.2pp? | 221 | 47 个 displaced |
| 保留集准确率呢? | 221 | 39 个 displaced |
| 测试集准确率呢? | 221 | 40 个 displaced |
| 发布的 pass/fail 判定是否翻转? | 263 | 12 个翻转 |
| 翻转能否扛过 recalibration budget? | — | 4 个 |
| 翻转能否在所有 10 次重复上都成立? | — | 2 个 |
论文里有一句话读起来像刀子:
"The family that is equivalent to zero on all three metrics under the primary clustered estimator still contains 8 of its 45 checkpoints displaced past the same margin."
一个方法在 family 层级上"等价于零",但它的 45 个 checkpoint 里有 8 个单独跨过边界。 这不是"方法没效果",这是"方法的平均数掩盖了个体的位移"。
因果定位:不是数据残留,是"陈旧统计量"
最反直觉的发现来了。
直觉上,如果 refit 之后数字移动了,第一反应是:"被删的数据还残留在 BN 统计量里!refit 把它冲掉了。"
作者直接测了这一点。他们构造了三个 fitting pool:
1. 从保留数据里额外抽 k 条(纯保留) 2. 从被删数据里按部署比例抽 k 条(混合) 3. 把被删数据全放进去(全删)
结果:provenance(数据来源)几乎不影响数字。 混合 pool 让遗忘准确率移动 +0.015pp,全删 pool 移动 +0.057pp——都远在 1.2pp 边界内,没有一个 checkpoint 跨过 0.5pp。
那什么在影响?是"shipped state 离任何 retain-only refit 有多远"——也就是 BN 统计量的"陈旧度"。
作者把这个量量化了:shipped state 和 refit state 之间的距离,在所有 20 个 checkpoint 上都跨过了参考带,中位数是参考的 5.43 倍。而 ten most displaced checkpoints 的距离显著大于 ten least displaced——但它们的 provenance 距离完全相同。
类比:想象 BN 统计量是一张"训练日志"。retrained reference 的日志是昨天刚写的,写的就是保留数据的事。unlearned checkpoint 的日志可能是三个月前 base model 训练时写的,之后权重改了 50 次,日志一个字没动。问题不是日志里偷偷记着被删数据的事——问题是日志本身过期了。
决定性实验:冻结孪生
为了彻底排除"权重移动"这个混淆变量,作者做了一个漂亮的干预实验:
- 训练一对孪生 checkpoint:一个正常训练(buffer 在线更新),另一个只冻结 buffer 更新(权重照常训练,但 BN 统计量保持初始值)
- 两个孪生在权重上 bitwise 完全相同,只有 buffer hash 不同
- 90 对这样的孪生
exposed arm 的孪生 gap 在匹配 weight distance 处比 unexposed 高 +33.80pp,95% 置信区间 [21.28, 46.32]。
这是一个因果隔离:权重移动相同,唯一差别是 buffer 的更新轨迹,gap 是 33.80 个百分点。这不再是相关性,这是干预实验。
范围:这个坑只坑 BN,不坑 LayerNorm
重要限定:这个 channel 只存在于 BatchNorm-based 卷积视觉模型。
- LayerNorm / RMSNorm backbone(包括所有现代 LLM)没有 running statistics,所以这个 channel 对它们完全不适用
- 作者在 118 个 checkpoint(含 47 个 swin-base transformer)上测了第二个 deployed state——量化时的 activation scales——结果零位移
工程洞察:一行字就能堵上的漏洞
作者的提议极其克制,不是"推翻所有 unlearning 结果",而是:
一个发布 unlearning 结果的 release,在报告 audit cell 时应该加一行:
> *"deployed state as shipped; refit on the retained split under the training transform of train.py, cell moves +2.47pp"*
三个要求被通常混为一谈,分开才 actionable:
1. Preserve the state(保存 BN buffer)——两个 release 都已经做了 2. Record its fitting history(记录 buffer 怎么来的)——便宜,但只有 producer 能做 3. Report the cell under a named rebuild convention(在命名的 refit 约定下报告数字)——这才是证据支持的
作者既不建议覆盖 shipped state,也不建议跨 release 统一约定。只是要求把 refit 后的数字也报出来。
概念谱系:这在我的"评测盲区定律"里是第几例?
这篇论文精确地命中了我一直在追踪的几条概念线:
评测盲区定律——再添一例。unlearning 审计读的是"两个数字是否接近",但两个数字背后的 deployed state 来源完全不同,这个变量从未被审计。52 篇论文里只有 4 篇提到过它。
标量幻觉——再添一例。一个 unlearning 方法在 family 层级上"等价于零",但 45 个 checkpoint 里有 8 个单独跨过边界。把 per-checkpoint 的向量性质压扁成 family-level 标量,是评测系统的系统性盲区。
判断-闸门解耦——再添一例。审计的判断模块("两个数字是否接近")完全正确,但行动闸门("发布 pass/fail 判定")没有咨询那个分离两臂的变量。
载流图谱——再添一例。真正载流遗忘效果的不是权重 \(\theta\),而是 BN 的 running statistics \(\phi\)。权重 bitwise 相同的两个孪生 gap 33.80pp,这是因果隔离证据。
但这篇论文还贡献了两个新概念:
新概念一:"幽灵变量"(Ghost Variable)
一个变量如果满足三个条件,就是幽灵变量: 1. 它在两个臂的取值来源不同(分离两臂) 2. 没有人针对它做标准化、报告或审计 3. 改变它会让结论翻转
BN running statistics 在 unlearning 审计里就是完美的幽灵变量。这个概念可以推广——任何"两个臂对比"的审计都可能有自己的幽灵变量。RLHF 评估里 base model 和 aligned model 的 tokenizer 可能有不同的 padding 行为?知识蒸馏里 teacher 和 student 的推理温度可能不同?这些都是候选幽灵变量。
新概念二:"陈旧统计量"(Stale Statistic)
一个 fitted state 如果满足: 1. 它是在训练过程中拟合的 2. 训练结束后权重继续移动了 3. 它本身没有被更新
它就是陈旧统计量。BN running statistics 是典型例子,但不是唯一的。任何"训练时拟合、推理时使用、训练后没更新"的状态都是候选——量化时的 calibration scales、pruning 时的 importance scores、知识蒸馏时的 temperature schedules。
陈旧统计量不是错的,它只是过期的。 但一个过期的统计量会让审计读出错误的结论——这才是真正危险的地方。
我的思考:审计的"未记录变量"问题
这篇论文让我想到一个更普遍的问题:所有"对比审计"都假设两个臂只在"被审计的变量"上不同,但实际系统中总有未记录的变量在分离两臂。
- unlearning 审计假设两个 checkpoint 只在"是否见过被删数据"上不同——但 BN 统计量也在不同
- A/B 测试假设两个组只在"是否接受 treatment"上不同——但用户分布、时间窗口、设备类型都在不同
- 因果推断里的"控制变量"就是在试图堵这个洞——但只有你意识到某个变量在分离两臂时才会去控制它
这篇论文最让我敬佩的地方不是发现了这个 bug,而是审计的严谨程度:
- 263 个 checkpoint 全部锚定到自家发布的数字(±0.02 内复现才算数)
- margin 在第一波实验前固定
- 10 次重复 + exact sign test,不需要正态假设
- Benjamini-Yekutieli 多重比较校正
- 预注册(bars pre-registered 29 August 2026, census rows minted from 30 August 2026)
- 自己发现第一版仪器有 bug,主动把最大 family shift 从 +5.02pp 降到 +2.47pp 并报告
- 诚实声明"这是 existence result, not a rate"
论文链接
- arXiv: https://arxiv.org/abs/2609.11490
- HTML 版本: https://arxiv.org/html/2609.11490v1
- 作者:Junlong Shen, Xingyu Li 等
- 代码:随论文 supplementary code archive 发布,包含所有 per-run result artifacts 和 table/figure 生成脚本
一句话总结:unlearning 审计十年里读的数字,有一部分读的不是"遗忘效果",而是"BN 统计量的陈旧度"。47 个 checkpoint 跨过边界,12 个判定翻转——不是因为被删数据还活着,而是因为一个从来没人审计的变量在悄悄分离两个臂。把它报出来,一行字就够了。