Batch Normalization批量归一化

       深度学习捷报连连、声名鹊起,随机梯度下降成了训练深度网络的主流方法。尽管随机梯度下降法对于训练深度网络简单高效,但是它有个毛病,就是需要我们人为的去选择参数,比如学习率、参数初始化、权重衰减系数、Drop out比例等。这些参数的选择对训练结果至关重要,以至于我们很多时间都浪费在这些的调参上。那么学完这篇文献之后,你可以不需要那么刻意的慢慢调整参数。
      批量标准化一般用在非线性映射(**函数)之前,对y=Wx+b进行规范化,使结果(输出信号的各个维度)的均值都为0,方差为1,让每一层的输入有一个稳定的分布会有利于网络的训练。

BN算法(Batch Normalization)其强大之处如下:

(1)你可以选择比较大的初始学习率,让你的训练速度飙涨。以前还需要慢慢调整学习率,甚至在网络训练到一半的时候,还需要想着学习率进一步调小的比例选择多少比较合适,现在我们可以采用初始很大的学习率,然后学习率的衰减速度也很大,因为这个算法收敛很快。当然这个算法即使你选择了较小的学习率,也比以前的收敛速度快,因为它具有快速训练收敛的特性;

(2)你再也不用去理会过拟合中drop out、L2正则项参数的选择问题,采用BN算法后,你可以移除这两项了参数,或者可以选择更小的L2正则约束参数了,因为BN具有提高网络泛化能力的特性;

(3)再也不需要使用使用局部响应归一化层了(局部响应归一化是Alexnet网络用到的方法,搞视觉的估计比较熟悉),因为BN本身就是一个归一化网络层;

(4)可以把训练数据彻底打乱(防止每批训练的时候,某一个样本都经常被挑选到,文献说这个可以提高1%的精度)。

总结一下:

  1. 减少了参数的人为选择,可以取消dropout和L2正则项参数,或者采取更小的L2正则项约束参数
  2. 减少了对学习率的要求
  3. 可以不再使用局部响应归一化了,BN本身就是归一化网络(局部响应归一化-AlexNet)
  4. 更破坏原来的数据分布,一定程度上缓解过拟合

        开始讲解算法前,先来思考一个问题:我们知道在神经网络训练开始前,都要对输入数据做一个归一化处理,那么具体为什么需要归一化呢?归一化后有什么好处呢?

  1. 神经网络学习过程本质就是为了学习数据分布,一旦训练数据与测试数据的分布不同,那么网络的泛化能力也大大降低
  2. 另外一方面,一旦每批训练数据的分布各不相同(batch梯度下降),那么网络就要在每次迭代都去学习适应不同的分布,这样将会大大降低网络的训练速度,这也正是为什么我们需要对数据都要做一个归一化预处理的原因。

       对于深度网络的训练是一个复杂的过程,只要网络的前面几层发生微小的改变,那么后面几层就会被累积放大下去。一旦网络某一层的输入数据的分布发生改变,那么这一层网络就需要去适应学习这个新的数据分布,所以如果训练过程中,训练数据的分布一直在发生变化,那么将会影响网络的训练速度。

        我们知道网络一旦train起来,那么参数就要发生更新,除了输入层的数据外(因为输入层数据,我们已经人为的为每个样本归一化),后面网络每一层的输入数据分布是一直在发生变化的,因为在训练的时候,前面层训练参数的更新将导致后面层输入数据分布的变化。以网络第二层为例:网络的第二层输入,是由第一层的参数和input计算得到的,而第一层的参数在整个训练过程中一直在变化,因此必然会引起后面每一层输入数据分布的改变。我们把网络中间层在训练过程中,数据分布的改变称之为:“Internal  Covariate Shift”。Paper《Batch Normalization: Accelerating Deep Network Training by  Reducing Internal Covariate Shift》所提出的算法,就是要解决在训练过程中,中间层数据分布发生改变的情况,于是就有了Batch  Normalization这个牛逼算法的诞生。

一、Motivation

        作者认为:网络训练过程中参数不断改变导致后续每一层输入的分布也发生变化,而学习的过程又要使每一层适应输入的分布,因此我们不得不降低学习率、小心地初始化。作者将分布发生变化称之为 internal covariate shift。

       大家应该都知道,我们一般在训练网络的时会将输入减去均值,还有些人甚至会对输入做白化等操作,目的是为了加快训练。为什么减均值、白化可以加快训练呢?这里做一个简单地说明:

      首先,图像数据是高度相关的,假设其分布如下图a所示(简化为2维)。由于初始化的时候,我们的参数一般都是0均值的,因此开始的拟合y=Wx+b,基本过原点附近,如图b红色虚线。因此,网络需要经过多次学习才能逐步达到如紫色实线的拟合,即收敛的比较慢。如果我们对输入数据先作减均值操作,如图c,显然可以加快学习。更进一步的,我们对数据再进行去相关操作,使得数据更加容易区分,这样又会加快训练,如图d。  
Batch Normalization批量归一化

 

二、初识BN(Batch  Normalization)

1、BN概述

     就像**函数层、卷积层、全连接层、池化层一样,BN(Batch Normalization)也属于网络的一层。在前面我们提到网络除了输出层外,其它层因为低层网络在训练的时候更新了参数,而引起后面层输入数据分布的变化。这个时候我们可能就会想,如果在每一层输入的时候,再加个预处理操作那该有多好啊,比如网络第三层输入数据X3(X3表示网络第三层的输入数据)把它归一化至:均值0、方差为1,然后再输入第三层计算,这样我们就可以解决前面所提到的“Internal Covariate Shift”的问题了。

      而事实上,paper的算法本质原理就是这样:在网络的每一层输入的时候,又插入了一个归一化层,也就是先做一个归一化处理,然后再进入网络的下一层。不过文献归一化层,可不像我们想象的那么简单,它是一个可学习、有参数的网络层。既然说到数据预处理,下面就先来复习一下最强的预处理方法:白化。

2、预处理操作选择

      说到神经网络输入数据预处理,最好的算法莫过于白化预处理。白化的方式有好几种,常用的有PCA白化(由于原始数据存在冗余,通过PCA降维,使用最低维度来表达数据,消除冗余,减速训练):即对数据进行PCA操作之后,在进行方差归一化。这样数据基本满足0均值、单位方差、弱相关性。作者首先考虑,对每一层数据都使用白化操作,但分析认为这是不可取的。因为白化需要计算协方差矩阵、求逆等操作,计算量很大,此外,反向传播时,白化操作不一定可导。所以在深度学习中,其实很少用到白化。经过白化预处理后,数据满足条件:a、特征之间的相关性降低,这个就相当于pca;b、数据均值、标准差归一化,也就是使得每一维特征均值为0,标准差为1。如果数据特征维数比较大,要进行PCA,也就是实现白化的第1个要求,是需要计算特征向量,计算量非常大,于是为了简化计算,作者忽略了第1个要求,采用Normalization方法。仅仅使用了下面的公式进行预处理。

         归一化公式:

                                                    Batch Normalization批量归一化 

       公式简单粗糙,但是依旧很牛逼。因此后面我们也将用这个公式,对某一个层网络的输入数据做一个归一化处理。需要注意的是,我们训练过程中采用batch随机梯度下降,上面的Batch Normalization批量归一化指的是每一批训练数据神经元Batch Normalization批量归一化的平均值;然后分母就是每一批数据神经元Batch Normalization批量归一化**度的一个标准差了。 

三、BN算法实现

1、BN算法概述

       经过前面简单介绍,这个时候可能我们会想当然的以为:好像很简单的样子,不就是在网络中间层数据做一个归一化处理嘛,然而其实实现起来并不是那么简单的。其实如果是仅仅使用上面的归一化公式,对网络某一层A的输出数据做归一化,然后送入网络下一层B,这样是会影响到本层网络A所学习到的特征的。打个比方,比如我网络中间某一层学习到特征数据本身就分布在S型**函数的两侧,你强制把它给我归一化处理、标准差也限制在了1,把数据变换成分布于s函数的中间部分,这样就相当于我这一层网络所学习到的特征分布被你搞坏了,如果简单的这么干,会降低层的表达能力。比如下图,在使用sigmoid**函数的时候,如果把数据限制到0均值单位方差,那么相当于只使用了**函数中近似线性的部分,这显然会降低模型表达能力。 
                                              Batch Normalization批量归一化 

这可怎么办?于是文献使出了一招惊天地泣鬼神的招式:变换重构,为BN增加了2个可学习参数γ、β,用来保持模型的表达能力。这就是算法关键之处:

                                                                   Batch Normalization批量归一化 

    上述公式中用到了均值E和方差Var,需要注意的是理想情况下E和Var应该是针对整个数据集的,但显然这是不现实的。因此,作者做了简化,用一个Batch的均值和方差作为对整个数据集均值和方差的估计。

每一个神经元Batch Normalization批量归一化都会有一对这样的参数γ、β。这样其实当:

                                                                   Batch Normalization批量归一化Batch Normalization批量归一化

      是可以恢复出原始的某一层所学到的特征的。因此我们引入了这个可学习重构参数γ、β,让我们的网络可以学习恢复出原始网络所要学习的特征分布

                                    Batch Normalization批量归一化

(我的理解:由于x归一化成Batch Normalization批量归一化,传入下一层,继续进行神经网络计算,这种方法会丢失一些特征,而引入γ、β参数,用于保存有该层所学到特征(这些特征没有带入到下一网络层进行计算))

最后Batch Normalization网络层的前向传导过程公式就是:

                                                              Batch Normalization批量归一化

                              Batch Normalization批量归一化

上面的公式中m指的是mini-batch size。

2、源码实现

            m = K.mean(X, axis=-1, keepdims=True)#计算均值
            std = K.std(X, axis=-1, keepdims=True)#计算标准差
            X_normed = (X - m) / (std + self.epsilon)#归一化
            out = self.gamma * X_normed + self.beta#重构变换

上面的X是一个二维矩阵,对于源码的实现就几行代码而已,轻轻松松。

3、实战使用

(1)需要注意的一点是Batch Normalization在training和testing时行为有所差别。Training时均值u和σ由当前batch计算得出;在Testing时μ和σ应使用Training时保存的均值或类似的经过处理的值,而不是由当前batch计算。网络一旦训练完毕,参数都是固定的。

      实际测试网络的时候,我们依然会应用下面的式子: 
                                                                 Batch Normalization批量归一化 
特别注意: 这里的均值和方差已经不是针对某一个Batch了,而是针对整个数据集而言。因此,在训练过程中除了正常的前向传播和反向求导之外,我们还要记录每一个Batch的均值和方差,以便训练完成之后按照下式计算整体的均值和方差: 

                                                                      Batch Normalization批量归一化

上面简单理解就是:对于均值来说直接计算所有batch的均值u值的平均值;然后对于标准偏差采用每个batch的Batch Normalization批量归一化的无偏估计。最后测试阶段,BN的使用公式就是:

                                                                     Batch Normalization批量归一化

(2)根据文献说,BN可以应用于一个神经网络的任何神经元上。文献主要是把BN变换,置于网络**函数层的前面。在没有采用BN的时候,**函数层是这样的:

                                                                               Batch Normalization批量归一化

也就是我们希望一个**函数,比如s型函数s(x)的自变量x是经过BN处理后的结果。因此前向传导的计算公式就应该是:

                                                                            Batch Normalization批量归一化

其实因为偏置参数b经过BN层后其实是没有用的,最后也会被均值归一化,当然BN层后面还有个β参数作为偏置项,所以b这个参数就可以不用了。因此最后把BN层+**函数层就变成了:

                                                                              Batch Normalization批量归一化

四、Batch Normalization在CNN中的使用

       通过上面的学习,我们知道BN层是对于每个神经元做归一化处理,甚至只需要对某一个神经元进行归一化,而不是对一整层网络的神经元进行归一化。既然BN是对单个神经元的运算,那么在CNN中卷积层上要怎么搞?假如某一层卷积层有6个特征图,每个特征图的大小是100*100,这样就相当于这一层网络有6*100*100个神经元,如果采用BN,就会有6*100*100个参数γ、β,这样岂不是太恐怖了。因此卷积层上的BN使用,其实也是使用了类似权值共享的策略,把一整张特征图当做一个神经元进行处理。

       卷积神经网络经过卷积后得到的是一系列的特征图,如果min-batch sizes为m,那么网络某一层输入数据可以表示为四维矩阵(m,f,p,q),m为min-batch sizes,f为特征图个数,p、q分别为特征图的宽高。在cnn中我们可以把每个特征图看成是一个特征处理(一个神经元),因此在使用Batch Normalization,mini-batch size 的大小就是:m*p*q,于是对于每个特征图都只有一对可学习参数:γ、β。说白了吧,这就是相当于求取所有样本所对应的一个特征图的所有神经元的平均值、方差,然后对这个特征图神经元做归一化。下面是来自于keras卷积层的BN实现一小段主要源码:

           input_shape = self.input_shape
            reduction_axes = list(range(len(input_shape)))
            del reduction_axes[self.axis]
            broadcast_shape = [1] * len(input_shape)
            broadcast_shape[self.axis] = input_shape[self.axis]
            if train:
                m = K.mean(X, axis=reduction_axes)
                brodcast_m = K.reshape(m, broadcast_shape)
                std = K.mean(K.square(X - brodcast_m) + self.epsilon, axis=reduction_axes)
                std = K.sqrt(std)
                brodcast_std = K.reshape(std, broadcast_shape)
                mean_update = self.momentum * self.running_mean + (1-self.momentum) * m
                std_update = self.momentum * self.running_std + (1-self.momentum) * std
                self.updates = [(self.running_mean, mean_update),
                                (self.running_std, std_update)]
                X_normed = (X - brodcast_m) / (brodcast_std + self.epsilon)
            else:
                brodcast_m = K.reshape(self.running_mean, broadcast_shape)
                brodcast_std = K.reshape(self.running_std, broadcast_shape)
                X_normed = ((X - brodcast_m) /
                            (brodcast_std + self.epsilon))
            out = K.reshape(self.gamma, broadcast_shape) * X_normed + K.reshape(self.beta, broadcast_shape)

采用这个方法网络的训练速度快到惊人啊,感觉训练速度是以前的十倍以上,再也不用担心自己这破电脑每次运行一下,训练一下都要跑个两三天的时间。

五、BN with TF

      BN在TensorFlow中主要有两个函数:tf.nn.moments以及tf.nn.batch_normalization,两者需要配合使用,前者用来返回均值和方差,后者用来进行批处理(BN)

1、tf.nn.moments:

该函数返回两个张量,均值mean和方差variance。

moments(
    x,
    axes,
    shift=None,
    name=None,
    keep_dims=False
)

    其中参数 x 为要传递的tensor,axes传递要进行计算的维度, 即想要 normalize的维度, [0] 代表 batch 维度,如果是图像数据,可以传入 [0, 1, 2],相当于求[batch, height, width] 的均值/方差,注意不要加入channel 维度。

我们需要利用这个函数计算出BN算法需要的前两项:

# 计算Wx_plus_b 的均值与方差,其中axis = [0] 表示想要标准化的维度
img_shape= [128, 32, 32, 64]
Wx_plus_b = tf.Variable(tf.random_normal(img_shape))
axis = list(range(len(img_shape)-1))               # [0,1,2] 
wb_mean, wb_var = tf.nn.moments(Wx_plus_b, axis)

函数说明:tf.random_normal用于从正态分布中输出随机值。

random_normal(shape,mean=0.0,stddev=1.0,dtype=tf.float32,seed=None,name=None)
  • shape:一个一维整数张量或Python数组。代表张量的形状。
  • mean:数据类型为dtype的张量值或Python值。是正态分布的均值。
  • stddev:数据类型为dtype的张量值或Python值。是正态分布的标准差。
  • dtype: 输出的数据类型。
  • seed:一个Python整数。是随机种子。
  • name: 操作的名称(可选)

运行结果:均值为0,方差为1

*** wb_mean ***
[  1.05310767e-03   1.16801530e-03   4.95071337e-03  -1.50891789e-03
  -2.95298663e-03  -2.07848335e-03  -3.81800164e-05  -3.11688287e-03
   3.26496479e-03  -2.68524280e-04  -2.08893605e-03  -3.05374013e-03
   1.43721583e-03  -3.61034041e-03  -3.03616724e-03  -1.10225368e-03
   6.14093244e-03  -1.37914100e-03  -1.13333750e-03   3.53972078e-03
  -1.48577197e-03   1.04353309e-03   3.27868876e-03  -1.40919012e-03
   3.09609319e-03   1.98166977e-04  -5.25404140e-03  -6.03850756e-04
  -1.04614964e-03   2.90997117e-03   5.78491192e-04  -4.97420435e-04
   3.03052540e-04   2.46527663e-04  -4.70882794e-03   2.79057049e-03
  -1.98713480e-03   4.13944060e-03  -4.80978837e-04  -3.90357309e-04
   9.11145413e-04  -4.80215019e-03   6.26503082e-04  -2.76877987e-03
   3.79961479e-04   5.36157866e-04  -2.12549698e-03  -5.41620655e-03
  -1.93006988e-03  -8.54363534e-05   4.97094262e-03  -2.45843385e-03
   4.16610064e-03   2.44746287e-03  -4.15429426e-03  -6.64028199e-03
   2.56747357e-03  -1.63110415e-03  -1.53350492e-03  -7.66420271e-04
  -1.81624549e-03   2.16634944e-03   1.74984348e-03  -4.17272677e-04]
*** wb_var ***
[ 0.99813616  0.9983741   1.00014114  1.0012747   0.99496585  1.00168002
  1.00439012  0.99607879  1.00104094  0.99969071  1.01024568  0.99614906
  1.00092578  0.99977148  1.00447345  0.99580348  0.99797201  0.99119431
  1.00352168  0.9958936   0.99980813  1.00598109  1.00050855  0.99667317
  0.99352562  1.0036608   0.99794698  0.99324805  0.99862647  0.99930048
  0.99658304  1.00278556  0.99731135  1.00254881  0.99352133  1.00371397
  1.00258803  1.00388253  1.00404358  0.99454063  0.99434716  1.00087452
  1.00818515  1.00019705  0.99542576  1.00410056  0.99707311  1.00215423
  1.00199771  0.99394888  0.9973973   1.00197709  0.99835181  0.99944276
  0.99977624  0.99892712  0.99871159  0.99913275  1.00471914  1.00210452
  0.99568754  0.99547535  0.99983472  1.00523198]**重点内容**

我们假设图片的shape[128, 32, 32, 64],它的运算方式如图:

                   Batch Normalization批量归一化

2、tf.nn.batch_normalization

batch_normalization(
    x,
    mean,
    variance,
    offset,
    scale,
    variance_epsilon,
    name=None
)
  • x:代表任意维度的输入张量。
  • mean:代表样本的均值。
  • variance:代表样本的方差。
  • offset:代表偏移,即相加一个转化值,也是公式中的beta。
  • scale:代表缩放,即乘以一个转化值,也是公式中的gamma。
  • variance_epsilon:是为了避免分母为0的情况下,给分母加上的一个极小值,默认即可。
  • name:名称。
scale = tf.Variable(tf.ones([64]))
offset = tf.Variable(tf.zeros([64]))
variance_epsilon = 0.001
Wx_plus_b = tf.nn.batch_normalization(Wx_plus_b, wb_mean, wb_var, offset, scale, variance_epsilon)

# 根据公式我们也可以自己写一个
Wx_plus_b1 = (Wx_plus_b - wb_mean) / tf.sqrt(wb_var + variance_epsilon)
Wx_plus_b1 = Wx_plus_b1 * scale + offset
# 因为底层运算方式不同,实际上自己写的最后的结果与直接调用tf.nn.batch_normalization获取的结果并不一致。

#Wx_plus_b
[[[[  3.32006335e-01  -1.00865233e+00   4.68401730e-01 ...,
     -1.31523395e+00  -1.13771069e+00  -2.06656289e+00]
   [  1.92613199e-01  -1.41019285e-01   1.03402412e+00 ...,
      1.66336447e-01   2.34183773e-01   1.18540943e+00]
   [ -7.14844346e-01  -1.56187916e+00  -8.09686005e-01 ...,
     -4.23679769e-01  -4.32125211e-01  -3.35091174e-01]
   ...,

from:https://blog.****.net/hjimce/article/details/50866313

from:https://blog.****.net/fontthrone/article/details/76652772

from:https://blog.****.net/shuzfan/article/details/50723877

form :https://www.cnblogs.com/king-lps/p/8378561.html