激活函数
激活函数
1. Sigmoid
原理:
Sigmoid 函数将一个实数输入映射到 (0, 1) 区间。其数学公式为:
\(\sigma(x) = \frac{1}{1 + e^{-x}}\)
在图像分割中,它主要有两个用途:
- 二分类问题的最后一层:将每个像素的 logit 值转换为一个概率,表示该像素属于“前景”的概率。
- 门控机制:在诸如 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 | 输出层(多分类) | 输出概率分布 | 计算成本高 | 多分类分割任务的最后一层 |
通用指南:
- 隐藏层:从 ReLU 开始,它是强大且快速的基线。如果遇到训练问题(如梯度消失或神经元死亡),可以尝试 Leaky ReLU、PReLU 或 ELU。对于追求更高性能的场景,可以实验 Swish。
- 输出层:
- 二分类分割:使用 Sigmoid。
- 多分类分割:使用 Softmax。
在实际项目中,通常会在隐藏层使用 ReLU 及其变种,并在最终的输出层根据任务类型(二分类或多分类)选择 Sigmoid 或 Softmax。
浙公网安备 33010602011771号