优化神经网络的学习训练过程
翻译自 http://neuralnetworksanddeeplearning.com/chap3.html ,《neural networks and deep learning》的第三章内容。原作者写的非常浅显易懂,英文好的朋友推荐直接读原作,文章也有别人翻译过,但是想加深自己的理解,就自己也尝试翻译了,水平有限,难免有疏漏,谨慎阅读。
当一个高尔夫球手在初次学习打高尔夫时,他通常会花费大量的时间学习基础的挥杆动作。他们会逐渐的学习其他的击球方式,学习怎么样切球,怎么样打出曲线球,并在学习训练过程中不断的修正他们的基础挥杆动作。我们到目前为止在研究的“反向传播”算法就跟高尔夫球学习中的挥杆动作一样,它是神经网络训练中的大部分基础工作。在这一章中,我将会介绍一些能够提升“反向传播”算法效率的技术,通过这些技术可以优化神经网络的学习效率。
我们在这一章中提出的优化技术包括:“交叉熵”损失函数;四种能够提高模型泛化能力的正则化方法(L1和L2正则化,dropout and artificial expansion of the training data));一种更好的初始化神经网络中各节点权重的方法;神经网络中的超参数设置方法。我也会稍微提到一些其他的方法。这四个部分之间内容都是相互独立的,所以你也可以直接挑选你感兴趣的部分,跳过那些你已经很了解或者不感兴趣的部分。我们也会给出一些技术实现的代码,并使用这些技术实现方法改善我们在第一章中提到的手写识别问题。
由于神经网络的中涉及的技术非常多,我们这一章只会研究其中的一些常用方法。我们希望通过深入研究少数几个最重要的方法,来加强对神经网络学习理论的认识。掌握这几个最重要的技术理论及方法,不仅仅能够优化神经网络的表现,并且有助于帮你解决你在学习神经网络中可能遇到的其他问题。这些技术会有助于你快速学习其他相关技术,为你奠定理论基础。
交叉熵
无论做什么,当我们发现自己做得不好,总是会令人感到沮丧的。很久之前,我第一次给一位听众演奏钢琴的时候,我非常紧张,在演奏时,我比琴谱低了一个八度。我感到很困惑,甚至无法完成整个演奏,直到别人指出了我的错误,那真是非常尴尬的经历。相比这个不愉快的经历,我们发现当我们被别人明确的指出错误根源时,我们反而能获得更大的进步。你能想象,我在下一次给别人演奏时,就能正确的演奏出琴谱了!相反,如果没有人指出我们的错误,我们学习的效率往往就比较低。
在神经网络训练中,我们也期望神经网络也能够从错误中获得进步。在神经网络训练过程中,这能实现吗?为了回答这个问题,我们来看一个简单的例子。我们用只有一个输入的单个神经元作为示例:
我们假定这个神经元训练完成后,具备的功能是:当输入为1时,它的输出是0。这个假定只是为了方便我们演示神经元的训练过程,有助于清晰的表述怎样以梯度下降的方式来获取权重w和偏移b的过程。下面我们将具体的讨论这个神经元是怎样学习的。
首先,我们初始化权重w=0.6,b=0.9。通常情况下,有一些常用的方法来初始化这些变量的取值,在这里我并没有采用这些方法,只是简单的指定了变量的初始值。当我指定了初始值之后,这个神经元的输出是0.82(激活函数采用sigmoid函数)。我们期望的输出是0,当前的输出0.82与期望值明显有较大偏差,因此这个神经元还需要进一步训练,获取更好的W和b。点击下图中右下方的“Run”按钮,你可以看到这个神经元是如何完成训练,逐渐的接近期望值0。(注意,这并不是一个预先处理过的动画图片,是通过浏览器计算梯度之后,js程序使用这个梯度更新权重w和偏移b,最终展示给你了结果。)这个示例中的设置学习率η=0.15,确保训练的过程比较慢,这样我们可以清晰的可视化该过程。我们也可以更改设置加快训练过程,使训练在数秒内完成。在这个训练过程中,我们使用的损失函数是我们在第一章介绍过的平方损失函数。我们会在后面简短的介绍下这个损失函数,如果你还是对该函数有疑问,则可以重新阅读第一章中的该内容。(注意,你可以重复点击“Run”按钮,重复观看该训练过程)。
补图,js效果(文章头部有原文链接,可至原文观看)
正像你所看到的这样,通过快速的更改权重w和偏移b,神经元输出的误差渐渐变小,最终该神经元给出的输出值是0.09.它并不是完全等于我们的期望输出值0,但是0.09也已经是非常接近这个期望了。假设我们初始化权重w=b=2.0,那么神经元初始输出值是0.98,这是一个非常糟糕的输出。你可以点击下面示例中的“Run”按钮,来观察一下这个神经元是如何学习的。
补图,js效果(文章头部有原文链接,可至原文观看)
虽然我们在这两个例子中使用了同样的学习率(η=0.15),但是我们发现第二例子的学习速度要更慢一些。事实上,第一个示例在差不多150次迭代的时候,权重w和偏移b基本上就不再改变了,神经元的输出也更快的接近期望值0.
这个神经元的学习过程和人类的学习过程并不相同。我在本章内容的开端曾说过,我们经常能够从严重的错误中获得更高的学习效率。但是神经元从严重错误中学习进步却比较困难,相比简单的错误,神经元反而能学习的更快。经过人们的研究发现,这种现象并不是仅仅在我们这个简单神经元训练中出现,在其他神经网络的训练中也经常出现。那么为什么神经元不能从错误中获得更高的学习效率呢?是否能发掘一种方法来避免这种现象出现?
更进一步的探寻这个问题,我们发现这个神经元在学习训练过程中,是以损失函数对权重W和偏移b的偏导数决定的方向不断更改W和b的取值,即∂C/∂w和∂C/∂b。所以我们说“学习效率低”实际上与说偏导数太小是等价的。那么问题的根源就是为什么这两个偏导数比较小,那我们先来计算一下这两个偏导数看看真实的情况是什么样子的。我们在这个示例中使用的是平方损失函数,定义如下所示
在该表达式中,a是神经元在输入x=1时的输出值,y=0是我们的期望输出值。但实际上,这个表达式是W和b的函数,其中a=σ(z),而z=wx+b。(σ是sigmoid激活函数。)应用导数链式规则可得到两个偏导数如下所示:
在上式中,我们最后将y=0和x=1分别代入该表达式。我们再接下来看看上式中的σ′(z)。σ函数如下图所示
通过上面这张函数图像,可知当神经元的输出取值接近1的时候,曲线变的非常平,σ′(z)取值也就非常小。上面的等式(55)和(56)这时的取值就是非常小。这就是学习速度变慢的根本原因所在。随后我们将看到在一般的神经网络中,学习速度变慢也是由这个原因引起的,我们的这个简单示例并不是特例。
交叉熵损失函数简介
我们应该如何应对学习速度慢的问题?经过上面的分析证明,我们可以通过将平方损失函数替换成一个其他类型的损失函数来避免这个问题。为了能更明白的解释交叉熵函数,我们会对上面那个单神经元的示例进行一些扩展。我们假定我们要训练的神经元具备多个变量输入,x1,x2,….,每个变量对应的权重为w1,w2,…,该神经元偏移为b
这个神经元的输出值是a=σ(z),其中z=∑jwjxj+b.我们将这个神经元的交叉熵函数定义如下
在上式中的n代表训练样本数,对所有的样本输入x,神经元输出a和标签y按上式进行累加求和。
我们很难从直观上观察出这个函数表达式对解决学习速度降低问题是否有效。实际上,我们甚至不确定这个函数表达的含义跟我们理解的“损失”含义是否一致。因此,让我们先明确一下这个函数能被用来做损失函数的原因。
交叉熵函数能够被用来做损失函数的原因有两个。第一,函数是非负的,也就是说该函数取值C>0。(注意:由于a=σ(z)是sigmoid函数的取值,所以00。)第二,当神经元的输出跟我们期望输出越接近,交叉熵函数的取值越接近0。为什么呢?由于训练样本中y=0,如果神经元输出值a≈0,此时神经元的输出与期望输出非常接近,而交叉熵函数中的ylna=0,并且(1-y)ln(1-a)≈0,累加所有项之后则有C≈0,即可说明当神经元的输出跟我们期望输出越接近,交叉熵函数的取值越接近0。同理,如果y=1,神经元的输出a≈1,交叉熵函数取值也接近0。
综上可知,交叉熵函数是一个取值大于0,并且当神经元的输出与y越接近,其取值就越接近0,这恰恰就是我们选择损失函数的标准(注:平方损失函数也可以简单的推导出这两条特性),所以理论上交叉熵函数是可以用作损失函数的。
下一个问题,交叉熵函数能避免学习速度降低的问题吗?我们先计算交叉熵函数的对W和b的偏导数,令a=σ(z),同时利用链式求导规则,可以得到下面的表达式
对上面的表达式做一些基础运算,就可以得到下面的表达式
其中σ(z)=1/(1+e-z),对其求导可知σ′(z)=σ(z)(1-σ(z))。我希望你能在下面的练习章节中自己推到这个等式,现在我们就直接使用该等式。当我们把σ′(z)代入等式(60),就可以将其简化成下面的表达式
这是一个非常漂亮的表达式。通过该表达式可知,学习速度依赖于(σ(z)-y)的取值。(σ(z)-y)在一定程度上反映了错误的程度,也就是说错误越大,神经元每次改变w和b的幅度越大,学习速度就越快,这似乎与我们的直觉更加接近(对比作者钢琴演奏的例子)。同时,这个函数也可以避免像表达式(55)那样出现由于σ′(z)平滑而带来的学习速度降低的问题。当我们使用交叉熵函数作为损失函数时,我们不再需要计算σ′(z),也就不再需要担心它的取值太小带来的问题。正是由于我们使用了交叉熵函数,它使我们完全避免了这个问题,你也许会认为这是一个很神奇的事情。其实这并不是一个很神奇的事情,我们一会就会让你了解到,选择交叉熵函数确实是由于它具备这一特性。
我们以同样的方式计算偏移b的偏导数,我不会再给出详细的证明步骤,你可以自己验证下面的表达式
同样,对比表达式(56),这再次避免了σ′(z)带来的学习速度降低的问题
练习
证明表达式:σ′(z)= σ(z)(1-σ(z))
让我们回到最开始的单神经元简单示例中,看看当我们使用交叉熵函数替换平方损失函数后都发生了些什么。我们首先看一下,在平方函数表现较好的情况下,交叉熵函数的表现。在下图中,我们使用交叉熵函数替换了平方损失函数,初始化权重w=0.6,权重b=0.9,点击下方的“Run”按钮,可以观察训练过程:
补图,js效果(文章头部有原文链接,可至原文观看)
不出意外,采用交叉熵函数的神经元学习的过程表现良好,与采用平方损失函数的神经元学习过程类似。接下来我们将权重w和偏移b都设置成2.0,采用平方损失函数的神经元训练过程并不理想(可观察上面示例),我们看一下采用交叉熵函数的神经元训练效果如何,如下图所示:
补图,js效果(文章头部有原文链接,可至原文观看)
通过上图可知,采用交叉熵函数的神经元训练过程非常迅速,并且正向我们预期那样得到了很好的效果。如果你仔细观察的话,你会发现采用交叉熵函数的训练的曲线在初始阶段要比平方损失函数曲线更加陡峭。曲线的坡度比较大,说明了交叉熵函数正像我们预期的那样从错误中学习的更快。
在这两个例子中,我并没有像平方损失函数示例中那样指明学习率的取值。在平方损失函数中,我们将η设置为0.15。我们也应该在交叉熵损失函数中采用相同的值吗?事实上,损失函数不同,很难准确的定义相同的学习率,就像苹果和橘子的对比一样。在这几个例子中,我们设置学习率的标准是可以让我们更容易的看清楚神经元的学习过程。假如你仍旧好奇,我们可以公布我们在交叉熵损失函数中采用的学习率:η=0.005。
你可能会认为学习率的改变会使上述几个示例失去了对比意义。当你在随机初始化设置学习率参数时,谁知道神经元学习速度会有多快呢?这种想法忽略了问题的重点。这几张图的重点不是神经元训练的绝对速度对比,而是神经元的学习速度是怎样变化的。在对比示例中,采用平方损失函数要比采用交叉熵损失函数,随着神经元在逐渐接近正确值的过程中,神经元从错误中学习的速度要慢很多。这是不依赖于学习率参数的设置的。
我们已经研究了交叉熵函数对单个神经元的作用。我们可以轻易的将交叉熵函数应用推广到多层神经网络的多个神经元中。我们假设y=y1,y2,…是神经元的期望输出值,这些神经元位于最终的输出层,而aL1,aL2,..是输出层的实际输出。我们可以定义交叉熵函数如下
这个表达式跟我们表达式(57)非常相似,不同的地方是我们对所有的输出神经元进行了累加求和。我不会通过求导再证明这个表达式在避免学习速度下降方面的作用,从表面上看它在多层神经网络上应该是有效的。假如你对这个感兴趣,你可以自己通过求导验证。
我们应该在什么时候采用交叉熵函数替换平方损失函数呢?事实上,在采用sigmoid函数作为激活函数的神经网络中,交叉熵函数基本上总是更好的选择。让我们看看原因是什么,当我们在建立神经网络时,我们会随机初始化权重和偏移。这些随机选择的初始值会对某些输入给出错误的输出,也就是说神经元在应该输出0的时候,错误的输出了1,或者相反的情况也有可能,这种情况基本上不可避免的总会出现。如果我们采用平方损失函数,神经元会学习速度比较慢。由于权重会持续从训练样本中学习,因此甚至可能会出现无法最终收敛,这显然不是我们想要的。
练习
l一个关于交叉熵函数可能会混淆的点是y和a在函数中的位置。你有可能会将−[ylna+(1−y)ln(1−a)]记成−[alny+(1−a)ln(1−y)]。想想第二个表达式当y=0或者y=1会发生什么?这些情况是否会影响第一个表达式?想想为什么。
l在本章节开始的示例中,我们假设交叉熵函数输出值很小,并且对于所有的训练样本都有σ(z)≈y。这个假设依赖于y只能取值0或者1。在分类问题时,这种建设是没有问题的,但是对其他问题(如回归问题)y可能取值位于0和1之间。事实是当σ(z)=y时,交叉熵函数会取到最小值(注:对表达式63中的变量a求偏导,可知最小值点为a=y,即σ(z)=y,将a=y代入表达式63,即可得到下面的表达式),该最小值为:
表达式−[ylny+(1−y)ln(1−y)]有时候也被称为二元熵
问题
l在多层多神经元网络中,交叉熵函数是否有效性。
按照我们提到的计算方法,平方损失函数对输出层权重的偏导数是:
当神经元处于最后的收敛阶段时,σ′(zLj)会让学习的速度快速下降。对于交叉熵函数来说,对于单个输入x,其输出误差δL是
因此,在采用交叉熵函数的神经网络的输出层,损失函数对输出层权重的偏导数是
σ′(zLj)也消失了,所以交叉熵函数能够避免学习速度下降的问题。交叉熵函数并不仅仅在单个神经元中生效,在更复杂的多层多个神经元网络中也能起到效果。对于偏差b的证明也是类似的过程,如果你对这一证明过程抱有疑问,你可以自己利用公式推导出这一结果。
l如果神经网络的输出层采用线性激活函数,则可以使用平方损失函数。
假定在多层神经网络结构的输出层采用线性激活函数,也就是说不采用sigmoid函数激活,输出层激活规则aLj=zLj。在这种情况下,采用平方损失函数的输出误差δL为
跟前面的处理过程类似,计算平方损失函数对输出层权重w和偏移b的偏导数如下
这说明如果激活函数是线性函数,平方损失函数并不会造成学习速度的下降。事实上,在采用线性激活函数的情况下,平方损失函数是一个很合适的选择。
将交叉熵函数应用到手写数字识别
未完....