深度学习|激活函数:网络表达增强
引言
在前文对 M-P 神经元结构的介绍以及「深度学习|模型推理:端到端任务处理」的推理过程演示中,我 们反复提到了激活函数
的概念,它决定了神经元的输出,在神经网络中起着至关重要的作用。激活函数的选择直接影响模型的性能和学习能力。
激活函数是一种用于引入非线性特性的数学函数,用于将加权和转换为更复杂的输出,使神经网络能够学习复杂的模式。通过引入非线性特性,可以解决非线性问题(否则神经网络的输出只是输入的线性组合,无法处理复杂的非线性问题),同时增强网络的表达能力,让其可以处理更复杂的问题。
激活函数对梯度的传播也很重要,一些激活函数(如
ReLU
)具有稀疏激活
性质,可以避免梯度消失问题
,有利于更好的传播误差和加快收敛
速度等作用。这些好的性质我们都将在后文对深度学习的进一步探讨中呈现给大家。
常见的激活函数
激活函数的选择往往根据求解问题的性质决定,一般回归问题可以使用恒等函数
,二分类问题可以使用 sigmoid
函数,多分类问题可以 使用 softmax
函数。
前文的手写数字识别任务中,输出层与隐层所用的激活函数 () 就有所不同。隐层使用了 ReLU
函数,输出层使用了 softmax
函数,我们将这些激活函数一一展开介绍。
依赖模块
我们会演示实现激活函数的
单个数据
运算的版本和批量数据
运算的版本,其中批量计算将采用对矩阵运算有底层(甚至硬件层面)优化的numpy
模块来实现;同时我们会使用 matplotlib 模块绘制各个激活函数的图形。此处表示全局引入 numpy 与 matplotlib 这两个模块,后文将不再提及。
import numpy as np
import matplotlib.pyplot as plt
激活函数图形绘制
我们将统一使用如下的
draw
函数来绘制激活函数的图形:def draw(X, activation_func, title, color='orange'):
"""
绘制激活函数图形
:param X: 输入数据
:param activation_func: 激活函数
:param title: 图形标题
:param color: 图形颜色
"""
# 创建一个新的图形,figsize 参数指定图形的大小为 6 x 3
plt.figure(figsize=(6, 3))
Y = activation_func(X)
plt.plot(X, Y, label=title, color=color)
plt.title(title)
plt.grid()
plt.xlabel('Input')
plt.ylabel('Output')
绘图数据准备
创建一批激活函数的输入数据 X,我们可以用它们进行激活函数的功能验证,同时将 X 的元素与对应的输出 Y 的元素构成的点描绘到坐标轴上,即可绘制出激活函数的图形。
# 创建一个从 -10 到 10 的线性空间,共 400 个数据点
X = np.linspace(-10, 10, 400)
阶跃函数
阶跃函数
以阈值为界,输入超过阈值则切换输出。如式 1 所示就是阶跃函数,它以 0 为界,小于 0 时输出 0,超过 0 时输出 1。
式 1 的 Python 代码实现如下:
def step_single(x):
"""阶跃函数(单个数据计算)"""
return 1 if x > 0 else 0
def step(x):
"""阶跃函数(批量数据计算)"""
return np.array(x > 0, dtype=np.int32)
绘制式 1 的图像:
draw(X, step, 'Step Function')
可以看到,阶跃函数是一条由 0 突变到 1 的曲线,其输出值只有 0 和 1 两种情况,这种函数在神经网络中的应用受到了限制,因为它不是连续的,也不可导。
感知机使用了阶跃函数作为激活函数,因此感知机的神经元之间流动的只能是 0 或 1 的二元信号。
Sigmoid
Sigmoid
函数是一种可以将输入信号转换为 0 到 1 之间的平滑输出信号的函数,其图形是一条如图 2 所示的平滑曲线,其数学式表示为式 2:
Sigmoid 函数的 Python 代码实现如下:
def sigmoid_single(x):
"""Sigmoid 函数(单个数据计算)"""
return 1 / (1 + math.exp(-x))
def sigmoid(x):
"""Sigmoid 函数(批量数据计算)"""
return 1 / (1 + np.exp(-x))
绘制 Sigmoid 函数的图像:
draw(X, sigmoid, 'Sigmoid Function')
Sigmoid 函数是一种常用的激活函数,它将输入信号转换为 (0, 1) 之间的输出,易于理解和解释。神经网络的隐层常使用 Sigmoid 函数作为激活函数,这使得神经元之间流动的是连续的实数值信号,这对神经网络的 BP 算法学习具有重要意义。
但 Sigmoid 函数也常常是导致梯度消失(vanishing gradient)问题的主因,可能影响深层网络的训练。
阶跃函数与 Sigmoid 函数的共同点是,当输入信号较小时(不重要的信号),输出接近 0,输入变大时(重要的信号),输出接近于 1,输出信号都介于 0 至 1 之间。
阶跃函数与 Sigmoid 函数都是非线性函数,这也是激活函数的基本要求,若使用线性函数做激活函数,将无法发挥多层神经网络的优势。比如三层神经网络使用线性函数作为激活函数,其运算可近似用 来表示,那与用 表示的单层网络是等效的。个中究竟,我们将在后续细说。
ReLU
ReLU
(Rectified Linear Unit
,线性整流函数 ),在输入大于 0 时,直接输出该值,输入小于等于 0 时直接输出 0。ReLU 激活函数主要用于避免神经网络学习过程中的梯度消失与梯度爆炸,其定义为式 3:
或简化为:
ReLU 函数的 Python 代码实现如下:
def relu_single(x):
"""ReLU 函数(单个数据计算)"""
return max(0, x)
def relu(x):
"""ReLU 函数(批量数据计算)"""
return np.maximum(0, x)
绘制 ReLU 函数的图像:
draw(X, relu, 'Relu Function')
ReLU 函数在正区间中输出是线性的,负区间输出为 0,计算简单,收敛速度快,被广泛使用。不过 ReLU 函数可能导致“死亡 ReLU”问题,即一些神经元在训练过程中始终输出 0。
Leaky ReLU
Leaky ReLU 是对 ReLU 的改进,当输入小于 0 时,输出一个很小的值 ,其中 是一个小的常数(例如 0.01)。Leaky ReLU 函数的定义为式 4:
其中 是一个小的常数(例如 0.01)。
Leaky ReLU 函数的 Python 代码实现如下:
def leaky_relu_single(x, alpha=0.01):
"""Leaky ReLU 函数(单个数据计算)"""
return x if x > 0 else alpha * x
# 4. Leaky ReLU
def leaky_relu(x, alpha=0.01):
"""Leaky ReLU 函数(批量数据计算)"""
return np.where(x > 0, x, alpha * x)
绘制 Leaky ReLU 函数的图像:
draw(X, leaky_relu, 'Leaky ReLU Function')
Leaky ReLU 解决了 ReLU 的“死亡”问题,在负区间提供了一个小的线性输出。
Softmax
Softmax
(归一化指数函数)将 n 个元素转换成和为 1,取值在 0.0 到 1.0 之间的概率分布表示,用于计算这 n 个元素中,第 k 个元素的概率。其定义为式 5:
Softmax 函数的 Python 代码实现如下:
def softmax_single(x):
"""Softmax 函数(单个数据计算)"""
e_x = np.exp(x - np.max(x)) # 减去最大值以提高数值稳定性
return e_x / e_x.sum()
def softmax(x):
"""Softmax 函数(批量数据计算)"""
e_x = np.exp(x - np.max(x)) # 减去最大值以提高数值稳定性
return e_x / e_x.sum(axis=0)
绘制 Softmax 函数的图像:
draw(X, softmax, 'Softmax Function')
Softmax 输出值在 (0, 1) 之间,且所有输出的和为 1,可用于将输入转换为概率分布。
Softmax 函数多用于多分类任务的神经网络学习阶段的输出层激活函数,一般输出时只取 Softmax 的值最大(最大概率)的项。推理阶段则可以省略 Softmax 的计算。
Tanh
Tanh
(双曲正切函数)将输入信号转换为 -1 到 1 之间的输出信号,其数学式表示为式 6:
Tanh 函数的 Python 代码实现如下:
def tanh_single(x):
"""Tanh 函数(单个数据计算)"""
return (math.exp(x) - math.exp(-x)) / (math.exp(x) + math.exp(-x))
def tanh(x):
"""Tanh 函数(批量数据计算)"""
return np.tanh(x)
绘制 Tanh 函数的图像:
draw(X, tanh, 'Tanh Function')
Tanh 函数输出范围在 (-1, 1) 之间,通常比 Sigmoid 函数更好。但仍然可能受到梯度消失的问题,不过影响要小一些。
恒等函数
恒等函数将输入按原样输出,常用于回归问题。
def identity(x):
"""恒等函数"""
return x