news 2026/5/16 21:09:14

深度学习之激活函数详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深度学习之激活函数详解

摘要:激活函数是深度神经网络中最核心的组件之一,它为网络引入非线性表达能力,使得堆叠多层神经元成为可能。本文系统梳理了深度学习中常用的激活函数,包括 Sigmoid、Tanh、ReLU、Leaky ReLU、ELU、SeLU、Swish、Mish 以及 Softmax 等,从数学公式、输出特性、梯度分析、优缺点对比四个维度展开深入剖析,并结合 NumPy 实现完整的代码示例与可视化对比,最后给出针对不同场景的激活函数选择指南,助力读者在实战中做出合理决策。

关键词:激活函数、深度学习、ReLU、梯度消失、非线性


1. 激活函数的作用

1.1 为什么要非线性激活

神经网络之所以能够拟合任意复杂的非线性函数,核心原因在于激活函数的非线性。假设网络中有两层线性变换:

y = W2 · (W1 · x + b1) + b2 = (W2 · W1) · x + (W2 · b1 + b2)

无论我们堆叠多少层线性变换,它们的嵌套结果仍然只是一个线性变换——整个网络等价于一个单层线性模型,根本无法捕捉变量之间的非线性关系。激活函数的出现打破了这个困局:每层线性变换之后,激活函数以非线性方式对结果进行"扭曲",使得网络的表示能力呈指数级增长。Universal Approximation Theorem(通用逼近定理)告诉我们,只要网络中包含足够多的隐藏单元,单层前馈网络即可逼近任意连续函数;而多层的组合则进一步大幅降低了所需的参数量和计算复杂度。

1.2 线性激活的问题

如果执意使用线性激活函数(即令激活值直接等于输入),会产生以下问题:

  1. 表达能力退化为线性:无论网络多深,信息流始终是线性的组合,无法拟合阶跃、曲线、周期性等非线性模式。

  2. 梯度在链式求导中快速衰减(浅层网络)或爆炸(权重过大):线性函数的导数是常数,连乘后数值极不稳定。

  3. 无法构建"特征交叉":深度学习的强大之处在于隐式地自动进行特征交叉组合,而线性激活完全丧失了这一能力。

因此,在隐藏层中使用非线性激活函数是深度学习的基本共识。


2. Sigmoid 函数

2.1 数学公式与输出范围

Sigmoid(亦称 Logistic 函数)的数学表达式为:

$$\sigma(x) = \frac{1}{1 + e^{-x}}$$

其输出值域为 $(0, 1)$,曲线呈 S 形,关于点 $(0, 0.5)$ 中心对称。

2.2 梯度特性

Sigmoid 的导数可以由自身表示,形式优雅:

$$\sigma'(x) = \sigma(x) \cdot (1 - \sigma(x))$$

这意味着在反向传播中,计算梯度只需用到当前层的激活值,无需额外存储。

2.3 核心优点

  • 平滑可导:函数在整个实数域上无穷阶可导,适合梯度下降。

  • 概率解释:输出天然位于 $(0, 1)$,在二分类任务的输出层可以直接解释为"属于正类的概率"。

2.4 三大缺陷

缺陷说明
梯度消失当 $
计算开销大涉及指数运算 $e^{-x}$,在硬件层面不如加减法和比较运算高效。
输出非零中心输出始终为正数($(0,1)$),导致后一层的输入偏置同号。在梯度反向传播时,同号的梯度信号会让权重更新方向始终在第一、三象限摆动,减缓收敛速度。

2.5 代码实现

import numpy as np ​ def sigmoid(x: np.ndarray) -> np.ndarray: """ Sigmoid 激活函数 公式: σ(x) = 1 / (1 + exp(-x)) 参数: x: 任意形状的输入数组 返回: 与输入形状相同的输出,值域 (0, 1) """ # 限制输入范围以避免 exp 溢出 x_clipped = np.clip(x, -500, 500) return 1.0 / (1.0 + np.exp(-x_clipped)) ​ ​ def sigmoid_derivative(x: np.ndarray) -> np.ndarray: """ Sigmoid 函数的梯度 公式: σ'(x) = σ(x) * (1 - σ(x)) 参数: x: 任意形状的输入数组 返回: 与输入形状相同的梯度数组 """ s = sigmoid(x) return s * (1.0 - s) ​ ​ # ------------------- 示例 ------------------- if __name__ == "__main__": import matplotlib.pyplot as plt ​ x = np.linspace(-10, 10, 400) y = sigmoid(x) dy = sigmoid_derivative(x) ​ plt.figure(figsize=(8, 4)) plt.plot(x, y, label="Sigmoid") plt.plot(x, dy, label="导数", linestyle="--") plt.axhline(0, color="gray", linewidth=0.5) plt.axvline(0, color="gray", linewidth=0.5) plt.title("Sigmoid 函数及其导数") plt.legend() plt.grid(True, alpha=0.3) plt.show()

3. Tanh 函数

3.1 数学公式与输出范围

Tanh(双曲正切)函数的表达式为:

$$\tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}}$$

其值域为 $(-1, 1)$,零中心(zero-centered),关于原点对称。

数学关系tanh(x) = 2σ(2x) - 1,即 Tanh 本质上是 Sigmoid 的一个线性变换版本。

3.2 零中心的优势

由于输出范围对称分布在负侧和正侧,梯度的正负号可以完整保留前一层的信号方向。这使得 Tanh 在实际训练中通常比 Sigmoid 收敛更快,是早期 RNN 和 NLP 任务中的常用选择。

3.3 仍存在的梯度消失

Tanh 的导数为:

$$\tanh'(x) = 1 - \tanh^2(x)$$

当 $|x|$ 较大时,$\tanh(x) \to \pm 1$,导数趋于 0。深层网络中同样面临梯度消失问题,只是缓解程度优于 Sigmoid。

3.4 代码实现

import numpy as np ​ def tanh_func(x: np.ndarray) -> np.ndarray: """ Tanh(双曲正切)激活函数 公式: tanh(x) = (exp(x) - exp(-x)) / (exp(x) + exp(-x)) 参数: x: 任意形状的输入数组 返回: 与输入形状相同的输出,值域 (-1, 1) """ # 稳定计算:避免大值时 exp 溢出 return np.tanh(x) ​ ​ def tanh_derivative(x: np.ndarray) -> np.ndarray: """ Tanh 函数的梯度 公式: tanh'(x) = 1 - tanh²(x) 参数: x: 任意形状的输入数组 返回: 与输入形状相同的梯度数组 """ return 1.0 - np.tanh(x) ** 2 ​ ​ # ------------------- 示例 ------------------- if __name__ == "__main__": import matplotlib.pyplot as plt ​ x = np.linspace(-10, 10, 400) y = tanh_func(x) dy = tanh_derivative(x) ​ plt.figure(figsize=(8, 4)) plt.plot(x, y, label="Tanh") plt.plot(x, dy, label="导数", linestyle="--") plt.axhline(0, color="gray", linewidth=0.5) plt.axvline(0, color="gray", linewidth=0.5) plt.title("Tanh 函数及其导数") plt.legend() plt.grid(True, alpha=0.3) plt.show()

4. ReLU 函数

4.1 数学公式

ReLU(Rectified Linear Unit,线性整流单元)的表达式极为简洁:

$$f(x) = \max(0, x) = \begin{cases} 0 & x < 0 \ x & x \ge 0 \end{cases}$$

4.2 核心优势

优势说明
计算极快只需一次比较运算,无需指数操作,比 Sigmoid/Tanh 快数倍。
缓解梯度消失正区间梯度恒为 1,反向传播时梯度稳定传递,深层网络也能正常训练。
稀疏激活性负值输出为 0,使得部分神经元处于"关闭"状态,形成稀疏表示,具有一定正则化效果。

4.3 神经元死亡问题(Dying ReLU)

当大量输入持续为负时,对应的权重和偏置在反向传播中梯度始终为 0,参数永远无法更新,这些神经元永久"死亡"。在实际应用中,当学习率过大或权重初始化不当时,这一问题尤为突出。

4.4 代码实现

import numpy as np ​ def relu(x: np.ndarray) -> np.ndarray: """ ReLU(线性整流)激活函数 公式: f(x) = max(0, x) 参数: x: 任意形状的输入数组 返回: 与输入形状相同的输出,值域 [0, +∞) """ return np.maximum(0, x) ​ ​ def relu_derivative(x: np.ndarray) -> np.ndarray: """ ReLU 函数的梯度 公式: f'(x) = 1 if x >= 0 else 0 参数: x: 任意形状的输入数组 返回: 与输入形状相同的梯度数组,元素为 0 或 1 """ return np.where(x >= 0, 1.0, 0.0) ​ ​ # ------------------- 示例 ------------------- if __name__ == "__main__": import matplotlib.pyplot as plt ​ x = np.linspace(-10, 10, 400) y = relu(x) dy = relu_derivative(x) ​ plt.figure(figsize=(8, 4)) plt.plot(x, y, label="ReLU") plt.plot(x, dy, label="导数", linestyle="--") plt.axhline(0, color="gray", linewidth=0.5) plt.axvline(0, color="gray", linewidth=0.5) plt.title("ReLU 函数及其导数") plt.legend() plt.grid(True, alpha=0.3) plt.show()

5. Leaky ReLU

5.1 数学公式

Leaky ReLU 在 ReLU 的负区间引入一个极小的斜率 $\alpha$(通常取 0.01),避免神经元完全死亡:

$$f(x) = \max(\alpha x, x) = \begin{cases} \alpha x & x < 0 \ x & x \ge 0 \end{cases}$$

5.2 解决神经元死亡

由于负区间有了一个很小的非零梯度,反向传播时即使输入长期为负,权重仍有微小的更新机会,从根本上规避了"Dying ReLU"问题。

5.3 代码实现

import numpy as np ​ def leaky_relu(x: np.ndarray, alpha: float = 0.01) -> np.ndarray: """ Leaky ReLU 激活函数 公式: f(x) = max(alpha * x, x),通常 alpha = 0.01 参数: x: 任意形状的输入数组 alpha: 负区间的斜率,默认 0.01 返回: 与输入形状相同的输出 """ return np.where(x >= 0, x, alpha * x) ​ ​ def leaky_relu_derivative(x: np.ndarray, alpha: float = 0.01) -> np.ndarray: """ Leaky ReLU 的梯度 参数: x: 任意形状的输入数组 alpha: 负区间的斜率,默认 0.01 返回: 与输入形状相同的梯度数组 """ return np.where(x >= 0, 1.0, alpha)

6. 其他常用激活函数

6.1 ELU(Exponential Linear Unit)

ELU 在负区间使用指数函数,输出接近零均值,同时在负区间有一定的"软饱和"特性,有助于网络学习更鲁棒的表示:

$$f(x) = \begin{cases} \alpha (e^x - 1) & x < 0 \ x & x \ge 0 \end{cases}$$

def elu(x: np.ndarray, alpha: float = 1.0) -> np.ndarray: return np.where(x >= 0, x, alpha * (np.exp(np.clip(x, -500, 500)) - 1)) ​ def elu_derivative(x: np.ndarray, alpha: float = 1.0) -> np.ndarray: return np.where(x >= 0, 1.0, elu(x, alpha) + alpha)

6.2 SeLU(Scaled Exponential Linear Unit)

SeLU 由 Klambauer 等人在 2017 年提出,其自归一化特性使得网络在不使用 Batch Normalization 的情况下也能保持稳定的方差传播:

$$f(x) = \lambda \cdot \text{ELU}(x), \quad \lambda \approx 1.0507$$

def selu(x: np.ndarray) -> np.ndarray: """SeLU 激活函数,lambda ≈ 1.0507""" ALPHA = 1.6732632423543772848170429916717 LAMBDA = 1.0507009873554804934193349852946 return LAMBDA * np.where(x >= 0, x, ALPHA * (np.exp(np.clip(x, -500, 500)) - 1))

6.3 Swish

Swish 由 Google Brain 在 2017 年提出,自门控机制(self-gated)使其具有平滑非单调特性,在许多任务上优于 ReLU:

$$f(x) = x \cdot \sigma(x) = \frac{x}{1 + e^{-x}}$$

def swish(x: np.ndarray) -> np.ndarray: """ Swish 激活函数 公式: f(x) = x * sigmoid(x) """ return x * sigmoid(x) ​ def swish_derivative(x: np.ndarray) -> np.ndarray: """ Swish 的梯度(使用链式法则推导) f'(x) = sigmoid(x) + x * sigmoid(x) * (1 - sigmoid(x)) = sigmoid(x) + x * sigmoid(x) * (1 - sigmoid(x)) """ s = sigmoid(x) return s + x * s * (1 - s)

6.4 Mish

Mish 由 Diganta Misra 在 2019 年提出,同样采用自门控设计,在 ImageNet 分类等任务上取得了优于 ReLU 和 Swish 的效果:

$$f(x) = x \cdot \tanh(\ln(1 + e^x))$$

def mish(x: np.ndarray) -> np.ndarray: """ Mish 激活函数 公式: f(x) = x * tanh(softplus(x)),其中 softplus(x) = ln(1 + e^x) """ return x * np.tanh(np.log1p(np.exp(np.clip(x, -500, 500))))

6.5 Softmax(含温度参数)

Softmax 是多分类任务输出层的标准激活函数,将任意实数向量转换为概率分布:

$$[\text{Softmax}(x)]i = \frac{e^{x_i}}{\sum{j=1}^{N} e^{x_j}}$$

温度参数(Temperature)是 Softmax 的重要扩展。引入温度 $T$ 后:

$$[\text{Softmax}T(x)]i = \frac{e^{x_i / T}}{\sum_{j=1}^{N} e^{x_j / T}}$$

  • $T \to 0$:输出趋近于 one-hot(最max的项概率趋近于 1),即"硬"决策。

  • $T = 1$:标准 Softmax。

  • $T \to +\infty$:输出趋近于均匀分布(完全随机)。

在蒸馏学习(Knowledge Distillation)和强化学习策略网络中,温度参数是控制探索与利用平衡的关键超参数。

def softmax(x: np.ndarray, axis: int = -1, temperature: float = 1.0) -> np.ndarray: """ Softmax 激活函数(支持温度参数) 公式: softmax(x)_i = exp(x_i / T) / Σ_j exp(x_j / T) 参数: x: 输入 logits,任意形状 axis: 计算 Softmax 的轴(默认为最后一维) temperature: 温度参数 T,T 越大输出越平滑(趋近均匀) 返回: 与输入形状相同的概率分布,每行/列和为 1 """ # 减去最大值以提升数值稳定性(不改变 Softmax 结果) x_scaled = x / temperature x_max = np.max(x_scaled, axis=axis, keepdims=True) exp_x = np.exp(x_scaled - x_max) # 数值稳定的写法 return exp_x / np.sum(exp_x, axis=axis, keepdims=True) ​ ​ def softmax_derivative(x: np.ndarray, temperature: float = 1.0) -> np.ndarray: """ Softmax 的雅可比矩阵(用于理解梯度流动) 对于第 i 个输出关于第 j 个输入: d(softmax_i) / d(x_j) = softmax_i * (δ_ij - softmax_j) 注意:实际反向传播中通常直接使用 cross-entropy + softmax 的组合梯度, 该梯度简化为 (softmax(x) - y),无需显式计算雅可比矩阵。 """ s = softmax(x, temperature=temperature) # 构造雅可比矩阵(仅作演示,对大维度矩阵开销较大) # jacobian[i, j] = s[i] * (δ[i,j] - s[j]) I = np.eye(x.shape[-1]) s_expanded = s[..., np.newaxis] jacobian = s_expanded * (I - s_expanded.swapaxes(-1, -2)) return jacobian

7. 激活函数选择指南

7.1 按层类型选择

层级推荐激活函数原因
二分类输出层Sigmoid输出值域 $(0,1)$,直接作为概率解释
多分类输出层Softmax输出归一化为概率分布,各类别互斥
回归输出层恒等函数(无激活)/ ReLU(正值输出)目标值无界时直接线性输出
隐藏层(默认首选)ReLU计算高效、梯度稳定,是工业界事实标准
隐藏层(需要更高精度)Leaky ReLU / ELU避免神经元死亡,输出更接近零中心
隐藏层(追求SOTA性能)Mish / Swish自门控机制带来更强的非线性表达能力
自归一化网络SeLU配合 AlphaDropout 可无需 BatchNorm

7.2 按网络架构选择

  • MLP(全连接网络):隐藏层用 ReLU,输出层根据任务选择 Sigmoid/Softmax/恒等。

  • CNN(卷积网络):ReLU 系列为主(ReLU → Leaky ReLU → Mish 逐步升级)。

  • RNN/LSTM/GRU:Tanh 仍是主流(门控机制需要零中心输出),输出层可用 Sigmoid。

  • Transformer/Attention:GELU(近似实现为0.5 * x * (1 + tanh(sqrt(2/pi) * (x + 0.044715*x^3))))已成为事实标准。

7.3 实战建议

  1. 从 ReLU 开始:大多数场景先用 ReLU 建立基线,简单高效。

  2. 警惕神经元死亡:训练 loss 不下降且梯度在浅层几乎为零时,换 Leaky ReLU 或 ELU。

  3. 学习率匹配:ReLU 网络对学习率较敏感,建议配合 He 初始化(fan_in方差)使用。

  4. BatchNorm + 激活函数的顺序:标准实践是Linear → BatchNorm → Activation,注意卷积网络中的 Channel 维度归一化。

  5. 不要在输出层使用 ReLU/Sigmoid/Softmax 之外的函数——输出层通常需要特定的数值范围或概率语义。


8. 实战代码:各激活函数对比可视化

以下代码在一张图上同时展示所有主流激活函数的曲线及其梯度,方便直观对比:

import numpy as np import matplotlib.pyplot as plt ​ # ============================================================ # 激活函数定义(汇总) # ============================================================ def sigmoid(x): x = np.clip(x, -500, 500) return 1.0 / (1.0 + np.exp(-x)) ​ def tanh_func(x): return np.tanh(x) ​ def relu(x): return np.maximum(0, x) ​ def leaky_relu(x, alpha=0.01): return np.where(x >= 0, x, alpha * x) ​ def elu(x, alpha=1.0): return np.where(x >= 0, x, alpha * (np.exp(np.clip(x, -500, 500)) - 1)) ​ def selu(x): ALPHA = 1.6732632423543772848170429916717 LAMBDA = 1.0507009873554804934193349852946 return LAMBDA * np.where(x >= 0, x, ALPHA * (np.exp(np.clip(x, -500, 500)) - 1)) ​ def swish(x): return x * sigmoid(x) ​ def mish(x): return x * np.tanh(np.log1p(np.exp(np.clip(x, -500, 500))))) ​ def gelu(x): """GELU(近似公式),Transformer 架构常用""" return 0.5 * x * (1 + np.tanh(np.sqrt(2 / np.pi) * (x + 0.044715 * x**3))) ​ ​ # ============================================================ # 梯度定义 # ============================================================ def get_gradients(activations, x): """利用数值微分计算各激活函数的梯度(用于绘图)""" eps = 1e-7 return {name: (激活(x + eps) - 激活(x - eps)) / (2 * eps) for name, 激活 in activations.items()} ​ ​ # ============================================================ # 绘图 # ============================================================ x = np.linspace(-4, 4, 500) ​ activations = { "Sigmoid": sigmoid, "Tanh": tanh_func, "ReLU": relu, "Leaky ReLU": leaky_relu, "ELU": elu, "SeLU": selu, "Swish": swish, "Mish": mish, "GELU": gelu, } ​ fig, axes = plt.subplots(2, 1, figsize=(14, 10), sharex=True) ​ # 上图:激活函数曲线 ax1 = axes[0] colors = plt.cm.tab10(np.linspace(0, 1, len(activations))) for (name, func), color in zip(activations.items(), colors): ax1.plot(x, func(x), label=name, color=color, linewidth=2) ax1.axhline(0, color="black", linewidth=0.8, linestyle="-") ax1.axvline(0, color="black", linewidth=0.8, linestyle="-") ax1.set_title("常用激活函数对比", fontsize=14) ax1.set_ylabel("Activation Output", fontsize=11) ax1.legend(loc="lower right", ncol=3, fontsize=9) ax1.grid(True, alpha=0.3) ax1.set_ylim(-2.5, 3) ​ # 下图:梯度曲线 ax2 = axes[1] grads = get_gradients(activations, x) for (name, _), color in zip(activations.items(), colors): ax2.plot(x, grads[name], label=name, color=color, linewidth=2) ax2.axhline(0, color="black", linewidth=0.8, linestyle="-") ax2.axvline(0, color="black", linewidth=0.8, linestyle="-") ax2.set_title("激活函数梯度(导数)对比", fontsize=14) ax2.set_xlabel("x", fontsize=11) ax2.set_ylabel("Gradient (dy/dx)", fontsize=11) ax2.legend(loc="upper right", ncol=3, fontsize=9) ax2.grid(True, alpha=0.3) ax2.set_ylim(-0.5, 1.5) ​ plt.tight_layout() plt.savefig("activation_functions_comparison.png", dpi=150) plt.show() ​ print("图表已保存至 activation_functions_comparison.png")

运行说明:将上述代码保存为.py文件(如plot_activations.py),确保已安装numpymatplotlibpip install numpy matplotlib),直接运行即可生成对比图。


9. 总结

激活函数虽小,却是深度学习网络的"灵魂"所在。本文系统梳理了从经典 Sigmoid/Tanh 到现代 ReLU 家族以及 Swish/Mish 等自适应激活函数的数学原理与实现细节,并给出了按场景分类的选择建议。核心要点回顾:

  1. 隐藏层优先选 ReLU,快速建立基线,再根据 Dying ReLU 问题考虑 Leaky ReLU/ELU。

  2. 输出层根据任务选择:二分类用 Sigmoid,多分类用 Softmax,回归用恒等函数。

  3. 前沿网络(Transformer/GNN)推荐 GELU,追求更高精度可尝试 Mish/Swish。

  4. 数值稳定性不可忽视:所有涉及指数运算的激活函数(Sigmoid、ELU、Swish、Mish)都应做好输入裁剪,避免溢出。

  5. 激活函数 + BatchNorm 的顺序应严格遵循Linear → BatchNorm → Activation,否则效果会大打折扣。

掌握激活函数的原理与选型逻辑,是构建高效深度学习模型的必经之路。希望本文能为你提供一份完整、实用的参考指南。


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/16 21:07:32

2025 Summary: Preview

这篇作为[2025年末总结]的预告吧&#xff0c;2024的总结感兴趣可以见博客置顶文章 先叠一下甲&#xff0c;是普通学生 | 爱好流 | 编程之大学摸鱼法&#xff1a;上课用手机摸/睡觉&#xff0c;下课用电脑/熬夜摸… 上一篇文章Re:Algo - Starting Algorithms from Zero收到了一…

作者头像 李华
网站建设 2026/5/16 21:07:17

从零上手Lauterbach TRACE32:一站式软硬件安装与配置实战

1. 认识Lauterbach TRACE32&#xff1a;嵌入式开发的瑞士军刀 第一次接触TRACE32时&#xff0c;我被这个黑色工具箱震撼到了——它看起来像特工电影里的装备&#xff0c;实际上却是嵌入式开发者的终极武器。作为德国Lauterbach公司研发的调试系统&#xff0c;TRACE32在汽车电子…

作者头像 李华
网站建设 2026/5/16 21:07:13

阅读APP书源配置终极指南:3种高效导入方法快速获取海量小说资源

阅读APP书源配置终极指南&#xff1a;3种高效导入方法快速获取海量小说资源 【免费下载链接】Yuedu &#x1f4da;「阅读」自用书源分享 项目地址: https://gitcode.com/gh_mirrors/yu/Yuedu 阅读APP作为一款强大的开源小说阅读工具&#xff0c;本身不提供小说内容&…

作者头像 李华
网站建设 2026/5/16 21:06:21

AI 新闻周报 | 2026年5月第2周(5.10-5.16)

AI 新闻周报 | 2026年5月第2周&#xff08;5.10-5.16&#xff09; &#x1f4c5; 周期&#xff1a;2026年5月10日 - 5月16日 &#x1f4dd; 本周 AI 领域迎来密集重磅事件&#xff0c;技术、商业、政策多维度同步突破。OpenAI 免费开放 GPT-5.5 Instant 并上线广告平台&#xf…

作者头像 李华