由 Softmax 想到的(三)—— 从 Sigmoid 到 ReLU,一些常见的激活函数

承接上文,提到 Logistic 模型,就会想到那个典型的 Sigmoid 函数。
其实 Sigmoid 本来就是“ S 形”的意思,不过似乎大家一说到 Sigmoid 函数就想起 Logistic 模型的那个函数。
该函数一般被记做 $\sigma(x)$ :

这个函数有一些很好的性质,比如:

  • 它是连续并且光滑的,因此是可导的。这意味它在每一个点都可以求梯度。
  • 它的定义域是 $[-\infty, +\infty]$ 。
  • 它的值域是 $(0, 1)$ ,虽然取不到两边的端点,但是可以在无穷远处无限趋近。把实数集映射到 $(0, 1)$ ,刚好满足概率的定义。

“完美”的函数。

但在被引入神经网络中担负起激活函数的职责后,似乎出了一些问题。

为什么需要激活函数?

非常遗憾的是并不是所有的优化问题都是凸优化问题,乃至于是否是线性问题就更谈不上了。
相反,人类所遇到的大部分优化问题都是非线性、非凸的。
于是人们模拟神经元的样子发明了神经网络(neural nets)。
而在神经网络的各个隐藏层间担任粘合作用的就是激活函数(activation function)。

究竟激活函数要设计成什么样子呢?

  • 可微性。因为我们想要求导、想要梯度。特别是在向后传播神经网络中一定是要可导的。
  • 单调性。激活函数如果是单调的,就能保证单层网络是凸的。凸的很好,有全局最值。
  • 输出值范围有限。基于梯度的优化方法会更加稳定。当激活函数的输出是无限的时候,模型的训练会更加高效,但是一般需要小心翼翼地设定合适的更小的学习率(learning rate)。

如果不考虑输出值范围。满足这些条件的一个平凡例子是 $f(x) = x$ ,但是这样不能让两层网络逼近非线性函数。于是人们想到了 Sigmoid 函数。

Sigmoid 函数的问题

最近几年,使用 Sigmoid 函数的人越来越少了,是因为它有一个致命的缺点,即当输入非常大或者非常小的时候(也称达到饱和,saturation),这些神经元的梯度是接近 0 的。所以这需要尤其注意参数的初始值设定,因为如果初始值很大,大部分神经元可能都会处在饱和的状态,从而“杀死”梯度(kill gradient)。这会导致神经网络变得很难学习。
事实上,由于:

我们可以了解到,Sigmoid 函数的梯度的最大值也才是 $1/4$ 。如果放在向后传导网络中,运用链式法则多乘几层隐藏层,那么得到的偏导数大概要趋近于 0 了。

另外一个小问题是,Sigmoid 函数并不是 0 均值的,所以正输入所计算出的的梯度也一定是正的(就是 1)。
当然,如果使用批训练(batch),能缓解一下这个问题。比 kill gradient 要好对付一点。

不很科学的说,我们似乎也不需要在神经网络的每一层都产生一个类似于概率的东西。
神经网络的每一层应该起到为下一层提供新的抽象特征(feature)的作用,特别是这些特征如果能有一定的稀疏性就好了。
(这里稍微啰嗦一句,神经网络本身就是一个交织在一起的结构,再加入 Dropout ,其实是采用了随机森林(random forest)的 Bagging 思想的优点。)

其它的激活函数呢?

  • tanh

    这个函数解决了 0 均值的问题。不过看图像就知道了,它是 Sigmoid 函数的变体。

  • ReLU

    数学表达式:$f(x) = \max(0, x)$ 。

    近几年,ReLU作为激活函数越来越火,因为:

    1. 它是线性的,而且是不饱和的。
    2. 运算简单,计算一个阈值即可。
    3. 梯度形式简单,0 或者 1。原点处的话定义一个次梯度就好。
    4. 虽然训练时很脆弱,非常大的梯度流过一个 ReLU 单元后很轻易就会“die”掉。但是反过来想,这个问题只要设定一个合适的比较小的学习率就可以让它发生的不太频繁。同时它还可以为每一层的特征提供一定的稀疏性(负输入将得到 0)。
  • Leaky-ReLU、P-ReLU、R-ReLU、Maxout

    请自行 wiki。这些函数其实都是 ReLU 的变体,看名字就知道。
    它们被设计出来解决“dying ReLU”的问题。但具体效果却未有定论。
    总之,都是一些实验表明它们表现的很好,一些实验又不是这样。
    大概是天下没有免费的午餐(No free lunch theoren)吧。

    毕竟神经网络就是炼丹。大家都懂的。

    这里要提一句,Maxout 函数的拟合能力还是很强的,两个 Maxout 节点就可以拟合任意的凸函数了(相减),前提是“隐”隐藏层的节点个数可以任意多。所以 Maxout 函数是又有 ReLU 的优点,又没有 ReLU 的缺点。如果你没有见过别人用这个大概是因为它唯一的缺点:它会把参数个数翻倍。(所以一般在使用的时候,都和 Dropout 结合。)

还有很多的激活函数可供选择就不一一赘述了。

怎么选择激活函数呢?

首先说这种问题不可能有标答的,我的实战经验也不是很多,算是说一点个人看法。

如果使用 ReLU ,那么一定要小心设置学习率。
如果“dying ReLU”的问题不好解决,试一试 Leaky ReLU、PReLU 或者 Maxout。

另外,个人认为:最好不要用 Sigmoid,但可以试试 tanh。

还有,很少有把各种激活函数串起来放在同一个网络中的,那其实并没有多大意义。