查看全课大纲(第 44 / 93 节)
  1. 1.计算机视觉概述
  2. 2.计算机视觉应用场景
  3. 3.困难与挑战
  4. 4.开源库介绍
  5. 5.图像显示与存储
  6. 6.CLAHE
  7. 7.腐蚀膨胀与开闭运算
  8. 8.滤波与边缘填充
  9. 9.各种滤波
  10. 10.图像金字塔
  11. 11.傅里叶变换
  12. 12.短时傅里叶变换与小波变换
  13. 13.颜色特征与颜色直方图
  14. 14.几何特征_边缘
  15. 15.几何特征-角点与斑点
  16. 16.局部特征-SIFT特征描述子
  17. 17.局部特征-SURF特征描述子
  18. 18.局部特征-ORB特征描述子
  19. 19.其他特征描述子
  20. 20.传统图像分割技术
  21. 21.GraphCut
  22. 22.GrabCut
  23. 23.人脸检测的传统方法
  24. 24.行人检测的传统方法HOG+SVM
  25. 25.行人检测DPM
  26. 26.人工神经网络的架构
  27. 27.目标函数与梯度下降
  28. 28.误差反向传播
  29. 29.可手算的神经网络
  30. 30.改进目标函数与克服梯度消散
  31. 31.神经网络之传统神经网络与深度网络的差异
  32. 32.各种梯度下降算法
  33. 33.如何选择合适的梯度下降算法
  34. 34.避免过适应
  35. 35.CNN的架构
  36. 36.CNN的误差反向传播_卷积层
  37. 37.CNN的误差反向传播_池化层
  38. 38.分类所用的数据集
  39. 39.Alexnet详解
  40. 40.VGG详解
  41. 41.GoogLeNet_v1
  42. 42.BatchNormalization
  43. 43.GoogLeNet_v3v4
  44. 44.ResNet
  45. 45.ResNeXt
  46. 46.CNN设计准则
  47. 47.图像检索与推土机距离
  48. 48.基于颜色相似性
  49. 49.基于形状相似性
  50. 50.基于纹理相似性
  51. 51.基于局部特征相似性
  52. 52.DL的二值哈希编码
  53. 53.高维空间中检索KDTree
  54. 54.高维空间中检索LSH
  55. 55.目标识别任务综述
  56. 56.RCNN
  57. 57.SPP-Net
  58. 58.Fast R-CNN
  59. 59.Faster R-CNN
  60. 60.R-CNN系列模型回顾
  61. 61.R-FCN的设计思路
  62. 62.R-FCN的实现
  63. 63.YOLOv1
  64. 64.YOLOv2_9000
  65. 65.YOLOv3
  66. 66.图像分割应用场景与数据集
  67. 67.上采样的实现方式
  68. 68.FCN网络架构的由来
  69. 69.FCN构建细节
  70. 70.DeepLabv1
  71. 71.CRF
  72. 72.DeepLabv2
  73. 73.DeepLabv3
  74. 74.特定的医学影像分割任务
  75. 75.U-Net
  76. 76.3D U-Net
  77. 77.V-Net
  78. 78.DenseNet
  79. 79.病理切片分析任务及数据集
  80. 80.病理切片分析
  81. 81.图说任务及其传统处理方法
  82. 82.深度语言模型与RNN
  83. 83.LSTM及其变种
  84. 84.NIC模型
  85. 85.SAT模型
  86. 86.图说的性能指标
  87. 87.判别模型与生成模型
  88. 88.KL散度与JS散度
  89. 89.VAE
  90. 90.GAN的基本原理
  91. 91.DCGAN与CGAN
  92. 92.Wasserstein GAN
  93. 93.SRGAN

ResNet

ResNet残差网络

小象实战讲义 · 基于深度学习的计算机视觉

在深度卷积神经网络的发展历程中,研究者们曾普遍认为“网络越深,性能越好”。然而,ResNet(残差网络)的提出揭示了更深网络所面临的“退化”问题,并引入了一种革命性的“捷径连接”结构来解决它。本节我们将深入剖析ResNet的核心思想——残差学习,理解它如何让训练数百层的超深网络成为可能,并成为后续众多视觉模型的基础架构。

💡 核心导读

  • 网络退化问题:传统CNN堆叠到一定深度(如20层以上)后,性能不升反降,这并非梯度问题,而是优化难题。
  • 残差学习:核心思想是让网络层学习输入与输出之间的“残差”(差值),而非直接学习复杂的底层映射,极大降低了优化难度。
  • 捷径连接:通过恒等映射将输入直接“抄近道”加到后续层的输出上,构成了残差块的基本结构。
  • Bottleneck设计:使用1×1卷积进行先降维再升维的操作,在保持甚至提升性能的同时,大幅减少了深层网络的参数量和计算量。

从网络退化到残差学习

在ResNet之前,VGGNet最多有19层,GoogLeNet(Inception V1)可视为22层。人们发现,当网络层数堆叠到20层左右时,似乎遇到了一个性能门槛。如果继续用传统方式堆叠更多层(例如56层),不仅性能没有提升,训练误差和测试误差反而都会显著增大。这种现象被称为网络退化(Degradation),它并非由梯度消失或爆炸引起(因为已有BN等技术缓解),而是更深网络本身难以优化导致的。

下图示意了这种退化现象:20层网络的训练误差可以收敛到较低水平,但56层的传统网络训练误差反而更高,测试性能也更差。

训练误差曲线示意图:
20层网络:误差平滑下降至低位(如2%)
56层传统网络:误差下降缓慢,最终停留在高位(如5%)

测试误差曲线示意图:
20层网络:误差收敛至约10%
56层传统网络:误差明显更高(如12%)

那么,如何让网络充分利用“深度”这一维度,而不是被其阻碍呢?残差网络(Residual Network)应运而生。

残差块:捷径连接与恒等映射

ResNet的核心创新是残差块(Residual Block)。我们通过一个简化结构来理解其思想。

假设我们希望网络学习一个底层映射 H(x)H(x),例如将输入图像映射为更抽象的特征。在传统网络中,堆叠的非线性层会直接尝试拟合这个映射 H(x)H(x)。

ResNet则换了一种思路。它让堆叠的层去拟合另一个映射 F(x):=H(x)−xF(x) := H(x) - x,即残差映射。然后,网络的输出通过捷径连接(Shortcut Connection) 将输入 xx 与学习到的残差 F(x)F(x) 相加得到:H(x)=F(x)+xH(x) = F(x) + x。

残差块结构示意图:
输入 x
│
├───────────────┐
│               │
│ 权重层        │
│ (如卷积、BN、ReLU) │
│               │
│ 权重层        │
│ (如卷积、BN)    │
│               │
└─────→ F(x)    │
        │       │
        └───⊕ ←──┘ (逐元素相加)
            │
        输出 H(x) = F(x) + x

这里的“⊕”代表逐元素相加(element-wise addition),要求 F(x)F(x) 的输出维度必须与 xx 相同。这条从输入直接连到相加操作的路径,就是“捷径”或“跳跃连接”。

为什么学习残差更容易? 我们用一个数值例子来理解。假设输入 x=10x=10,我们希望最终输出 H(x)=10H(x)=10(即恒等映射),而当前网络实际输出 H(x)=10.1H(x)=10.1。

  • 传统学习:目标是让 10.1→1010.1 \rightarrow 10,这是一个相对变化率仅为 (10.1−10)/10.1≈1%(10.1-10)/10.1 \approx 1\% 的微调,可能需要极小的学习率才能精准调整。
  • 残差学习:目标是让残差 F(x)=H(x)−x=0.1→0F(x) = H(x) - x = 0.1 \rightarrow 0。从 0.10.1 到 00 的变化,可以看作是一个100% 的相对变化。学习器对这个目标更敏感,使用常规甚至稍大的学习率也能有效更新。

因此,残差学习将优化目标从一个绝对量的精确拟合,转变为一个相对量(残差)的修正,尤其是在目标值附近时,大大降低了优化难度。当残差 F(x)=0F(x)=0 时,该残差块就等价于一个恒等映射 H(x)=xH(x)=x,这至少能保证网络性能不会比浅层网络更差。

需要说明的是,上面的数值例子只是为了建立直观。更本质的原因是:恒等映射在残差形式下变得“容易实现”——只要把残差分支的权重压向 0,F(x)F(x) 就自然趋近 0,网络的默认行为就是“什么都不改”;而让一堆非线性层精确地凑出恒等映射则困难得多。这正是“退化问题”被化解的关键。

ResNet的网络架构与设计细节

ResNet并非只有残差块这一项创新,它在整体架构上也做了精心设计。

整体架构对比 ResNet提出了多种深度的版本,如ResNet-34, ResNet-50, ResNet-101, ResNet-152。下图对比了ResNet-34与VGG-19的结构,可见ResNet层数更深,但通过残差块保持了可训练性。

网络结构对比简表(以ImageNet输入224x224为例):
VGG-19: [2x64C3] MP -> [2x128C3] MP -> [4x256C3] MP -> [4x512C3] MP -> [4x512C3] MP -> FC*3
ResNet-34: 初始卷积层 -> [3个残差块,各2层,共6层,输出64维] -> [4个残差块,各2层,共8层,输出128维] -> [6个残差块,各2层,共12层,输出256维] -> [3个残差块,各2层,共6层,输出512维] -> 全局平均池化 -> FC

(注:C3表示3x3卷积,MP表示最大池化,FC表示全连接层)

关键设计原则

  1. 大量使用3×3卷积核:继承了VGG的设计,结构简洁。
  2. 用步长为2的卷积替代池化层进行下采样:ResNet中取消了独立的池化层(除了最开始的池化)。在需要缩小特征图尺寸时,使用步长(stride)为2的卷积层同时完成特征提取和下采样。
  3. 广泛使用批量归一化(Batch Normalization):在每个卷积层后都使用BN,加速训练并提升稳定性。
  4. 取消或减少的部分:
    • 取消最大池化层(除网络开端外)。
    • 极大减少全连接层的使用:网络末端用全局平均池化(Global Average Pooling) 替代传统CNN中多个大尺寸全连接层,只在最后保留一个用于分类的全连接层,大幅减少参数。
    • 由于BN起到了正则化作用,ResNet中不使用Dropout(原论文明确说明)。

下采样与维度匹配 当捷径连接需要跨越多层,且前后特征图尺寸或通道数发生变化时(例如下采样后通道数翻倍),需要对恒等映射的 xx 进行处理。ResNet采用两种方式:

  1. 补零填充:捷径仍然做恒等映射,但当特征图空间尺寸减半时,先以步长2对 xx 隔点采样完成空间下采样,再在通道维度上补零,把 xx 的通道数补齐到与 F(x)F(x) 一致。这样做不引入额外参数,代价是下采样时新增的那些通道没有携带来自输入的信息。
  2. 1×1卷积投影:用步长为2的1×1卷积对 xx 同时完成下采样与升维,使其形状与 F(x)F(x) 完全匹配。这样做会引入少量参数(即投影矩阵),但信息利用更充分。

Bottleneck:构建超深网络的钥匙

对于ResNet-34及更浅的网络,残差块使用两个3×3卷积层(称为“BasicBlock”)。但要构建50层、101层、152层这样的超深网络,计算量会非常庞大。为此,ResNet引入了 Bottleneck(瓶颈)结构。

Bottleneck块使用三个卷积层:1×1, 3×3, 1×1。

  • 第一个1×1卷积用于降维,减少通道数(例如从256降到64)。
  • 第二个3×3卷积在低维空间进行主要的特征提取。
  • 第三个1×1卷积用于升维,恢复通道数(例如从64升回256)。
Bottleneck块结构(以输入/输出256维为例):
输入 x (256维)
│
├───────────────┐
│               │
│ 1x1卷积       │ (降维至64维)
│ BN, ReLU      │
│               │
│ 3x3卷积       │ (保持64维)
│ BN, ReLU      │
│               │
│ 1x1卷积       │ (升维回256维)
│ BN            │ (注意:最后一个BN在相加之后才接ReLU)
│               │
└─────→ F(x)    │
        │       │
        └───⊕ ←──┘
            │
        输出 (经过ReLU激活)

这种“先压缩,再处理,再扩展”的策略,在保持甚至增强模型表达能力的同时,显著降低了3×3卷积层的计算成本。3×3卷积的计算量与输入输出通道数的乘积成正比。Bottleneck结构将3×3卷积的输入输出通道数从256大幅降低到64,使得构建超深网络变得可行。

不同深度的ResNet正是通过堆叠不同数量的BasicBlock或Bottleneck块构建而成的。

代码示例:理解残差连接与维度变换

为了直观理解残差连接中的逐元素相加以及维度不匹配时的处理,我们用NumPy模拟一个小型示例。

示例1:恒等映射的残差连接 假设我们有一个4x4的二维特征图(为简化,模拟单通道),经过一个“网络层”(这里用简单的标量乘法模拟变换)后,再与输入相加。

import numpy as np

# 模拟输入特征图 x
x = np.array([[1, 2, 3, 4],
              [5, 6, 7, 8],
              [9, 10, 11, 12],
              [13, 14, 15, 16]], dtype=np.float32)

# 模拟一个简单的“网络层”F,这里假设F(x) = 0.1 * x
# 在实际网络中,F是复杂的卷积、BN等组合
def simulated_F(input_feature):
    # 模拟一个非常简单的变换
    return 0.1 * input_feature

F_x = simulated_F(x)
print("残差 F(x):")
print(F_x)
print()

# 残差连接:H(x) = F(x) + x
H_x = F_x + x
print("输出 H(x) = F(x) + x:")
print(H_x)
残差 F(x):
[[0.1        0.2        0.3        0.4       ]
 [0.5        0.6        0.7        0.8       ]
 [0.90000004 1.         1.1        1.2       ]
 [1.3000001  1.4        1.5        1.6       ]]

输出 H(x) = F(x) + x:
[[ 1.1  2.2  3.3  4.4]
 [ 5.5  6.6  7.7  8.8]
 [ 9.9 11.  12.1 13.2]
 [14.3 15.4 16.5 17.6]]

示例2:维度不匹配时的1×1卷积投影 当捷径连接两端的维度不一致时,需要对输入x进行投影。这里我们模拟通道数翻倍的情况,按 (高, 宽, 通道) 的布局组织数据。

import numpy as np

# 模拟输入 x,形状为 (高, 宽, 通道) = (2, 2, 2),每个空间位置有两个通道值
x = np.array([[[10., 11.],
               [20., 21.]],
              [[30., 31.],
               [40., 41.]]], dtype=np.float32)
print("输入 x 形状:", x.shape)
print("输入 x:")
print(x)
print()

# 假设 F(x) 的输出是 2x2x4(通道数翻倍),需要把 x 从 2 通道投影到 4 通道
# 1x1 卷积的权重形状为 (输出通道, 输入通道, 1, 1)
W_proj = np.array([[[[1.0]], [[0.0]]],    # 输出通道0:只取输入通道0
                   [[[0.0]], [[1.0]]],    # 输出通道1:只取输入通道1
                   [[[0.5]], [[0.5]]],    # 输出通道2:两个通道取平均
                   [[[1.0]], [[-1.0]]]],  # 输出通道3:通道0减通道1
                  dtype=np.float32)
print("投影权重 W_proj 形状:", W_proj.shape)

# 1x1 卷积等价于对每个空间位置做一次 (输出通道, 输入通道) 的矩阵乘法
x_flat = x.reshape(-1, 2)            # (4个空间位置, 2个输入通道)
W_flat = W_proj.reshape(4, 2)        # (4个输出通道, 2个输入通道)
x_proj = x_flat @ W_flat.T           # (4, 2) @ (2, 4) -> (4, 4)
x_proj = x_proj.reshape(2, 2, 4)     # 还原为 (高, 宽, 通道)

print("\n投影后的 x 形状:", x_proj.shape)
print("投影后的 x:")
print(x_proj)
输入 x 形状: (2, 2, 2)
输入 x:
[[[10. 11.]
  [20. 21.]]

 [[30. 31.]
  [40. 41.]]]

投影权重 W_proj 形状: (4, 2, 1, 1)

投影后的 x 形状: (2, 2, 4)
投影后的 x:
[[[10.  11.  10.5 -1. ]
  [20.  21.  20.5 -1. ]]

 [[30.  31.  30.5 -1. ]
  [40.  41.  40.5 -1. ]]]

这个例子展示了如何通过一个1×1卷积(投影矩阵W_proj)将输入x从2通道变换到4通道,以匹配后续残差块输出的维度,从而能够进行逐元素相加。

Inception与ResNet的结合

ResNet的思想影响深远,其捷径连接机制也被其他网络架构所吸收。一个著名的例子就是 Inception-ResNet,它将GoogLeNet的Inception模块与ResNet的残差连接相结合。在Inception-ResNet中,每个Inception模块的输出会与模块的输入(经过适当处理)相加,形成了Inception式的残差块。这种融合进一步提升了模型的性能,体现了深度学习模型中模块化设计和思想融合的强大力量。

📝 动手练一练

  1. 概念辨析:假设一个传统的30层CNN在训练集上表现比20层的版本更差(训练误差更高)。请判断以下哪些可能是主要原因,哪些不是? A. 30层网络发生了严重的梯度消失。 B. 30层网络模型复杂度太高,对训练集过拟合了。 C. 30层网络更深,优化器更难找到好的解,即遇到了“网络退化”。 D. 30层网络需要更小的学习率,但当前学习率设置得太大。
👉 点击查看参考答案
  • A. 不是主要原因。虽然梯度消失是深度网络的常见问题,但题目描述的是“训练集”上表现更差。梯度消失主要影响梯度回传,导致网络底层参数更新困难,但通常不会直接导致更深的网络在训练集上误差更高。且现代网络常用BN、ReLU等技术缓解梯度消失。
  • B. 不是主要原因。过拟合通常表现为训练误差低但测试误差高。题目明确说训练误差更高,这与过拟合的典型现象不符。
  • C. 是主要原因。这正是ResNet论文中指出的“网络退化”现象:更深网络的训练误差反而更大,这不是过拟合或梯度问题,而是单纯的非线性层堆叠过多导致优化困难。
  • D. 可能是部分原因,但不是本质原因。学习率设置不当会影响收敛,但“网络退化”是更深网络结构本身带来的优化难题,即使精心调整超参数,传统深层网络也往往难以达到浅层网络的性能。

核心答案:C是主要原因。

  1. 手算理解:考虑一个极端情况,在一个残差块中,我们希望其学习到的映射是恒等映射,即 H(x)=xH(x) = x。根据公式 H(x)=F(x)+xH(x) = F(x) + x,请问此时网络需要让残差映射 F(x)F(x) 学习到什么值?这说明了残差学习在优化上的什么潜在优势?
👉 点击查看参考答案

根据公式 H(x)=F(x)+xH(x) = F(x) + x,若希望 H(x)=xH(x) = x,则需满足: x=F(x)+xx = F(x) + x 两边同时减去 xx,得到: F(x)=0F(x) = 0

因此,网络需要让残差映射 F(x)F(x) 学习到全零输出。

优化优势:将学习目标从“拟合一个复杂的恒等映射 H(x)=xH(x)=x”转变为“让输出变为0”。将所有权重推向0,比让它们精确配合以产生恒等映射要容易得多。特别是在权重初始化为接近0的小随机值时,这个目标(输出为0)的初始状态就已经很接近了。这为优化器提供了一个更简单、更平滑的起点。这也意味着,即使增加的残差块没有学到有用的特征,它至少可以“退化”为恒等映射,而不会损害网络原有的性能,保证了网络深度增加时性能不会下降。

本章小结

ResNet通过引入“残差学习”和“捷径连接”,巧妙地解决了超深神经网络的退化问题,使训练数百层的网络成为现实。其核心在于让网络层学习输入与输出之间的残差,而非完整的映射,这显著降低了优化难度。Bottleneck结构则通过1×1卷积进行先降维再升维的操作,在维持性能的前提下控制了计算成本,为构建ResNet-50、101、152等模型提供了基础。ResNet不仅在当时刷新了多项视觉任务记录,其残差思想更成为深度学习架构设计中一个普适而强大的工具。

📋 行动清单

  • 在脑海中绘制一个ResNet残差块(BasicBlock)的结构图,明确标出输入、卷积层、BN层、ReLU激活、捷径连接和相加操作的位置。
  • 理解Bottleneck结构中三个卷积层(1×1, 3×3, 1×1)各自的作用,并解释为何它能减少计算量。
  • 思考:如果在你的一个项目中使用CNN,什么情况下你会考虑采用或借鉴ResNet的残差结构?

2026 视角

ResNet(2015)的提出是CNN架构发展的一个里程碑,其残差连接思想影响至今。从2026年的视角回望,我们可以看到两条清晰的技术演进脉络:

  1. CNN架构的持续进化:ResNet之后,CNN的设计并未停滞。2017年的DenseNet通过更密集的跨层连接,进一步改善了梯度和特征流动。2019年的EfficientNet通过复合缩放(深度、宽度、分辨率)系统化地平衡了模型性能与效率。而ConvNeXt(2022) 则代表了现代CNN设计的集大成者。它“现代化”了标准ResNet,借鉴了Swin Transformer等视觉Transformer的设计理念,如使用更大的卷积核(7x7)、更少的激活函数、更少的归一化层、以及分阶段的下采样设计。ConvNeXt在多个视觉任务上取得了与先进Transformer相媲美的性能,证明了经过精心设计的CNN仍然具有强大的竞争力,其归纳偏置(平移等变性、局部性)对于许多视觉任务依然是有效的先验知识。

  2. Vision Transformer的崛起与融合:另一方面,2020年Vision Transformer(ViT)的诞生开启了新的范式。ViT将图像切分为序列化的图块,用纯Transformer架构进行处理,展现了强大的全局建模能力和可扩展性。随后的Swin Transformer(2021)通过引入移位窗口和层次化设计,高效地建模了多尺度特征。这引发了一场关于“CNN归纳偏置是否必要”的讨论。ViT派认为,数据足够多时,Transformer可以从数据中学习到所需的任何偏置;CNN派则认为,CNN的归纳偏置是高效且有效的先验。

当前共识与趋势:

  • 并非替代,而是融合与选择:在今天,CNN(尤其是ConvNeXt这类现代变体)和Vision Transformer是并存的强大工具。CNN在计算效率、对中小规模数据集的友好度方面仍有优势。ViT及其变体在需要极强全局上下文理解或利用海量预训练数据的任务上表现卓越。
  • 架构融合:许多新模型融合了CNN和Transformer的优点,例如在早期使用CNN提取局部特征,再用Transformer进行全局关系建模。
  • ResNet的遗产:残差连接已成为几乎所有深度网络架构(包括Transformer)的标准组件,用于缓解梯度消失和稳定深层训练。ResNet本身也因其简洁、鲁棒和出色的迁移学习能力,至今仍是许多视觉任务(如检测、分割)中常用的骨干网络(backbone)选择之一。

因此,学习ResNet不仅是为了理解一个历史模型,更是为了掌握“残差学习”这一深度学习的核心设计范式,并理解现代视觉基础模型演进的起点。

—— 小象教研组