《神经网络和深度学习》系列文章三十:如何选择神经网络的超参数
出处: Michael Nielsen的《Neural Network and Deep Learning》,点击末尾“阅读原文”即可查看英文原文。
声明:我们将在每周四连载该书的中文翻译。
本节译者:朱小虎 、张广宇。转载已获得译者授权,禁止二次转载。
使用神经网络识别手写数字
反向传播算法是如何工作的
改进神经网络的学习方法
改进神经网络的学习方式
交叉熵损失函数
用交叉熵解决手写数字识别问题
交叉熵意味着什么?它从哪里来?
Softmax
过拟合
正则化
为什么正则化能够降低过拟合?
其他正则化技术
权重初始化
重温手写数字识别:代码
如何选择神经网络的超参数
其他技术
神经网络能够计算任意函数的视觉证明
为什么深度神经网络的训练是困难的
深度学习
直到现在,我们还没有解释对诸如学习速率
>>> import mnist_loader
>>> training_data, validation_data, test_data = \
... mnist_loader.load_data_wrapper()
>>> import network2
>>> net = network2.Network([784, 30, 10])
>>> net.SGD(training_data, 30, 10, 10.0, lmbda = 1000.0,
... evaluation_data=validation_data, monitor_evaluation_accuracy=True)
Epoch 0 training complete
Accuracy on evaluation data: 1030 / 10000
Epoch 1 training complete
Accuracy on evaluation data: 990 / 10000
Epoch 2 training complete
Accuracy on evaluation data: 1009 / 10000
...
Epoch 27 training complete
Accuracy on evaluation data: 1009 / 10000
Epoch 28 training complete
Accuracy on evaluation data: 983 / 10000
Epoch 29 training complete
Accuracy on evaluation data: 967 / 10000
我们分类准确率并不比随机选择更好。网络就像随机噪声产生器一样。
你可能会说,“这好办,降低学习速率和规范化参数就好了。”不幸的是,你并不先验地知道这些就是需要调整的超参数。可能真正的问题出在
本节,我会给出一些用于设定超参数的启发式想法。目的是帮你发展出一套工作流来确保很好地设置超参数。当然,我不会覆盖超参数优化的每个方法。那是太繁重的问题,而且也不会是一个能够完全解决的问题,也不存在一种通用的关于正确策略的共同认知。总是会有一些新的技巧可以帮助你提高一点性能。但是本节的启发式想法能帮你开个好头。
宽泛策略: 在使用神经网络来解决新的问题时,一个挑战就是获得任何一种非寻常的学习,也就是说,达到比随机的情况更好的结果。这个实际上会很困难,尤其是遇到一种新类型的问题时。让我们看看有哪些策略可以在面临这类困难时候尝试。
假设,我们第一次遇到 MNIST 分类问题。刚开始,你很有激情,但是当第一个神经网络完全失效时,你会觉得有些沮丧。此时就可以将问题简化。丢开训练和验证集合中的那些除了
你通过简化网络来加速实验进行更有意义的学习。如果你相信
你可以通过提高监控的频率来在试验中获得另一个加速了。在 network2.py中,我们在每个训练的回合的最后进行监控。每回合
>>> net = network2.Network([784, 10])
>>> net.SGD(training_data[:1000], 30, 10, 10.0, lmbda = 1000.0, \
... evaluation_data=validation_data[:100], \
... monitor_evaluation_accuracy=True)
Epoch 0 training complete
Accuracy on evaluation data: 10 / 100
Epoch 1 training complete
Accuracy on evaluation data: 10 / 100
Epoch 2 training complete
Accuracy on evaluation data: 10 / 100
...
我们仍然获得完全的噪声!但是有一个进步:现在我们每一秒钟可以得到反馈,而不是之前每 10 秒钟才可以。这意味着你可以更加快速地实验其他的超参数,或者甚至接近同步地进行不同参数的组合的评比。
在上面的例子中,我设置
>>> net = network2.Network([784, 10])
>>> net.SGD(training_data[:1000], 30, 10, 10.0, lmbda = 20.0, \
... evaluation_data=validation_data[:100], \
... monitor_evaluation_accuracy=True)
Epoch 0 training complete
Accuracy on evaluation data: 12 / 100
Epoch 1 training complete
Accuracy on evaluation data: 14 / 100
Epoch 2 training complete
Accuracy on evaluation data: 25 / 100
Epoch 3 training complete
Accuracy on evaluation data: 18 / 100
...
哦也!现在有了信号了。不是非常糟糕的信号,却真是一个信号。我们可以基于这点,来改变超参数从而获得更多的提升。可能我们猜测学习速率需要增加(你可能会发现,这只是一个不大好的猜测,原因后面会讲,但是相信我)所以为了测试我们的猜测就将
>>> net = network2.Network([784, 10])
>>> net.SGD(training_data[:1000], 30, 10, 100.0, lmbda = 20.0, \
... evaluation_data=validation_data[:100], \
... monitor_evaluation_accuracy=True)
Epoch 0 training complete
Accuracy on evaluation data: 10 / 100
Epoch 1 training complete
Accuracy on evaluation data: 10 / 100
Epoch 2 training complete
Accuracy on evaluation data: 10 / 100
Epoch 3 training complete
Accuracy on evaluation data: 10 / 100
...
这并不好!告诉我们之前的猜测是错误的,问题并不是学习速率太低了。所以,我们试着将
>>> net = network2.Network([784, 10])
>>> net.SGD(training_data[:1000], 30, 10, 1.0, lmbda = 20.0, \
... evaluation_data=validation_data[:100], \
... monitor_evaluation_accuracy=True)
Epoch 0 training complete
Accuracy on evaluation data: 62 / 100
Epoch 1 training complete
Accuracy on evaluation data: 42 / 100
Epoch 2 training complete
Accuracy on evaluation data: 43 / 100
Epoch 3 training complete
Accuracy on evaluation data: 61 / 100
...
这样好点了!所以我们可以继续,逐个调整每个超参数,慢慢提升性能。一旦我们找到一种提升性能的
所有这些作为一种宽泛的策略看起来很有前途。然而,我想要回到寻找超参数的原点。实际上,即使是上面的讨论也传达出过于乐观的观点。实际上,很容易会遇到神经网络学习不到任何知识的情况。你可能要花费若干天在调整参数上,仍然没有进展。所以我想要再重申一下在前期你应该从实验中尽可能早的获得快速反馈。直觉上看,这看起来简化问题和架构仅仅会降低你的效率。实际上,这样能够将进度加快,因为你能够更快地找到传达出有意义的信号的网络。一旦你获得这些信号,你可以尝尝通过微调超参数获得快速的性能提升。这和人生中很多情况一样——万事开头难。
好了,上面就是宽泛的策略。现在我们看看一些具体的设置超参数的推荐。我会聚焦在学习率
学习速率: 假设我们运行了三个不同学习速率(
使用
然而,如果
所以,有了这样的想法,我们可以如下设置
显然,
在 MNIST 数据中,使用这样的策略会给出一个关于学习速率
这看起来相当直接。然而,使用训练代价函数来选择
使用提前停止来确定训练的迭代次数: 正如我们在本章前面讨论的那样,提前停止表示在每个回合的最后,我们都要计算验证集上的分类准确率。当准确率不再提升,就终止它。这让选择回合数变得很简单。特别地,也意味着我们不再需要担心显式地掌握回合数和其他超参数的关联。而且,这个过程还是自动的。另外,提前停止也能够帮助我们避免过度拟合。尽管在实验前期不采用提前停止,这样可以看到任何过匹配的信号,使用这些来选择规范化方法,但提前停止仍然是一件很棒的事。
我们需要再明确一下什么叫做分类准确率不再提升,这样方可实现提前停止。正如我们已经看到的,分类准确率在整体趋势下降的时候仍旧会抖动或者震荡。如果我们在准确度刚开始下降的时候就停止,那么肯定会错过更好的选择。一种不错的解决方案是如果分类准确率在一段时间内不再提升的时候终止。例如,我们要解决 MNIST 问题。如果分类准确度在近
这种
我们还没有在我们的 MNIST 实验中使用提前终止。原因是我们已经比较了不同的学习观点。这样的比较其实比较适合使用同样的训练回合。但是,在 network2.py 中实现提前终止还是很有价值的:
问题
修改 network2.py 来实现提前终止,并让
回合不提升终止策略中的 称为可以设置的参数。 你能够想出不同于
回合不提升终止策略的其他提前终止策略么?理想中,规则应该能够获得更高的验证准确率而不需要训练太久。将你的想法实现在network2.py 中,运行这些实验和 回合不提升终止策略比较对应的验证准确率和训练的回合数。
学习速率调整: 我们一直都将学习速率设置为常量。但是,通常采用可变的学习速率更加有效。在学习的前期,权重可能非常糟糕。所以最好是使用一个较大的学习速率让权重变化得更快。越往后,我们可以降低学习速率,这样可以作出更加精良的调整。
我们要如何设置学习速率呢?其实有很多方法。一种自然的观点是使用提前终止的想法。就是保持学习速率为一个常量直到验证准确率开始变差。然后按照某个量下降学习速率,比如说按照
可变学习速率可以提升性能,但是也会产生大量可能的选择。这些选择会让人头疼——你可能需要花费很多精力才能优化学习规则。对刚开始实验,我建议使用单一的常量作为学习速率的选择。这会给你一个比较好的近似。后面,如果你想获得更好的性能,值得按照某种规则进行实验,根据我已经给出的资料。
练习
更改 network2.py 实现学习规则:每次验证准确率满足
回合不提升终止策略时改变学习速率;当学习速率降到初始值的 时终止。
规范化参数: 我建议,开始时不包含规范化(
练习
使用梯度下降来尝试学习好的超参数的值其实很受期待。你可以想像关于使用梯度下降来确定
的障碍么?你能够想象关于使用梯度下降来确定 的障碍么?
在本书前面,我是如何选择超参数的: 如果你使用本节给出的推荐策略,你会发现你自己找到的
相较于这样的折衷,其实我本可以尝试优化每个单一的观点的超参数选择。理论上,这可能是更好更公平的方式,因为那样的话我们可以看到每个观点的最优性能。但是,我们现在依照目前的规范进行了众多的比较,实践上,我觉得要做到需要过多的计算资源了。这也是我使用折衷方式来采用尽可能好(却不一定最优)的超参数选择。
小批量数据大小: 我们应该如何设置小批量数据的大小?为了回答这个问题,让我们先假设正在进行在线学习,也就是说使用大小为
一个关于在线学习的担忧是使用只有一个样本的小批量数据会带来关于梯度的错误估计。实际上,误差并不会真的产生这个问题。原因在于单一的梯度估计不需要绝对精确。我们需要的是确保代价函数保持下降的足够精确的估计。就像你现在要去北极点,但是只有一个不大精确的(差个
基于这个观点,这看起来好像我们需要使用在线学习。实际上,情况会变得更加复杂。在上一章的问题中我指出我们可以使用矩阵技术来对所有在小批量数据中的样本同时计算梯度更新,而不是进行循环。所以,取决于硬件和线性代数库的实现细节,这会比循环方式进行梯度更新快好多。也许是
现在,看起来这对我们帮助不大。我们使用
(1)
这里是对小批量数据中所有训练样本求和。而在线学习是
(2)
即使它仅仅是
(3)
这看起来项做了
所以,选择最好的小批量数据大小也是一种折衷。太小了,你不会用上很好的矩阵库的快速计算。太大,你是不能够足够频繁地更新权重的。你所需要的是选择一个折衷的值,可以最大化学习的速度。幸运的是,小批量数据大小的选择其实是相对独立的一个超参数(网络 整体架构外的参数),所以你不需要优化那些参数来寻找好的小批量数据大小。因此,可以选择的方式就是使用某些可以接受的值(不需要是最优的)作为其他参数的选择,然后进行不同小批量数据大小的尝试,像上面那样调整
当然,你也发现了,我这里并没有做到这么多。实际上,我们的实现并没有使用到小批量数据更新快速方法。就是简单使用了小批量数据大小为
自动技术: 我已经给出很多在手动进行超参数优化时的启发式规则。手动选择当然是种理解网络行为的方法。不过,现实是,很多工作已经使用自动化过程进行。通常的技术就是网格搜索(grid search),可以系统化地对超参数的参数空间的网格进行搜索。网格搜索的成就和限制(易于实现的变体)在 James Bergstra 和 YoshuaBengio
总结: 跟随上面的经验并不能帮助你的网络给出绝对最优的结果。但是很可能给你一个好的开始和一个改进的基础。特别地,我已经非常独立地讨论了超参数的选择。实践中,超参数之间存在着很多关系。你可能使用
选择超参数的难度在于如何选择超参数的方法太分散, 这些方法分散在许多的研究论文,软件程序甚至仅仅在一些研究人员的大脑中, 因而变得更加困难。很多很多的论文给出了(有时候矛盾的)建议。然而,还有一些特别有用的论文对这些繁杂的技术进行了梳理和总结。Yoshua Bengio在 2012 年的论文[3]中给出了一些实践上关于训练神经网络用到的反向传播和梯度下降的技术的推荐策略。Bengio 对很多问题的讨论比我这里更加细致,其中还包含如何进行系统化的超参数搜索。另一篇非常好的论文是 1998 年的 Yann LeCun、LéonBottou、Genevieve Orr 和 Klaus-Robert Müller 所著的[4]。这些论文搜集在 2012年的一本书中,这本书介绍了很多训练神经网络的常用技巧[5]。这本书挺贵的,但很多的内容其实已经被作者共享在网络上了,也许在搜索引擎上能够找到一些。
在你读这些文章时,特别是进行试验时,会更加清楚的是超参数优化就不是一个已经被完全解决的问题。总有一些技巧能够尝试着来提升性能。有句关于作家的谚语是:“书从来不会完结,只会被丢弃。”这点在神经网络优化上也是一样的:超参数的空间太大了,所以人们无法真的完成优化,只能将问题丢给后人。所以你的目标应是发展出一个工作流来确保自己快速地进行参数优化,这样可以留有足够的灵活性空间来尝试对重要的参数进行更加细节的优化。
设定超参数的挑战让一些人抱怨神经网络相比较其他的机器学习算法需要大量的工作进行参数选择。我也听到很多不同的版本:“的确,参数完美的神经网络可能会在这问题上获得最优的性能。但是,我可以尝试一下随机森林(或者 SVM 或者……这里脑补自己偏爱的技术)也能够工作的。我没有时间搞清楚那个最好的神经网络。” 当然,从一个实践者角度,肯定是应用更加容易的技术。这在你刚开始处理某个问题时尤其如此,因为那时候,你都不确定一个机器学习算法能够解决那个问题。但是,如果获得最优的性能是最重要的目标的话,你就可能需要尝试更加复杂精妙的知识的方法了。如果机器学习总是简单的话那是太好不过了,但也没有一个应当的理由说机器学习非得这么简单。
[1] search for hyper-parameter optimization,作者为 James Bergstra 和 Yoshua Bengio (2012)。
[2] Bayesian optimization of machine learning algorithms,作者为 Jasper Snoek, Hugo Larochelle,和 Ryan Adams。
[3] recommendations for gradient-based training of deep architectures,作者 为 Yoshua Bengio (2012)。
[4] BackProp,作者为 Yann LeCun, Léon Bottou, Genevieve Orr 和 Klaus-Robert Müller (1998)。
[5] Networks: Tricks of the Trade,由 Grégoire Montavon, Geneviève Orr 和 Klaus-Robert Müller 编辑。
“哈工大SCIR”公众号
编辑部:郭江,李家琦,徐俊,李忠阳,俞霖霖
本期编辑:李忠阳
长按下图并点击 “识别图中二维码”,即可关注哈尔滨工业大学社会计算与信息检索研究中心微信公共号:”哈工大SCIR” 。点击左下角“阅读原文”,即可查看原文。