torch.nn.BatchNorm2d



因为批量标准化是在 c 维上完成的,即在(n,h,w)切片上计算统计信息,所以通常称之为空间批量标准化。
image.png
Pytorch中的BN层的动量平滑和常见的动量法计算方式是相反的,默认的momentum=0.1

函数参数讲解:

1.num_features:一般输入参数为batch_size,num_features,height,width,即为其中特征的数量,即为输入BN层的通道数;
2.eps:分母中添加的一个值,目的是为了计算的稳定性,默认为:1e-5,避免分母为0;
3.momentum:一个用于运行过程中均值和方差的一个估计参数(我的理解是一个稳定系数,类似于SGD中的momentum的系数);
4.affine:当设为true时,会给定可以学习的系数矩阵gamma和beta

5 Track running stats-一个布尔值,当设置为 True 时,该模块跟踪运行的平均值和方差,当设置为 False 时,该模块不跟踪这些统计信息,并初始化运行 mean 和运行 var 的统计缓冲区。当这些缓冲区为 None 时,此模块始终使用批量统计信息。无论是训练还是评估。默认值: True

一般来说pytorch中的模型都是继承nn.Module类的,都有一个属性trainning指定是否是训练状态,训练状态与否将会影响到某些层的参数是否是固定的,比如BN层或者Dropout层。通常用model.train()指定当前模型model为训练状态,model.eval()指定当前模型为测试状态。

同时,BN的API中有几个参数需要比较关心的,一个是affine指定是否需要仿射,还有个是track_running_stats指定是否跟踪当前batch的统计特性。容易出现问题也正好是这三个参数:trainning,affine,track_running_stats。

affine

其中的affine指定是否需要仿射,也就是是否需要上面算式的第四个,如果affine=False则γ=1,β=0,并且不能学习被更新。一般都会设置成affine=True。
if self.affine:
self.weight = Parameter(torch.empty(num_features, **factory_kwargs))
self.bias = Parameter(torch.empty(num_features, **factory_kwargs))
else:
self.register_parameter(“weight”, None)
self.register_parameter(“bias”, None)

track_running_stats

  1. if self.track_running_stats:
  2. self.register_buffer('running_mean', torch.zeros(num_features,**factory_kwargs))
  3. self.register_buffer('running_var', torch.ones(num_features, **factory_kwargs))
  4. self.running_mean: Optional[Tensor]
  5. self.running_var: Optional[Tensor]
  6. self.register_buffer('num_batches_tracked',
  7. torch.tensor(0, dtype=torch.long,
  8. **{k: v for k, v in factory_kwargs.items() if k != 'dtype'}))
  9. else:
  10. self.register_buffer("running_mean", None)
  11. self.register_buffer("running_var", None)
  12. self.register_buffer("num_batches_tracked", None)

trainning和track_running_stats,track_running_stats=True表示跟踪整个训练过程中的batch的统计特性,得到方差和均值,而不只是仅仅依赖与当前输入的batch的统计特性。相反的,如果track_running_stats=False那么就只是计算当前输入的batch的统计特性中的均值和方差了。当在推理阶段的时候,如果track_running_stats=False,此时如果batch_size比较小,那么其统计特性就会和全局统计特性有着较大偏差,可能导致糟糕的效果。
如果BatchNorm2d的参数track_running_stats设置False,那么加载预训练后每次模型测试测试集的结果时都不一样;track_running_stats设置为True时,每次得到的结果都一样。

running_mean和running_var参数是根据输入的batch的统计特性计算的,严格来说不算是“学习”到的参数,不过对于整个计算是很重要的。BN层中的running_mean和running_var的更新是在forward操作中进行的,而不是在optimizer.step()中进行的,因此如果处于训练中泰,就算不进行手动step(),BN的统计特性也会变化。

pytorch的batchnorm使用时需要小心,training和track_running_stats可以组合出三种behavior,很容易掉坑里(我刚发现我对track_running_stats的理解错了)。

  1. training=True, track_running_stats=True, 这是常用的training时期待的行为,running_mean 和running_var会跟踪不同batch数据的mean和variance,但是仍然是用每个batch的mean和variance做normalization。
  2. training=True, track_running_stats=False, 这时候running_mean 和running_var不跟踪跨batch数据的statistics了,但仍然用每个batch的mean和variance做normalization。
  3. training=False, track_running_stats=True, 这是我们期待的test时候的行为,即使用training阶段估计的running_mean 和running_var.
  4. training=False, track_running_stats=False,同2(!!!).

我就是对情况4的理解错了。
参考:How to set learning rate as 0 in BN layer
另外,requires_grad 我的理解是只对affine参数起作用的。BatchNorm代码里并没有显式使用这个变量:torch.nn.modules.batchnorm - PyTorch master documentation

4. 冻结BN及其统计数据

从上面的分析可以看出来,正确的冻结BN的方式是在模型训练时,把BN单独挑出来,重新设置其状态为eval (在model.train()之后覆盖training状态).
https://www.zhihu.com/question/282672547/answer/529154567
https://pytorch.org/docs/stable/_modules/torch/nn/modules/batchnorm.html#BatchNorm2dhttps://github.com/pytorch/pytorch/blob/master/aten/src/ATen/native/Normalization.cpp
https://github.com/pytorch/pytorch/blob/7aae51cdedcbf0df5a7a8bf50a947237ac4b3ee8/aten/src/ATen/native/cudnn/BatchNorm.cpp#L52-L143
https://github.com/pytorch/pytorch/blob/420b37f3c67950ed93cd8aa7a12e673fcfc5567b/aten/src/ATen/native/Normalization.cpp#L61-L126
https://zhuanlan.zhihu.com/p/166101119