STAGE 2 计算机视觉

CNN 卷积神经网络

图像识别的基石 — 从卷积操作到 ResNet

📊 Excel 手推 ⏱️ 30 分钟 🎯 计算机视觉
1
📋 本章要点
  • 卷积层通过局部感受野和参数共享大幅减少参数量
  • 池化层降低空间维度,增强平移不变性
  • VGG 证明了"更深的网络 + 小卷积核"优于"浅层 + 大卷积核"
  • ResNet 的残差连接解决了深层网络的退化问题

为什么需要 CNN?

假设我们有一张 224×224 的彩色图片,展平后有 224×224×3 = 150,528 个像素。 如果第一个隐藏层有 1000 个神经元,仅第一层就需要 1.5 亿个参数!这不仅计算量巨大,而且容易过拟合。

更重要的是,全连接层把像素展平,完全丢失了空间信息 — 它不知道哪些像素是相邻的,也无法识别局部特征(如边缘、纹理)。

类比:想象你第一次看一幅画 — 你不会同时注意每一个像素。 你会先看局部的纹理和线条,再组合成更大的形状,最后理解整幅画的内容。CNN 做的正是同样的事情。

CNN 的三大核心思想

🔍

局部感受野

每个神经元只看输入的一小块区域,而不是整个图像

⚖️

权重共享

同一个卷积核在整张图上滑动,参数量大幅减少

📐

池化

降低空间分辨率,提取主要特征,增强平移不变性

2

📊 卷积操作手推

下面展示一个 5×5 输入与 3×3 卷积核的运算过程。 点击单元格可以编辑输入值和卷积核!修改后特征图会自动更新。

输入 (5×5)

01234
0 1 0 1 0 1
1 0 1 0 1 0
2 1 0 1 0 1
3 0 1 0 1 0
4 1 0 1 0 1

卷积核 (3×3)

012
0 1 0 1
1 0 1 0
2 1 0 1

当前步骤详情

选择一个输出位置查看计算过程

输出特征图 (3×3) 点击单元格查看计算步骤

012
0 4 2 4
1 2 5 2
2 4 2 4

💡 观察:卷积核在输入上滑动,每到一个位置就做逐元素相乘再求和。 3×3 的核在 5×5 的输入上滑动,输出是 3×3(即 5-3+1=3)。这就是卷积操作的本质!

3

📊 池化操作手推

池化(Pooling)用于降低空间分辨率。最常用的是最大池化(Max Pooling): 在每个 2×2 区域中取最大值。 点击输入单元格可以编辑!

输入 (4×4)

0123
0 1 3 2 4
1 5 6 1 2
2 3 2 8 7
3 4 1 3 5

Max Pooling 输出 (2×2)

01
0 6 4
1 4 8

池化说明

每个 2×2 区域取最大值。绿色高亮的数字是被选中的最大值。

💡 池化的作用: ① 降低维度,减少计算量; ② 提取主要特征,丢弃不重要的细节; ③ 提供平移不变性 — 即使目标稍微移动,池化后的输出也几乎不变。

4

🏗️ 经典架构演进

CNN 的发展经历了从简单到复杂的演进过程。每一个里程碑都带来了关键的创新。

1998

LeNet-5

由 Yann LeCun 提出,是第一个成功的 CNN,用于手写数字识别(MNIST)。 它证明了卷积网络可以有效学习图像特征。

结构流程

输入
32×32
Conv1
6@28×28
Pool1
6@14×14
Conv2
16@10×10
Pool2
16@5×5
FC
120
FC
84
输出
10

创新:首次成功将卷积 + 池化 + 全连接组合用于图像识别,奠定了 CNN 的基本架构。

2014

VGG

VGG 的核心思想非常简单:用多个 3×3 小卷积核堆叠代替大卷积核。 例如,两个 3×3 卷积的感受野等价于一个 5×5,但参数更少、非线性更强。

VGG-16 简化结构

输入
224×224
2×Conv
64
Pool 2×Conv
128
Pool 3×Conv
256
... FC
1000

LeNet vs VGG 对比

特性LeNet-5VGG-16
深度5 层16 层
输入尺寸32×32224×224
卷积核5×53×3
参数量~60K~138M

创新:证明了"更深 + 更小卷积核"比"更浅 + 更大卷积核"效果更好。

2015

ResNet

网络越深,理论上应该越好。但实际上,超过一定深度后,训练误差反而上升(不是过拟合,是训练误差!)。 这是因为深层网络的梯度消失问题。

ResNet 的解决方案极其优雅:残差连接(Skip Connection)。 不直接学习目标映射 H(x),而是学习残差 F(x) = H(x) - x, 最终输出 F(x) + x

残差块 (Residual Block)

x Conv → BN → ReLU → Conv → BN F(x)
identity
x
+
residual
F(x)
ReLU F(x) + x

VGG vs ResNet 对比

特性VGG-16ResNet-50
深度16 层50 层
Skip Connection
参数量~138M~25M
Top-5 错误率7.3%3.6%

为什么残差连接有效?

  • 梯度高速公路:梯度可以通过 skip connection 直接回传,不经过非线性变换
  • 学习更容易:学习 F(x) ≈ 0 比学习 H(x) = x 更简单
  • 集成效果:ResNet 相当于多个不同深度网络的集成
2017

DenseNet

DenseNet 把"连接"做到了极致:每一层都与前面所有层相连。 第 l 层的输入是前面所有层输出的拼接(concatenation),而不是相加。

ResNet
x + F(x)
逐元素相加
DenseNet
[x, F₁(x), F₂(x), ...]
通道拼接
特点
参数更少,特征复用
但显存占用更大
5

🎮 互动实验:卷积参数探索

拖动滑块改变卷积参数,观察输出特征图尺寸如何变化。

7
3
1
0
输出尺寸公式:
⌊( n + 2× p - k ) / s ⌋ + 1
输出尺寸 = ⌊(7 + 0 - 3) / 1⌋ + 1 = 5
💡 尝试把步长改为 2,看看输出尺寸如何减半
6

💻 PyTorch 代码

conv2d_example.py
import torch
import torch.nn as nn

# 2D 卷积层
conv = nn.Conv2d(
    in_channels=3,      # 输入通道数(RGB=3)
    out_channels=16,    # 输出通道数(卷积核个数)
    kernel_size=3,     # 卷积核大小 3×3
    stride=1,          # 步长
    padding=1          # 填充(保持尺寸不变)
)

# 输入: (batch, channels, height, width)
x = torch.randn(1, 3, 32, 32)  # 1张 32×32 RGB图
out = conv(x)
print(out.shape)  # torch.Size([1, 16, 32, 32])

# 查看参数量
params = sum(p.numel() for p in conv.parameters())
print(f"参数量: {params}")  # 448 = 3×16×3×3 + 16
maxpool_example.py
# 最大池化层
pool = nn.MaxPool2d(
    kernel_size=2,  # 2×2 窗口
    stride=2       # 步长=2,尺寸减半
)

x = torch.randn(1, 16, 32, 32)
out = pool(x)
print(out.shape)  # torch.Size([1, 16, 16, 16])

# 自适应池化(输出固定尺寸)
adaptive_pool = nn.AdaptiveAvgPool2d((1, 1))
out = adaptive_pool(x)
print(out.shape)  # torch.Size([1, 16, 1, 1])
simple_cnn.py
class SimpleCNN(nn.Module):
    def __init__(self):
        super().__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 32, 3, padding=1),  # 32×32→32×32
            nn.ReLU(),
            nn.MaxPool2d(2),                    # 32×32→16×16
            nn.Conv2d(32, 64, 3, padding=1), # 16×16→16×16
            nn.ReLU(),
            nn.MaxPool2d(2),                    # 16×16→8×8
        )
        self.classifier = nn.Sequential(
            nn.Flatten(),                         # 64×8×8 = 4096
            nn.Linear(4096, 128),
            nn.ReLU(),
            nn.Linear(128, 10),                 # 10 类分类
        )

    def forward(self, x):
        x = self.features(x)
        x = self.classifier(x)
        return x

# 使用
model = SimpleCNN()
x = torch.randn(1, 3, 32, 32)
print(model(x).shape)  # torch.Size([1, 10])
7

🧠 小测验

🔗 相关章节推荐
  • UNetUNet 是 CNN 的编码器-解码器变体
  • MLP卷积层是特殊的全连接层
  • Autoencoder卷积自编码器的结构基础

1. 卷积操作的本质是什么?

2. ResNet 的核心创新是什么?

3. 池化的作用是什么?

8

📝 总结

🔍

卷积操作

局部感受野 + 权重共享,用小卷积核在图像上滑动提取特征。参数量远少于全连接层。

📐

池化操作

降低空间分辨率,提取主要特征,提供平移不变性。最常用的是 2×2 最大池化。

🏗️

架构演进

LeNet → VGG → ResNet → DenseNet。核心趋势:更深、更小卷积核、更好的梯度流。

🎯

核心思想

CNN 的核心是"局部特征提取 + 层次化组合" — 从边缘到纹理,从纹理到部件,从部件到物体。