概述:为什么训练准确率高不等于模型好
训练模型时,经常会看到这样的现象:
text
train_acc 很高
val_acc 明显低
train_loss 持续下降
val_loss 先下降后上升
这通常说明模型出现了过拟合。模型把训练集里的模式甚至噪声记住了,但面对验证集或测试集时泛化不好。
正则化的目标不是让训练 loss 最低,而是让模型在未见数据上表现更稳。常见手段包括:
Dropout:训练时随机丢弃部分神经元输出。weight_decay:约束参数不要过大。- 数据增强:让模型见到更多输入变化。
- 早停:验证集长期不提升时停止训练。
- 减小模型容量:降低过拟合能力。
读完本文,你应该能判断模型是否过拟合,并能在 Paddle 中使用 Dropout、weight_decay 和早停策略控制过拟合。
过拟合、欠拟合与正常学习
先看三种训练状态:
| 状态 | 训练集表现 | 验证集表现 | 常见原因 |
|---|---|---|---|
| 欠拟合 | 差 | 差 | 模型太小、训练不足、学习率不合适 |
| 正常学习 | 好 | 好 | 容量和正则化较合适 |
| 过拟合 | 很好 | 较差 | 模型太大、数据太少、正则不足 |
可以用曲线理解:
text
正常学习:
train_loss 下降,val_loss 也下降
过拟合:
train_loss 继续下降,val_loss 开始上升
欠拟合:
train_loss 和 val_loss 都高
判断过拟合时,不要只看一轮结果,要看多个 epoch 的趋势。
Dropout:训练时随机关闭部分神经元
Paddle 中使用:
python
dropout = paddle.nn.Dropout(p=0.5)
官方文档说明,Dropout 是一种正则化手段,会在训练过程中按照概率 p 将部分神经元输出置为 0,以减少神经元之间的共同适应。
示例:
python
import paddle
import paddle.nn as nn
paddle.seed(2026)
x = paddle.ones([2, 8], dtype="float32")
dropout = nn.Dropout(p=0.5)
dropout.train()
y_train = dropout(x)
dropout.eval()
y_eval = dropout(x)
print("train output:")
print(y_train)
print("eval output:")
print(y_eval)
训练模式下,部分位置会被随机置零;评估模式下,Dropout 不再随机丢弃。
关键点 :Dropout 是否生效取决于模型模式,所以训练和评估时必须正确调用 model.train() 与 model.eval()。
Dropout 放在哪里
MLP 中常见放法:
python
class MLP(nn.Layer):
def __init__(self):
super().__init__()
self.net = nn.Sequential(
nn.Linear(2, 64),
nn.ReLU(),
nn.Dropout(p=0.3),
nn.Linear(64, 32),
nn.ReLU(),
nn.Dropout(p=0.3),
nn.Linear(32, 2),
)
def forward(self, x):
return self.net(x)
常见原则:
- 放在隐藏层激活之后更常见。
- 不要放在最终 logits 后面。
p不是越大越好。- 小模型或数据充分时,Dropout 不一定带来提升。
常见尝试:
text
p = 0.1
p = 0.3
p = 0.5
如果 p 太大,模型可能欠拟合。
weight_decay:通过优化器约束参数
权重衰减常用于限制参数过大。在 Paddle 优化器中可以设置:
python
optimizer = paddle.optimizer.Adam(
learning_rate=0.001,
parameters=model.parameters(),
weight_decay=1e-4,
)
直觉上,训练目标变成:
text
不仅要分类正确
还希望参数不要过大
过大的参数可能让模型对训练数据过度敏感。weight_decay 会给这种倾向加一点惩罚。
常见取值:
text
1e-5
1e-4
1e-3
如果 weight_decay 太小,效果可能不明显;太大则可能让模型学不动。
Dropout 与 weight_decay 的区别
| 方法 | 作用位置 | 主要效果 |
|---|---|---|
| Dropout | 网络前向中的激活值 | 减少神经元共同适应 |
| weight_decay | 优化器参数更新 | 惩罚过大权重 |
| 数据增强 | 输入数据 | 增加训练样本变化 |
| 早停 | 训练过程 | 防止继续过拟合 |
它们不是互斥关系,可以组合使用。
推荐顺序:
text
先跑无正则 baseline
观察是否过拟合
加入 weight_decay
必要时加入 Dropout
再考虑数据增强和早停
不要一开始把所有正则化都加满,否则很难知道是哪一个起作用。
早停:验证集不再提升就停止
早停不是 Paddle 特有 API,而是一种训练策略。基本逻辑:
text
记录 best_val_acc
如果验证集长期没有提升
停止训练
示例:
python
best_val_acc = 0.0
patience = 5
bad_epochs = 0
for epoch in range(100):
train_loss, train_acc = train_one_epoch(model, train_loader, optimizer)
val_loss, val_acc = evaluate(model, val_loader)
if val_acc > best_val_acc:
best_val_acc = val_acc
bad_epochs = 0
paddle.save(model.state_dict(), "best_model.pdparams")
else:
bad_epochs += 1
print("epoch:", epoch, "val_acc:", val_acc, "bad_epochs:", bad_epochs)
if bad_epochs >= patience:
print("early stop at epoch:", epoch)
break
早停适合:
- 验证集指标已经停滞。
- 训练时间有限。
- 模型后期明显过拟合。
完整示例:带 Dropout 和 weight_decay 的 MLP
python
import paddle
import paddle.nn as nn
import paddle.nn.functional as F
class CircleDataset(paddle.io.Dataset):
def __init__(self, num_samples, seed):
super().__init__()
paddle.seed(seed)
self.x = paddle.rand([num_samples, 2], dtype="float32") * 4.0 - 2.0
radius_square = paddle.sum(self.x ** 2, axis=1)
self.y = (radius_square > 1.0).astype("int64")
def __len__(self):
return self.y.shape[0]
def __getitem__(self, index):
return self.x[index], self.y[index]
class RegularizedMLP(nn.Layer):
def __init__(self):
super().__init__()
self.net = nn.Sequential(
nn.Linear(2, 64),
nn.ReLU(),
nn.Dropout(p=0.3),
nn.Linear(64, 32),
nn.ReLU(),
nn.Dropout(p=0.3),
nn.Linear(32, 2),
)
def forward(self, x):
return self.net(x)
def accuracy(logits, labels):
pred = paddle.argmax(logits, axis=1)
return paddle.mean((pred == labels).astype("float32"))
def train_one_epoch(model, loader, optimizer):
model.train()
total_loss = 0.0
total_acc = 0.0
count = 0
for batch_x, batch_y in loader:
logits = model(batch_x)
loss = F.cross_entropy(logits, batch_y)
acc = accuracy(logits, batch_y)
loss.backward()
optimizer.step()
optimizer.clear_grad()
total_loss += float(loss.numpy())
total_acc += float(acc.numpy())
count += 1
return total_loss / count, total_acc / count
def evaluate(model, loader):
model.eval()
total_acc = 0.0
count = 0
with paddle.no_grad():
for batch_x, batch_y in loader:
logits = model(batch_x)
total_acc += float(accuracy(logits, batch_y).numpy())
count += 1
return total_acc / count
def main():
paddle.seed(2026)
train_loader = paddle.io.DataLoader(CircleDataset(512, 2026), batch_size=32, shuffle=True)
val_loader = paddle.io.DataLoader(CircleDataset(512, 2027), batch_size=64, shuffle=False)
model = RegularizedMLP()
optimizer = paddle.optimizer.Adam(
learning_rate=0.001,
parameters=model.parameters(),
weight_decay=1e-4,
)
best_val_acc = 0.0
patience = 8
bad_epochs = 0
for epoch in range(100):
train_loss, train_acc = train_one_epoch(model, train_loader, optimizer)
val_acc = evaluate(model, val_loader)
if val_acc > best_val_acc:
best_val_acc = val_acc
bad_epochs = 0
paddle.save(model.state_dict(), "best_regularized_mlp.pdparams")
else:
bad_epochs += 1
print(
"epoch:", epoch,
"train_loss:", train_loss,
"train_acc:", train_acc,
"val_acc:", val_acc,
"best_val_acc:", best_val_acc,
)
if bad_epochs >= patience:
print("early stop at epoch:", epoch)
break
if __name__ == "__main__":
main()
常见错误:正则化排查清单
错误一:评估时忘记 model.eval
包含 Dropout 的模型必须在验证和推理前切换:
python
model.eval()
否则验证集结果会带随机性,指标不稳定。
错误二:Dropout p 设置过大
如果 p=0.8,大部分激活都会被丢弃,模型可能欠拟合。入门阶段先试 0.1 到 0.5。
错误三:只看训练准确率
正则化的目标是提升泛化。应同时看:
text
train_acc
val_acc
train_loss
val_loss
错误四:weight_decay 过大
如果训练集也学不好,可能正则过强。尝试降低到 1e-5 或暂时关闭。
错误五:把正则化当成万能修复
如果数据标签错了、输入 shape 错了、学习率过大,正则化解决不了根因。
调参建议:先建立 baseline
推荐流程:
- 无 Dropout、无 weight_decay,训练 baseline。
- 如果训练集和验证集都差,先解决欠拟合。
- 如果训练集好、验证集差,加入
weight_decay=1e-4。 - 再加入
Dropout(p=0.1 或 0.3)。 - 观察验证集指标,不要只看训练 loss。
- 保存验证集最好的模型。
建议练习:观察过拟合和正则化效果
- 减小训练集到 128 条,观察是否更容易过拟合。
- 把隐藏层改成 128,观察训练集和验证集差距。
- 加入
Dropout(p=0.3),观察验证集变化。 - 加入
weight_decay=1e-4,观察参数约束效果。 - 实现早停,并保存 best model。
- 故意评估时不调用
eval(),观察指标波动。
总结
这一篇讲了 Paddle 中常见正则化思路:
- 过拟合表现为训练集好、验证集差。
Dropout在训练时随机丢弃激活,评估时需model.eval()。weight_decay通过优化器约束权重大小。- 早停根据验证集指标停止训练。
- 正则化强度不是越大越好。
- 调参要先建立 baseline,再逐步加入策略。
如果只能记住一句话,那就是:
正则化不是为了让训练 loss 更低,而是为了让模型在新数据上更可靠。