为什么使用激活函数
如果没有激活函数,神经网络就变成了线性模型,输出是输入的线性组合,使用一层与使用多层没有区别。如下式所示,输入为x,经过线性层计算出a1,将a1输入下个线性层得到a2,展开后可以看出,最终得到的仍然是wx+b的线性组合,只是参数值不同。
图片.png
另外,线性层无法解决非线性问题,如在预测房价问题中,如果不使用激活函数,则房价可能计算成负值,这也与实际不符。理论上,加了激活函数后,模型可以逼近任意函数。
激活函数又分线性激活函数和非线性激活函数,一般使用的都是非线性激活函数,线性激活函数与线性层类似,只在输出层偶尔使用,不在此展开讨论。
何时使用激活函数
激活函数一般放置在线性变换之后,在线性变换和激活函数之间,常常插入归一化层,用于解决饱和的非线性激活函数问题(下面Sigmoid函数部分详细说明)。
如何选择激活函数
从一些当前流行的深度学习网络代码中,可以看到,当前使用的激活函数绝大部分是ReLU;在一些特殊情况下,也使用Sigmoid,比如二分类问题的最后一层使用Sigmoid将输出转换到0-1之间;又如使用注意力网络时,注意力加权需要使用0-1之间的权值时,也用到Sigmoid函数。作为一般的夹在线性层之间的普通激活函数,ReLU是默认选择。
常用激活函数
下面介绍常用激活函数的方法、原理、梯度计算、直观图示,以及使用中可能遇到的问题。
sigmoid激活函数
sigmoid是较早期的激活函数,它的输入是任意的x,输出在0-1之间,实现数据映射。
其公式如下:
其求导过程如下:
Python代码实现:
def sigmoid(x):
return 1. / (1 + np.exp(-x))
试想将值代入时,当x趋近正无穷,分母为1,计算结果为1;当x趋近负无穷,分母为无穷大,计算结果为0。当x为0时,分母为2,计算结果为0.5。
函数图示如下:
如图所示,映射过程中将值压缩到0-1之间,它对0附近的值比较敏感,其图形接近线性,而其它部分的数值在sigmoid后绝对值缩小很多。
梯度是x方向上变化引起的y方向的变化,在x值较大的情况下,x的变化对y影响很小,这就是所谓的“非线性激活函数的饱和”问题。在使用梯度下降法给网络调参时,接近0的梯度使学习的速度变得非常缓慢。这一现象在调试程序时尤为明显:大不把网络参数以及输入数据设置得比较小,且不使用归一化层的情况下,使用Sigmoid函数收敛得非常慢。
此外,sigmoid还包括幂运算,运算量也比较大,目前除了上述比较特殊的场景,已很少使用。
tanh激活函数
tanh激活函数可视为sigmoid函数的改进版本,其图示如下:
在数学上,tanh是sigmoid的平移,它把数据范围压缩到-1~1之间,均值为0。前人证明0均值的tanh激活函数效果更好。0均值同样可应用于其它场景提高模型效果。
其公式如下:
Python代码实现:
import numpy as np
np.tanh(x)
尽管tanh略优于sigmoid,但它也有sigmoid同样的缺点:计算量大及饱和问题,目前也很少使用,另外,0均值也可通过归一化层实现。因此,只做简单介绍,不再展开。
ReLU激活函数
ReLU激活函数,原理,计算以及求导都非常简单:如果x大于0,则y=x,如果小于0,则y=0,其图示如下:
其公式如下:
其导数是:
需要注意的是,ReLU在0值不可微,但一般情况下,不会遇到太多的0值,因此将0值的梯度置为0或1都可以。
Python代码实现:
def relu(x):
return np.maximum(0,x)
ReLU非常简单,运算速度非常快,收敛也快,由于它没有压缩数据,因此,也避免了由激活函数引起的梯度问题。另外,处理结果不能为负(如房价不能为负,或者避免sum累积时正负抵消)的问题时,也可以在层后添加ReLU激活函数。
ReLU衍生的激活函数
虽然ReLU相对于之前算法表现优异,但也存在问题,试想当梯度大幅变化时,由于ReLU在大于0的情况下,不做处理直接向后传递,则可能造成之前线性层参数的大幅变化,因此,很可能产生大量小于0的数据输入ReLU。若ReLU的输入大多是负数,则会导致大部分梯度无法向后传递,从而引起“Dead ReLU”问题。其现象是由于某些特殊数据引发了无法继续收敛。
为解决这一问题,出现了一些ReLU变种,来处理小于0的数据,比如ELU,Leaky RELU。
ELU公式如下:
做图如下:
Python代码如下:
def elu(x,a):
y = []
for i in x:
if i<0:
i = a * (np.exp(i)-1)
y.append(i)
return y
它的均值趋近0,没有Dead ReLU的问题,但计算量略大。更简单一点的还有Leaky ReLU,如下图所示:
它对于0以下的部分乘一个较小的a值,一般是0.01。另外,引入归一化层也可解决Dead ReLU问题,因此,推荐以ReLU作为默认的激活函数。