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