欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

python+tensorflow实现批标准化(Batch Normalization)

程序员文章站 2022-07-04 22:59:31
...

背景

       论文:Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift

        批标准化(Batch Normalization )简称BN算法,是为了克服神经网络层数加深导致难以训练而诞生的一个算法。根据Internal Covariate Shift理论,当训练集的样本数据和目标样本集分布不一致的时候,训练得到的模型无法很好的泛化。

         而在神经网络中,每一层的输入在经过层内操作之后必然会导致与原来对应的输入信号分布不同,并且前层神经网络的增加会被后面的神经网络不对的累积放大。这个问题的一个解决思路就是根据训练样本与目标样本的比例对训练样本进行一个矫正,而BN算法(批标准化)则可以用来规范化某些层或者所有层的输入,从而固定每层输入信号的均值与方差。

使用方法

         批标准化一般用在非线性映射(**函数)之前,对y= Wx + b进行规范化,使结果(输出信号的各个维度)的均值都为0,方差为1,让每一层的输入有一个稳定的分布会有利于网络的训练

         在神经网络收敛过慢或者梯度爆炸时的那个无法训练的情况下都可以尝试该方法。

优点

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

计算公式

python+tensorflow实现批标准化(Batch Normalization)

其过程类似于归一化但是又不同.

参考

BN原理的详细参考建议:BN学习笔记:点击这里

 

                                         BN with TensorFlow

(1)组成部分

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

A. tf.nn.moments————TensorFlow中的函数(用来返回均值和方差)

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

  Returns:
    Two `Tensor` objects: `mean` and `variance`.

其中参数 x 为要传递的tensor,axes是个int数组,传递要进行计算的维度,返回值是两个张量: mean and variance,我们需要利用这个函数计算出BN算法需要的前两项,公式见前面的原理部分。


参考代码如下:

   假设输入的样本为 [batchsize,Height,Width,Channels]=[128, 32, 32,64]

# 计算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)

运行结果,因为初始的数据是随机的,所以每次的运行结果并不一致:

*** 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],它的运算方式如图:

python+tensorflow实现批标准化(Batch Normalization)

B. tf.nn.batch_normalization————TensorFlow中的函数(用来进行批处理(BN))

batch_normalization(
    x,
    mean,
    variance,
    offset,
    scale,
    variance_epsilon,
    name=None
)

其中x为输入的tensor,mean、variancetf.nn.moments()求出,而offset、scale一般分别初始化为0和1,variance_epsilon一般设为比较小的数字即可,参考代码如下:

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获取的结果并不一致

运行结果,因为初始的数据是随机的,所以每次的运行结果并不一致,但是本例子中的计算差异始终存在:

# 这里我们只需比较前两的矩阵即可发现存在的数值差异
[[[[  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]
   ..., 

[[[[  3.31096262e-01  -1.01013660e+00   4.63186830e-01 ...,
     -1.31972826e+00  -1.13898540e+00  -2.05973744e+00]
   [  1.91642866e-01  -1.42231822e-01   1.02848673e+00 ...,
      1.64460197e-01   2.32336998e-01   1.18214881e+00]
   [ -7.16206789e-01  -1.56353664e+00  -8.14172268e-01 ...,
     -4.26598638e-01  -4.33694094e-01  -3.33635926e-01]

 

 

(2) 完整的代码

# - * - coding: utf - 8 -*-
#
# 作者:田丰(FontTian)
# 创建时间:'2017/8/2'
# 邮箱:aaa@qq.com
# CSDN:http://blog.csdn.net/fontthrone

import tensorflow as tf
import os

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
# 计算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))
wb_mean, wb_var = tf.nn.moments(Wx_plus_b, axis)

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

with tf.Session() as sess:
    tf.global_variables_initializer().run()

    print('*** wb_mean ***')
    print(sess.run(wb_mean))
    print('*** wb_var ***')
    print(sess.run(wb_var))
    print('*** Wx_plus_b ***')
    print(sess.run(Wx_plus_b))
    print('**** Wx_plus_b1 ****')
    print(sess.run(Wx_plus_b1))

(3)自定义的函数封装形式

def batch_norm(x, is_training):
    # x:input with shape=[batchsize,Height,Width,Channels]
    # is_training: Is it in training? Value = True or False
    epsilon=1e-3
    phase_train = tf.convert_to_tensor(is_training, dtype = tf.bool)
    axis = list(range(len(x.get_shape()) - 1))
    with tf.variable_scope('batch_norm'):
        n_out = int(x.get_shape()[3])
        beta = tf.Variable(tf.constant(0.0, shape = [n_out], dtype = x.dtype),
                           name = 'beta', trainable = True, dtype = x.dtype)
        gamma = tf.Variable(tf.constant(1.0, shape = [n_out], dtype = x.dtype),
                            name = 'gamma', trainable = True, dtype = x.dtype)
        batch_mean, batch_var = tf.nn.moments(x, axis, name = 'moments')
           
    ####滑动平均(3个步骤)######对模型参数进行平均得到的模型往往比单个模型的结果要好很多
#(1)指定decay参数创建实例: 
        ema = tf.train.ExponentialMovingAverage(decay = 0.9)

        def mean_var_with_update():
#(2)对模型变量使用apply方法:
#ema.apply后面的括号是更新列表,每次运行sess.run(ema_op)时,对更新列表中的元素求滑动平均值,在实
#际应用中会使用tf.trainable_variable()自动将所有待训练的参数汇总为列表apply方法会为每个变量
#(也可以指定特定变量)创建各自的shadow variable, 即影子变量。之所以叫影子变量,是因为它会全程
#跟随训练中的模型变量。影子变量会被初始化为模型变量的值,然后,每训练一个step,就更新一次。更新的
#方式为:
   # shadow_variable = decay * shadow_variable + (1 - decay) * updated_model_variable
            ema_apply_op = ema.apply([batch_mean, batch_var])
            with tf.control_dependencies([ema_apply_op]):
          #(3)在优化方法使用梯度更新模型参数后执行MovingAverage:
                return tf.identity(batch_mean), tf.identity(batch_var)

        # return mean_var_with_update if true, else return lambda
        mean, var = control_flow_ops.cond(phase_train, mean_var_with_update,
                                          lambda:(ema.average(batch_mean), ema.average(batch_var))
                                          )
        normed = tf.nn.batch_normalization(x, mean, var, beta, gamma, epsilon)

    return normed

 

 

附录:

假设注入样本大小为x.get_shape() = [batchsize,Height,Width,Channels] = [128, 32, 32,64]tf.nn.moments计算时,保留Channels维度(即axis=[3]),其他维度(即axis=[0,1,2])进行总体计算降掉,所以meanvariance的大小均为:shape=(Channels,)=(64,).执行过程如图所示:

python+tensorflow实现批标准化(Batch Normalization)