引言
入门篇用 PyTorch 搭了第一个 MLP(/ml-neural-networks-basics/)。进阶要解决两个问题:MLP 处理不了的结构(图像的空间局部性、序列的时序性)与深网络的训练困境(梯度消失、过拟合)。本文先给 CNN 与 RNN 两大家族的结构,再集中解决「怎么把深网络训好」——正则化、优化器、学习率调度、迁移学习,最后用 CIFAR-10 完整跑通。
前置:/ml-neural-networks-basics/(张量/自动求导/训练循环)、/ml-model-evaluation/(过拟合与评估)。
目录
- 1. 深度学习全景:从 MLP 到三大网络族
- 2. CNN 卷积网络:空间特征提取
- 3. 池化与感受野
- 4. RNN/LSTM:序列建模
- 5. 正则化:Dropout、BatchNorm 与数据增强
- 6. 优化器演进:SGD、Momentum 与 Adam
- 7. 训练技巧:学习率调度、早停与梯度裁剪
- 8. 迁移学习与微调
- 9. PyTorch 实战:CIFAR-10 完整流程
- 10. 速查表
- 延伸阅读
1. 深度学习全景:从 MLP 到三大网络族
| 网络族 | 擅长 | 典型结构 | 应用 |
|---|---|---|---|
| MLP(全连接) | 表格数据 | 线性层堆叠 | 分类/回归 |
| CNN | 图像/网格 | 卷积+池化+全连接 | CV |
| RNN/Transformer | 序列 | 循环/注意力 | NLP、时序 |
| GAN | 生成 | 生成器+判别器 | 图像生成 |
为什么 MLP 处理图像不行:
图像 32×32×3 = 3072 维,全连接首层就要 3072×N 参数
且丢失空间结构(像素相邻关系)
→ CNN 用「局部连接 + 权值共享」解决
心智:选网络 = 匹配数据结构的先验——图像要局部性,序列要时序性,表格用 MLP 就够。
2. CNN 卷积网络:空间特征提取
卷积核(Kernel):小窗口在图上滑动,提取局部特征——权值共享大幅减少参数。
输入 32×32×3(RGB 三通道)
→ 卷积层 3×3×3×16(16 个核)→ 输出 30×30×16
→ 激活(ReLU)→ 池化 → ...
→ 展平 → 全连接 → Softmax(10)
PyTorch 定义一个小 CNN:
import torch.nn as nn
class SimpleCNN(nn.Module):
def __init__(self):
super().__init__()
self.features = nn.Sequential(
nn.Conv2d(3, 16, kernel_size=3, padding=1), # 3→16 通道
nn.ReLU(),
nn.MaxPool2d(2), # 尺寸减半
nn.Conv2d(16, 32, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2),
)
self.classifier = nn.Sequential(
nn.Flatten(),
nn.Linear(32 * 8 * 8, 64), # 32×8×8 展平
nn.ReLU(),
nn.Linear(64, 10),
)
def forward(self, x):
return self.classifier(self.features(x))
卷积层关键参数:kernel_size(核大小)、padding(补零保尺寸)、stride(步长)、channels(通道数)。
| 参数 | 作用 |
|---|---|
| 核大小 3×3 / 5×5 | 感受野大小 |
| padding | 保持特征图尺寸 |
| stride | 下采样 |
| 通道数 | 特征表达能力 |
3. 池化与感受野
池化(Pooling):对局部取最大/平均——降维 + 平移不变性:
# 最大池化:取 2×2 区域最大值
nn.MaxPool2d(kernel_size=2, stride=2)
# 平均池化:取均值
nn.AvgPool2d(kernel_size=2)
感受野(Receptive Field):某个输出特征「看到」输入的区域大小:
3 层 3×3 卷积 → 感受野 7×7(3 个 3×3 堆叠 ≈ 1 个 7×7,但参数更少、非线性更多)
| 设计决策 | 说明 |
|---|---|
| 小核堆叠 vs 大核 | 3×3 堆叠参数少、表达力相当 |
| 池化时机 | 尺寸减半后通道翻倍(经典 VGG 风格) |
| 全局平均池化 | 替代全连接做分类头(省参数防过拟合) |
记忆:感受野 = 模型能「看多远」;堆小核既能看远又省参数,是 CNN 设计的主流。
4. RNN/LSTM:序列建模
RNN:带隐藏状态 h_t 的循环结构,处理「有先后」的数据:
h_t = tanh(W_h h_{t-1} + W_x x_t + b)
输出 y_t = softmax(W_y h_t)
问题:长序列梯度消失——早期信息传不到后面。
LSTM 解决:门控机制(遗忘门/输入门/输出门 + 细胞状态 C):
import torch.nn as nn
# LSTM 层(hidden=64, num_layers=2)
lstm = nn.LSTM(input_size=10, hidden_size=64, num_layers=2, batch_first=True)
# 输入: (batch, seq_len, input_size)
x = torch.randn(32, 50, 10)
out, (h_n, c_n) = lstm(x) # out: (batch, seq_len, hidden)
| 组件 | 作用 |
|---|---|
| 遗忘门 | 决定丢弃哪些旧记忆 |
| 输入门 | 决定写入哪些新信息 |
| 输出门 | 决定输出哪些状态 |
| 细胞状态 | 长程记忆通道 |
注意:现代 NLP 已被 Transformer 取代(见 /ml-nlp-basics/);LSTM 仍用于中等长度时序与轻量序列任务。
5. 正则化:Dropout、BatchNorm 与数据增强
深网络必配的三件套——专治过拟合与训练不稳:
nn.Sequential(
nn.Conv2d(3, 32, 3, padding=1),
nn.BatchNorm2d(32), # 归一化每层输入 → 训练更稳、可加学习率
nn.ReLU(),
nn.Dropout(0.3), # 训练时随机丢弃 30% 神经元 → 抗过拟合
...
)
| 正则化 | 机制 | 适用 |
|---|---|---|
| Dropout | 随机屏蔽神经元,测试时全开 | 全连接、大模型 |
| BatchNorm | 层内归一化,加速收敛 | 卷积、MLP |
| 数据增强 | 旋转/翻转/裁剪制造更多样本 | 图像/序列 |
| L1/L2 权重衰减 | 惩罚大权重 | 通用 |
| 早停 | 验证集不再提升就停 | 通用 |
数据增强(图像):
from torchvision import transforms
train_tf = transforms.Compose([
transforms.RandomHorizontalFlip(),
transforms.RandomRotation(10),
transforms.ColorJitter(brightness=0.2),
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,)),
])
记忆:Dropout 防过拟合、BN 稳训练、增强造数据——深网络默认配置。
6. 优化器演进:SGD、Momentum 与 Adam
优化器就是「怎么沿着梯度走」:
| 优化器 | 思想 | 特点 |
|---|---|---|
| SGD | 沿负梯度走固定步长 | 稳,可能陷局部极小 |
| SGD+Momentum | 带惯性,冲过平坦区 | 收敛更快更稳 |
| Adagrad | 按历史梯度调步长 | 学习率自动衰减 |
| Adam | 自适应学习率 + 动量 | 默认首选,快而稳 |
| AdamW | Adam + 解耦权重衰减 | 大模型主流 |
import torch.optim as optim
# 首选
optimizer = optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4)
# 需要精细控制时
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9,
weight_decay=5e-4)
选型建议:
- 起步/默认 → Adam/AdamW
- 结果欠佳想调 → 换回 SGD+Momentum(有时更好泛化)
- 大模型/预训练 → AdamW + 余弦退火
记忆:Adam 快而稳是默认,SGD 慢但可能更好泛化——训练好坏的锅多在学习率,不在优化器本身。
7. 训练技巧:学习率调度、早停与梯度裁剪
学习率是最重要的超参——三大调度:
# 1. 阶梯下降:每 N 轮减半
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1)
# 2. 余弦退火:平滑下降到最低
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=50)
# 3. 预热 + 退火(大模型常用)
scheduler = optim.lr_scheduler.OneCycleLR(optimizer, max_lr=1e-3, epochs=50)
早停(Early Stopping)——验证集不再改善就停,防过拟合:
best_val, patience, counter = float('inf'), 5, 0
for epoch in range(epochs):
train(model, loader)
val = evaluate(model, val_loader)
if val < best_val:
best_val = val
torch.save(model.state_dict(), 'best.pth')
counter = 0
else:
counter += 1
if counter >= patience:
print('早停'); break
梯度裁剪(防梯度爆炸,RNN 必配):
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
训练四板斧:监控学习曲线 → 早停 → 调度学习率 → 必要时裁剪梯度。
8. 迁移学习与微调
迁移学习:用预训练权重起步,大幅降低数据需求与训练时间。
策略:
| 数据量 | 策略 |
|---|---|
| 很少(<1k) | 冻结主干,只训分类头 |
| 中等(1k-10k) | 微调深层,冻结浅层 |
| 充足(>10k) | 全量微调 + 小学习率 |
PyTorch 微调:
import torchvision.models as models
# 用预训练 ResNet18
model = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1)
# 冻结所有层
for param in model.parameters():
param.requires_grad = False
# 替换分类头(ImageNet 1000 类 → 目标 N 类)
model.fc = nn.Linear(model.fc.in_features, num_classes)
# 分类头用较大学习率,主干用小学习率
optimizer = optim.AdamW([
{'params': model.fc.parameters(), 'lr': 1e-3},
{'params': (p for p in model.parameters() if p.requires_grad), 'lr': 1e-4},
])
记忆:迁移 = 「站在巨人肩膀上」——预训练学的是通用特征,微调学的是你的任务。
9. PyTorch 实战:CIFAR-10 完整流程
端到端训练(含上面所有技巧):
import torch, torch.nn as nn, torchvision
from torchvision import datasets, transforms
# 1. 数据(增强 + 归一化)
train_tf = transforms.Compose([
transforms.RandomHorizontalFlip(),
transforms.RandomCrop(32, padding=4),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
])
trainset = datasets.CIFAR10(root='./data', train=True, download=True, transform=train_tf)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True)
# 2. 模型(复用上面 SimpleCNN)
model = SimpleCNN()
# 3. 优化器 + 调度 + 损失
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4)
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=30)
criterion = nn.CrossEntropyLoss()
# 4. 训练循环
for epoch in range(30):
model.train()
for images, labels in trainloader:
optimizer.zero_grad()
loss = criterion(model(images), labels)
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
optimizer.step()
scheduler.step()
# 每轮评估验证集、早停判断...
print(f'epoch {epoch}: loss={loss.item():.4f}')
训练闭环清单:数据增强 → 模型 → AdamW + 权重衰减 → 余弦调度 → 早停 → 存最佳模型。
10. 速查表
| 需求 | 做法 |
|---|---|
| 图像分类 | CNN(卷积+池化) |
| 序列/时序 | LSTM 或 Transformer |
| 防过拟合 | Dropout + 权重衰减 + 早停 |
| 训练不稳 | BatchNorm + 预热 |
| 数据不足 | 数据增强 |
| 优化器 | 默认 AdamW |
| 学习率 | 余弦退火 / 阶梯 |
| 数据少但有预训练 | 迁移学习微调 |
| 梯度爆炸 | 梯度裁剪 |
一句话记忆:图像用 CNN、序列用 LSTM、默认 AdamW;Dropout 防过拟合、BN 稳训练、增强造数据、调度管学习率;数据少就迁移微调——深网络的工程四件套齐活。
延伸阅读
- /ml-neural-networks-basics/ — 张量、自动求导与第一个网络
- /ml-nlp-basics/ — Transformer 与 NLP 进阶
- /ml-cv-basics/ — 计算机视觉实战
- /ml-model-evaluation/ — 过拟合与调参评估
- [[ai-ml]] — 算法原理深度专题
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。