归一化层,目前主要有几个方法: (将输入图像shape 标记为[N, C, H, W],(N 是 batch size, C是 channel, HW是高和宽))
BatchNorm: 在batch上,对 NHW 做归一化,对小batchsize效果不好
LayerNorm: 在通道方向上,对 CHW 归一化,主要对RNN作用
InstanceNorm: 在图像像素上,对HW 归一化,主要是风格迁移
GroupNorm: 将channel 分组,然后再做归一化
SwithabelNorm: 将BN,LN, IN 结合, 赋予权重,让网络自己学习归一化层应该使用什么方法。
类比:如果把输入图像 x∈ℝ N×C×H×W, 类比为一摞书,这摞书总共有 N 本,每本有 C 页,每页有 H 行,每行 W 个字符。
计算均值(方差同理)
BN 相当于把这些书按页码一一对应地加起来(例如:第1本书第36页,加第2本书第36页…),再除以每个页码下的字符总数:N×H×W,因此可以把 BN 看成求“平均书”的操作(注意这个“平均书”每页只有一个字)
LN 相当于把每一本书的所有字加起来,再除以这本书的字符总数:C×H×W,即求整本书的“平均字”
IN 相当于把一页书中所有字加起来,再除以该页的总字数:H×W,即求每页书的“平均字”
GN 相当于把一本 C 页的书平均分成 G 份,每份成为有 C/G 页的小册子,对这个 C/G 页的小册子,求每个小册子的“平均字”
需要注意它们的映射参数γ和β的区别:对于 BN,IN,GN, 其γ和β都是维度等于通道数 C 的向量。而对于 LN,其γ和β都是维度等于 normalized_shape 的矩阵。
对比:
Batch Normalization对属于同一个Batch中的数据长度要求是相同的,不适合处理序列型的数据。所以它在NLP领域的RNN上效果并不显著,但在CV领域的CNN上效果显著。
Layer Normalization的改进点在于:对指定层中神经元的输出进行操作,统计该层所有神经元输出的均值和方差,然后再对这一层的输出进行 Normalization的操作。它在CV领域的CNN上效果并不显著,但在NLP领域的RNN上效果显著。
归一化最初是由BatchNorm的引入,后续在不同的模型上有各种改进。
那么当初为什么引入batchnorm呢? 深度学习尤其是在CV上都需要对数据归一化,因为深度神经网络主要是为了学习训练数据的分布,并在测试集上达到很好的泛华效果,但是如果每个batch输入的数据都具有不同的分布,显然会给网络训练带来困难。另一方面,数据经过衣岑层网络计算后,其数据分布也会发生变化,此现象成为Internal Convariate Shift,这会给下一层网络学习带来困难,batchnorm就是做批规范化,解决这个分布变化的问题。
什么是Internal Covariate Shift? 这google小组在Batch Normalization中提出的,其主要描述的是:训练深度网络的时候经常发生训练困难的问题,因为每一次参数迭代更新后,上一层网络的输出数据经过这一层网络计算后,数据的分布会发生变化,为下一层网络的学习带来困难(神经网络本来就是要学习数据的分布,要是分布一直在变,学习就很困难了)这个现象称之为Internal Convariate Shift.
Batch Normalization 之前的解决方案就是使用较小的学习率,和小心的初始化参数,对数据做白化处理,但是显然治标不治本。
Internal Covariate Shift 和 Convariate Shift具有相似性,但是并不是一个东西,前者发生在神经网络内部,所以是Internal,后者发生在输入数据上。ConvariateShift 主要描述的是由于训练数据和测试数据在分布的差异性,给网络的泛化性和训练速度带来了影响,常使用的方法是做归一化或者白化,如下图所示
举个简单线性分类栗子,大家应该都知道,我们一般在训练网络的时会将输入减去均值,还有些人甚至会对输入做白化等操作,目的是为了加快训练。为什么减均值、白化可以加快训练呢,这里做一个简单地说明:首先,图像数据是高度相关的,假设其分布如图a所示(简化为2维)。由于初始化的时候,我们的参数一般都是0均值的,因此开始的拟合y=Wx+b,基本过原点附近,如图b红色虚线。因此,网络需要经过多次学习才能逐步达到如紫色实线的拟合,即收敛的比较慢。如果我们对输入数据先作减均值操作,如图c,显然可以加快学习。更进一步的,我们对数据再进行去相关操作,使得数据更加容易区分,这样又会加快训练,如图d。
白化的方式有好几种,常用的有PCA白化:即对数据进行PCA操作之后,在进行方差归一化。这样数据基本满足0均值、单位方差、弱相关性。作者首先考虑,对每一层数据都使用白化操作,但分析认为这是不可取的。因为白化需要计算协方差矩阵、求逆等操作,计算量很大,此外,反向传播时,白化操作不一定可导
Convariate Shift 就是描述的输入数据分布不一致现象,对数据做归一化可以加速训练速度,能对数据做去相关性,突出他们之间的分布相对差异就更好了。BatchNorm做到了,前文已说过,BatchnormBatchnormBatchnorm是归一化的一种手段,极限来说,这种方式会减小图像之间的绝对差异,突出相对差异,加快训练速度。所以说,并不是在深度学习的所有领域都可以使用BatchNorm,下文会写到其不适用的情况。
BN的基本思想其实相当直观:因为深层神经网络在做非线性变换前的激活输入值(就是那个x=WU+B,U是输入)随着网络深度加深或者在训练过程中,其分布逐渐发生偏移或者变动,之所以训练收敛慢,一般是整体分布逐渐往非线性函数的取值区间的上下限两端靠近(对于Sigmoid函数来说,意味着激活输入值WU+B是大的负值或正值),所以这导致后向传播时低层神经网络的梯度消失,这是训练深层神经网络收敛越来越慢的本质原因,而BN就是通过一定的规范化手段,把每层神经网络任意神经元这个输入值的分布强行拉回到均值为0方差为1的标准正太分布而不是萝莉分布(哦,是正态分布),其实就是把越来越偏的分布强制拉回比较标准的分布,这样使得激活输入值落在非线性函数对输入比较敏感的区域,这样输入的小变化就会导致损失函数较大的变化,意思是这样让梯度变大,避免梯度消失问题产生,而且梯度变大意味着学习收敛速度快,能大大加快训练速度。
其实一句话就是:**对于每个隐层神经元,把逐渐向非线性函数映射后向取值区间极限饱和区靠拢的输入分布强制拉回到均值为0方差为1的比较标准的正态分布,使得非线性变换函数的输入值落入对输入比较敏感的区域,以此避免梯度消失问题。因为梯度一直都能保持比较大的状态,所以很明显对神经网络的参数调整效率比较高,就是变动大,就是说向损失函数最优值迈动的步子大,也就是说收敛地快。**NB说到底就是这么个机制,方法很简单,道理很深刻。
之前说过,为了减小InternalConvariateShift,对神经网络的每一层做归一化就可以了。假设将每一层输出后的数据都归一化到0均值,1方差,满足正态分布,但是此时有一个问题,每层数据分布都是标准正太分布,导致其完全学习不到输入数据的特征,因为费尽心思学习到的特征分布被归一化了,因此,直接对每一层做归一化显然是不合理的。
但是如果稍微做修改,加入可训练的参数做归一化,就是BatchNorm实现的了,下面就是伪代码:
之所以称之为BatchNorm是因为所有Norm 的数据是一个batch的,假设输入数据共m个,输出是yi=BN(x1-xm), BN的步骤如下:
求出批量数据的均值,求出该批次数据的方差,对每个x做归一化,(最后最重要的是)引入缩放和平移变量r和beta,计算归一化后的值。
我们来详细看一下 这两个额外参数。之前说过,如果直接做归一化不做其他处理,神经网络是学不到任何东西的,但是加入这两个参数后,就不一样了。
先考虑特殊情况下,如果 gamma和beta分别等于该batch的 标准差和均值,那么yi 就还原成 归一化之前的x了,也就是缩放平移到了归一化前的分布,相当于batchnorm没有起作用。这两个参数分布称之为平移参数(beta)和缩放参数(gamma).这样就保证了每一次数据经过归一化后还保留了原有的学习来的特征,同时又能完成归一化操作 加速训练。
def Batchnorm_simple_for_train(x, gamma, beta, bn_param):
"""
param:x : 输入数据,设shape(B,L)
param:gama : 缩放因子 γ
param:beta : 平移因子 β
param:bn_param : batchnorm所需要的一些参数
eps : 接近0的数,防止分母出现0
momentum : 动量参数,一般为0.9, 0.99, 0.999
running_mean :滑动平均的方式计算新的均值,训练时计算,为测试数据做准备
running_var : 滑动平均的方式计算新的方差,训练时计算,为测试数据做准备
"""
running_mean = bn_param['running_mean'] #shape = [B]
running_var = bn_param['running_var'] #shape = [B]
results = 0. # 建立一个新的变量
x_mean=x.mean(axis=0) # 计算x的均值
x_var=x.var(axis=0) # 计算方差
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
首先计算均值和方差,然后归一化,然后缩放和平移,完事!但是这是在训练中完成的任务,每次训练给一个批量,然后计算批量的均值方差,但是在测试的时候可不是这样,测试的时候每次只输入一张图片,这怎么计算批量的均值和方差,于是,就有了代码中下面两行,在训练的时候实现计算好mean,var 测试的时候直接拿来用就可以了,不用计算均值和方差。
running_mean = momentum * running_mean + (1 - momentum) * x_mean
running_var = momentum * running_var + (1 - momentum) * x_var
所以测试时 是这样的,直接拿训练时的 mean和 var 来做归一化处理。
def Batchnorm_simple_for_test(x, gamma, beta, bn_param):
"""
param:x : 输入数据,设shape(B,L)
param:gama : 缩放因子 γ
param:beta : 平移因子 β
param:bn_param : batchnorm所需要的一些参数
eps : 接近0的数,防止分母出现0
momentum : 动量参数,一般为0.9, 0.99, 0.999
running_mean :滑动平均的方式计算新的均值,训练时计算,为测试数据做准备
running_var : 滑动平均的方式计算新的方差,训练时计算,为测试数据做准备
"""
running_mean = bn_param['running_mean'] #shape = [B]
running_var = bn_param['running_var'] #shape = [B]
results = 0. # 建立一个新的变量
x_normalized=(x-running_mean )/np.sqrt(running_var +eps) # 归一化
results = gamma * x_normalized + beta # 缩放平移
return results , bn_param
下面是来自知乎的 tf的BN的代码:
def batch_norm_layer(x, train_phase, scope_bn):
with tf.variable_scope(scope_bn):
# 新建两个变量,平移、缩放因子
beta = tf.Variable(tf.constant(0.0, shape=[x.shape[-1]]), name='beta', trainable=True)
gamma = tf.Variable(tf.constant(1.0, shape=[x.shape[-1]]), name='gamma', trainable=True)
# 计算此次批量的均值和方差
axises = np.arange(len(x.shape) - 1)
batch_mean, batch_var = tf.nn.moments(x, axises, name='moments')
# 滑动平均做衰减
ema = tf.train.ExponentialMovingAverage(decay=0.5)
def mean_var_with_update():
ema_apply_op = ema.apply([batch_mean, batch_var])
with tf.control_dependencies([ema_apply_op]):
return tf.identity(batch_mean), tf.identity(batch_var)
# train_phase 训练还是测试的flag
# 训练阶段计算runing_mean和runing_var,使用mean_var_with_update()函数
# 测试的时候直接把之前计算的拿去用 ema.average(batch_mean)
mean, var = tf.cond(train_phase, mean_var_with_update,
lambda: (ema.average(batch_mean), ema.average(batch_var)))
normed = tf.nn.batch_normalization(x, mean, var, beta, gamma, 1e-3)
return normed
其中 tf.nn.batch_normalization(x, mean, var, beta, gamma, 1e-3) 就是计算batch norm的过程,具体如下:
def batch_normalization(x,
mean,
variance,
offset,
scale,
variance_epsilon,
name=None):
with ops.name_scope(name, "batchnorm", [x, mean, variance, scale, offset]):
inv = math_ops.rsqrt(variance + variance_epsilon)
if scale is not None:
inv *= scale
return x * inv + (offset - mean * inv
if offset is not None else -mean * inv)
优点:
缺点:
韩国团队在2017NTIRE图像超分辨率中取得了top1的成绩,主要原因竟是去掉了网络中的batchnorm层,由此可见,BN并不是适用于所有任务的,在image-to-image这样的任务中,尤其是超分辨率上,图像的绝对差异显得尤为重要,所以batchnorm的scale并不适合。
Layer Norm 是与BatchNorm很相似的 归一化算法,
LN的提出: BN针对一个minibatch的输入样本,计算均值和方差,基于计算的均值和方差来对某一层神经网络的输入X中每一个case进行归一化操作。但BN有两个明显不足:1、高度依赖于mini-batch的大小,实际使用中会对mini-Batch大小进行约束,不适合类似在线学习(mini-batch为1)情况;2、不适用于RNN网络中normalize操作:BN实际使用时需要计算并且保存某一层神经网络mini-batch的均值和方差等统计信息,对于对一个固定深度的前向神经网络(DNN,CNN)使用BN,很方便;但对于RNN来说,sequence的长度是不一致的,换句话说RNN的深度不是固定的,不同的time-step需要保存不同的statics特征,可能存在一个特殊sequence比其的sequence长很多,这样training时,计算很麻烦。但LN可以有效解决上面这两个问题。
我们知道,针对深度学习,存在“covariate shift”的现象,因此需要通过normalize操作,使Hi层的输入拥有固定的均值和方差,以此削弱协方差偏移现象对深度网络的训练时的影响,加快网络收敛。normalize的对Hi层输入进行归一化处理。 但是直接对第i层归一化是不现实的,因为需要针对整个training set 来计算,因此BN通过mini-batch的输入样本 近似的计算归一化中的均值和方差,成为batch-normalization.
与 BN不一样,LN是针对深度网络的某一层的所有神经元的输入,计算归一化的。
由此可见BN与LN的不同之处在于:LN中同层神经元输入拥有相同的均值和方差,不同的输入样本有不同的均值和方差;而BN中则针对不同神经元输入计算均值和方差,同一个minibatch中的输入拥有相同的均值和方差。因此,LN不依赖于mini-batch的大小和输入sequence的深度,因此可以用于bath-size为1和RNN中对边长的输入sequence的normalize操作。
在传统RNN中,recurrent unit经过累加求和后的输入(summed input)的量级会随着训练进行发生波动,导致梯度爆炸或梯度消失发生。加入LN之后,Normalization term会对summed input的进行尺度变换,使RNN在training和inference时更加稳定。
实践证明,LN用于RNN进行Normalization时,取得了比BN更好的效果。但用于CNN时,效果并不如BN明显
GN:主要是针对Batch Normalization对小batchsize效果差,GN将channel方向分group,然后每个group内做归一化,算(C//G)HW的均值,这样与batchsize无关,不受其约束。
参考:
BN-LN-IN-GN
BatchNorm解读
LayerNorm
归一化