深度学习Normalization层大总结

说到BN层,相信做深度学习的同学都有所耳闻,但是其中的原理是什么?有什么作用?有时候可能不是很清楚。为了深入了解其内在的原理,特别通过此篇博客进行记录,并与大家一起共同进步,不正之处还请同学们多多指正呀。

一、Normalization

Normalization到底是什么呢?它起到什么作用呢?
从字面意思来看是归一化、标准化的意思,相信学习过机器学习的同学都应该有所了解。在传入估计器或者预测器进行训练时,往往传入的数据并不是我们满意的数据,所以需要对数据进行处理。之前有分享过一篇有关机器学习数据处理的笔记
在深度学习中,Batch Normalization 是 Sergey 等人于2015年在 Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift 中提出的一种归一化方法。在文中作者提到,通常希望输入的数据能够满足独立同分布,并且希望数据能分布在激活函数梯度较大的范围之内。可是在深度神经网络中,底层的参数更新会对高层的输入分布产生很大的影响,也就是说即使一开始我们的数据满足了要求,可是随着网络的传递,数据也会发生变化,不能满足要求。作者称这个现象叫 internal covariate shift此外,(internal corvariate shift和covariate shift是两回事,前者是网络内部,后者是针对输入数据,比如我们在训练数据前做归一化等预处理操作)。为了解决这个问题,作者提出了 Batch Normalization。
Normalization就是将输入的数据进行归一化,使其服从均值为 0, 方差为 1 的分布。这样使得输入具有相同的均值和方差,固定了每一层的输入分布,从而加速网络的收敛。

Normalization的主要作用如下:
a. 缓解梯度消失,可以增大 learning rate, 使训练更快收敛。
b. 简化调参,网络更稳定。在调参时,学习率调得过大容易出现震荡于不收敛,而 BN 抑制了参数微小变化随网络加深而被放大的问题,因此对于参数变化的适应能力更强。
c. 更不容易过拟合, 因此可以替代 dropout,或者少用 dropout,可以减少 L2 正则化的权重,同时可以减少 argument 的操作,可以不用太操心 initialization 的操作。
Normalization的位置:
通常在神经网络中按照如下的位置进行排列:

Conv -> Norm层 -> 激活函数层

当然也有人尝试如下的放置方法
Conv -> 激活函数层 -> Norm 层

后来在Rethinking the Usage of Batch Normalization and Dropout in the Training ofDeep Neural Network中证明:我们不应该把Norm 层放在ReLU之前,因为ReLU的非负响应会使权值层更新的方式不太理想。
Normalization的几种方式:
1、batchNorm是在batch上,对NHW做归一化,对小batchsize效果不好;
2、layerNorm在通道方向上,对CHW归一化,主要对RNN作用明显;
3、instanceNorm在图像像素上,对HW做归一化,用在风格化迁移;
4、GroupNorm将channel分组,然后再做归一化;
5、SwitchableNorm是将BN、LN、IN结合,赋予权重,让网络自己去学习归一化层应该使用什么方法。下图将三种方法进行了形象的说明。从C方向看过去是指一个个通道,从N看过去是一张张图片。每6个竖着排列的小正方体组成的长方体代表一张图片的一个feature map。蓝色的方块是一起进行Normalization的部分。由此就可以很清楚的看出,Batch Normalization是指6张图片中的每一张图片的同一个通道一起进行Normalization操作。layerNorm是一张图片中的通道之间进行Normalization操作,而Instance Normalization是指单张图片的单个通道单独进行Noramlization操作。
在这里插入图片描述

二、Batch Normalization

Batch Normalization(2015年)
文章地址:https://arxiv.org/pdf/1502.03167.pdf
文中提出Batch Normalization的计算细节:
在这里插入图片描述
上图中: x i x_i xi为输入数据, μ β \mu_\beta μβ为对一个 batch 的输入数据取平均值, σ β 2 σ \sigma^2_\betaσ σβ2σ为方差, x i ^ \hat{x_i} xi^是归一化之后的输入数据,其满足均值为0, 方差为1 , 是一个标准正太分布。 其中 ϵ \epsilon ϵ 是一个很小的正数,仅仅是为了保证分母不为零。第四个方程是在 x i ^ \hat{x_i} xi^基础上做了一个 γ \gamma γ 倍的缩放 和 β \beta β的偏移。而这里的 γ \gamma γ β \beta β 是超参数,也就是需要通过训练来的学习。
以上的的过程描述的非常清晰,就是将输入的数据进行归一化,使其服从均值为 0, 方差为1的分布。这样使得输入具有相同的均值和方差,固定了每一层的输入分布,从而加速网络的收敛。
第四个方程的操作主要是为了避免对一些激活函数对其造成影响,如对于 sigmoid 激活函数,归一化之后的数据输入 sigmoid 激活函数,会使数据落在近乎线性的区域(红色标记的区域仅仅是一个示意,并不严谨),这个区域里 sigmoid 激活函数近乎于一个线性函数,实质上并不能起到非线性的作用,这样就不能起到拟合复杂系统的作用,当拟合能力弱了之后自然训练的结果也不会好。作者通过第四步的操作,将符合标准正态分布的数据变换回其原本的分布。
在这里插入图片描述
我们可以理解为BN的本质就是一个以 γ \gamma γ β \beta β为参数,从 x i x_i xi y i y_i yi的映射。即:
B N γ β : x i . . . m → y i . . . m BN_{\gamma\beta}:x_{i...m} \rightarrow y_{i...m} BNγβ:xi...myi...m
其反向传播如下:
在这里插入图片描述
在测试时,均是使用单个样本进行检测,当一个模型训练完成之后,它的所有参数都确定了,包括均值和方差, γ \gamma γ β \beta β。测试的时候还是类似的:在这里插入图片描述
运行到BN层时使用的均值和方差均是来自于训练样本,随机抽取多个批次,每个批次的大小都是m,进行计算。即:均值采用训练集所有 batch 均值的期望,方差采用训练集所有 batch 的方差的无偏估计
E ( x ) ← E ( μ B ) E(x) \leftarrow E(\mu_{\Beta}) E(x)E(μB)
V a r ( x ) ← m m − 1 E ( σ 2 B ) V a r ( x ) ← \frac{m }{m-1}E ( σ^2B ) Var(x)m1mE(σ2B)
当然,这样比较麻烦,需要大量的计算。所以在训练时就将均值和方差保存下来,并通过滑动平均(moving average)进行更新。其中的均值和方差分别成为滑动均值 E m o v i n g E_{moving} Emoving 和滑动方差 V a r m o v i n g Var_{moving} Varmoving
E m o v i n g ( x ) = m ∗ E m o v i n g ( x ) + ( 1 − m ) ∗ E s a m p l e E_{moving}(x)=m*E_{moving}(x)+(1-m)*E_{sample} Emoving(x)=mEmoving(x)+(1m)Esample
V a r m o v i n g ( x ) = m ∗ V a r m o v i n g ( x ) + ( 1 − m ) ∗ V a r s a m p l e Var_{moving}(x)=m*Var_{moving}(x)+(1-m)*Var_{sample} Varmoving(x)=mVarmoving(x)+(1m)Varsample
E s a m p l e E_{sample} Esample为采样均值, V a r s a m p l e Var_{sample} Varsample 为采样方差,此处的m为遗忘因子momentum,默认为0.99.
根据参数 γ \gamma γ β \beta β和上述方式得到:
根据参数γ \gammaγ和β \betaβ和上述方式得到:
y = γ x − u V a r ( x ) + ϵ + β \gamma\frac{x-u}{\sqrt{Var(x)+\epsilon}}+\beta γVar(x)+ϵ xu+β

import numpy as np

def Batchnorm(x, gamma, beta, bn_param):

    # x_shape:[B, C, H, W]
    running_mean = bn_param['running_mean']
    running_var = bn_param['running_var']
    results = 0.
    eps = 1e-5

    x_mean = np.mean(x, axis=(0, 2, 3), keepdims=True)
    x_var = np.var(x, axis=(0, 2, 3), keepdims=True0)
    x_normalized = (x - x_mean) / np.sqrt(x_var + eps)
    results = gamma * x_normalized + beta

    # 因为在测试时是单个图片测试,这里保留训练时的均值和方差,用在后面测试时用
    running_mean = momentum * running_mean + (1 - momentum) * x_mean
    running_var = momentum * running_var + (1 - momentum) * x_var

    bn_param['running_mean'] = running_mean
    bn_param['running_var'] = running_var

    return results, bn_param

BN的优缺点:
1、batch normalization 和 batch size 紧密相关,当 batch size 比较小时,由于每次计算均值和方差是在一个batch上,所以如果batchsize太小,则计算的均值、方差不足以代表整个数据分布,并不能得到好的统计数据,效果也就大打折扣。
2、batch normalization 需要存储均值和方差信息
3、batch normalization实际使用时需要计算并且保存某一层神经网络batch的均值和方差等统计信息,对于对一个固定深度的前向神经网络(DNN,CNN)使用BN,很方便;但对于RNN来说,sequence的长度是不一致的,换句话说RNN的深度不是固定的,不同的time-step需要保存不同的statics特征,可能存在一个特殊sequence比其他sequence长很多,这样training时,计算很麻烦。

三、Layer Nornalization

Layer Normalization 2016年
文章地址:https://arxiv.org/pdf/1607.06450v1.pdf
顾名思义,batch normalization 针对每个神经元的输入进行统计,而 layer normalization 则是在每个训练的样本下, 针对每个 layer 中的所有神经元的输入进行统计,得到的均值和方差作为一个规范,对该层的所有输入进行相同的规范化操作。与BN不同,LN是针对深度网络的某一层的所有神经元的输入按以下公式进行normalize操作。具体计算如下:
在这里插入图片描述

H为层内隐藏神经元数量。在层归一化下,同一层内的所有隐藏神经元具有相同的归一项和σ,但不同的训练案例具有不同的归一项。与批量归一化不同,层归一化对小批量的大小没有任何约束。
Layer Normalization 的特点如下:
1、layer normalization 同一层的均值和方差相同, 不同的输入样本有不同的均值和方差
2、layer normalization 训练和测试的时候进行相同的操作,不需要存储统计信息
3、layer normalization 可以用于RNN, 或者是 batch size 较小的场合
4、layer normalization 对于相似度较大的特征, 会降低模型的表示能力,此时 batch normalization 更优。
5、对比 batch normalization 和 layer normalization, 目前 batch normalization 使用更广,对于较大batch size的时候,batch normalization 的表现比 layer normalization好很多, 但是 layer normalization 具有更好适用性。

BN与LN的区别在于:
1、LN中同层神经元输入拥有相同的均值和方差,不同的输入样本有不同的均值和方差;
2、BN中则针对不同神经元输入计算均值和方差,同一个batch中的输入拥有相同的均值和方差。
所以,LN不依赖于batch的大小和输入sequence的深度,因此可以用于batchsize为1和RNN中对边长的输入sequence的normalize操作。
其示例代码如下:

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

在四维的图像中

def Layernorm(x, gamma, beta):

    # x_shape:[B, C, H, W]
    results = 0.
    eps = 1e-5

    x_mean = np.mean(x, axis=(1, 2, 3), keepdims=True)
    x_var = np.var(x, axis=(1, 2, 3), keepdims=True0)
    x_normalized = (x - x_mean) / np.sqrt(x_var + eps)
    results = gamma * x_normalized + beta
    return results

四、Instance Normalization

Instance Normalization 2016 年
文章地址:https://arxiv.org/pdf/1607.08022.pdf

源码地址:https://github.com/DmitryUlyanov/texture_nets
在 GAN 和 style transfer 的任务中,目前的 IN 要好于 BN,IN 主要用于对单张图像的单个通道的数据做处理,而 BN 主要是对整个 Bacth 的数据做处理,注重对每个batch进行归一化,保证数据分布一致,因为判别模型中结果取决于数据整体分布。由于BN在训练时每个 batch 的均值和方差会由于 shuffle 都会改变,所以可以理解为一种数据增强,而 IN 可以理解为对数据做一个归一化的操作。
但是图像风格化中,生成结果主要依赖于某个图像实例,所以对整个batch归一化不适合图像风格化中,因而对HW做归一化。可以加速模型收敛,并且保持每个图像实例之间的独立。
在这里插入图片描述
实现代码如下:

def Instancenorm(x, gamma, beta):

    # x_shape:[B, C, H, W]
    results = 0.
    eps = 1e-5

    x_mean = np.mean(x, axis=(2, 3), keepdims=True)
    x_var = np.var(x, axis=(2, 3), keepdims=True0)
    x_normalized = (x - x_mean) / np.sqrt(x_var + eps)
    results = gamma * x_normalized + beta
    return results

五、Group Normalization

Group Normalization 2018 年
文章地址:https://arxiv.org/pdf/1803.08494.pdf
Group Normalization(GN)是针对Batch Normalization(BN)在batch size较小时错误率较高而提出的改进算法,因为BN层的计算结果依赖当前 batch 的数据,当 batch size 较小时(比如2、4这样),该 batch 数据的均值和方差的代表性较差,因此对最后的结果影响也较大。GN 根据通道进行分组,然后在每个组内做归一化,计算器均值和方差,从而不受批次中样本数量的约束。这里将通道分为 G 组,G 是一个超参数,通常默认为 32。
如下图所示,随着 batch size 越来越小,BN 层所计算的统计信息的可靠性越来越差,这样就容易导致最后错误率的上升;而在 batch size 较大时则没有明显的差别。虽然在分类算法中一般的 GPU 显存都能 cover 住较大的 batch 设置,但是在目标检测、分割以及视频相关的算法中,由于输入图像较大、维度多样以及算法本身原因等,batch size 一般都设置比较小,所以 GN 对于这种类型算法的改进应该比较明显。
在这里插入图片描述

代码如下:

def GroupNorm(x, gamma, beta, G=16):

    # x_shape:[B, C, H, W]
    results = 0.
    eps = 1e-5
    x = np.reshape(x, (x.shape[0], G, x.shape[1]/16, x.shape[2], x.shape[3]))

    x_mean = np.mean(x, axis=(2, 3, 4), keepdims=True)
    x_var = np.var(x, axis=(2, 3, 4), keepdims=True0)
    x_normalized = (x - x_mean) / np.sqrt(x_var + eps)
    results = gamma * x_normalized + beta
    return results

六、Switchable Normalization

文章地址:https://arxiv.org/pdf/1806.10779.pdf

代码链接:https://github.com/switchablenorms/Switchable-Normalization

归一化虽然提高模型泛化能力,然而归一化层的操作是人工设计的。在实际应用中,解决不同的问题原则上需要设计不同的归一化操作,并没有一个通用的归一化方法能够解决所有应用问题;一个深度神经网络往往包含几十个归一化层,通常这些归一化层都使用同样的归一化操作,因为手工为每一个归一化层设计操作需要进行大量的实验。因此作者提出自适配归一化方法——Switchable Normalization(SN)来解决上述问题。与强化学习不同,SN使用可微分学习,为一个深度网络中的每一个归一化层确定合适的归一化操作。在这里插入图片描述
在这里插入图片描述
代码如下:

def SwitchableNorm(x, gamma, beta, w_mean, w_var):
    # x_shape:[B, C, H, W]
    results = 0.
    eps = 1e-5

    mean_in = np.mean(x, axis=(2, 3), keepdims=True)
    var_in = np.var(x, axis=(2, 3), keepdims=True)

    mean_ln = np.mean(x, axis=(1, 2, 3), keepdims=True)
    var_ln = np.var(x, axis=(1, 2, 3), keepdims=True)

    mean_bn = np.mean(x, axis=(0, 2, 3), keepdims=True)
    var_bn = np.var(x, axis=(0, 2, 3), keepdims=True)

    mean = w_mean[0] * mean_in + w_mean[1] * mean_ln + w_mean[2] * mean_bn
    var = w_var[0] * var_in + w_var[1] * var_ln + w_var[2] * var_bn

    x_normalized = (x - mean) / np.sqrt(var + eps)
    results = gamma * x_normalized + beta
    return results

七、Weight Normalization

前面的 BN 和 LN 都是针对输入数据进行的 normalization, 而 WN 则是针对 weight 来进行归一化。WN不依赖于输入数据的分布,故可应用于mini-batch较小的情景且可用于动态网络结构。此外,WN还避免了LN中对每一层使用同一个规范化公式的不足。规范化方式尽管对输入数据的尺度化(scale)来源不同,但其本质上都实现了数据的规范化操作。

  • 12
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值