深度学习 — 激活函数



一,基础概念

激活函数的作用是在隐藏层引入非线性,使得神经网络能够学习和表示复杂的函数关系,使网络具备非线性能力,增强其表达能力。

1.1 线性

  • 核心观点:如果神经网络的隐藏层不使用激活函数,整个网络将表现为一个线性模型,无法捕捉数据中的非线性关系。
  • 数学推导
  • 单层网络:输出为线性变换,即

对于单层网络(输入层到输出层),如果没有激活函数,输出 a ( 1 ) \mathbf{a}^{(1)} a(1) 可以表示为:
a ( 1 ) = W ( 1 ) x + b ( 1 ) \mathbf{a}^{(1)} = \mathbf{W}^{(1)} \mathbf{x} + \mathbf{b}^{(1)} a(1)=W(1)x+b(1)

  • 多层网络
    如果有L层,每层都没有激活函数,则第l层的输出为: a ( l ) = W ( l ) a ( l − 1 ) + b ( l ) \mathbf{a}^{(l)} = \mathbf{W}^{(l)} \mathbf{a}^{(l-1)} + \mathbf{b}^{(l)} a(l)=W(l)a(l1)+b(l)
  • 结论
    • 激活函数是引入非线性特性、使神经网络能够处理复杂问题的关键。
    • 没有激活函数的多层神经网络等价于一个单层线性模型。

1.2 非线性可视化

  • 工具:使用 TensorFlow Playground 进行可视化。
  • 可视化目的:通过可视化的方式理解非线性拟合能力。
  • 可视化工具的特点
    • 可以选择不同的数据集、调整训练与测试数据的比例。
    • 可以选择输入特征、调整权重和偏置。
    • 可以观察神经网络的输出,通过颜色显示数据点和预测值。
    • 提供了对网络结构(如隐藏层数量、激活函数等)的调整功能。
  • 可视化结果
    • 激活函数的使用使得神经网络能够捕捉数据中的非线性关系。
    • 不同的激活函数和网络结构对拟合能力有显著影响。
      在这里插入图片描述

1.3总结

  • 线性与非线性的重要性
    • 没有激活函数的神经网络只能处理线性问题,而现实世界中的许多问题是非线性的。
    • 激活函数是神经网络能够处理复杂非线性问题的关键。
  • 可视化工具的作用
    • TensorFlow Playground 提供了一个直观的方式来理解神经网络的非线性拟合能力。
    • 通过调整网络结构和激活函数,可以观察到不同的拟合效果,从而更好地理解神经网络的工作原理。

二,常见的激活函数

激活函数通过引入非线性来增强神经网络的表达能力,对于解决线性模型的局限性至关重要。由于反向传播算法(BP)用于更新网络参数,因此激活函数必须是可微的,也就是说能够求导的。

2.1 sigmoid

Sigmoid激活函数是一种常见的非线性激活函数,特别是在早期神经网络中应用广泛。它将输入映射到0到1之间的值,因此非常适合处理概率问题。

方面 内容
公式 f ( x ) = σ ( x ) = 1 1 + e − x f(x) = \sigma(x) = \frac{1}{1 + e^{-x}} f(x)=σ(x)=1+ex1
特征 1. 将任意实数输入映射到 (0, 1) 之间,适合处理概率场景。
2. 一般用于二分类输出层。
3. 导数计算方便,可以用自身表达式表示: σ ′ ( x ) = σ ( x ) ⋅ ( 1 − σ ( x ) ) \sigma'(x) = \sigma(x) \cdot (1 - \sigma(x)) σ(x)=σ(x)(1σ(x))
优点 1. 输出范围在 (0, 1) 之间,适合处理概率问题。
2. 导数计算简单,便于反向传播。
缺点 1. 梯度消失:输入非常大或非常小时,梯度接近于0,导致早期层权重更新缓慢,训练速度变慢甚至停滞。
2. 信息丢失:输入值相差很大时,激活值几乎相同(如输入100和10000的激活值都接近1)。
3. 计算成本高:涉及指数运算,计算复杂度比ReLU等函数高。
import torch
import matplotlib.pyplot as plt

# plt支持中文
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False


def test001():
    # 一行两列绘制图像
    _, ax = plt.subplots(1, 2)
    # 绘制函数图像
    x = torch.linspace(-10, 10, 100)
    y = torch.sigmoid(x)
    # 网格
    ax[0].grid(True)
    ax[0].set_title("sigmoid 函数曲线图")
    ax[0].set_xlabel("x")
    ax[0].set_ylabel("y")
    # 在第一行第一列绘制sigmoid函数曲线图
    ax[0].plot(x, y)

    # 绘制sigmoid导数曲线图
    x = torch.linspace(-10, 10, 100, requires_grad=True)
    # y = torch.sigmoid(x) * (1 - torch.sigmoid(x))
    # 自动求导
    torch.sigmoid(x).sum().backward()
    ax[1].grid(True)
    ax[1].set_title("sigmoid 函数导数曲线图", color="red")
    ax[1].set_xlabel("x")
    ax[1].set_ylabel("y")
    # ax[1].plot(x.detach().numpy(), y.detach())
    # 用自动求导的结果绘制曲线图
    ax[1].plot(x.detach().numpy(), x.grad.detach().numpy())
    # 设置曲线颜色
    ax[1].lines[0].set_color("red")

    plt.show()


if __name__ == "__main__":
    test001()

在这里插入图片描述

2.2 tanh

tanh(双曲正切)是一种常见的非线性激活函数,常用于神经网络的隐藏层。tanh 函数也是一种S形曲线,输出范围为 ( − 1 , 1 ) (−1,1) (1,1)

方面 内容
公式 tanh ( x ) = e x − e − x e x + e − x \text{tanh}(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}} tanh(x)=ex+exexex
特征 1. 输出范围在 ( − 1 , 1 ) (-1, 1) (1,1) 之间,零中心化输出有助于加速收敛。
2. 是关于原点对称的奇函数,输入为0时输出也为0,有助于数据平衡。
3. 全局连续可微,导数公式为: d d x tanh ( x ) = 1 − tanh 2 ( x ) \frac{d}{dx} \text{tanh}(x) = 1 - \text{tanh}^2(x) dxdtanh(x)=1tanh2(x)
优点 1. 输出零中心化,加速收敛。
2. 对称性有助于数据平衡。
3. 全局可微,适合梯度下降优化。
缺点 1. 梯度消失:输入值非常大或非常小时,导数接近于0,深层网络中仍会导致训练缓慢甚至无法收敛。
2. 计算成本:涉及指数运算,计算复杂度较高,尽管与 Sigmoid 相比差异不大。
import torch
import matplotlib.pyplot as plt

# plt支持中文
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False


def test001():
    # 一行两列绘制图像
    _, ax = plt.subplots(1, 2)
    # 绘制函数图像
    x = torch.linspace(-10, 10, 100)
    y = torch.tanh(x)
    # 网格
    ax[0].grid(True)
    ax[0].set_title("tanh 函数曲线图")
    ax[0].set_xlabel("x")
    ax[0].set_ylabel("y")
    # 在第一行第一列绘制tanh函数曲线图
    ax[0].plot(x, y)

    # 绘制tanh导数曲线图
    x = torch.linspace(-10, 10, 100, requires_grad=True)
    # y = torch.tanh(x) * (1 - torch.tanh(x))
    # 自动求导:需要标量才能反向传播
    torch.tanh(x).sum().backward()
    ax[1].grid(True)
    ax[1].set_title("tanh 函数导数曲线图", color="red")
    ax[1].set_xlabel("x")
    ax[1].set_ylabel("x.grad")
    # ax[1].plot(x.detach().numpy(), y.detach())
    # 用自动求导的结果绘制曲线图
    ax[1].plot(x.detach().numpy(), x.grad.detach().numpy())
    # 设置曲线颜色
    ax[1].lines[0].set_color("red")

    plt.show()


if __name__ == "__main__":
    test001()

在这里插入图片描述

2.3 Relu

ReLU(Rectified Linear Unit)是深度学习中最常用的激活函数之一,它的全称是修正线性单元。ReLU 激活函数的定义非常简单,但在实践中效果非常好。

方面 内容
定义 ReLU ( x ) = max ⁡ ( 0 , x ) \text{ReLU}(x) = \max(0, x) ReLU(x)=max(0,x)
特征 1. 当 x > 0 x > 0 x>0 时,ReLU(x) = x
2. 当 x ≤ 0 x \leq 0 x0 时,ReLU(x) = 0
3. 导数是分段函数: ReLU ′ ( x ) = { 1 , if  x > 0 0 , if  x ≤ 0 \text{ReLU}'(x) = \begin{cases} 1, & \text{if } x > 0 \\ 0, & \text{if } x \leq 0 \end{cases} ReLU(x)={1,0,if x>0if x0
4. 计算简单,只需要一次比较运算。
优点 1. 计算简单:加速神经网络的训练。
2. 缓解梯度消失问题:在正半区导数恒为1,不存在饱和问题,有利于深层网络的梯度传播。
3. 稀疏激活:引入稀疏性,减少冗余信息,提高效率和泛化能力。
缺点 1. 神经元死亡问题:当输入小于等于0时,神经元输出为0,且导数为0,可能导致神经元永远不再激活,降低模型的表达能力。
import torch
import torch.nn.functional as F
import matplotlib.pyplot as plt

# 中文问题
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False


def test006():
    # 输入数据x
    x = torch.linspace(-20, 20, 1000)
    y = F.relu(x)
    # 绘制一行2列
    _, ax = plt.subplots(1, 2)
    ax[0].plot(x.numpy(), y.numpy())
    # 显示坐标格子
    ax[0].grid()
    ax[0].set_title("relu 激活函数")
    ax[0].set_xlabel("x")
    ax[0].set_ylabel("y")

    # 绘制导数函数
    x = torch.linspace(-20, 20, 1000, requires_grad=True)
    F.relu(x).sum().backward()
    ax[1].plot(x.detach().numpy(), x.grad.numpy())
    ax[1].grid()
    ax[1].set_title("relu 激活函数导数", color="red")
    # 设置绘制线色颜色
    ax[1].lines[0].set_color("red")
    ax[1].set_xlabel("x")
    ax[1].set_ylabel("x.grad")

    plt.show()


if __name__ == "__main__":
    test006()

在这里插入图片描述

2.4 LeakyRelu

Leaky ReLU是一种对 ReLU 函数的改进,旨在解决 ReLU 的一些缺点,特别是Dying ReLU 问题。Leaky ReLU 通过在输入为负时引入一个小的负斜率来改善这一问题。

方面 内容
定义 Leaky ReLU ( x ) = { x , if  x > 0 α x , if  x ≤ 0 \text{Leaky ReLU}(x) = \begin{cases} x, & \text{if } x > 0 \\ \alpha x, & \text{if } x \leq 0 \end{cases} Leaky ReLU(x)={x,αx,if x>0if x0
其中, α \alpha α 是一个非常小的常数(如 0.01),控制负半轴的斜率。
特征 1. 当 x > 0 x > 0 x>0 时,Leaky ReLU(x) = x。
2. 当 x ≤ 0 x \leq 0 x0 时,Leaky ReLU(x) = α x \alpha x αx,其中 α \alpha α 是一个小的负斜率。
3. 计算简单,与 ReLU 类似,只需简单的比较和线性运算。
优点 1. 避免神经元死亡:通过在 x ≤ 0 x \leq 0 x0 区域引入一个小的负斜率,即使输入值小于等于零,Leaky ReLU 仍然会有梯度,允许神经元继续更新权重,避免神经元在训练过程中完全“死亡”的问题。
2. 计算简单:与 ReLU 类似,计算开销低。
缺点 1. 参数选择 α \alpha α 是一个需要调整的超参数,选择合适的 α \alpha α 值可能需要实验和调优。
2. 可能出现负激活:如果 α \alpha α 设定得不当,仍然可能导致激活值过低,影响网络的性能。
import torch
import torch.nn.functional as F
import matplotlib.pyplot as plt

# 中文设置
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False


def test006():
    x = torch.linspace(-5, 5, 200)
    # 设置leaky_relu的负斜率超参数
    slope = 0.03
    y = F.leaky_relu(x, slope)
    # 一行两列
    _, ax = plt.subplots(1, 2)
    # 开始绘制函数曲线图
    ax[0].plot(x, y)
    ax[0].set_title("Leaky ReLU 函数曲线图")
    ax[0].set_xlabel("x")
    ax[0].set_ylabel("y")
    ax[0].grid(True)

    # 绘制leaky_relu的梯度曲线图
    x = torch.linspace(-5, 5, 200, requires_grad=True)
    F.leaky_relu(x, slope).sum().backward()
    ax[1].plot(x.detach().numpy(), x.grad)
    ax[1].set_title("Leaky ReLU 梯度曲线图", color="red")
    ax[1].set_xlabel("x")
    ax[1].set_ylabel("x.grad")
    ax[1].grid(True)
    # 设置线的颜色
    ax[1].lines[0].set_color("red")

    plt.show()


if __name__ == "__main__":
    test006()

在这里插入图片描述

2.5 softmax

Softmax激活函数通常用于分类问题的输出层,它能够将网络的输出转换为概率分布,使得输出的各个类别的概率之和为 1。Softmax 特别适合用于多分类问题。

方面 内容
定义 S o f t m a x ( z i ) = e z i ∑ j = 1 n e z j \mathrm{Softmax}(z_i) = \frac{e^{z_i}}{\sum_{j=1}^n e^{z_j}} Softmax(zi)=j=1nezjezi
特征 1. 将输出转化为概率分布,每个输出值在 (0, 1) 之间,且所有输出值之和为 1。
2. 突出差异,使得概率最大的类别的输出值更接近 1,其他类别更接近 0。
3. 常与交叉熵损失函数结合使用,用于多分类问题。
4. 导数计算:当 i = j i = j i=j 时, ∂ p i ∂ z i = p i ( 1 − p i ) \frac{\partial p_i}{\partial z_i} = p_i(1 - p_i) zipi=pi(1pi);当 i ≠ j i \neq j i=j 时, ∂ p i ∂ z j = − p i p j \frac{\partial p_i}{\partial z_j} = -p_i p_j zjpi=pipj
优点 1. 概率化输出:将网络输出转化为概率分布,便于分类决策。
2. 突出差异:放大差异,使得最大概率值更接近 1,其他值更接近 0。
3. 数学性质良好:导数计算简单,适合多分类问题。
缺点 1. 数值不稳定性:当 z i z_i zi 的数值过大时, e z i e^{z_i} ezi 可能导致数值溢出。可以通过减去最大值 max ⁡ ( z ) \max(z) max(z) 来解决: S o f t m a x ( z i ) = e z i − max ⁡ ( z ) ∑ j = 1 n e z j − max ⁡ ( z ) \mathrm{Softmax}(z_i) = \frac{e^{z_i - \max(z)}}{\sum_{j=1}^n e^{z_j - \max(z)}} Softmax(zi)=j=1nezjmax(z)ezimax(z)
2. 计算开销大:在处理大量类别时(如大词汇表),计算开销较大。
import torch
import torch.nn as nn

# 表示4分类,每个样本全连接后得到4个得分,下面示例模拟的是两个样本的得分
input_tensor = torch.tensor([[-1.0, 2.0, -3.0, 4.0], [-2, 3, -3, 9]])

softmax = nn.Softmax()
output_tensor = softmax(input_tensor)
# 关闭科学计数法
torch.set_printoptions(sci_mode=False)
print("输入张量:", input_tensor)
print("输出张量:", output_tensor)
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐