目录
2.3.4 ELU(Exponential Linear Unit,指数线性单元)
1.基本概念
1.1 神经网络
人工神经网络(Artificial Neural Networks,简写为ANNs)是一种模仿动物神经网络行为特征,进行分布式并行信息处理的算法数学模型。这种网络依靠系统的复杂程度,通过调整内部大量节点之间相互连接的关系,从而达到处理信息的目的,并具有自学习和自适应的能力。神经网络类型众多,其中最为重要的是多层感知机。
1.2 感知机
感知机是1957年,由Rosenblatt提出会,是神经网络和支持向量机的基础。感知机是有生物学上的一个启发,他的参照对象和理论依据可以参照下图:(我们的大脑可以认为是一个神经网络,是一个生物的神经网络,在这个生物的神经网络里边呢,他的最小单元我们可以认为是一个神经元,一个neuron,这些很多个神经元连接起来形成一个错综复杂的网络,我们把它称之为神经网络。
我们人的神经网络是由这样一些神经元来构成的,那么这个神经元他的一些工作机制呢就是通过这样一个下面图的结构,首先接收到一些信号,这些信号通过这些树突(dendrite)组织,树突组织接收到这些信号送到细胞里边的细胞核(nucleus),这些细胞核对接收到的这些信号,这些信号是以什么形式存在的呢?这些信号比如说眼睛接收到的光学啊,或者耳朵接收到的声音信号,到树突的时候会产生一些微弱的生物电,那么就形成这样的一些刺激,那么在细胞核里边对这些收集到的接收到的刺激进行综合的处理,当他的信号达到了一定的阈值之后,那么他就会被激活,就会产生一个刺激的输出,那么就会形成一个我们大脑接收到的进一步的信号,那么他是通过轴突这样的输出计算的,这就是我们人脑的一个神经元进行感知的时候大致的一个工作原理。)
1.3 神经元
人工神经元(Artificial Neuron),简称神经元(Neuron),是构成神经网络 的基本单元,其主要是模拟生物神经元的结构和特性,接收一组输入信号并产生 输出。
从图中可以看出,他使用的是一个简单的一层网络,其中激活函数是阶跃函数(这也是最早使用的激活函数)。
于是这个感知机模型的公式可表示为:
其中的激活函数可表示为:
当x<0时,令输出为0,代表类别0;当x>=0时,令输出为1,代表类别1。然后通过感知机收敛算法一步步迭代,优化参数w和b,最终实现了最原始的二分类模型。
2.激活函数
2.1 为什么引入激活函数?
激活函数对模型学习、理解非常复杂和非线性的函数具有重要作用。
激活函数可以引入非线性因素。如果不使用激活函数,则输出信号仅是一个简单的线性函数。线性函数一个一级多项式,线性方程的复杂度有限,从数据中学习复杂函数映射的能力很小。没有激活函数,神经网络将无法学习和模拟其他复杂类型的数据,例如图像、视频、音频、语音等。
激活函数可以把当前特征空间通过一定的线性映射转换到另一个空间,让数据能够更好的被分类。
2.2 Sigmoid型函数
Sigmoid 型函数是指一类 S 型曲线函数,为两端饱和函数,常用的 Sigmoid 型函数有Logistic函数和Tanh函数。
2.2.1 Logistic函数
将输出视作二元分类问题的概率时, sigmoid仍然被广泛用作输出单元上的激活函数 (可以将sigmoid视为softmax的特例)。 然而,sigmoid在隐藏层中已经较少使用, 它在大部分时候被更简单、更容易训练的ReLU所取代。
from matplotlib import pyplot as plt
import numpy as np
# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
def tanh(x):
"""tanh函数"""
return ((np.exp(x) - np.exp(-x)) / (np.exp(x) + np.exp(-x)))
def dx_tanh(x):
"""tanh函数的导数"""
return 1 - tanh(x) * tanh(x)
if __name__ == '__main__':
x = np.arange(-10, 10, 0.01)
fx = tanh(x)
dx_fx = dx_tanh(x)
plt.subplot(1, 2, 1)
ax = plt.gca() # 得到图像的Axes对象
ax.spines['right'].set_color('none') # 将图像右边的轴设为透明
ax.spines['top'].set_color('none') # 将图像上面的轴设为透明
ax.xaxis.set_ticks_position('bottom') # 将x轴刻度设在下面的坐标轴上
ax.yaxis.set_ticks_position('left') # 将y轴刻度设在左边的坐标轴上
ax.spines['bottom'].set_position(('data', 0)) # 将两个坐标轴的位置设在数据点原点
ax.spines['left'].set_position(('data', 0))
plt.title('tanh 函数')
plt.xlabel('x')
plt.ylabel('fx')
plt.plot(x, fx)
plt.subplot(1, 2, 2)
ax = plt.gca() # 得到图像的Axes对象
ax.spines['right'].set_color('none') # 将图像右边的轴设为透明
ax.spines['top'].set_color('none') # 将图像上面的轴设为透明
ax.xaxis.set_ticks_position('bottom') # 将x轴刻度设在下面的坐标轴上
ax.yaxis.set_ticks_position('left') # 将y轴刻度设在左边的坐标轴上
ax.spines['bottom'].set_position(('data', 0)) # 将两个坐标轴的位置设在数据点原点
ax.spines['left'].set_position(('data', 0))
plt.title('tanh函数的导数')
plt.xlabel('x')
plt.ylabel('dx_fx')
plt.plot(x, dx_fx)
plt.show()
Sigmoid 作为激活函数,将输入映射到0~1,相对于阶跃函数,可以直接利用梯度下降算法优化网络参数,因此可以通过 Sigmoid 函数将输出转译为概率输出,常用于表示分类问题的事件概率。对S(x)求导:
在输入x=0时,导数最大为0.25;当输入为正负无穷时,梯度趋于0,会发生梯度弥散。
优点:
连续函数,平滑、易于求导;
缺点:
Sigmoid函数在变量取绝对值非常大的正值或负值时会出现饱和现象,意味着函数会变得很平并且对输入的微小改变会变得不敏感。在反向传播时,当梯度接近于0,权重基本不会更新,很容易就会出现梯度消失的情况,从而无法完成深层网络的训练。
.Sigmoid函数的输出不是0均值的,会导致后层的神经元的输入是非0均值的信号,这会对梯度产生影响。
指数级计算,计算量大;容易出现梯度弥散的情况。
2.2.2 Tanh函数
Tanh函数也是一种Sigmoid型函数,其定义为
m = nn.Tanh()
input = torch.randn(2)
output = m(input)
Tanh函数可以看作放大并平移的Logistic函数,其值域是(−1, 1), tanh(𝑥) = 2𝜎(2𝑥) − 1
Tanh 函数能够将输入x映射到[−1,1]区间,解决了Sigmoid函数输出值域不对称问题,是以0为中心的对称函数,收敛速度快,不容易出现loss值震荡。但是无法解决梯度弥散问题,同时计算量也大。
tanh函数的导数图像如下所示。 当输入接近0时,tanh函数的导数接近最大值1。 与我们在sigmoid函数图像中看到的类似, 输入在任一方向上越远离0点,导数越接近0。
2.3 ReLU函数 (修正线性单元)
2.3.1 Relu
Sigmoid 函数在输入值较大或较小时容易出现梯度弥散现象,网络参数长时间得不到更新,很难训练较深层次的网络模型。 AlexNet 采用ReLU(Rectified Linear Unit,修正线性单元), ReLU实际上是一个斜坡(ramp)函数,定义为
from matplotlib import pyplot as plt
import numpy as np
# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
def relu(x):
"""relu函数"""
# temp = np.zeros_like(x)
# if_bigger_zero = (x > temp)
# return x * if_bigger_zero
return np.where(x<0,0,x)
def dx_relu(x):
"""relu函数的导数"""
# temp = np.zeros_like(x)
# if_bigger_equal_zero = (x >= temp)
# return if_bigger_equal_zero * np.ones_like(x)
return np.where(x < 0, 0, 1)
# ---------------------------------------------
if __name__ == '__main__':
x = np.arange(-10, 10, 0.01)
fx = relu(x)
dx_fx = dx_relu(x)
plt.subplot(1, 2, 1)
ax = plt.gca() # 得到图像的Axes对象
ax.spines['right'].set_color('none') # 将图像右边的轴设为透明
ax.spines['top'].set_color('none') # 将图像上面的轴设为透明
ax.xaxis.set_ticks_position('bottom') # 将x轴刻度设在下面的坐标轴上
ax.yaxis.set_ticks_position('left') # 将y轴刻度设在左边的坐标轴上
ax.spines['bottom'].set_position(('data', 0)) # 将两个坐标轴的位置设在数据点原点
ax.spines['left'].set_position(('data', 0))
plt.title('Relu函数')
plt.xlabel('x')
plt.ylabel('fx')
plt.plot(x, fx)
plt.subplot(1, 2, 2)
ax = plt.gca() # 得到图像的Axes对象
ax.spines['right'].set_color('none') # 将图像右边的轴设为透明
ax.spines['top'].set_color('none') # 将图像上面的轴设为透明
ax.xaxis.set_ticks_position('bottom') # 将x轴刻度设在下面的坐标轴上
ax.yaxis.set_ticks_position('left') # 将y轴刻度设在左边的坐标轴上
ax.spines['bottom'].set_position(('data', 0)) # 将两个坐标轴的位置设在数据点原点
ax.spines['left'].set_position(('data', 0))
plt.title('Relu函数的导数')
plt.xlabel('x')
plt.ylabel('dx_fx')
plt.plot(x, dx_fx)
plt.show()
单边抑制特性: ReLU 对小于 0 的值全部抑制为 0;对于正数则直接输出
优点:
1)使训练快速收敛,解决了梯度弥散。在信息传递的过程中,大于0的部分梯度总是为1。
2)稀疏性:模拟神经元的激活率是很低的这一特性;ReLU的输入在大于0时才能传播信息,正是这样的稀疏性提高了网络的性能。
缺点:ReLU 神经元在训练时比较容易“死亡”。在训 练时,如果参数在一次不恰当的更新后,第一个隐藏层中的某个 ReLU 神经元在 所有的训练数据上都不能被激活,那么这个神经元自身参数的梯度永远都会是 0,在以后的训练过程中永远不能被激活.这种现象称为死亡 ReLU 问题。
2.3.2 Leaky ReLU
ReLU 函数在输入x < 0时梯度值恒为 0,也可能会造成梯度弥散现象,为了克服这个问题,提出了带泄露的ReLU(Leaky ReLU),在输入 𝑥 < 0时,保持一个很小的梯度𝛾,这 样当神经元非激活时也能有一个非零的梯度可以更新参数,避免永远不能被激活。
from matplotlib import pyplot as plt
import numpy as np
# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
def leaky_relu(x):
"""leaky relu函数"""
return np.where(x<0,0.01*x,x)
def dx_leaky_relu(x):
"""leaky relu函数的导数"""
return np.where(x < 0, 0.01, 1)
# ---------------------------------------------
if __name__ == '__main__':
x = np.arange(-10, 10, 0.01)
fx = leaky_relu(x)
dx_fx = dx_leaky_relu(x)
plt.subplot(1, 2, 1)
ax = plt.gca() # 得到图像的Axes对象
ax.spines['right'].set_color('none') # 将图像右边的轴设为透明
ax.spines['top'].set_color('none') # 将图像上面的轴设为透明
ax.xaxis.set_ticks_position('bottom') # 将x轴刻度设在下面的坐标轴上
ax.yaxis.set_ticks_position('left') # 将y轴刻度设在左边的坐标轴上
ax.spines['bottom'].set_position(('data', 0)) # 将两个坐标轴的位置设在数据点原点
ax.spines['left'].set_position(('data', 0))
plt.title('Leaky ReLu函数')
plt.xlabel('x')
plt.ylabel('fx')
plt.plot(x, fx)
plt.subplot(1, 2, 2)
ax = plt.gca() # 得到图像的Axes对象
ax.spines['right'].set_color('none') # 将图像右边的轴设为透明
ax.spines['top'].set_color('none') # 将图像上面的轴设为透明
ax.xaxis.set_ticks_position('bottom') # 将x轴刻度设在下面的坐标轴上
ax.yaxis.set_ticks_position('left') # 将y轴刻度设在左边的坐标轴上
ax.spines['bottom'].set_position(('data', 0)) # 将两个坐标轴的位置设在数据点原点
ax.spines['left'].set_position(('data', 0))
plt.title('Leaky Relu函数的导数')
plt.xlabel('x')
plt.ylabel('dx_fx')
plt.plot(x, dx_fx)
plt.show()
2.3.3 Softplus函数
Softplus 函数可以看作 Rectifier 函数的平滑版本,其 定义为 Softplus(𝑥) = log(1 + exp(𝑥))。 Softplus函数其导数刚好是Logistic函数.Softplus函数虽然也具有单侧抑制、宽兴奋边界的特性,却没有稀疏激活性。
2.3.4 ELU(Exponential Linear Unit,指数线性单元)
近似的零中心化的非线性函数,其定义为
from matplotlib import pyplot as plt
import numpy as np
# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
def ELU(x):
"""ELU函数"""
return np.where(x<0,np.exp(x)-1,x)
def dx_ELU(x):
"""ELU函数的导数"""
return np.where(x < 0, np.exp(x), 1)
# ---------------------------------------------
if __name__ == '__main__':
x = np.arange(-10, 10, 0.01)
fx = ELU(x)
dx_fx = dx_ELU(x)
plt.subplot(1, 2, 1)
ax = plt.gca() # 得到图像的Axes对象
ax.spines['right'].set_color('none') # 将图像右边的轴设为透明
ax.spines['top'].set_color('none') # 将图像上面的轴设为透明
ax.xaxis.set_ticks_position('bottom') # 将x轴刻度设在下面的坐标轴上
ax.yaxis.set_ticks_position('left') # 将y轴刻度设在左边的坐标轴上
ax.spines['bottom'].set_position(('data', 0)) # 将两个坐标轴的位置设在数据点原点
ax.spines['left'].set_position(('data', 0))
plt.title('ELU函数')
plt.xlabel('x')
plt.ylabel('fx')
plt.plot(x, fx)
plt.subplot(1, 2, 2)
ax = plt.gca() # 得到图像的Axes对象
ax.spines['right'].set_color('none') # 将图像右边的轴设为透明
ax.spines['top'].set_color('none') # 将图像上面的轴设为透明
ax.xaxis.set_ticks_position('bottom') # 将x轴刻度设在下面的坐标轴上
ax.yaxis.set_ticks_position('left') # 将y轴刻度设在左边的坐标轴上
ax.spines['bottom'].set_position(('data', 0)) # 将两个坐标轴的位置设在数据点原点
ax.spines['left'].set_position(('data', 0))
plt.title('ELU函数的导数')
plt.xlabel('x')
plt.ylabel('dx_fx')
plt.plot(x, dx_fx)
plt.show()
𝛾 ≥ 0是一个超参数,决定𝑥 ≤ 0时的饱和曲线,并调整输出均值在0附近。
2.4 Softmax
Softmax 函数定义:
Softmax 函数不仅可以将输出值映射到[0,1]区间,还满足所有的输出值之和为 1 的特性。
通过 Softmax 函数可以将输出层的输出转译为类别概率,在多分类问题中使用的非常频繁。
m = nn.Softmax(dim=1)
input = torch.randn(2, 3)
output = m(input)
2.5 Mish
Mish=x * tanh(ln(1+e^x))
from matplotlib import pyplot as plt
import numpy as np
# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
def sech(x):
"""sech函数"""
return 2 / (np.exp(x) + np.exp(-x))
def sigmoid(x):
"""sigmoid函数"""
return 1 / (1 + np.exp(-x))
def softplus(x):
"""softplus函数"""
return np.log10(1+np.exp(x))
def tanh(x):
"""tanh函数"""
return ((np.exp(x) - np.exp(-x)) / (np.exp(x) + np.exp(-x)))
if __name__ == '__main__':
x = np.arange(-10, 10, 0.01)
fx = x * tanh(softplus(x))
dx_fx = sech(softplus(x))*sech(softplus(x))*x*sigmoid(x)+fx/x
plt.subplot(1, 2, 1)
ax = plt.gca() # 得到图像的Axes对象
ax.spines['right'].set_color('none') # 将图像右边的轴设为透明
ax.spines['top'].set_color('none') # 将图像上面的轴设为透明
ax.xaxis.set_ticks_position('bottom') # 将x轴刻度设在下面的坐标轴上
ax.yaxis.set_ticks_position('left') # 将y轴刻度设在左边的坐标轴上
ax.spines['bottom'].set_position(('data', 0)) # 将两个坐标轴的位置设在数据点原点
ax.spines['left'].set_position(('data', 0))
plt.title('Mish函数')
plt.xlabel('x')
plt.ylabel('fx')
plt.plot(x, fx)
plt.subplot(1, 2, 2)
ax = plt.gca() # 得到图像的Axes对象
ax.spines['right'].set_color('none') # 将图像右边的轴设为透明
ax.spines['top'].set_color('none') # 将图像上面的轴设为透明
ax.xaxis.set_ticks_position('bottom') # 将x轴刻度设在下面的坐标轴上
ax.yaxis.set_ticks_position('left') # 将y轴刻度设在左边的坐标轴上
ax.spines['bottom'].set_position(('data', 0)) # 将两个坐标轴的位置设在数据点原点
ax.spines['left'].set_position(('data', 0))
plt.title('Mish函数的导数')
plt.xlabel('x')
plt.ylabel('dx_fx')
plt.plot(x, dx_fx)
plt.show()
上无边界(即正值可以达到任何高度)避免了由于封顶而导致的饱和。理论上对负值的轻微允许允许更好的梯度流,而不是像ReLU中那样的硬零边界。平滑的激活函数允许更好的信息深入神经网络,从而得到更好的准确性和泛化。
2.6 Maxout
Maxout 单元是一种分段线性函数。Sigmoid 型 函数、ReLU 等激活函数的输入是神经元的净输入 𝑧,是一个标量.而 Maxout 单 元的输入是上一层神经元的全部原始输出,是一个向量𝒙 = [𝑥1 ; 𝑥2 ; ⋯ ; 𝑥𝐷]。
Maxout单元的非线性函数定义为
Maxout单元不单是净输入到输出之间的非线性映射,而是整体学习输入到 输出之间的非线性映射关系。 Maxout激活函数可以看作任意凸函数的分段线性近似,并且在有限的点上是不可微的。
2.7 小结
函数 | 优点 | |
Sigmoid | Sigmoid函数的输出映射在(0,1)之间,单调连续,输出范围有限,优化稳定 求导容易。 | 幂运算,计算成本高;容易出现梯度弥散(反向传播时,很容易就会出现梯度消失的情况,从而无法完成深层网络的训练);其输出并不是以0为中心的。 |
Tanh | 函数位于[-1, 1]区间上,比Sigmoid函数收敛速度更快;相比Sigmoid函数,其输出以0为中心 | 还是没有改变Sigmoid函数的最大问题——由于饱和性产生的梯度消失。 |
ReLU | 梯度不饱和,收敛速度快;相对于sigmoid/tanh激活函数,极大的改善了梯度消失的问题;不需要进行指数运算,因此运算速度快、复杂度低;在没有无监督预训练的时候也能有较好的表现; ReLU会使一部分神经元的输出为0,这样就造成了网络的稀疏性,并且减少了参数的相互依存关系,缓解了过拟合问题的发生。 | 对参数初始化和学习率非常敏感,存在神经元死亡; ReLU的输出均值也大于0,偏移现象和 神经元死亡会共同影响网络的收敛性 |
Leaky ReLU | 当p = 0时,LeayReLU 函数退化为 ReLU 函数。当p ≠ 0时,x < 0能够获得较小的梯度值,从而避免了梯度弥散。 | |
Maxout | Maxout的拟合能力非常强,可以拟合任意的凸函数。Maxout具有ReLU的所有优点,线性、不饱和性。同时没有ReLU的一些缺点。如:神经元的死亡。 | 每个神经元中有两组(w,b)参数,那么参数量就增加了一倍,这就导致了整体参数的数量激增 |
3. 激活函数的性质
1.非线性︰当激活函数是非线性的,一个两层的神经网络就可以基本上逼近所有的函数。
2.可微性:可微性保证了在优化中梯度的可计算性。传统的激活函数如Sigmoid等满足处处可微。对于分段线性函数比如ReLU,只满足几乎处处可微(即仅在有限个点处不可微)。对于SGD算法来说,由于几乎不可能收敛到梯度接近零的位置,有限的不可微点对于优化结果不会有很大影响.
3.计算简单:激活函数在神经网络前向的计算次数与神经元的个数成正比,因此简单的非线性函数自然更适合用作激活函数。
4.非饱和性(saturation):饱和指的是在某些区间梯度接近于零(即梯度消失),使得参数无法继续更新的问题。最经典的例子是Sigmoid的导数在x为比较大的正值和比较小的负值时都会接近于0。更极端的例子是阶跃函数,在几乎所有位置的梯度都为O,因此处处饱和,无法作为激活函数。ReLU在x>0时导数恒为1,因此对于再大的正值也不会饱和。但同时对于x<0,其梯度恒为0,这时候它也会出现饱和的现象。LeakyReLU和PReLU的提出正是为了解决这一问题。
5.单调性(monotonic):即导数符号不变。当激活函数是单调的时候,单层网络能够保证是凸函数。
6.输出范围有限:有限的输出范围使得网络对于一些比较大的输入也会比较稳定,这也是为什么早期的激活函数都以此类函数为主,如Sigmoid、Tanh。但这导致了前面提到的梯度消失问题,而且强行让每一层的输出限制到固定范围会限制其表达能力。因此现在这类函数仅用于某些需要特定输出范围的场合,比如概率输出(此时loss函数中的log操作能够抵消其梯度消失的影响)、LSTM里的gate函数。
7.接近恒等变换。使得输出的幅值不会随着深度的增加而发生显著的增加,从而使网络更为稳定,同时梯度也能够更容易地回传。这个与非线性是有点矛盾的,因此激活函数基本只是部分满足这个条件。
8.参数少:大部分激活函数都是没有参数的。像PReLU带单个参数会略微增加网络的大小。Maxout,尽管本身没有参数,但在同样输出通道数下k路Maxout需要的输入通道数是其它函数的k倍,这意味着神经元数目也需要变为k倍;但如果不考虑维持输出通道数的情况下,该激活函数又能将参数个数减少为原来的k倍。
9.归一化(normalization):对应的激活函数是SELU,主要思想是使样本分布自动归一化到零均值、单位方差的分布,从而稳定训练。归一化的思想也被用于网络结构的设计,比如Batch Normalization
4. 如何选择激活函数?
1.如果输出是0、1值(二分类问题),则输出层选择Sigmoid函数,然后其它的所有单元都选择Relu函数。
2 如果在隐藏层上不确定使用哪个激活函数,那么通常会使用Relu激活函数。有时,也会使用Tanh激活函数,但Relu的一个优点是:当是负值的时候,导数等于0。
3.Sigmoid激活函数:除了输出层是一个二分类问题基本不会用它。
4.Tanh激活函数:Tanh是非常优秀的,几乎适合所有场合。
5.ReLu激活函数:最常用的默认函数,如果不确定用哪个激活函数,就使用ReLu或者LeakyReLu,再去尝试其他的激活函数。
6.如果遇到了一些死的神经元,我们可以使用LeakyReLU 函数.
相关申明及相关参考:
主要学习笔记地址
https://yolov5.blog.csdn.net/article/details/127138038
https://blog.csdn.net/hhhhhhhhhhwwwwwwwwww/article/details/119604015
https://blog.csdn.net/MengYa_Dream/article/details/119714973
https://blog.csdn.net/liuy9803/article/details/81545664
如有侵权,请联系删除。