Tensorflow 和批量归一化在批量大小等于1时的输出全为零

我有一个关于批量归一化(以下简称BN)的理解问题。

我有一个卷积网络运行得很好,我在编写测试来检查形状和输出范围时发现,当我将批量大小设置为1时,我的模型输出为零(包括logits和激活值)。

我用BN制作了一个最简单的卷积网络原型:

输入 => 卷积 + ReLU => BN => 卷积 + ReLU => BN => 卷积层 + Tanh

模型使用泽维尔初始化进行初始化。我猜测BN在训练过程中进行了一些需要批量大小大于1的计算。

我在PyTorch中发现了一个似乎在讨论这个问题的issue:https://github.com/pytorch/pytorch/issues/1381

有谁能解释一下这是怎么回事吗?我还是有点不太明白。


运行示例:

重要提示: 运行此脚本需要Tensorlayer库:pip install tensorlayer

import tensorflow as tfimport tensorlayer as tlimport numpy as npdef conv_net(inputs, is_training):    xavier_initilizer = tf.contrib.layers.xavier_initializer(uniform=True)    normal_initializer = tf.random_normal_initializer(mean=1., stddev=0.02)    # 输入层    network = tl.layers.InputLayer(inputs, name='input')    fx = [64, 128, 256, 256, 256]    for i, n_out_channel in enumerate(fx):        with tf.variable_scope('h' + str(i + 1)):            network = tl.layers.Conv2d(                network,                n_filter    = n_out_channel,                filter_size = (5, 5),                strides     = (2, 2),                padding     = 'VALID',                act         = tf.identity,                W_init      = xavier_initilizer,                name        = 'conv2d'            )            network = tl.layers.BatchNormLayer(                network,                act        = tf.identity,                is_train   = is_training,                gamma_init = normal_initializer,                name       = 'batch_norm'            )            network = tl.layers.PReluLayer(                layer  = network,                a_init = tf.constant_initializer(0.2),                name   ='activation'            )    ############# 输出层 ###############    with tf.variable_scope('h' + str(len(fx) + 1)):        '''        network = tl.layers.FlattenLayer(network, name='flatten')        network = tl.layers.DenseLayer(            network,            n_units = 100,            act     = tf.identity,            W_init  = xavier_initilizer,            name    = 'dense'        )        '''        output_filter_size = tuple([int(i) for i in network.outputs.get_shape()[1:3]])        network = tl.layers.Conv2d(            network,            n_filter    = 100,            filter_size = output_filter_size,            strides     = (1, 1),            padding     = 'VALID',            act         = tf.identity,            W_init      = xavier_initilizer,            name        = 'conv2d'        )        network = tl.layers.BatchNormLayer(            network,            act        = tf.identity,            is_train   = is_training,            gamma_init = normal_initializer,            name       = 'batch_norm'        )        net_logits = network.outputs        network.outputs = tf.nn.tanh(            x        = network.outputs,            name     = 'activation'        )        net_output = network.outputs    return network, net_output, net_logitsif __name__ == '__main__':    tf.logging.set_verbosity(tf.logging.DEBUG)    #################################################    #                模型定义               #    #################################################    PLH_SHAPE = [None, 256, 256, 3]    input_plh = tf.placeholder(tf.float32, PLH_SHAPE, name='input_placeholder')    convnet, net_out, net_logits = conv_net(input_plh, is_training=True)    with tf.Session() as sess:        tl.layers.initialize_global_variables(sess)        convnet.print_params(details=True)        #################################################        #                  启动运行                 #        #################################################        for BATCH_SIZE in [1, 2]:            INPUT_SHAPE = [BATCH_SIZE, 256, 256, 3]            batch_data = np.random.random(size=INPUT_SHAPE)            output, logits = sess.run(                [net_out, net_logits],                feed_dict={                    input_plh: batch_data                }            )            if tf.logging.get_verbosity() == tf.logging.DEBUG:                print("\n\n###########################")                print("\n批量大小 = %d\n" % BATCH_SIZE)            tf.logging.debug("输出 => 形状: %s - 均值: %e - 标准差: %f - 最小值: %f - 最大值: %f" % (                output.shape,                output.mean(),                output.std(),                output.min(),                output.max()            ))            tf.logging.debug("logits => 形状: %s - 均值: %e - 标准差: %f - 最小值: %f - 最大值: %f" % (                logits.shape,                logits.mean(),                logits.std(),                logits.min(),                logits.max()            ))            if tf.logging.get_verbosity() == tf.logging.DEBUG:                print("###########################")

输出如下:

###########################批量大小 = 1DEBUG:tensorflow:输出 => 形状: (1, 1, 1, 100) - 均值: 0.000000e+00 - 标准差: 0.000000 - 最小值: 0.000000 - 最大值: 0.000000DEBUG:tensorflow:logits => 形状: (1, 1, 1, 100) - 均值: 0.000000e+00 - 标准差: 0.000000 - 最小值: 0.000000 - 最大值: 0.000000######################################################批量大小 = 2DEBUG:tensorflow:输出 => 形状: (2, 1, 1, 100) - 均值: -1.430511e-08 - 标准差: 0.760749 - 最小值: -0.779634 - 最大值: 0.779634DEBUG:tensorflow:logits => 形状: (2, 1, 1, 100) - 均值: -4.768372e-08 - 标准差: 0.998715 - 最小值: -1.044437 - 最大值: 1.044437###########################

回答:

你可能应该阅读一些关于批量归一化的解释,比如这个。你也可以看看TensorFlow的相关文档

基本上,你可以用两种方式进行批量归一化,但这两种方式在处理批量大小为1时都有问题:

  • 使用移动均值和方差,像素级别处理,因此它们是与批次中每个样本形状相同的张量。这是在@layog的回答中使用的方法,我认为也是原始论文中使用的,最为常用。

  • 使用整个图像/特征空间的移动均值和方差,因此它们只是形状为(n_channels,)的向量(秩1)。

在两种情况下,你都会有:

输出 = gamma * (输入 - 均值) / 标准差 + beta

Beta通常设置为0,gamma设置为1,因为在BN之后有线性函数。

在训练过程中均值方差是在当前批次中计算的,当批次大小为1时会导致问题:

  • 在第一种情况下,你会得到均值=输入,因此输出=0
  • 在第二种情况下,均值将是所有像素的平均值,这样会好一些;但如果你的宽度和高度也为1,那么你再次得到均值=输入,因此输出=0

我认为大多数人(以及原始方法)使用第一种方式,这就是为什么你会得到0(尽管TensorFlow文档似乎也建议第二种方法是常用的)。你提供的链接中的论点似乎是在考虑第二种方法。

无论你使用哪种方法,使用BN时,只有在使用较大的批量大小(例如,至少10)时,你才会得到好的结果。

Related Posts

L1-L2正则化的不同系数

我想对网络的权重同时应用L1和L2正则化。然而,我找不…

使用scikit-learn的无监督方法将列表分类成不同组别,有没有办法?

我有一系列实例,每个实例都有一份列表,代表它所遵循的不…

f1_score metric in lightgbm

我想使用自定义指标f1_score来训练一个lgb模型…

通过相关系数矩阵进行特征选择

我在测试不同的算法时,如逻辑回归、高斯朴素贝叶斯、随机…

可以将机器学习库用于流式输入和输出吗?

已关闭。此问题需要更加聚焦。目前不接受回答。 想要改进…

在TensorFlow中,queue.dequeue_up_to()方法的用途是什么?

我对这个方法感到非常困惑,特别是当我发现这个令人费解的…

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注