激活函数的理解和实现-最新整理

回顾上一篇 神经网络的数学基础:张量运算

引言

学习神经网络的时候我们总是听到激活函数这个词,而且很多资料都会提到常用的激活函数,比如Sigmoid函数、Tanh函数、ReLU函数。我们就来详细了解下激活函数方方面面的知识。本文的内容包括几个部分:

  • 什么是激活函数?
  • 为什么需要激活函数?激活函数的作用是什么?
  • 常用激活函数有哪些,都有什么优特点?
  • 如何选择适合项目的激活函数?
  • 激活函数的Python实现
  • 简单三层神经网络- 前馈输出

如果你对以上几个问题不是很清楚,下面的内容对你是有价值的。

什么是激活函数?

在上一篇末尾我们讲到了神经网络的内积,但那还不是那一层的最终输出,最终输出什么?输出多少? 需要有一个函数来确定,这个函数将输入信号的总和(内积)转换为输出信号,这种函数一般称为激活函数(activation function)。如“激活”一词所示,激活函数的作用在于决定如何来激活输入信号的总和。下图中h()函数作用到a上,从而得到神经元到输出y,h()就是激活函数或者叫做激励函数。
图5-1

为什么需要激活函数?激活函数的作用是什么?

在谈到激活函数时,也经常会看到“非线性函数”和“线性函数”等术语。函数本来是输入某个值后会返回一个值的转换器。向这个转换器输入某个值后,输出值是输入值的常数倍的函数称为线性函数(用数学式表示为h(x) = cx。c 为常数)。因此,线性函数是一条笔直的直线。而非线性函数,顾名思义,指的是不像线性函数那样呈现出一条直线的函数。
神经网络的激活函数必须使用非线性函数。换句话说,激活函数不能使用线性函数。为什么不能使用线性函数呢?因为使用线性函数的话,加深神经网络的层数就没有意义了。线性函数的问题在于,不管如何加深层数,总是存在与之等效的“无隐藏层的神经网络”。
如果不考虑激活函数,考虑一个三层神经网络的输出公式:

a 1 = x w 1 + b 1      a 1    a 2 = a 1 w 2 + b 2      a 2    a 3 = a 2 w 3 + b 3 a^{1} = xw^{1} + b^{1} ~~~~a^{1}传递到第二层 ~~\\ a^{2} = a^{1}w^{2} + b^{2} ~~~~a^{2}传递到第三层 ~~\\ a^{3} = a^{2}w^{3} + b^{3}
注意,1,2,3代表层数
可以看到这种神经网络层之间信号传递全是我们中学学过的平面解析几何里线性(y = kx + b)形式的传递方式,是没有意义的,无论叠加多少层。
正因为上面的原因,神经网络引入非线性函数的激活函数,才可以充分发挥层叠加所带来的优势,这样几乎可以逼近任意函数。

常用激活函数有哪些,都有什么优特点?

sigmoid函数是神经网络中最常使用的激活函数,在深度学习领域,自从提出ReLU函数后,陆续提出了一些新的激活函数或ReLU的衍生函数。主要激活函数如下所示:

  • sigmoid & tanh
  • ReLU以及其衍生函数
  • MaxOut
Sigmoid & tanh

Sigmoid 是常用的非线性激活函数,它的数学形式如下:

          f ( x ) = 1 1 + e x p ( x ) ~~~~~~~~~f(x)=\frac {1}{1+exp^{(-x)}}

exp(−x)表示 e ( x ) e^{(−x)} 的意思。e是纳皮尔常数2.7182. sigmoid函数看上去有些复杂,但它也仅仅是个函数而已。而函数就是给定某个输入后,会返回某个输出的转换器。比如,向sigmoid函数输入1.0或2.0后,就会有某个值被输出,类似h(1.0) = 0.731 . . .、h(2.0) = 0.880 . . .。神经网络中用sigmoid 函数作为激活函数,进行信号的转换,转换后的信号被传送给下一个神经元。

tanh函数是sigmoid函数的一种变体,以0点为中心。取值范围为 [-1,1] ,而不是sigmoid函数的 [0,1] 。公式:
t a n h ( x ) = e x e x e x + e x tanh(x)=\frac {e^x - e^{-x}}{e^x + e^{-x}}
tanh 是对 sigmoid 的平移和收缩: t a n h ( x ) = 2 σ ( 2 x ) 1 tanh(x)=2⋅σ(2x)−1

sigmoid优缺点:
优点: 它能够把输入的连续实值变换为0和1之间的输出,特别的,如果是非常大的负数,那么输出就是0;如果是非常大的正数,输出就是1.
缺点: sigmoid函数曾经被使用的很多,它也有其固有缺点。

  • 饱和的神经元会"杀死"梯度,指离中心点较远的x处的导数接近于0,停止反向传播的学习过程.
  • sigmoid的输出不是以0为中心,而是0.5,这样在求权重w的梯度时,梯度总是正或负的.
  • 指数计算耗时

tanh优缺点:
优点:

  • 梯度消失问题程度 tanh(x)的梯度消失问题比sigmoid要轻
  • tanh解决了Sigmoid函数的不是zero-centered输出问题
    缺点: 梯度消失(gradient vanishing)的问题和幂运算的问题仍然存在。

也正是因为有缺点,才有其它激活函数的产生和发展。

ReLU及其衍生函数

Relu公式:
f ( x ) = { x , x > = 0 0 , x < 0 f(x) = \left\{\begin{matrix}x, & x>=0 \\ 0, &x<0 \end{matrix}\right.
ReLU函数其实就是一个取最大值函数,注意这并不是全区间可导的,但是我们可以取sub-gradient,如上图所示。ReLU虽然简单,但却是近几年的重要成果,有以下几大优点:

  • 解决了gradient vanishing问题 (在正区间)
  • 计算速度非常快,只需要判断输入是否大于0
  • 收敛速度远快于sigmoid和tanh

ReLU也有几个需要特别注意的问题:

  • ReLU的输出不是zero-centered
  • 坏死: ReLU强制的稀疏处理会减少模型的有效容量(即特征屏蔽太多,导致模型无法学习到有效特征)。由于ReLU在x <0时梯度为0,这样就导致负的梯度在这个ReLU被置零,而且这个神经元有可能再也不会被任何数据激活,称为神经元“坏死”。
  • 无负值: ReLU和sigmoid的一个相同点是结果是正值,没有负值.

尽管如此,ReLU目前仍是最常用的activation function.
下面看ReLU的变种。

ReLU变种

为了在输入小于0时能够输出负值,人们提出了Leaky ReLU,PReLU和RReLU等激活函数。这些函数的负数端斜率和整数端不同。
当x<0时, f ( x ) = a x f(x)=ax ,其中a非常小,可以避免在x<0时,不能够学习的情况,公式为: f ( x ) = m a x ( a x , x ) f(x) = max(ax,x) ,称为Parametric Rectifier(PReLU),将 α作为可学习的参数。
当 α 从高斯分布中随机产生时称为Random Rectifier(RReLU)PReLU中, a a 是在根据误差反向传播算法进行训练时确定的,从一个均匀分布的随机抽取的数值。测试时,可以取均匀分布的中值。
a a 通常是训练前确定的, 比如α=0.01,是Leaky ReLU

这几个激活函数性能测试差别不大,RReLU最优。Relu函数以及衍生函数的图像:
图5-3
优点:

  • 不会过拟合(saturate)
  • 计算简单有效
  • 比sigmoid/tanh收敛快
指数线性单元ELU

ELU公式:
f ( x ) = { a ( e x 1 ) , x &lt; = 0 x , x &gt; 0 f(x) = \left\{\begin{matrix}a(e^x-1), &amp; x&lt;=0 \\ x, &amp;x&gt;0 \end{matrix}\right.
exponential linear unit, 该激活函数由Djork等人提出,被证实有较高的噪声鲁棒性,同时能够使得使得神经元的平均激活均值趋近为 0,同时对噪声更具有鲁棒性。由于需要计算指数,计算量较大。

SELU

SELU是给ELU乘上系数 λ, 即 S E L U ( x ) = λ E L U ( x ) SELU(x)= λ⋅ELU(x) , SELU公式:

f ( x ) = λ { a ( e x 1 ) , x &lt; = 0 x , x &gt; 0 f(x) = λ \left\{\begin{matrix}a(e^x-1), &amp; x&lt;=0 \\ x, &amp;x&gt;0 \end{matrix}\right.
论文: 自归一化神经网络(Self-Normalizing Neural Networks)中提出只需要把激活函数换成SELU就能使得输入在经过一定层数之后变成固定的分布. 参考对这篇论文的讨论.

MaxOut函数

maxout激活函数由Google的花书作者Ian Goodfellow等人提出的,可参考Maxout Networks
Maxout可以看做是在深度学习网络中加入一层激活函数层,包含一个参数k.这一层相比ReLU,sigmoid等,其特殊之处在于增加了k个神经元,然后输出激活值最大的值.
我们常见的隐含层节点输出:
h i ( x ) = s i g m o i d ( x T W . . . i + b i ) h_i (x)= sigmoid(x^TW_{...i} + bi)
在Maxout网络中,其隐含层节点的输出表达式为:
单元 h k h_k 的值可表示为:
h i ( x ) = max j ( 1 , k ) z i j h_i(x)=\max_{j\in(1,k)}z_{ij}
其中:
z i j = i = 0 N w i j x i + b i j z_{ij}=\sum_{i=0}^Nw_{ij}x_i + b_{ij}
公式中 z i j z_{ij} 是对输入 x i x_i 对应的权重 w i j w_{ij} 的乘积之和加上偏置的结果。这里是k个单元输出值中取最大值最为单元的输出,所以maxout可以学到单元之间的关系。下图可以清楚到看到变化,注意图中画圈到部分,这是一个正常到神经单元(2个输入1个输出),变成箭头所指的结构,中间加了一层k个神经元,同时多出来了一个W和b,计算处理取最大值得到 h i ( x ) h_i(x)
在这里插入图片描述
此外,maxout激活函数还可以理解为一种分段线性函数来近似任意凸函数。如图所示,在卷积层使用maxout作为激活函数时,从多个特征值的相同位置中选取最大值最为最后的特征图。池化操作是从相同的特征图的局部区域中选取最大值,可以看作是特征图的缩小处理,而maxout激活函数是从不同特征图中选取最大值作为最后的特征图,所以可以看作是减少了特征图个数。
图5-2
优点

  • Maxout的拟合能力非常强,可以拟合任意的凸函数。
  • Maxout具有ReLU的所有优点,线性、不饱和性。
  • 没有ReLU的一些缺点。如:神经元的死亡。

缺点:从上面的激活函数公式中可以看出,每个神经元中有两组(w,b)参数,那么参数量就增加了一倍,这就导致了整体参数的数量激增。

如何选择适合项目的激活函数?

到目前位置,似乎还没有这方面的理论推导告诉你,在你的项目中用哪个激活函数最佳,所以你要有深刻的数学洞察力,如果没有这种能力,就凭经验,在实践中不断调整优化。不过有人建议如下顺序,可以参考:

  1. 首先尝试ReLU,速度快,但要注意训练的状态.
  2. 如果ReLU效果欠佳,尝试Leaky ReLU或Maxout等变种。
  3. 尝试tanh正切函数(以零点为中心,零点处梯度为1)
  4. sigmoid/tanh在RNN(LSTM、注意力机制等)结构中有所应用,作为门控或者概率值.
  5. 在浅层神经网络中,如不超过4层的,可选择使用多种激励函数,没有太大的影响。
激活函数的Python实现

由于贴出代码使文章过长,要看代码从这里下载
通过一个Activation类实现,各种激活函数,并有绘制激活函数的方法,你可以修改使用。
输出的图形:
图5-9

简单三层神经网络-前馈输出

本节是为初学者准备的,目的是通过简单神经网络为例子,讲述前馈网络输出的计算,本节主要讲述激活函数在其中的应用,中高级经验者略过不看。

上一篇我们到例子计算了神经网络层到内积,现在继续完善它,今天要完成神经网络层在加入激活函数后的输出,下图是从输入到第1层输入信号:
图5-6
上一篇我们计算了第1层A1(内积)的值,现在考虑激活函数h(),来计算Z1的值

import numpy as np
X = np.array([1.0, 0.5])
W1 = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])
B1 = np.array([0.1, 0.2, 0.3])
print(W1.shape) # (2, 3)
print(X.shape) # (2,)
print(B1.shape) # (3,)
A1 = np.dot(X, W1) + B1
Z1 = sigmoid(A1)

我们再看第1层传到第2层,信号的传送参见如下图:
图5-7 从第1层到第2层第信号传输

W2 = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
B2 = np.array([0.1, 0.2])
print(Z1.shape) # (3,)
print(W2.shape) # (3, 2)
print(B2.shape) # (2,)
A2 = np.dot(Z1, W2) + B2
Z2 = sigmoid(A2)

再来看,第2层到输出层,参见如下图:
图5-8 从第2 层到输出层的信号传递

def identity_function(x):
	return x
W3 = np.array([[0.1, 0.3], [0.2, 0.4]])
B3 = np.array([0.1, 0.2])
A3 = np.dot(Z2, W3) + B3
Y = identity_function(A3) # 或者Y = A3

代码实现,可以从这里下载,这里节省篇幅,不贴了,见谅。
代码实现类一个Network类,结合上面的Activation类,可以演示同一个神经网络在不同激活函数作用下的输出,从而对激活函数对作用有深刻认识。
继续阅读 深刻理解机器学习的: 目标函数,损失函数和代价函数

发布了36 篇原创文章 · 获赞 42 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_28710515/article/details/89289022