1. 介绍
每个激活函数的输入都是一个数字,然后对其进行某种固定的数学操作。激活函数给神经元引入了非线性因素,如果不用激活函数的话,无论神经网络有多少层,输出都是输入的线性组合。激活函数的意义在于它能够引入非线性特性,使得神经网络可以拟合非常复杂的函数,从而提高了神经网络的表达能力和预测性能。
激活函数的发展经历了Sigmoid -> Tanh -> ReLU -> Leaky ReLU -> Maxout这样的过程,还有一个特殊的激活函数Softmax,因为它只会被用在网络中的最后一层,用来进行最后的分类和归一化。
具体来说,激活函数的作用有以下几个方面:
-
引入非线性特性:激活函数能够将神经元的输入信号转换为输出信号,从而引入非线性特性,使得神经网络可以拟合非常复杂的函数。
-
压缩输出范围:激活函数能够将神经元的输出范围压缩到一定的范围内,这有助于防止神经元输出的值过大或过小,从而提高了神经网络的稳定性和泛化性能。
-
增加网络深度:激活函数能够增加神经网络的深度,从而提高了神经网络的表达能力和预测性能。
-
改善梯度消失问题:激活函数能够改善神经网络中的梯度消失问题,从而提高了神经网络的训练效率和收敛速度。
2. 各种激活函数
2.1 sigmoid函数
Sigmoid函数,又称为S型函数或 Logistic函数,是一种常用的激活函数,在机器学习和深度学习中尤其常见,特别是在二元分类问题中。Sigmoid函数的形式定义如下:
f ( x ) = 1 1 + e − x f(x) = \frac{1}{1+e^{-x}} f(x)=1+e−x1
import numpy as np
def sigmiod(x):
return 1. / (1. + np.exp(-x))
这个函数将任意实数输入
x
x
x映射到(0, 1)之间的输出值,因此非常适合用于建模概率输出,即输出可以被解释为事件发生的概率。Sigmoid函数的图形呈现出"S"形,两端平缓,中间陡峭,具体特点包括:
- 连续且光滑:Sigmoid函数在整个实数域上都是连续且可导的,这使得它在求梯度时非常方便,适合用于梯度下降等优化算法。
- 边界限定:输出范围限定在(0, 1)之间,这使得它非常适合用于二元分类问题,其中输出值可以被视为正类别的概率。
- 饱和性:当输入 x x x的值很大或很小时,Sigmoid函数的导数(即斜率)会接近于0,这称为饱和。这意味着在输入值远离0时,函数变得非常平缓,梯度很小,可能导致训练过程中的梯度消失问题,尤其是在深层神经网络中。
- 中心对称性:Sigmoid函数不是严格意义上的中心对称,但其关于点 x = 0 x=0 x=0有某种形式的对称性,使得输入值为0时函数输出0.5,这可以作为分类的阈值。
2.2 Tanh
Tanh函数是一种具有S形状的激活函数,其特点是将输入值映射到-1到1之间的连续范围内,输出值也具有良好的可解释性。Tanh解决了Sigmoid的输出是不是零中心的问题,Tanh函数在某些情况下可以表现出色,但是它也存在梯度消失和输出饱和等问题,因此在深度神经网络中使用并不广泛。
Tanh(双曲正切)函数是一种常用的激活函数,其数学定义为
f
(
x
)
=
e
x
−
e
−
x
e
x
+
e
−
x
f(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}}
f(x)=ex+e−xex−e−x
它的输出范围是 (-1, 1),在输入接近正无穷时趋于1,在接近负无穷时趋于-1,在接近0时趋于0。在神经网络中,Tanh函数通常用于隐藏层的激活函数。
import numpy as np
def tanh(x):
return np.tanh(x)
tanh 函数的特点包括:
- 输出范围为 ( -1, 1 ):tanh 函数将任何实数输入映射到区间 ( -1, 1 ) 内。
- 中心化:tanh 函数的输出以原点为中心,即在 ( x = 0 ) 处取得零值,这有助于网络的收敛速度。
- 平滑连续:tanh 函数在整个定义域上是平滑且连续的,因此在优化算法中通常不会出现梯度突变的情况。
- 易于求导:tanh 函数的导数可以用函数自身来表示,简化了梯度计算的过程。
在神经网络中,tanh 函数通常用作隐藏层的激活函数,因为它能够将输入的线性组合映射到比 sigmoid 函数更大的范围,有助于网络的表达能力。
2.3 ReLU函数
ReLU(Rectified Linear Unit)函数是一种常用的激活函数,其数学表达式为:
ReLU ( x ) = max ( 0 , x ) \text{ReLU}(x) = \max(0, x) ReLU(x)=max(0,x)
即当输入 ( x ) 大于等于零时,输出为 ( x ),否则输出为零。
ReLU 函数的特点包括:
- 简单非线性:ReLU 函数是简单的非线性函数,计算效率高,因为它只是简单地将负值裁剪为零。
- 梯度稀疏性:在负值区间,ReLU 函数的梯度为零,这可能会导致所谓的“神经元死亡”问题,即某些神经元永远不会被激活。
- 非中心化:ReLU 函数在原点附近不对称,输出值的均值偏向于正值。
- 稀疏激活性:由于 ReLU 在负数区域的输出为零,因此它具有稀疏激活性,即只有部分神经元会被激活,这有助于减少过拟合。
在深度学习中,ReLU 函数通常用作隐藏层的激活函数,因为它能够加速网络的训练速度,并且通常表现出良好的性能。
import numpy as np
def ReLU(x):
return np.maxinum(0, x)
ReLU函数是当前最常用的激活函数之一,它的特点是简单、快速,并且在许多情况下表现出色。ReLU函数将负数输入映射到0,将正数输入保留不变,因此在训练过程中可以避免梯度消失的问题。
但是ReLU函数在输入为负数时输出为0,这可能导致神经元死亡,ReLU单元比较脆弱并且可能“死掉”,而且是不可逆的,因此导致了数据多样化的丢失。通过合理设置学习率,会降低神经元“死掉”的概率。因此后续的改进版本LeakyReLU得到了广泛的应用。
2.4 LeakyReLU函数
LeakyReLU(Leaky Rectified Linear Unit)函数是一种改进的激活函数,它在**负数区域具有小的斜率,以解决普通 ReLU 函数可能导致的神经元死亡问题。**其数学表达式为:
LeakyReLU
(
x
)
=
{
x
if
x
≥
0
α
x
if
x
<
0
\text{LeakyReLU}(x) = \begin{cases} x & \text{if } x \geq 0 \\ \alpha x & \text{if } x < 0 \end{cases}
LeakyReLU(x)={xαxif x≥0if x<0
其中, α \alpha α 是一个小的正数,通常很接近零,它决定了负数区域的斜率。
def LeakyReLU(x, alpha=0.01):
return np.maxinum(alpha*x, x)
def LeakyReLU(x, alpha=0.01):
return x if x > 0 else alpha * x
LeakyReLU函数是ReLU函数的改进版本,它在输入为负数时输出一个小的负数,从而避免了ReLU函数可能导致神经元死亡的问题。
LeakyReLU函数的优点是简单、快速,并且在许多情况下表现出色,但是其超参数需要手动调整,因此在实际应用中需要进行一定的调试。
LeakyReLU 函数的特点包括:
- 具有小的负斜率:在负数区域,LeakyReLU 函数具有一个小的斜率 α \alpha α,使得负数区域不再是完全的零,而是有一定的输出。
- 解决了神经元死亡问题:LeakyReLU 可以一定程度上解决普通 ReLU 函数可能导致的神经元死亡问题,即在训练过程中,部分神经元可能永远不会被激活的问题。
- 非中心化:与普通的 ReLU 类似,LeakyReLU 函数在原点附近不对称,输出值的均值偏向于正值。
在深度学习中,LeakyReLU 函数通常作为隐藏层的激活函数,尤其在遇到神经元死亡问题时,LeakyReLU 可以是一个很好的选择。
2.5 Maxout
Maxout 是一种激活函数,与传统的激活函数不同,它并不是基于元素级的运算,而是基于神经元的组合运算。Maxout 激活函数的基本思想是在每个神经元的输入中选择最大的值作为激活输出。
Maxout 函数的数学表达式如下:
f
(
x
)
=
m
a
x
(
w
1
T
x
+
b
1
,
w
2
T
x
+
b
2
)
f(x) = max(w^{T}_{1}x+b_1, w^{T}_{2}x+b_2)
f(x)=max(w1Tx+b1,w2Tx+b2)
其中, x x x是输入向量, w 1 w_{1} w1 和 w 2 w_{2} w2 是权重向量, b 1 b_{1} b1 和 b 2 b_{2} b2 是偏置项。Maxout 函数的输出取决于两个线性组合中的最大值。
import numpy as np
def maxout(x, weights, biases):
output1 = np.dot(x, weights[0] + biases[0])
output2 = np.dot(x, weights[1]+ biases[1])
return np.maximum(output1, output2)
x = np.array([1.0, 2.0, 3.0])
weights = [np.array([0.5, 0.3, 0.2]), np.array([0.4, 0.5, 0.6])]
biases = [0.1, 0.2]
print(maxout(x, weights, biases))
Maxout 激活函数的特点包括:
- 非线性:Maxout 激活函数是一种非线性的激活函数,它能够学习到更加复杂的函数关系,增强了神经网络的表达能力。
- 自适应性:Maxout 激活函数可以自适应地学习不同的激活模式,而不受到预先固定的激活函数形式的限制。
- 通用性:Maxout 激活函数可以用于各种不同类型的神经网络结构,包括全连接网络、卷积神经网络等。
虽然 Maxout 激活函数在某些任务中表现出色,但由于其参数量较大,每个神经元的参数double,这就导致整体参数的数量激增,训练速度较慢,并且需要更多的数据来训练,因此在实际应用中并不常见,更常用的是 ReLU、LeakyReLU 等激活函数。
2.6 Softmax函数
Softmax 函数是一种常用的激活函数,通常用于多类别分类问题中,将模型的原始输出转换为表示概率分布的形式。Softmax 函数能够将输入转换为介于 0 和 1 之间的概率值,并且保证所有类别的概率之和为 1。
Softmax 函数的数学表达式如下:
对于输入向量 z = ( z 1 , z 2 , . . . , z k ) z = (z_1, z_2, ..., z_k) z=(z1,z2,...,zk),Softmax 函数的输出 σ ( z ) \sigma(z) σ(z) 的第 i i i 个分量为:
σ ( z ) i = e z i ∑ j = 1 k e z j \sigma(z)_i = \frac{e^{z_i}}{\sum_{j=1}^{k}e^{z_j}} σ(z)i=∑j=1kezjezi
其中, e e e 是自然常数(约等于2.71828), k k k 是类别的总数。
Softmax 函数的特点包括:
- 将原始分数转换为概率值:Softmax 函数将模型的原始输出转换为表示概率分布的形式,可以理解为对每个类别的置信度的归一化。
- 输出的概率之和为 1:Softmax 函数保证了所有类别的概率之和为 1,因此可以直接用于多类别分类问题。
- 平滑连续:Softmax 函数在整个定义域上是平滑且连续的,因此在优化算法中通常不会出现梯度突变的情况。
Softmax 函数在深度学习中广泛应用于多类别分类任务,如图像分类、语言模型等。
import numpy as np
def Softmax(x):
exp_x = np.exp(x)
return exp_x / np.sum(exp_x, axis=0, keepdims=True)
# 测试
x = np.array([1.0, 2.0, 3.0])
print(Softmax(x)) # 输出 [0.09003057 0.24472847 0.66524096]
import torch
def Softmax(x):
exp_x = torch.exp(x)
return exp_x / torch.sum(exp_x, dim=0, keepdim=True)
# 测试
x = torch.tensor([1.0, 2.0, 3.0])
print(Softmax(x)) # 输出 [0.09003057 0.24472847 0.66524096]
Softmax函数是一种常用于多分类问题的激活函数,它将输入值映射到0到1之间的概率分布,可以将神经网络的输出转换为各个类别的概率值。Softmax函数的优点是简单、易于理解,并且在多分类问题中表现出色,但是它也存在梯度消失和输出饱和等问题。
2.7 GELU函数
GELU(Gaussian Error Linear Unit)函数是一种常用的激活函数,它在近年来被广泛应用于深度学习模型中。GELU 函数的定义如下:
GELU
(
x
)
=
x
⋅
Φ
(
x
)
\text{GELU}(x) = x \cdot \Phi(x)
GELU(x)=x⋅Φ(x)
其中, Φ ( x ) \Phi(x) Φ(x) 是标准正态分布的累积分布函数,即:
Φ ( x ) = 1 2 ( 1 + erf ( x 2 ) ) \Phi(x) = \frac{1}{2} (1+\text{erf}(\frac{x}{\sqrt{2}})) Φ(x)=21(1+erf(2x))
其中,erf 表示误差函数。
使用ReLU激活的FFN
Transformer模型通过多头注意力层和FFN层交替工作。FFN层存在于Transformer架构的编码器和解码器部分中。例如,下方的编码器块由多头注意力层和一个FFN层组成。
使用GELU激活的FFN
GELU 函数的特点包括:
- 平滑且连续:GELU 函数是平滑且连续的函数,具有良好的数学性质,在优化算法中通常不会出现梯度突变的情况。
- 近似于 ReLU:在输入较大的情况下,GELU 函数近似于恒等函数,与 ReLU 函数类似,具有线性的特性;而在输入较小的情况下,GELU 函数的形状更接近于 S 型函数,可以缓解梯度消失问题。
- 收敛性:GELU 函数在模型训练过程中通常能够快速收敛,有助于提高训练速度和模型性能。
GELU 函数在很多深度学习模型中被广泛应用,尤其是在自然语言处理(NLP)和计算机视觉(CV)任务中。
import numpy as np
from scipy.special import erf
def gelu(x):
return 0.5 * x * (1 + erf(x / np.sqrt(2)))
# 示例用法
x = np.array([-1.0, 0.0, 1.0])
print(gelu(x))
GELU函数是一种近年来提出的激活函数,它的特点是在ReLU函数的基础上引入了高斯误差线性单元,从而在某些情况下能够表现出色。GELU函数具有平滑的非线性特性,可以避免ReLU函数可能导致的神经元死亡问题。
2.8 SiLU
SiLU(Sigmoid Linear Unit)是一种激活函数,也称为 Sigmoid 激活函数或 Sigmoid 激活器。它的数学表达式如下:
Swish ( x ) = x ⋅ σ ( x ) \text{Swish}(x) = x \cdot \sigma(x) Swish(x)=x⋅σ(x)
其中, σ ( x ) \sigma(x) σ(x) 是 Sigmoid 函数,定义为:
σ ( x ) = 1 1 + e − x \sigma(x) = \frac{1}{1 + e^{-x}} σ(x)=1+e−x1
SiLU 函数的特点是在输入为负数时,逐渐趋近于 0;在输入为正数时,逐渐增加,但增加的速率逐渐减小。与 Swish 函数类似,SiLU 函数在一定程度上保留了线性变换的特性,在输入为正数时具有接近线性的行为,这有助于加速梯度传播和模型训练。
SiLU 函数在神经网络中被广泛应用,特别是在自然语言处理(NLP)和计算机视觉(CV)任务中,常作为隐藏层激活函数使用。相比于一些传统的激活函数如 Sigmoid 函数和 Tanh 函数,SiLU 函数能够提供更好的梯度传播和更快的收敛速度,从而提高了神经网络模型的训练效率和性能。
2.9 SwiGLU
SwiGLU 是 Swish 函数和 GELU 函数的混合,是一种激活函数,常用于神经网络的隐藏层。它将 Swish 函数和 GELU 函数的输出相加,以获得更好的性能和泛化能力。下面详细介绍一下 Swish 函数和 GELU 函数,然后再说明 SwiGLU 函数的特点。
Swish 函数:
Swish 函数是由 Google Brain 团队于 2017 年提出的一种激活函数。它的数学表达式为:
Swish ( x ) = x ⋅ σ ( β x ) \text{Swish}(x) = x \cdot \sigma(\beta x) Swish(x)=x⋅σ(βx)
其中, σ ( x ) \sigma(x) σ(x) 是 Sigmoid 函数,定义为:
σ ( x ) = 1 1 + e − x \sigma(x) = \frac{1}{1 + e^{-x}} σ(x)=1+e−x1
Swish 函数的特点是在输入为负数时,逐渐趋近于 0;在输入为正数时,接近于线性变换。相比于 ReLU 函数,在一些情况下,Swish 函数能够提供更好的梯度传播,从而加速模型的训练。
GELU 函数:
GELU(Gaussian Error Linear Unit)函数是另一种激活函数,它在 2016 年被提出。它的数学表达式为:
GELU
(
x
)
=
x
⋅
Φ
(
x
)
\text{GELU}(x) = x \cdot \Phi(x)
GELU(x)=x⋅Φ(x)
其中, Φ ( x ) \Phi(x) Φ(x) 是标准正态分布的累积分布函数,即:
Φ ( x ) = 1 2 ( 1 + erf ( x 2 ) ) \Phi(x) = \frac{1}{2} (1+\text{erf}(\frac{x}{\sqrt{2}})) Φ(x)=21(1+erf(2x))
GELU 函数的特点是与 Swish 函数类似,在输入为负数时,逐渐趋近于 0;在输入为正数时,接近于线性变换。与 Swish 函数相比,GELU 函数具有更复杂的数学形式,因此有时候会导致更慢的计算速度。
SwiGLU 函数:
SwiGLU 函数将 Swish 函数和 GELU 函数的输出相加,其数学表达式为:
SwiGLU x = Swish ( x ) + GELU ( x ) \text{SwiGLU}{x} = \text{Swish}(x) + \text{GELU}(x) SwiGLUx=Swish(x)+GELU(x)
SwiGLU 函数的特点是综合了 Swish 函数和 GELU 函数的优点,能够在一定程度上提高模型的性能和泛化能力。在实际应用中,SwiGLU 函数通常作为神经网络的隐藏层激活函数使用,能够有效地提高模型的表达能力和训练效果。
import numpy as np
from scipy.special import erf
def swish(x):
return x * (1 / (1 + np.exp(-x)))
def gelu(x):
return 0.5 * x * (1 + erf(x / np.sqrt(2)))
def swiglu(x):
return swish(x) + gelu(x)
# 示例用法
x = np.array([-1.0, 0.0, 1.0])
print(swiglu(x))
GLU及其变体
class FeedForward(nn.Module):
def __init__(self, dim: int, hidden_dim: int, multiple_of: int, dropout: float):
super().__init__()
hidden_dim = multiple_of * ((2 * hidden_dim // 3 + multiple_of - 1) // multiple_of)
self.w1 = nn.Linear(dim, hidden_dim)
self.w2 = nn.Linear(hidden_dim, dim)
self.w3 = nn.Linear(dim, hidden_dim)
self.dropout = nn.Dropout(dropout)
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.dropout(self.w2(F.silu(self.w1(x)) * self.w3(x)))
为什么这里使用的是silu激活函数? 因为SiLU其实就是
β
\beta
β为1时的Swish激活函数:
f
(
x
)
=
x
⋅
σ
(
x
)
f(x) = x \cdot \sigma(x)
f(x)=x⋅σ(x)
3. 性能测试
我们采用控制变量法进行激活函数的推理速度测试,x为输入,范围为-1到1之间的十万个数据,运行次数为100计算激活函数的计算耗时。
参考:
https://zhuanlan.zhihu.com/p/32610035
https://zhuanlan.zhihu.com/p/650237644
https://mp.weixin.qq.com/s/8pZ1IH_WoFG-QCjOztdc5Q