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

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

1. 梯度消失与梯度爆炸

我们设第一层卷积的参数为 (W1,b1)(W_1, b_1)第二层卷积的参数是,(W2,b2)(W_2, b_2)依次类推。又设**函数为 ff,每一层卷积在经过**函数前的值为 ,经aia_i过**函数后的值为 fif_i

按照上面的表示,在CNN中,输入 xx ,第一层的输出就是f1=(W1x+b1)f_1 = (W_1 x+ b_1) ,第二层的输出就是f2=f(W2f(W1x+b1)+b2)f_2 = f(W_2f(W_1x + b_1)+b_2) ,第三层的输出就是f3=f(W3f(W2f(W1x+b1)+b2)+b3)f_3 = f(W_3f(W_2f(W_1x+b_1)+b_2)+b_3)。设最终损失为CC ,我们来尝试从第三层开始,用BP算法推导一下损失对参数 W1W_1的偏导数,看看会发生什么。

为了简洁起见,略过求导过程,最后的结果为:
CW1=Ca3a3z3W3a2z2W2a1z1z1W1 \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}

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

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

ReLU函数的改进就是它使得 a3z3{0,1}\frac{\partial{a_3}}{\partial{z_3}}\in\{0,1\}a2z2{0,1}\frac{\partial{a_2}}{\partial{z_2}}\in\{0,1\}a1z1{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:

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

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

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

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

bi=biαLbi b_i = b_i - \alpha \frac{\partial L}{\partial b_i}

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

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

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

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

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

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

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

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

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

Sigmoid 的函数图像如下:

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

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

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

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

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的代价在一开始下降得非常缓慢;直观上看,初始的误差越大,收敛得越缓慢(实际原因是当初试误差大的时候,梯度更新幅度主要由**函数导数项决定,虽然初试误差大,但由于**函数导数项相差很大,造成直观上看初试误差大,反倒收敛的慢的表面现象)。

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

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

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

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

Cb=(ay)σ(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**函数梯度自带“消失”属性,即sigma(z)0.25sigma \prime (z) \le 0.25,当神经网络层数比较深时,其多次乘积造成梯度回传到前层的时候更新量近似为0,导致前层神经网络难以得到训练

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

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

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

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

我们来看看每个样本的交叉熵损失函数的形式:
C=1nx[ylna+(1y)ln(1a)] C = -\frac{1}{n}\sum_x[y lna + (1-y)ln(1-a)]
其中,x表示样本,n表示样本的总数。那么,重新计算参数w的梯度:
Cwj=1nx(yσ(z)(1y)1σ(z))σwj=1nx(yσ(z)(1y)1σ(z))σ(z)xj=1nxσ(z)xjσ(z)(1σ(z))(σ(z)y)=1nxxj(σ(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的梯度为:
Cb=1nx(σ(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模型中,我们可以将输出层第 ii 个神经元的**函数定义为如下形式:

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

很容易看出,所有的aiLa_i^L都是在(0,1) 之间的数字,而j=1nLezJL\sum_{j=1}^{n_L}e^{z_J^L}作为归一化因子保证了所有的aiLa_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,aL,y)=lnaiL J(W,b,a^L,y) = -ln a_i^L

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

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

KaTeX parse error: No such environment: align at position 8: \begin{̲a̲l̲i̲g̲n̲}̲ \frac{\partial…
同样的可以得到biLb_i^L的梯度表达式为:
J(W,b,aL,y)biL=aiL1 \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的变种,表达式为:
    
tanh(z)=ezezez+ez tanh(z) = \frac{e^z-e^{-z}}{e^z+e^{-z}}

tanh**函数和sigmoid**函数的关系为:

tanh(z)=2sigmoid(2z)1 tanh(z) = 2sigmoid(2z)-1

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

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

softplus(z)=log(1+ez) 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] 交叉熵代价函数(作用及公式推导)