用交叉熵解决手写数字识别问题

我们可以很容易在程序中将交叉熵应用于梯度下降法(gradient descent)和反向传播算法(backpropagation)。在本章的后面我会改进之前的手写数字识别程序network.py。新的程序取名network2.py,它不仅仅用到了交叉熵,还用到了本章将要介绍的其他技术1。但现在我们先看一下新程序解决手写数字问题的效果如何。和第一章的例子一样,我们的网络将用个隐层神经元,mini-batch的大小选为。设定学习速率为2,迭代次数选为。network2.py的接口和network.py稍有不同,但是仍然能够很清楚看到整个过程。

  1. >>> import mnist_loader
  2. >>> training_data, validation_data, test_data = \
  3. ... mnist_loader.load_data_wrapper()
  4. >>> import network2
  5. >>> net = network2.Network([784, 30, 10], cost=network2.CrossEntropyCost)
  6. >>> net.large_weight_initializer()
  7. >>> net.SGD(training_data, 30, 10, 0.5, evaluation_data=test_data,
  8. ... monitor_evaluation_accuracy=True)

注意,这里顺便提一下net.large_weight_initializer()这条命令是用来初始化权重和偏置的。我们需要运行一下这条命令,因为在这章的后面我会改变我们网络初始化权重的默认方式。运行完这些命令显示的正确率为个百分点。这非常接近我们第一章用均方误差函数的结果个百分点。

接下来我们选取隐藏层神经元个数为,代价函数为交叉熵函数,其它的参数保持不变。在这种情况下,我们得到的准确率为个百分点。这比我们第一章的结果有了提升。你可能觉得只是提升了一点,但是考虑到误差率从个百分点降低到个百分点。这也就是说,我们缩减了十四分之一的原始误差。这的确是个不错的提升。

由此看来,交叉熵得到了比均方误差更好的结果。但是,仅仅通过这次结果并不能说明交叉熵是一个更好的选择,原因是我对超参数的调节工作投入还不够,比如学习速率,mini-batch的大小等等。为了证明交叉熵确实能够带来结果提升,我们要做一个彻底的超参数优化工作。这个工作实验最后的结果依然验证了我们之前理论上的结论:相比均方误差,交叉熵是一个更好的选择。

顺便说一句,这也是我们在这章或者在这本书中采用的模式。我们采用了一些新的技术,然后做实验验证它,然后我们「改进了」结果。我们当然乐意看到结果得到了改进。但是带来改进的原因通常是令人困扰的。只有我们花费大量时间去优化所有的超参数才能得到令人信服的结果。这样做的工作量非常大,通常我们不会做那么详尽的研究。相反,我们会做一些类似于上面的非正式的测试。不过你仍要记得这种测试的证据并不充分,要对参数失效的迹象要保持警觉。

到目前为止,我们花费了大量篇幅去讨论交叉熵。它只对我们手写数字识别有一点提升,为何要那么大费周章地去介绍它呢?在这章的后面我们会看到其他技术——尤其是正则化,能够大大改进我们的结果。那么为什么要如此关注交叉熵呢?一部分原因是交叉熵是广泛使用的代价函数,因此它值得我们深入了解。但是更重要的理由是神经元饱和在神经元网络中是一个重要的问题,它会贯穿整本书。我花费那么长篇幅讨论交叉熵是因为它是一个很好的实验,能让我们对神经元饱和有了初步的认知。

1 代码托管在Github

2 在第一章中,我们使用了平方代价函数和的学习率。正如之前所讨论的,当代价函数改变后,我们不能精确地定义什么是「相同的」学习率。对于对比的两种代价函数,在其它超参数相同的情况下,我尝试了进行实验找到能够产生相似表现的学习率。有一种非常粗浅的想法找到交叉熵代价函数和平方代价函数两者的学习率的联系。正如我们之前所看到的,平方代价函数的梯度表达式中多一项。如果我们对算一下均值,我们得到。我们可以(大致上)得到,当学习率相同时,平方代价函数的速度会平均上慢6倍。这表明一个可行的方法是将平方代价函数的学习率除以6。当然,这远远不是一个严谨的推断,别太当真了。但是你也可以将其视为一种有用的初始化方法。

原文: https://hit-scir.gitbooks.io/neural-networks-and-deep-learning-zh_cn/content/chap3/c3s2.html