激活函数

为什么使用激活函数

如果没有激活函数,神经网络就变成了线性模型,输出是输入的线性组合,使用一层与使用多层没有区别。如下式所示,输入为 x,经过线性层计算出 a1,将 a1 输入下个线性层得到 a2,展开后可以看出,最终得到的仍然是 wx+b 的线性组合,只是参数值不同。

图片.png

另外,线性层无法解决非线性问题,如在预测房价问题中,如果不使用激活函数,则房价可能计算成负值,这也与实际不符。理论上,加了激活函数后,模型可以逼近任意函数。

激活函数又分线性激活函数和非线性激活函数,一般使用的都是非线性激活函数,线性激活函数与线性层类似,只在输出层偶尔使用,不在此展开讨论。

何时使用激活函数

激活函数一般放置在线性变换之后,在线性变换和激活函数之间,常常插入归一化层,用于解决饱和的非线性激活函数问题(下面 Sigmoid 函数部分详细说明)。

如何选择激活函数

从一些当前流行的深度学习网络代码中,可以看到,当前使用的激活函数绝大部分是 ReLU;在一些特殊情况下,也使用 Sigmoid,比如二分类问题的最后一层使用 Sigmoid 将输出转换到 0-1 之间;又如使用注意力网络时,注意力加权需要使用 0-1 之间的权值时,也用到 Sigmoid 函数。作为一般的夹在线性层之间的普通激活函数,ReLU 是默认选择。

常用激活函数

下面介绍常用激活函数的方法、原理、梯度计算、直观图示,以及使用中可能遇到的问题。

sigmoid 激活函数

sigmoid 是较早期的激活函数,它的输入是任意的 x,输出在 0-1 之间,实现数据映射。

其公式如下:

其求导过程如下:

Python 代码实现:

1
2
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 代码实现:

1
2
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 代码实现:

1
2
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 代码如下:

1
2
3
4
5
6
7
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 作为默认的激活函数。