前言
1943年,心理学家Warren McCulloch和数学家Walter Pitts发表论文《A Logical Calculus Of The Ideas Immanent In Nervous Activity》(神经活动中固有思想的逻辑演算),提出了简化脑细胞的概念,也就是所谓的MP神经元模型。
McCulloch和Pitts把神经细胞描述为带有二元输出的简单逻辑门。多个信号到达树突,然后整合到细胞体,当累计信号量超过一定阈值时,输出信号就通过轴突。
将MP神经元模型放在数学模型中来看,我们用
x
x
x来表示输入,假设一个神经元接收
m
m
m个输入,令向量
x
=
[
x
1
x
2
⋮
x
m
]
,
ω
=
[
ω
1
ω
2
⋮
ω
m
]
x = \begin{bmatrix} x_1 \\ x_2 \\ \vdots \\ x_m \\ \end{bmatrix},\omega = \begin{bmatrix} \omega_1 \\ \omega_2 \\ \vdots \\ \omega_m \\ \end{bmatrix}
x=
x1x2⋮xm
,ω=
ω1ω2⋮ωm
来表示输入层,接收来自其他神经元的信号。
这些信号的来源不同,对神经元的影响也不同,因此需要给他们分配不同的权重,
ω
\omega
ω则表示权重。
对接收到的输入信号加权求和之后,与产生神经兴奋的阈值
θ
θ
θ相比较,得到中间结果
z
z
z。
z
z
z也叫做净输入(net input),
z
=
ω
1
x
1
+
ω
2
x
2
+
.
.
.
+
ω
m
x
m
z=\omega_1x_1+\omega_2x_2+...+\omega_mx_m
z=ω1x1+ω2x2+...+ωmxm
最后通过阶跃函数模型神经兴奋。当
z
z
z的值小于0时,神经元处于抑制状态,输出为-1.当
z
z
z的值大于0时,神经元被激活,处于兴奋状态,输出为1.
ϕ
(
z
)
=
{
1
,
z
≥
0
−
1
,
e
l
s
e
\phi(z)=\begin{cases} 1 , z≥0 \\ -1 , else \\ \end{cases}
ϕ(z)={1,z≥0−1,else
这个阶跃函数,就被称为激活函数。
数学模型表示如下图:
激活函数的性质
激活函数在神经元中是非常重要的,为了增强网络的表示能力和学习能力,激活函数需要具备以下几点性质:
- 连续并且可导(允许少数点上不可导)的非线性函数。可导的激活函数可以直接利用数值优化的方法来学习网络参数;
- 激活函数及其导函数要尽可能地简单,有利于提高网络计算效率;
- 激活函数的导函数的值域要在一个合适的区间内,不能太大也不能太小,否则会影响训练的效率和稳定性。
假设
f
(
x
)
f(x)
f(x)是一个激活函数,当
x
→
−
∞
x→-∞
x→−∞时,其导函数
f
′
(
x
)
→
0
f'(x)→0
f′(x)→0则称其为左饱和激活函数,当
x
→
+
∞
x→+∞
x→+∞时,其导函数
f
′
(
x
)
→
0
f'(x)→0
f′(x)→0则称其为右饱和激活函数。当一个函数既满足左饱和、又满足右饱和,那么就被称为饱和激活函数,否则就被称为非饱和激活函数。
#常见的激活函数
1.Sigmoid函数
Sigmoid型函数是传统神经网络中最常用的激活函数,指一类S型曲线函数,为两端饱和函数,常用的Sigmoid型函数有Logistic函数和Tanh函数。
1.1 Logistic函数
Logistic函数定义为:
σ
(
x
)
=
1
1
+
e
−
x
\sigma(x)=\displaystyle\frac{1}{1+e^{-x}}
σ(x)=1+e−x1
其值域为
(
0
,
1
)
(0,1)
(0,1) ,可以看成是一个“挤压”函数,把一个实数域的输入挤压到
(
0
,
1
)
(0,1)
(0,1) ,当输入值在0附近时,Sigmoid函数近似为线性函数;当输入值靠近两端时,对函数进行抑制,输入越小,越接近于0;输入越大,越接近于1。这样的特点与生物神经元类似,对一些输入会产生兴奋(输出为1)对另一些输入会产生抑制(输出为0)。
使用Python绘制Logistic函数的实现代码及绘图结果如下:
#Logistic
import numpy as np
import matplotlib.pyplot as plt
# 定义Logistic激活函数
def logistic(x):
return 1.0 / (1.0 + np.exp(-x))
# 生成输入值范围
x = np.arange(-10, 10, 0.1)
# 计算Logistic函数的输出
y = logistic(x)
# 绘制Logistic函数的图像
plt.plot(x, y, label='Logistic', color='b')
plt.xlabel('Input')
plt.ylabel('Output')
plt.title('Logistic Activation Function')
plt.axhline(0, color='black', linewidth=0.5, linestyle='--')
plt.axvline(0, color='black', linewidth=0.5, linestyle='--')
plt.legend()
plt.grid(True)
plt.show()
Logistic函数的优点为:
- 输出映射在(0,1)之间,输出可以直接看作概率分布,单调连续,优化稳定;
- 易于求导。
Logistic函数的缺点为:
- 容易出现梯度弥散,反向传播时容易出现梯度消失和梯度爆炸;
- 输出不是0均值(zero-centered),导致收敛速度下降;
- 解析式中含有幂运算,求解较为耗时。
1.2 Tanh函数
Tanh函数定义为:
t
a
n
h
(
x
)
=
e
x
−
e
−
x
e
x
+
e
−
x
tanh(x)=\displaystyle\frac{e^x-e^{-x}}{e^x+e^{-x}}
tanh(x)=ex+e−xex−e−x
Tanh函数可以看作是放大且平移的Logistic函数,其值域是(-1,1)。
t
a
n
h
(
x
)
=
2
σ
(
2
x
)
−
1
tanh(x)=2\sigma(2x)-1
tanh(x)=2σ(2x)−1
使用Python绘制Tanh函数的实现代码及绘图结果如下:
#Tanh
import numpy as np
import matplotlib.pyplot as plt
# 定义Tanh函数
def tanh(x):
return (np.exp(x) - np.exp(-x)) / (np.exp(x) + np.exp(-x))
# 生成输入值范围
x = np.arange(-10, 10, 0.1)
# 计算Tanh函数的输出
y = tanh(x)
# 绘制Tanh函数的图像
plt.plot(x, y, label='Tanh', color='b')
plt.xlabel('Input')
plt.ylabel('Output')
plt.title('Tanh Activation Function')
plt.axhline(0, color='black', linewidth=0.5, linestyle='--')
plt.axvline(0, color='black', linewidth=0.5, linestyle='--')
plt.legend()
plt.grid(True)
plt.show()
Tanh函数的优点为:
- 输出是零中心化的(zero-centered),提高收敛速度
Tanh函数的缺点为:
- 没有解决反向传播时容易出现梯度消失和梯度爆炸的问题
2.ReLU函数
ReLU(Rectified Linear Unit,修正线性单元),也叫Rectifier函数,是目前深度神经网络中经常使用的激活函数。ReLU实际上是一个斜坡(ramp)函数,定义为:
R
e
L
U
(
x
)
=
{
x
,
x
≥
0
0
,
x
<
0
ReLU(x)=\begin{cases} x, x\geq0\\ 0, x<0\\ \end{cases}
ReLU(x)={x,x≥00,x<0
使用Python绘制ReLU函数的实现代码及绘图结果如下:
#ReLU
import numpy as np
import matplotlib.pyplot as plt
# 定义ReLU函数
def relu(x):
return np.maximum(0, x)
# 生成一系列输入值
x = np.linspace(-5, 5, 100)
# 计算ReLU函数的输出
y = relu(x)
# 绘制ReLU函数图像
plt.plot(x, y, label='ReLU', color='b')
plt.xlabel('Input')
plt.ylabel('Output')
plt.title('ReLU Function')
plt.axhline(0, color='black', linewidth=0.5, linestyle='--')
plt.grid(True, linestyle='--', alpha=0.7)
plt.legend()
plt.show()
ReLU函数的优点为:
- 只需要进行加、乘和比较的操作,计算上更加高效;
- 具有生物合理性,比如单侧抑制、宽兴奋边界等,有很好的稀疏性,有约一半的神经元同时处于激活状态;
- 在正区间解决了梯度消失的问题;梯度不饱和,收敛速度远远快于Sigmoid函数
ReLU函数的缺点为:
- 输出不是0均值(zero-centered),影响梯度下降的效率;
- 在训练时,如果参数在一次不恰当的更新后,第一个隐藏层中的某个ReLU神经元在所有的训练数据上都不能被激活,那么这个神经元自身参数的梯度将永远都是0,在以后的训练过程中永远不能被激活,也就是所谓的死亡问题
- 对参数初始化和学习率非常敏感
为了解决以上问题,提出了ReLU的变体。
2.1 Leaky ReLU函数
带泄露的ReLU(Leaky ReLU)在输入
x
<
0
x<0
x<0时,保持一个很小的梯度
γ
\gamma
γ.这样当神经元非激活时也能有一个非零的梯度可以更新参数,避免永远不能被激活的情况。Leaky ReLU函数定义为:
L
e
a
k
y
R
e
L
U
(
x
)
=
{
x
,
x
>
0
γ
x
,
x
≤
0
LeakyReLU(x)=\begin{cases} x, x>0\\ \gamma x, x\leq0\\ \end{cases}
LeakyReLU(x)={x,x>0γx,x≤0
其中
γ
\gamma
γ 是一个很小的常数,比如0.01.当
γ
<
1
\gamma<1
γ<1时,LeakyReLU也可以写为:
L
e
a
k
y
R
e
L
U
(
x
)
=
m
a
x
(
x
,
γ
x
)
LeakyReLU(x)=max(x,\gamma x)
LeakyReLU(x)=max(x,γx)
使用Python绘制LeakyReLU函数的实现代码及绘图结果如下:
#LeakyReLU
import numpy as np
import matplotlib.pyplot as plt
# 定义Leaky ReLU函数
def leaky_relu(x, negative_slope=0.01):
return np.where(x >= 0, x, negative_slope * x)
# 生成输入值范围
x = np.linspace(-5, 5, 100)
# 计算Leaky ReLU的输出
y = leaky_relu(x, negative_slope=0.01)
# 绘制Leaky ReLU函数的图像
plt.plot(x, y, label='Leaky ReLU (0.01)', color='b')
plt.xlabel('Input')
plt.ylabel('Output')
plt.title('Leaky ReLU Activation Function')
plt.axhline(0, color='black', linewidth=0.5, linestyle='--')
plt.axvline(0, color='black', linewidth=0.5, linestyle='--')
plt.legend()
plt.grid(True)
plt.show()
LeakyReLU函数的优点为:
- 解决了ReLU函数的死亡神经元问题(Dead Neurons Problem),使得神经元在负数输入情况下也能产生非零输出,从而增强了模型的稀疏表示能力;
- 可以避免梯度消失问题(Vanishing Gradient Problem),从而加快收敛速度和提高模型的训练性能;
- 相比于其他激活函数(如sigmoid和tanh等),LeakyReLU计算速度更快。
LeakReLU函数的缺点为:
- 在输入小于零的情况下,LeakyReLU引入了一些线性项,可能导致模型过于依赖于线性关系,影响模型的表达能力;
- 需要额外的超参数来调整负数输入情况下的斜率,增加了模型设计的复杂性;
- 由于不具备严格的非线性特性,可能不适用于某些需要更强非线性表达能力的任务。
2.2 PReLU函数
带参数的ReLU(PReLU)引入一个可学习的参数,不同神经元可以有不同的参数。对于第i个神经元,PReLU函数定义为:
P
R
e
L
U
(
x
)
=
{
x
,
x
>
0
γ
i
x
,
x
≤
0
PReLU(x)=\begin{cases} x, x>0\\ \gamma_i x, x\leq0\\ \end{cases}
PReLU(x)={x,x>0γix,x≤0
其中
γ
i
\gamma_i
γi 为
x
≤
0
x\leq0
x≤0时函数的斜率。因此PReLU是非饱和函数。如果
γ
i
=
0
\gamma_i=0
γi=0,那么PReLU就退化为ReLU.如果
γ
i
\gamma_i
γi是一个很小的常数,那么PReLU就可以看作是Leaky ReLU。
使用Python绘制PReLU函数的实现代码及绘图结果如下:
#PReLU
import numpy as np
import matplotlib.pyplot as plt
#定义prelu函数
def prelu(x):
return np.where(x<0, x * 0.5, x)
# 生成输入值范围
x = np.arange(-10, 10, 0.01)
# 计算PReLU的输出
y = prelu(x)
# 绘制PReLU函数的图像
plt.plot(x, y, label='PReLU (0.01)', color='b')
plt.xlabel('Input')
plt.ylabel('Output')
plt.title('PReLU Activation Function')
plt.axhline(0, color='black', linewidth=0.5, linestyle='--')
plt.axvline(0, color='black', linewidth=0.5, linestyle='--')
plt.legend()
plt.grid(True)
plt.show()
PReLU函数的优点为:
- 解决了ReLU函数的死亡神经元问题和梯度消失问题,因为PReLU可以在负数输入情况下学习到不同的斜率,使得神经元能够更好地适应不同的任务和数据;
- 具有更强的表达能力和灵活性,因为PReLU的参数可以根据数据的特征进行学习和调整,从而提高模型的适应性;
- 在一些复杂的任务中,PReLU可以比LeakyReLU和ReLU更好地模拟数据的非线性关系,从而提高模型的性能。
PReLU函数的缺点为:
- PReLU引入了额外的参数,增加了模型的复杂性和计算成本;
- 在某些情况下,PReLU可能会在训练过程中导致模型过拟合,需要进行适当的正则化和参数调整;
- 学习到的参数可能与数据的分布相关,如果数据在不同区域有不同的分布特点,可能需要额外的技术手段来适应这种情况。
2.3 ELU函数
ELU(Exponential Linear Unit,指数线性单元)是一个近似的零中心化的非线性函数,其定义为:
E
L
U
(
x
)
=
{
x
,
x
>
0
γ
(
e
x
−
1
)
,
x
≤
0
=
m
a
x
(
0
,
x
)
+
m
i
n
(
0
,
γ
(
e
x
−
1
)
)
ELU(x)=\begin{cases} x, x>0\\ \gamma(e^x-1), x\leq0\\ \end{cases} =max(0,x)+min(0,\gamma(e^x-1))
ELU(x)={x,x>0γ(ex−1),x≤0=max(0,x)+min(0,γ(ex−1))
其中
γ
≥
0
\gamma\geq0
γ≥0 是一个超参数,决定
x
≤
0
x\leq0
x≤0时的饱和曲线,并调整输出均值在0附近。
使用Python绘制ELU函数的实现代码及绘图结果如下:
#ELU
import numpy as np
import matplotlib.pyplot as plt
# 定义ELU函数
def elu(x, alpha=1.0):
return np.where(x >= 0, x, alpha * (np.exp(x) - 1))
# 生成输入值范围
x = np.arange(-10, 10, 0.1)
# 计算ELU函数的输出
y = elu(x)
# 绘制ELU函数的图像
plt.plot(x, y, label='ELU', color='b')
plt.xlabel('Input')
plt.ylabel('Output')
plt.title('ELU Activation Function')
plt.axhline(0, color='black', linewidth=0.5, linestyle='--')
plt.axvline(0, color='black', linewidth=0.5, linestyle='--')
plt.legend()
plt.grid(True)
plt.show()
ELU函数的优点为:
- 解决了梯度消失问题:当输入值为负时,ELU函数不会产生饱和现象,可以保持较大的导数。这有助于减轻梯度消失问题,使得网络更容易训练;
- 提供了更平滑的导数曲线:ELU函数在正区域和负区域的斜率都比较平滑,这有助于提高训练的稳定性和收敛速度;
- 具有近似指数线性性质:当输入值为正时,ELU函数呈现指数线性增长,有助于模型学习非线性特征。
ELU函数的缺点为:
- 计算复杂度高:ELU函数的计算包含指数运算,相比于其他激活函数,如ReLU,计算复杂度要高一些,导致模型的训练速度稍慢;
- 不具备整流性质:与ReLU相比,ELU函数在负区域的输出值不是恒定的0,这可能使得模型在某些应用场景中表现得不太好;
- 参数依赖性:ELU函数引入了一个额外的超参数(alpha),这需要进行调优以获得最佳性能。
2.4 Softplus函数
Softplus函数可以看作是ReLU函数的平滑版本,其定义为:
S
o
f
t
p
l
u
s
(
x
)
=
l
o
g
(
1
+
e
x
)
Softplus(x)=log(1+e^x)
Softplus(x)=log(1+ex)
Softplus函数的导数刚好是Logistic函数,Softplus函数也具有单侧抑制、宽兴奋边界的特性,但没有稀疏激活性。
使用Python绘制Softplus函数的实现代码及绘图结果如下:
#Softplus
import numpy as np
import matplotlib.pyplot as plt
# 定义softplus函数
def softplus(x):
return np.log(1 + np.exp(x))
# 生成输入值范围
x = np.arange(-10, 10, 0.1)
# 计算softplus函数的输出值
y = softplus(x)
# 绘制softplus函数的图像
plt.plot(x, y, label='Softplus', color='b')
plt.xlabel('Input')
plt.ylabel('Output')
plt.title('Softplus Function')
plt.axhline(0, color='black', linewidth=0.5, linestyle='--')
plt.axvline(0, color='black', linewidth=0.5, linestyle='--')
plt.legend()
plt.grid(True)
plt.show()
Softplus函数的优点为:
- 与sigmoid函数相比,softplus函数的梯度更加稳定,不存在梯度饱和现象,因此在深度学习模型中更常被使用;
- softplus函数是一个连续、平滑、非常接近于ReLU函数的函数,因此能够保留输入信号的特征,同时最小化信息丢失。
Softplus函数的缺点为:
- softplus函数在输入值很小的时候,输出值接近于0,这使得梯度的数值变得非常小,这会影响模型的训练速度和效率;
- softplus函数在输入值很大的时候,可能会发生数值上溢,这会导致计算机无法计算出对应的输出值,因此在实现该函数时需要进行处理。
3.Swish函数(SiLU函数)
Swish函数是一种自门控激活函数,其定义为:
S
w
i
s
h
(
x
)
=
x
σ
(
β
x
)
Swish(x)=x\sigma(\beta x)
Swish(x)=xσ(βx)
其中
σ
(
⋅
)
\sigma(·)
σ(⋅)为Logistic函数,
β
\beta
β为可学习的参数或一个固定参数。
σ
(
⋅
)
∈
(
0
,
1
)
\sigma(·)\in(0,1)
σ(⋅)∈(0,1) 可以看作是一种软性的门控机制。当
σ
(
β
x
)
\sigma(\beta x)
σ(βx)接近于1时,门处于“开”的状态,激活函数的输出近似于x本身;当
σ
(
β
x
)
\sigma(\beta x)
σ(βx)接近于0时,门处于“关”的状态,激活函数的输出近似于0.
使用Python绘制Swish函数的实现代码及绘图结果如下:
#Swish
import numpy as np
import matplotlib.pyplot as plt
# 定义Swish函数
def swish(x, beta):
return x / (1 + np.exp(-beta*x))
# 生成输入值范围
x = np.arange(-10, 10, 0.1)
# 定义不同的β值
beta_values = [0, 0.5, 1, 100]
# 绘制Swish函数在不同的β值下的图像
for beta in beta_values:
y = swish(x, beta)
plt.plot(x, y, label=f'Beta = {beta}')
plt.xlabel('Input')
plt.ylabel('Output')
plt.title('Swish Function with Different Beta Values')
plt.axhline(0, color='black', linewidth=0.5, linestyle='--')
plt.axvline(0, color='black', linewidth=0.5, linestyle='--')
plt.legend()
plt.grid(True)
plt.show()
当 β = 0 \beta=0 β=0时,Swish函数变为线性函数 x / 2 x/2 x/2。当 β = 1 \beta=1 β=1时 ,Swish函数在 x > 0 x>0 x>0时近似线性,在 x < 0 x<0 x<0时近似饱和,同时具有一定的非单调性,此时我们也称其为SiLU函数.当 β → + ∞ \beta\rightarrow+\infty β→+∞时, σ ( β x ) \sigma(\beta x) σ(βx) 趋向于离散的0-1函数,此时Swish函数近似为ReLU函数,因此,Swish函数可以看作是线性函数和ReLU函数之间的非线性插值函数,其程度由参数 β \beta β控制。
Swish函数的优点为:
- Swish函数具有与ReLU函数相似的非线性形式,在一定程度上解决了梯度消失问题;
- Swish函数在实现时简单易用,同时可以使用标准的反向传播算法进行训练,计算速度相对较快;
- Swish函数是可微的,可以应用于很多需要计算梯度的模型中。
Swish函数的缺点为:
- Swish函数在输入值很小时,输出值接近于0,这使得梯度的数值变得非常小,这会影响模型的训练速度和效率;
- Swish函数在输入值很大的时候,可能会发生数值上溢,这会导致计算机无法计算出对应的输出值,因此在实现该函数时需要进行处理;
- Swish函数具有一个额外的超参数 beta,如果不选择合适的 beta,可能会影响模型的效果和训练速度。
4.GELU函数
GELU(Gaussian Error Linear Unit,高斯误差线性单元)也是一种通过门控机制来调整其输出的激活函数,和Swish比较类似。其定义为:
G
E
L
U
(
x
)
=
x
P
(
X
≤
x
)
GELU(x)=xP(X\leq x)
GELU(x)=xP(X≤x)
其中
P
(
X
≤
x
)
P(X\leq x)
P(X≤x) 为高斯分布
N
(
μ
,
σ
2
)
N(\mu,\sigma^2)
N(μ,σ2) ,其中
μ
,
σ
\mu,\sigma
μ,σ 为超参数,一般设
μ
=
0
,
σ
=
1
\mu=0,\sigma=1
μ=0,σ=1。由于高斯分布的累积分布函数为S型函数,因此GELU函数可以用Tanh函数或者Logistic函数来近似。表示为:
G
E
L
U
(
x
)
≈
0.5
x
(
1
+
t
a
n
h
(
2
π
(
x
+
0.044715
x
3
)
)
)
≈
x
σ
(
1.702
x
)
GELU(x)\approx0.5x(1+tanh(\sqrt{\frac{2}{\pi}}(x+0.044715x^3)))\\ \approx x\sigma(1.702x)
GELU(x)≈0.5x(1+tanh(π2(x+0.044715x3)))≈xσ(1.702x)
使用Python绘制GELU函数的实现代码及绘图结果如下:
#GELU
import numpy as np
import matplotlib.pyplot as plt
# 定义GELU函数
def gelu(x):
return 0.5 * x * (1 + np.tanh(np.sqrt(2 / np.pi) * (x + 0.044715 * np.power(x, 3))))
# 生成输入值范围
x = np.linspace(-5, 5, 100)
# 计算GELU函数的输出值
y = gelu(x)
# 绘制GELU数图像
plt.plot(x, y, label='Softplus', color='b')
plt.xlabel('Input')
plt.ylabel('Output')
plt.title('GELU Function')
plt.axhline(0, color='black', linewidth=0.5, linestyle='--')
plt.axvline(0, color='black', linewidth=0.5, linestyle='--')
plt.grid(True)
plt.show()
当使用Logistic函数来近似时,GELU相当于一种特殊的Swish函数。
GELU函数的优点为:
- GELU函数具有平滑的非线性形式,可以避免ReLU函数在输入为负时出现的零梯度问题;
- GELU函数在实践中表现良好,它在一些自然语言处理和图像处理任务中取得了较好的性能;
- GELU函数是可微的,可以使用标准的反向传播算法进行训练,并且对于深度神经网络的训练是有效的。
GELU函数的缺点为:
- GELU函数的计算比ReLU函数更加复杂,包含指数和双曲正切运算,因此在一些实际的应用中,GELU函数的计算成本可能会比较高;
- GELU函数的输出在接近于零的区域附近非常接近线性,这可能导致网络在这个区域内收敛速度较慢;
- GELU函数的性能可能对超参数的选择比较敏感,如果不选择合适的超参数,可能会影响模型的效果和训练速度。