Internal Covariate Shift

Batch Normalization的原论文作者给了Internal Covariate Shift一个较规范的定义:在深层网络训练的过程中,由于网络中参数变化而引起内部结点数据分布发生变化的这一过程被称作Internal Covariate Shift。
这句话该怎么理解呢?我们同样以1.1中的图为例,我们定义每一层的线性变换为 归一化 - 图1,其中 归一化 - 图2 代表层数;非线性变换为 归一化 - 图3 ,其中 归一化 - 图4 为第 归一化 - 图5 层的激活函数。
随着梯度下降的进行,每一层的参数 归一化 - 图6归一化 - 图7 都会被更新,那么 归一化 - 图8 的分布也就发生了改变,进而 归一化 - 图9 也同样出现分布的改变。而 归一化 - 图10 作为第 归一化 - 图11 层的输入,意味着 归一化 - 图12 层就需要去不停适应这种数据分布的变化,这一过程就被叫做Internal Covariate Shift。

Internal Covariate Shift会带来什么问题?

(1)上层网络需要不停调整来适应输入数据分布的变化,导致网络学习速度的降低
我们在上面提到了梯度下降的过程会让每一层的参数 归一化 - 图13归一化 - 图14 发生变化,进而使得每一层的线性与非线性计算结果分布产生变化。后层网络就要不停地去适应这种分布变化,这个时候就会使得整个网络的学习速率过慢。
(2)网络的训练过程容易陷入梯度饱和区,减缓网络收敛速度
当我们在神经网络中采用饱和激活函数(saturated activation function)时,例如sigmoid,tanh激活函数,很容易使得模型训练陷入梯度饱和区(saturated regime)。随着模型训练的进行,我们的参数 归一化 - 图15 会逐渐更新并变大,此时 归一化 - 图16 就会随之变大,并且 归一化 - 图17 还受到更底层网络参数 归一化 - 图18 的影响,随着网络层数的加深, 归一化 - 图19 很容易陷入梯度饱和区,此时梯度会变得很小甚至接近于0,参数的更新速度就会减慢,进而就会放慢网络的收敛速度。
对于激活函数梯度饱和问题,有两种解决思路。第一种就是更为非饱和性激活函数,例如线性整流函数ReLU可以在一定程度上解决训练进入梯度饱和区的问题。另一种思路是,我们可以让激活函数的输入分布保持在一个稳定状态来尽可能避免它们陷入梯度饱和区,这也就是Normalization的思路。

归一化方法

归一化层,目前主要有这几个方法,Batch Normalization(2015年)、Layer Normalization(2016年)、Instance Normalization(2017年)、Group Normalization(2018年)、Switchable Normalization(2018年);

将输入的图像shape记为[N, C, H, W],这几个方法主要的区别就是在,

  • batchNorm是在batch上,对NHW做归一化,对小batchsize效果不好;
  • layerNorm在通道方向上,对CHW归一化,主要对RNN作用明显;
  • instanceNorm在图像像素上,对HW做归一化,用在风格化迁移;
  • GroupNorm将channel分组,然后再做归一化;
  • SwitchableNorm是将BN、LN、IN结合,赋予权重,让网络自己去学习归一化层应该使用什么方法。

image.png

BatchNorm

归一化 - 图21

  1. import numpy as np
  2. def Batchnorm(x, gamma, beta, bn_param):
  3. # x_shape:[B, C, H, W]
  4. running_mean = bn_param['running_mean']
  5. running_var = bn_param['running_var']
  6. results = 0.
  7. eps = 1e-5
  8. x_mean = np.mean(x, axis=(0, 2, 3), keepdims=True)
  9. x_var = np.var(x, axis=(0, 2, 3), keepdims=True0)
  10. x_normalized = (x - x_mean) / np.sqrt(x_var + eps)
  11. results = gamma * x_normalized + beta
  12. # 因为在测试时是单个图片测试,这里保留训练时的均值和方差,用在后面测试时用
  13. running_mean = momentum * running_mean + (1 - momentum) * x_mean
  14. running_var = momentum * running_var + (1 - momentum) * x_var
  15. bn_param['running_mean'] = running_mean
  16. bn_param['running_var'] = running_var
  17. return results, bn_param

LayerNorm

BN不适用于RNN网络中normalize操作:BN实际使用时需要计算并且保存某一层神经网络mini-batch的均值和方差等统计信息,对于对一个固定深度的前向神经网络(DNN,CNN)使用BN,很方便;但对于RNN来说,sequence的长度是不一致的,换句话说RNN的深度不是固定的,不同的time-step需要保存不同的statics特征,可能存在一个特殊sequence比其的sequence长很多,这样training时,计算很麻烦。

LN用于RNN效果比较明显,但是在CNN上,不如BN。

  1. def ln(x, b, s):
  2. _eps = 1e-5
  3. output = (x - x.mean(1)[:,None]) / tensor.sqrt((x.var(1)[:,None] + _eps))
  4. output = s[None, :] * output + b[None,:]
  5. return output

用在图像上:

  1. def Layernorm(x, gamma, beta):
  2. # x_shape:[B, C, H, W]
  3. results = 0.
  4. eps = 1e-5
  5. x_mean = np.mean(x, axis=(1, 2, 3), keepdims=True)
  6. x_var = np.var(x, axis=(1, 2, 3), keepdims=True0)
  7. x_normalized = (x - x_mean) / np.sqrt(x_var + eps)
  8. results = gamma * x_normalized + beta
  9. return results

InstanceNorm

图像风格化中,生成结果主要依赖于某个图像实例,所以对整个batch归一化不适合图像风格化中,因而对HW做归一化。可以加速模型收敛,并且保持每个图像实例之间的独立。

归一化 - 图22

  1. def Instancenorm(x, gamma, beta):
  2. # x_shape:[B, C, H, W]
  3. results = 0.
  4. eps = 1e-5
  5. x_mean = np.mean(x, axis=(2, 3), keepdims=True)
  6. x_var = np.var(x, axis=(2, 3), keepdims=True0)
  7. x_normalized = (x - x_mean) / np.sqrt(x_var + eps)
  8. results = gamma * x_normalized + beta
  9. return results

GroupNorm

主要是针对Batch Normalization对小batchsize效果差,GN将channel方向分group,然后每个group内做归一化,算归一化 - 图23的均值,这样与batchsize无关,不受其约束。

归一化 - 图24

Switchable Normalization

  1. def SwitchableNorm(x, gamma, beta, w_mean, w_var):
  2. # x_shape:[B, C, H, W]
  3. results = 0.
  4. eps = 1e-5
  5. mean_in = np.mean(x, axis=(2, 3), keepdims=True)
  6. var_in = np.var(x, axis=(2, 3), keepdims=True)
  7. mean_ln = np.mean(x, axis=(1, 2, 3), keepdims=True)
  8. var_ln = np.var(x, axis=(1, 2, 3), keepdims=True)
  9. mean_bn = np.mean(x, axis=(0, 2, 3), keepdims=True)
  10. var_bn = np.var(x, axis=(0, 2, 3), keepdims=True)
  11. mean = w_mean[0] * mean_in + w_mean[1] * mean_ln + w_mean[2] * mean_bn
  12. var = w_var[0] * var_in + w_var[1] * var_ln + w_var[2] * var_bn
  13. x_normalized = (x - mean) / np.sqrt(var + eps)
  14. results = gamma * x_normalized + beta
  15. return results