PyTorch04-PyTorch基础实战
四、PyTorch的主要组成模块
4.1 ResNet
本节学习的内容有:
- 梯度消失/爆炸的简介
- 代码里面为什么要设计BasicBlock和Bottleneck两种结构
- 代码里面的expansion作用
残差神经网络(ResNet)由 何恺明、张祥雨、任少卿、孙剑 等人在 2015 年(发表于 CVPR 2016)代表微软亚洲研究院(MSRA)团队提出。
核心解决的问题:网络深层化时的“退化问题”(Degradation Problem)
- 背景痛点:在 ResNet 出现前,传统的神经网络(如 AlexNet、VGG)虽然通过增加层数能提升特征提取能力,但当网络加深到一定程度(如 20 层以上)时,训练集上的准确率反而开始下降。
- 现象本质:这种现象并非由过拟合引起(因为训练集误差也在增大),而是因为网络过深导致梯度在反向传播中难以有效传递,使得优化算法极难收敛到优秀解。
- ResNet 的解决方案:提出了残差学习(Residual Learning)机制。通过引入“跳跃连接(Skip Connection)”(原文中叫快捷连接(Shortcut Connection)),将网络原本需要直接拟合的目标映射 \(H(x)\) 转化为学习残差 \(F(x) = H(x) - x\) 。这种设计使得信息与梯度能够跨层无损传递,从根本上攻克了极深网络难以训练的瓶颈,成功将神经网络的深度从十几层提升到了上百层甚至上千层(如 ResNet-50, ResNet-101, ResNet-152)。
4.1.1 基本介绍
随着卷积神经网络的出现,人们发现多层卷积或全连接网络的效果通常优于单层网络。因此,很多人潜意识里认为网络的层数越多,提取特征的能力越强,效果就会越好。然而,微软研究院的何恺明、张祥雨、任少卿、孙剑等人发现,当网络加深到一定程度后,整个网络的效果反而变差了,这种现象被称为“退化现象”(Degradation Problem)。值得注意的是,通过批归一化(Batch Normalization)和恰当的权重初始化,梯度消失和梯度爆炸在很大程度上已经被缓解,但深层网络依然极难训练。他们指出,退化的本质并不是过拟合,也不是简单的梯度消失,而是极深的网络结构使得优化算法(如 SGD)难以对特征进行有效优化——让深层网络直接学习“什么都不做(恒等映射)”其实非常困难。针对这一优化难题,他们提出了 ResNet(残差神经网络)。ResNet 最核心的贡献在于引入了快捷连接(Shortcut Connection,也称跳跃连接),将输入 \(x\) 直接跨层传递并与残差部分叠加(\(F(x) + x\))。这相当于为信息与梯度搭建了一条“无损高速公路”,不仅让网络极易学习恒等变换,也极大地简化了深层神经网络的优化过程,真正推动了神经网络迈向数百层乃至上千层的超深时代。
4.1.1.1 残差连接
以下是对残差连接过程中相关概念与过程中精简描述。
4.1.1.1.1 核心概念拆解
在残差网络结构中,我们可以将一个基础的残差块(Residual Block)拆解为三个要素:
- \(x\):输入数据(即进入这个残差块之前的特征图)。
- \(H(x)\):我们最终期望这个残差块学到的目标函数/目标映射(即期望输出)。
- \(F(x)\):卷积层/全连接层经过权重计算后实际学出的映射,即残差(Residual)。
其核心公式为:
\[H(x) = F(x) + x \quad \implies \quad F(x) = H(x) - x\]
这里的 \(F(x) = H(x) - x\) 在数学上就叫作残差(目标输出与输入的差值)。
4.1.1.1.2 详细解释:为何叠加 \(F(x) + x\) 是 ResNet 的核心突破?
要理解这个设计的精妙之处,可以从“传统的困难”和“残差的解决思路”两个角度来看:
1. 传统网络(没有 Shortcut):直接硬刚 \(H(x)\)
在传统的深层网络(如 VGG)中,数据 \(x\) 依次经过多个卷积层、激活层,网络被强行要求直接去拟合最终的目标映射 \(H(x)\)。
- 致命痛点:假设网络加深到一定程度后,新增的这几层其实不需要改变任何特征(即理想的目标是“恒等映射” \(H(x) = x\))。
- 现实难题:要让多个含有非线性激活函数(如 ReLU)的复杂卷积层去从 0 刚好学习出 \(H(x) = x\) 这个恒等变换,在数学和优化上是极其困难的。优化器(如 SGD)很容易在层层复杂的参数空间中迷失,导致特征表达被“搅乱”,训练误差反而上升(即退化现象)。
2. 残差网络(引入 Shortcut):拆解为 \(F(x) + x\)
ResNet 改变了思路:它不让卷积层直接去猜 \(H(x)\),而是把路径拆成两条:
- 主干路径(卷积层):负责学习残差 \(F(x)\)。
- 快捷路径(Shortcut):负责将输入 \(x\) 原封不动地直接“打横拉过去”。
最后在输出端将两者相加,得到 \(H(x) = F(x) + x\)。
3. 为什么这样设计极易优化?
极致的退化兜底(学习 \(F(x) = 0\) 极其简单)
如果新增的这几层没有任何用处,优化器只需要把这些卷积层的权重调整为 0(或者非常接近 0),此时 \(F(x) = 0\)。
整个模块的输出就变成了:
\[H(x) = 0 + x = x\]
网络瞬间自动退化为一个无损传递的恒等映射!这意味着,增加残差层至少绝对不会让模型的效果变差,彻底消除了深层网络退化的风险。
搭建梯度的“无损高速公路”
在反向传播计算梯度时,根据求导法则:
\[\frac{\partial H(x)}{\partial x} = \frac{\partial F(x)}{\partial x} + 1\]
注意公式中的 \(+ 1\)!这意味着,哪怕主干路径的梯度 \(\frac{\partial F(x)}{\partial x}\) 因为层数太深而趋近于 0(梯度消失),总梯度项中依然保留了一个常数项 \(1\)。
梯度可以顺着 Shortcut 这条“高速公路”直达前方的浅层网络,保证了前方的网络层永远能接收到足够的更新信号。
4.1.1.2 梯度消失与梯度爆炸
尽管何恺明等人证实了“退化现象”并非由梯度消失或梯度爆炸直接引起,但在ResNet诞生之前,梯度消失与梯度爆炸确实是阻碍神经网络加深的主要瓶颈之一。以下是对这二者的精简描述:梯度消失与梯度爆炸,本质都是在神经网络反向传播中,连乘效应被网络层数无限放大导致的数值异常现象。
- 梯度消失(Gradient Vanishing)
- 现象:网络前期的梯度极小,接近于 \(0\)。
- 成因:反向传播基于链式法则求导,当每层的导数或权重小于 \(1\) 时,多层相乘会导致梯度呈指数级衰减。
- 后果:靠近输入端的浅层网络权重无法更新,模型训练停滞。
- 梯度爆炸(Gradient Exploding)
- 现象:网络中的梯度极大,甚至变为无穷大(
NaN/Inf)。 - 成因:当每层的导数或权重大于 \(1\) 时,多层连乘导致梯度呈指数级暴增。
- 后果:权重更新幅度过大,引发数值溢出,模型震荡发散乃至崩塌。
- 现象:网络中的梯度极大,甚至变为无穷大(
4.1.2 源码解读
以下根据torchvision的ResNet源码来解读相关概念与设计思想。
4.1.2.1 卷积核的封装
以下是ResNet的源码(vision/torchvision/models/resnet.py)文件中开头处的部分内容,其中将3x3和1x1的卷积核进行了封装,提升了代码的可读性与工程可扩展性。基于类似设计理念与目的,其他深度学习代码实现中可能会将卷积层、激活函数层和BN层封装在一起。
1 | |
4.1.2.2 基本模块的设计
4.1.2.2.1 ImageNet 架构设计
ResNet 网络是由结构高度统一的模块重复堆叠而成的(如附图中的 \([\dots] \times 3\),表示将该残差块连续堆叠
3 次)。为了提升代码的可读性与工程可扩展性,ResNet
采用了模块化设计思想:针对不同深度的模型,分别抽象封装出
BasicBlock(基础残差块) 和
Bottleneck(瓶颈残差块)
两个核心类。在构建不同规模的网络(如 ResNet-18 至
ResNet-152)时,只需灵活调整传入的模块类型以及各阶段(Stage)的堆叠次数即可实现高效复用。
ResNet常见的大小有下图的ResNet-18,ResNet-34,ResNet-50、ResNet-101和ResNet-152,其中网络后面的数字代表的是网络的层数。
这张图来自于 ResNet 原论文(Deep Residual Learning for Image Recognition),展示了 ResNet-18、34、50、101 和 152 这五种经典残差网络架构的具体层数配置与计算量对比。
4.1.2.2.1.1 表格列与行的含义
layer name(层名称):网络从输入到输出划分为conv1以及conv2_x~conv5_x共 5 个阶段(Stage)。output size(特征图输出尺寸):输入图片假设为 \(224 \times 224\)。随着网络深入,特征图分辨率逐步下采样为 \(112 \times 112 \to 56 \times 56 \to 28 \times 28 \to 14 \times 14 \to 7 \times 7\)。- 各网络列(18/34/50/101/152-layer):方括号中代表残差块(Residual
Block)的内部结构,右侧的
×N代表该残差块在当前阶段重复堆叠的次数。 FLOPs(浮点运算量):衡量模型计算复杂度的指标。层数越深,计算量越大(从 \(1.8 \times 10^9\) 增加至 \(11.3 \times 10^9\))。
4.1.2.2.1.2 核心架构区别:基础残差块 vs 瓶颈结构(Bottleneck)
表格中将 ResNet 明显分为了两大类别:
- 浅层网络(ResNet-18 / ResNet-34):使用基础块(Basic Block)
- 结构:每一个残差块由两个 \(3 \times 3\) 的卷积层组成,如 \(\begin{bmatrix} 3\times 3, 64 \\ 3\times 3, 64 \end{bmatrix}\)。
- 特点:输入通道数与输出通道数保持一致(例如均为 64),结构简单直接。
- 深层网络(ResNet-50 / ResNet-101 / ResNet-152):使用瓶颈块(Bottleneck Block)
- 结构:每一个残差块改用三个卷积层组合,如 \(\begin{bmatrix} 1\times 1, 64 \\ 3\times 3, 64 \\
1\times 1, 256 \end{bmatrix}\)。
- 第一层 \(1 \times 1\):降维(例如将 256 维降到 64 维。其中64就是卷积核的输出通道大小),减少后续 \(3 \times 3\) 卷积的计算量。
- 第二层 \(3 \times 3\):在低维空间进行空间特征提取。
- 第三层 \(1 \times 1\):升维(将 64 维恢复/拓展到 256 维)。
- 目的:在层数急剧增加时控制参数量和计算复杂度,使得 ResNet-50 虽然层数是 ResNet-34 的 1.5 倍,但 FLOPs(\(3.8 \times 10^9\))与 ResNet-34(\(3.6 \times 10^9\))非常接近。
4.1.2.2.1.3 层数是如何计算出来的?(以 ResNet-50 为例)
所谓“ResNet-50”中的 50 层,指的是带可学习权重参数的卷积层和全连接层的总和(不计入 Pooling 层与 Activation 层):
conv1:1 层(\(7 \times 7\) 卷积)conv2_x:\(3 \text{ 个 Bottleneck} \times 3 \text{ 层} = 9\) 层conv3_x:\(4 \text{ 个 Bottleneck} \times 3 \text{ 层} = 12\) 层conv4_x:\(6 \text{ 个 Bottleneck} \times 3 \text{ 层} = 18\) 层conv5_x:\(3 \text{ 个 Bottleneck} \times 3 \text{ 层} = 9\) 层fc:1 层(全连接分类层,1000-d fc)
\[\text{总层数} = 1 + 9 + 12 + 18 + 9 + 1 = 50 \text{ 层}\]
同理,ResNet-101 和 ResNet-152 主要是在
conv4_x阶段大幅增加了残差块的重复次数(分别重复 23 次和 36 次),用来抽取更高阶的语义特征。
4.1.2.2.1.4 附:图表中瓶颈块是指什么
瓶颈块(Bottleneck Block) 在图表中指的就是 ResNet-50、101 和 152 这三列中,所有由三个卷积层组合构成的方括号结构整体。
- 图表中的具体位置与形态
在图表右侧的 50-layer、101-layer 和
152-layer 列中,每个 Stage
里面的一个方括号单元就是一个瓶颈块。
例如以 50-layer 的 conv2_x 阶段为例:
\[\begin{bmatrix} 1\times 1, 64 \\ 3\times 3, 64 \\ 1\times 1, 256 \end{bmatrix} \times 3\]
这整组方括号括起来的内容,就是一个标准的瓶颈模块:
- 括号内部的 3 行:代表这个瓶颈块内部顺序连接的
3 个卷积层。
- \(1 \times 1, 64\)(降维层:把输入通道压缩到 64)
- \(3 \times 3, 64\)(空间特征提取层:在 64 通道的低维空间运算)
- \(1 \times 1, 256\)(升维层:把通道重新恢复/扩充到 256)
- 右侧的 \(\times 3\):代表将这一个瓶颈模块按顺序连续堆叠 3 次。
- 为什么叫“瓶颈(Bottleneck)”?
它被称为“瓶颈”是因为其通道数呈现出 “大 \(\to\) 小 \(\to\) 大” 的两头宽、中间窄的物理形状:
1 | |
数据流经过中间的 \(3 \times 3\) 卷积层时,特征通道被压缩成了一个“窄瓶颈”,以此大幅降低了参数量和计算开销。
4.1.2.3 downsample操作
正如上面图表中内容所示,根据残差块结构的特征差异(是否包含瓶颈层结构、convN_x的输入通道数与最终输出通道数是否相同),可以将ResNet-18,ResNet-34,ResNet-50、ResNet-101和ResNet-152划分为两大类:ResNet-18、ResNet-34只包含浅层网络属于一类,ResNet-50、ResNet-101和ResNet-152包含深层网络属于第二类。
torchvision
官方源码同样采用了这种模块化设计思想,分别封装了如上图所示的
BasicBlock(左)和
Bottleneck(右)两个核心模块。在源码实现中,残差块最终输出的通道数通过
expansion 参数进行控制:两个模块均接收通道数为
in_planes 的特征图,并统一输出通道数为
planes \* block.expansion
的特征图。其中,BasicBlock 的 expansion 设为
1(输出通道等于 planes),而 Bottleneck 的
expansion 设为 4(输出通道扩充为 planes 的 4
倍)。此外,右侧 Shortcut 支路上的 downsample
操作,正是用于在做残差叠加前解决输入 \(x\) 与主干输出 \(F(x)\)
空间尺寸或通道维度不匹配问题的线性映射分支。
4.1.2.4 Shortcut Connection
下图展示了shortcut connection 的过程:
Shortcut Connection(快捷连接) 是跨越卷积层将输入 \(x\) 直连至输出端的旁路结构,让网络只需拟合残差 \(\mathcal{F}(x)\) 以缓解深层退化与梯度消失;当输入 \(x\) 与主干输出 \(\mathcal{F}(x)\) 维度相同时,采用无参数的 恒等映射 直接相加(\(y = \mathcal{F}(x) + x\));当因下采样或通道变化导致维度不一致时,则采用 投影映射 ,通过 \(1 \times 1\) 卷积调整 \(x\) 的尺寸与通道数对齐后再行叠加(\(y = \mathcal{F}(x) + W_s x\))。关于调整x尺寸,其中情况可以大致描述如下:
这里的“尺寸”包含两个层面:通道尺寸(Channel) 和 空间尺寸(Height / Width)。
\(1 \times 1\) 卷积不仅能改变通道数,只要把步长(stride)设置为 2,就能同时将图像的高和宽缩小一半,实现空间尺寸的调整。
具体通过以下两种方式完成调整:
调整通道尺寸(Channel Dimension)
原理:通过控制 \(1 \times 1\) 卷积核的数量(即
out_channels)来改变通道数。例子:输入特征图形状为 \([C_{\text{in}}=64, H=56, W=56]\),使用 256 个 \(1 \times 1\) 卷积核(
stride=1),输出特征图就会变为 \([C_{\text{out}}=256, H=56, W=56]\)。
调整空间尺寸(Height & Width)
原理:将 \(1 \times 1\) 卷积的步长设置为
stride=2。此时卷积核在图像上隔一个像素跳着滑动采样,从而使输出的高和宽变为原来的 \(\frac{1}{2}\)。代入公式:
根据尺寸计算公式:
\[H_{\text{out}} = \lfloor \frac{H_{\text{in}} + 2 \times \text{padding} - \text{kernel\_size}}{\text{stride}} \rfloor + 1\]
当输入 \(H_{\text{in}} = 56\),
kernel_size = 1,padding = 0,stride = 2时:\[H_{\text{out}} = \lfloor \frac{56 + 0 - 1}{2} \rfloor + 1 = 27 + 1 = 28\]
例子:输入形状为 \([64, 56, 56]\),使用步长为 2 的 \(1 \times 1\) 卷积(假设输出通道设为 128),输出特征图就会变为 \([128, 28, 28]\)。
总结(Shortcut 支路中的 Downsample)
在 ResNet 的 Shortcut 支路上,当需要进行 downsample
时,正是使用了 stride=2 且改变卷积核数量的 \(1 \times 1\) 卷积,一次性将输入
\(x\)
的通道数加倍并将其高宽减半,完美对齐了主干分支
\(F(x)\) 的维度与尺寸。
上图展示了ImageNet的示例网络构架图。其中左图表示VGG-19模型的网络构架图($19.6 ^{9} $ FLOPs ),作为参考。中图表示一个有34个参数层的普通网络($3.6 ^{9} $ FLOPs )。右图表示一个有34个参数层的残差网络($3.6 ^{9} $ FLOPs )。
对比中图与右图可以看到,右图中添加了shortcut connection操作,且这种shortcut connection操作只发生在堆叠的残差模块之间。这种shortcut connection操作分为两种:恒等映射、投影映射,分别对应右图中最右侧的实曲线、虚曲线。只有当特征图的通道数或空间尺寸发生变化时,才会用到投影映射,保证块的输入与最终输出在通道维度与空间尺寸上同时完成对齐。比如跨 Stage 的 “3x3 conv, 128, /2”卷积层中指定通道数变为128、步长Stride=2,这样会将特征图的通道数变为原来的2倍、空间尺寸变为原来的1/2,此时,旁路(Shortcut 支路)通过施加一个步长为 2 的 \(1 \times 1\) 卷积(配置 128 个卷积核)进行线性映射,使输入 \(x\) 的空间尺寸与通道维度同时与主干输出对齐,从而确保两路特征图能够正常执行逐元素相加。
在 ResNet 网络结构中,无论是 BasicBlock 还是 BottleNeck 结构均包含了残差连接(Shortcut Connection)操作,确保块的输入与最终输出在通道维度上完成对齐。BasicBlock采用两次标准 \(3 \times 3\) 卷积直接进行特征提取,适用于浅层网络。BottleNeck对残差主干进行了模块化优化。首先通过 \(1 \times 1\) 卷积减少通道数(降维),接着在低维空间利用 \(3 \times 3\) 卷积提取空间特征,最后再用 \(1 \times 1\) 卷积恢复通道数(升维),最终将输入 \(x\) 与主干输出 \(F(x)\) 相加。这种“降维-卷积-升维”的设计在保持特征表达能力的同时,相比同等深度的纯 \(3 \times 3\) 卷积极大地压缩了参数量与计算量,使得 ResNet 能够在堆叠更多层数(如 50、101、152 层)时依然保持高效的计算性能与优异的准确率。