引言
不知道小伙伴使用pytorch时是否遇到了下面的情况:我们加载训练好的参数,然后输入经过网络,但是不经过梯度下降训练,也就是说模型的参数没有更改,这是我们仍然会发现我们输出的指标(例如准确率)下降。
现象
最近跑的一个程序,加载好预训练模型
checkpoint = load_checkpoint(args.resume)
model.load_state_dict(checkpoint['state_dict'])
随后我们加载好预训练模型,然后执行下面一段代码: 模型设为train模式,然后经过网络得到输出,此时我们并没有对输出的结果进行训练,参数没有变。然后模式设为eval模式,我们得到的最终评价指标res。
self.model.train()
input_dict = self._parse_data(inputs)
output_dict = self._forward(input_dict)
self.model.eval()
res = evaluator.evaluate(test_loader, dataset=test_dataset)
但是不同的res实验结果却不一样。
分析
经过查阅资料发现模型内部有一个”捣蛋鬼“。那就是Batch Normalization。Batch Normalization(简称为BN),中文翻译成批规范化,是在深度学习中普遍使用的一种技术,通常用于解决多层神经网络中间层的协方差偏移(Internal Covariate Shift)问题,类似于网络输入进行零均值化和方差归一化的操作,不过是在中间层的输入中操作而已,具体原理不累述了。
问题的关键: BN层的统计数据更新是在每一次训练阶段model.train()后的forward()方法中自动实现的,而不是在梯度计算与反向传播中更新optim.step()中完成。
所以说即使不梯度计算,整个模型还是由于BN的变化更新了。
拓展
BN其实坑还是不少的,还有两个值得小伙伴们注意的坑。
第一个是我们如果加载一个与训练好包括BN的模型迁移到其他任务,例如想着直接在物体检测框架的模型上添加mask分支,冻结detection参数,只训练mask相关的参数。那么如果不冻结BN的话,detection相关的性能指标一直在变!如何冻结呢?
正确打开方式如下:
def fix_bn(m):
classname = m.__class__.__name__
if classname.find('BatchNorm') != -1:
m.eval()
model = models.resnet50(pretrained=True)
model.cuda()
model.train()
model.apply(fix_bn)
第二个坑就是当使用网络进行推理的时候,发现每次更改测试集的batch size大小竟然会导致推理结果不同,甚至产生错误结果,后来发现在网络中定义了BN层,BN层在训练过程中,会将一个Batch的中的数据转变成正太分布,在推理过程中使用训练过程中的参数对数据进行处理,然而网络并不知道你是在训练还是测试阶段,因此,需要手动的加上:
model.train()
model.eval()