一.BN理论
概述
机器学习领域有个很重要的假设:独立同分布假设IID,就是假设训练数据和测试数据是满足相同分布的,这是通过训练数据获得的模型能够在测试集获得好的效果的一个基本保障。
Batch normalization就是在深度神经网络训练过程中使得每一层神经网络的输入保持相同分布的。(BN是在传统的基础上,不仅仅只对输入层的输入数据进行标准化,还对每个隐藏层的输入进行标准化)
背景知识-归一化的好处:
我们知道在神经网络训练开始前,都要对输入数据做一个归一化处理,那么具体为什么需要归一化呢?归一化后有什么好处呢?原因在于神经网络学习过程本质就是为了学习数据分布,一旦训练数据与测试数据的分布不同,那么网络的泛化能力也大大降低;另外一方面,一旦每批训练数据的分布各不相同(batch 梯度下降),那么网络就要在每次迭代都去学习适应不同的分布,这样将会大大降低网络的训练速度,这也正是为什么我们需要对数据都要做一个归一化预处理的原因。
Internal Covariate Shift问题:
深度学习这种包含很多隐层的网络结构,在训练过程中,因为各层参数老在变,所以每个隐层都会面临covariate shift的问题,也就是在训练过程中,隐层的输入分布老是变来变去,这就是所谓的“Internal Covariate Shift”(也就是,数据分布的改变),Internal指的是深层网络的隐层,是发生在网络内部的事情,而不是covariate shift问题只发生在输入层。(带来影响:对于深度网络的训练是一个复杂的过程,只要网络的前面几层发生微小的改变,那么后面几层就会被累积放大下去。一旦网络某一层的输入数据的分布发生改变,那么这一层网络就需要去适应学习这个新的数据分布,所以如果训练过程中,训练数据的分布一直在发生变化,那么将会影响网络的训练速度。)
启发:
BN不是凭空拍脑袋拍出来的好点子,它是有启发来源的:之前的研究表明如果在图像处理中对输入图像进行白化(Whiten)操作的话——所谓白化,就是对输入数据分布变换到0均值,单位方差的正态分布——那么神经网络会较快收敛,那么BN作者就开始推论了:图像是深度神经网络的输入层,做白化能加快收敛,那么其实对于深度网络来说,其中某个隐层的神经元是下一层的输入,意思是其实深度神经网络的每一个隐层都是输入层,不过是相对下一层来说而已,那么能不能对每个隐层都做白化呢?这就是启发BN产生的原初想法,而BN也确实就是这么做的,可以理解为对深层神经网络每个隐层神经元的激活值做简化版本的白化操作。
BatchNorm的基本思想:
能不能让每个隐层节点的激活输入分布固定下来呢?这样就避免了“Internal Covariate Shift”问题了。
因为深层神经网络在做非线性变换前的激活输入值(就是那个x=WU+B,U是输入)随着网络深度加深或者在训练过程中,其分布逐渐发生偏移或者变动,之所以训练收敛慢,一般是整体分布逐渐往非线性函数的取值区间的上下限两端靠近(对于Sigmoid函数来说,意味着激活输入值WU+B是大的负值或正值),所以这导致反向传播时低层神经网络的梯度消失,这是训练深层神经网络收敛越来越慢的本质原因。
BN就是通过一定的规范化手段,把每层神经网络任意神经元这个输入值的分布强行拉回到均值为0方差为1的标准正态分布;其实就是把越来越偏的分布强制拉回比较标准的分布,这样使得激活输入值落在非线性函数对输入比较敏感的区域,这样输入的小变化就会导致损失函数较大的变化,意思是这样让梯度变大,避免梯度消失问题产生,而且梯度变大意味着学习收敛速度快,能大大加快训练速度。
BatchNorm是基于Mini-Batch SGD的;Mini-Batch SGD相对于One Example SGD的两个优势:梯度更新方向更准确;并行计算速度快。
BN benefit
(1)你可以选择比较大的初始学习率,让你的训练速度飙涨。以前还需要慢慢调整学习率,甚至在网络训练到一半的时候,还需要想着学习率进一步调小的比例选择多少比较合适,现在我们可以采用初始很大的学习率,然后学习率的衰减速度也很大,因为这个算法收敛很快。当然这个算法即使你选择了较小的学习率,也比以前的收敛速度快,因为它具有快速训练收敛的特性;
(2)你再也不用去理会过拟合中drop out、L2正则项参数的选择问题,采用BN算法后,你可以移除这两项了参数,或者可以选择更小的L2正则约束参数了,因为BN具有提高网络泛化能力的特性;
(3)再也不需要使用局部响应归一化层了(局部响应归一化是Alexnet网络用到的方法,搞视觉的估计比较熟悉),因为BN本身就是一个归一化网络层。
BN潜在问题:
当拉回到非线性函数的“线性区域”之后,多层的线性函数变换其实这个深层是没有意义的,因为多层线性网络跟一层线性网络是等价的。这意味着网络的表达能力下降了,这也意味着深度的意义就没有了。所以BN为了保证非线性的获得,对变换后的满足均值为0方差为1的x又进行了scale加上shift操作(y=scale*x+shift),每个神经元增加了两个参数scale和shift参数,这两个参数是通过训练学习到的,意思是通过scale和shift把这个值从标准正态分布左移或者右移一点并长胖一点或者变瘦一点,每个实例挪动的程度不一样,这样等价于非线性函数的值从正中心周围的线性区往非线性区动了动。核心思想应该是想找到一个线性和非线性的较好平衡点,既能享受非线性的较强表达能力的好处,又避免太靠非线性区两头使得网络收敛速度太慢。
参考原文:
【深度学习】深入理解Batch Normalization批标准化 - 郭耀华 - 博客园
https://www.cnblogs.com/guoyaohua/p/8724433.html(写错了,先BN后激活函数)
深度学习(二十九)Batch Normalization 学习笔记 - hjimce的专栏 - CSDN博客
https://blog.csdn.net/hjimce/article/details/50866313
二.代码
来源:
tensorflow中batch normalization的用法 - handspeaker - 博客园
https://www.cnblogs.com/hrlnw/p/7227447.html
import tensorflow as tf
import os
from tensorflow.examples.tutorials.mnist import input_data
tf.logging.set_verbosity(tf.logging.INFO)
if __name__ == '__main__':
mnist = input_data.read_data_sets('F:/vsCode_tensorflow/PKU_TF/PKU_TF_shuzi/data2', one_hot=True)
x = tf.placeholder(tf.float32, [None, 784])
y_ = tf.placeholder(tf.float32, [None, 10])
image = tf.reshape(x, [-1, 28, 28, 1])
# 此写法特殊: 但是也是计算卷积之后,(加bias),再BN,然后进行max Pooling
conv1 = tf.layers.conv2d(image, filters=32, kernel_size=[3, 3], strides=[1, 1], padding='same',
activation=tf.nn.relu,
kernel_initializer=tf.truncated_normal_initializer(stddev=0.1),
name='conv1')
bn1 = tf.layers.batch_normalization(conv1, training=True, name='bn1')
pool1 = tf.layers.max_pooling2d(bn1, pool_size=[2, 2], strides=[2, 2], padding='same', name='pool1')
conv2 = tf.layers.conv2d(pool1, filters=64, kernel_size=[3, 3], strides=[1, 1], padding='same',
activation=tf.nn.relu,
kernel_initializer=tf.truncated_normal_initializer(stddev=0.1),
name='conv2')
bn2 = tf.layers.batch_normalization(conv2, training=True, name='bn2')
pool2 = tf.layers.max_pooling2d(bn2, pool_size=[2, 2], strides=[2, 2], padding='same', name='pool2')
# 全连接层 1层 全连接层不用BN
flatten_layer = tf.contrib.layers.flatten(pool2, 'flatten_layer')
weights = tf.get_variable(shape=[flatten_layer.shape[-1], 10], dtype=tf.float32,
initializer=tf.truncated_normal_initializer(stddev=0.1), name='fc_weights')
biases = tf.get_variable(shape=[10], dtype=tf.float32,
initializer=tf.constant_initializer(0.0), name='fc_biases')
logit_output = tf.nn.bias_add(tf.matmul(flatten_layer, weights), biases, name='logit_output')
# softmax 之后 ,再求loss
cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=logit_output))
pred_label = tf.argmax(logit_output, 1)
label = tf.argmax(y_, 1)
accuracy = tf.reduce_mean(tf.cast(tf.equal(pred_label, label), tf.float32))
update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
global_step = tf.get_variable('global_step', [], dtype=tf.int32,
initializer=tf.constant_initializer(0), trainable=False)
learning_rate = tf.train.exponential_decay(learning_rate=0.1, global_step=global_step, decay_steps=5000,
decay_rate=0.1, staircase=True)
opt = tf.train.AdadeltaOptimizer(learning_rate=learning_rate, name='optimizer')
with tf.control_dependencies(update_ops):
grads = opt.compute_gradients(cross_entropy)
train_op = opt.apply_gradients(grads, global_step=global_step)
tf_config = tf.ConfigProto()
tf_config.gpu_options.allow_growth = True
tf_config.allow_soft_placement = True
sess = tf.InteractiveSession(config=tf_config)
sess.run(tf.global_variables_initializer())
# only save trainable and bn variables
var_list = tf.trainable_variables()
if global_step is not None:
var_list.append(global_step)
g_list = tf.global_variables()
bn_moving_vars = [g for g in g_list if 'moving_mean' in g.name]
bn_moving_vars += [g for g in g_list if 'moving_variance' in g.name]
var_list += bn_moving_vars
saver = tf.train.Saver(var_list=var_list,max_to_keep=5)
# save all variables
# saver = tf.train.Saver(max_to_keep=5)
if tf.train.latest_checkpoint('F:/DL/lstm_blog_case/batch_norm1/bn1_ckpt/') is not None:
saver.restore(sess, tf.train.latest_checkpoint('F:/DL/lstm_blog_case/batch_norm1/bn1_ckpt/'))
train_loops = 10000
for i in range(train_loops):
batch_xs, batch_ys = mnist.train.next_batch(32)
_, step, loss, acc = sess.run([train_op, global_step, cross_entropy, accuracy],
feed_dict={x: batch_xs, y_: batch_ys})
if step % 100 == 0: # print training info
log_str = 'step:%d \t loss:%.6f \t acc:%.6f' % (step, loss, acc)
tf.logging.info(log_str)
if step % 1000 == 0: # save current model
save_path = os.path.join('F:/DL/lstm_blog_case/batch_norm1/bn1_ckpt/', 'mnist.ckpt')
saver.save(sess, save_path, global_step=step)
sess.close()