深度学习中主流激活函数的数学原理与PyTorch实现综述
1. 定义与作用
什么是激活函数?激活函数有什么用?
答:激活函数(Activation Function)是一种添加到人工神经网络中的函数,
旨在帮助网络学习数据中的复杂模式。
类似于人类大脑中基于神经元的模型,激活函数最终决定了要发射给下一个神经元的内容。
核心作用:
- 引入非线性能力
在人工神经网络中,其主要作用是对所有的隐藏层和输出层添加一个非线性的操作,使得神经网络的输出更为复杂、表达能力更强。若神经网络中不使用激活函数,无论网络有多少层,其整体仍等价于一个线性模型。激活函数通过引入非线性变换,使网络能够学习和逼近复杂的非线性关系,从而具备强大的表达能力(representation power),这是深度学习能够处理图像、语音、自然语言等复杂任务的基础。
- 控制神经元的激活状态
激活函数对加权输入进行变换,决定当前神经元的输出强度或是否“激活”。例如,ReLU 在输入为负时输出为0,实现了稀疏激活,有助于提升模型效率和泛化能力。
- 影响梯度传播与训练稳定性
绝大多数神经网络采用基于梯度下降的优化方法(如SGD、Adam),因此激活函数通常需要是可微分(或几乎处处可微)的,以便通过反向传播算法计算梯度。然而,某些激活函数(如
Sigmoid 或 Tanh)在输入值过大或过小时会出现梯度饱和现象,导致梯度消失(vanishing gradient),严重影响深层网络的训练效率。
- 促进特征选择与模型表达多样性
不同的激活函数具有不同的数学特性(如输出范围、稀疏性、零中心性、是否可导等),合理选择可提升模型性能。例如:
- Sigmoid:输出范围为 (0,1),适合二分类任务的输出层,用于表示概率;
- Softmax:常用于多分类任务的输出层,将输出转化为类别概率分布;
- ReLU 及其变体(如 Leaky ReLU、ELU、GELU):广泛用于隐藏层,具备良好梯度传播特性,缓解梯度消失问题;
- 线性激活函数(Identity):输出等于输入,常用于回归任务的输出层,不限制输出范围。
激活函数分类:
激活函数能分成两类——饱和激活函数和非饱和激活函数。
- 右饱和:
当x趋向于正无穷时,函数的导数趋近于0,此时称为右饱和
- 左饱和:
当x趋向于负无穷时,函数的导数趋近于0,此时称为左饱和
- 饱和函数和非饱和函数:
一个函数既满足右饱和,又满足左饱和,则称为饱和函数否则称为非饱和函数。
2. 常用激活函数解析
2.1 Sigmoid函数
Sigmoid
函数的定义:f(x)=11+e−xf(x) = \frac{1}{1 + e^{-x}}f(x)=1+e−x1Sigmoid
函数的导数:f′(x)=e−x(1+e−x)2=f(x)(1−f(x))f'(x) = \frac{e^{-x}}{(1 + e^{-x})^2} = f(x)(1 - f(x))f′(x)=(1+e−x)2e−x=f(x)(1−f(x))
优点:
- 其值域为
[0,1]
,非常适合作为模型的输出函数用于输出一个(0,1)
范围内的概率值,可用于将预测概率作为输出的模型,比如用于表示二分类的类别或者用于表示置信度。 Sigmoid
函数的输出范围是0到1。由于输出值限定在0到1,因此它对每个神经元的输出进行了归一化。- 该函数是连续可导的(即可微),可以提供非常平滑的梯度值,防止模型训练过程中出现突变的梯度(即避免跳跃的输出值)。
不足:
- 从其导数的函数图像上可以看到,其导数的最大值只有0.25,而且当x在
[-5,5]
的范围外时其导数值就已经几乎接近于0了。这种情况会导致训练过程中神经元处于一种饱和状态,反向传播时其权重几乎得不到更新,从而使得模型变得难以训练,这种现象被称为梯度消失问题。 - 其输出不是以0为中心而是都大于0的(这会降低权重更新的效率),这样下一层的神经元会得到上一层输出的全正信号作为输入,所以
Sigmoid
激活函数不适合放在神经网络的前面层而一般是放在最后的输出层中使用。 - 需要进行指数运算(计算机运行得较慢),计算量大及计算复杂度高,训练耗时;指数的越大其倒数就越小,容易产生梯度消失。
Pytorch代码:
import torch
import torch.nn as nn# Sigmoid函数
print('*' * 25 + "Sigmoid函数" + "*" * 25)
m = nn.Sigmoid()
input = torch.randn(2)
print("原:", input)
print("结果:", m(input))
print('*' * 50)
2.2 Tanh函数
Tanh
函数的公式: f(x)=ex−e−xex+e−xf(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}}f(x)=ex+e−xex−e−xTanh
函数的导函数: f′(x)=4(ex+e−x)2=1−[f(x)]2f'(x) = \frac{4}{(e^x + e^{-x})^2} = 1 - [f(x)]^2f′(x)=(ex+e−x)24=1−[f(x)]2
优点:
- 在分类任务中,双曲正切函数(Tanh)逐渐取代 Sigmoid 函数作为标准的激活函数,其具有很多神经网络所钟爱的特征。它是完全可微分的,反对称,对称中心在原点。
- 输出是S型曲线,具备打破网络层与网络层之间的线性关系。负输入将被强映射为负,而零输入被映射为接近零;
tanh
的输出间隔为1且值域是以0为中心的[-1,1]
。 - 在一般的二元分类问题中,tanh 函数用于隐藏层,而 sigmoid 函数用于输出层,但这并不是固定的,需要根据特定问题进行调整。
不足:
- 当输入较大或较小时,输出几乎是平滑的并且梯度较小,这不利于权重更新。
- Tanh函数也需要进行指数运算,所以其也会存在计算复杂度高且计算量大的问题。
- 当神经网络的层数增多的时候,由于在进行反向传播的时候,链式求导,多项相乘,函数进入饱和区(导数接近于零的地方)就会逐层传递,这种现象被称为梯度消失。
Pytorch代码:
import torch
import torch.nn as nn# Tanh函数
print('*' * 25 + "Tanh函数" + "*" * 25)
m = nn.Tanh()
input = torch.randn(2)
print("原:", input)
print("结果:", m(input))
print('*' * 50)
2.3 ReLU 函数
ReLU
函数的公式: f(x)={x,x≥00,x<0f(x) = \begin{cases} x, & x \geq 0 \\ 0, & x < 0 \end{cases}f(x)={x,0,x≥0x<0ReLU
函数的导函数: f′(x)={1,x≥00,x<0f'(x) = \begin{cases} 1, & x \geq 0 \\ 0, & x < 0 \end{cases}f′(x)={1,0,x≥0x<0
优点:
ReLU
函数在正输入时是线性的,收敛速度快,计算速度快,同时符合恒等性的特点。当输入为正时,由于导数是1,能够完整传递梯度,不存在梯度消失的问题(梯度饱和问题)。- 计算速度快。
ReLU
函数中只存在线性关系且无论是函数还是其导数都不包含复杂的数学运算,因此它的计算速度比sigmoid
和tanh
更快。 - 当输入大于0时,梯度为1,能够有效避免链式求导法则梯度相乘引起的梯度消失和梯度爆炸;计算成本低。
- 当输入为正的时候,导数不为零,从而允许基于梯度的学习(尽管在
x=0
的时候,导数是未定义的)。当输入为负值的时候,ReLU 的学习速度可能会变得很慢,甚至使神经元直接无效,因为此时输入小于零而梯度为零。
不足:
ReLU
的输入值为负的时候,输出始终为0,其一阶导数也始终为0,这样会导致神经元不能更新参数
,也就是神经元不学习了,这种现象叫做“Dead Neuron”。为了解决ReLU
函数这个缺点,在ReLU
函数的负半区间引入一个泄露(Leaky)值,所以称为Leaky ReLU
函数。- 与
Sigmoid
一样,其输出不是以0为中心的(ReLU
的输出为0
或正数)。 ReLU
在小于0的时候梯度为零,导致了某些神经元永远被抑制,最终造成特征的学习不充分;这是典型的Dead ReLU
问题,所以需要改进随机初始化,避免将过多的负数特征送入ReLU
。
Pytorch代码:
import torch
import torch.nn as nn# Relu函数
print('*' * 25 + "Relu函数" + "*" * 25)
m = nn.ReLU()
input = torch.randn(2)
print("原:", input)
print("结果:", m(input))
print('*' * 50)
2.4 Leaky ReLU函数
Leaky ReLU
函数的公式: f(x)={x,x≥0λx,x<0, λ∈(0,1)f(x) = \begin{cases} x, & x \geq 0 \\ \lambda x, & x < 0 \end{cases},\ \lambda \in (0,1)f(x)={x,λx,x≥0x<0, λ∈(0,1)Leaky ReLU
函数的导函数: f′(x)={1,x≥0λ,x<0, λ∈(0,1)f'(x) = \begin{cases} 1, & x \geq 0 \\ \lambda, & x < 0 \end{cases},\ \lambda \in (0,1)f′(x)={1,λ,x≥0x<0, λ∈(0,1)
Leaky ReLU函数(ReLU
的改进):
-
与ReLU函数相比,Leaky ReLU给负输入添加了一个非常小的线性分量(如0.01x),以缓解负值区域梯度为零的问题;这有助于扩展ReLU函数的适用范围,通常超参数𝜆的值设为0.01左右;其输出范围为负无穷到正无穷。
-
Leaky ReLU激活函数通过在负半轴引入一个较小的正斜率(例如𝜆=0.01),使得负输入时仍有一定梯度,避免信息完全丢失,从而解决ReLU的“神经元死亡”(Dead ReLU)问题。该斜率𝜆为人为设定的超参数,一般取0.01。这样可确保在模型训练过程中,即使输入小于0,神经元的权重仍能被更新。
-
Leaky ReLU不会出现Dead ReLU问题,但在深层网络中仍可能面临梯度爆炸的风险,尤其是在网络较深或初始化不当的情况下。因此,必要时可结合使用具有饱和特性的激活函数(如
Sigmoid
或Tanh
)进行输出层设计,或配合梯度裁剪、权重初始化等技术来提升训练稳定性。
Leaky ReLU不足:
-
Leaky ReLU 是经典且广泛使用的 ReLU 激活函数的一种变体,其特点是对负值输入引入一个很小的非零斜率(例如 0.01)。由于其导数在负半轴不为零,能够有效缓解“神经元静默”(即 Dead ReLU)问题,从而允许梯度在反向传播中持续流动,支持基于梯度的学习,尽管在负区域的学习速度较慢。
-
从理论上讲,Leaky ReLU 保留了 ReLU 的计算高效性和非线性特性,同时避免了神经元因梯度为零而永久失效的问题,因此不会出现 Dead ReLU 现象。然而,在实际应用中,Leaky ReLU 并未被一致证明在所有任务和网络结构中都优于标准 ReLU,其性能提升依赖于具体场景、超参数选择和模型结构,因此是否使用仍需根据实验效果权衡。
Pytorch代码:
import torch
import torch.nn as nn# Leaky Relu函数
print('*' * 25 + "Leaky Relu函数" + "*" * 25)
m = nn.LeakyReLU(negative_slope=0.1) # negative_slope是个常数
input = torch.randn(2)
print("原:", input)
print("结果:", m(input))
print('*' * 50)
2.5 PReLU 函数
PReLU
函数的公式: f(x)={x,x≥0αx,x<0, (α是可训练参数)f(x) = \begin{cases} x, & x \geq 0 \\ \alpha x, & x < 0 \end{cases},\ (\alpha\text{是可训练参数})f(x)={x,αx,x≥0x<0, (α是可训练参数)PReLU
函数的导函数: f′(x)={1,x≥0α,x<0, (α是可训练参数)f'(x) = \begin{cases} 1, & x \geq 0 \\ \alpha, & x < 0 \end{cases},\ (\alpha\text{是可训练参数})f′(x)={1,α,x≥0x<0, (α是可训练参数)
PReLU 函数( ReLU 的改进):
- 在负值域,
PReLU
的斜率较小,这也可以避免Dead ReLU
问题。与ELU相比,PReLU在负值域是线性运算。尽管斜率很小,但不会趋于0。 - 公式与Leaky ReLu相似,但并不完全一样。
𝛼可以是常数,或自适应调整的参数
。也就是说,如果让 a 自适应,那么 PReLu 会在反向传播时更新参数a。 参数α通常为0到1之间的数字,并且通常相对较小
。- (1)如果
𝛼 = 0
,则f(x)变为ReLU
。
(2)如果𝛼 > 0
,则f(x)变为leaky ReLU
。
(3)如果𝛼
是可学习的参数,则f(x)变为PReLU
。
Pytorch代码:
import torch
import torch.nn as nn# PRelu函数
print('*' * 25 + "PRelu函数" + "*" * 25)
m = nn.PReLU(num_parameters=1) # num_parameters是个可训练参数
input = torch.randn(2)
print("原:", input)
print("结果:", m(input))
print('*' * 50)
2.6 RReLU 函数
-
RReLU
函数的公式: f(x)={x,x≥0αx,x<0f(x) = \begin{cases} x, & x \geq 0 \\ \alpha x, & x < 0 \end{cases}f(x)={x,αx,x≥0x<0 -
RReLU
函数的导函数: f′(x)={1,x≥0α,x<0f'(x) = \begin{cases} 1, & x \geq 0 \\ \alpha, & x < 0 \end{cases}f′(x)={1,α,x≥0x<0
RReLU 函数( ReLU 的改进):
- RReLU 和 PReLU 的表达式一样,但 𝛼 参数不一样,这里的
𝛼是个随机震荡的数
,范围(pytorch):1/8~1/3。(对应图的参数为lower =1/8,upper =1/3) - RReLU(随机校正线性单元)。在 RReLU 中,
负部分的斜率在训练中被随机化到给定的范围内,
然后再测试中被固定。
Pytorch代码:
import torch
import torch.nn as nn# RRelu函数
print('*' * 25 + "RRelu函数" + "*" * 25)
m = nn.RReLU(lower=0.1, upper=0.3) # 这里的参数属于(lower,upper)之间的震荡随机数
input = torch.randn(2)
print("原:", input)
print("结果:", m(input))
print('*' * 50)
2.7 ELU函数
ELU
函数的公式: f(x)={x,x≥0a(ex−1),x<0f(x) = \begin{cases} x, & x \geq 0 \\ a(e^x - 1), & x < 0 \end{cases}f(x)={x,a(ex−1),x≥0x<0ELU
函数的导函数: f′(x)={1,x≥0aex,x<0f'(x) = \begin{cases} 1, & x \geq 0 \\ a e^x, & x < 0 \end{cases}f′(x)={1,aex,x≥0x<0
ELU 函数( ReLU 的改进):(上图的 α 值为1.0):
- ELU 在负值时是一个指数函数(使激活的平均值接近零),
具有软饱和特性
,对噪声更鲁棒,抗干扰能力强;在较小的输入下会饱和至负值,从而减少前向传播的变异和信息。 - 均值向零加速学习。
通过减少偏置偏移的影响,使正常梯度接近于单位自然梯度
,可以使学习更快。 - 输出有负值,
使得其输出的平均值为0;右侧的正值特性
,可以像relu一样缓解梯度消失的问题。
ELU 不足:
ELu 也是为了解决 Dead ReLu 而提出的改进型
。计算上稍微比 Leaky ReLu 复杂一点,但从精度看似乎并未提高多少。
- 尽管理论上比 ReLU 要好,但目前在实践中
没有充分的证据表明 ELU 总是比 ReLU 好。
Pytorch代码:
import torch
import torch.nn as nn# ELU函数
print('*' * 25 + "ELU函数" + "*" * 25)
m = nn.ELU(alpha=1.0) # 这里的参数alpha默认为1
input = torch.randn(2)
print("原:", input)
print("结果:", m(input))
print('*' * 50)
2.8 SELU函数
SELU
函数的公式: f(x)={x,x>0α(ex−1),x≤0f(x) = \begin{cases} x, & x > 0 \\ \alpha(e^x - 1), & x \leq 0 \end{cases}f(x)={x,α(ex−1),x>0x≤0SELU
函数的导函数: f′(x)={λ,x>0λαex,x≤0f'(x) = \begin{cases} \lambda, & x > 0 \\ \lambda \alpha e^x, & x \leq 0 \end{cases}f′(x)={λ,λαex,x>0x≤0
SELU函数(ReLU的改进):(上图的𝛼=1.67 and 𝝀=1.05.):
- 当其中参数α=1.67 , λ=1.05时,在网络权重服从正态分布的条件下,
各层输出的分布会向标准正态分布靠拢。
这种自我标准化的特性可以避免梯度消失和爆炸。 - SELU激活函数是在自归一化网络中定义的,
通过调整均值和方差来实现内部的归一化
,这种内部归一化比外部归一化更快,这使得网络收敛得更快。 - SELU是给ELU乘上一个系数,该系数大于1。在这篇paper Self-Normalizing Neural Networks中,作者提到,SELU可以使得输入在经过一定层数之后变为固定的分布。以前的ReLU、P-ReLU、ELU等激活函数都是在负半轴坡度平缓,
这样在激活的方差过大时可以让梯度减小
,防止了梯度爆炸,但是在正半轴其梯度简答的设置为了1。而SELU的正半轴大于1,在方差过小的时候可以让它增大,
但是同时防止了梯度消失。这样激活函数就有了一个不动点,网络深了之后每一层的输出都是均值为0,方差为1。
pytorch代码:
import torch
import torch.nn as nn# SELU函数
print('*' * 25 + "SELU函数" + "*" * 25)
m = nn.SELU() # 这里的参数默认alpha=1.67,scale=1.05
input = torch.randn(2)
print("原:", input)
print("结果:", m(input))
print('*' * 50)
2.9 Softmax函数
Softmax
函数的公式: f(x)=exi∑i=0nexif(x) = \frac{e^{x_i}}{\sum_{i=0}^{n} e^{x_i}}f(x)=∑i=0nexiexi
这里使用梯度无法求导,所以导函数图像是一个 y=0
的直线。
Softmax 激活函数的特点:
- 在零点不可微,负输入的梯度为零,
这意味着对于该区域的激活
,权重不会在反向传播期间更新。 - 将预测结果转化为非负数、预测结果概率之和等于1。
- 经过使用指数形式的
Softmax函数能够将差距大的数值距离拉的更大
。在深度学习中通常使用反向传播求解梯度进而使用梯度下降进行参数更新的过程,而指数函数在求导的时候比较方便。
Softmax不足:
使用指数函数,当输出值非常大的话,计算得到的数值也会变的非常大,数值可能会溢出。
Pytorch代码:
import torch
import torch.nn as nnprint('*' * 25 + "Softmax函数" + "*" * 25)
m = nn.Softmax(dim=0) # 维度为0
input = torch.randn(2)
print("原:", input)
print("结果:", m(input))
print('*' * 50)
2.10 Mish 函数
Mish
函数的公式: f(x)=x⋅tanh(ln(1+ex))f(x) = x \cdot \tanh(\ln(1 + e^x))f(x)=x⋅tanh(ln(1+ex))
Mish 优点:
Mish
激活函数无边界(即正值可以达到任何高度)避免了由于封顶而导致的饱和。理论上对负值的轻微允许允许更好的梯度流
,而不是像 ReLU 中那样的硬零边界。平滑的激活函数允许更好的信息深入神经网络
,从而得到更好的准确性和泛化。
Mish 函数具有的特点:
- 无上界有下界,这样可以保证没有饱和区域,因此在训练过程中不会有梯度消失的问题,这个和relu后面的激活函数一样。
有下限的话能够保证具有一定的regularization effect
,这对于神经网络训练来说是一个很好的特性。 - 非单调性函数,输入较小负数的时候往往梯度回传也会很小,
这样会导致收敛较慢
,但是如果输入较大负数的时候不缩小的话,又容易梯度爆炸。这种性质有助于小的负值,从而稳定网络梯度流。 - 无穷阶连续性和光滑性,Mish函数是光滑函数,
具有较好的泛化能力和结果的有效优化能力
,可以提高结果的质量。
3. 绘制激活函数
import torch
import numpy as np
import matplotlib.pylab as plt
import sys
from matplotlib import pyplot as plt
from IPython import display
from matplotlib import style
from torch.nn.modules.module import Module
from torch.nn import functional as F
from torch import Tensordef xyplot(x_vals, y_vals, name):plt.rcParams['figure.figsize'] = (5, 3.5)plt.grid(c='black', linewidth=0.08)plt.plot(x_vals.detach().numpy(), y_vals.detach().numpy(), label=name, linewidth=1.5)font = {'family': 'SimHei'}; # 中文字体plt.legend(loc='upper left', prop=font)# dark_background, seaborn, ggplot# plt.style.use("seaborn")ax = plt.gca()ax.spines['right'].set_color("none")ax.spines['top'].set_color("none")ax.spines['bottom'].set_position(("data", 0))ax.spines['left'].set_position(("data", 0))ax.spines['bottom'].set_linewidth(0.5)ax.spines['left'].set_linewidth(0.5)ax.xaxis.set_ticks_position('bottom')ax.yaxis.set_ticks_position('left')# 激活函数以及导函数
def func_(y_func, func_n, save_=None, x_low=-6.0, x_top=6.0):x = torch.arange(-6.0, 6.0, 0.1, requires_grad=True)y = y_func(x)xyplot(x, y, '原函数')# 导数y.sum().backward()xyplot(x, x.grad, "导函数")plt.title(f"{func_n}(x)")if save_ is not None:plt.draw()plt.savefig(f"./{func_n}.jpg")plt.close()plt.show()# 由于我使用的pytorch版本没有Mish函数,所以整了一个
class Mish(Module):__constants__ = ['beta', 'threshold']beta: intthreshold: intdef __init__(self, beta: int = 1, threshold: int = 20) -> None:super(Mish, self).__init__()self.beta = betaself.threshold = thresholddef forward(self, input: Tensor) -> Tensor:return input * torch.tanh(F.softplus(input, self.beta, self.threshold))def extra_repr(self) -> str:return 'beta={}, threshold={}'.format(self.beta, self.threshold)#
class Swish(Module):__constants__ = ['beta']beta: intdef __init__(self, beta: int = 1) -> None:super(Swish, self).__init__()self.beta = betadef forward(self, input: Tensor) -> Tensor:return input * torch.sigmoid(input * self.beta)# RReLU
func_(torch.nn.RReLU(), "RReLU", True)
# ELU
func_(torch.nn.ELU(), "ELU", True)
# SELU
func_(torch.nn.SELU(), "SELU", True)
# CELU
func_(torch.nn.CELU(), "CELU", True)
# GELU
func_(torch.nn.GELU(), "GELU", True)
# ReLU6
func_(torch.nn.ReLU6(), "ReLU6", True)
# Swish
func_(Swish(), "Swish", True)
# Hardswish
func_(torch.nn.Hardswish(), "Hardswish", True)
# SiLU
func_(torch.nn.SiLU(), "SiLU", True)
# Softplus
func_(torch.nn.Softplus(), "Softplus", True)
# Mish
func_(Mish(), "Mish", True)
# Softmax
func_(torch.nn.Softmax(), "Softmax", True)
4. 总结
激活函数的发展体现了从“简单有效”到“精细调控梯度流”的演进。现代深度学习中,ReLU及其变体、Swish、Mish、GELU 成为主流选择,而 Sigmoid 和 Tanh 已逐步退出隐藏层舞台。合理选择激活函数,不仅能加速收敛,还能提升模型鲁棒性与最终性能。
关键原则:
- 避免梯度消失/爆炸
- 保持梯度流动稳定
- 平衡表达能力与计算效率
- 匹配任务需求(分类/回归)
掌握这些激活函数的数学形式、特性与适用场景,是构建高效神经网络的基础。
📊 选择建议总结
场景 | 推荐激活函数 |
---|---|
隐藏层通用选择 | ReLU、Swish、Mish、GELU |
深层网络防梯度消失 | ELU、SELU、Swish、Mish |
需要光滑/可微性 | Softplus、Mish、GELU |
自归一化网络 | SELU + Alpha Dropout |
二分类输出层 | Sigmoid |
多分类输出层 | Softmax |
回归输出层 | Identity |
轻量化模型 | Hard-Sigmoid、Hard-Swish |