激活函数

激活函数

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 维的概率分布,表示该像素属于每个类别的概率。

优点

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

缺点

  • 计算成本较高。
  • 可能存在数值不稳定性(通常通过 LogSoftmaxnn.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 ReLUPReLUELU。对于追求更高性能的场景,可以实验 Swish
  2. 输出层
    • 二分类分割:使用 Sigmoid
    • 多分类分割:使用 Softmax

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

posted @ 2025-08-28 23:49  剪水行舟154  阅读(26)  评论(0)    收藏  举报