没有激活函数的神经网络,不管多深,都只是一条"直线"。
1. 一句话理解 ReLU
ReLU(Rectified Linear Unit,修正线性单元)的数学公式极其简单:
负数变 0,正数不变。 就这么简单。
| 输入 x | 输出 ReLU(x) |
|---|---|
| -3.0 | 0 |
| -0.5 | 0 |
| 0 | 0 |
| 0.5 | 0.5 |
| 3.0 | 3.0 |
2. 为什么需要 ReLU?——引入非线性
这是 ReLU 存在的根本原因。
2.1 线性层在做什么
nn.Linear 执行的是线性变换:。
线性变换有一个数学上的铁律:线性变换的组合,仍然是线性变换。
# 两层线性层
y = W₂(W₁x + b₁) + b₂
= W₂W₁x + W₂b₁ + b₂
= W'x + b' # 数学上等价于一层!
不管你堆 2 层、10 层还是 100 层线性层,结果在数学上永远等价于一层。就像你连续旋转、缩放一张照片多次,最终效果总能用"一次旋转 + 一次缩放"替代。
2.2 一层能做什么?——只能画直线
一层线性层只能在空间中画一条直线(或高维超平面)来分类:
能分开(线性可分) 分不开(线性不可分)
●●●● ●○●○
●●●● ○●○●
────── ← 一条直线 ●○●○ ← 没有一条直线能分开
○○○○ ○●○●
○○○○
而现实中的任务——手写数字识别、图像分类、语音理解——决策边界几乎都是弯曲的。只靠直线,搞不定。
2.3 ReLU 引入非线性,让多层不再等于一层
在两个线性层之间插入 ReLU:
y = W₂ · ReLU(W₁x + b₁) + b₂
由于 ReLU 不是线性函数(它在 处有一个"折点"),这个组合无法化简为一层。每多一层 Linear + ReLU,网络就多一次"折叠空间"的能力,最终可以拟合任意复杂的曲线。
# 没有 ReLU → 100 层 = 1 层(废了)
nn.Linear(784, 256)
nn.Linear(256, 10)
# 有 ReLU → 每层都有意义
nn.Linear(784, 256)
nn.ReLU() # ← 非线性,让上下两层不能合并
nn.Linear(256, 10)
一句话:ReLU 的作用是引入非线性,让深度网络真正"深"起来。
3. PyTorch 中的用法
3.1 三种调用方式
import torch
import torch.nn as nn
import torch.nn.functional as F
x = torch.tensor([-2.0, -1.0, 0.0, 1.0, 2.0])
# 方式一:模块式(常用于 nn.Sequential)
relu = nn.ReLU()
print(relu(x)) # tensor([0., 0., 0., 1., 2.])
# 方式二:函数式
print(F.relu(x)) # tensor([0., 0., 0., 1., 2.])
# 方式三:inplace 模式(直接修改原 tensor,省内存)
relu_ip = nn.ReLU(inplace=True)
relu_ip(x) # x 本身被修改
3.2 在网络中的典型位置
ReLU 放在线性层之后,通常最后一层不加 ReLU(输出层需要原始分数):
class Net(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(784, 256)
self.fc2 = nn.Linear(256, 128)
self.fc3 = nn.Linear(128, 10)
self.relu = nn.ReLU()
def forward(self, x):
x = self.relu(self.fc1(x)) # Linear → ReLU
x = self.relu(self.fc2(x)) # Linear → ReLU
x = self.fc3(x) # 最后一层不加 ReLU
return x
或用 nn.Sequential 简写:
model = nn.Sequential(
nn.Linear(784, 256),
nn.ReLU(),
nn.Linear(256, 128),
nn.ReLU(),
nn.Linear(128, 10), # 输出层,不加 ReLU
)
3.3 inplace 参数
nn.ReLU(inplace=False) # 默认,返回新 tensor,原数据保留
nn.ReLU(inplace=True) # 直接修改原 tensor,省内存
| 参数 | 内存 | 安全性 | 建议 |
|---|---|---|---|
inplace=False | 多占一份 | 安全 | 训练阶段用这个 |
inplace=True | 省一份 | 可能影响梯度计算 | 推理阶段或确认安全时使用 |
4. ReLU 的优缺点
4.1 优点
| 优点 | 说明 |
|---|---|
| 计算极快 | 只做 max(0, x),没有指数运算,比 Sigmoid/Tanh 快得多 |
| 缓解梯度消失 | 正数区间梯度恒为 1,不会像 Sigmoid 那样梯度越来越小 |
| 稀疏激活 | 约 50% 神经元输出 0,产生稀疏表示,有类似正则化的效果 |
4.2 缺点
| 缺点 | 说明 |
|---|---|
| Dead ReLU | 如果某个神经元的输入始终为负,它的输出永远是 0,梯度也是 0,再也无法更新——这个神经元就"死了" |
| 输出非零中心 | 输出全 ≥ 0,可能导致下一层的梯度更新出现 zig-zag 现象 |
4.3 Dead ReLU 问题图解
正常神经元: Dead 神经元:
输入有正有负 输入始终为负
──╲ ╱── ────────── 0
╲╱
有输出,能学习 永远输出 0,停止学习
常见原因:学习率设得太大,导致权重更新幅度过大,神经元被"推"到负区间后回不来。
解决方案:使用 LeakyReLU 等变体(见第 6 节)。
5. ReLU vs Sigmoid vs Tanh
在 ReLU 出现之前,Sigmoid 和 Tanh 是主流的激活函数。ReLU 之所以取代它们,主要因为梯度消失问题。
5.1 三者对比
| 属性 | ReLU | Sigmoid | Tanh |
|---|---|---|---|
| 公式 | |||
| 输出范围 | |||
| 梯度范围 | 0 或 1 | ||
| 计算成本 | 极低 | 高(指数运算) | 高(指数运算) |
| 梯度消失 | 正区间不会 | 严重 | 较严重 |
| 零中心 | 否 | 否 | 是 |
5.2 为什么 Sigmoid 会梯度消失
Sigmoid 的梯度最大只有 0.25。在反向传播中,梯度要逐层相乘:
10 层网络的梯度:0.25¹⁰ = 0.00000095
梯度几乎为零,前面的层学不动了。
ReLU 的正区间梯度恒为 1,不管多少层,梯度都不会被压缩:
10 层网络的梯度:1¹⁰ = 1
梯度完整传播,每层都能有效学习。
5.3 各自的适用场景
| 激活函数 | 适用场景 |
|---|---|
| ReLU | 隐藏层的默认选择 |
| Sigmoid | 二分类输出层(输出概率) |
| Tanh | 需要零中心输出的场景(如 RNN) |
6. ReLU 家族:常用变体
当原版 ReLU 遇到 Dead ReLU 等问题时,可以换用以下变体:
6.1 LeakyReLU
给负数区间一个小斜率(默认 0.01),防止神经元"死掉":
nn.LeakyReLU(negative_slope=0.01)
6.2 PReLU
和 LeakyReLU 类似,但斜率 是可学习的参数:
nn.PReLU() # α 初始值 0.25,通过训练自动调整
6.3 ELU
负数区间用指数曲线,输出均值更接近 0:
nn.ELU(alpha=1.0)
6.4 GELU
Transformer 时代的主流激活函数(GPT、BERT 都在用):
其中 是标准正态分布的累积分布函数。直觉上可以理解为:以概率的方式决定是否保留输入。
nn.GELU()
6.5 选择指南
| 场景 | 推荐 |
|---|---|
| CNN 隐藏层(默认) | nn.ReLU() |
| 遇到 Dead ReLU 问题 | nn.LeakyReLU() 或 nn.PReLU() |
| Transformer / NLP | nn.GELU() |
| 需要零中心输出 | nn.ELU() |
| 不确定选哪个 | 先用 nn.ReLU(),效果不好再换 |
7. 常见问题
Q1:最后一层要加 ReLU 吗?
不加。 最后一层的输出是原始分数(logits),需要保留正负信息:
- 分类任务:logits 交给
CrossEntropyLoss(内部会做 Softmax) - 回归任务:直接输出预测值
model = nn.Sequential(
nn.Linear(784, 256),
nn.ReLU(), # 隐藏层加 ReLU
nn.Linear(256, 10), # 输出层不加 ReLU
)
Q2:ReLU 放在 BatchNorm 前面还是后面?
两种都有人用,但先 BN 后 ReLU 是更常见的做法:
nn.Linear(784, 256),
nn.BatchNorm1d(256), # 先归一化
nn.ReLU(), # 再激活
理由:BN 把数据分布拉回均值 0,让更多值落在 ReLU 的正区间,减少 Dead ReLU。
Q3:ReLU 和 Dropout 的顺序?
先 ReLU 后 Dropout:
nn.Linear(784, 256),
nn.ReLU(), # 先激活
nn.Dropout(0.5), # 再随机丢弃
nn.Linear(256, 10),
Q4:为什么 ReLU 这么简单却这么有效?
- 计算快 —— GPU 上 max 操作极其高效
- 梯度好 —— 正区间梯度恒为 1,不消失不爆炸
- 稀疏性 —— 天然产生稀疏激活,类似大脑中只有少部分神经元同时激活
8. 一句话总结
ReLU 的本质作用是引入非线性,让多层网络不退化为一层。 它用最简单的方式————赋予了神经网络拟合任意复杂函数的能力,是现代深度学习的基石。