详解TensorFlow的 tf.layers.batch_normalization 函数:批归一化层

  • Post category:Python

TensorFlow中tf.layers.batch_normalization函数详解

1. batch normalization作用

Batch normalization(批量归一化)是深度学习中一种常用的正则化方式,加快神经网络训练的收敛速度,并且增强神经网络对输入数据的鲁棒性。

batch normalization实际做了两件事:

  1. 对每个batch内的数据进行归一化
  2. 按照一定比例将数据进行重构,使得模型对于某些特定的数据的变化更加敏感。

2. tf.layers.batch_normalization函数

batch normalization包括很多具体的实现方式,其中在TensorFlow里,tf.layers模块提供了实现batch normalization的API函数tf.layers.batch_normalization。下面我来具体讲解这个函数的使用方法。

2.1 函数参数

进入正题,我们先来看下tf.layers.batch_normalization这个函数的参数:

tf.layers.batch_normalization(
    inputs,
    axis=-1,
    momentum=0.99,
    epsilon=0.001,
    center=True,
    scale=True,
    beta_initializer=tf.zeros_initializer(),
    gamma_initializer=tf.ones_initializer(),
    moving_mean_initializer=tf.zeros_initializer(),
    moving_variance_initializer=tf.ones_initializer(),
    beta_regularizer=None,
    gamma_regularizer=None,
    beta_constraint=None,
    gamma_constraint=None,
    training=False,
    trainable=True,
    name=None,
    reuse=None,
    renorm=False,
    renorm_clipping=None,
    renorm_momentum=0.99,
    fused=None,
    virtual_batch_size=None,
    adjustment=None
)

可以看到,这个函数的参数有点多,但仔细看一下会发现很多参数都是可以默认的。那下面我来逐一解释下每个参数的作用:

  • inputs:输入的tensor。一般是2~5维的tensor,代表不同batch大小的多维数据。
  • axis:代表需要归一化的维度,默认是最后一维,即按照channel数进行归一化。
  • momentum:动量参数,一般取0.99,代表模型更新时新权重与旧权重的加权平均值。
  • epsilon:防止除数为0的参数,默认是0.001。
  • center:是否添加偏移参数beta,类似于偏置项,对应原公式中的beta,默认为True。
  • scale:是否添加缩放参数gamma,对应原公式中的gamma,默认为True。
  • beta_initializer:偏移量的初始化函数,默认用全零函数。
  • gamma_initializer:缩放量的初始化函数,默认用全一的函数。
  • moving_mean_initializer:求均值时的初始化函数,默认为全零函数。
  • moving_variance_initializer:求方差时的初始化函数,默认为全一函数。
  • beta_regularizer:偏移量的正则化参数,可选参数。
  • gamma_regularizer:缩放量的正则化参数,可选参数。
  • beta_constraint:偏移量的约束条件,可选参数。
  • gamma_constraint:缩放量的约束条件,可选参数。
  • training:训练或测试的时间,主要用于batch内归一化的算法选择。默认是False.
  • trainable:是否更新beta和gamma参数,如果为False则其不更新。默认是True。
  • name:tf.variable_scope下的名字,默认是’BatchNorm’
  • reuse:是否reuse该变量,默认是False。
  • renorm:是否进行重归一化,一般不用设置,默认是False。
  • renorm_clipping:修整(l2范数)落在[trainable_variables_full_batch_size/renorm_clipping, trainable_variables_full_batch_size*renorm_clipping]之间。默认为None。
  • renorm_momentum:重归一化动量,默认是0.99。
  • fused:主要是cuda加速,设置False代表不使用cuda性能加速,默认是True。
  • virtual_batch_size:这个我不太清楚,可以忽略。
  • adjustment:似乎是一个系数,类似于momentum参数,使用攻略的过程中并没有太用到。

2.2 demo1:训练过程中使用tf.layers.batch_normalization

现在我们来看一个样例,在训练的过程中如何使用tf.layers.batch_normalization函数。假设我们的原始数据是一个$1x28x28x3$的tensor,batch_size是128。

import tensorflow as tf

def conv2d(inputs, filters, kernel_size, strides, padding='SAME', activation=tf.nn.relu, name=None):
    return tf.layers.conv2d(
        inputs=inputs,
        filters=filters,
        kernel_size=kernel_size,
        strides=strides,
        padding=padding,
        activation=activation,
        kernel_initializer=tf.contrib.layers.xavier_initializer(),
        bias_initializer=tf.zeros_initializer(),
        name=name
    )

def batch_norm(inputs, training=True, name=None):
    return tf.layers.batch_normalization(
        inputs=inputs,
        axis=-1,
        momentum=0.99,
        epsilon=0.001,
        center=True,
        scale=True,
        training=training,
        name=name
    )

def dense(inputs, units, activation=tf.nn.relu, name=None):
    return tf.layers.dense(
        inputs=inputs,
        units=units,
        activation=activation,
        kernel_initializer=tf.contrib.layers.xavier_initializer(),
        bias_initializer=tf.zeros_initializer(),
        name=name
    )

# 输入参数
inputs = tf.placeholder(dtype=tf.float32, shape=[None, 28, 28, 3])
targets = tf.placeholder(dtype=tf.int64, shape=[None])
training_flag = tf.placeholder(dtype=tf.bool)

# 构建模型
x = inputs
x = conv2d(inputs=x, filters=32, kernel_size=3, strides=1, padding='VALID', name='conv2d_1')
x = batch_norm(inputs=x, training=training_flag, name='batch_norm_1')
x = conv2d(inputs=x, filters=64, kernel_size=3, strides=1, padding='VALID', name='conv2d_2')
x = batch_norm(inputs=x, training=training_flag, name='batch_norm_2')
x = tf.contrib.layers.flatten(x)
x = dense(inputs=x, units=256, name='dense_1')
x = batch_norm(inputs=x, training=training_flag, name='batch_norm_3')
output = dense(inputs=x, units=10, activation=None, name='output')

# 定义损失函数和优化器
loss = tf.losses.sparse_softmax_cross_entropy(logits=output, labels=targets)
opt = tf.train.AdamOptimizer(learning_rate=0.001).minimize(loss)

# 开始训练
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    for epoch in range(10):
        # 训练过程中需要设置training_flag为True 
        # 训练过程中需要将trainable参数设置为True,使其可以更新beta和gamma参数
        train_loss = 0
        for _ in range(total_batch):
            batch_x, batch_y = ...
            _, l = sess.run([opt, loss], feed_dict={inputs: batch_x, targets: batch_y, training_flag: True})
            train_loss += l
        train_loss /= total_batch
        print('Epoch {}/{}, training loss: {:.6f}'.format(epoch + 1, train_loss))

在这个样例中,我们用了两个卷积层和两个batch normalization层,其中在batch_norm函数的training参数设置为了True,代表着现在是训练过程中,需要使用到批量均一化的算法。

另外,在调用这个函数之前,我们需要先调用denseconv2d函数来定义神经网络的结构。

2.3 demo2:测试过程中使用tf.layers.batch_normalization

下面我们来看一个在测试过程中使用tf.layers.batch_normalization函数的样例。假设我们在MNIST手写数字识别的测试集上进行预测,我们的原始数据是一个$1x28x28x1$的tensor。

import tensorflow as tf
import numpy as np

def conv2d(inputs, filters, kernel_size, strides, padding='SAME', activation=tf.nn.relu, name=None):
    return tf.layers.conv2d(
        inputs=inputs,
        filters=filters,
        kernel_size=kernel_size,
        strides=strides,
        padding=padding,
        activation=activation,
        kernel_initializer=tf.contrib.layers.xavier_initializer(),
        bias_initializer=tf.zeros_initializer(),
        name=name
    )

def batch_norm(inputs, training=True, name=None):
    return tf.layers.batch_normalization(
        inputs=inputs,
        axis=-1,
        momentum=0.99,
        epsilon=0.001,
        center=True,
        scale=True,
        training=training,
        name=name
    )

def dense(inputs, units, activation=tf.nn.relu, name=None):
    return tf.layers.dense(
        inputs=inputs,
        units=units,
        activation=activation,
        kernel_initializer=tf.contrib.layers.xavier_initializer(),
        bias_initializer=tf.zeros_initializer(),
        name=name
    )

# 输入参数
inputs = tf.placeholder(dtype=tf.float32, shape=[None, 28, 28, 1])
training_flag = tf.placeholder(dtype=tf.bool)

# 构建模型
x = inputs
x = conv2d(inputs=x, filters=32, kernel_size=3, strides=1, padding='VALID', name='conv2d_1')
x = batch_norm(inputs=x, training=training_flag, name='batch_norm_1')
x = conv2d(inputs=x, filters=64, kernel_size=3, strides=1, padding='VALID', name='conv2d_2')
x = batch_norm(inputs=x, training=training_flag, name='batch_norm_2')
x = tf.contrib.layers.flatten(x)
x = dense(inputs=x, units=256, name='dense_1')
x = batch_norm(inputs=x, training=False, name='batch_norm_3')
output = dense(inputs=x, units=10, activation=None, name='output')

# 定义预测函数
predict = tf.argmax(output, axis=-1)

# 开始测试
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    saver.restore(sess, 'model/')

    # 测试过程中需要将training_flag设置为False,
    # 这样的话模型将不再进行batch内归一化的操作,
    # 而是根据之前训练出的均值和方差进行归一化
    test_pred = []
    for idx in range(data_num):
        test_x = ...
        pred = sess.run(predict, feed_dict={inputs: test_x, training_flag: False})
        test_pred.append(pred)

在这个样例中,我们同样定义了两个卷积层和两个batch normalization层,但这次的不同在于,我们将batch_norm函数的training参数设置成了False,代表是测试过程,需要根据之前训练好的均值和方差进行激活值的归一化。

2.4 参考文献

  1. https://www.tensorflow.org/api_docs/python/tf/layers/batch_normalization
  2. https://tensorflow.google.cn/api_docs/python/tf/layers/batch_normalization