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)\),而是把路径拆成两条:

  1. 主干路径(卷积层):负责学习残差 \(F(x)\)
  2. 快捷路径(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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...
def conv3x3(in_planes: int, out_planes: int, stride: int = 1, groups: int = 1, dilation: int = 1) -> nn.Conv2d:
"""3x3 convolution with padding"""
return nn.Conv2d(
in_planes,
out_planes,
kernel_size=3,
stride=stride,
padding=dilation,
groups=groups,
bias=False,
dilation=dilation,
)


def conv1x1(in_planes: int, out_planes: int, stride: int = 1) -> nn.Conv2d:
"""1x1 convolution"""
return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False)
...

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,其中网络后面的数字代表的是网络的层数。

image-20260828175030500

这张图来自于 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 明显分为了两大类别:

  1. 浅层网络(ResNet-18 / ResNet-34):使用基础块(Basic Block)
  • 结构:每一个残差块由两个 \(3 \times 3\) 的卷积层组成,如 \(\begin{bmatrix} 3\times 3, 64 \\ 3\times 3, 64 \end{bmatrix}\)
  • 特点:输入通道数与输出通道数保持一致(例如均为 64),结构简单直接。
  1. 深层网络(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. 第一层 \(1 \times 1\)降维(例如将 256 维降到 64 维。其中64就是卷积核的输出通道大小),减少后续 \(3 \times 3\) 卷积的计算量。
    2. 第二层 \(3 \times 3\):在低维空间进行空间特征提取。
    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 这三列中,所有由三个卷积层组合构成的方括号结构整体

  1. 图表中的具体位置与形态

在图表右侧的 50-layer101-layer152-layer 列中,每个 Stage 里面的一个方括号单元就是一个瓶颈块。

例如以 50-layerconv2_x 阶段为例:

\[\begin{bmatrix} 1\times 1, 64 \\ 3\times 3, 64 \\ 1\times 1, 256 \end{bmatrix} \times 3\]

这整组方括号括起来的内容,就是一个标准的瓶颈模块:

  • 括号内部的 3 行:代表这个瓶颈块内部顺序连接的 3 个卷积层
    1. \(1 \times 1, 64\)(降维层:把输入通道压缩到 64)
    2. \(3 \times 3, 64\)(空间特征提取层:在 64 通道的低维空间运算)
    3. \(1 \times 1, 256\)(升维层:把通道重新恢复/扩充到 256)
  • 右侧的 \(\times 3\):代表将这一个瓶颈模块按顺序连续堆叠 3 次
  1. 为什么叫“瓶颈(Bottleneck)”?

它被称为“瓶颈”是因为其通道数呈现出 “大 \(\to\)\(\to\) 大” 的两头宽、中间窄的物理形状:

1
2
3
4
5
6
7
8
9
输入通道 (256) 

[1x1 卷积] ──> 降维到 64 ──────┐
├─> 【瓶颈区域】:中间的 3x3 卷积只在 64 维低维处理
[3x3 卷积] ──> 保持 64 ──────┘ 计算量被极大地限制住了!

[1x1 卷积] ──> 恢复到 256

输出通道 (256)

数据流经过中间的 \(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 的特征图。其中,BasicBlockexpansion 设为 1(输出通道等于 planes),而 Bottleneckexpansion 设为 4(输出通道扩充为 planes 的 4 倍)。此外,右侧 Shortcut 支路上的 downsample 操作,正是用于在做残差叠加前解决输入 \(x\) 与主干输出 \(F(x)\) 空间尺寸或通道维度不匹配问题的线性映射分支。

677ca58e-1358-43c7-9709-1fc3ed2a3392

4.1.2.4 Shortcut Connection

下图展示了shortcut connection 的过程:

image-20260829225614504

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,就能同时将图像的高和宽缩小一半,实现空间尺寸的调整。

具体通过以下两种方式完成调整:

  1. 调整通道尺寸(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]\)

  2. 调整空间尺寸(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]\)

  3. 总结(Shortcut 支路中的 Downsample)

在 ResNet 的 Shortcut 支路上,当需要进行 downsample 时,正是使用了 stride=2 且改变卷积核数量的 \(1 \times 1\) 卷积,一次性将输入 \(x\)通道数加倍并将其高宽减半,完美对齐了主干分支 \(F(x)\) 的维度与尺寸。

image-20260829225456318

上图展示了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 层)时依然保持高效的计算性能与优异的准确率。

4.1.2.5 BasicBlock

4.1.2.6 BottleNeck

4.1.3 总结

4.2 基础实战——FashionMNIST时装分类


PyTorch04-PyTorch基础实战
https://jiangsanyin.github.io/2026/08/27/PyTorch04-PyTorch基础实战/
作者
sanyinjiang
发布于
2026年8月27日
许可协议