Skip to content

CNN 卷积神经网络

CNN 核心概念

卷积操作

python
# PyTorch 卷积
nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3, stride=1, padding=1)

# 数学含义:卷积核在图像上滑动,做点积运算
# 输入: (B, C, H, W) → 输出: (B, F, H', W')

卷积核(Filter/Kernel):

  • 3×3、5×5、7×7 大小
  • 深度等于输入通道数
  • 学习参数:权重矩阵 + 偏置

步长(Stride):

  • 卷积核每次移动的像素数
  • stride=1: 输出尺寸不变
  • stride=2: 输出尺寸减半

填充(Padding):

  • padding=1: 周围补一圈0
  • 保持空间尺寸
  • 边缘信息不被丢弃

池化层

python
# 最大池化
nn.MaxPool2d(kernel_size=2, stride=2)

# 平均池化
nn.AvgPool2d(kernel_size=2, stride=2)

# 全局池化(压缩到单个值)
nn.AdaptiveAvgPool2d((1, 1))  # 输出 (B, C, 1, 1)

特征图(Feature Map)

输入图像: 224×224×3 (RGB)

卷积层1: 224×224×64   (64个特征图)
池化层1: 112×112×64   (尺寸减半)
卷积层2: 112×112×128
池化层2: 56×56×128
卷积层3: 56×56×256
池化层3: 28×28×256
...
展平 → 全连接 → 输出

经典 CNN 架构

LeNet-5 (1998)

输入(32×32) 
→ Conv(6, 5×5) 
→ AvgPool(2×2) 
→ Conv(16, 5×5) 
→ AvgPool(2×2) 
→ Flatten(400) 
→ FC(120) 
→ FC(84) 
→ FC(10)

手写数字识别 MNIST,5万参数。

AlexNet (2012)

输入(224×224×3)
→ Conv(96, 11×11, stride=4) → MaxPool(3×3)
→ Conv(256, 5×5) → MaxPool(3×3)
→ Conv(384, 3×3)
→ Conv(384, 3×3)
→ Conv(256, 3×3) → MaxPool(3×3)
→ Flatten(9216) → FC(4096) → FC(4096) → FC(1000)

ImageNet 2012 冠军,深度学习复兴标志。创新点:

  • ReLU 激活
  • Dropout
  • GPU 并行训练
  • 数据增强

VGGNet (2014)

VGG-16:
输入(224×224×3)
→ Conv(64)×2 → MaxPool
→ Conv(128)×2 → MaxPool
→ Conv(256)×3 → MaxPool
→ Conv(512)×3 → MaxPool
→ Conv(512)×3 → MaxPool
→ Flatten → FC(4096) → FC(4096) → FC(1000)

VGG-19: 多了几个 Conv(512)

特点:小卷积核(3×3)堆叠,深度更深。1.38亿参数。

GoogLeNet / Inception (2014)

Inception 模块:

  ┌─────────────┐
  │ 1×1 Conv   │  1×1 Conv 降维
  │ 3×3 Conv   │  3×3 Conv
  │ 5×5 Conv   │  5×5 Conv
  │ 3×3 MaxPool│  1×1 Conv
  └─────────────┘

   通道拼接

并行多尺度卷积,Inception v1 用了 22 层,参数只有 500 万(比 AlexNet 少 12 倍)。

ResNet (2015)

残差块:
输入 ──→ Conv ──→ Conv ──→ + ──→ 输出
 └──→ 1×1 Conv ──→ 1×1 Conv ──→┘
                (快捷连接)
F(x) = H(x) - x  # 学习残差,而非直接学习映射
H(x) = F(x) + x  # 实际输出 = 预测 + 输入

ImageNet 2015 冠军,解决了深层网络梯度消失问题。152 层,VGG-16 的 8 倍深,复杂度更低。

python
# PyTorch ResNet
from torchvision.models import resnet50, resnet101, resnet152
model = resnet50(pretrained=True)

DenseNet (2017)

密集块:
每一层都与其他所有层连接
x₀ → Conv → Concat(x₀, x₁) → Conv → Concat(x₀,x₁,x₂) → ...

特征复用,减少参数。

EfficientNet (2019)

复合缩放:

深度 d: depth_coefficient
宽度 w: width_coefficient  
分辨率 r: resolution_coefficient

φ: 缩放因子
depth = d^φ
width = w^φ
resolution = r^φ

更高效的精度-参数权衡。

CNN 核心问题

梯度消失/爆炸

python
# 解决方案
# 1. 残差连接 (ResNet)
# 2. 批量归一化 (BatchNorm)
nn.BatchNorm2d(num_features)

# 3. 权重初始化
torch.nn.init.kaiming_normal_(layer.weight)

过拟合

python
# 1. Dropout
nn.Dropout(p=0.5)  # 训练时随机断开50%连接

# 2. 数据增强
transforms.RandomHorizontalFlip()
transforms.RandomCrop(224, padding=32)

# 3. L2 正则
optimizer = torch.optim.Adam(lr=1e-3, weight_decay=1e-4)

计算量优化

python
# Depthwise Separable Convolution (MobileNet)
# 普通卷积: 3×3 × in_channels × out_channels
# Depthwise: 3×3 × in_channels + 1×1 × in_channels × out_channels
# 参数量减少约 8-9 倍

# 1×1 卷积降维 (Inception)
nn.Conv2d(192, 64, 1)  # 1×1 压缩通道

常用 CNN 框架

python
import torchvision.models as models

# 图像分类预训练模型
resnet18 = models.resnet18(pretrained=True)
resnet50 = models.resnet50(pretrained=True)
vgg16 = models.vgg16(pretrained=True)
efficientnet_b0 = models.efficientnet_b0(pretrained=True)
mobilenet_v2 = models.mobilenet_v2(pretrained=True)

# 修改最后一层
num_features = model.fc.in_features
model.fc = nn.Linear(num_features, 10)  # 10类输出

迁移学习

python
# 1. 直接用预训练特征(不训练)
for param in model.parameters():
    param.requires_grad = False
model.fc = nn.Linear(512, 10)

# 2. 微调(训练所有层)
for param in model.parameters():
    param.requires_grad = True

# 3. 微调(训练后面几层)
for param in model.layer4.parameters():
    param.requires_grad = False

# 训练
optimizer = torch.optim.Adam(fc_params, lr=1e-3)

代码示例:图像分类

python
import torch
import torch.nn as nn
import torchvision.transforms as transforms
from torchvision import models

class ImageClassifier(nn.Module):
    def __init__(self, num_classes=10):
        super().__init__()
        # 使用预训练 ResNet
        self.backbone = models.resnet18(pretrained=True)
        # 冻结前面层
        for param in self.backbone.parameters():
            param.requires_grad = False
        # 替换分类头
        self.backbone.fc = nn.Linear(512, num_classes)
    
    def forward(self, x):
        return self.backbone(x)

# 数据
transform = transforms.Compose([
    transforms.Resize(256),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406],
                        std=[0.229, 0.224, 0.225])
])

# 训练
model = ImageClassifier()
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)

for epoch in range(10):
    for images, labels in dataloader:
        outputs = model(images)
        loss = criterion(outputs, labels)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

CNN 面试要点

1. 卷积核大小/步长/填充对输出尺寸的影响
   output_size = (input_size + 2*padding - kernel_size) / stride + 1

2. 1×1 卷积的作用
   - 降维/升维
   - 通道间信息融合
   - 减少参数量

3. 残差连接为什么有效
   - 缓解梯度消失
   - 让网络更容易学习恒等映射
   - 允许深层网络训练

4. VGG vs ResNet
   - VGG: 小卷积核堆叠
   - ResNet: 残差学习

5. 常见正则化
   - Dropout
   - BatchNorm
   - 数据增强
   - L2 正则

基于 MIT 许可发布