How To Do Ablation Experiments

延续 上一篇文章, 再说一说怎么科学的在 paper 里做 ablations.

Ablation / 控制变量

一组理想的 ablation 实验, 应当所有实验尽量使用一份代码实现, 和相同的实验 recipe, 这样才算是真的 ablation. 其中尤其不要忽视实现的重要性, 因为同一个 feature 在不同的实现里可能会有重要的区别. 例如, 一个 TensorFlow 跑的实验和一个 PyTorch 跑的实验就不能放到一组 ablation 里. 我的 Where Are Pixels? -- a Deep Learning Perspective 也说了很多底层实现细节对模型的影响.

反例: EfficientNet 和类似的不少文章设计了新的网络, 却没有跟已有网络结构的 ablations, 只有在不同 recipe 下的 system-level 结果. Revisiting ResNets: Improved Training and Scaling Strategies 一文就说, 其实 ResNet 在加强的 recipe 下仍然很 competitive, 而那些看似很厉害的新模型, 很大程度上受益于它们使用的 recipe. 这篇文章的开头和结尾写的很好, 摘抄一下:

Novel computer vision architectures monopolize the spotlight, but the impact of the model architecture is often conflated with simultaneous changes to training methodology and scaling strategies.

...

We hope our work encourages further scrutiny in maintaining consistent methodology for both proposed innovations and baselines alike.

开头吐槽只想 claim 大新闻; 结尾吐槽别人实验做的没有 scrutiny.

反例: FCOS 是一个 system-level 效果很好的 detector, 然而它并没有充分的 ablation 来说明它的效果 为什么好. ATSS 一文就把 FCOS 和 RetinaNet 之间的所有区别进行了 ablation, 发现 FCOS 的性能提升有不少得益于与其中心思想无关的改动.

变量的相关性

"控制变量" 并没有看上去的那么美好: 深度学习作为没有太多理论的科学, 不同变量之间常常存在潜在的, 未知的相关性. (其他缺乏理论的学科, 例如医学, 心理学, 社会学也有类似问题). 这种相关性会带来如下一些后果:

  • 相关性让 ablation 的结论更可疑. 虽然一个实验支持了 claim, 但是这个 claim 可能跟实验里被控制的变量相关, 那么也许换一组变量后, claim 就不再成立了. 对于深度学习 paper 的读者, 这也是一个常见的的 concern: ablation 证明了在这个 baseline 下你的方法有用, 可是换个 baseline 呢?

    要缓解这个 concern, 应当选择 常用的, 有代表性的 实验 recipe (包括 baseline, hyperparameter, evaluation protocol 等). 一个好的 baseline 并不需要是 SOTA, 但是需要是一个领域内大家公认具有代表性的结果. 如果实验并不是在一个读者熟悉的条件和设定下, 读者更容易怀疑 ablation 的结论是否换个 recipe 仍然通用, 是否有意或无意中选择了对 baseline 不利的设定, 是不是拿着锤子找钉子. 这些都会弱化结论的可信度. 标新立异的选择往往是需要 justify 的, 要说明 "这个锤子为什么适合这种钉子" (见最后一节).

    反例: 某 paper 发明了一个新的 layer, 然后: "在一个 (自己设计的) 30 层 ResNet 上做了实验, 实验设定和参数见附录".

    反例: 某 paper 发明了新的优化方法, 然后实验是卫星图像分类或医疗图像分类这种小众领域.

  • 相关性使得不同变量带来的效果常常不可叠加: A 变量和 B 变量可能各自能够将结果提高 1%, 但是合在一起也只能提高 1%.

    举个直观的例子: 假如某 paper 发明了一个新的 loss function 能够提高结果, 但也许这个 loss function 的主要原理是改变了 gradient magnitude. 这时候, 把旧的 loss function 的系数调一调也能得到一样的效果. 在这里, "loss function" 和它的系数就是两个相关的变量, 他们带来的效果是可以互相替代的. 如果研究者不注意, 写了这样一篇 loss function 的 paper, 被人发现跟调系数没区别, 那 paper 的价值就消失了.

    这是另一个我们要使用 常用 recipe 的重要原因: 一个常用的 recipe 往往是已经被 well-tuned, well-studied 的. 这意味着如果能在这个 recipe 上做出 improvement, 这个 improvement 没法通过简单的 tuning 得到. 这也会让结论更强. 即使在 ResNet 早已不是 SOTA 的今天, 我如果要做 CNN 结构相关的实验, 可能仍然会选择从 ResNet 出发.

  • 相关性导致新的方法需要改变 (而不是控制) 变量才有效. 例如, 很多新的模型可能需要找一个新的 learning rate. 这时候, "learning rate" 这个变量就没有控制. 在改变了这个变量的同时还要 convince 读者这个实验是有效的, 是需要做额外的工作的. 下一节会详细解释.

Recipe 的进化

前面说 ablations 要使用常用的 recipe. 但是, recipe 也要与时俱进: 一个曾经不常用的 trick 可能在未来会进化成大家都在用的标准 recipe, 一个新的方法可能需要一个新的 recipe. 如果每篇文章都严格 "控制变量", 只使用旧的 recipe, 领域可能会陷入 local optimum. 那么, recipe 的进化要如何发生呢?

假设 A, B, C, ... 是一些与 ablation 的主要 claim 没有紧密关联的 recipe (例如 hyperparameter / tricks. 为方便理解, 可以把它们当作几个不同的 learning rate), 且 baseline + A 是 baseline 的 "标准" recipe. 当我们在开发一个新的方法 "proposed method" 时, 也许会发现用 B 来做实验比 A 更好 (proposed + B > proposed + A). 这时, 作者可以展示下面这些实验:

  1. proposed + B > baseline + A. 这是不足以 claim proposed > baseline 的.

    当然, 作者也可以选择将 "B" claim 为 "proposed method" 的一部分 -- 但是这会弱化文章的价值, 因为它让 "proposed method" 更复杂了. 读者也会疑惑: 也许只有 B 就够了, "proposed method" 里剩下的部分也许价值不大.

  2. proposed + B > max(baseline + A, baseline + B) : 这样来 claim proposed > baseline, 读者一般是接受的.

    在此基础上, 读者会好奇 proposed + A 表现如何. 如果proposed + A < baseline + A, 则说明 proposed 依赖 B. 如果 B 是某个复杂的 trick 的话, 这种依赖也会降低 proposed 的价值.

    要注意到, 以上的结果无法排除下面这种可能性: 存在一个 C, 使得 max(proposed + B, proposed + C) < baseline + C. C 的存在会使得 baseline 看上去比 proposed 更好. 但是, 由于我们假设 baseline + A 已经是一个常用的标准 recipe 了, 如果存在这样的 C, 那 C 大概率是 nontrivial 的, 不太可能是简单的调参. 这也是为什么要尽量使用标准 recipe.

  3. 为了尽量降低 C 存在的可能, 在计算资源允许的情况下应当对 recipe 进行公平的搜索: 如果 proposed 使用的 hyperparameter B 是 grid search 找出来的, 那么也应对 baseline 的 hyperparameter 进行类似的 grid search, 看看是否能找到一个更好的 C.

    例子: DETR 的训练代价比主流 detection 模型都大得多 (100-500 epochs), 这点在技术上难以避免. 这个区别导致公平的实验不容易做, 因为主流模型 (Faster R-CNN) 还没有一个常用的, 训练这么长时间的 recipe. 据我了解, 作者们当时尝试了不少方法提高 Faster R-CNN 在这个训练长度下的性能, 尽量让 baseline 更强. 这是很负责任的做法.

Baseline 无法 reproduce

前面两节都提到了, 使用一个常用的, 有代表性的, 标准的 baseline 是很重要的. 这样的 baseline 在文中的结果应该至少与别人 paper 相同实验的结果接近. 如果 baseline 比别人差, 说明 baseline 里一定有某些因素与那个常用的 recipe 不同, 因此会弱化结论的可信度.

反例: 某 paper 提出了 ResNet 的小改动, 但是文中的 ResNet baseline 比 pytorch 官方样例显著的差. 在这个 baseline 上有 1% 的提升, 并不意味着在常用的 baseline 上能有提升: 因为如前文所说, 不同的因素常常是不可叠加的. 事实就是, 有许多方法只在弱的 baseline 上有效.

如果 baseline 确实无法 reproduce 怎么办? 这种处境很遗憾. 一个 research topic 如果没有大家公认的 reproducible 的代码和 baseline 设定, 就容易陷入乱象. 例如 A Metric Learning Reality Check, Deep Reinforcement Learning that Matters 都是在吐槽各自领域里的问题. 这正是为什么要做开源高质量 codebase.

锤子和钉子

前面说到, 实验设定一般使用 "常用, 标准" 的 recipe, 否则有拿着锤子找钉子的嫌疑. 而有的时候, 如果我们恰好要 claim "我的锤子适合特定的钉子", 那么巧妙的改变 recipe 也许会有更好的效果. 下面举几个正 / 反面例子.

正例: ResNet paper 多次 report 了 training error (Fig. 4, 6), 这也许会显得奇怪, 毕竟 training error 不是一个大家常用的 metric. 这是因为文章的大 claim 是关于 residual connection 对训练 / 优化有帮助, 而 deep plain network 难以优化. Training error 才是跟这个 claim 直接相关的 metric, validation error 变好只是 training error 变好的一个副产品.

正 / 反例: Optimizer 的根本目标是降低 training loss, 所以比较不同的 optimizer (例如 SGD/Adam) 的时候不能不看 training loss. 这篇 paper Sec. 5 就吐了这个槽: 有的 optimizer 跑出来的 validation error 更低就声称自己更好, 但是实际上发现它的 training loss 更高.

反例: 我 review 过的某 paper claim 一个方法能够提高模型的 capacity 或表达能力, 但是实验是拿 ResNet 在 Cifar10 上看 validation error. 虽然 validation error 是一个常见的 metric, 但是 ResNet 在 Cifar10 上严重 overfit (training error = 0), validation error 跟模型 capacity 没什么关系.

正例: detection 里有很多可用 metric, 如大小物体的 AP, 不同 IoU 的 AP, 等等. 当有合适的 justification 的时候 (例如模型设计上对大物体更友好), 比较其中某个特定的 metric 能够帮助文章的 claim. PointRend paper 里为了证明 "边界结果更准确" 这个 claim, 设计了一个新 metric: 拿 COCO 训练的模型在 LVIS 的高质量标注下算 AP. 这样得到的结果比使用标准的 metric 更有说服力.

正例: Mask R-CNN 的 Table 2 (d) 使用了一个很少见的 recipe: 基本没人用的 ResNet-Conv5 backbone. 这是为了证明关于 RoIAlign vs. RoIPool 的 claim: RoIPool 的 feature map 不对齐, stride 越大, 影响越大. 通过 Conv5 (stride=32) 上的实验更加强化了这个 claim. 当初之所以在 detectron2 里保留 Conv4 这些性能并不好的模型, 就是因为它们在许多实验中仍然有研究价值.

正例: 我的 Rethinking “Batch” in BatchNorm 实验很多, 里面做了对 BatchNorm 的各种魔改. 这些魔改里, 大多数的目的不是为了 propose 一种新方法, 而是通过改变 BatchNorm 的行为来验证某个 claim. 如何找一个好钉子, 设计一个实验来巧妙的突出 claim, 是一项技术活.

Comments