激活函数(Activation Function)
激活函数是神经网络中的非线性变换函数,它决定神经元是否应该被激活(即是否将信息传递给下一层)。
为什么需要激活函数?
核心问题:线性模型的局限性
# 如果没有激活函数(或使用线性激活)# 每一层都是线性变换:y = Wx + b# 两层神经网络h=W1*x+b1 output=W2*h+b2# 最终等价于单层线性模型output=(W2*W1)*x+(W2*b1+b2)# = W' * x + b'# 结论:多层线性层可以合并成一层,无法解决复杂问题激活函数的作用:引入非线性,让神经网络能够学习和表示复杂模式。
常见激活函数
1.Sigmoid(逻辑函数)
importtorchimporttorch.nn.functionalasF# 公式: σ(x) = 1 / (1 + e^(-x))# 输出范围: (0, 1)x=torch.tensor([-2,-1,0,1,2])y=torch.sigmoid(x)print(y)# tensor([0.1192, 0.2689, 0.5000, 0.7311, 0.8808])特点:
- ✅ 输出可解释为概率
- ✅ 平滑、可微
- ❌ 梯度消失(两端饱和)
- ❌ 输出不是零中心
应用:二分类输出层
2.Tanh(双曲正切)
# 公式: tanh(x) = (e^x - e^(-x)) / (e^x + e^(-x))# 输出范围: (-1, 1)x=torch.tensor([-2,-1,0,1,2])y=torch.tanh(x)print(y)# tensor([-0.9640, -0.7616, 0.0000, 0.7616, 0.9640])特点:
- ✅ 零中心(利于优化)
- ✅ 比 Sigmoid 范围更大
- ❌ 仍有梯度消失问题
应用:RNN、传统神经网络隐藏层
3.ReLU(修正线性单元)⭐ 最常用
# 公式: ReLU(x) = max(0, x)# 输出范围: [0, ∞)x=torch.tensor([-2,-1,0,1,2])y=F.relu(x)print(y)# tensor([0, 0, 0, 1, 2])特点:
- ✅ 计算简单快速
- ✅ 缓解梯度消失(正区间)
- ✅ 稀疏激活(部分神经元输出0)
- ❌ Dead ReLU(负区间梯度为0)
- ❌ 输出不是零中心
应用:CNN、现代深度学习隐藏层
4.Leaky ReLU
# 公式: LeakyReLU(x) = max(αx, x),通常 α=0.01# 输出范围: (-∞, ∞)x=torch.tensor([-2,-1,0,1,2])y=F.leaky_relu(x,negative_slope=0.01)print(y)# tensor([-0.0200, -0.0100, 0.0000, 1.0000, 2.0000])特点:
- ✅ 解决 Dead ReLU 问题
- ✅ 负区间有微小梯度
- ❌ 需要调参 α
5.Softmax
# 公式: Softmax(x_i) = e^(x_i) / Σ e^(x_j)# 输出范围: (0, 1),所有输出和为1x=torch.tensor([2.0,1.0,0.1])y=F.softmax(x,dim=0)print(y)# tensor([0.6590, 0.2424, 0.0986])print(y.sum())# tensor(1.0000)特点:
- ✅ 输出为概率分布
- ✅ 突出最大值
应用:多分类输出层
可视化对比
importmatplotlib.pyplotaspltimporttorchimporttorch.nn.functionalasF x=torch.linspace(-5,5,100)# 计算各种激活函数sigmoid=torch.sigmoid(x)tanh=torch.tanh(x)relu=F.relu(x)leaky_relu=F.leaky_relu(x,0.1)plt.figure(figsize=(12,8))plt.plot(x,sigmoid,label='Sigmoid')plt.plot(x,tanh,label='Tanh')plt.plot(x,relu,label='ReLU')plt.plot(x,leaky_relu,label='Leaky ReLU')plt.grid(True,alpha=0.3)plt.legend()plt.title('常见激活函数对比')plt.xlabel('x')plt.ylabel('激活值')plt.show()实际应用示例
importtorch.nnasnnclassSimpleNN(nn.Module):def__init__(self,input_size,hidden_size,num_classes):super().__init__()self.fc1=nn.Linear(input_size,hidden_size)self.fc2=nn.Linear(hidden_size,hidden_size)self.fc3=nn.Linear(hidden_size,num_classes)# 激活函数self.relu=nn.ReLU()self.softmax=nn.Softmax(dim=1)defforward(self,x):# 隐藏层使用 ReLUx=self.relu(self.fc1(x))x=self.relu(self.fc2(x))# 输出层根据任务选择# 二分类:Sigmoid# 多分类:Softmax# 回归:无激活函数x=self.fc3(x)returnself.softmax(x)# 使用示例model=SimpleNN(784,256,10)# MNIST手写数字识别选择指南
| 任务类型 | 隐藏层推荐 | 输出层推荐 |
|---|---|---|
| 分类(二分类) | ReLU / Leaky ReLU | Sigmoid |
| 分类(多分类) | ReLU / Leaky ReLU | Softmax |
| 回归 | ReLU / Tanh | 无(线性) |
| 图像处理 | ReLU / Leaky ReLU | 根据任务 |
| 序列数据 | Tanh / ReLU | Softmax |
| 概率预测 | ReLU | Sigmoid |
常见问题
Q: 为什么不能只用 ReLU?
- ReLU 在负区间输出0,可能导致神经元"死亡"
- 某些任务需要负值输出(如 Tanh)
Q: 梯度消失是什么?
- Sigmoid/Tanh 在两端导数接近0
- 深层网络反向传播时梯度逐渐消失
- 导致浅层权重无法更新
Q: 可以自定义激活函数吗?
# 可以,但需要可微classSwish(nn.Module):defforward(self,x):returnx*torch.sigmoid(x)核心要点:激活函数让神经网络从线性模型变成通用函数逼近器,能够学习任意复杂的模式!