当训练时,将"tf.layers.batch_normalization"的"training"设置为False会得到更好的验证结果。

5
我使用TensorFlow来训练DNN。我了解到Batch Normalization对于DNN非常有帮助,因此我在DNN中使用了它。
我使用"tf.layers.batch_normalization"并按照API文档的说明构建网络:在训练时,将其参数"training=True",在验证时,则设置为"training=False"。并且添加tf.get_collection(tf.GraphKeys.UPDATE_OPS)
以下是我的代码:
# -*- coding: utf-8 -*-
import tensorflow as tf
import numpy as np

input_node_num=257*7
output_node_num=257

tf_X = tf.placeholder(tf.float32,[None,input_node_num])
tf_Y = tf.placeholder(tf.float32,[None,output_node_num])
dropout_rate=tf.placeholder(tf.float32)
flag_training=tf.placeholder(tf.bool)
hid_node_num=2048

h1=tf.contrib.layers.fully_connected(tf_X, hid_node_num, activation_fn=None)
h1_2=tf.nn.relu(tf.layers.batch_normalization(h1,training=flag_training))
h1_3=tf.nn.dropout(h1_2,dropout_rate)

h2=tf.contrib.layers.fully_connected(h1_3, hid_node_num, activation_fn=None)
h2_2=tf.nn.relu(tf.layers.batch_normalization(h2,training=flag_training))
h2_3=tf.nn.dropout(h2_2,dropout_rate)

h3=tf.contrib.layers.fully_connected(h2_3, hid_node_num, activation_fn=None)
h3_2=tf.nn.relu(tf.layers.batch_normalization(h3,training=flag_training))
h3_3=tf.nn.dropout(h3_2,dropout_rate)

tf_Y_pre=tf.contrib.layers.fully_connected(h3_3, output_node_num, activation_fn=None)

loss=tf.reduce_mean(tf.square(tf_Y-tf_Y_pre))

update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
with tf.control_dependencies(update_ops):
    train_step = tf.train.AdamOptimizer(1e-4).minimize(loss)

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

    for i1 in range(3000*num_batch):
        train_feature=... # Some processing
        train_label=...  # Some processing
        sess.run(train_step,feed_dict={tf_X:train_feature,tf_Y:train_label,flag_training:True,dropout_rate:1}) # when train , set "training=True" , when validate ,set "training=False" , get a bad result . However when train , set "training=False" ,when validate ,set "training=False" , get a better result .

        if((i1+1)%277200==0):# print validate loss every 0.1 epoch
            validate_feature=... # Some processing
            validate_label=... # Some processing

            validate_loss = sess.run(loss,feed_dict={tf_X:validate_feature,tf_Y:validate_label,flag_training:False,dropout_rate:1})
            print(validate_loss)

我的代码有错误吗?如果我的代码正确,我认为我得到了一个奇怪的结果:

训练时,我设置“training = True”,在验证时,设置“training = False”,结果不好。我每0.1个epoch打印一次验证损失,第1到第3个epoch的验证损失为

 0.929624
 0.992692
 0.814033
 0.858562
 1.042705
 0.665418
 0.753507
 0.700503
 0.508338
 0.761886
 0.787044
 0.817034
 0.726586
 0.901634
 0.633383
 0.783920
 0.528140
 0.847496
 0.804937
 0.828761
 0.802314
 0.855557
 0.702335
 0.764318
 0.776465
 0.719034
 0.678497
 0.596230
 0.739280
 0.970555

然而,当我更改代码“sess.run(train_step,feed_dict={tf_X:train_feature,tf_Y:train_label,flag_training:True,dropout_rate:1})”时,即:在训练时设置“training=False”,在验证时设置“training=True”。结果非常好。第一轮的验证损失为

 0.474313
 0.391002
 0.369357
 0.366732
 0.383477
 0.346027
 0.336518
 0.368153
 0.330749
 0.322070
 0.335551

为什么会出现这个结果?在训练时需要设置 "training=True",在验证时需要设置 "training=False" 吗?

你的 batch_size 是多少? - Ishamael
@Ishamael 我用于训练的数据是实时生成的。每个生成数据的 batch_size 都是 不固定 的,每个批次通常有 300 到 500 个样本。 (我还有一个问题:在一般的 DNN 训练中,通常会将 batch_size 设置为固定值,例如将 batch_size = 1000,则每个批次都有 1000 个样本。我想知道 DNN 训练是否必须要 确保 batch_size 固定?例如,第1个批次有300个样本,第2个批次有500个样本,第3个批次有400个样本...这种方式是否符合DNN理论?) - Evan
变量批大小应该没问题。假设您的数据是生成的,那么在生成的批次中,样本之间是否比跨生成的批次更相似?如果是,那么这将成为批量归一化的问题。 - Ishamael
@Ishamael 是的,在生成的批次中,样本之间的相似度比跨生成批次的样本更高。 我大概明白了为什么在训练中设置“training=true”时结果不好,而在设置“training=false”时结果很好: - Evan
@Ishamael 我认为原因是: 由于使用了 **tf.get_collection(tf.GraphKeys.UPDATE_OPS)**,在训练 DNN 时,无论 "training=true" 还是 "training=false",全局均值 mean_global 和全局方差 var_global 都会被批次均值 mean_batch 和批次方差 var_batch 更新。 - Evan
@Ishamael 当设置“training=true”时,每个样本批次都使用mean_batchvar_batch进行归一化。当设置“training=false”时,每个样本批次都使用全局均值mean_global和全局方差var_global进行归一化。在不同的批次之间,mean_batchvar_batch的差异很大,但mean_globalvar_global的差异很小。您认为这个解释合理吗? - Evan
2个回答

17

TL;DR: 使用小于默认动量的标准化层,例如:

tf.layers.batch_normalization( h1, momentum = 0.9, training=flag_training )
当你设置training = False时,表示批归一化层将使用其内部存储的平均值和方差对批量进行归一化,而不是使用批量自身的平均值和方差。当training = False时,这些内部变量也不会被更新。由于它们被初始化为 mean=0variance=1 , 这意味着批归一化实际上被关闭了——该层减去零并将结果除以 1。
因此,如果你使用training = False进行训练并像这样进行评估,那就意味着你在没有任何批归一化的情况下训练网络。它仍然会产生合理的结果,因为呐,批归一化之前也有生命,尽管确实不太光彩......
如果你打开批归一化并使用training = True,那么它将开始对每个批次进行归一化,并收集每个批次的平均值和方差的移动平均值。现在,这里有一个棘手的部分。移动平均值是指数移动平均值,在tf.layers.batch_normalization()中默认的动量为0.99。平均值再次从 0 开始,方差从 1 开始。但由于每个更新都是以 (1-momentum) 的权重应用的,因此它将在无限逼近实际的平均值和方差。例如,在100步之后,它将达到实际值的约73.4%,因为0.991000.366。如果你有数值较大的值,差异可能会很大。
因此,如果你处理的批次数量相对较小,则在运行测试时,内部存储的平均值和方差仍可能明显偏离。那么你的网络就是在正确归一化的数据上训练的,并在错误归一化的数据上进行测试。
为了加快内部批归一化值的收敛速度,可以应用较小的动量,例如0.9
tf.layers.batch_normalization( h1, momentum = 0.9, training=flag_training )

(对所有批标准化层重复此过程。) 但需要注意的是,这样做有一个缺点。数据中的随机波动会更大地影响存储的均值和方差,特别是在使用较小的动量时,导致最终结果(用于推断)受训练停止位置的影响较大,这显然不是最优的。使用较大的动量很有用。根据训练步骤的数量,我们通常为1001,00010,000个训练步骤分别使用0.90.990.999。没有必要超过0.999

另一个重要的事情是正确地随机化训练数据。如果您首先使用整个数据集的较小数值进行训练,则规范化的收敛速度会更慢。最好完全随机化训练数据的顺序,并确保使用至少14个批量大小(经验法则)。


顺便说一下:众所周知,将零去偏值可以显着加快收敛速度,而ExponentialMovingAverage类具有此功能。但是批标准化层没有这个功能,除非使用tf.slimbatch_norm,如果您愿意为slim重新构建代码。


5
你设置Training = False的原因是为了提高性能,这是因为批量归一化有四个变量(beta、gamma、mean、variance)。当Training = False时,平均值和方差确实不会更新。然而,gamma和beta仍然会更新。因此,你的模型有两个额外的变量,从而具有更好的性能。
此外,我猜测即使没有批量归一化,你的模型也具有相对良好的性能。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接