本文主要是介绍改进神经网络的学习方式(译文)----下,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
如何选择神经网络的超参数
直到现在,我们还没有解释对诸如学习率 $$\eta$$,规范化参数 $$\lambda$$ 等等超参数选择的方法。我只是给出那些效果很好的值而已。实践中,当你使用神经网络解决问题时,寻找好的超参数其实是很困难的一件事。例如,我们要解决 MNIST 问题,开始时对于选择什么样的超参数一无所知。假设,刚开始的实验中选择前面章节的参数都是运气较好。但在使用学习率 $$\eta=10.0$$ 而规范化参数 $$\lambda=1000.0$$。下面是我们的一个尝试:
>>> 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 / 10000Epoch 1 training complete
Accuracy on evaluation data: 990 / 10000Epoch 2 training complete
Accuracy on evaluation data: 1009 / 10000...Epoch 27 training complete
Accuracy on evaluation data: 1009 / 10000Epoch 28 training complete
Accuracy on evaluation data: 983 / 10000Epoch 29 training complete
Accuracy on evaluation data: 967 / 10000
我们分类准确度并不比随机选择更好。网络就像随机噪声产生器一样。
你可能会说,“这好办,降低学习率和规范化参数就好了。”不幸的是,你并不先验地知道这些就是需要调整的超参数。可能真正的问题出在 $$30$$ 个隐藏元中,本身就不能很有效,不管我们如何调整其他的超参数都没有作用的?可能我们真的需要至少 $$100$$ 个隐藏神经元?或者是 $$300$$ 个隐藏神经元?或者更多层的网络?或者不同输出编码方式?可能我们的网络一直在学习,只是学习的回合还不够?可能 minibatch 的太小了?可能我们需要切换成二次代价函数?可能我们需要尝试不同的权重初始化方法?等等。很容易就在超参数的选择中迷失了方向。如果你的网络规模很大,或者使用了很多的训练数据,这种情况就很令人失望了,因为一次训练可能就要几个小时甚至几天乃至几周,最终什么都没有获得。如果这种情况一直发生,就会打击你的自信心。可能你会怀疑神经网络是不是适合你所遇到的问题?可能就应该放弃这种尝试了?
本节,我会给出一些用于设定超参数的启发式想法。目的是帮你发展出一套工作流来确保很好地设置超参数。当然,我不会覆盖超参数优化的每个方法。那是太繁重的问题,而且也不会是一个能够完全解决的问题,也不存在一种通用的关于正确策略的共同认知。总是会有一些新的技巧可以帮助你提高一点性能。但是本节的启发式想法能帮你开个好头。
宽的策略:在使用神经网络来解决新的问题时,一个挑战就是获得任何一种非寻常的学习,也就是说,达到比随机的情况更好的结果。这个实际上会很困难,尤其是遇到一种新类型的问题时。让我们看看有哪些策略可以在面临这类困难时候尝试。
假设,我们第一次遇到 MNIST 分类问题。刚开始,你很有激情,但是当第一个神经网络完全失效时,你会就得有些沮丧。此时就可以将问题简化。丢开训练和验证集合中的那些除了 $$0$$ 和 $$1$$ 的那些图像。然后试着训练一个网络来区分 $$0$$ 和 $$1$$。不仅仅问题比 $$10$$ 个分类的情况简化了,同样也会减少 80% 的训练数据,这样就给出了 $$5$$ 倍的加速。这样可以保证更快的实验,也能给予你关于如何构建好的网络更快的洞察。
你通过简化网络来加速实验进行更有意义的学习。如果你相信 $$[784, 10]$$ 的网络更可能比随机更加好的分类效果,那么就从这个网络开始实验。这会比训练一个 $$[784, 30 ,10]$$ 的网络更快,你可以进一步尝试后一个。
你可以通过提高监控的频率来在试验中获得另一个加速了。在 network2.py
中,我们在每个训练的回合的最后进行监控。每回合 $$50,000$$,在接受到网络学习状况的反馈前需要等上一会儿——在我的笔记本上训练 $$[784, 30, 10]$$ 网络基本上每回合 $$10$$ 秒。当然,$$10$$ 秒并不太长,不过你希望尝试几十种超参数就很麻烦了,如果你想再尝试更多地选择,那就相当棘手了。我们可以通过更加频繁地监控验证准确度来获得反馈,比如说在每 $$1,000$$ 次训练图像后。而且,与其使用整个 $$10,000$$ 幅图像的验证集来监控性能,我们可以使用 $$100$$ 幅图像来进行验证。真正重要的是网络看到足够多的图像来做真正的学习,获得足够优秀的估计性能。当然,我们的程序 network2.py
并没有做这样的监控。但是作为一个凑合的能够获得类似效果的方案,我们将训练数据减少到前 $$1,000$$ 幅 MNIST 训练图像。让我们尝试一下,看看结果。(为了让代码更加简单,我并没有取仅仅是 0 和 1 的图像。当然,那样也是很容易就可以实现)。
>>> 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 / 100Epoch 1 training complete
Accuracy on evaluation data: 10 / 100Epoch 2 training complete
Accuracy on evaluation data: 10 / 100
...
我们仍然获得完全的噪声!但是有一个进步:现在我们每一秒钟可以得到反馈,而不是之前每 10 秒钟才可以。这意味着你可以更加快速地实验其他的超参数,或者甚至近同步地进行不同参数的组合的评比。
在上面的例子中,我设置 $$\lambda=1000.0$$,跟我们之前一样。但是因为这里改变了训练样本的个数,我们必须对 $$\lambda$$ 进行调整以保证权重下降的同步性。这意味着改变 $$\lambda = 20.0$$。如果我们这样设置,则有:
>>> 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 / 100Epoch 1 training complete
Accuracy on evaluation data: 14 / 100Epoch 2 training complete
Accuracy on evaluation data: 25 / 100Epoch 3 training complete
Accuracy on evaluation data: 18 / 100
...
哦也!现在有了信号了。不是非常糟糕的信号,却真是一个信号。我们可以基于这点,来改变超参数从而获得更多的提升。可能我们猜测学习率需要增加(你可以能会发现,这只是一个不大好的猜测,原因后面会讲,但是相信我)所以为了测试我们的猜测就将 $$\eta$$ 调整至 $$100.0$$:
>>> 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 / 100Epoch 1 training complete
Accuracy on evaluation data: 10 / 100Epoch 2 training complete
Accuracy on evaluation data: 10 / 100Epoch 3 training complete
Accuracy on evaluation data: 10 / 100...
这并不好!告诉我们之前的猜测是错误的,问题并不是学习率太低了。所以,我们试着将 $$\eta$$ 将至 $$\eta=1.0$$:
>>> 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 / 100Epoch 1 training complete
Accuracy on evaluation data: 42 / 100Epoch 2 training complete
Accuracy on evaluation data: 43 / 100Epoch 3 training complete
Accuracy on evaluation data: 61 / 100...
这样好点了!所以我们可以继续,逐个调整每个超参数,慢慢提升性能。一旦我们找到一种提升性能的 $$\eta$$ 值,我们就可以尝试寻找好的值。然后按照一个更加复杂的网络架构进行实验,假设是一个有 $$10$$ 个隐藏元的网络。然后继续调整 $$\eta$$ 和 $$\lambda$$。接着调整成 $$20$$ 个隐藏元。然后将其他的超参数调整再调整。如此进行,在每一步使用我们 hold out 验证数据集来评价性能,使用这些度量来找到越来越好的超参数。当我们这么做的时候,一般都需要花费更多时间来发现由于超参数改变带来的影响,这样就可以一步步减少监控的频率。
所有这些作为一种宽泛的策略看起来很有前途。然而,我想要回到寻找超参数的原点。实际上,即使是上面的讨论也传达出过于乐观的观点。实际上,很容易会遇到神经网络学习不到任何知识的情况。你可能要花费若干天在调整参数上,仍然没有进展。所以我想要再重申一下在前期你应该从实验中尽可能早的获得快速反馈。直觉上看,这看起来简化问题和架构仅仅会降低你的效率。实际上,这样能够将进度加快,因为你能够更快地找到传达出有意义的信号的网络。一旦你获得这些信号,你可以尝尝通过微调超参数获得快速的性能提升。这和人生中很多情况一样——万事开头难。
好了,上面就是宽泛的策略。现在我们看看一些具体的设置超参数的推荐。我会聚焦在学习率 $$\eta$$,L2 规范化参数 $$\lambda$$,和 minibatch 大小。然而,很多的观点同样可以应用在其他的超参数的选择上,包括一些关于网络架构的、其他类型的规范化和一些本书后面遇到的如 momentum co-efficient 这样的超参数。
学习率:假设我们运行了三个不同学习率($$\eta=0.025$$、$$\eta=0.25$$、$$\eta=2.5$$)的 MNIST 网络。我们会像前面介绍的实验那样设置其他的超参数,进行 $$30$$ 回合,minibatch 大小为 $$10$$,然后 $$\lambda = 5.0$$。我们同样会使用整个 $$50,000$$ 幅训练图像。下面是一副展示了训练代价的变化情况的图:
使用 $$\eta=0.025$$,代价函数平滑下降到最后的回合。使用 $$\eta=0.25$$,代价刚开始下降,在大约 $$20$$ 回合后接近饱和状态,后面就是微小的震荡和随机抖动。最终使用 $$\eta=2.5$$ 代价从始至终都震荡得非常明显。为了理解震荡的原因,回想一下随机梯度下降其实是期望我们能够逐渐地抵达代价函数的谷底的,
然而,如果 $$\eta$$ 太大的话,步长也会变大可能会使得算法在接近最小值时候又越过了谷底。这在 $$\eta=2.5$$ 时非常可能发生。当我们选择 $$\eta=0.25$$ 时,初始几步将我们带到了谷底附近,但一旦到达了谷底,又很容易跨越过去。而在我们选择 $$\eta=0.025$$ 时,在前 $$30$$ 回合的训练中不再受到这个情况的影响。当然,选择太小的学习率,也会带来另一个问题——随机梯度下降算法变慢了。一种更加好的策略其实是,在开始时使用 $$\eta=0.25$$,随着越来越接近谷底,就换成 $$\eta=0.025$$。这种可变学习率的方法我们后面会介绍。现在,我们就聚焦在找出一个单独的好的学习率的选择,$$\eta$$。
所以,有了这样的想法,我们可以如下设置 $$\eta$$。首先,我们选择在训练数据上的代价立即开始下降而非震荡或者增加时作为 $$\eta$$ 的阈值的估计。这个估计并不需要太过精确。你可以估计这个值的量级,比如说从 $$\eta=0.01$$ 开始。如果代价在训练的前面若干回合开始下降,你就可以逐步地尝试 $$\eta=0.1, 1.0,...$$,直到你找到一个 $$\eta$$ 的值使得在开始若干回合代价就开始震荡或者增加。相反,如果代价在 $$\eta=0.01$$ 时就开始震荡或者增加,那就尝试 $$\eta=0.001, 0.0001,...$$ 直到你找到代价在开始回合就下降的设定。按照这样的方法,我们可以掌握学习率的阈值的量级的估计。你可以选择性地优化估计,选择那些最大的 $$\eta$$,比方说 $$\eta=0.5$$ 或者 $$\eta=0.2$$(这里也不需要过于精确)。
显然,$$\eta$$ 实际值不应该比阈值大。实际上,如果 $$\eta$$ 的值重复使用很多回合的话,你更应该使用稍微小一点的值,例如,阈值的一半这样的选择。这样的选择能够允许你训练更多的回合,不会减慢学习的速度。
在 MNIST 数据中,使用这样的策略会给出一个关于学习率 $$\eta$$ 的一个量级的估计,大概是 $$0.1$$。在一些改良后,我们得到了阈值 $$\eta=0.5$$。所以,我们按照刚刚的取一半的策略就确定了学习率为 $$\eta=0.25$$。实际上,我发现使用 $$\eta=0.5$$ 在 $$30$$ 回合内表现是很好的,所以选择更低的学习率,也没有什么问题。
这看起来相当直接。然而,使用训练代价函数来选择 $$\eta$$ 看起来和我们之前提到的通过验证集来确定超参数的观点有点矛盾。实际上,我们会使用验证准确度来选择规范化超参数,minibatch 大小,和层数及隐藏元个数这些网络参数,等等。为何对学习率要用不同的方法呢?坦白地说,这些选择其实是我个人美学偏好,个人习惯罢了。原因就是其他的超参数倾向于提升最终的测试集上的分类准确度,所以将他们通过验证准确度来选择更合理一些。然而,学习率仅仅是偶然地影响最终的分类准确度的。学习率主要的目的是控制梯度下降的步长,监控训练代价是最好的检测步长过大的方法。所以,这其实就是个人的偏好。在学习的前期,如果验证准确度提升,训练代价通常都在下降。所以在实践中使用那种衡量方式并不会对判断的影响太大。
使用 Early stopping 来确定训练的回合数:正如我们在本章前面讨论的那样,Early stopping 表示在每个回合的最后,我们都要计算验证集上的分类准确度。当准确度不再提升,就终止它。这让选择回合数变得很简单。特别地,也意味着我们不再需要担心显式地掌握回合数和其他超参数的关联。而且,这个过程还是自动的。另外,Early stopping 也能够帮助我们避免过匹配。尽管在实验前期不采用 Early stopping,这样可以看到任何过匹配的信号,使用这些来选择规范化方法,但 early stopping 仍然是一件很棒的事。
我们需要再明确一下什么叫做分类准确度不再提升,这样方可实现 Early stopping。正如我们已经看到的,分类准确度在整体趋势下降的时候仍旧会抖动或者震荡。如果我们在准确度刚开始下降的时候就停止,那么肯定会错过更好的选择。一种不错的解决方案是如果分类准确度在一段时间内不再提升的时候终止。例如,我们要解决 MNIST 问题。如果分类准确度在近 $$10$$ 个回合都没有提升的时候,我们将其终止。这样不仅可以确保我们不会终止得过快,也能够使我们不要一直干等直到出现提升。
这种 $$10$$ 回合不提升就终止的规则很适合 MNIST 问题的一开始的探索。然而,网络有时候会在很长时间内于一个特定的分类准确度附近形成平缓的局面,然后才会有提升。如果你尝试获得相当好的性能,这个规则可能就会太过激进了——停止得太草率。所以,我建议在你更加深入地理解网络训练的方式时,仅仅在初始阶段使用 $$10$$ 回合不提升规则,然后逐步地选择更久的回合,比如说:$$20$$ 回合不提升就终止,$$20$$ 回合不提升就终止,以此类推。当然,这就引入了一种新的需要优化的超参数!实践中,其实比较容易设置这个超参数来获得相当好的结果。类似地,对不同于 MNIST 的问题,$$10$$ 回合不提升就终止的规则会太多激进或者太多保守,这都取决于问题的本身特质。然而,进行一些小的实验,发现好的提前终止的策略还是非常简单的。
我们还没有使用提前终止在我们的 MNIST 实验中。原因是我们已经比较了不同的学习观点。这样的比较其实比较适合使用同样的训练回合。但是,在 network2.py
中实现提前终止还是很有价值的:
问题
- 修改
network2.py
来实现提前终止,并让 $$n$$ 回合不提升终止策略中的 $$n$$ 称为可以设置的参数。 - 你能够想出不同于 $$n$$ 回合不提升终止策略的其他提前终止策略么?理想中,规则应该能够获得更高的验证准确度而不需要训练太久。将你的想法实现在
network2.py
中,运行这些实验和 $$10$$ 回合不提升终止策略比较对应的验证准确度和训练的回合数。
学习率调整:我们一直都将学习率设置为常量。但是,通常采用可变的学习率更加有效。在学习的前期,权重可能非常糟糕。所以最好是使用一个较大的学习率让权重变化得更快。越往后,我们可以降低学习率,这样可以作出更加精良的调整。
我们要如何设置学习率呢?其实有很多方法。一种自然的观点是使用提前终止的想法。就是保持学习率为一个常量知道验证准确度开始变差。然后按照某个量下降学习率,比如说按照 $$10$$ 或者 $$2$$。我们重复此过程若干次,知道学习率是初始值的 $$1/1024$$(或者$$1/1000$$)。那时就终止。
可变学习率可以提升性能,但是也会产生大量可能的选择。这些选择会让人头疼——你可能需要花费很多精力才能优化学习规则。对刚开始实验,我建议使用单一的常量作为学习率的选择。这会给你一个比较好的近似。后面,如果你想获得更好的性能,值得按照某种规则进行实验,根据我已经给出的资料。
A readable recent paper which demonstrates the benefits of variable learning rates in attacking MNIST is Deep, Big, Simple Neural Nets Excel on Handwritten Digit Recognition, by Dan Claudiu Cireșan, Ueli Meier, Luca Maria Gambardella, and Jürgen Schmidhuber (2010).
练习
- 更改
network2.py
实现学习规则:每次验证准确度满足满足$$10$$ 回合不提升终止策略时改变学习率;当学习率降到初始值的 $$1/128$$ 时终止。
规范化参数:我建议,开始时不包含规范化($$\lambdas=0.0$$),确定 $$\eta$$ 的值。使用确定出来的 $$\eta$$,我们可以使用验证数据来选择好的 $$\lambda$$。从尝试 $$\lambda=1.0$$ 开始,然后根据验证集上的性能按照因子 $$10$$增加或减少其值。一旦我已经找到一个好的量级,你可以改进 $$\lambda$$ 的值。这里搞定后,你就可以返回再重新优化 $$\eta$$。
练习
- 使用梯度下降来尝试学习好的超参数的值其实很受期待。你可以想像关于使用梯度下降来确定 $$\lambda$$ 的障碍么?你能够想象关于使用梯度下降来确定 $$\eta$$ 的障碍么?
在本书前面,我是如何选择超参数的:如果你使用本节给出的推荐策略,你会发现你自己找到的 $$\eta$$ 和 $$\lambda$$ 不总是和我给出的一致。原因自傲与,本书有一些限制,有时候会使得优化超参数变得不现实。想想我们已经做过的使用不同观点学习的对比,比如说,比较二次代价函数和交叉熵代价函数,比较权重初始化的新旧方法,使不使用规范化,等等。为了使这些比较有意义,我通常会将参数在这些方法上保持不变(或者进行合适的尺度调整)。当然,同样超参数对不同的学习观点都是最优的也没有理论保证,所以我用的那些超参数常常是折衷的选择。
相较于这样的折衷,其实我本可以尝试优化每个单一的观点的超参数选择。理论上,这可能是更好更公平的方式,因为那样的话我们可以看到每个观点的最优性能。但是,我们现在依照目前的规范进行了众多的比较,实践上,我觉得要做到需要过多的计算资源了。这也是我使用折衷方式来采用尽可能好(却不一定最优)的超参数选择。
minibatch 大小:我们应该如何设置 minibatch 的大小?为了回答这个问题,让我们先假设正在进行在线学习,也就是说使用大小为 $$1$$ 的minibatch。
一个关于在线学习的担忧是使用只有一个样本的 minibatch 会带来关于梯度的错误估计。实际上,误差并不会真的产生这个问题。原因在于单一的梯度估计不需要绝对精确。我们需要的是确保代价函数保持下降的足够精确的估计。就像你现在要去北极点,但是只有一个不大精确的(差个 $$10-20$$ 度)指南针。如果你不再频繁地检查指南针,指南针会在平均状况下给出正确的方向,所以最后你也能抵达北极点。
基于这个观点,这看起来好像我们需要使用在线学习。实际上,情况会变得更加复杂。在上一章的问题中 我指出我们可以使用矩阵技术来对所有在 minibatch 中的样本同时计算梯度更新,而不是进行循环。所以,取决于硬件和线性代数库的实现细节,这会比循环方式进行梯度更新快好多。也许是 $$50$$ 和 $$100$$ 倍的差别。
现在,看起来这对我们帮助不大。我们使用 $$100$$ 的minibatch 的学习规则如下;
这里是对 minibatch 中所有训练样本求和。而在线学习是
即使它仅仅是 $$50$$ 倍的时间,结果仍然比直接在线学习更好,因为我们在线学习更新得太过频繁了。假设,在 minibatch 下,我们将学习率扩大了 $$100$$ 倍,更新规则就是
这看起来项做了 $$100$$ 次独立的在线学习。但是仅仅比在线学习花费了 $$50$$ 倍的时间。当然,其实不是同样的 100 次在线学习,因为 minibatch 中 $$\nabla C_x$$ 是都对同样的权重进行衡量的,而在线学习中是累加的学习。使用更大的 minibatch 看起来还是显著地能够进行训练加速的。
所以,选择最好的 minibatch 大小也是一种折衷。太小了,你不会用上很好的矩阵库的快速计算。太大,你是不能够足够频繁地更新权重的。你所需要的是选择一个折衷的值,可以最大化学习的速度。幸运的是,minibatch 大小的选择其实是相对独立的一个超参数(网络整体架构外的参数),所以你不需要优化那些参数来寻找好的 minibatch 大小。因此,可以选择的方式就是使用某些可以接受的值(不需要是最优的)作为其他参数的选择,然后进行不同 minibatch 大小的尝试,像上面那样调整 $$\eta$$。画出验证准确度的值随时间(非回合)变化的图,选择哪个得到最快性能的提升的 minibatch 大小。得到了 minibatch 大小,也就可以对其他的超参数进行优化了。
当然,你也发现了,我这里并没有做到这么多。实际上,我们的实现并没有使用到 minibatch 更新快速方法。就是简单使用了 minibatch 大小为 $$10$$。所以,我们其实可以通过降低 minibatch 大小来进行提速。我也没有这样做,因为我希望展示 minibatch 大于 $$1$$ 的使用,也因为我实践经验表示提升效果其实不明显。在实践中,我们大多数情况肯定是要实现更快的 minibatch 更新策略,然后花费时间精力来优化 minibatch 大小,来达到总体的速度提升。
自动技术:我已经给出很多在手动进行超参数优化时的启发式规则。手动选择当然是种理解网络行为的方法。不过,现实是,很多工作已经使用自动化过程进行。通常的技术就是网格搜索(grid search),可以系统化地对超参数的参数空间的网格进行搜索。网格搜索的成就和限制(易于实现的变体)在 James Bergstra 和 Yoshua Bengio $$2012$$ 年的论文中已经给出了综述。很多更加精细的方法也被大家提出来了。我这里不会给出介绍,但是想指出 2012 年使用贝叶斯观点自动优化超参数的论文。代码可以获得,也已经被其他的研究人员使用了。
总结:跟随上面的经验并不能帮助你的网络给出绝对最优的结果。但是很可能给你一个好的开始和一个改进的基础。特别地,我已经非常独立地讨论了超参数的选择。实践中,超参数之间存在着很多关系。你可能使用 $$\eta$$ 进行试验,发现效果不错,然后去优化 $$\lambda$$,发现这里又对 $$\eta$$ 混在一起了。在实践中,一般是来回往复进行的,最终逐步地选择到好的值。总之,启发式规则其实都是经验,不是金规玉律。你应该注意那些没有效果的尝试的信号,然后乐于尝试更多试验。特别地,这意味着需要更加细致地监控神经网络行为,特别是验证集上的准确度。
选择超参数的难度由于如何选择超参数的方法太繁多(分布在太多的研究论文,软件程序和仅仅在一些研究人员的大脑中)变得更加困难。很多很多的论文给出了(有时候矛盾的)建议。然而,还有一些特别有用的论文对这些繁杂的技术进行了梳理和总结。Yoshua Bengio 在 $$2012$$ 年的论文中给出了一些实践上关于训练神经网络用到的反向传播和梯度下降的技术的推荐策略。Bengio 对很多问题的讨论比我这里更加细致,其中还包含如何进行系统化的超参数搜索。另一篇文章是 $$1998$$ 年的 Yann LeCun、Léon Bottou、Genevieve Orr 和 Klaus-Robert Müller 的。这些论文搜集在 2012年的一本书中,这本书介绍了很多训练神经网络的常用技巧。这本书挺贵的,但很多的内容其实已经被作者共享在网络上了,也许在搜搜引擎上能够找到一些。
在你读这些文章时,特别是进行试验时,会更加清楚的是超参数优化就不是一个已经被完全解决的问题。总有一些技巧能够尝试着来提升性能。有句关于作家的谚语是:“书从来不会完结,只会被丢弃。”这点在神经网络优化上也是一样的:超参数的空间太大了,所以人们无法真的完成优化,只能将问题丢给后人。所以你的目标应是发展出一个工作流来确保自己快速地进行参数优化,这样可以留有足够的灵活性空间来尝试对重要的参数进行更加细节的优化。
设定超参数的挑战让一些人抱怨神经网络相比较其他的机器学习算法需要大量的工作进行参数选择。我也听到很多不同的版本:“的确,参数完美的神经网络可能会在这问题上获得最优的性能。但是,我可以尝试一下随机森林(或者 SVM 或者……这里脑补自己偏爱的技术)也能够工作的。我没有时间搞清楚那个最好的神经网络。” 当然,从一个实践者角度,肯定是应用更加容易的技术。这在你刚开始处理某个问题时尤其如此,因为那时候,你都不确定一个机器学习算法能够解决那个问题。但是,如果获得最优的性能是最重要的目标的话,你就可能需要尝试更加复杂精妙的知识的方法了。如果机器学习总是简单的话那是太好不过了,但也没有一个应当的理由说机器学习非得这么简单。
其他技术
本章中讲述的每个技术都是很值得学习的,但是不仅仅是由于那些我提到的愿意。更重要的其实是让你自己熟悉在神经网络中出现的问题以及解决这些问题所进行分析的方式。所以,我们现在已经学习了如何思考神经网络。本章后面部分,我会简要地介绍一系列其他技术。这些介绍相比之前会更加粗浅,不过也会传达出关于神经网络中多样化的技术的精神。
随机梯度下降的变种
通过反向传播进行的随机梯度下降已经在 MNIST 数字分类问题上有了很好的表现。然而,还有很多其他的观点来优化代价函数,有时候,这些方法能够带来比 minibatch 随机梯度下降更好的效果。本节,我会介绍两种观点,Hessian 和 momentum 技术。
Hessian 技术:为了更好地讨论这个技术,我们先把神经网络放在一边。相反,我直接思考最小化代价函数 $$C$$ 的抽象问题,其中 $$C$$ 是多个参数的函数,$$w=w_1,w_2,...$$,所以 $$C=C(w)$$。借助于泰勒展开式,代价函数可以在点 $$w$$ 处被近似为:
我们可以将其压缩为:
其中 $$\nabla C$$ 是通常的梯度向量,$$H$$ 就是矩阵形式的 Hessian 矩阵,其中 $$jk$$-th 项就是 $$\partial^2 C/\partial w_j\partial w_k$$。假设我们通过丢弃更高阶的项来近似 $$C$$,
使用微积分,我们可证明右式表达式可以进行最小化,选择:
根据(105)是代价函数的比较好的近似表达式,我们期望从点 $$w$$ 移动到 $$w+\Delta w = w - H^{-1}\nabla C$$ 可以显著地降低代价函数的值。这就给出了一种优化代价函数的可能的算法:
- 选择开始点,$$w$$
- 更新 $$w$$ 到新点 $$w' = w - H_{-1}\nabla C$$,其中 Hessian $$H$$ 和 $$\nabla C$$ 在 $$w$$ 处计算出来的
- 更新 $$w'$$ 到新点 $$w'' = w' - H'^{-1}\nabla' C$$,其中 Hessian $$H'$$ 和 $$\nabla' C$$ 在 $$w'$$ 处计算出来的
- ...
实际应用中,(105)是唯一的近似,并且选择更小的 step 会更好。我们通过重复地使用改变量 $$\Delta w = -\etaH^{-1} \nabla C$$ 来 改变 $$w$$,其中 $$\eta$$ 就是学习率。
这个最小化代价函数的方法常常被称为 Hessian 技术 或者 Hessian 优化。在理论上和实践中的结果都表明 Hessian 方法比标准的梯度下降方法收敛速度更快。特别地,通过引入代价函数的二阶变化信息,可以让 Hessian 方法避免在梯度下降中常碰到的多路径(pathologies)问题。而且,反向传播算法的有些版本也可以用于计算 Hessian。
如果 Hessian 优化这么厉害,为何我们这里不使用它呢?不幸的是,尽管 Hessian 优化有很多可取的特性,它其实还有一个不好的地方:在实践中很难应用。这个问题的部分原因在于 Hessian 矩阵的太大了。假设你有一个 $$10^7$$ 个权重和偏差的网络。那么对应的 Hessian 矩阵会有 $$10^7 \times 10^7=10^14$$ 个元素。这真的是太大了!所以在实践中,计算 $$H^{-1}\nabla C$$ 就极其困难。不过,这并不表示学习理解它没有用了。实际上,有很多受到 Hessian 优化启发的梯度下降的变种,能避免产生太大矩阵的问题。让我们看看其中一个称为基于 momentum 梯度下降的方法。
基于 momentum 的梯度下降:直觉上看,Hessian 优化的优点是它不仅仅考虑了梯度,而且还包含梯度如何变化的信息。基于 momentum 的梯度下降就基于这个直觉,但是避免了二阶导数的矩阵的出现。为了理解 momentum 技术,想想我们关于梯度下降的原始图片,其中我们研究了一个球滚向山谷的场景。那时候,我们发现梯度下降,除了这个名字外,就类似于球滚向山谷的底部。momentum 技术修改了梯度下降的两处使之类似于这个物理场景。首先,为我们想要优化的参数引入了一个称为速度(velocity)的概念。梯度的作用就是改变速度,而不是直接的改变位置,就如同物理学中的力改变速度,只会间接地影响位置。第二,momentum 方法引入了一种摩擦力的项,用来逐步地减少速度。
让我们给出更加准确的数学描述。我们引入对每个权重 $$w_j$$ 设置相应的速度变量 $$v=v_1,v_2,...$$。注意,这里的权重也可以笼统地包含偏差。然后我们将梯度下降更新规则 $$w\rightarrow w'=w-\eta\nabla C$$ 改成
在这些方程中,$$\mu$$ 是用来控制阻碍或者摩擦力的量的超参数。为了理解这个公式,可以考虑一下当 $$\mu=1$$ 的时候,对应于没有任何摩擦力。所以,此时你可以看到力 $$\nabla C$$ 改变了速度,$$v$$,速度随后再控制 $$w$$ 变化率。直觉上看,我们通过重复地增加梯度项来构造速度。这表示,如果梯度在某些学习的过程中几乎在同样的方向,我们可以得到在那个方向上比较大的移动量。想想看,如果我们直接按坡度下降,会发生什么:
每一步速度都不断增大,所以我们会越来越快地达到谷底。这样就能够确保 momentum 技术比标准的梯度下降运行得更快。当然,这里也会有问题,一旦达到谷底,我们就会跨越过去。或者,如果梯度本该快速改变而没有改变,那么我们会发现自己在错误的方向上移动太多了。这就是在(107)式中使用 $$\mu$$ 这个超参数的原因了。前面提到,$$\mu$$ 可以控制系统中的摩擦力大小;更加准确地说,你应该将 $$1-\mu$$ 看成是摩擦力的量。当 $$\mu=1$$ 时,没有摩擦,速度完全由梯度 $$\nabla C$$ 决定。相反,若是 $$\mu=0$$,就存在很大的摩擦,速度无法叠加,公式(107)(108)就变成了通常的梯度下降,$$w\righarrow w'=w-\eta \nabla C$$。在实践中,使用 $$0$$ 和 $$1$$ 之间的 $$\mu$$ 值可以给我们避免过量而又能够叠加速度的好处。我们可以使用 hold out 验证数据集来选择合适的 $$\mu$$ 值,就像我们之前选择 $$\eta$$ 和 $$\lambda$$ 那样。
我到现在也没有把 $$\mu$$ 看成是超参数。原因在于 $$\mu$$ 的标准命名不大好:它叫做 moment co-efficient。这其实很让人困惑,因为 $$\mu$$ 并不是物理学那个叫做动量(momentum)的东西。并且,它更像摩擦力的概念。然而,现在这个术语已经被大家广泛使用了,所以我们继续使用它。
关于 momentum 技术的一个很好的特点是它基本上不需要改变太多梯度下降的代码就可以实现。我们可以继续使用反向传播来计算梯度,就和前面那样,使用随机选择的 minibatch 的方法。这样的话,我们还是能够从 Hessian 技术中学到的优点的——使用梯度如何改变的信息。也仅仅需要进行微小的调整。实践中,momentum 技术很常见,也能够带来学习速度的提升。
练习
- 如果我们使用 $$\mu>1$$ 会有什么问题?
- 如果我们使用 $$\mu<0$$ 会有什么问题?
问题
- 增加基于 momentum 的随机梯度下降到
network2.py
中。
其他优化代价函数的方法:很多其他的优化代价函数的方法也被提出来了,并没有关于哪种最好的统一意见。当你越来越深入了解神经网络时,值得去尝试其他的优化技术,理解他们工作的原理,优势劣势,以及在实践中如何应用。前面我提到的一篇论文,介绍并对比了这些技术,包含共轭梯度下降和 BFGS 方法(也可以看看 limited memory BFGS,L-BFGS)。另一种近期效果很不错技术是 Nesterov 的加速梯度技术,这个技术对 momentum 技术进行了改进。然而,对很多问题,标准的随机梯度下降算法,特别当 momentum 用起来后就可以工作得很好了,所以我们会继续在本书后面使用随机梯度下算法。
人工神经元的其他模型
到现在,我们使用的神经元都是 sigmoid 神经元。理论上讲,从这样类型的神经元构建起来的神经网络可以计算任何函数。实践中,使用其他模型的神经元有时候会超过 sigmoid 网络。取决于不同的应用,基于其他类型的神经元的网络可能会学习得更快,更好地泛化到测试集上,或者可能两者都有。让我们给出一些其他的模型选择,便于了解常用的模型上的变化。
可能最简单的变种就是 $$\tanh$$(发音为 tanch)神经元,使用双曲正切(hyperbolic tangent)函数替换了 sigmoid 函数。输入为 $$x$$,权重向量为 $$w$$,偏差为 $$b$$ 的 $$\tanh$$ 神经元的输出是
这其实和 sigmoid 神经元关系相当密切。回想一下 $$\tanh$$ 函数的定义:
进行简单的代数运算,我们可以得到
也就是说,$$\tanh$$ 仅仅是 sigmoid 函数的按比例变化版本。我们同样也能用图像看看 $$\tanh$$ 的形状:
这两个函数之间的一个差异就是 $$\tanh$$ 神经元的输出的值域是 $$(-1, 1)$$ 而非 $$(0, 1)$$。这意味着如果你构建基于 $$\tanh$$ 神经元,你可能需要正规化最终的输出(取决于应用的细节,还有你的输入),跟 sigmoid 网络略微不同。
类似于 sigmoid 神经元,基于 $$\tanh$$ 的网络可以在理论上,计算任何将输入映射到 $$(-1, 1)$$ 的函数。而且,诸如反向传播和随机梯度下降这样的想法也能够轻松地用在 $$\tanh$$ 神经元构成的网络上的。
练习
- 证明公式(111)
那么你应该在网络中使用什么类型的神经元呢,$$\tanh$$ 还是 sigmoid?实话讲,确实并没有先验的答案!然而,存在一些理论论点和实践证据表明 $$\tanh$$ 有时候表现更好。
例如 Efficient BackProp, by Yann LeCun, Léon Bottou, Genevieve Orr and Klaus-Robert Müller (1998), and Understanding the difficulty of training deep feedforward networks, by Xavier Glorot and Yoshua Bengio (2010).
让我简要介绍一下其中关于 $$\tanh$$ 的一个理论观点。假设我们使用 sigmoid 神经元,所有激活值都是正数。让我们考虑一下权重 $$w_{jk}^{l+1}$$ 输入到第 $$l+1$$ 层的第 $$j$$ 个神经元上。反向传播的规则告诉我们相关的梯度是 $$a_k^l\delta_j^{l+1}$$。因为所有的激活值都是正数,所以梯度的符号就和 $$\delta_j^{l+1}$$ 一致。这意味着如果 $$\delta_j^{l+1}$$ 为正,那么所有的权重 $$w_{jk}^{l+1}$$ 都会在梯度下降时减少,而如果 $$\delta_j^{l+1}$$ 为负,那么所有的权重 $$w_{jk}^{l+1}$$ 都会在梯度下降时增加。换言之,针对同一的神经元的所有权重都会或者一起增加或者一起减少。这就有问题了,因为某些权重可能需要有相反的变化。这样的话,只能是某些输入激活值有相反的符号才可能出现。所以,我们用 $$\tanh$$ 替换就能够达到这个目的。因此,因为 $$\tanh$$ 是关于 $$0$$ 对称的,$$tanh(-z)=-tanh(z)$$,我们甚至期望,大概地,隐藏层的激活值能够在正负间保持平衡。这样其实可以保证对权重更新没有系统化的单方面的偏差。
我们应当如何看待这个论点?尽管论点是建设性的,但它还只是一个启发式的规则,而非严格证明说 $$\tanh$$ 就一定超过 sigmoid 函数。可能 sigmoid 神经元还有其他的特性能够补偿这个问题?实际上,对很多任务,$$\tanh$$ 在实践中给出了微小的甚至没有性能提升。不幸的是,我们还没有快速准确的规则说哪种类型的神经元对某种特定的应用学习得更快,或者泛化能力最强。
另一个变体就是 Rectified Linear 神经元或者 Rectified Linear Unit,简记为 RLU。输入为 $$x$$,权重向量为 $$w$$,偏差为 $$b$$ 的 RLU 神经元的输出是:
图像上看,函数 $$\max(0,z)$$ 是这样的:
显然,这样的神经元和 sigmoid 和 $$\tanh$$ 都不一样。然而,RLU 也是能够用来计算任何函数的,也可以使用反向传播算法和随机梯度下降进行训练。
什么时候应该使用 RLU 而非其他神经元呢?一些近期的图像识别上的研究工作找到了使用 RLU 所带来的好处。然而,就像 $$\tanh$$ 神经元那样,我们还没有一个关于什么时候 什么原因 RLU 表现更好的深度的理解。为了让你感受一下这个问题,回想起 sigmoid 神经元在饱和时停止学习的问题,也就是输出接近 $$0$$ 或者 $$1$$ 的时候。在这章我们也反复看到了问题就是 $$\sigma'$$ 降低了梯度,减缓了学习。$$\tanh$$ 神经元也有类似的问题。对比一下,提高 RLU 的带权输入并不会导致其饱和,所以就不存在前面那样的学习速度下降。另外,当带权输入是负数的时候,梯度就消失了,所以神经元就完全停止了学习。这就是很多有关理解 RLU 何时何故更优的问题中的两个。
我已经给出了一些不确定性的描述,指出我们现在还没有一个坚实的理论来解释如何选择激活函数。实际上,这个问题比我已经讲过的还要困难,因为其实是有无穷多的可能的激活函数。所以对给定问题,什么激活函数最好?什么激活函数会导致学习最快?哪个能够给出最高的测试准确度?其实现在并没有太多真正深刻而系统的研究工作。理想中,我们会有一个理论告诉人们,准确细致地,如何选择我们的激活函数。另外,我们不应该让这种缺失阻碍我们学习和应用神经网络!我们已经有了一些强大的工作,可以使用它们完成很多的研究工作。本书剩下的部分中,我会继续使用 sigmoid 神经元作为首选,因为他们其实是强大的也给出了具体关于神经网络核心思想的示例。但是你需要记住的是,这些同样的想法也都可以用在其他类型的神经元上,有时候的确会有一些性能的提升。
有关神经网络的故事
问题:你怎么看那些全部由实验效果支撑(而非数学保证)的使用和研究机器学习技术呢?同样,在哪些场景中,你已经注意到这些技术失效了?
答案:你需要认识到,我们的理论工具的缺乏。有时候,我们有很好的关于某些特定的技术应该可行的数学直觉。有时候我们的直觉最终发现是错误的。…… 这个问题其实是:我的方法在这个特定的问题的工作得多好,还有方法表现好的那些问题的范围有多大。
- Question and answer by Yann LeCun
曾经我参加量子力学基础的会议时,我注意到让我最好奇的口头表达:在报告结束时,听众的问题通常是以“我对你的观点很赞同,但是...”开始。量子力学基础不是我的擅长领域,我注意到这种类型的质疑,因为在其他的科学会议上,我很少(或者说,从未)听到这种同情。那时候,我思考了这类问题存在的原因,实际上是因为这个领域中很少有重大的进展,人们都是停在原地。后来,我意识到,这个观念相当的尖刻。发言人正在尝试解决一些人们所遇到的一些最难的问题。进展当然会非常缓慢!但是,听听人们目前正在思考的方式也是非常有价值的,即使这些尝试不一定会有无可置疑的新进展。
你可能会注意到类似于“我对你的观点很赞同,但是...”的话语。为了解释我们已经看到的情况,我通常会使用“启发式地,...”或者“粗略地讲,...”,然后接上解释某个现象或者其他问题的故事。这些故事是可信的,但是实验性的证据常常是不够充分的。如果你通读研究文献,你会发现在神经网络研究中很多类似的表达,基本上都是没有太过充分的支撑证据的。所以我们应该怎样看待这样的故事呢?
在科学的很多分支——尤其是那些解决相当简单现象的领域——很容易会得到一些关于很一般的假说的非常扎实非常可靠的证据。但是在神经网络中,存在大量的参数和超参数及其间极其复杂的交互。在这样复杂系统中,构建出可靠的一般的论断就尤其困难。在完全一般性上理解神经网络实际上,和量子力学基础一样,都是对人类思维极限的挑战。实际上,我们通常是和一些一般的理论的具体的实例在打交道——找到正面或者反面的证据。所以,这些理论在有新的证据出现时,也需要进行调整甚至丢弃。
对这种情况的一种观点是——任何启发式的关于神经网络的论点会带来一个挑战。例如,考虑之前我引用的语句 ,解释 dropout 工作的原因:“这个技术减少了复杂的神经元之间的互适应,因为一个神经元不能够依赖于特定其他神经元的存在。因此,这个就强制性地让我们学习更加健壮的在很多不同的神经元的随机子集的交集中起到作用的那些特征。”这是一个丰富而又争议的假说,我们可以根据这个观点发展出一系列的研究项目,搞清楚哪些部分真的,哪些是假的,那个需要变化和改良。实际上,有一小部分研究人员正在调查 dropout(和其他变体)试着理解其工作的机制,还有 dropout 的极限所在。所以,这些研究也跟随着那些我们已经讨论过的启发式想法。每个启发式想法不仅仅是一个(潜在的)解释,同样也是一种更加细化地调查和理解的挑战。
当然,对某个单独的人去研究所有这些启发式想法其实在时间上是不允许的。需要神经网络的研究群体花费数十年(或者更多)来发展出一个相当强大,基于证据的关于神经网络工作的原理的理论。那么这是不是就意味着我们应当因为它的不严格和无法充分地证明而放弃启发式规则么?不!实际上,我们需要这样的启发式想法来启迪和指导我们的思考。这有点像大航海时代:早期的探险家在一种重要的指导方式都有错误的前提下有时候都进行了探索(并作出了新的发现)。后来,这些错误在我们对地理知识的清晰后而被纠正过来。当你对某件事理解不深时——就像探险家对地理的理解和我们现在对神经网络的理解——忽略一些相对严格的纠正每一步思考而胆大地探索若干问题显得更加重要。所以你应该将这些故事看成是一种关于我们如何思考神经网络的有用的指导,同时保留关于这些想法的能力极限的合理的关注,并细致地跟踪对任何一个推理的证据的强弱。换言之,我们需要很好的故事来不断地激励和启发自己去勇敢地探索,同时使用严格的深刻的调查来发现真理。
这篇关于改进神经网络的学习方式(译文)----下的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!