神经网络——损失函数与**函数的选择及各自优缺点
DNN可以使用的损失函数和**函数不少。这些损失函数和**函数如何选择呢?下面我们就对DNN损失函数和**函数的选择做一个总结。
1. 梯度消失与梯度爆炸
我们设第一层卷积的参数为 第二层卷积的参数是,依次类推。又设**函数为 ,每一层卷积在经过**函数前的值为 ,经过**函数后的值为 。
按照上面的表示,在CNN中,输入 ,第一层的输出就是 ,第二层的输出就是 ,第三层的输出就是。设最终损失为 ,我们来尝试从第三层开始,用BP算法推导一下损失对参数 的偏导数,看看会发生什么。
为了简洁起见,略过求导过程,最后的结果为:
我们常常说原始神经网络的梯度消失问题,这里的 、就是梯度消失的“罪魁祸首”。
例如sigmoid的函数,它的导数的取值范围是(0, 0.25],也就是说对于导数中的每一个元素,我们都有, ,小于1的数乘在一起,必然是越乘越小的。这才仅仅是3层,如果10层的话, 根据 ,第10层的误差相对第一层卷积的参数 的梯度将是一个非常小的值,这就是所谓的“梯度消失”。
ReLU函数的改进就是它使得 , , ,这样的话只要一条路径上的导数都是1,无论神经网络是多少层,这一部分的乘积都始终为1,因此深层的梯度也可以传递到浅层中。
当然,当Relu = 0引发的 “dead neuron"是另外一个问题,事实上,使用Relu**函数引发的"dead neuron” 统计平均占据总神经元数量的 40%,为此而引入Relu函数的各种变形,如Leaky Relu等;
Note:
上面只讨论了 对梯度消失/爆炸的影响,而梯度下降过程中另外一项则是由损失函数决定,由此,即 由**函数决定,而 则是由损失函数决定。
对于loss function梯度下降过程更新参数的过程,损失函数、**函数及权重的组合构成了总的梯度下降过程(对CNN,其各channel的不同,而RNN则共享同一,这个也是为什么RNN更容易出现梯度消失/爆炸的原因,下文再详细叙述),三者均是造成梯度下降\爆炸的原因(但往往**函数对梯度消失爆炸的影响更大),这也是为什么将sigmoid函数换成了Relu函数后依旧存在梯度消失\爆炸问题,因为换成了Relu函数只是解决了梯度消失\爆炸的一个方面,此外的连乘也是一大重要因素(这也是引入batch norm的原因:batchnorm就是通过对每一层的输出规范为均值和方差一致的方法,消除了带来的放大缩小的影响,进而解决梯度消失和爆炸的问题,或者可以理解为BN将输出从饱和区拉倒了非饱和区!详细可参考详解机器学习中的梯度消失、爆炸原因及其解决方法)
从上述公式推导中不难看出,其中的连乘项是不可避免的,能控制的只有 以及 ,前者由损失函数决定,后者由**函数决定。
结合参数更新公式:
我们希望的理想情况是损失函数在误差大 大的地方具有较大梯度,在误差小的地方具有较小梯度。
2. 均方差损失函数 + Sigmoid**函数的问题
在讲反向传播算法时,我们用均方差损失函数和Sigmoid**函数做了实例,首先我们就来看看均方差+Sigmoid的组合有什么问题。
对于均方差损失函数,公式如下:
其中,C表示代价,x表示样本,y表示实际值,a表示输出值,n表示样本的总数。为简单起见,同样一个样本为例进行说明,此时二次代价函数为:
目前训练ANN最有效的算法是反向传播算法。简而言之,训练ANN就是通过反向传播代价,以减少代价为导向,调整参数。参数主要有:神经元之间的连接权重w,以及每个神经元本身的偏置b。调参的方式是采用梯度下降算法(Gradient descent),沿着梯度方向调整参数大小。w和b的梯度推导如下:
其中,表示神经元的输入,表示**函数。从以上公式可以看出,w和b的梯度跟**函数的梯度成正比,**函数的梯度越大,w和b的大小调整得越快,训练收敛得就越快。
而神经网络若采用的**函数为Sigmoid函数,Sigmoid**函数的表达式为:
Sigmoid 的函数图像如下:
从图上可以看出,对于Sigmoid,当 的取值越来越大后,函数曲线变得越来越平缓,意味着此时的导数也越来越小。同样的,当 的取值越来越小时,也有这个问题。仅仅在 取值为0附近时,导数的取值较大。
即sigmoid只有在0附近取值时具有较大梯度,当取值较大或者较小时候, (对应上文的)值都很小,
上文中得到,运用梯度下降反向传播进行参数更新时,每一层向前递推都要乘以,得到梯度变化值。Sigmoid的这个曲线意味着在大多数时候,我们的梯度变化值很小,导致我们的更新到极值的速度较慢,也就是我们的算法收敛速度较慢。
2.1 实例论述
以一个神经元的二类分类训练为例,进行两次实验(ANN常用的**函数为sigmoid函数,该实验也采用该函数):输入一个相同的样本数据x=1.0(该样本对应的实际分类y=0);两次实验各自随机初始化参数,从而在各自的第一次前向传播后得到不同的输出值,形成不同的代价(误差):
在实验1中,随机初始化参数,使得第一次输出值为0.82(该样本对应的实际值为0);经过300次迭代训练后,输出值由0.82降到0.09,逼近实际值。而在实验2中,第一次输出值为0.98,同样经过300迭代训练,输出值只降到了0.20。
从两次实验的代价曲线中可以看出:实验1的代价随着训练次数增加而快速降低,但实验2的代价在一开始下降得非常缓慢;直观上看,初始的误差越大,收敛得越缓慢(实际原因是当初试误差大的时候,梯度更新幅度主要由**函数导数项决定,虽然初试误差大,但由于**函数导数项相差很大,造成直观上看初试误差大,反倒收敛的慢的表面现象)。
其实,误差大导致训练缓慢的原因在于使用了二次代价函数。
主要是由于当使用二次代价损失函数时候,进行后向传播过程中引入了**函数导数项,证明如下::
二次代价函数的公式为:
其中,C表示代价,x表示样本,y表示实际值,a表示输出值,n表示样本的总数。为简单起见,同样一个样本为例进行说明,此时二次代价函数为:
目前训练ANN最有效的算法是反向传播算法。简而言之,训练ANN就是通过反向传播代价,以减少代价为导向,调整参数。参数主要有:神经元之间的连接权重w,以及每个神经元本身的偏置b。调参的方式是采用梯度下降算法(Gradient descent),沿着梯度方向调整参数大小。w和b的梯度推导如下:
其中,z表示神经元的输入, 表示**函数。从以上公式可以看出,w 和 b 的梯度跟**函数的梯度成正比,**函数的梯度越大,w 和 b 的大小调整得越快,训练收敛得就越快。
而当神经网络常用的**函数为sigmoid函数,该函数的曲线如下所示:
如图所示,实验2的初始输出值(0.98)对应的梯度明显小于实验1的输出值(0.82),因此实验2的参数梯度下降得比实验1慢。这就是初始的代价(误差)越大,导致训练越慢的原因。
本质是二次损失函数引入**函数梯度项,而sigmoid**函数梯度自带“消失”属性,即,当神经网络层数比较深时,其多次乘积造成梯度回传到前层的时候更新量近似为0,导致前层神经网络难以得到训练
与我们的期望不符,即:不能像人一样,错误越大,改正的幅度越大,从而学习得越快。
事实上,sigmoid函数作为**函数的实际应用并不多,况且,能用sigmoid的地方均可用tanh函数替代,况且tanh函数还自带聚合效果(关于原点对称),目前可能用到地方也就是二分类,作为输出函数,即将0,1分类换算成概率输出。
3. 使用交叉熵损失函数+Sigmoid**函数改进DNN算法收敛速度
上一节我们讲到Sigmoid的函数特性导致反向传播算法收敛速度慢的问题,那么如何改进呢?换掉Sigmoid?这当然是一种选择。另一种常见的选择是用交叉熵损失函数来代替均方差损失函数。
我们来看看每个样本的交叉熵损失函数的形式:
其中,x表示样本,n表示样本的总数。那么,重新计算参数w的梯度:
其中(具体证明见附录):
因此,w的梯度公式中原来的被消掉了;另外,该梯度公式中的表示输出值与实际值之间的误差。所以,当误差越大,梯度就越大,参数w调整得越快,训练速度也就越快。同理可得,b的梯度为:
实际情况证明,交叉熵代价函数带来的训练效果往往比二次代价函数要好。
4. 使用对数似然损失函数和softmax**函数进行DNN分类输出
在前面我们讲的所有DNN相关知识中,我们都假设输出是连续可导的值。但是如果是分类问题,那么输出是一个个的类别,那我们怎么用DNN来解决这个问题呢?
比如假设我们有一个三个类别的分类问题,这样我们的DNN输出层应该有三个神经元,假设第一个神经元对应类别一,第二个对应类别二,第三个对应类别三,这样我们期望的输出应该是(1,0,0),(0,1,0) 和 (0,0,1)这三种。即样本真实类别对应的神经元输出应该无限接近或者等于1,而非该样本真实输出对应的神经元的输出应该无限接近或者等于0。或者说,我们希望输出层的神经元对应的输出是若干个概率值,这若干个概率值即我们DNN模型对于输入值对于各类别的输出预测,同时为满足概率模型,这若干个概率值之和应该等于1。
DNN分类模型要求是输出层神经元输出的值在0到1之间,同时所有输出值之和为1。很明显,现有的普通DNN是无法满足这个要求的。但是我们只需要对现有的全连接DNN稍作改良,即可用于解决分类问题。在现有的DNN模型中,我们可以将输出层第 个神经元的**函数定义为如下形式:
其中,是输出层第 层的神经元个数,或者说我们的分类问题的类别数。
很容易看出,所有的都是在(0,1) 之间的数字,而作为归一化因子保证了所有的之和为1。
这个方法很简洁漂亮,仅仅只需要将输出层的**函数从Sigmoid之类的函数转变为上式的**函数即可。上式这个**函数就是我们的softmax**函数。它在分类问题中有广泛的应用。将DNN用于分类问题,在输出层用softmax**函数也是最常见的了。
下面这个例子清晰的描述了softmax**函数在前向传播算法时的使用。假设我们的输出层为三个神经元,而未**的输出为3,1和-3,我们求出各自的指数表达式为:20,2.7和0.05,我们的归一化因子即为22.75,这样我们就求出了三个类别的概率输出分布为0.88,0.12和0。
从上面可以看出,将softmax用于前向传播算法是也很简单的。那么在反向传播算法时还简单吗?反向传播的梯度好计算吗?答案是Yes!
对于用于分类的softmax**函数,对应的损失函数一般都是用对数似然函数,即:
其中 即为训练样本真实的类别序号。
可见损失函数只和真实类别对应的输出有关,这样假设真实类别是第 类,则其他不属于第 类序号对应的神经元的梯度导数直接为0。对于真实类别第 类,他对应的第 个 链接 对应的梯度计算为:
KaTeX parse error: No such environment: align at position 8:
\begin{̲a̲l̲i̲g̲n̲}̲ \frac{\partial…
同样的可以得到的梯度表达式为:
可见,梯度计算也很简洁,也没有第一节说的训练速度慢的问题。举个例子,假如我们对于第2类的训练样本,通过前向算法计算的未**输出为(1,5,3),则我们得到softmax**后的概率输出为:(0.015,0.866,0.117)。由于我们的类别是第二类,则参数b的反向传播的梯度应该为:(0.015,0.866-1,0.117)。是不是很简单呢?
当softmax输出层的反向传播计算完以后,后面的普通DNN层的反向传播计算和之前讲的普通DNN没有区别。
5. DNN其他**函数
除了上面提到了**函数,DNN常用的**函数还有:
1) tanh:这个是sigmoid的变种,表达式为:
tanh**函数和sigmoid**函数的关系为:
tanh和sigmoid对比主要的特点是它的输出落在了[-1,1],这样输出可以进行标准化。同时tanh的曲线在较大时变得平坦的幅度没有sigmoid那么大,这样求梯度变化值有一些优势。当然,要说tanh一定比sigmoid好倒不一定,还是要具体问题具体分析。
2) softplus:这个其实就是sigmoid函数的原函数,表达式为:
它的导数就是sigmoid函数。softplus的函数图像和ReLU有些类似。它出现的比ReLU早,可以视为ReLU的鼻祖。
3)PReLU:从名字就可以看出它是ReLU的变种,特点是如果未**值小于0,不是简单粗暴的直接变为0,而是进行一定幅度的缩小。如下图。当然,由于ReLU的成功,有很多的跟风者,有其他各种变种ReLU,这里就不多提了。
6. DNN损失函数和**函数小结
上面我们对DNN损失函数和**函数做了详细的讨论,重要的点有:1)如果使用sigmoid**函数,则交叉熵损失函数一般肯定比均方差损失函数好。2)如果是DNN用于分类,则一般在输出层使用softmax**函数和对数似然损失函数。3)ReLU**函数对梯度消失问题有一定程度的解决,尤其是在CNN模型中。
参考资料
[1] Neural Networks and Deep Learning by By Michael Nielsen
[2] Deep Learning, book by Ian Goodfellow, Yoshua Bengio, and Aaron Courville
[3] 深度神经网络(DNN)损失函数和**函数的选择
[4] 交叉熵代价函数(作用及公式推导)