Keras 2.1.3+ 的一个改变带来的问题

今天被一个奇怪的事情折腾了一天:课程作业无法得到应有的结果,本应得到高于90%的准确率的模型,训练出来只有40%。
幸好在讨论区找到了答案:Keras在2.1.3版后,对BatchNormalization这个层的功能做了修改。但是这个修改并没有体现在Keras自己的文档中, 只在GitHub的问题区有零星的讨论。

不少人对这个改变有不同的看法,主要两种观点及其验证可以见以下三篇文章:1 2 3

总结来说,其实在2.1.2及之前,BN这个层是有bug的,这个bug就是:如果该层被设为trainable=False,实际在训练中,这个层还是会更新mean和variance的值,并且这些参数会更新到测试阶段使用。

到了2.1.3及以后,这个bug被消灭了,但是对很多做transfer learning的人来说,却有新的问题出现了。因为做transfer learning时,一般会导入Inception、ResNet等已经训练好的模型作为底层模型。这些底层模型的训练集和我们用来训练的数据肯定是有区别的,因此会导致BN这一层的mean和variance不正确。

如果是训练和测试的时候都一样错就罢了,起码模型的结果是一致的。Keras另一个参数trainning的默认值决定了模型在训练的时候,是以mini-batch的mean和variance数据来训练的,但并不会更新到模型中,因此测试用的mean和variance仍然是导入的模型的原始数据。

上面这种设计带来的结果就是,在某些情况下,同样的模型同样的数据,训练和测试的loss或者acc会差很远。因为是同样的数据,所以根本不是over-fitting的问题(虽然看起来很像)。这也正是我遇到的问题。

针对这个问题,解决办法有两个思路:

  1. 彻底锁死BN层的参数,即使训练阶段也不更新,可以通过设置training=False来实现。但是我初步测试了一下这个方法的训练效果不好。
  2. 把BN层设为可训练。我初步测试了一下跟2.1.2版的训练效果差不多。