【问题标题】:How to use Batch Normalization correctly in tensorflow?如何在张量流中正确使用批量标准化?
【发布时间】:2017-04-14 06:44:00
【问题描述】:

我在 tensorflow 中尝试了多个版本的 batch_normalization,但都没有奏效!当我在推理时设置 batch_size = 1 时,结果都是不正确的。

版本一:直接使用tensorflow.contrib中的正式版

from tensorflow.contrib.layers.python.layers.layers import batch_norm

这样使用:

output = lrelu(batch_norm(tf.nn.bias_add(conv, biases), is_training), 0.5, name=scope.name)

is_training = 训练时为真,推理时为假。

版本 2:来自How could I use Batch Normalization in TensorFlow?

def batch_norm_layer(x, train_phase, scope_bn='bn'):
    bn_train = batch_norm(x, decay=0.999, epsilon=1e-3, center=True, scale=True,
            updates_collections=None,
            is_training=True,
            reuse=None, # is this right?
            trainable=True,
            scope=scope_bn)
    bn_inference = batch_norm(x, decay=0.999, epsilon=1e-3, center=True, scale=True,
            updates_collections=None,
            is_training=False,
            reuse=True, # is this right?
            trainable=True,
            scope=scope_bn)
    z = tf.cond(train_phase, lambda: bn_train, lambda: bn_inference)
    return z

这样使用:

output = lrelu(batch_norm_layer(tf.nn.bias_add(conv, biases), is_training), 0.5, name=scope.name)

is_training 是训练时的占位符,在推理时为 True 和 False。

版本 3:来自 slim https://github.com/tensorflow/models/blob/master/inception/inception/slim/ops.py

def batch_norm_layer(inputs,
           is_training=True,
           scope='bn'):
  decay=0.999
  epsilon=0.001
  inputs_shape = inputs.get_shape()
  with tf.variable_scope(scope) as t_scope:
    axis = list(range(len(inputs_shape) - 1))
    params_shape = inputs_shape[-1:]
    # Allocate parameters for the beta and gamma of the normalization.
    beta, gamma = None, None
    beta = tf.Variable(tf.zeros_initializer(params_shape),
        name='beta',
        trainable=True)
    gamma = tf.Variable(tf.ones_initializer(params_shape),
        name='gamma',
        trainable=True)
    moving_mean = tf.Variable(tf.zeros_initializer(params_shape),
        name='moving_mean',
        trainable=False)
    moving_variance = tf.Variable(tf.ones_initializer(params_shape),
        name='moving_variance',
        trainable=False)
    if is_training:
      # Calculate the moments based on the individual batch.
      mean, variance = tf.nn.moments(inputs, axis)

      update_moving_mean = moving_averages.assign_moving_average(
          moving_mean, mean, decay)
      update_moving_variance = moving_averages.assign_moving_average(
          moving_variance, variance, decay)
    else:
      # Just use the moving_mean and moving_variance.
      mean = moving_mean
      variance = moving_variance
      # Normalize the activations.
    outputs = tf.nn.batch_normalization(
       inputs, mean, variance, beta, gamma, epsilon)
    outputs.set_shape(inputs.get_shape())
    return outputs

这样使用:

output = lrelu(batch_norm_layer(tf.nn.bias_add(conv, biases), is_training), 0.5, name=scope.name)

is_training = 训练时为真,推理时为假。

版本 4:与版本 3 类似,但添加了 tf.control_dependencies

def batch_norm_layer(inputs,
           decay=0.999,
           center=True,
           scale=True,
           epsilon=0.001,
           moving_vars='moving_vars',
           activation=None,
           is_training=True,
           trainable=True,
           restore=True,
           scope='bn',
           reuse=None):
  inputs_shape = inputs.get_shape()
  with tf.variable_op_scope([inputs], scope, 'BatchNorm', reuse=reuse):
      axis = list(range(len(inputs_shape) - 1))
      params_shape = inputs_shape[-1:]
      # Allocate parameters for the beta and gamma of the normalization.
      beta = tf.Variable(tf.zeros(params_shape), name='beta')
      gamma = tf.Variable(tf.ones(params_shape), name='gamma')
      # Create moving_mean and moving_variance add them to
      # GraphKeys.MOVING_AVERAGE_VARIABLES collections.
      moving_mean = tf.Variable(tf.zeros(params_shape), name='moving_mean',
            trainable=False)
      moving_variance = tf.Variable(tf.ones(params_shape),   name='moving_variance', 
            trainable=False)
  control_inputs = []
  if is_training:
      # Calculate the moments based on the individual batch.
      mean, variance = tf.nn.moments(inputs, axis)

      update_moving_mean = moving_averages.assign_moving_average(
          moving_mean, mean, decay)
      update_moving_variance = moving_averages.assign_moving_average(
          moving_variance, variance, decay)
      control_inputs = [update_moving_mean, update_moving_variance]
  else:
      # Just use the moving_mean and moving_variance.
      mean = moving_mean
      variance = moving_variance
  # Normalize the activations. 
  with tf.control_dependencies(control_inputs):
      return tf.nn.batch_normalization(
        inputs, mean, variance, beta, gamma, epsilon)

这样使用:

output = lrelu(batch_norm(tf.nn.bias_add(conv, biases), is_training), 0.5, name=scope.name)

is_training = 训练时为真,推理时为假。

Batch_normalization 的 4 个版本都不正确。那么,如何正确使用批归一化呢?

另一个奇怪的现象是,如果我这样设置batch_norm_layer为null,推理结果都是一样的。

def batch_norm_layer(inputs, is_training):
    return inputs

【问题讨论】:

  • 我坚信了解我所使用的基本概念。我建议您阅读有关批量标准化的论文,以真正了解它为什么以及如何提供帮助:arxiv.org/pdf/1502.03167.pdf
  • 如果你说“都是不正确的”,你是什么意思?
  • 意思是“他们都错了”。

标签: tensorflow deep-learning


【解决方案1】:

我已经测试,只要设置相同,以下批量标准化的简化实现与tf.contrib.layers.batch_norm 的结果相同。

def initialize_batch_norm(scope, depth):
    with tf.variable_scope(scope) as bnscope:
         gamma = tf.get_variable("gamma", shape[-1], initializer=tf.constant_initializer(1.0))
         beta = tf.get_variable("beta", shape[-1], initializer=tf.constant_initializer(0.0))
         moving_avg = tf.get_variable("moving_avg", shape[-1], initializer=tf.constant_initializer(0.0), trainable=False)
         moving_var = tf.get_variable("moving_var", shape[-1], initializer=tf.constant_initializer(1.0), trainable=False)
         bnscope.reuse_variables()


def BatchNorm_layer(x, scope, train, epsilon=0.001, decay=.99):
    # Perform a batch normalization after a conv layer or a fc layer
    # gamma: a scale factor
    # beta: an offset
    # epsilon: the variance epsilon - a small float number to avoid dividing by 0
    with tf.variable_scope(scope, reuse=True):
        with tf.variable_scope('BatchNorm', reuse=True) as bnscope:
            gamma, beta = tf.get_variable("gamma"), tf.get_variable("beta")
            moving_avg, moving_var = tf.get_variable("moving_avg"), tf.get_variable("moving_var")
            shape = x.get_shape().as_list()
            control_inputs = []
            if train:
                avg, var = tf.nn.moments(x, range(len(shape)-1))
                update_moving_avg = moving_averages.assign_moving_average(moving_avg, avg, decay)
                update_moving_var = moving_averages.assign_moving_average(moving_var, var, decay)
                control_inputs = [update_moving_avg, update_moving_var]
            else:
                avg = moving_avg
                var = moving_var
            with tf.control_dependencies(control_inputs):
                output = tf.nn.batch_normalization(x, avg, var, offset=beta, scale=gamma, variance_epsilon=epsilon)
    return output

tf.contrib.layers.batch_norm 中使用官方实现的批标准化的主要提示是:(1)将is_training=True 设置为训练时间,is_training=False 设置为验证和测试时间; (2) 设置updates_collections=None,确保moving_variancemoving_mean更新到位; (3)注意范围设置并谨慎; (4) 如果您的数据集较小或您的总训练更新/步数不是那么大,请将 decay 设置为小于默认值(默认为 0.999)的值(decay=0.9decay=0.99)。

【讨论】:

  • 谢谢匡仲宇,除了第4条,我和你的结论是一样的。
  • 我一直对tf.contrib.layers.batch_norm 有疑问。我的网络在训练时会收敛,但是当我测试网络并设置 is_training=False 时,它给了我无意义的结果。但是,is_training=True 时的测试结果对我来说更有意义(与没有 batch_norm 的网络相比,即使准确度几乎为零)。任何想法?我在这里问:[stackoverflow.com/questions/42770757/…batch_norm 在测试时无法正常工作(is_training=False))
  • @Zhongyu Kuang 你能解释更多关于updates_collections的信息吗?我们用 tf.GraphKeys.UPDATE_OPS 更新它们是什么。以及如何在推理中使用它们。
  • 嗨,updates_collections=None 怎么样,你能帮我理解一下吗?我检查了代码如果我们使用更新选项怎么办?
【解决方案2】:

我发现邝中宇的代码非常有用,但我坚持如何在训练和测试操作之间动态切换,即如何从 python 布尔 is_training 移动到 tensorflow 布尔占位符 is_training。我需要这个功能才能在训练期间在验证集上测试网络。

从他的代码开始,受到this的启发,我写了如下代码:

def batch_norm(x, scope, is_training, epsilon=0.001, decay=0.99):
    """
    Returns a batch normalization layer that automatically switch between train and test phases based on the 
    tensor is_training

    Args:
        x: input tensor
        scope: scope name
        is_training: boolean tensor or variable
        epsilon: epsilon parameter - see batch_norm_layer
        decay: epsilon parameter - see batch_norm_layer

    Returns:
        The correct batch normalization layer based on the value of is_training
    """
    assert isinstance(is_training, (ops.Tensor, variables.Variable)) and is_training.dtype == tf.bool

    return tf.cond(
        is_training,
        lambda: batch_norm_layer(x=x, scope=scope, epsilon=epsilon, decay=decay, is_training=True, reuse=None),
        lambda: batch_norm_layer(x=x, scope=scope, epsilon=epsilon, decay=decay, is_training=False, reuse=True),
    )


def batch_norm_layer(x, scope, is_training, epsilon=0.001, decay=0.99, reuse=None):
    """
    Performs a batch normalization layer

    Args:
        x: input tensor
        scope: scope name
        is_training: python boolean value
        epsilon: the variance epsilon - a small float number to avoid dividing by 0
        decay: the moving average decay

    Returns:
        The ops of a batch normalization layer
    """
    with tf.variable_scope(scope, reuse=reuse):
        shape = x.get_shape().as_list()
        # gamma: a trainable scale factor
        gamma = tf.get_variable("gamma", shape[-1], initializer=tf.constant_initializer(1.0), trainable=True)
        # beta: a trainable shift value
        beta = tf.get_variable("beta", shape[-1], initializer=tf.constant_initializer(0.0), trainable=True)
        moving_avg = tf.get_variable("moving_avg", shape[-1], initializer=tf.constant_initializer(0.0), trainable=False)
        moving_var = tf.get_variable("moving_var", shape[-1], initializer=tf.constant_initializer(1.0), trainable=False)
        if is_training:
            # tf.nn.moments == Calculate the mean and the variance of the tensor x
            avg, var = tf.nn.moments(x, range(len(shape)-1))
            update_moving_avg = moving_averages.assign_moving_average(moving_avg, avg, decay)
            update_moving_var = moving_averages.assign_moving_average(moving_var, var, decay)
            control_inputs = [update_moving_avg, update_moving_var]
        else:
            avg = moving_avg
            var = moving_var
            control_inputs = []
        with tf.control_dependencies(control_inputs):
            output = tf.nn.batch_normalization(x, avg, var, offset=beta, scale=gamma, variance_epsilon=epsilon)

    return output

那我这样使用batch_norm层:

fc1_weights = tf.Variable(...)
fc1 = tf.matmul(x, fc1_weights)
fc1 = batch_norm(fc1, 'fc1_bn', is_training=is_training)
fc1 = tf.nn.relu(fc1)

其中 is_training 是一个布尔占位符。请注意,不需要添加偏差,因为它被 Batch Normalization paper 中解释的 beta 参数替换。

执行期间:

# Training phase
sess.run(loss, feed_dict={x: bx, y: by, is_training: True})

# Testing phase
sess.run(loss, feed_dict={x: bx, y: by, is_training: False})

【讨论】:

  • 请注意,您可以使用 tf.contrib.layers.batch_norm() - 它接受的 is_training 参数可能是布尔占位符!
  • 实际上,问题是关于在 tensorflow 中实现批量标准化的不同方法,所以我提供了我编写的代码来实现它,而不使用 contrib 模块中的任何函数。官方表示,“contrib 模块包含易失性或实验性代码”,因此在某些情况下避免使用它可能会很有用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-04-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-07
  • 1970-01-01
  • 2022-01-24
相关资源
最近更新 更多