68·深度学习入门进阶

激活函数:ReLU、Sigmoid 与 Softmax

deep-learningactivation

第68课 激活函数:ReLU、Sigmoid 与 Softmax

1. 学习目标

  • 理解激活函数在神经网络中的核心作用
  • 掌握 Sigmoid、ReLU 和 Softmax 三种常见激活函数的数学特性与适用场景
  • 能够使用 Python 实现基本激活函数的前向传播计算
  • 了解选择激活函数时的常见考量因素

2. 核心概念

激活函数是什么?

想象一下,神经网络的每一层都在对输入数据进行线性变换(加权求和)。如果只有线性变换,那么无论堆叠多少层,整个网络仍然只能表示一个线性函数,这在处理复杂问题(如图像识别)时是远远不够的。

激活函数 就是为网络引入非线性的关键组件。它就像是神经元的“决策开关”,决定这个神经元是否应该被激活(即,它的输出信号有多强)。

三种主流激活函数

1. Sigmoid 函数

Sigmoid 函数曾是深度学习早期的宠儿,它能将任何实数输入“挤压”到 (0, 1) 的区间内,形状像一个平滑的“S”。

import numpy as np
import matplotlib.pyplot as plt

# Sigmoid 函数
def sigmoid(z):
    return 1 / (1 + np.exp(-z))

优点: 输出在 (0, 1) 之间,解释为概率,非常适合二分类问题的输出层。 缺点:

  • 梯度消失: 当输入值非常大或非常小时,其梯度(导数)接近于 0,导致反向传播时参数几乎不更新,网络训练缓慢甚至停滞。
  • 输出非零中心化: 输出总是正数,可能导致后续层的梯度更新出现锯齿状振荡。

2. ReLU (Rectified Linear Unit) 函数

ReLU 是当前深度学习中最常用的激活函数之一。它的规则非常简单:如果输入大于 0,输出就是它本身;如果输入小于等于 0,输出就是 0。

# ReLU 函数
def relu(z):
    return np.maximum(0, z)

优点:

  • 计算高效: 只需一个简单的判断,比 Sigmoid 的指数运算快得多。
  • 有效缓解梯度消失: 在正区间内,梯度恒为 1,有利于梯度的反向传播。 缺点:
  • Dead ReLU(神经元死亡): 如果一个神经元的输入持续为负,它的输出恒为 0,梯度也为 0,导致它再也无法被激活。通过使用 Leaky ReLU 等变体可以缓解此问题。

3. Softmax 函数

Softmax 不是为单个神经元设计的,而是为一个输出层设计的。它将一个包含任意实数的向量,转换为一个概率分布,其中每个元素的值都在 (0, 1) 之间,并且所有元素的和为 1。

# Softmax 函数
def softmax(z):
    # 减去最大值是为防止数值溢出(指数爆炸)
    e_z = np.exp(z - np.max(z))
    return e_z / e_z.sum(axis=0)

应用场景: 它是多分类问题(例如,识别图片是猫、狗还是鸟)输出层的标准选择

3. 代码示例:神经网络前向传播中的激活函数

下面我们通过一个简单的单隐藏层神经网络,来直观感受这三种激活函数的计算过程。

import numpy as np

# 1. 模拟输入数据 (2个样本,每个样本3个特征)
X = np.array([[0.5, -0.2, 0.1],
              [0.1, 0.8, -0.3]])
print(f"输入数据 X:\n{X}\n")

# 2. 定义第一层的权重和偏置 (假设隐藏层有4个神经元)
W1 = np.random.randn(3, 4)  # 形状 (输入特征数, 隐藏神经元数)
b1 = np.zeros((1, 4))       # 形状 (1, 隐藏神经元数)

# 3. 第一层的线性变换
Z1 = np.dot(X, W1) + b1     # 形状 (样本数, 隐藏神经元数)
print(f"隐藏层线性输出 Z1:\n{Z1}\n")

# 4. 分别应用不同的激活函数,观察输出变化
# 情况A:使用 Sigmoid
A1_sigmoid = sigmoid(Z1)
print(f"经过 Sigmoid 激活:\n{A1_sigmoid}\n")

# 情况B:使用 ReLU
A1_relu = relu(Z1)
print(f"经过 ReLU 激活:\n{A1_relu}\n")

# 5. 模拟第二层(输出层)的权重和偏置,用于多分类
W2 = np.random.randn(4, 3)  # 假设输出3个类别
b2 = np.zeros((1, 3))

# 使用 ReLU 激活后的结果作为下一层的输入
Z2 = np.dot(A1_relu, W2) + b2
print(f"输出层线性输出 Z2:\n{Z2}\n")

# 6. 输出层使用 Softmax
A2_softmax = softmax(Z2)    # 注意:这里对每个样本的输出向量分别应用
print(f"经过 Softmax 激活 (多分类概率分布):\n{A2_softmax}\n")
print(f"每个样本的预测概率和: {A2_softmax.sum(axis=1)}")

代码解读:

  1. 我们首先模拟了一个简单的数据集 X
  2. 第一层(隐藏层)进行线性变换得到 Z1
  3. 关键步骤:Z1 分别应用 Sigmoid 和 ReLU,得到 A1_sigmoidA1_relu。你会看到,经过激活后,数据的分布完全改变了(ReLU 将所有负值变为 0)。
  4. A1_relu 作为输入,经过第二层的线性变换得到 Z2
  5. 最后,在输出层应用 Softmax,将原始的分数 Z2 转换为三个类别的概率分布。

4. 实践练习

练习1:计算激活值

给定输入 z = [-1.0, 0.0, 0.5, 2.0],请分别计算:

  1. Sigmoid(z)
  2. ReLU(z)
  3. (思考题) 如果这是一个二分类问题的输出层,使用哪个函数更合适?为什么?

练习2:模拟神经元

编写一个简单的 Neuron 类,包含 __init__, forward 方法。初始化时接受输入维度 input_dim 和激活函数类型 activation (‘sigmoid’, ‘relu’)。在 forward 方法中实现 z = w*x + b 和激活函数的计算。

# 你的代码框架
class Neuron:
    def __init__(self, input_dim, activation=’sigmoid’):
        # 初始化权重 w 和偏置 b (随机初始化)
        pass

    def forward(self, x):
        # 计算线性变换和激活
        pass

# 测试你的神经元
neuron = Neuron(2, activation=’relu’)
x_test = np.array([0.5, -0.3])
print(neuron.forward(x_test))

练习3:设计激活函数选择

根据以下场景,你会为输出层选择哪种激活函数?请给出理由。

  1. 预测一张图片是“猫”还是“狗”(二分类)。
  2. 预测明天股票的确切价格(回归问题)。
  3. 同时预测一张图片中的物体是“猫”、“狗”、“鸟”中的哪一个(多分类)。

5. 常见错误

  1. 在输出层误用激活函数:

    • 错误: 对多分类问题输出层使用 Sigmoid。
    • 后果: 输出的各类别概率之和不为1,无法解释为概率分布。
    • 正确做法: 二分类输出层用 Sigmoid,多分类输出层用 Softmax。
  2. 忽略 Dead ReLU 问题:

    • 表现: 网络部分神经元输出恒为0,训练 loss 不再下降。
    • 缓解方法: 使用 Leaky ReLU (max(0.01*z, z)) 或调整学习率、权重初始化策略。
  3. 混淆激活函数与损失函数:

    • Sigmoid/Softmax输出层的激活函数,用于产生预测。
    • 二元交叉熵/交叉熵损失函数,用于衡量预测与真实标签的差距。两者在数学上常常配对使用,但概念不同。

6. 小结

  • 激活函数为神经网络注入非线性,使其能够拟合复杂的函数关系。
  • Sigmoid:输出 (0,1),有梯度消失风险,适用于二分类输出层。
  • ReLU:计算简单,梯度在正区间恒为1,是当前隐藏层的主流选择,但需注意 Dead ReLU。
  • Softmax:将输出向量转换为概率分布,是多分类问题输出层的标准配置。
  • 选择激活函数时,需要综合考虑问题类型(分类/回归)、网络位置(隐藏层/输出层)以及计算效率和训练稳定性

下一课,我们将学习如何用损失函数来量化模型预测的误差,并通过优化器(如SGD, Adam)利用误差来更新网络参数,这是训练模型的核心动力。

练习编辑器

rust
Loading...

继续学习

完成本课后,建议继续学习下一课「损失函数与优化器」 以巩固所学知识。