神经网络常见的 40 多种激活函数:应用场景、数学公式、代码实现与函数图象
1. 什么是激活函数
激活函数,属于神经网络中的概念。
激活函数,就像神经元的开关,决定了输入信号能否被传递,以及以什么形式传递。
为应对不同的场景,激活函数不断发展出了各种实现。它们存在的意义,就是为信号传递赋予不同种类的「非线性」特征,从而让神经网络能够表达更为丰富的含义。
本文旨在梳理常见的 40 多种激活函数(也包含少量经典的输出层函数)。
2. 说明
本文将简要介绍激活函数的概念和使用场景,并列出其数学公式,然后基于 Python 进行可视化实现。最后一节则以表格的形式,从多个维度对比了其中最为经典的 20 多个激活函数,以期为读者提供选型参考。
本文所有代码实现均基于 Jupyter Notebook,感兴趣的读者可以后台留言获取完整 ipynb 文件。
为使得各激活函数的代码实现更为简洁,首先做一些初始化操作,如导入对应 Python 库、定义对应的绘图函数等,如下:
# -*- coding: utf-8 -*-
# 导入必要的库
import numpy as np
import matplotlib.pyplot as plt
from scipy.special import expit as sigmoid # scipy 的 sigmoid
import warnings
warnings.filterwarnings("ignore")
# 设置中文字体和图形样式
plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
plt.style.use('seaborn-v0_8') # 使用美观样式
# 定义输入范围
x = np.linspace(-10, 10, 1000)
# 定义画图函数(单张图)
def plot_activation(func, grad_func, name):
y = func(x)
dy = grad_func(x)
plt.figure(figsize=(8, 5))
plt.plot(x, y, label=name, linewidth=1.5)
plt.plot(x, dy, label=f"{name}'s derivative", linestyle='--', linewidth=1.5)
plt.title(f'{name} Function and Its Derivative')
plt.legend()
plt.grid(True)
plt.axhline(0, color='black', linewidth=0.5)
plt.axvline(0, color='black', linewidth=0.5)
plt.show()
# 定义画图函数(多张图,用于对比不同参数的效果)
def plot_activations(functions, x):
plt.figure(figsize=(10, 7))
for func, grad_func, name in functions:
y = func(x)
dy = grad_func(x)
plt.plot(x, y, label=name, linewidth=1.5)
plt.plot(x, dy, label=f"{name}'s derivative", linestyle='--', linewidth=1.5)
plt.title('Activation Functions and Their Derivatives')
plt.legend()
plt.grid(True)
plt.axhline(0, color='black', linewidth=0.5)
plt.axvline(0, color='black', linewidth=0.5)
plt.show()
接下来,让我们开始吧!
3. 经典激活函数
3.1. Sigmoid
适用于二分类问题的输出层,将输出压缩到 (0,1) 区间表示概率。不推荐用于隐藏层,因易导致梯度消失。
公式
$$
\sigma(x) = \frac{1}{1 + e^{-x}}
$$
实现
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def sigmoid_grad(x):
s = sigmoid(x)
return s * (1 - s)
plot_activation(sigmoid, sigmoid_grad, 'Sigmoid')
图像
3.2. Tanh(双曲正切)
Tanh 输出零中心化,使梯度更新方向更均衡,收敛更快,是一种比 Sigmoid 更优的激活函数,适合隐藏层使用,尤其在 RNN 中仍有应用。但它仍可能梯度消失。
公式
$$
\tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}}
$$
实现
def tanh(x):
return np.tanh(x)
def tanh_grad(x):
return 1 - np.tanh(x)**2
plot_activation(tanh, tanh_grad, 'Tanh')
图像
3.3. Linear
主要用于回归任务的输出层,保持输出为原始实数,不进行非线性变换。
不适合用在隐藏层(否则整个网络等价于单层线性模型,无法学习非线性特征)。
在某些特定模型(如自编码器的中间层或策略网络)中也可能使用。
公式
$$
f(x) = x
$$
实现
def linear(x):
return x
def linear_grad(x):
return np.ones_like(x)
plot_activation(linear, linear_grad, 'Linear')
图像
3.4. Softmax
多分类问题的输出层标准激活函数,将输出转化为概率分布。不用于隐藏层。
公式
$$
\text{Softmax}(x_i) = \frac{e^{x_i}}{\sum_j e^{x_j}}
$$
实现
from mpl_toolkits.mplot3d import Axes3D
def softmax(x):
exp_x = np.exp(x - np.max(x, axis=0, keepdims=True)) # 数值稳定
return exp_x / np.sum(exp_x, axis=0, keepdims=True)
def softmax_grad(x):
s = softmax(x).reshape(-1, 1)
return np.diagflat(s) - np.dot(s, s.T) # Jacobian矩阵
# 生成输入数据(二维,便于可视化)
x = np.linspace(-10, 10, 100)
y = np.linspace(-10, 10, 100)
X, Y = np.meshgrid(x, y)
inputs = np.vstack([X.ravel(), Y.ravel()]).T
# 计算Softmax输出(取第一个维度作为输出值,因为Softmax输出是概率分布)
outputs = np.array([softmax(p)[0] for p in inputs]).reshape(X.shape)
# 计算梯度(取Jacobian矩阵的第一个对角线元素)
gradients = np.array([softmax_grad(p)[0, 0] for p in inputs]).reshape(X.shape)
# 绘制Softmax函数
fig = plt.figure(figsize=(12, 5))
# 1. Softmax函数图像
ax1 = fig.add_subplot(121, projection='3d')
ax1.plot_surface(X, Y, outputs, cmap='viridis', alpha=0.8)
ax1.set_title('Softmax (First Output Dimension)')
ax1.set_xlabel('x1')
ax1.set_ylabel('x2')
ax1.set_zlabel('P(x1)')
# 2. Softmax梯度图像
ax2 = fig.add_subplot(122, projection='3d')
ax2.plot_surface(X, Y, gradients, cmap='plasma', alpha=0.8)
ax2.set_title('Gradient of Softmax (∂P(x1)/∂x1)')
ax2.set_xlabel('x1')
ax2.set_ylabel('x2')
ax2.set_zlabel('Gradient')
plt.tight_layout()
plt.show()
图像
4. ReLU 函数及其变体
4.1. ReLU(Rectified Linear Unit)
中文名称是线性整流函数,是在神经网络中常用的激活函数。通常意义下,其指代数学中的斜坡函数。
公式
$$
\text{ReLU}(x) = \max(0, x)
$$
实现
def relu(x):
return np.maximum(0, x)
def relu_grad(x):
return (x > 0).astype(float)
plot_activation(relu, relu_grad, 'ReLU')
图像
4.2. ReLU6
ReLU6 是 ReLU 的有界版本,输出限制在 [0, 6] 区间。
主要用于移动端和轻量级网络(如 MobileNet、EfficientNet 的早期版本),其有界性有助于提升低精度推理(如量化)时的稳定性。
也常见于强化学习(如 DQN)中,用于限制输出范围,防止训练波动。
公式
$$
\text{ReLU6}(x) = \min(\max(0, x), 6)
$$
实现
def relu6(x):
return np.minimum(np.maximum(0, x), 6)
def relu6_grad(x):
dx = np.zeros_like(x)
dx[(x > 0) & (x < 6)] = 1
return dx
plot_activation(relu6, relu6_grad, 'ReLU6')
图像
4.3. Leaky ReLU
Leaky ReLU 是对传统 ReLU 的改进,它试图解决「死亡 ReLU」问题,即某些神经元可能永远不会再激活的问题。
公式
$$
\text{LeakyReLU}(x) = \begin{cases}
x & \text{if } x > 0 \
\alpha x & \text{if } x \leq 0
\end{cases}
$$
通常固定取 $\alpha = 0.01$。
实现
def leaky_relu(x, alpha=0.01):
return np.where(x > 0, x, x * alpha)
def leaky_relu_grad(x, alpha=0.01):
dx = np.ones_like(x)
dx[x < 0] = alpha
return dx
plot_activation(leaky_relu, leaky_relu_grad, 'Leaky ReLU')
图像
4.4. PReLU(Parametric ReLU)
上一节的 Leaky ReLU 是「固定小斜率」,而 PReLU 将该斜率变为可学习参数,表达能力更强。
公式
$$
\text{PReLU}(x) = \begin{cases}
x & \text{if } x > 0 \
\alpha x & \text{if } x \leq 0
\end{cases}
$$
其中 $\alpha$ 是可学习参数。
实现
def prelu(x, alpha=0.25):
return np.where(x > 0, x, alpha * x)
def prelu_grad(x, alpha=0.25):
return np.where(x > 0, 1, alpha)
functions_to_plot = [
(lambda x: prelu(x, 0.1), lambda x: prelu_grad(x, 0.1), 'PReLU α=0.1'),
(lambda x: prelu(x, 0.25), lambda x: prelu_grad(x, 0.25), 'PReLU α=0.25'),
(lambda x: prelu(x, 0.5), lambda x: prelu_grad(x, 0.5), 'PReLU α=0.5')]
plot_activations(functions_to_plot, x)
图像
4.5. RReLU(Randomized ReLU)
RReLU 是一种在训练时使用随机斜率的变体 ReLU 激活函数,而在测试时则采用固定的斜率。其主要目的是为了减少过拟合并解决「死亡 ReLU」问题。
由于 RReLU 在训练时使用的是一个区间内的随机值,而测试时使用的是固定值。为了简化起见,这里使用一个确定性的斜率(例如训练过程中使用的平均斜率)。
以下代码实现了 RReLU 函数及其导数,并使用了一个介于 lower 和 upper 之间的固定斜率来代替随机选择的过程,以便进行可视化。
在实际应用中,对于每个负输入值,斜率会在给定范围内随机选择,但在测试或推理阶段,通常会使用所有可能斜率的平均值。
实现
def rrelu(x, lower=1/8., upper=1/3.):
# 在实际应用中,这里的a应该是在[lower, upper]之间随机选取的
# 但为了绘图方便,我们取平均值作为固定的a
a = (lower + upper) / 2
return np.where(x >= 0, x, a * x)
def rrelu_grad(x, lower=1/8., upper=1/3.):
a = (lower + upper) / 2
dx = np.ones_like(x)
dx[x < 0] = a
return dx
plot_activation(lambda x: rrelu(x), lambda x: rrelu_grad(x), 'RReLU')
图像
4.6. ELU(Exponential Linear Unit)
ELU 旨在解决传统激活函数在深度神经网络中可能遇到的一些问题,例如梯度消失和「死亡神经元」问题。
它能产生负值输出,使激活均值接近零,加速收敛。适合深层网络,训练稳定性优于 ReLU,但计算稍慢。
公式
$$
\text{ELU}(x) = \begin{cases}
x & \text{if } x > 0 \
\alpha (e^x - 1) & \text{if } x \leq 0
\end{cases}
$$
实现
def elu(x, alpha=1.0):
return np.where(x > 0, x, alpha * (np.exp(x) - 1))
def elu_grad(x, alpha=1.0):
return np.where(x > 0, 1, elu(x, alpha) + alpha)
functions_to_plot = [
(lambda x: elu(x, 0.1), lambda x: elu_grad(x, 0.1), 'ELU α=0.1'),
(lambda x: elu(x, 0.25), lambda x: elu_grad(x, 0.25), 'ELU α=0.25'),
(lambda x: elu(x, 0.5), lambda x: elu_grad(x, 0.5), 'ELU α=0.5'),
(lambda x: elu(x, 1), lambda x: elu_grad(x,1), 'ELU α=1'),
(lambda x: elu(x, 2), lambda x: elu_grad(x,2), 'ELU α=2')]
plot_activations(functions_to_plot, x)
图像
4.7. SELU(Scaled Exponential Linear Units)
SELU 是一种自归一化激活函数,它能够使得神经网络的输出在一定条件下自动趋近于零均值和单位方差,从而有助于加速训练过程,并且有可能提高模型的性能。
SELU 激活函数是由 Günter Klambauer 等人在 2017 年的论文《Self-Normalizing Neural Networks》中提出的。
公式
$$
\text{SELU}(x) = \lambda \begin{cases}
x & \text{if } x > 0 \
\alpha (e^x - 1) & \text{if } x \leq 0
\end{cases}
$$
其中 $\lambda \approx 1.0507$ 和 $\alpha \approx 1.6733$ 是固定常数。
实现
# SELU 参数(论文中推荐值)
lambda_s = 1.0507009873554804934193349852946
alpha_s = 1.673261549988240216825385979984
def selu(x, lambda_=1.0507009873554804934193349852946, alpha=1.673261549988240216825385979984):
return lambda_ * np.where(x > 0, x, alpha * (np.exp(x) - 1))
def selu_grad(x, lambda_=1.0507009873554804934193349852946, alpha=1.673261549988240216825385979984):
return lambda_ * np.where(x > 0, 1, alpha * np.exp(x))
# 调用plot_activation绘制SELU及其导数
plot_activation(lambda x: selu(x), lambda x: selu_grad(x), 'SELU')
图像
4.8. CELU(Continuously Differentiable Exponential Linear Unit)
CELU 是 ELU 的改进版本,保证了在 x = 0 处连续可导(平滑性优于 ELU),有助于优化稳定性。
与 ELU 类似,能产生负值激活,促进神经元平均输出接近零,适合深层网络训练。在某些对梯度平滑性要求较高的任务中可作为 ReLU、ELU 的替代选择,但计算成本略高。
公式
$$
\text{CELU}(x) = \begin{cases}
x & \text{if } x > 0 \
\alpha (e^{x/\alpha} - 1) & \text{if } x \leq 0
\end{cases}
$$
实现
def celu(x, alpha=1.0):
return np.where(x > 0, x, alpha * (np.exp(x / alpha) - 1))
def celu_grad(x, alpha=1.0):
dx = np.ones_like(x)
dx[x <= 0] = np.exp(x[x <= 0] / alpha)
return dx
plot_activation(celu, celu_grad, "CELU")
图像
4.9. GELU(Gaussian Error Linear Unit,高斯误差线性单元)
GELU 是 Transformer 等现代架构(如 BERT)的标准激活函数,平滑且非单调,在 NLP 和大模型中广泛使用。它的性能优于 ReLU,逐渐成为 ReLU 的替代选择。
GELU 是由 Dan Hendrycks 和 Kevin Gimpel 在 2016 年的论文《Gaussian Error Linear Units (GELUs)》中提出。
公式
$$
\text{GELU}(x) = x \cdot \Phi(x) = x \cdot \frac{1}{2}[1 + \text{erf}(x/\sqrt{2})]
$$
其中 $\Phi(x)$ 是标准正态分布的累积分布函数。
可以用双曲正切函数(tanh)近似表示,常见形式为:
$$
\text{GELU}(x) \approx 0.5x \left[1 + \tanh\left(\sqrt{2/\pi} \left(x + 0.044715x^3\right)\right)\right]
$$
实现
def gelu(x):
return 0.5 * x * (1 + np.tanh(np.sqrt(2 / np.pi) * (x + 0.044715 * np.power(x, 3))))
def gelu_grad(x):
# 导数计算较为复杂,这里简化处理
return 0.5 * (1 + np.tanh(np.sqrt(2 / np.pi) * (x + 0.044715 * np.power(x, 3)))) + \
0.5 * x * (1 - np.tanh(np.sqrt(2 / np.pi) * (x + 0.044715 * np.power(x, 3)))**2) * \
(np.sqrt(2 / np.pi) * (1 + 0.134145 * np.power(x, 2)))
plot_activation(gelu, gelu_grad, "GELU")
图像
5. 现代高性能激活函数
5.1. Swish
由 Google 提出,在某些深度模型中表现优于 ReLU,尤其在注意力机制和移动端模型中有效。
公式
$$
\text{Swish}(x) = x \cdot \sigma(\beta x) = \frac{x}{1 + e^{-\beta x}}
$$
而 $\beta$ 是一个可学习参数。
实现
def swish(x, beta=1):
return x / (1 + np.exp(-beta*x))
def swish_grad(x, beta=1):
s = 1 / (1 + np.exp(-beta*x))
f = x * s
return f + (s * (1 - f)) * beta
plot_activation(swish, swish_grad, "Swish")
图像
5.2. SiLU(Sigmoid Linear Unit)
SiLU 是 Swish 激活函数在 $\beta = 1$ 时的特例。
实现与图像可参考 Swish。
5.3. E-Swish
E-Swish 是 SiLU 的缩放版本,通过超参数 $\beta$ 增强非线性表达能力。
公式
$$
\text{E-Swish}(x) = \beta \cdot x \cdot \sigma(x)
$$
实现
假设 $\beta$ 为 1.5:
def eswish(x, beta=1.5):
return beta * x * sigmoid(x)
def eswish_grad(x, beta=1.5):
s = sigmoid(x)
return beta * s * (1 + x * (1 - s))
plot_activation(lambda x: eswish(x, beta=1.5), lambda x: eswish_grad(x, beta=1.5), 'E-Swish')
图像
5.4. Mish
Mish 是一种自门控(self-gated)的非单调激活函数,由 Diganta Misra 在 2019 年的论文《Mish: A Self Regularized Non-Monotonic Neural Activation Function》中提出。
它在深度学习中表现出色,尤其在图像分类等任务中,性能常优于 ReLU 及其变体(如 Swish、Leaky ReLU 等)。
公式
$$
\text{Mish}(x) = x \cdot \tanh(\ln(1 + e^x))
$$
实现
def mish(x):
return x * np.tanh(np.log(1 + np.exp(x)))
def mish_grad(x):
sp = np.log(1 + np.exp(x))
tanh_sp = np.tanh(sp)
sech2_sp = 1 - tanh_sp**2
return tanh_sp + x * sech2_sp * sigmoid(x)
plot_activation(mish, mish_grad, 'Mish')
图像
5.5. SQNL(Square Nonlinearity)
SQNL 激活函数使用平方算子引入所需的非线性,其特点是计算操作次数更少。其在多层感知器人工神经网络架构问题中的收敛速度更快。此外,该函数的导数是线性的,因此梯度计算速度更快。
SQNL 激活函数是由 Adedamola Wuraola 等人在 2018 年的论文《SQNL: A New Computationally Efficient Activation Function》中提出的。
公式
$$
\text{SQNL}(x) = \begin{cases}
1 & \text{if } x > 2 \
x - \frac{x^2}{4} & \text{if } 0 \leq x \leq 2 \
x + \frac{x^2}{4} & \text{if } -2 \leq x < 0 \
-1 & \text{if } x < -2
\end{cases}
$$
实现
def sqnl(x):
return np.where(x > 2, 1,
np.where(x >= 0, x - (x**2)/4,
np.where(x >= -2, x + (x**2)/4, -1)))
def sqnl_grad(x):
return np.where(x > 2, 0,
np.where(x >= 0, 1 - x/2,
np.where(x >= -2, 1 + x/2, 0)))
plot_activation(sqnl, sqnl_grad, 'SQNL')
图像
5.6. Bent Identity
Bent Identity 是一种平滑、非单调、可微、无上界的激活函数,输出接近输入值但带有轻微非线性弯曲(「bent」)。
它适用于回归任务或自编码器的隐藏层,尤其在需要保留输入结构的同时引入轻微非线性变换的场景。其导数始终大于 0.5,避免梯度消失,适合浅层网络或需要稳定梯度的训练过程。
但由于计算涉及平方根,速度较慢,不常用于大规模深度网络。
另可参阅:www.gabormelli.com/RKB/Bent_Identity_Activation_Function
公式
$$
\text{Bent Identity}(x) = \frac{\sqrt{x^2 + 1} - 1}{2} + x
$$
实现
def bent_identity(x):
return (np.sqrt(x**2 + 1) - 1) / 2 + x
def bent_identity_grad(x):
return x / (2 * np.sqrt(x**2 + 1)) + 1
plot_activation(bent_identity, bent_identity_grad, 'Bent Identity')
图像
6. 门控与组合型激活函数
6.1. GLU(Gated Linear Unit)
GLU 是一种门控机制激活函数,通过将输入的一部分作为「门」来调制另一部分的输出,增强了模型的表达能力。
GLU 广泛应用于 Transformer 变体(如 GLU Variants Improve Transformer)、序列模型(如 CNN-based NLP 模型)和语音任务中。
相比传统激活函数,GLU 能更灵活地控制信息流动,提升建模能力。常见变体包括 SwiGLU、ReGLU 等,在大模型(如 Llama 系列)中表现优异。
公式
$$
\text{GLU}(x) = x \otimes \sigma(Wx + b)
$$
其中 $\otimes$ 表示逐元素乘法,$\sigma$ 是 sigmoid 函数。
实现
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
def glu_2d(x):
"""二维输入版本的GLU"""
a, b = x[..., 0], x[..., 1] # 分割输入的两个维度
return a * sigmoid(b)
def plot_glu_2d():
# 创建二维输入网格
x = np.linspace(-4, 4, 50)
y = np.linspace(-4, 4, 50)
X, Y = np.meshgrid(x, y)
xy = np.stack([X, Y], axis=-1) # 组合成(50,50,2)的输入
# 计算GLU输出
Z = glu_2d(xy)
# 3D可视化
fig = plt.figure(figsize=(12, 6))
# 1. 激活函数曲面
ax1 = fig.add_subplot(121, projection='3d')
ax1.plot_surface(X, Y, Z, cmap='viridis', alpha=0.8)
ax1.set_title('GLU: a * σ(b)')
ax1.set_xlabel('Input a')
ax1.set_ylabel('Input b')
ax1.set_zlabel('Output')
# 2. 梯度场切片(固定b=0时的梯度)
ax2 = fig.add_subplot(122)
b_zero_idx = np.abs(y).argmin() # 找到b=0的索引
grad_at_b0 = Z[b_zero_idx] * (1 - Z[b_zero_idx]) # ∂(aσ(b))/∂a = σ(b)
ax2.plot(x, grad_at_b0, label='∂GLU/∂a at b=0', color='blue')
ax2.plot(x, np.zeros_like(x), label='∂GLU/∂b at b=0', color='red')
ax2.set_title('Gradient Slices at b=0')
ax2.legend()
ax2.grid(True)
plt.tight_layout()
plt.show()
# 执行可视化
plot_glu_2d()
图像
6.2. Maxout
Maxout 是一种分段线性激活函数,定义为多个线性变换的最大值。它是一种可学习的分段线性激活函数,具有很强的表达能力——理论上,只要有足够多的片段,它可以逼近任意凸函数。
它与 Dropout 结合使用时表现优异,曾广泛用于全连接网络。但由于每个 Maxout 单元需要 k 倍参数(即 k 个 W_i, b_i),参数量大、计算开销高,因此在现代 CNN 或大模型中较少使用。适合对模型表达力要求高、但对计算资源不敏感的研究性任务。
公式
$$
\text{Maxout}(x) = \max_{i \in {1, \ldots, k}} (W_i x + b_i)
$$
实现
def maxout(x, w1=1.0, w2=-1.0, b1=0.0, b2=0.0):
"""
Maxout 简化版(k=2)用于可视化:
f(x) = max(w1*x + b1, w2*x + b2)
常用设置:w1=1, w2=-1 → f(x) = max(x, -x) = |x|(绝对值)
"""
return np.maximum(w1 * x + b1, w2 * x + b2)
def maxout_grad(x, w1=1.0, w2=-1.0, b1=0.0, b2=0.0):
"""
Maxout 梯度:根据哪个线性函数被激活返回对应权重
"""
linear1 = w1 * x + b1
linear2 = w2 * x + b2
return np.where(linear1 >= linear2, w1, w2)
# 可视化:f(x) = max(x, -x) = |x|
plot_activation(lambda x: maxout(x, w1=1.0, w2=-1.0),
lambda x: maxout_grad(x, w1=1.0, w2=-1.0),
'Maxout (k=2, |x|)')
图像
6.3. SReLU(S-shaped Rectified Linear Unit)
SReLU 是一种参数自适应的 S 形激活函数,能够根据数据自动学习激活曲线的形状,兼具线性和饱和特性。适用于需要灵活非线性变换的全连接网络或卷积网络,在某些图像分类和回归任务中表现优于 ReLU 和 ELU。
其设计目标是模拟生物神经元的响应特性,在深度模型中可提升表达能力。
但由于引入了四个可学习参数(每通道或共享),增加了模型复杂度,训练成本较高,目前应用不如 ReLU 或 GELU 广泛。
公式
$$
\text{SReLU}(x) = \begin{cases}
t_l + a_l (x - t_l) & \text{if } x \leq t_l \
x & \text{if } t_l < x < t_r \
t_r + a_r (x - t_r) & \text{if } x \geq t_r
\end{cases}
$$
其中 $t_l, a_l, t_r, a_r$ 是可学习参数。
实现
def srelu(x, tl=0.0, al=0.01, tr=1.0, ar=0.01):
return np.where(x <= tl, tl + al * (x - tl),
np.where(x < tr, x,
tr + ar * (x - tr)))
def srelu_grad(x, tl=0.0, al=0.01, tr=1.0, ar=0.01):
return np.where(x <= tl, al,
np.where(x < tr, 1.0, ar))
plot_activation(lambda x: srelu(x, tl=0.0, al=0.01, tr=1.0, ar=0.01),
lambda x: srelu_grad(x, tl=0.0, al=0.01, tr=1.0, ar=0.01),
'SReLU')
图像
6.4. CReLU(Concatenated ReLU)
CReLU 是一种受「CNN 模型中滤光片成对」启发而发展出来的一种改进 ReLU 激活函数。由 Wenling Shang 等人于 2016 年在论文《Understanding and Improving Convolutional Neural Networks via Concatenated Rectified Linear Units》中提出。
公式
$$
\text{CReLU}(x) = [\text{ReLU}(x), \text{ReLU}(-x)]
$$
即输出是 ReLU(x) 和 ReLU(-x) 的拼接。
实现
def crelu(x):
"""
输出维度翻倍
"""
return np.concatenate([relu(x), relu(-x)], axis=-1)
def crelu_grad(x):
"""
CReLU 梯度:返回 [d/dx ReLU(x), d/dx ReLU(-x)]
注意:ReLU(-x) 对 x 的导数是:
- 如果 x < 0: ReLU(-x) = -x, 导数为 -1
- 如果 x >= 0: ReLU(-x) = 0, 导数为 0
=> 即: -LeakyReLU(-x, negative_slope=1) 或 -H(x<0)
所以:
d/dx ReLU(-x) = -1 if x < 0 else 0
"""
grad_positive = relu_grad(x) # ReLU(x) 的梯度: 1 if x > 0 else 0
grad_negative = np.where(x < 0, -1, 0) # ReLU(-x) 的梯度: -1 if x < 0 else 0
return np.concatenate([grad_positive, grad_negative], axis=-1)
def plot_crelu_separate():
x = np.linspace(-3, 3, 1000)
y = crelu(x)
grad = crelu_grad(x)
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.plot(x, y[:len(x)], label='ReLU(x)')
plt.plot(x, y[len(x):], label='ReLU(-x)')
plt.title('CReLU: [ReLU(x), ReLU(-x)]')
plt.legend()
plt.grid(True)
plt.subplot(1, 2, 2)
plt.plot(x, grad[:len(x)], label="d/dx ReLU(x)", linestyle='--',)
plt.plot(x, grad[len(x):], label="d/dx ReLU(-x)", linestyle='--',)
plt.title('CReLU Gradient')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
plot_crelu_separate()
图像
7. 特殊用途与研究型函数
7.1. Softplus
Softplus 是 ReLU 的平滑近似版本,输出始终为正,且处处连续可导。当 x 很大时趋近于 x,当 x 很小时趋近于 0。
适用于需要平滑、非线性、非饱和(无上界)激活的场景,如:
- 变分自编码器(VAE)中用于生成方差参数(保证正值);
- 强化学习中的策略网络输出层;
- 需要避免 ReLU「神经元死亡」问题但又希望保持单侧软饱和特性的任务。
其主要缺点是计算开销较大(涉及指数和对数),且在 x 很大时可能产生数值溢出,需做稳定处理(如 torch.nn.Softplus 内部实现会做裁剪)。
作为理论性质良好的激活函数,常用于概率建模和生成模型中。
公式
$$
\text{Softplus}(x) = \ln(1 + e^x)
$$
实现
def softplus(x):
return np.log(1 + np.exp(x))
def softplus_grad(x):
return sigmoid(x)
plot_activation(softplus, softplus_grad, 'Softplus')
图像
7.2. Softsign
Softsign 是 Tanh 的替代品,输出范围 $(-1,1)$,具有平滑的 S 形曲线但计算更简单。
应用场景主要有:
- 替代 Tanh/Sigmoid:需平滑饱和激活时(如 RNN、生成模型)。
- 对抗梯度消失:梯度衰减比 Tanh 更缓慢,适合深层网络。
- 低精度训练:计算无指数运算,对量化友好。
优点:
- 计算高效:仅需一次除法和绝对值运算(比 Tanh 快约 2 倍)。
- 梯度平缓:最大梯度为 1(对比 Tanh 的 0.25),缓解梯度消失。
- 输出归一化:天然将输入压缩到 $(-1,1)$,避免数值爆炸。
缺点:
- 饱和区梯度趋零:当 $|x| \to \infty$ 时梯度接近 0,可能拖慢训练。
- 非零中心化:输出均值不为零(类似 Sigmoid),需配合 BatchNorm。
- 表达能力有限:非线性弱于 Swish 等新型激活函数。
公式
$$
\text{Softsign}(x) = \frac{x}{1 + |x|}
$$
实现
def softsign(x):
return x / (1 + np.abs(x))
def softsign_grad(x):
return 1 / ((1 + np.abs(x)) ** 2)
plot_activation(softsign, softsign_grad, "Softsign")
图像
7.3. Sine
Sine 是一种周期性、有界、平滑振荡的激活函数。与 ReLU、Sigmoid 等传统激活函数不同,它具有无限多的极值点和零点,能自然地建模周期性或高频信号。
主要适用于:
- 神经隐式表示(Neural Implicit Representations),如 SIREN(Sinusoidal Representation Networks),用于表示图像、音频、3D 形状等连续信号;
- 函数逼近任务,尤其是包含周期性、振荡行为的物理系统建模(如波函数、机械振动);
- 需要高频率细节重建的场景(如超分辨率、神经辐射场 NeRF 的变体)。
虽然不适用于通用深度分类网络,但在特定科学计算和表示学习任务中表现出色。
公式
$$
\text{Sine}(x) = \sin(x)
$$
实现
def sine(x):
return np.sin(x)
def sine_grad(x):
return np.cos(x)
plot_activation(sine, sine_grad, "Sine")
图像
7.4. Cosine
Cosine 是一种周期性、有界、偶函数的激活函数,与 Sine 类似,输出在 [-1, 1] 之间振荡,具有平滑性和无限可导性。
虽然不作为标准神经网络的通用激活函数使用,但在以下特定场景中有应用价值:
- 周期性信号建模:在函数逼近任务中,用于表示具有固定周期的连续信号(如音频、电磁波);
- 位置编码的替代或补充:在 Transformer 或神经隐式场中,与 Sine 配合使用构建更丰富的周期基函数;
- 对比学习中的相似度建模:cos(x) 本身是余弦相似度的核心,某些自定义层可能直接使用 cos(x) 作为非线性变换;
- 神经隐式表示(Neural Implicit Fields):与 Sine 一起用于构建高频基函数,例如在 Fourier Feature Networks 中作为输入映射的一部分。
与 Sine 的区别:cos(x) = sin(x + π/2),即余弦是正弦的相位偏移版本。在建模能力上两者等价,但 cos(0) = 1,而 sin(0) = 0,因此 cos(x) 在零点有最大响应,更适合需要「中心对称高响应」的场景。
公式
$$
\text{Cosine}(x) = \cos(x)
$$
实现
def cosine(x):
return np.cos(x)
def cosine_grad(x):
return -np.sin(x)
plot_activation(cosine, cosine_grad, "Cosine")
图像
7.5. Sinc(归一化或非归一化正弦函数)
Sinc 是一种振荡衰减型激活函数,具有无限支撑但随 |x| 增大而幅度减小。其特性源于信号处理中的理想低通滤波器和插值核。
主要特点是在 0 处有一个主峰,向两边衰减并振荡,幅度逐渐减小。
虽然在标准深度学习中极少使用,但在以下特定领域有潜在价值:
- 信号与图像重建任务:在神经隐式表示中用于建模带限信号(band-limited signals),理论上可完美重建奈奎斯特频率以下的信号;
- 插值网络:设计用于上采样或超分辨率的网络中,作为先验引导的激活函数;
- 物理信息神经网络(PINN):在需要满足特定频域约束的微分方程求解中,Sinc 的频域稀疏性可能带来优势;
- 傅里叶相关架构:作为输入特征映射的一部分,增强模型对周期性和频率结构的感知能力。
也有一些注意的地方:
- Sinc 函数在 x = 0 处不可导(需特殊处理),且存在多个零点和振荡,容易导致梯度不稳定;
- 计算开销较大(涉及 sin 和除法),且在 |x| 较大时梯度接近零,易造成训练困难;
- 目前仍属研究性激活函数,未在主流模型中广泛应用。
总体来说,Sinc 是一种理论性质优良但训练挑战大的激活函数,适用于对信号保真度要求高的科学计算任务,不适合通用深度网络。
公式
归一化形式:
$$
\text{Sinc}(x) = \frac{\sin(\pi x)}{\pi x}
$$
非归一化形式:
$$
\text{Sinc}(x) = \frac{\sin(x)}{x}
$$
在数学和物理中常见非归一化形式,而在信号处理(尤其是数字信号处理)中通常使用归一化形式。
实现(归一化形式)
def sinc(x):
# 避免除以零,对于 x=0 的情况,sinc 函数定义为 1
return np.where(np.abs(x) < 1e-7, 1.0, np.sin(np.pi * x) / (np.pi * x))
def sinc_grad(x):
# sinc(x) = sin(πx) / (πx)
# 使用商法则求导: (u/v)' = (u'v - uv') / v^2
pi_x = np.pi * x
sin_pi_x = np.sin(pi_x)
cos_pi_x = np.cos(pi_x)
# 分母为零时的处理
small = np.abs(x) < 1e-7
# 正常情况下的导数
grad = (pi_x * cos_pi_x - sin_pi_x) / (pi_x ** 2)
# 在 x=0 处导数为 0
grad = np.where(small, 0.0, grad)
return grad
plot_activation(sinc, sinc_grad, "Sinc")
图像(归一化形式)
实现(非归一化形式)
def sinc_unscaled(x):
# 避免除以零,对于 x=0 的情况,sinc 函数定义为 1
return np.where(np.abs(x) < 1e-7, 1.0, np.sin(x) / (x))
def sinc_unscaled_grad(x):
# 使用商法则求导: (sin(x)/x)' = (x*cos(x) - sin(x)) / x^2
sin_x = np.sin(x)
cos_x = np.cos(x)
# 处理 x=0 的极限情况(此时导数为0)
small = np.abs(x) < 1e-7
grad = np.where(small, 0.0, (x * cos_x - sin_x) / (x ** 2))
return grad
plot_activation(sinc_unscaled, sinc_unscaled_grad, "Sinc_unscaled")
图像(非归一化形式)
7.6. ArcTan
ArcTan 是一种有界、平滑、单调递增的激活函数,输出范围为 $(-\frac{\pi}{2}, \frac{\pi}{2})$,接近饱和时梯度趋近于零。其特点包括:
- 输出自动归一化到有限区间,有助于稳定训练;
- 处处连续可导,无尖锐转折;
- 比 Tanh 更缓慢地饱和,对异常值更鲁棒。
适用场景:
- 回归任务的输出层,当输出需要有界但不强制在 [-1,1] 时(相比 Tanh 更宽);
- RBF 网络或函数逼近系统中作为隐藏层激活,用于建模平滑非线性映射;
- 强化学习策略网络,输出连续动作且需限制范围;
- 某些物理系统建模中,需要输出对输入变化敏感但又不爆炸的场景。
公式
$$
\text{ArcTan}(x) = \arctan(x)
$$
实现
def arctan(x):
return np.arctan(x)
def arctan_grad(x):
return 1 / (1 + np.power(x, 2))
plot_activation(arctan, arctan_grad, "ArcTan")
图像
7.7. LogSigmoid
LogSigmoid 是 Sigmoid 的对数形式,核心价值在于数值稳定的损失计算,是深度学习框架内部实现的重要组成部分,但一般不直接作为网络层的激活函数暴露给用户。
公式
$$
\text{LogSigmoid}(x) = \log(\sigma(x)) = -\log(1 + e^{-x})
$$
实现
def log_sigmoid(x):
"""
公式等价于:f(x) = -softplus(-x)
输出范围: (-∞, 0)
注意:在 x 很大时稳定,但 x 很小时可能下溢。
"""
return -np.log(1 + np.exp(-x))
def log_sigmoid_stable(x):
"""
数值稳定的 LogSigmoid 实现,避免 exp(-x) 溢出。
使用分段函数:
x >= 0: -log(1 + exp(-x))
x < 0: x - log(1 + exp(x))
"""
return np.where(x >= 0,
-np.log(1 + np.exp(-x)),
x - np.log(1 + np.exp(x)))
def log_sigmoid_grad(x):
"""
LogSigmoid 的梯度。恰好等于 Sigmoid 函数本身
推导:
f(x) = log(σ(x)) = -log(1 + exp(-x))
f'(x) = σ(x) = 1 / (1 + exp(-x))
"""
return sigmoid(x)
plot_activation(log_sigmoid_stable, log_sigmoid_grad, 'LogSigmoid')
图像
8. 自动化搜索与结构创新
8.1. TanhExp(Tanh Exponential Activation)
TanhExp 是一种结合指数与双曲正切的自门控激活函数,在保持 ReLU 风格的同时增强非线性表达能力,适合对性能有更高要求的视觉任务。
TanhExp 是 Xinyu Liu 等人于 2020 年在论文《TanhExp: A smooth activation function with high convergence speed for lightweight neural networks》中提出的。
公式
$$
\text{TanhExp}(x) = x \cdot \tanh(e^x)
$$
实现
def tanhexp(x):
return x * np.tanh(np.exp(x))
def tanhexp_grad(x):
"""
TanhExp 梯度(使用链式法则)
f(x) = x * tanh(exp(x))
f'(x) = tanh(exp(x)) + x * sech^2(exp(x)) * exp(x)
"""
exp_x = np.exp(x)
tanh_e = np.tanh(exp_x)
sech2_e = 1 - tanh_e**2 # sech^2(x) = 1 - tanh^2(x)
return tanh_e + x * sech2_e * exp_x
plot_activation(tanhexp, tanhexp_grad, 'TanhExp')
图像
8.2. PAU(Power Activation Unit)
PAU 是一种基于幂函数的可学习激活函数。
其主要适用于研究场景,因为计算开销大、稳定性差,不推荐用于主流深度学习模型或大规模网络。
在实际应用中,更推荐使用 Swish、GELU 等高效且稳定的激活函数。
公式
$$
\text{PAU}(x) = \sum_{k=1}^{K} a_k x^{b_k}
$$
其中 $a_k$ 和 $b_k$ 是可学习参数。
实现
def pau(x, a1=1.0, a2=0.1, b1=1.0, b2=2.0):
"""
PAU简化版,K=2
f(x) = a1 * x^b1 + a2 * x^b2
注意:
- 当 x < 0 且 b_k 非整数时,x^b_k 可能为复数
- 此处使用 np.power 并允许 warning(或限制 b_k 为整数)
"""
# 处理负数的幂运算(避免复数)
# 方法:对负数取绝对值并保留符号
def safe_power(x, b):
return np.sign(x) * np.power(np.abs(x), b)
term1 = a1 * safe_power(x, b1)
term2 = a2 * safe_power(x, b2)
return term1 + term2
def pau_grad(x, a1=1.0, a2=0.1, b1=1.0, b2=2.0):
"""
修正后的梯度计算:
f'(x) = a1*b1*x^(b1-1) + a2*b2*x^(b2-1)
(严格处理x=0和负数情况)
"""
def safe_grad(x, a, b):
# 处理x=0和负数
if b == 1:
return np.ones_like(x) * a
mask = x >= 0
pos_part = a * b * np.power(np.maximum(x, 1e-7), b-1) * mask
neg_part = a * b * np.power(np.maximum(-x, 1e-7), b-1) * (~mask)
return pos_part + neg_part
return safe_grad(x, a1, b1) + safe_grad(x, a2, b2)
plot_activation(lambda x: pau(x, a1=1.0, a2=0.1, b1=1.0, b2=2.0),
lambda x: pau_grad(x, a1=1.0, a2=0.1, b1=1.0, b2=2.0),
'PAU (Power Activation Unit)')
图像
8.3. Learnable Sigmoid
Learnable Sigmoid 是标准 Sigmoid 的可学习扩展版本。
公式
$$
\text{Learnable Sigmoid}(x) = \frac{1}{1 + e^{-(\alpha x + \beta)}}
$$
其中 $\alpha$ 和 $\beta$ 是可学习参数。
实现
def learnable_sigmoid(x, alpha=1.0, beta=0.0):
"""
Learnable Sigmoid: f(x) = 1 / (1 + exp(-(alpha * x + beta)))
参数:
x: 输入
alpha: 控制斜率(>1 更陡,<1 更平缓)
beta: 控制偏移(>0 右移,<0 左移)
输出范围: (0, 1)
"""
return 1 / (1 + np.exp(-(alpha * x + beta)))
def learnable_sigmoid_grad(x, alpha=1.0, beta=0.0):
"""
Learnable Sigmoid 梯度:
f(x) = sigmoid(alpha*x + beta)
f'(x) = alpha * f(x) * (1 - f(x))
"""
s = learnable_sigmoid(x, alpha, beta)
return alpha * s * (1 - s)
plot_activation(lambda x: learnable_sigmoid(x, alpha=2.0, beta=0.0),
lambda x: learnable_sigmoid_grad(x, alpha=2.0, beta=0.0),
'Learnable Sigmoid (α=2.0)')
图像
8.4. Parametric Softplus
Parametric Softplus 是标准 Softplus 函数的可学习扩展版本。
公式
$$
\text{Parametric Softplus}(x) = \frac{1}{\beta} \log(1 + e^{\alpha x})
$$
其中 $\alpha$ 和 $\beta$ 是可学习参数。
实现
def parametric_softplus(x, alpha=1.0, beta=1.0):
"""
Parametric Softplus: f(x) = (1/β) * log(1 + exp(α * x))
参数:
x: 输入
alpha: 输入缩放因子(>1 更陡,<1 更平缓)
beta: 输出温度系数(>1 更平滑,<1 更陡峭)
输出范围: (0, ∞)
注意:
- 当 alpha*x 过大时,exp(alpha*x) 可能溢出
- 此处使用数值稳定版本(见梯度部分)
"""
# 数值稳定版本:避免 exp 溢出
# 使用恒等式:log(1 + exp(z)) = z + log(1 + exp(-z)) for z > 0
z = alpha * x
# 分段处理
return np.where(z > 20, z / beta,
np.where(z < -20, np.exp(z) / beta,
np.log(1 + np.exp(z)) / beta))
def parametric_softplus_grad(x, alpha=1.0, beta=1.0):
"""
Parametric Softplus 梯度:
f(x) = log(1 + exp(αx)) / β
f'(x) = (α / β) * sigmoid(αx)
"""
sigmoid_alpha_x = 1 / (1 + np.exp(-alpha * x))
return (alpha / beta) * sigmoid_alpha_x
plot_activation(lambda x: parametric_softplus(x, alpha=2.0, beta=0.5),
lambda x: parametric_softplus_grad(x, alpha=2.0, beta=0.5),
'Parametric Softplus (α=2.0, β=0.5)')
图像
8.5. Dynamic ReLU
Dynamic ReLU 是一种内容感知的可变形激活函数,其参数(如斜率、阈值)由输入数据动态生成,而非全局共享。
它最初用于轻量级网络(如 RepVGG、DyNet),能显著提升性能而几乎不增加计算量。
公式
$$\text{Dynamic ReLU}(x) = \begin{cases}
a(x) \cdot x + b(x) & \text{if } x < 0 \
x & \text{if } x \geq 0
\end{cases}
$$
其中 $a(x)$ 和 $b(x)$ 是由输入动态生成的函数。
实现
这里实现一个简化版本。
def dynamic_relu(x, global_context=0.0, a_min=0.01, a_max=0.2, b_scale=0.1):
"""
Dynamic ReLU (简化版本,方便可视化)
假设 'global_context' 是来自输入的统计量(如均值、最大值)
用它生成负半轴的斜率 a 和偏置 b
f(x) =
a * x + b, x < 0
x, x >= 0
参数:
x: 输入
global_context: 模拟全局上下文(如 batch 的均值)
a_min, a_max: 动态斜率范围
b_scale: 动态偏置的缩放因子
注意:真实版本中 a,b 由小型网络生成
"""
# 模拟动态参数生成(真实中为小型网络)
a = a_min + (a_max - a_min) * sigmoid(global_context) # a ∈ [a_min, a_max]
b = b_scale * tanh(global_context) # b ∈ [-b_scale, b_scale]
return np.where(x < 0, a * x + b, x)
def dynamic_relu_grad(x, global_context=0.0, a_min=0.01, a_max=0.2, b_scale=0.1):
"""
Dynamic ReLU 梯度:
f'(x) = a, x < 0
1, x >= 0
"""
a = a_min + (a_max - a_min) * sigmoid(global_context)
return np.where(x < 0, a, 1.0)
# 可视化不同上下文下的Dynamic ReLU
functions_to_plot = [
(lambda x: dynamic_relu(x, global_context=-2.0), lambda x: dynamic_relu_grad(x, global_context=-2.0), 'Dynamic ReLU (ctx=-2.0)'),
(lambda x: dynamic_relu(x, global_context=0.0), lambda x: dynamic_relu_grad(x, global_context=0.0), 'Dynamic ReLU (ctx=0.0)'),
(lambda x: dynamic_relu(x, global_context=2.0), lambda x: dynamic_relu_grad(x, global_context=2.0), 'Dynamic ReLU (ctx=2.0)')]
plot_activations(functions_to_plot, x)
图像
8.6. EvoNorm
EvoNorm 是一种通过神经架构搜索(NAS)发现的激活函数,它结合了归一化和激活,能自适应地根据输入分布调整行为。
公式
$$\text{EvoNorm}(x) = \frac{x}{\sqrt{\text{Var}(x) + \epsilon}} \cdot \sigma(\gamma x + \beta)
$$
其中 $\gamma$ 和 $\beta$ 是可学习参数,$\epsilon$ 是小常数。
实现
def evonorm(x, gamma=1.0, beta=0.0, epsilon=1e-5):
"""
EvoNorm 简化版(仅激活部分,不含完整归一化)
"""
# 计算输入的方差(简化处理,实际中应按批次和通道计算)
var = np.var(x)
# 归一化并应用sigmoid门控
return (x / np.sqrt(var + epsilon)) * sigmoid(gamma * x + beta)
def evonorm_grad(x, gamma=1.0, beta=0.0, epsilon=1e-5):
"""
EvoNorm 梯度(简化版)
"""
var = np.var(x)
std = np.sqrt(var + epsilon)
s = sigmoid(gamma * x + beta)
# 使用链式法则计算梯度
norm_grad = 1 / std
sigmoid_grad = s * (1 - s) * gamma
return norm_grad * s + (x / std) * sigmoid_grad
plot_activation(lambda x: evonorm(x, gamma=1.0, beta=0.0),
lambda x: evonorm_grad(x, gamma=1.0, beta=0.0),
'EvoNorm')
图像
9. Transformer 专用激活函数
9.1. GeGLU(Gated Exponential Linear Unit)
GeGLU 是专门为 Transformer 设计的门控激活函数,在大型语言模型(如 LLaMA、GLM)中表现出色。
公式
$$\text{GeGLU}(x) = \text{GLU}(x; W, V, b, c) = \sigma(xW + b) \odot (xV + c)
$$
其中 $\odot$ 表示逐元素乘法。
实现
def geglu_2d(x):
"""二维输入版本的GeGLU"""
a, b = x[..., 0], x[..., 1] # 分割输入的两个维度
return sigmoid(a) * b
def plot_geglu_2d():
# 创建二维输入网格
x = np.linspace(-4, 4, 50)
y = np.linspace(-4, 4, 50)
X, Y = np.meshgrid(x, y)
xy = np.stack([X, Y], axis=-1) # 组合成(50,50,2)的输入
# 计算GeGLU输出
Z = geglu_2d(xy)
# 3D可视化
fig = plt.figure(figsize=(12, 6))
# 1. 激活函数曲面
ax1 = fig.add_subplot(121, projection='3d')
ax1.plot_surface(X, Y, Z, cmap='viridis', alpha=0.8)
ax1.set_title('GeGLU: σ(a) * b')
ax1.set_xlabel('Input a')
ax1.set_ylabel('Input b')
ax1.set_zlabel('Output')
# 2. 梯度场切片(固定b=0时的梯度)
ax2 = fig.add_subplot(122)
b_zero_idx = np.abs(y).argmin() # 找到b=0的索引
grad_at_b0 = sigmoid(X[b_zero_idx]) # ∂(σ(a)*b)/∂a = σ'(a)*b
ax2.plot(x, grad_at_b0, label='∂GeGLU/∂a at b=0', color='blue')
ax2.plot(x, np.zeros_like(x), label='∂GeGLU/∂b at b=0', color='red')
ax2.set_title('Gradient Slices at b=0')
ax2.legend()
ax2.grid(True)
plt.tight_layout()
plt.show()
# 执行可视化
plot_geglu_2d()
图像
9.2. SwiGLU(Swish Gated Linear Unit)
SwiGLU 是另一种专为 Transformer 设计的门控激活函数,在大型语言模型(如 PaLM)中使用。
公式
$$\text{SwiGLU}(x, W, V, W_2, b, c, d) = \text{Swish}(xW + b) \odot (xV + c)
$$
实现
def swiglu_2d(x):
"""二维输入版本的SwiGLU"""
a, b = x[..., 0], x[..., 1] # 分割输入的两个维度
return (a * sigmoid(a)) * b # Swish(a) * b
def plot_swiglu_2d():
# 创建二维输入网格
x = np.linspace(-4, 4, 50)
y = np.linspace(-4, 4, 50)
X, Y = np.meshgrid(x, y)
xy = np.stack([X, Y], axis=-1) # 组合成(50,50,2)的输入
# 计算SwiGLU输出
Z = swiglu_2d(xy)
# 3D可视化
fig = plt.figure(figsize=(12, 6))
# 1. 激活函数曲面
ax1 = fig.add_subplot(121, projection='3d')
ax1.plot_surface(X, Y, Z, cmap='viridis', alpha=0.8)
ax1.set_title('SwiGLU: Swish(a) * b')
ax1.set_xlabel('Input a')
ax1.set_ylabel('Input b')
ax1.set_zlabel('Output')
# 2. 梯度场切片(固定b=0时的梯度)
ax2 = fig.add_subplot(122)
b_zero_idx = np.abs(y).argmin() # 找到b=0的索引
a_vals = X[b_zero_idx]
swish_grad = sigmoid(a_vals) * (1 + a_vals * (1 - sigmoid(a_vals))) # Swish'(a)
ax2.plot(x, swish_grad, label='∂SwiGLU/∂a at b=0', color='blue')
ax2.plot(x, np.zeros_like(x), label='∂SwiGLU/∂b at b=0', color='red')
ax2.set_title('Gradient Slices at b=0')
ax2.legend()
ax2.grid(True)
plt.tight_layout()
plt.show()
# 执行可视化
plot_swiglu_2d()
图像
9.3. ReGLU(ReLU Gated Linear Unit)
ReGLU 是使用 ReLU 作为门控的 GLU 变体,在 Transformer 中也有应用。
公式
$$
\text{ReGLU}(x) = \text{ReLU}(xW + b) \odot (xV + c)
$$
实现
def reglu_2d(x):
"""二维输入版本的ReGLU"""
a, b = x[..., 0], x[..., 1] # 分割输入的两个维度
return np.maximum(0, a) * b # ReLU(a) * b
def plot_reglu_2d():
# 创建二维输入网格
x = np.linspace(-4, 4, 50)
y = np.linspace(-4, 4, 50)
X, Y = np.meshgrid(x, y)
xy = np.stack([X, Y], axis=-1) # 组合成(50,50,2)的输入
# 计算ReGLU输出
Z = reglu_2d(xy)
# 3D可视化
fig = plt.figure(figsize=(12, 6))
# 1. 激活函数曲面
ax1 = fig.add_subplot(121, projection='3d')
ax1.plot_surface(X, Y, Z, cmap='viridis', alpha=0.8)
ax1.set_title('ReGLU: ReLU(a) * b')
ax1.set_xlabel('Input a')
ax1.set_ylabel('Input b')
ax1.set_zlabel('Output')
# 2. 梯度场切片(固定b=0时的梯度)
ax2 = fig.add_subplot(122)
b_zero_idx = np.abs(y).argmin() # 找到b=0的索引
relu_grad = (X[b_zero_idx] > 0).astype(float) # ReLU'(a)
ax2.plot(x, relu_grad, label='∂ReGLU/∂a at b=0', color='blue')
ax2.plot(x, np.zeros_like(x), label='∂ReGLU/∂b at b=0', color='red')
ax2.set_title('Gradient Slices at b=0')
ax2.legend()
ax2.grid(True)
plt.tight_layout()
plt.show()
# 执行可视化
plot_reglu_2d()
图像
10. 轻量化激活函数
10.1. Hard Swish
Hard Swish 是 Swish 的高效近似版本,计算成本更低,适合移动端和嵌入式设备。
公式
$$
\text{Hard Swish}(x) = x \cdot \frac{\text{ReLU6}(x + 3)}{6}
$$
实现
def hard_swish(x):
"""Hard Swish 激活函数"""
return x * np.minimum(np.maximum(x + 3, 0), 6) / 6
def hard_swish_grad(x):
"""Hard Swish 梯度"""
# ReLU6(x+3) 的导数是:
# 0 当 x < -3
# 1 当 -3 ≤ x ≤ 3
# 0 当 x > 3
relu6_grad = np.where((x >= -3) & (x <= 3), 1.0, 0.0)
# 使用乘积法则:(f*g)' = f'*g + f*g'
# f = x, g = ReLU6(x+3)/6
# f' = 1, f*g' = x * relu6_grad / 6
g = np.minimum(np.maximum(x + 3, 0), 6) / 6
return g + x * relu6_grad / 6
plot_activation(hard_swish, hard_swish_grad, 'Hard Swish')
图像
10.2. Hard Sigmoid
Hard Sigmoid 是 Sigmoid 的高效近似版本,计算成本更低,适合移动端和嵌入式设备。
公式
$$
\text{Hard Sigmoid}(x) = \max(0, \min(1, \frac{x + 3}{6}))
$$
实现
def hard_sigmoid(x):
"""Hard Sigmoid 激活函数"""
return np.maximum(0, np.minimum(1, (x + 3) / 6))
def hard_sigmoid_grad(x):
"""Hard Sigmoid 梯度"""
# Hard Sigmoid 的导数是:
# 0 当 x < -3
# 1/6 当 -3 ≤ x ≤ 3
# 0 当 x > 3
return np.where((x >= -3) & (x <= 3), 1.0/6.0, 0.0)
plot_activation(hard_sigmoid, hard_sigmoid_grad, 'Hard Sigmoid')
图像
10.3. QuantReLU
QuantReLU 是为量化神经网络设计的 ReLU 变体,考虑了量化误差的影响。
公式
$$
\text{QuantReLU}(x) = \max(0, x + \epsilon)
$$
其中 $\epsilon$ 是量化误差项。
实现
def quant_relu(x, epsilon=0.1):
"""QuantReLU 激活函数"""
return np.maximum(0, x + epsilon)
def quant_relu_grad(x, epsilon=0.1):
"""QuantReLU 梯度"""
return np.where(x + epsilon > 0, 1.0, 0.0)
functions_to_plot = [
(lambda x: quant_relu(x, epsilon=0.0), lambda x: quant_relu_grad(x, epsilon=0.0), 'QuantReLU ε=0.0'),
(lambda x: quant_relu(x, epsilon=0.1), lambda x: quant_relu_grad(x, epsilon=0.1), 'QuantReLU ε=0.1'),
(lambda x: quant_relu(x, epsilon=0.2), lambda x: quant_relu_grad(x, epsilon=0.2), 'QuantReLU ε=0.2')]
plot_activations(functions_to_plot, x)
图像
10.4. LUT-based Activation
LUT-based Activation 是基于查找表的激活函数,通过预计算激活函数值来加速推理。
实现
def create_lut(func, min_val=-10, max_val=10, num_entries=256):
"""创建激活函数的查找表"""
x_vals = np.linspace(min_val, max_val, num_entries)
lut = func(x_vals)
return x_vals, lut
def lut_activation(x, x_vals, lut):
"""基于查找表的激活函数"""
# 将输入映射到查找表索引
min_val, max_val = x_vals[0], x_vals[-1]
indices = np.clip((x - min_val) / (max_val - min_val) * (len(lut) - 1), 0, len(lut) - 1).astype(int)
return lut[indices]
# 创建ReLU的LUT
x_vals, relu_lut = create_lut(relu)
def lut_relu(x):
return lut_activation(x, x_vals, relu_lut)
def lut_relu_grad(x):
# 简化处理,使用原始ReLU的梯度
return relu_grad(x)
# 对比原始ReLU和LUT ReLU
functions_to_plot = [
(relu, relu_grad, 'Original ReLU'),
(lut_relu, lut_relu_grad, 'LUT ReLU')]
plot_activations(functions_to_plot, x)
图像
11. 激活函数对比
下表从多个维度对比了本文中介绍的 20 多种激活函数:
12. 总结
激活函数是神经网络中的关键组件,它们为网络引入非线性,使得神经网络能够学习复杂的模式。从简单的 Sigmoid 和 Tanh,到现代的 GELU 和 Swish,再到专门为 Transformer 设计的 GeGLU 和 SwiGLU,激活函数的发展反映了深度学习领域的不断进步。
选择合适的激活函数取决于多种因素,包括网络架构、任务类型、计算资源和性能需求。在实际应用中,ReLU 及其变体仍然是许多任务的首选,但在特定场景下,其他激活函数可能表现更好。
希望本文能够帮助读者更好地理解各种激活函数的特性,并在实际项目中做出合适的选择。