Aller au contenu principal

深度学习 4 — 卷积网络 (2/3)

:::tip Kaggle 笔记本 本章的完整可执行代码在 Kaggle 上:打开 →

法语和英语版本可在首页查看。 :::

在上一章中,我们在 CSV 存储的数据集上训练了 CNN(MNIST、CIFAR-10)。便于入门,但实际上图像几乎总是来自 PNG/JPG 文件。我们还发现稳定和正则化深层 CNN 的两种关键技术:Batch NormalizationDropout

为什么要学这一章?

您将学到:

  • 使用 ImageFolder 从磁盘加载图像;
  • 加快数据流水线num_workerspin_memory);
  • Batch Normalization:为什么以及在哪里放置;
  • Dropout 减少过拟合。

ImageFolder:按文件夹组织

PyTorch 期望非常简单的组织:一个类别一个文件夹

dataset/
├── train/
│ ├── 0/
│ │ ├── img_001.png
│ │ └── img_002.png
│ ├── 1/
│ └── ...
└── test/
├── 0/
└── ...

子文件夹名称作为标签。torchvision.datasets.ImageFolder 完成所有工作。

from torchvision import datasets, transforms
from torch.utils.data import DataLoader

transform = transforms.Compose([
transforms.Resize((28, 28)),
transforms.ToTensor() # PIL [0,255] → tensor [0,1] (C, H, W)
])

train_dataset = datasets.ImageFolder(root='dataset/train', transform=transform)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)

ToTensor() 的魔力

ToTensor() 一次完成三件事:

  1. 将 PIL Image 转换为 PyTorch 张量;
  2. 除以 255 → 值在 [0,1][0, 1] 中;
  3. 从 HWC 重新排列为 CHW

实际后果:使用 ImageFolder + ToTensor(),您再也不需要 reshape 或 transpose。批次直接以 (B,C,H,W)(B, C, H, W) 格式到达。

来源需要 reshape 吗?
CSV(第 3 章)
ImageFolder(这里)否,由 ToTensor() 已经是 BCHW

加快加载

当 GPU 上训练"似乎慢"时,原因很少是模型。通常是数据流水线慢。四个杠杆:

num_workers

默认 num_workers=0:所有事情都在主进程中发生。num_workers > 0 时,单独的进程并行准备批次。

DataLoader(train_dataset, batch_size=256, shuffle=True, num_workers=4)

通常值:Kaggle 上 2-4,在强力机器上 8。

pin_memory

使用 pin_memory=True,张量在固定(page-locked)内存中分配,加速到 GPU 的传输。

DataLoader(..., pin_memory=True)

for Xb, yb in train_loader:
Xb = Xb.to(device, non_blocking=True)

persistent_workers

避免在每个 epoch 重新创建 worker。当 epoch 短时有用。

DataLoader(..., num_workers=4, persistent_workers=True)

总结

DataLoader(
train_dataset,
batch_size=128,
shuffle=True,
num_workers=4,
persistent_workers=True,
pin_memory=True,
)

目标:GPU 永远不应等待批次。

Batch Normalization

问题: 在每个梯度步骤,权重发生变化,因此馈送到下一层的激活分布也发生变化。模型必须不断重新调整——训练缓慢且不稳定。

解决方案: 按批次归一化激活。对于每个通道,重新居中(均值 ≈ 0)和重新缩放(方差 ≈ 1),然后应用学习的仿射变换(每个通道两个参数 γ\gammaβ\beta)。

好处

  • 训练更稳定;
  • 更快收敛(通常允许 lr 加倍或加三倍);
  • 对初始化的敏感性降低;
  • 轻微的正则化效果。

在哪里放 BN

标准位置:在卷积之后,激活之前

Conv2d → BatchNorm2d → ReLU
self.conv = nn.Conv2d(32, 64, kernel_size=3, padding=1)
self.bn = nn.BatchNorm2d(64) # conv 输出 64 通道
self.relu = nn.ReLU()

PyTorch 自动处理双模式:

  • model.train():在当前批次上计算的统计;
  • model.eval():训练期间累积的平均统计。

:::warning 评估时总是调用 eval() 没有 model.eval(),模型会继续使用当前批次的统计——改变其预测。养成习惯。 :::

Dropout

问题: 深层网络可能通过过度依赖某些神经元组合而过拟合,创建脆弱的路径。

解决方案: 在训练期间,每一步随机禁用 pp 比例的神经元(密集层通常 p=0.5p = 0.5,conv 层 0.20.2-0.30.3)。网络学习不依赖任何特定的神经元。

在评估时,dropout 由 model.eval() 自动禁用

在哪里放 Dropout

主要在网络末端的密集层Linear)中。

Linear → ReLU → Dropout
self.fc = nn.Linear(512, 256)
self.dropout = nn.Dropout(p=0.5)

更少在 conv 层之后(BN 在该级别已经发挥正则化作用)。

BN,Dropout,还是两者?

技术何时使用
仅 BatchNorm现代 CNN 中的标准。许多情况下足够。
仅 Dropout当 BatchNorm 出现问题时(小批次、RNN、某些 transformer)。
两者兼容。BN 在 conv 部分,Dropout 在密集部分。

这两种技术与 ReLU 同样重要,是深度学习工具包的必备。

典型现代 CNN 架构

class CNN(nn.Module):
def __init__(self):
super().__init__()
self.features = nn.Sequential(
nn.Conv2d(3, 32, 3, padding=1), nn.BatchNorm2d(32), nn.ReLU(), nn.MaxPool2d(2),
nn.Conv2d(32, 64, 3, padding=1), nn.BatchNorm2d(64), nn.ReLU(), nn.MaxPool2d(2),
nn.Conv2d(64, 128, 3, padding=1), nn.BatchNorm2d(128), nn.ReLU(), nn.MaxPool2d(2),
)
self.classifier = nn.Sequential(
nn.Flatten(),
nn.Linear(128 * 4 * 4, 256), nn.ReLU(), nn.Dropout(0.5),
nn.Linear(256, 10),
)

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

这是大多数现代"自制"架构中的骨架。


Kaggle 上的完整笔记本(可分叉)→