从入门到入神|PyTorch 基础系列 08
上一关,我们已经看懂了:
nn.BatchNorm2d(64)
也知道一个经典 CNN(Convolutional Neural Network,卷积神经网络)模块经常写成:
Conv → BN → ReLU
其中:
-
Conv(Convolution,卷积):提取和变换特征;
-
BN(Batch Normalization,批归一化):调整特征值的数值分布;
-
ReLU(Rectified Linear Unit,修正线性单元):加入非线性。
前两个我们已经逐渐理解了。
但代码里的:
x = F.relu(x)
或者:
nn.ReLU()
到底做了什么?
为什么几乎每个 CNN 里都能看到它?
如果把 ReLU 全部删掉,网络还能训练吗?
这一关,我们就只解决 ReLU。
一、先看完整代码
先不用理解理论,直接看看 ReLU 对 Tensor 做了什么。
import torch
import torch.nn as nn
# ==============================
# 创建输入 Tensor
# ==============================
x = torch.tensor([
[-2.0, -1.0, 0.0, 1.0, 3.0]
])
print("原始 Tensor:")
print(x)
# ==============================
# ReLU
# ==============================
relu = nn.ReLU()
y = relu(x)
print("\n经过 ReLU:")
print(y)
运行结果:
原始 Tensor:
tensor([[-2., -1., 0., 1., 3.]])
经过 ReLU:
tensor([[0., 0., 0., 1., 3.]])
发生的事情非常简单:
-2 → 0
-1 → 0
0 → 0
1 → 1
3 → 3
所以 ReLU 最基本的规则就是:
负数变成 0,正数保持不变。
二、ReLU 到底是什么?
ReLU 的全称是:
Rectified Linear Unit(修正线性单元,一种非常常见的激活函数)
它的数学形式非常简单:
$$
f(x)=\max(0,x)
$$
意思就是:
如果:
$$
x<0
$$
那么:
$$
f(x)=0
$$
如果:
$$
x>0
$$
那么:
$$
f(x)=x
$$
例如:
| 输入 $x$ | ReLU 输出 |
|---|---|
| -3 | 0 |
| -1 | 0 |
| 0 | 0 |
| 2 | 2 |
| 5 | 5 |
所以初学阶段可以直接记:
ReLU 就像一道门:负值被挡住,正值继续通过。

三、ReLU 会改变 Tensor Shape 吗?
不会。
假设输入:
[8,64,32,32]
经过:
relu = nn.ReLU()
x = relu(x)
输出仍然是:
[8,64,32,32]
也就是说:
B:8 → 8
C:64 → 64
H:32 → 32
W:32 → 32
全部不变。
ReLU 改变的是:
Tensor 里面的数值。
不是:
Tensor 的 Shape。
例如某个 Feature Map(特征图,网络内部提取出来的特征表示)原来可能包含:
-0.8 1.2 -2.1
0.4 -0.3 2.7
-1.5 0.9 1.6
经过 ReLU:
0 1.2 0
0.4 0 2.7
0 0.9 1.6
Shape 没变。
只是负数被截断成了 0。

四、为什么神经网络还需要 ReLU?
现在问题来了。
Conv 已经能够提取特征了。
那为什么还要:
Conv
→
ReLU
甚至:
Conv
→
BN
→
ReLU
真正的关键在一个词:
Non-linearity(非线性,让网络能够表示更加复杂的输入输出关系)。
五、什么叫“非线性”?
先看一个非常简单的式子:
$$
y=2x+1
$$
这是一个线性/仿射关系。
如果后面再接一层:
$$
z=3y+2
$$
把前面的 $y$ 代进去:
$$
z=3(2x+1)+2
$$
得到:
$$
z=6x+5
$$
发现了吗?
虽然我们写了两层:
第一层
→
第二层
但最后依然可以合并成:
$$
z=ax+b
$$
也就是说:
连续叠加线性变换,并不会自动产生真正的非线性表达能力。
卷积虽然看起来比这个公式复杂得多,但 Conv 本质上仍然属于线性/仿射变换。
如果很多 Conv 中间完全没有激活函数:
Conv
→
Conv
→
Conv
→
Conv
整个网络的表达能力会受到很大限制。
所以需要在这些线性变换之间加入:
ReLU
把网络变成:
Conv
→
ReLU
→
Conv
→
ReLU
→
Conv
这时网络才能逐步组合出更加复杂的非线性关系。

六、为什么“很多层”不等于“表达能力一定更强”?
这是一个很容易产生的误解。
假设我们一直增加:
Conv
→
Conv
→
Conv
→
Conv
→
Conv
看起来网络已经很深了。
但如果中间完全没有非线性激活:
很多线性变换仍然可以组合成一个更大的线性/仿射变换。
所以:
网络深,不代表一定真正拥有深层网络应有的表达能力。
ReLU 的作用之一,就是让不同层之间不再只是简单的线性叠加。
这也是为什么激活函数不是“装饰”。
它是神经网络能够学习复杂模式的重要组成部分。
七、为什么 ReLU 要把负数变成 0?
看到这里,很容易产生一个问题:
负数也是信息,为什么直接把它变成 0?
首先需要明确:
ReLU 并不是因为“负数没有用”。
它只是采用了一种非常简单的非线性规则:
$$
f(x)=\max(0,x)
$$
当某个特征响应为负时:
→ 0
当它为正时:
→ 保留
这样既能够加入非线性,又保持计算非常简单。
所以不要把它理解成:
“网络认为所有负数都是错误的。”
更准确的理解是:
ReLU 使用“负值截断、正值保留”的方式构造非线性特征变换。
八、为什么 ReLU 这么常见?
激活函数并不只有 ReLU。
但 ReLU 在 CNN 中非常常见,一个重要原因就是:
简单。
计算:
$$
\max(0,x)
$$
不需要非常复杂的运算。
同时对于正数区域:
$$
f(x)=x
$$
变化也非常直接。
因此 ReLU 成为了很多经典深度网络中的基础组件。
初学阶段不用急着比较各种激活函数。
现在先真正搞懂:
nn.ReLU()
就够了。
九、nn.ReLU() 和 F.relu(x) 有什么区别?
以后代码里会经常看到两种写法。
第一种:
import torch.nn as nn
relu = nn.ReLU()
x = relu(x)
第二种:
import torch.nn.functional as F
x = F.relu(x)
它们在最基础的 ReLU 使用场景中完成的是同一种核心操作:
$$
f(x)=\max(0,x)
$$
区别主要在写法。
nn.ReLU()
它是一个 PyTorch Module(模块,可以作为网络结构中的一层)。
所以可以写进:
nn.Sequential(
...
)
例如:
block = nn.Sequential(
nn.Conv2d(3, 64, 3, padding=1),
nn.BatchNorm2d(64),
nn.ReLU()
)
F.relu(x)
它是函数式写法。
例如:
x = self.conv(x)
x = self.bn(x)
x = F.relu(x)
初学阶段可以先这样记:
nn.ReLU()更像“定义一个网络层”。
F.relu(x)更像“直接对当前 Tensor 执行 ReLU”。
对于 ReLU 本身,这两种写法都非常常见。
十、ReLU 有可学习参数吗?
没有。
这一点正好可以和上一关的 BatchNorm 对比。
BatchNorm:
nn.BatchNorm2d(64)
通常拥有:
γ
β
这些可以通过训练学习的参数。
但:
nn.ReLU()
没有需要训练的权重。
它的规则从一开始就固定好了:
$$
f(x)=\max(0,x)
$$
所以:
ReLU 本身没有可学习参数。
十一、把 Conv、BN、ReLU 放在一起重新看
现在我们终于能够完整理解:
block = nn.Sequential(
nn.Conv2d(
3,
64,
kernel_size=3,
padding=1,
bias=False
),
nn.BatchNorm2d(64),
nn.ReLU()
)
假设输入:
[8,3,32,32]
第一步:Conv2d
[8,3,32,32]
→
[8,64,32,32]
主要完成:
提取和变换特征,同时把 Channel 从 3 变成 64。
第二步:BatchNorm2d
[8,64,32,32]
→
[8,64,32,32]
Shape 不变。
主要完成:
调整每个 Channel 的数值分布。
第三步:ReLU
[8,64,32,32]
→
[8,64,32,32]
Shape 依然不变。
主要完成:
加入非线性,把负值截断为 0。

现在看到:
Conv → BN → ReLU
应该已经不再只是记住一个固定模板了。
而是知道每一步到底负责什么。
十二、BN 和 ReLU 都不改变 Shape,它们有什么区别?
这是非常容易混淆的一点。
假设输入都是:
[8,64,32,32]
经过 BN:
[8,64,32,32]
→
[8,64,32,32]
经过 ReLU:
[8,64,32,32]
→
[8,64,32,32]
Shape 看起来完全一样。
但它们做的事情完全不同。
| 操作 | Shape | 核心作用 |
|---|---|---|
| BatchNorm | 通常不变 | 调整数值分布 |
| ReLU | 不变 | 加入非线性 |
| Pooling | H、W通常变小 | 压缩空间尺寸 |
| Conv2d | 视参数而定 | 提取和变换特征 |
这也是一个非常重要的认识:
判断一个网络层做了什么,不能只看 Shape。
Shape 不变:
不代表 Tensor 内部没有发生变化。

十三、ReLU 会不会也有问题?
会。
ReLU 并不是完美的。
因为所有:
$$
x<0
$$
都会被直接变成:
$$
0
$$
如果某个神经元长期落在负值区域,它的输出可能长期为 0。
这种现象通常被称为:
Dying ReLU(ReLU 死亡,某些神经元长期输出 0,难以继续有效更新)。
初学阶段现在不用深入研究它。
只需要知道:
ReLU 很常用,但并不是唯一的激活函数,也不是在所有情况下都没有缺点。
以后遇到其他激活函数时,我们再继续比较。
十四、这一关最容易搞错的 5 个问题
1. ReLU 会改变 Channel 吗?
不会。
例如:
[8,64,32,32]
→ ReLU →
[8,64,32,32]
2. ReLU 会改变 H、W 吗?
不会。
ReLU 只处理 Tensor 中的数值。
3. ReLU 是把所有数值都变成 0~1 吗?
不是。
例如:
3.5
经过 ReLU 仍然是:
3.5
它不是把数据压缩到 [0,1]。
4. ReLU 有可学习参数吗?
没有。
它的规则固定为:
$$
f(x)=\max(0,x)
$$
5. 没有 ReLU,网络只是“效果差一点”吗?
不能这么简单理解。
如果一个深层网络始终只堆叠线性/仿射变换,没有非线性激活:
多层结构的表达能力会受到根本性的限制。
所以激活函数是深度神经网络非常关键的组成部分。
十五、再跑一次完整的 Conv → BN → ReLU
最后用完整代码把这一关串起来。
import torch
import torch.nn as nn
# ==============================
# 输入
# ==============================
x = torch.randn(
8,
3,
32,
32
)
# ==============================
# 网络模块
# ==============================
conv = nn.Conv2d(
in_channels=3,
out_channels=64,
kernel_size=3,
padding=1,
bias=False
)
bn = nn.BatchNorm2d(64)
relu = nn.ReLU()
# ==============================
# Forward
# ==============================
print("Input:")
print(x.shape)
x = conv(x)
print("\nConv:")
print(x.shape)
x = bn(x)
print("\nBatchNorm:")
print(x.shape)
x = relu(x)
print("\nReLU:")
print(x.shape)
输出:
Input:
torch.Size([8, 3, 32, 32])
Conv:
torch.Size([8, 64, 32, 32])
BatchNorm:
torch.Size([8, 64, 32, 32])
ReLU:
torch.Size([8, 64, 32, 32])
现在已经可以解释:
Conv:
改变并提取特征
BN:
调整特征值分布
ReLU:
加入非线性
而整个过程中:
Conv:
C 从 3 → 64
BN:
Shape 不变
ReLU:
Shape 不变
十六、这一关真正需要记住什么?
如果只记住五句话:
第一
ReLU 的全称是:
Rectified Linear Unit(修正线性单元)
第二
核心公式:
$$
f(x)=\max(0,x)
$$
也就是:
负值变成 0,正值保持不变。
第三
ReLU 不改变 Tensor Shape:
[8,64,32,32]
→
[8,64,32,32]
改变的是 Tensor 内部的数值。
第四
ReLU 最重要的作用不是“删掉负数”。
而是:
为网络加入非线性表达能力。
第五
现在看到:
Conv → BN → ReLU
应该能够分别理解:
Conv 负责特征变换,BN 调整数值分布,ReLU 加入非线性。
十七、走到第八关以后,我们又多理解了一件事
前几关,我们一直非常关注:
Shape
到了 BatchNorm 和 ReLU以后,会逐渐发现:
网络里有很多重要操作,Shape 根本不会发生变化。
但这不代表它们没有作用。
BN:
Shape 不变,但数值分布变了。
ReLU:
Shape 不变,但特征的表达方式变了。
所以现在看网络代码时,已经需要同时问两个问题:
第一:Tensor Shape 怎么变?
第二:Tensor 里面的数值怎么变?
这说明我们已经开始从:
“追踪 Tensor 的外形”
继续进入:
“理解 Tensor 内部的特征变化”。
下一关
下一关可以继续沿着一个完整 CNN 往下走:
深度学习入门第九关:
nn.Linear(128,10)里的 128 和 10 到底是什么?全连接层到底在做什么?
我们会把前面的:
Conv
→
Pooling
→
GAP / Flatten
真正接到:
Linear
→
最终输出
第一次把一个 CNN 的“特征提取部分”和“最终输出部分”完整接起来。
从入门,到入神。

397

被折叠的 条评论
为什么被折叠?



