神经网络——损失函数与激活函数的选择及各自优缺点

DNN可以使用的损失函数和激活函数不少。这些损失函数和激活函数如何选择呢?下面我们就对DNN损失函数和激活函数的选择做一个总结。

1. 梯度消失与梯度爆炸

我们设第一层卷积的参数为 ( W 1 , b 1 ) (W_1, b_1) 第二层卷积的参数是, ( W 2 , b 2 ) (W_2, b_2) 依次类推。又设激活函数为 f f ,每一层卷积在经过激活函数前的值为 ,经 a i a_i 过激活函数后的值为 f i f_i

按照上面的表示,在CNN中,输入 x x ,第一层的输出就是 f 1 = ( W 1 x + b 1 ) f_1 = (W_1 x+ b_1) ,第二层的输出就是 f 2 = f ( W 2 f ( W 1 x + b 1 ) + b 2 ) f_2 = f(W_2f(W_1x + b_1)+b_2) ,第三层的输出就是 f 3 = f ( W 3 f ( W 2 f ( W 1 x + b 1 ) + b 2 ) + b 3 ) f_3 = f(W_3f(W_2f(W_1x+b_1)+b_2)+b_3) 。设最终损失为 C C ,我们来尝试从第三层开始,用BP算法推导一下损失对参数 W 1 W_1 的偏导数,看看会发生什么。

为了简洁起见,略过求导过程,最后的结果为:
C W 1 = C a 3 a 3 z 3 W 3 a 2 z 2 W 2 a 1 z 1 z 1 W 1 \frac{\partial C}{\partial W_1} = \frac{\partial C}{\partial a_3}\frac{\partial a_3}{\partial z_3}W_3\frac{\partial a_2}{\partial z_2}W_2\frac{\partial a_1}{\partial z_1}\frac{\partial z_1}{\partial W_1}

我们常常说原始神经网络的梯度消失问题,这里的 a 3 z 3 \frac{\partial a_3}{\partial z_3} a 2 z 2 \frac{\partial a_2}{\partial z_2} 就是梯度消失的“罪魁祸首”。

例如sigmoid的函数,它的导数的取值范围是(0, 0.25],也就是说对于导数中的每一个元素,我们都有 0 < a 3 z 3 0.25 0 < \frac{\partial a_3}{\partial z_3} \le 0.25 0 < a 2 z 2 0.25 0 <\frac{\partial a_2}{\partial z_2} \le 0.25 ,小于1的数乘在一起,必然是越乘越小的。这才仅仅是3层,如果10层的话, 根据 0.2 5 10 0.000000954 0.25^{10} \approx 0.000000954 ,第10层的误差相对第一层卷积的参数 W 1 W_1 的梯度将是一个非常小的值,这就是所谓的“梯度消失”。

ReLU函数的改进就是它使得 a 3 z 3 { 0 , 1 } \frac{\partial{a_3}}{\partial{z_3}}\in\{0,1\} a 2 z 2 { 0 , 1 } \frac{\partial{a_2}}{\partial{z_2}}\in\{0,1\} a 1 z 1 { 0 , 1 } \frac{\partial{a_1}}{\partial{z_1}}\in\{0,1\} ,这样的话只要一条路径上的导数都是1,无论神经网络是多少层,这一部分的乘积都始终为1,因此深层的梯度也可以传递到浅层中。

当然,当Relu = 0引发的 “dead neuron"是另外一个问题,事实上,使用Relu激活函数引发的"dead neuron” 统计平均占据总神经元数量的 40%,为此而引入Relu函数的各种变形,如Leaky Relu等;

Note:

上面只讨论了 a 3 z 3 \frac{\partial a_3}{\partial z_3} 对梯度消失/爆炸的影响,而梯度下降过程中另外一项 C a 3 \frac{\partial C}{\partial a_3} 则是由损失函数决定,由此,即 a i z i \frac{\partial a_i}{\partial z_i} 由激活函数决定,而 L a i \frac{\partial L}{\partial a_i} 则是由损失函数决定。

对于loss function梯度下降过程更新参数的过程,损失函数、激活函数及权重 W i W_i 的组合构成了总的梯度下降过程(对CNN,其各channel的 W i W_i 不同,而RNN则共享同一 W i W_i ,这个也是为什么RNN更容易出现梯度消失/爆炸的原因,下文再详细叙述),三者均是造成梯度下降\爆炸的原因(但往往激活函数对梯度消失爆炸的影响更大),这也是为什么将sigmoid函数换成了Relu函数后依旧存在梯度消失\爆炸问题,因为换成了Relu函数只是解决了梯度消失\爆炸的一个方面,此外 W i W_i 的连乘也是一大重要因素(这也是引入batch norm的原因:batchnorm就是通过对每一层的输出规范为均值和方差一致的方法,消除了 W i W_i 带来的放大缩小的影响,进而解决梯度消失和爆炸的问题,或者可以理解为BN将输出从饱和区拉倒了非饱和区!详细可参考详解机器学习中的梯度消失、爆炸原因及其解决方法)

扫描二维码关注公众号,回复: 3760577 查看本文章

从上述公式推导中不难看出,其中 W i W_i 的连乘项是不可避免的,能控制的只有 C a i \frac{\partial C}{\partial a_i} 以及 a i z i \frac{\partial a_i}{\partial z_i} ,前者由损失函数决定,后者由激活函数决定。

结合参数更新公式:
w i = w i α L w i w_i = w_i - \alpha \frac{\partial L}{\partial w_i}

b i = b i α L b i b_i = b_i - \alpha \frac{\partial L}{\partial b_i}

我们希望的理想情况是损失函数在误差大 y f ( X ) y - f(X) 大的地方具有较大梯度,在误差小的地方具有较小梯度。

2. 均方差损失函数 + Sigmoid激活函数的问题

在讲反向传播算法时,我们用均方差损失函数和Sigmoid激活函数做了实例,首先我们就来看看均方差+Sigmoid的组合有什么问题。

对于均方差损失函数,公式如下:
L = 1 2 n x y ( x ) a L ( x ) 2 L = \frac{1}{2n} \sum_x ||y(x) - a^L(x)||^2
其中,C表示代价,x表示样本,y表示实际值,a表示输出值,n表示样本的总数。为简单起见,同样一个样本为例进行说明,此时二次代价函数为:
L = ( y a ) 2 2 L = \frac{(y-a)^2}{2}

目前训练ANN最有效的算法是反向传播算法。简而言之,训练ANN就是通过反向传播代价,以减少代价为导向,调整参数。参数主要有:神经元之间的连接权重w,以及每个神经元本身的偏置b。调参的方式是采用梯度下降算法(Gradient descent),沿着梯度方向调整参数大小。w和b的梯度推导如下:

C w = ( a y ) σ ( z ) x \frac{\partial C}{\partial w} = (a - y) \sigma \prime(z) x

C b = ( a y ) σ ( z ) \frac{\partial C}{\partial b} = (a - y) \sigma \prime(z)

其中, z z 表示神经元的输入, σ \sigma 表示激活函数。从以上公式可以看出,w和b的梯度跟激活函数的梯度成正比,激活函数的梯度越大,w和b的大小调整得越快,训练收敛得就越快。

而神经网络若采用的激活函数为Sigmoid函数,Sigmoid激活函数的表达式为:
σ ( z ) = 1 1 + e z \sigma(z) = \frac{1}{1+e^{-z}}

Sigmoid 的函数图像如下:

从图上可以看出,对于Sigmoid,当 z z 的取值越来越大后,函数曲线变得越来越平缓,意味着此时的导数 σ ( z ) \sigma\prime(z) 也越来越小。同样的,当 z z 的取值越来越小时,也有这个问题。仅仅在 z z 取值为0附近时,导数 σ ( z ) \sigma\prime(z) 的取值较大。

即sigmoid只有在0附近取值时具有较大梯度,当 z z 取值较大或者较小时候, σ ( z ) \sigma\prime(z) (对应上文的 a z \frac{\partial a}{\partial z} )值都很小,

上文中得到,运用梯度下降反向传播进行参数更新时,每一层向前递推都要乘以 σ ( z ) \sigma\prime(z) ,得到梯度变化值。Sigmoid的这个曲线意味着在大多数时候,我们的梯度变化值很小,导致我们的 W , b W, b 更新到极值的速度较慢,也就是我们的算法收敛速度较慢。

2.1 实例论述

以一个神经元的二类分类训练为例,进行两次实验(ANN常用的激活函数为sigmoid函数,该实验也采用该函数):输入一个相同的样本数据x=1.0(该样本对应的实际分类y=0);两次实验各自随机初始化参数,从而在各自的第一次前向传播后得到不同的输出值,形成不同的代价(误差):

实验1:第一次输出值为0.82

实验2:第一次输出值为0.98

在实验1中,随机初始化参数,使得第一次输出值为0.82(该样本对应的实际值为0);经过300次迭代训练后,输出值由0.82降到0.09,逼近实际值。而在实验2中,第一次输出值为0.98,同样经过300迭代训练,输出值只降到了0.20。

从两次实验的代价曲线中可以看出:实验1的代价随着训练次数增加而快速降低,但实验2的代价在一开始下降得非常缓慢;直观上看,初始的误差越大,收敛得越缓慢(实际原因是当初试误差大的时候,梯度更新幅度主要由激活函数导数项决定,虽然初试误差大,但由于激活函数导数项相差很大,造成直观上看初试误差大,反倒收敛的慢的表面现象)。

其实,误差大导致训练缓慢的原因在于使用了二次代价函数。

主要是由于当使用二次代价损失函数时候,进行后向传播过程中引入了激活函数导数项,证明如下:
二次代价函数的公式为:

L = 1 2 n x y ( x ) a L ( x ) 2 L = \frac{1}{2n} \sum_x ||y(x) - a^L(x)||^2
其中,C表示代价,x表示样本,y表示实际值,a表示输出值,n表示样本的总数。为简单起见,同样一个样本为例进行说明,此时二次代价函数为:
L = ( y a ) 2 2 L = \frac{(y-a)^2}{2}
目前训练ANN最有效的算法是反向传播算法。简而言之,训练ANN就是通过反向传播代价,以减少代价为导向,调整参数。参数主要有:神经元之间的连接权重w,以及每个神经元本身的偏置b。调参的方式是采用梯度下降算法(Gradient descent),沿着梯度方向调整参数大小。w和b的梯度推导如下:

C w = ( a y ) σ ( z ) x \frac{\partial C}{\partial w} = (a - y) \sigma \prime(z) x

C b = ( a y ) σ ( z ) \frac{\partial C}{\partial b} = (a - y) \sigma \prime(z)

其中,z表示神经元的输入, σ \sigma 表示激活函数。从以上公式可以看出,w 和 b 的梯度跟激活函数的梯度成正比,激活函数的梯度越大,w 和 b 的大小调整得越快,训练收敛得就越快。

而当神经网络常用的激活函数为sigmoid函数,该函数的曲线如下所示:

如图所示,实验2的初始输出值(0.98)对应的梯度明显小于实验1的输出值(0.82),因此实验2的参数梯度下降得比实验1慢。这就是初始的代价(误差)越大,导致训练越慢的原因。

本质是二次损失函数引入激活函数梯度项,而sigmoid激活函数梯度自带“消失”属性,即 s i g m a ( z ) 0.25 sigma \prime (z) \le 0.25 ,当神经网络层数比较深时,其多次乘积造成梯度回传到前层的时候更新量近似为0,导致前层神经网络难以得到训练

与我们的期望不符,即:不能像人一样,错误越大,改正的幅度越大,从而学习得越快。

事实上,sigmoid函数作为激活函数的实际应用并不多,况且,能用sigmoid的地方均可用tanh函数替代,况且tanh函数还自带聚合效果(关于原点对称),目前可能用到地方也就是二分类,作为输出函数,即将0,1分类换算成概率输出。

3. 使用交叉熵损失函数+Sigmoid激活函数改进DNN算法收敛速度

上一节我们讲到Sigmoid的函数特性导致反向传播算法收敛速度慢的问题,那么如何改进呢?换掉Sigmoid?这当然是一种选择。另一种常见的选择是用交叉熵损失函数来代替均方差损失函数。

我们来看看每个样本的交叉熵损失函数的形式:
C = 1 n x [ y l n a + ( 1 y ) l n ( 1 a ) ] C = -\frac{1}{n}\sum_x[y lna + (1-y)ln(1-a)]
其中,x表示样本,n表示样本的总数。那么,重新计算参数w的梯度:
C w j = 1 n x ( y σ ( z ) ( 1 y ) 1 σ ( z ) ) σ w j = 1 n x ( y σ ( z ) ( 1 y ) 1 σ ( z ) ) σ ( z ) x j = 1 n x σ ( z ) x j σ ( z ) ( 1 σ ( z ) ) ( σ ( z ) y ) = 1 n x x j ( σ ( z ) y ) \frac{\partial C}{\partial w_j} = -\frac{1}{n} \sum_x (\frac{y}{\sigma(z)} - \frac{(1-y)}{1 - \sigma(z)})\frac{\partial \sigma}{\partial w_j} \\ = -\frac{1}{n} \sum_x (\frac{y}{\sigma(z)} - \frac{(1-y)}{1 - \sigma(z)})\sigma \prime (z) x_j \\ = \frac{1}{n} \sum_x \frac{\sigma \prime (z) x_j}{\sigma(z)(1- \sigma(z))}(\sigma(z) - y) \\ = \frac{1}{n}\sum_{x} x_j (\sigma(z) - y)
其中(具体证明见附录):
σ ( z ) = σ ( z ) ( 1 σ ( z ) ) \sigma \prime (z) = \sigma(z)(1- \sigma(z))

因此,w的梯度公式中原来的 σ ( z ) \sigma \prime (z) 被消掉了;另外,该梯度公式中的 σ ( z ) y \sigma(z) - y 表示输出值与实际值之间的误差。所以,当误差越大,梯度就越大,参数w调整得越快,训练速度也就越快。同理可得,b的梯度为:
C b = 1 n x ( σ ( z ) y ) \frac{\partial C}{\partial b} = \frac{1}{n} \sum_x (\sigma (z) - y)
实际情况证明,交叉熵代价函数带来的训练效果往往比二次代价函数要好。

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模型中,我们可以将输出层第 i i 个神经元的激活函数定义为如下形式:

a i L = e z i L j = 1 n L e z J L a_i^L = \frac{e^{z_i^L}}{\sum_{j=1}^{n_L}e^{z_J^L}}
其中, n L n_L 是输出层第 L L 层的神经元个数,或者说我们的分类问题的类别数。

很容易看出,所有的 a i L a_i^L 都是在(0,1) 之间的数字,而 j = 1 n L e z J L \sum_{j=1}^{n_L}e^{z_J^L} 作为归一化因子保证了所有的 a i L a_i^L 之和为1。

这个方法很简洁漂亮,仅仅只需要将输出层的激活函数从Sigmoid之类的函数转变为上式的激活函数即可。上式这个激活函数就是我们的softmax激活函数。它在分类问题中有广泛的应用。将DNN用于分类问题,在输出层用softmax激活函数也是最常见的了。

下面这个例子清晰的描述了softmax激活函数在前向传播算法时的使用。假设我们的输出层为三个神经元,而未激活的输出为3,1和-3,我们求出各自的指数表达式为:20,2.7和0.05,我们的归一化因子即为22.75,这样我们就求出了三个类别的概率输出分布为0.88,0.12和0。

从上面可以看出,将softmax用于前向传播算法是也很简单的。那么在反向传播算法时还简单吗?反向传播的梯度好计算吗?答案是Yes!

对于用于分类的softmax激活函数,对应的损失函数一般都是用对数似然函数,即:
J ( W , b , a L , y ) = k y k l n a k L J(W,b,a^L,y) = - \sum\limits_ky_klna_k^L

其中 y k y_k 的取值为0或者1,如果某一训练样本的输出为第 i i 类。则 y i = 1 y_i = 1 , 其余的 j i j\ne i 都有 y j = 0 y_j=0 。由于每个样本只属于一个类别,所以这个对数似然函数可以简化为:

J ( W , b , a L , y ) = l n a i L J(W,b,a^L,y) = -ln a_i^L

其中 i i 即为训练样本真实的类别序号。

可见损失函数只和真实类别对应的输出有关,这样假设真实类别是第 i i 类,则其他不属于第 i i 类序号对应的神经元的梯度导数直接为0。对于真实类别第 i i 类,他对应的第 j j w w 链接 w i j L w_{ij}^L 对应的梯度计算为:

J ( W , b , a L , y ) w i j L = J ( W , b , a L , y ) a i L a i L z i L z i L w i j L = 1 a i L ( e z i L ) j = 1 n L e z j L e z i L e z i L ( j = 1 n L e z j L ) 2 a j L 1 = 1 a i L ( e z i L j = 1 n L e z j L e z i L j = 1 n L e z j L e z i L j = 1 n L e z j L ) a j L 1 = 1 a i L a i L ( 1 a i L ) a j L 1 = ( a i L 1 ) a j L 1 \frac{\partial J(W,b,a^L,y)}{\partial w_{ij}^L} = \frac{\partial J(W,b,a^L,y)}{\partial a_i^L}\frac{\partial a_i^L}{\partial z_i^L}\frac{\partial z_i^L}{\partial w_{ij}^L} \\ = -\frac{1}{a_i^L}\frac{(e^{z_i^L})\sum\limits_{j=1}^{n_L}e^{z_j^L}-e^{z_i^L}e^{z_i^L}}{(\sum\limits_{j=1}^{n_L}e^{z_j^L)^2}} a_j^{L-1} \\ = -\frac{1}{a_i^L} (\frac{e^{z_i^L}}{\sum\limits_{j=1}^{n_L}e^{z_j^L}}-\frac{e^{z_i^L}}{\sum\limits_{j=1}^{n_L}e^{z_j^L}}\frac{e^{z_i^L}}{\sum\limits_{j=1}^{n_L}e^{z_j^L}}) a_j^{L-1} \\ = -\frac{1}{a_i^L} a_i^L(1- a_i^L) a_j^{L-1} \\ = (a_i^L -1) a_j^{L-1}

同样的可以得到 b i L b_i^L 的梯度表达式为:
J ( W , b , a L , y ) b i L = a i L 1 \frac{\partial J(W,b,a^L,y)}{\partial b_i^L} = a_i^L -1
可见,梯度计算也很简洁,也没有第一节说的训练速度慢的问题。举个例子,假如我们对于第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的变种,表达式为:
    
t a n h ( z ) = e z e z e z + e z tanh(z) = \frac{e^z-e^{-z}}{e^z+e^{-z}}

tanh激活函数和sigmoid激活函数的关系为:

t a n h ( z ) = 2 s i g m o i d ( 2 z ) 1 tanh(z) = 2sigmoid(2z)-1

tanh和sigmoid对比主要的特点是它的输出落在了[-1,1],这样输出可以进行标准化。同时tanh的曲线在较大时变得平坦的幅度没有sigmoid那么大,这样求梯度变化值有一些优势。当然,要说tanh一定比sigmoid好倒不一定,还是要具体问题具体分析。

2) softplus:这个其实就是sigmoid函数的原函数,表达式为:

s o f t p l u s ( z ) = l o g ( 1 + e z ) softplus(z) = log(1+e^z)

它的导数就是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] 交叉熵代价函数(作用及公式推导)

猜你喜欢

转载自blog.csdn.net/Dby_freedom/article/details/83342863