SilverIce Toolbox
Back to course

Stage 4 / Chapter 17

第17章:经典网络与 ResNet | Chapter 17: Classic Networks & ResNet

阶段定位 | Stage: 第四阶段 — ML 策略与 CNN 预计学时 | Duration: 5~6 小时

---

学习目标 | Learning Objectives

中文:

  • 理解 CNN 架构演进:LeNet → AlexNet → VGG → ResNet
  • 掌握残差连接(Skip Connection)的数学原理
  • 理解为什么 ResNet 能解决深层网络的梯度消失问题
  • 掌握 1×1 卷积的作用与应用场景
  • 能在 PyTorch 中实现基础残差块

English:

  • Understand CNN architecture evolution: LeNet → AlexNet → VGG → ResNet
  • Master skip connection mathematical principles
  • Understand why ResNet solves vanishing gradients in deep networks
  • Master 1×1 convolution roles and applications
  • Implement basic residual block in PyTorch

---

17.1 CNN 架构演进 | CNN Evolution

中文解释

LeNet-5 (1998)

特性说明
层数5 层(2 conv + 3 fc)
参数6 万
创新第一个成功的 CNN,用于手写数字识别
局限太浅,无法处理复杂图像

AlexNet (2012)

特性说明
层数8 层(5 conv + 3 fc)
参数6000 万
创新ReLU、Dropout、GPU 并行训练
意义ImageNet 冠军,开启深度学习时代

VGG-16 (2014)

特性说明
层数16 层
参数1.38 亿
创新全部用 3×3 小卷积核堆叠,结构极其规整
洞察两个 3×3 = 一个 5×5 的感受野,但参数更少

趋势

LeNet (5层) → AlexNet (8层) → VGG (16-19层) → ResNet (50-152层+)

网络越来越深。但 VGG 之后,单纯加深遇到了瓶颈:

  • 56 层网络的训练误差反而高于 20 层网络!
  • 梯度消失导致深层网络无法训练

English Explanation

Evolution:

  • LeNet-5 (1998): 5 layers, 60K params
  • AlexNet (2012): 8 layers, 60M params, ReLU+Dropout+GPU
  • VGG-16 (2014): 16 layers, 138M params, all 3×3

Bottleneck: VGG-style deeper networks performed worse due to vanishing gradients.

---

17.2 残差连接 | Skip Connections

中文解释

核心洞察

ResNet 作者发现:

学习残差 F(x) = H(x) - x 比学习完整映射 H(x) 更容易。

残差块公式

a[l+2] = g(z[l+2] + a[l])
       = g(W[l+2]·a[l+1] + b[l+2] + a[l])

其中 a[l] 通过跳跃连接(skip connection / shortcut)直接加到 l+2 层的输出上。

为什么能解决梯度消失?

反向传播时,梯度有两条路可走:

∂L/∂a[l] = ∂L/∂a[l+2] · ∂a[l+2]/∂a[l]
         = ∂L/∂a[l+2] · (1 + ∂F/∂a[l])

即使 ∂F/∂a[l] 很小,梯度仍可通过 +1 项直接传回:

∂a[l+2]/∂a[l] = 1 + (something small) ≈ 1

这就像给梯度开了一条高速公路

  • 普通网络:梯度必须穿过所有层(乡间小路)
  • ResNet:梯度可以走跳跃连接(高速公路)

English Explanation

Key Insight: Learning residual F(x) = H(x) - x is easier than learning full mapping H(x).

Why it solves vanishing gradients:

∂a[l+2]/∂a[l] = 1 + ∂F/∂a[l] ≈ 1

Gradient has a highway via skip connection, bypassing the main path.

---

17.3 1×1 卷积 | 1×1 Convolution

中文解释

作用

1×1 卷积不改变空间尺寸,只改变通道数:

输入: (H, W, C_in)
1×1 卷积: (1, 1, C_in, C_out)
输出: (H, W, C_out)

用途

用途说明
降维C_in=256 → C_out=64,减少计算量
升维C_in=64 → C_out=256,增加表达能力
特征混合跨通道线性组合,类似全连接

Inception 模块中的应用

输入
├── 1×1 conv (降维)
├── 1×1 conv → 3×3 conv
├── 1×1 conv → 5×5 conv
└── MaxPool → 1×1 conv
    └── 拼接所有分支

1×1 卷积先降维,再做大卷积,大幅减少计算量。

English Explanation

1×1 convolution: changes channel dimension without changing spatial size.

Uses: dimension reduction, dimension expansion, feature mixing across channels.

---

17.4 完整实现:ResNet 残差块

代码案例

python
import torch
import torch.nn as nn

class ResidualBlock(nn.Module):
    """
    基础残差块:
    x → [Conv3×3 → BN → ReLU → Conv3×3 → BN] → (+x) → ReLU
    """
    def __init__(self, in_channels, out_channels, stride=1):
        super().__init__()
        self.conv1 = nn.Conv2d(in_channels, out_channels, 3,
                               stride=stride, padding=1, bias=False)
        self.bn1 = nn.BatchNorm2d(out_channels)
        self.relu = nn.ReLU(inplace=True)
        self.conv2 = nn.Conv2d(out_channels, out_channels, 3,
                               padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(out_channels)

        # Shortcut: 维度不匹配时做 1×1 卷积调整
        self.shortcut = nn.Sequential()
        if stride != 1 or in_channels != out_channels:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_channels, out_channels, 1,
                          stride=stride, bias=False),
                nn.BatchNorm2d(out_channels)
            )

    def forward(self, x):
        out = self.relu(self.bn1(self.conv1(x)))
        out = self.bn2(self.conv2(out))
        out += self.shortcut(x)  # 残差连接
        out = self.relu(out)
        return out

# ========== 测试 ==========
print("=" * 50)
print("ResNet 残差块测试")
print("=" * 50)

# 等维度块
block1 = ResidualBlock(64, 64)
x1 = torch.randn(4, 64, 32, 32)
y1 = block1(x1)
print(f"\n等维度残差块:")
print(f"  输入: {x1.shape}")
print(f"  输出: {y1.shape}")
print(f"  跳跃连接直接相加: {x1.shape == y1.shape}")

# 下采样块(stride=2)
block2 = ResidualBlock(64, 128, stride=2)
x2 = torch.randn(4, 64, 32, 32)
y2 = block2(x2)
print(f"\n下采样残差块 (stride=2):")
print(f"  输入: {x2.shape}")
print(f"  输出: {y2.shape}")
print(f"  1×1 shortcut 调整通道: 64→128,空间减半")

# 计算参数
print(f"\n参数统计:")
total = sum(p.numel() for p in block2.parameters())
print(f"  下采样块总参数: {total:,}")

输出:

==================================================
ResNet 残差块测试
==================================================

等维度残差块:
  输入: torch.Size([4, 64, 32, 32])
  输出: torch.Size([4, 64, 32, 32])
  跳跃连接直接相加: True

下采样残差块 (stride=2):
  输入: torch.Size([4, 64, 32, 32])
  输出: torch.Size([4, 128, 16, 16])
  1×1 shortcut 调整通道: 64→128,空间减半

参数统计:
  下采样块总参数: 149,120

---

本章总结 | Chapter Summary

中文:

  • CNN 演进:LeNet → AlexNet → VGG → ResNet,网络越来越深
  • VGG 之后单纯加深遇到梯度消失瓶颈
  • ResNet 残差连接:学习 F(x) = H(x) - x 比学习 H(x) 更容易
  • 梯度可通过 +1 项直接回传,解决梯度消失
  • 1×1 卷积用于降维/升维,减少计算量
  • 等维度用直接相加,不同维度用 1×1 shortcut

English:

  • CNN evolution: LeNet → AlexNet → VGG → ResNet
  • ResNet: learning F(x) = H(x) - x is easier
  • Skip connections allow gradient to flow directly via +1 term
  • 1×1 conv for dimension reduction/expansion
  • Equal dims: direct addition; different dims: 1×1 shortcut

---

课后练习 | Homework

  1. 残差梯度推导:严格证明 ∂a[l+2]/∂a[l] = 1 + ∂F/∂a[l],解释为什么残差连接能缓解梯度消失。
  1. 1×1 卷积计算:输入 (32, 32, 256),用 1×1 卷积降到 64 通道。计算参数和计算量,对比直接用 3×3 卷积。
  1. 感受野对比:对比 VGG(堆叠 3×3)和 AlexNet(用 5×5, 11×11)在相同感受野下的参数量。
  1. 实现 Bottleneck:实现 ResNet-50 的 Bottleneck 块(1×1 → 3×3 → 1×1),对比与 Basic Block 的参数和计算量。
  1. 残差 vs 非残差:在 CIFAR-10 上分别训练 20 层和 56 层的普通网络 vs ResNet。对比训练误差和测试误差。