【深度学习】归一化方法

为什么要做归一化?

        神经网络学习的本质就是学习数据的分布。如果没有对数据进行归一化处理,那么每一批次训练的数据的分布就有可能不一样。从大的方面来讲,神经网络需要在多个分布中找到一个合适的平衡点;从小的方面来说,由于每层网络的输入数据在不断的变化,这会导致不容易找到合适的平衡点,最终使得构建的神经网络模型不容易收敛。当然,如果只是对输入数据做归一化,这样只能保证数据在输入层是一致的,并不能保证每层网络的输入数据分布是一致的,所以在神经网络模型的中间层也需要加入归一化处理。

        现阶段常见的归一化方法主要有:batch noralization(论文:https://arxiv.org/pdf/1502.03167.pdf)、layer normalization(论文:https://arxiv.org/pdf/1607.06450v1.pdf)、instance normalization(论文:https://arxiv.org/pdf/1607.08022.pdf)、group normalization(论文:https://arxiv.org/pdf/1803.08494.pdf)以及switchable normalization(论文:https://arxiv.org/pdf/1806.10779.pdf)

将输入的feature map shape记作 $ [ N, C, H, W] $

这几种方法的主要区别在于:

1. batch normalization(BN)是在batch上,对 $ N, H, W $ 做归一化,而保留通道 $ C $ 的维度。BN对较小的batch size效果不好。BN适用于固定深度的前向神经网络,如CNN,不适用于RNN;

2. layer normalization(LN)在通道方向上,对 $ C, H, W $ 在做归一化,主要对RNN效果明显;

3. instance normalization(IN)在图像像数上,对 $ H, W $ 做归一化,用在风格化迁移;

4. group normalization(GN)将channel分组,然后再做归一化。

        给个子图表示一个特征图,其中 $ N $ 为批量, $ C $ 为通道, $ (H, W) $ 为特征图的高度和宽度。通过蓝色部分的值来计算均值和方差,从而进行归一化。

        

        如果把特征图 $ x\in \mathbb{R}^{N\times C\times H\times W} $ 比喻成一本书,这摞书总共有 $ N $ 本,每本有 $ C $ 页,煤业有 $ H $ 行,每行有 $ W $ 个字符。

  • BN求均值时,相当于是报这些书按照页码一一对应地加起来(如,第一本书第36页,第二本书第36页……),再除以每个页码下的字符总数: $ N\times H\times W $ ,因此可以把BN看成求“平均书”的操作(注意这个“平均书”每页只有一个字),求标准差时也是同理。
  • LN求均值时,相当于把每一本书的所有字加起来,再除以这本书的字符总数: $ N\times H\times W $ ,即求整本书的“平均字”,求标准差也是同理。
  • IN求均值时,相当于把一页书中所有字加起来,再除以该页的总字数: $ H\times W $ ,即求每页书的“平均字”,求标准差也是同理。
  • GN相当于把一本 $ C $ 页的书平均分为 $ G $ 份,每份成为有 $ C/G $ 页的小册子,求每个小册子的“平均字”和字的“标准差”。

 


 

1.batch normalization,BN

        BN的主要思想:针对每个神经元,使数据再进入激活函数之前,沿着通道计算每个batch的均值、方差,“强迫”数据保持均值为0,方差为1的正态分布,避免梯度消失。具体来说,就是把第1个样本的第1个通道,加上第2个样本的第1个通道,……,再加上第 $ N $ 个样本的第1个通道,求平均值,得到通道1的均值(注意是除以 $ N\times H\times W $ ,而不是单纯的除以 $ N $ ,最后得到的是一个代表这个batch第1个通道的平均值,而不是一个 $ H\times W $ 的矩阵)。求通道1的方差也是同理。对所有的通道都施加一边这个操作,就得到了所有通道的均值和方差。

        BN的位置:全连接层或者卷积层后,激活函数前。

        BN算法流程:

  • 沿着通道计算每个batch的均值 $ \mu $ 
  • 沿着通道计算每个batch的方差 $ \sigma ^{2} $ 
  • 做归一化
  • 加入缩放和平移变量 $ \gamma ,\beta  $ 

\begin{align}
\mu &=\frac{1}{m}\sum_{i=1}^{m}z^{(i)}\\
\sigma ^{2}&=\frac{1}{m}\sum_{i=1}^{m}(z^{(i)}-\mu)^{2}\\
z_{Norm}^{i}&=\frac{z^{(i)}}{\sqrt{\sigma ^{2}+\varepsilon }}\\
\tilde{z}^{(i)}&=\gamma z_{Norm}^{i}+\beta
\end{align}

其中, $ \varepsilon $ 是一个很小的正值,如 $ 10^{-8} $ 。加入缩放和平移变量的两个原因:保证每一次数据经过归一化后还保留原有学习到的特征,同时又能完成归一化操作,加速训练。这两个参数是用来学习的参数。

        BN的作用:

  • 允许较大的学习率;
  • 减弱对初始化的强依赖性;
  • 保持隐藏层中数值的均值、方差不变,让数值更稳定,为后面的网络提供坚实的基础;
  • 有轻微的正则化作用(相当于给隐藏层加入噪声,类似于dropout)。

        BN存在的问题:

  • 每次是在一个batch上计算均值、方差,如果batch太小,则计算的均值、方差不足以代表整个数据分布。
  • batch size太大:会超过内存容量,需要跑更多的epoch,导致总训练时间边长,会直接固定梯度下降的方向,导致很难更新。

         在tensorflow中可以通过使用 $ tf.layers.batch\_normalization() $ 来实现BN。这个operation隐藏了mean,var,alpha和beta参数的显示声明。因此在调用过程中,需要特别注意正确调用的方式。

        【使用BN训练】

        注意把 $ tf.layers.batch\_normalization(x, training=is\_training,name=scope) $ 输入参数的 $ training=True $ 。此外还需要添加 $ update\_ops $ 以便于每一次训练结束后及时跟新BN参数。

1 update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
2 
3 # 保证train_op在update_ops执行之后再执行
4 with tf.control_dependencies(update_ops):
5     train_op = optimizer.minimize(loss)

        【保存带BN的模型】

        保存模型的时候,不能只保存trainable_variables,因为BN的参数不属于trainable_variables。为了方便,可以使用tf.global_variables()。使用方法如下:

1 ####
2 saver = tf.train.Saver(var_list=tf.global_variables())
3 
4 savepath = saver.save(sess, 'here_is_your_personal_model_path')
5 ####

        【读取带BN的模型】

        与保存类似,读取模型的时候,变量也需要为global_variables。

1 saver = tf.train.Saver()
2 
3 # or saver = tf.train.Saver(tf.global_variables())
4 
5 saver.restore(sess, "here_is_your_pesonal_model_path")

        在inference的时候还需要把 $ tf.layers.batch\_normalization(x, training=is\_training,name=scope) $ 里的 $ training $ 参数设置为 $ False $ 

 


 

2.layer normalization,LN

        针对BN不适用于深度不固定的网络(sequence长度不一致,如RNN),LN对深度网络某一层的所有神经元的输入按照以下公式进行normalization操作:

\begin{align}
\mu ^{l}&=\frac{l}{H}\sum_{i=1}^{H}a_{i}^{l}\\
\sigma ^{l}&=\sqrt{\frac{l}{H}\sum_{i=1}^{H}(a_{i}^{l}-\mu ^{l})^{2}}
\end{align}

        LN中同层神经元的输入拥有相同的均值和方差,不同的输入样本有不同的均值和方差。

        对于特征图 $ x\in \mathbb{R}^{N\times C\times H\times W} $ ,LN对每个样本的 $ C, G, W $ 维度上的数据求均值和标准差,保留维度 $ N $ 。其均值和标准差的计算公式如下:

\begin{align}
\mu _{n}(x)&=\frac{1}{CHW}\sum_{c=1}^{C}\sum_{h=1}^{H}\sum_{w=1}^{W}x_{nchw}\\
\sigma _{n}(x)&=\sqrt{\frac{1}{CHW}\sum_{c=1}^{C}\sum_{h=1}^{H}\sum_{w=1}^{W}(x_{nchw}-\mu _{n(x)^{2}+\varepsilon })}
\end{align}

        LN的优势时不需要批训练,在单条数据内部就能归一化。LN不依赖batch size和输入sequence的长度,因此可以用在batch size为1的网络和RNN中。LN用于RNN效果比较明显,效果不如BN。


3.instance normalization,IN

        IN针对图像像素做normalization,最初用于图像的风格化迁移。在图像风格化中,生成结果主要依赖于某个图像实例,feature map的各个channel的均值和方差会影响到最终生成的图像风格。所以对整个batch归一化不适合图像风格化中,因此对 $ H, W $ 坐归一化。可以加速模型的收敛,并且保持每个图像实例之间的独立。

        对于特征图 $ x\in \mathbb{R}^{N\times C\times H\times W} $ ,IN对每个样本的 $ H, W $ 维度的数据求均值和标准差,保留 $ N, C $ 维度,也就是说,它只在channel内部求均值和标准差,计算公式如下:

\begin{align}
y_{tijk}&=\frac{x_{tijk}-\mu_{ti}}{\sqrt{\sigma _{ti}^{2}+\varepsilon }}\\
\mu _{ti}&=\frac{1}{HW}\sum_{l=1}^{W}\sum_{m=1}^{H}x_{tilm}\\
\sigma _{ti}^{2}&=\frac{1}{HW}\sum_{l=1}^{W}\sum_{m=1}^{H}(x_{tilm}=mu_{ti})
\end{align}


 

4.group normalization,GN

        GN是为了解决BN对较小的mini-batch size效果差的问题。GN适用于占用显存较大的任务,例如图像分割。对于这类问题,可能batch size只能是个位数,再大显存就不够用了。当batch size是个位数时,BN的表现很差,因为没有办法通过几个样本的数据量来近似总体的均值和方差。GN也是独立于batch的,它是LN和IN的折中方法。

        GN的主要思想:在channel方向进行分组(group),然后在每个group内做normalization,计算 $ (C/G)*H*W $ 的均值和方差,这样就使得与batch size无关,不受其约束。

        具体实现方法:GN计算 均值和标准差时,把每一个样本的feature map的channel分成 $ G $ 组,每组将有 $ C/G $ 个channel,然后将这些channel中的元素计算均值和标准差。各组channel用其对应的归一化参数独立的进行归一化。具体的计算公式如下:

\begin{align}
\mu _{ng}(x)&=\frac{1}{(C/G)HW}\sum_{c=gC/G}^{(g+1)C/G}\sum_{h=1}^{H}\sum_{w=1}^{W}x_{nchw}\\
\sigma _{ng}(x)&=\sqrt{\frac{1}{(C/G)HW}\sum_{c=gC/G}^{(g+1)C/G}\sum_{h=1}^{H}\sum_{w=1}^{W}(x_{nchw}-\mu _{ng}(x))^{2}+\varepsilon }
\end{align}

 1 def GroupNorm(x, gamma, beta, G, eps=1e-5):
 2     # x:input features with shape [N,C, H, W]
 3     # gamma, beta: scale and offset, with shape [1, C, 1, 1]
 4     # G: number of groups for GN
 5     N, C, H, W = x.shape
 6     x = tf.reshape(x, [N, G, C // G, H, W])
 7 
 8     mean, var = tf.nn.moments(x, [2, 3, 4], keep_dims=True)
 9 
10     x = tf.reshape(x, [N, C, H, W])
11 
12     return x * gamma + beta

总结

  • BN是在batch上,对 $ N, H, W $ 做归一化,保留通道 $ C $ 的维度。BN相当于把这些书按照页码一一对应地加起来,再除以每个页码下的字符总数: $ N\times H\times W $ 。
  • LN在通道的方向上,对 $ C, H, W $ 做归一化。LN相当于把每一本书的所有字加起来,在初一这本书的字符总数: $ C\times H\times W $ 。
  • IN在图像像素上,对 $ H, W $ 做归一化。IN相当于把一页书中所有字加起来,再除以该页的总字数: $ H\times W $ 。
  • GN将channel分组,然后再做归一化。GN相当于把一本 $ C $ 页的书平均分成 $ G $ 份,每一份有 $ C/G $ 页的小册子,对每一个小册子做normalization。

        此外,还需注意他们的映射参数 $ \gamma ,\beta  $ 的区别:对于BN,IN,GN,其中 $ \gamma ,\beta  $ 都是维度等于通道数 $ C $ 的向量。而对于LN, 其 $ \gamma ,\beta  $ 都是维度等于normalized_shape的矩阵。

        BN和IN可以设置参数:momentum和track_running_stats来获得在整体数据上更准确的均值和标准差。LN和GN只能计算当前batch内数据的真实均值和标准差。

 

posted @ 2020-08-07 10:26  _再遇见  阅读(4896)  评论(0编辑  收藏  举报