为什么 TensorFlow 示例在增加批量大小时会失败?

Why does TensorFlow example fail when increasing batch size?

我正在查看 Tensorflow MNIST example for beginners 并发现在这部分:

for i in range(1000):
  batch_xs, batch_ys = mnist.train.next_batch(100)
  sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})

将批量大小从 100 更改为 204 以上会导致模型无法收敛。它最高可达 204,但在 205 和我尝试过的任何更高数字时,准确度最终会低于 10%。这是一个错误,算法问题还是其他问题?

这是 运行 他们为 OS X 安装的二进制文件,似乎是 0.5.0 版。

您在初学者示例中使用的是非常基本的线性模型吗?

这里有一个调试它的技巧 - 在增加批量大小时观察交叉熵(第一行来自示例,第二行是我刚刚添加的):

cross_entropy = -tf.reduce_sum(y_*tf.log(y))
cross_entropy = tf.Print(cross_entropy, [cross_entropy], "CrossE")

批量大小为 204 时,您将看到:

I tensorflow/core/kernels/logging_ops.cc:64] CrossE[92.37558]
I tensorflow/core/kernels/logging_ops.cc:64] CrossE[90.107414]

但是在 205,你会看到这样的序列,从一开始:

I tensorflow/core/kernels/logging_ops.cc:64] CrossE[472.02966]
I tensorflow/core/kernels/logging_ops.cc:64] CrossE[475.11697]
I tensorflow/core/kernels/logging_ops.cc:64] CrossE[1418.6655]
I tensorflow/core/kernels/logging_ops.cc:64] CrossE[1546.3833]
I tensorflow/core/kernels/logging_ops.cc:64] CrossE[1684.2932]
I tensorflow/core/kernels/logging_ops.cc:64] CrossE[1420.02]
I tensorflow/core/kernels/logging_ops.cc:64] CrossE[1796.0872]
I tensorflow/core/kernels/logging_ops.cc:64] CrossE[nan]

Ack - NaN 出现了。基本上,大批量正在创建如此巨大的梯度,以至于您的模型正在失控——它应用的更新太大,并且大大超出了它应该去的方向。

实际上,有几种方法可以解决这个问题。您可以将学习率从 0.01 降低到例如 0.005,这会导致最终精度为 0.92。

train_step = tf.train.GradientDescentOptimizer(0.005).minimize(cross_entropy)

或者您可以使用更复杂的优化算法(Adam、Momentum 等)尝试做更多的事情来找出梯度的方向。或者你可以使用一个更复杂的模型,它有更多的自由参数来分散那个大梯度。

@dga 给出了很好的答案,但我想扩展一下。

当我写新手教程的时候,我是这样实现代价函数的:

cross_entropy = -tf.reduce_sum(y_*tf.log(y))

我这样写是因为它看起来与交叉熵的数学定义最相似。但这样做实际上可能更好:

cross_entropy = -tf.reduce_mean(y_*tf.log(y))

为什么使用均值而不是求和可能更好?好吧,如果我们求和,那么将批量大小加倍会使成本加倍,同时也会使梯度的大小加倍。除非我们调整我们的学习率(或使用一种算法来为我们调整它,就像@dga 建议的那样)我们的训练将会爆炸!但是如果我们使用均值,那么我们的学习率就会变得与批量大小无关,这很好。

我鼓励您查看 Adam (tf.train.AdamOptimizer())。它往往比新元更能容忍摆弄东西。

@dga 很好地向您解释了这种行为的原因(cross_entropy 变得太大),因此算法将无法收敛。有几种方法可以解决这个问题。他已经建议降低学习率了。

梯度下降是最基本的算法。几乎所有其他 optimizers 都将正常工作:

train_step = tf.train.AdagradOptimizer(0.01).minimize(cross_entropy)
train_step = tf.train.AdamOptimizer().minimize(cross_entropy)
train_step = tf.train.FtrlOptimizer(0.01).minimize(cross_entropy)
train_step = tf.train.RMSPropOptimizer(0.01, 0.1).minimize(cross_entropy)

另一种方法是使用 tf.nn.softmax_cross_entropy_with_logits 来处理数值不稳定性。

当0*log(0)出现时出现Nan:

替换:

cross_entropy = -tf.reduce_sum(y_*tf.log(y))

与:

cross_entropy = -tf.reduce_sum(y_*tf.log(y + 1e-10))