神经网络中的激活函数
1.什么是激活函数?
神经网络中,在隐藏层接一个线性变换后,再接一个非线性变换(sigmoid、tanh、ReLu等),这种非线性变换叫做激活函数(Activation Function)或者传递函数。
2.为什么要使用激活函数?
激活函数一般都是非线性函数,神经网络中的激活函数它的主要作用是提供网络的非线性建模能力。如果你的神经网络模型中,使用线性激活函数或者没有使用一个激活函数,那么无论多少层的神经网络模型一直在做的只是计算线性函数,不如去掉全部隐藏层。在实例中证明,在隐藏层使用线性激活函数,输出层用sigmoid函数,这个模型的复杂度和没有隐藏层的logistic回归是一样的。
在神经网络中,隐藏层使用线性激活函数几乎没啥用处,线性函数的组合本身就是线性函数。当引入非线性激活函数才会让模型变得有意义。
3.常用的激活函数
3.1 sigmoid函数/Logistic函数
sigmoid函数:
a
=
g
(
z
)
=
1
1
+
e
−
z
a=g\left ( z \right )=\frac{1}{1+e^{-z}}
a=g(z)=1+e−z1
sigmoid函数的导数:
g
(
z
)
′
=
d
d
z
g
(
z
)
=
α
(
1
−
α
)
g({z})'=\frac{d}{dz}g\left ( z \right )=\alpha (1-\alpha )
g(z)′=dzdg(z)=α(1−α)
缺点:
1.sigmoid在定义域内处处可导,并且导数逐渐趋近于0。如果Z值很大或者很小的时候,函数的梯度(斜率)也就会非常小,在反向传播过程中,导致向底层传输的梯度也变得非常小,此时网络参数很难得到有效训练。这种现象称为梯度消失。
2.sigmoid的输出不是以0为中心。导致在反向传播过程中,要么都往正方向更新要么往负方向更新,使得收敛缓慢。
3.解析式中含有幂运算,计算求解比较耗时。
sigmoid代码实现如下:
def sigmoid(x):
"""sigmoid函数"""
return 1 / (1 + np.exp(-x))
def der_sigmoid(x):
"""sigmoid函数的导数"""
return sigmoid() * (1 - sigmoid(x))
3.2 tanh函数(双曲正切函数)
tanh函数:
a
=
g
(
z
)
=
t
a
n
h
(
z
)
=
e
z
−
e
−
z
e
z
+
e
−
z
a=g\left ( z \right )=tanh\left ( z \right )=\frac{e^{z}-e^{-z}}{e^{z}+e^{-z}}
a=g(z)=tanh(z)=ez+e−zez−e−z
tanh函数的导数:
g
(
z
)
′
=
d
d
z
g
(
z
)
=
1
−
(
t
a
n
h
(
z
)
)
2
g(z)'=\frac{d}{dz}g(z)=1-(tanh(z))^{2}
g(z)′=dzdg(z)=1−(tanh(z))2
优点:
tanh函数解决了sigmoid函数的不是zero-centered输出问题,输出均值是0。
缺点:
sigmoid函数和tanh函数两者共同的缺点:在𝑧特别大或者特别小的情况下,导数的梯度或者函数的斜率会变得特别小,最后就会接近于 0,导致降低梯度下降的速度。
tanh代码实现如下:
def tanh(x):
"""tanh函数"""
return ((np.exp(x) - np.exp(-x)) / (np.exp(x) + np.exp(-x)))
def der_tanh(x):
"""tanh函数的导数"""
return 1 - tanh(x) * tanh(x)
3.3 ReLu函数(修正线性单元函数)
ReLu函数:
a
=
m
a
x
(
0
,
z
)
a=max(0,z)
a=max(0,z)
ReLu函数的导数:
g
(
z
)
′
=
{
0
,
i
f
z
<
0
1
,
i
f
z
>
0
u
n
d
e
f
i
n
e
d
,
i
f
z
=
0
}
g(z)^{'}=\begin{Bmatrix} 0 ,& if z<0\\ 1,& if z>0\\ undefined,& if z=0 \end{Bmatrix}
g(z)′=⎩⎨⎧0,1,undefined,ifz<0ifz>0ifz=0⎭⎬⎫
优点:
ReLu函数是目前最常用的默认选择激活函数。当x>0时,不存在饱和问题,在x>0时保持梯度不衰减,缓解梯度消失的问题。并且收敛速度远快于sigmoid和tanh。
缺点:
当x<0时,ReLu硬饱和,随着训练的推进,部分输入会落入到硬饱和区,导致权重无法更新。和sigmoid类似,ReLu的输出均值大于0,出现偏移现象。
ReLu代码实现如下:
def relu(x):
"""relu函数"""
temp = np.zeros_like(x)
if_bigger_zero = (x > temp)
return x * if_bigger_zero
def der_relu(x):
"""relu函数的导数"""
temp = np.zeros_like(x)
if_bigger_equal_zero = (x >= temp)
return if_bigger_equal_zero * np.ones_like(x)
3.4 Leaky-ReLu函数(PReLu)
PReLu函数:
a
=
m
a
x
(
α
z
,
z
)
a=max(αz,z)
a=max(αz,z)
ReLu函数的导数:
g
(
z
)
′
=
{
α
,
i
f
z
<
0
1
,
i
f
z
≥
0
}
g(z)^{'}=\begin{Bmatrix} \alpha ,& if z<0\\ 1,& if z\geq 0 \end{Bmatrix}
g(z)′={α,1,ifz<0ifz≥0}
PReLU也是针对ReLU的一个改进型,在负数区域内,PReLU有一个很小的斜率,这样也可以避免ReLU死掉的问题。相比于ELU,PReLU在负数区域内是线性运算,斜率虽然小,但是不会趋于0,这算是一定的优势吧。
我们看PReLU的公式,里面的参数α一般是取0~1之间的数,而且一般还是比较小的,如零点零几。当α=0.01时,我们叫PReLU为Leaky ReLU,算是PReLU的一种特殊情况吧。
PReLu代码实现如下:
def prelu(x):
"""prelu函数"""
return np.where(x<0,alpha * x,x)
def der_prelu(x):
"""prelu函数的导数"""
return np.where(x<0,alpha,1)
3.5 softmax函数
softmax用于多分类过程中,它将多个神经元的输出,映射到(0,1)区间内,可以看成概率来理解。
softmax函数公式:
S
i
=
e
i
∑
j
e
j
S_{i}=\frac{e^{i}}{\sum _{j}e^{j}}
Si=∑jejei
接下来对公式进行细分说明:
(1)非线性变换之前计算:
z
(
l
)
=
W
(
l
)
a
(
l
−
1
)
+
b
(
l
)
z^{(l)}=W^{(l)}a^{(l-1)}+b^{(l)}
z(l)=W(l)a(l−1)+b(l)
(2)经过非线性变换,临时变量:
t
=
e
Z
l
t=e^{Z^{l}}
t=eZl
(3)归一化:
a
l
=
t
i
∑
j
=
1
n
t
j
a^{l}=\frac{t_{i}}{\sum_{j=1}^{n}t_{j}}
al=∑j=1ntjti
(4)
a
l
a^{l}
al表示的就是第几个类别的概率值,这些概率值和为1.
举例如下:
softmax直白来说就是将原来输出是3,1,-3通过softmax函数一作用,就映射成为(0,1)的值,而这些值的累和为1(满足概率的性质),那么我们就可以将它理解成概率,在最后选取输出结点的时候,我们就可以选取概率最大(也就是值对应最大的)结点,作为我们的预测目标。
3.6 MaxOut函数
Maxout模型实际上也是一种新型的激活函数,我们要先知道什么是maxout。我们假设网络某一层的输入特征向量为:X=(x1,x2,……xd),也就是我们输入是d个神经元。Maxout隐藏层每个神经元的计算公式如下:
h
i
(
x
)
=
m
a
x
(
Z
i
j
)
j
∈
[
1
,
k
]
h_{i}(x)=\underset{j\in \left [ 1,k \right ]}{max (Z_{ij})}
hi(x)=j∈[1,k]max(Zij)
上面的公式就是maxout隐藏层神经元i的计算公式。其中,k就是maxout层所需要的参数了,由我们人为设定大小。就像dropout一样,也有自己的参数p(每个神经元dropout概率),maxout的参数是k。公式中Z的计算公式为:
z
i
j
=
x
T
W
.
.
.
i
j
+
b
i
j
z_{ij}=x^{T}W_{...ij} + b_{ij}
zij=xTW...ij+bij
权重w是一个大小为(d,m,k)三维矩阵,b是一个大小为(m,k)的二维矩阵,这两个就是我们需要学习的参数。如果我们设定参数k=1,那么这个时候,网络就类似于以前我们所学普通的MLP网络。
我们可以这么理解,本来传统的MLP算法在第i层到第i+1层,参数只有一组,然而现在我们不怎么干了,我们在这一层同时训练n组参数,然后选择激活值最大的作为下一层神经元的激活值。下面还是用一个例子进行讲解,比较容易搞懂。
为了简单起见,假设我们网络第i层有2个神经元x1、x2,第i+1层的神经元个数为1个,如下图所示:
(1)以前MLP的方法。我们要计算第i+1层,那个神经元的激活值的时候,传统的MLP计算公式就是:
z
=
W
∗
X
+
b
z=W*X + b
z=W∗X+b
o
u
t
=
f
(
z
)
out=f(z)
out=f(z)
其中f就是我们所谓的激活函数,比如Sigmod、Relu、Tanh等。
(2)Maxout 的方法。如果我们设置maxout的参数k=5,maxout层就如下所示:
相当于在每个输出神经元前面又多了一层。这一层有5个神经元,此时maxout网络的输出计算公式为:
z1=w1*x+b1
z2=w2*x+b2
z3=w3*x+b3
z4=w4*x+b4
z5=w5*x+b5
out=max(z1,z2,z3,z4,z5)
也就是说第(i+1)层的激活值计算了5次,可我们明明只需要1个激活值,那么我们该怎么办?其实上面的叙述中已经给出了答案,取这5者的最大值来作为最终的结果。
总结一下,maxout明显增加了网络的计算量,使得应用maxout的层的参数个数成k倍增加,原本只需要1组就可以,采用maxout之后就需要k倍了。
再叙述一个稍微复杂点的应用maxout的网络,网络图如下:
对上图做个说明,第i层有3个节点,红点表示,而第(i+1)层有4个结点,用彩色点表示,此时在第(i+1)层采用maxout(k=3)。我们看到第(i+1)层的每个节点的激活值都有3个值,3次计算的最大值才是对应点的最终激活值。我举这个例子主要是为了说明,决定结点的激活值的时候并不是以层为单位,仍然以节点为单位。
优点:
与常规激活函数不同的是,它是一个可学习的分段线性函数.然而任何一个凸函数,都可以由线性分段函数进行逼近近似。其实我们可以把以前所学到的激活函数:ReLU、abs激活函数,看成是分成两段的线性函数,如下示意图所示:
实验结果表明Maxout与Dropout组合使用可以发挥比较好的效果。
那么,前边的两种ReLU便是两种Maxout,函数图像为两条直线的拼接。