过拟合是深度学习最常见的问题。本课系统化地诊断和解决过拟合,让模型在真实世界中可靠工作。
| 指标 | 正常训练 | 过拟合 | 欠拟合 |
|---|---|---|---|
| 训练损失 | 持续下降 | 很低 | 较高 |
| 验证损失 | 持续下降 | 先降后升 | 较高 |
| 训练-验证差距 | 小 | 大 | 小但都高 |
绘制训练/验证损失随epoch的变化
关键拐点:验证损失开始上升 → 过拟合开始
import torch
import torch.nn as nn
import copy
# 过拟合诊断与处理
torch.manual_seed(42)
# 生成有噪声的数据
X = torch.randn(200, 20)
y = (X[:, 0] * 3 + X[:, 1] * 2 + X[:, 2] * 0.5 > 0).long()
# 划分数据
X_train, X_val = X[:150], X[150:]
y_train, y_val = y[:150], y[150:]
# 添加噪声标签(模拟真实场景)
noise_idx = torch.randperm(150)[:15]
y_train_noisy = y_train.clone()
y_train_noisy[noise_idx] = 1 - y_train_noisy[noise_idx]
# 大模型(容易过拟合)
class BigModel(nn.Module):
def __init__(self, dropout=0.0, use_bn=False):
super().__init__()
layers = [nn.Linear(20, 512)]
if use_bn: layers.append(nn.BatchNorm1d(512))
layers.append(nn.ReLU())
if dropout > 0: layers.append(nn.Dropout(dropout))
layers.append(nn.Linear(512, 256))
if use_bn: layers.append(nn.BatchNorm1d(256))
layers.append(nn.ReLU())
if dropout > 0: layers.append(nn.Dropout(dropout))
layers.append(nn.Linear(256, 2))
self.net = nn.Sequential(*layers)
def forward(self, x):
return self.net(x)
# 早停实现
class EarlyStopping:
def __init__(self, patience=10, min_delta=0):
self.patience = patience
self.min_delta = min_delta
self.counter = 0
self.best_loss = None
self.should_stop = False
def __call__(self, val_loss):
if self.best_loss is None:
self.best_loss = val_loss
elif val_loss > self.best_loss - self.min_delta:
self.counter += 1
if self.counter >= self.patience:
self.should_stop = True
else:
self.best_loss = val_loss
self.counter = 0
# 对比不同正则化策略
configs = [
("无正则化", {"dropout": 0.0, "use_bn": False, "wd": 0.0}),
("Dropout(0.3)", {"dropout": 0.3, "use_bn": False, "wd": 0.0}),
("BatchNorm", {"dropout": 0.0, "use_bn": True, "wd": 0.0}),
("权重衰减(0.01)", {"dropout": 0.0, "use_bn": False, "wd": 0.01}),
("Dropout+BN+WD", {"dropout": 0.3, "use_bn": True, "wd": 0.01}),
]
print("=== 正则化策略对比 ===")
for name, cfg in configs:
torch.manual_seed(42)
model = BigModel(dropout=cfg["dropout"], use_bn=cfg["use_bn"])
optimizer = torch.optim.Adam(model.parameters(), lr=0.001, weight_decay=cfg["wd"])
loss_fn = nn.CrossEntropyLoss()
early_stop = EarlyStopping(patience=20)
train_losses = []
val_losses = []
best_val_loss = float('inf')
for epoch in range(200):
model.train()
optimizer.zero_grad()
loss = loss_fn(model(X_train), y_train_noisy)
loss.backward()
optimizer.step()
model.eval()
with torch.no_grad():
val_loss = loss_fn(model(X_val), y_val)
train_losses.append(loss.item())
val_losses.append(val_loss.item())
early_stop(val_loss.item())
if val_loss.item() < best_val_loss:
best_val_loss = val_loss.item()
if early_stop.should_stop:
break
train_acc = (model(X_train).argmax(1) == y_train).float().mean()
val_acc = (model(X_val).argmax(1) == y_val).float().mean()
gap = abs(train_acc.item() - val_acc.item())
print(f"{name:>20}: train_acc={train_acc:.2%}, val_acc={val_acc:.2%}, "
f"差距={gap:.2%}, epochs={len(train_losses)}, best_val={best_val_loss:.4f}")
本课的核心知识点构成了训练技巧学习的重要一环。让我们从更高维度审视这些知识之间的关系:
⚠️ 初学者常犯的错误:
💡 最佳实践:
深度学习是一个整体——每一课的知识都不是孤立的:
💡 继续深入的学习路径:
画出完整的学习曲线,识别过拟合拐点。
综合运用数据增强+Dropout+权重衰减,达到最佳泛化。
实现K折交叉验证,获得更可靠的性能估计。
⚠️ 需要避免的典型错误:
💡 提升模型性能的系统方法:
理解本课内容需要将其置于更大的知识网络中。每个核心概念都不是孤立的,它们相互关联、相互支撑:
建立正确的直觉比记住公式更重要:
1. 维度直觉:高维空间中,数据分布与低维直觉截然不同(维度诅咒)
2. 信息瓶颈:神经网络逐层压缩信息,保留任务相关的、丢弃无关的
3. 流形假设:高维数据实际分布在低维流形上,学习即发现流形结构
4. 偏差-方差权衡:简单模型欠拟合(高偏差),复杂模型过拟合(高方差)
本课作为训练技巧阶段的核心内容,与前后课程紧密关联:
| 关联课程 | 关联点 | 协同效应 |
|---|---|---|
| 前序课程 | 提供了本课的基础知识 | 循序渐进的理解 |
| 后续课程 | 本课内容是后续的基础 | 逐步构建能力 |
| 平行课程 | 同一阶段的互补知识 | 多角度深入理解 |
| 实战项目 | 综合运用所有知识 | 理论与实践结合 |
💡 准备面试时,确保能回答以下问题:
掌握本课内容后,可以通过以下方式继续深入:
诊断过拟合,对症下药——你是模型的守护者!