首页 / PyTorch 入门教程 / 正则化与防过拟合

PyTorch 入门教程

正则化与防过拟合

本教程共 60 篇 · 第 27 篇 · 更新于 2026-08-17 · 约 4 分钟阅读

PyTorch过拟合正则化Dropout权重衰减数据增强深度学习入门

本节目标:看懂过拟合的典型症状,学会三件防过拟合的武器:Dropout、权重衰减、数据增强,并知道什么时候该用哪个。

过拟合:背答案的学生

训练集准确率 99%,测试集只有 70%。这个现象你迟早会撞上,它叫过拟合(Overfitting)。

打个比方:学生把练习册答案全背下来了,平时作业满分,一换新题就抓瞎。模型也一样——参数太多、数据太少时,它不去学规律,反而把训练样本的噪声都记了下来。

过拟合通常是三个因素凑出来的:模型容量太大,扛得住复杂细节;数据量太少,撑不起模型的野心;训练时间太长,把噪声也学了个遍。

举个极端例子:把 100 张图硬塞给一个能记住 1000 万参数的模型,它完全可以把每张图「背」下来。但换个只有 1 万参数的小模型,想背都背不动,只能老老实实学规律。判断标准很简单:训练准确率高、验证准确率明显低,中间的大缺口就是过拟合。反过来两边都低,那是欠拟合,该加大模型而不是加正则。

所有防过拟合的手段,本质就一句话:给模型的学习能力上点约束。下面三件武器各有各的招。

武器一:Dropout

Dropout 的做法很粗暴:训练时每轮随机把一部分神经元的输出置零,强迫网络不能依赖任何单个神经元。

类比一下:团队每次开会随机抽掉几个人,剩下的人就得把活都学会,谁都不敢划水。训练出来的网络冗余度高,换新数据也不慌。从另一个角度看,Dropout 每次都在训练一个「缺胳膊少腿」的子网络,推理时所有子网络一起投票,效果接近集成学习。

import torch.nn as nn

model = nn.Sequential(
    nn.Linear(256, 128),
    nn.ReLU(),
    nn.Dropout(p=0.5),   # 训练时随机丢 50% 的神经元输出
    nn.Linear(128, 10),
)

p 是置零概率。关键细节在训练和推理的差异上:

dropout = nn.Dropout(p=0.5)
x = torch.ones(2, 10)

dropout.train()
print(dropout(x))   # 大约一半是 0,另一半是 2.0(放大 1/(1-p) 倍)

dropout.eval()
print(dropout(x))   # 全 1.0,Dropout 自动关闭

训练时活下来的神经元输出放大 1/(1-p) 倍,是为了保持整体量级不变;推理时 Dropout 关闭、全部神经元参与,所以验证前一定记得 model.eval()。这也是同一份代码 eval 前后输出不同的原因。

Dropout 放哪层也有讲究:通常放在激活函数之后、下一层之前,输出层前一般不加。卷积层则要慎用——相邻像素高度相关,逐点丢弃效果差,要用也是 nn.Dropout2d 按整个通道丢。

Tip

p 的参考值:大的全连接层用 0.5,中小网络 0.2~0.3,Transformer 通常 0.1,卷积层一般不用。感觉过拟合严重就调大,欠拟合就调小。

武器二:权重衰减

权重衰减(Weight Decay)的思路:给损失函数加一项「权重越大罚得越狠」的约束,也就是 L2 正则化。模型为了少交罚款,会主动把权重往小了压。

权重小有什么好处?输出对输入的微小变化不那么敏感,拟合的曲线更平滑,自然不容易过拟合。它和 Dropout 一唱一和:一个压参数的个头,一个断神经元的连接,从两个方向给模型「减负」。

PyTorch 里用起来只需一行:

optimizer = torch.optim.SGD(model.parameters(), lr=0.01, weight_decay=1e-4)

常用取值 1e-4 到 1e-2,从小的开始试。weight_decay 越大压得越狠,但过大了模型会变「呆」,连训练集都学不动,所以要一点点往上加。

Warning

这里有个著名的坑:标准 Adam 把 weight_decay 直接加进梯度(等价于 L2 正则),但在 Adam 的自适应学习率缩放下,L2 正则的效果会被弱化。torch.optim.AdamW 把权重衰减从梯度里解耦出来,直接在参数更新时衰减,这才是论文里推荐的做法。现在 AdamW 是业界默认选择。

武器三:数据增强

前两件武器都在模型上做文章,第三件换个角度:数据太少,那就「伪造」数据。

训练时对每张图随机裁剪、翻转、调色,模型每次看到的样本都略有不同。数据量名义上没变,多样性却翻了倍,模型被迫去学不变的本质特征,而不是死记像素。

from torchvision import transforms

train_tf = transforms.Compose([
    transforms.RandomResizedCrop(224),       # 随机裁剪缩放
    transforms.RandomHorizontalFlip(),       # 随机水平翻转
    transforms.ColorJitter(brightness=0.2),  # 随机调亮度
    transforms.ToTensor(),
])

注意两点:增强只加在训练集上,验证集保持原样;增强手段要符合任务常识,比如识别数字 6 和 9 的任务就别随机旋转。第 12 章讲过 transforms 的细节,这里不展开。数据增强常和 Dropout、权重衰减一起用,效果叠加。

其他防过拟合手段

工具箱里还有几样:

  1. 早停(Early Stopping):每个 epoch 看验证集指标,连续几个 epoch 不涨就停。它还有个附带好处:省时间。模型从「刚好」到「过拟合」之间通常还有一段缓慢下降期,早停把这段也省了。实现也不难:
if val_acc > best_acc:
    best_acc = val_acc
    torch.save(model.state_dict(), "best.pth")   # 顺手存最好的
elif no_improve_epochs >= 5:
    break                                        # 5 轮没进步,收工
  1. BatchNorm:本身带一点正则化效果。用了它之后,对 Dropout 的依赖会减轻(下一章细讲)。
  2. 标签平滑(Label Smoothing):别让模型对标签 100% 自信,nn.CrossEntropyLoss(label_smoothing=0.1) 一行搞定。它对错误分类留有余地,损失曲线也更平滑。
  3. 剪枝(Pruning):训练完把不重要的权重删掉。本是压缩模型的手段,顺带改善泛化,torch.nn.utils.prune 里有现成工具,当进阶方向了解即可。

实战思路

我的习惯是:先不加正则,把模型跑通;看到过拟合迹象,再逐个上手段。

  1. 数据增强和早停成本最低,先上
  2. 还压不住,加 Dropout 和权重衰减
  3. 都不行,回头检查数据质量和模型复杂度

另外提一句,正则化和数据质量是两码事。标签标错了、类别不均衡,也会让验证集难看,别全甩锅给过拟合。

记住一句话:正则化是调料,不是主菜。模型结构和数据本身不对,加再多调料也救不回来。

下一章聊聊让训练变稳的另一类层:归一化层。