2.1.2 激活函数
在神经网络中,神经元节点的激活函数定义了对神经元输出的映射,控制着神经元激活的阈值和输出信号的强度。激活函数通常有以下性质。
- 非线性:神经网络中激活函数的主要作用是提供网络的非线性建模能力。如非特别说明,激活函数一般是非线性函数。假设一个深度神经网络中仅包含线性卷积和全连接运算,那么该网络仅能表达线性映射,即便增加网络的深度也还是线性映射,难以对实际环境中非线性分布的数据进行有效建模。加入(非线性)激活函数之后,深度神经网络才具备了分层的非线性映射学习能力。因此,激活函数是深度神经网络中不可或缺的部分。
- 可微性:当优化方法是基于梯度优化时,这个性质是必需的。
- 单调性:当激活函数是单调函数时,单层网络能保证是凸函数。
- f(x)≈x:当激活函数满足这个性质时,如果参数初始化为很小的随机值,那么神经网络的训练将非常高效;如果不满足这个性质,那么就需要详细地设置初始值。
- 输出值的范围:当激活函数的输出值范围有限时,基于梯度优化的方法会更加稳定,因为特征的表示受有限权重值的影响更显著;当激活函数的输出值范围无限时,模型的训练会更加高效,不过在这种情况下,一般需要更小的学习率。
常用激活函数如表2-1[1]所示。
表2-1 常用激活函数
1. Sigmoid函数
接触过算法的读者估计对Sigmoid函数都不陌生。逻辑回归能做分类预测的关键就是它能利用Sigmoid函数将线性回归结果映射到(0,1)范围内,进而得到各类别的概率值,实现分类的目的。在ReLU(修正线性单元)出现前,大多数神经网络使用Sigmoid函数作为激活函数进行信号转换,并将转换后的信号传递给下一个神经元。
Sigmoid激活函数定义为:
式中e是纳皮尔常数,值为2.7182…。
Sigmoid函数的R实现代码非常简单,例如以下代码定义了Sigmoid函数,并绘制出S曲线,结果如图2-2所示。
> # 自定义Sigmoid函数 > sigmod <- function(x){ + return(1/(1+exp(-x))) + } > # 绘制Sigmoid曲线 > x <- seq(-6,6,length.out = 100) > plot(x,sigmod(x),type = 'l',col = 'blue',lwd = 2, + xlab = NA,ylab = NA,main = 'Sigmoid函数曲线') > grid()
图2-2 Sigmoid函数曲线
Sigmoid函数在大部分定义域内都会趋于一个饱和的定值。当x取很大的正值时,Sigmoid值会无限趋近于1;当x取很大的负值时,Sigmoid值会无限趋近于0。
Sigmoid作为激活函数主要有以下三个缺点。
- 梯度消失:当Sigmoid函数趋近0和1的时候,其函数曲线会变得非常平坦,即Sigmoid的梯度趋近于0,当神经网络使用Sigmoid激活函数进行反向传播时,输出接近0或1的神经元的梯度趋近0,所以这些神经元的权重不会更新,进而导致梯度消失。
- 不以零为中心:Sigmoid的输出是以0.5为中心,而不是以零为中心的。
- 计算成本高:与其他非线性激活函数相比,以自然常数e为底的指数函数的计算成本更昂贵。
2. Tanh函数
Tanh函数也叫双曲正切函数,它也是在引入ReLU之前经常用到的激活函数。Tanh函数的定义如下:
Sigmoid函数和Tanh函数之间存在计算上的关系,如下所示:
以下代码实现Tanh函数的定义及曲线绘制,结果如图2-3所示。
> # 自定义Tanh函数 > tanh <- function(x){ + return((exp(x)-exp(-x))/(exp(x)+exp(-x))) + } > # 绘制Tanh曲线 > x <- seq(-6,6,length.out = 100) > plot(x,tanh(x),type = 'l',col = 'blue',lwd = 2, + xlab = NA,ylab = NA,main = 'Tanh函数曲线') > grid()
图2-3 Tanh函数曲线
可以看到,Tanh函数跟Sigmoid函数的曲线很相似,都是一条S曲线。只不过Tanh函数是把输入值转换到(-1,1)范围内。Sigmoid函数曲线在|x|>4之后会非常平缓,极为贴近0或1;Tanh函数曲线在|x|>2之后会非常平缓,极为贴近-1或1。与Sigmoid函数不同,Tanh函数的输出以零为中心。Tanh输出趋于饱和时也会“杀死”梯度,出现梯度消失的问题。
3. ReLU函数
ReLU的英文全称为Rectified Linear Unit,可以翻译成整流线性单元或者修正线性单元。与传统的Sigmoid激活函数相比,ReLU能够有效缓解梯度消失问题,从而直接以监督的方式训练深度神经网络,无须依赖无监督的逐层预训练。这也是2012年深度卷积神经网络在ILSVRC竞赛中取得里程碑式突破的重要原因之一。
ReLU函数在输入大于0时直接输出该值,在输入小于等于0时输出0。其公式如下:
以下代码实现ReLU函数的定义及曲线绘制,结果如图2-4所示。
> # 自定义ReLU函数 > relu <- function(x){ + return(ifelse(x<0,0,x)) + } > # 绘制ReLU曲线 > x <- seq(-6,6,length.out = 100) > plot(x,relu(x),type = 'l',col = 'blue',lwd = 2, + xlab = NA,ylab = NA,main = 'ReLU函数曲线') > grid()
图2-4 ReLU函数曲线
可见,ReLU在x<0时硬饱和。由于x>0时ReLU的一阶导数为1,所以,ReLU能够在x>0时保持梯度不衰减,从而缓解梯度消失问题。但随着训练的推进,部分输入会落入硬饱和区,导致对应的权重无法更新。这种现象被称为神经元死亡。与Sigmoid类似,ReLU的输出均值也大于0,所以偏移现象和神经元死亡现象共同影响着网络的收敛性。
ReLU虽然简单,但却是近几年的重要成果。ReLU有以下几大优点。
- 解决了梯度消失问题(在正区间)。
- 计算速度非常快,只需要判断输入是否大于0。
- 收敛速度远快于Sigmoid和Tanh。
ReLU也存在一些缺点。
- 不以零为中心:和Sigmoid激活函数类似,ReLU函数的输出不以零为中心。
- 神经元死亡问题:某些神经元可能永远不会被激活,导致相应的参数永远不能被更新。在正向传播过程中,如果x<0,则神经元保持非激活状态,且在反向传播中“杀死”梯度。这样权重无法得到更新,网络无法学习。
4. Leaky ReLU函数
Leaky ReLU译为渗漏整流线性单元。为了解决神经元死亡问题,有人提出了将ReLU的前半段设为非0,即使用Leaky ReLU函数来解决。该函数的数学公式为:
以下代码实现Leaky ReLU函数的定义及曲线绘制,结果如图2-5所示。
> # 自定义Leaky ReLU函数 > relu <- function(x){ + return(ifelse(x<0,0.01*x,x)) + } > # 绘制Leaky ReLU曲线 > x <- seq(-6,6,length.out = 100) > plot(x,relu(x),type = 'l',col = 'blue',lwd = 2, + xlab = NA,ylab = NA,main = 'Leaky ReLU函数曲线') > grid()
图2-5 Leaky ReLU函数曲线
可见,当x<0时,Leaky ReLU得到0.01的正梯度,在一定程度上缓解了神经元死亡问题,但是其结果并不连贯。
另外一种直观的想法是基于超参数的方法解决神经元死亡问题,即Parametric ReLU(超参数整流线性单元),其中超参数α值可由方向导向学到。理论上来讲,Leaky ReLU有ReLU的所有优点,如高效计算、快速收敛等,并能缓解神经元死亡问题,但是在实际操作中,并没有完全证明Leaky ReLU总是好于ReLU。
选择一个适合的激活函数并不容易,需要考虑很多因素,通常的做法是,如果不确定哪一个激活函数效果更好,可以都试试,在验证集或者测试集上进行评价,从中选择表现更好的激活函数。
以下是几种常见的选择情况。
- 如果输出是0、1值(二分类问题),则输出层选择Sigmoid函数,其他所有单元选择ReLU函数。
- 如果在隐藏层上不确定使用哪个激活函数,那么通常会使用ReLU激活函数,但是要注意初始化和学习率(learning rate)的设置。可以将偏置项的所有元素都设置成一个小的正值,例如0.1,使得ReLU在初始时就对训练集中的大多数输入呈现激活状态。有时,也会使用Tanh激活函数,但ReLU更优,因为当ReLU的值是负值时,导数等于0。
- 如果遇到了一些死的神经元,可以使用Leaky ReLU函数。
Keras的激活函数可以通过设置单独的激活层实现,也可以在构建网络层时通过传递参数activation实现。Keras中预定义的激活函数包括Softmax、ELU、Softplus、Softsign、ReLU、Tanh、Sigmoid、hard_sigmoid及Linear。