激活函数

激活函数

1. Sigmoid

原理:
Sigmoid 函数将一个实数输入映射到 (0, 1) 区间。其数学公式为:

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

在图像分割中,它主要有两个用途:

  1. 二分类问题的最后一层:将每个像素的 logit 值转换为一个概率,表示该像素属于“前景”的概率。
  2. 门控机制:在诸如 U-Net 的跳跃连接中,有时会使用 Sigmoid 作为注意力门(Attention Gate)的激活函数。

优点:

  • 输出范围在 0 到 1 之间,非常适合表示概率。

缺点:

  • 梯度消失:当输入值非常大或非常小时,函数的梯度会接近于 0,导致权重更新缓慢。
  • 输出不是零中心的:这可能导致后续层的输入始终为正,使得梯度更新呈锯齿状,影响收敛速度。
  • 计算成本相对较高。

PyTorch 代码实现:

import torch
import torch.nn as nn
import matplotlib.pyplot as plt

# 方式1:使用 torch.sigmoid 函数
x = torch.randn(5)
output = torch.sigmoid(x)
print(f"Input: {x}")
print(f"Sigmoid Output: {output}")

# 方式2:使用 nn.Sigmoid 模块
sigmoid_layer = nn.Sigmoid()
output_tensor = sigmoid_layer(x)

# 可视化
x = torch.linspace(-10, 10, 100)
y = torch.sigmoid(x)
plt.figure(figsize=(8, 5))
plt.plot(x.numpy(), y.numpy(), label='Sigmoid', lw=3)
plt.title("Sigmoid Activation Function")
plt.xlabel("Input")
plt.ylabel("Output")
plt.grid(True)
plt.legend()
plt.show()

2. Softmax

原理:
Softmax 函数是 Sigmoid 函数在多分类问题上的推广。它将一个包含任意实数的 K 维向量 z “压缩”为另一个 K 维向量 \(\sigma(z)\),使得每个元素的范围都在 (0, 1) 之间,并且所有元素的和为 1。其数学公式为:

\(\sigma(z)*i = \frac{e^{z_i}}{\sum*{j=1}^{K} e^{z_j}} \quad \text{for } i=1,2,\dots,K\)

在图像分割中,它几乎专门用于多分类(类别数 > 2)分割网络的最后一层。对于每个像素位置,它接收所有 C 个类别(通道)的 logits,并输出一个 C 维的概率分布,表示该像素属于每个类别的概率。

优点:

  • 直接将输出解释为概率分布,非常直观。
  • 是多分类分割任务的标准选择。

缺点:

  • 计算成本较高。
  • 可能存在数值不稳定性(通常通过 LogSoftmax 和 nn.CrossEntropyLoss 的组合来规避)。

PyTorch 代码实现:

import torch
import torch.nn as nn

# 模拟一个分割网络的输出: [BatchSize, Num_Classes, Height, Width]
# 例如:1张图片,3个类别(背景、类别1、类别2),图像尺寸2x2
logits = torch.randn(1, 3, 2, 2) * 5 # 未经激活的原始输出
print("Raw Logits (per pixel for 3 channels):")
print(logits)

# 应用Softmax
softmax_layer = nn.Softmax(dim=1) # 在通道维度(C)上进行Softmax
probabilities = softmax_layer(logits)

print("\nProbabilities after Softmax (sum over channels=1 for each pixel):")
print(probabilities)
print(f"Sum of probabilities for first pixel: {probabilities[0, :, 0, 0].sum().item()}")

# 通常训练时,我们使用CrossEntropyLoss,它内部已经结合了LogSoftmax和NLLLoss,数值更稳定。
# criterion = nn.CrossEntropyLoss() # 常用损失函数
# loss = criterion(logits, target) # target是标签图,不需要是one-hot格式

3. ReLU (Rectified Linear Unit)

原理:
ReLU 是目前深度学习中最常用、最基础的激活函数。其公式非常简单:

\(f(x) = \max(0, x)\)

它将所有负值置为零,而正值保持不变。

优点:

  • 计算效率极高:只有比较和赋值操作。
  • 缓解梯度消失:在正区间梯度恒为 1,有效解决了 Sigmoid/Tanh 的梯度消失问题,加速模型收敛。
  • 产生稀疏性:负值输出为 0,使得网络变得稀疏,减少了参数间的相互依赖性。

缺点:

  • Dying ReLU 问题:如果输入始终为负,其梯度为 0,相应的神经元将“死亡”(永远无法被激活),权重也无法更新。

PyTorch 代码实现:

import torch.nn as nn

relu_layer = nn.ReLU(inplace=True) # inplace=True 可以节省内存,但需谨慎使用
input_tensor = torch.tensor([-2., -1., 0., 1., 2.])
output = relu_layer(input_tensor)
print(f"Input: {input_tensor}")
print(f"ReLU Output: {output}") # 输出: tensor([0., 0., 0., 1., 2.])

4. Leaky ReLU

原理:
为了解决 ReLU 的“死亡”问题,Leaky ReLU 被提出。它为负输入值引入了一个很小的负斜率(通常为 0.01),而不是直接置为零。

\(f(x) = \begin{cases} x & \text{if } x > 0 \ \alpha x & \text{if } x \leq 0 \end{cases}\)

优点:

  • 解决了“Dying ReLU”问题,负值区域也有一个很小的梯度,确保神经元在训练过程中至少能被小幅更新。
  • 仍然保持了 ReLU 计算高效的优点。

缺点:

  • 需要手动选择或学习超参数 \(\alpha\)(尽管 0.01 通常效果不错)。

PyTorch 代码实现:

leaky_relu_layer = nn.LeakyReLU(negative_slope=0.01, inplace=True)
input_tensor = torch.tensor([-2., -1., 0., 1., 2.])
output = leaky_relu_layer(input_tensor)
print(f"Input: {input_tensor}")
print(f"LeakyReLU Output: {output}") # 输出: tensor([-0.0200, -0.0100,  0.0000,  1.0000,  2.0000])

变种:PReLU (Parametric ReLU) 将 \(\alpha\) 作为一个可学习的参数,让网络自己决定负斜率的幅度。

prelu_layer = nn.PReLU(num_parameters=1, init=0.01) # num_parameters=1表示所有通道共享一个参数

5. ELU (Exponential Linear Unit)

原理:
ELU 是另一种解决“Dying ReLU”问题的方法。它对负值的处理是使用一个指数函数,使其平滑地渐近于一个负值 \(-\alpha\)。

\(f(x) = \begin{cases} x & \text{if } x > 0 \ \alpha (e^x - 1) & \text{if } x \leq 0 \end{cases}\)

优点:

  • 比 Leaky ReLU 更平滑,可能带来更好的学习效果。
  • 输出均值更接近 0,类似于批归一化(BatchNorm)的效果,可能加速训练。

缺点:

  • 由于使用了指数运算,计算成本比 ReLU 和 Leaky ReLU 更高。

PyTorch 代码实现:

elu_layer = nn.ELU(alpha=1.0, inplace=True) # alpha α 默认为1.0
input_tensor = torch.tensor([-2., -1., 0., 1., 2.])
output = elu_layer(input_tensor)
print(f"Input: {input_tensor}")
print(f"ELU Output: {output}") # 输出: tensor([-0.8647, -0.6321,  0.0000,  1.0000,  2.0000])

6. Swish

原理:
Swish 是由 Google 研究人员发现的一个自门控(self-gated)激活函数,其公式为:

\(f(x) = x \cdot \sigma(\beta x)\)

其中 \(\sigma\) 是 Sigmoid 函数,\(\beta\) 是一个可学习或固定的参数(通常默认为 1)。它可以被简单地理解为“用 Sigmoid 函数来自动缩放输入”。

优点:

  • 在许多深度学习任务(包括一些分割任务)上,其性能被证明优于或与 ReLU 相当。
  • 它是平滑且非单调的,这个特性被证明对优化非常有益。

缺点:

  • 计算成本比 ReLU 高,因为包含了 Sigmoid 运算。

PyTorch 代码实现:
PyTorch 没有内置的 nn.Swish,但可以轻松实现。

class Swish(nn.Module):
    def __init__(self, beta=1.0):
        super(Swish, self).__init__()
        self.beta = beta

    def forward(self, x):
        return x * torch.sigmoid(self.beta * x)

# 使用
swish_activation = Swish(beta=1.0)
input_tensor = torch.tensor([-2., -1., 0., 1., 2.])
output = swish_activation(input_tensor)
print(f"Input: {input_tensor}")
print(f"Swish Output: {output}")

# 从PyTorch 1.7(大约)开始,有一个内置的版本,但可能叫nn.SiLU
# swish_layer = nn.SiLU() # SiLU是Swish的另一个名字

总结与选择建议

激活函数 常用位置 优点 缺点 推荐场景
ReLU 隐藏层 计算高效,缓解梯度消失 神经元“死亡” 默认的隐藏层首选,广泛应用
Leaky ReLU / PReLU 隐藏层 解决“死亡”问题,计算仍高效 需选择超参数 \(\alpha\) 担心 ReLU 导致神经元死亡时
ELU 隐藏层 平滑,输出接近零中心 计算成本高 对训练稳定性要求高的复杂模型
Swish 隐藏层 性能常优于 ReLU 计算成本高 追求最先进性能,可接受稍高的计算开销
Sigmoid 输出层(二分类) 输出概率 梯度消失,非零中心 二分类分割任务的最后一层
Softmax 输出层(多分类) 输出概率分布 计算成本高 多分类分割任务的最后一层

通用指南:

  1. 隐藏层:从 ReLU 开始,它是强大且快速的基线。如果遇到训练问题(如梯度消失或神经元死亡),可以尝试 Leaky ReLU、PReLU 或 ELU。对于追求更高性能的场景,可以实验 Swish。
  2. 输出层:
    • 二分类分割:使用 Sigmoid。
    • 多分类分割:使用 Softmax。

在实际项目中,通常会在隐藏层使用 ReLU 及其变种,并在最终的输出层根据任务类型(二分类或多分类)选择 Sigmoid 或 Softmax。

posted @ 2025-08-28 23:49  Quentin_Yws154  阅读(61)  评论(0)    收藏  举报