3、正则化与防止过拟合:L1/L2正则化原理、Dropout机制详解、早停法(Early Stopping)实践、数据增强技巧

做模型训练,最怕什么?

不是loss降不下去,而是loss降得太漂亮了——训练集上准确率99%,一上验证集直接崩到60%。

这就是过拟合。说白了,模型把训练数据里的噪声和细节都背下来了,反而没学到真正的规律。我刚开始做AI那会儿,就吃过这个亏。一个图像分类模型,训练了三天三夜,loss曲线漂亮得像教科书一样,结果上线第一天就被用户骂惨了。

从那以后,我养成了一个习惯:每次训练模型,第一件事不是调学习率,而是先把正则化手段安排上。

这一章,我就把四种最常用的防过拟合手段掰开揉碎了讲给你听。

核心观点:过拟合的本质是模型复杂度超过了数据的信息量。正则化的目标,就是在模型能力和泛化能力之间找到那个甜点。

正则化与防止过拟合 · 知识体系 防止过拟合 L1/L2正则化 约束权重,降低复杂度 Dropout机制 随机丢弃,防止共适应 早停法 监控验证集,及时止损 数据增强 扩充数据,增加多样性 四种手段可以组合使用,效果往往1+1>2

3.1 L1/L2正则化:给权重戴上镣铐

正则化的核心思想很简单——在损失函数里加一项惩罚项,让模型不敢把权重学得太大。

你想想看,如果某个特征的权重特别大,那这个特征稍微变一点,预测结果就剧烈抖动。这种模型肯定不稳定。正则化就是告诉模型:「权重别太嚣张,收敛点。」

L2正则化(权重衰减)

L2正则化,也叫权重衰减。它的惩罚项是权重的平方和。

# L2正则化的损失函数
loss = 原始损失 + λ * Σ(w_i²)

# 梯度更新时,相当于每次多减一点权重
w = w - η * (∂loss/∂w + 2λ * w)

λ是正则化强度,我一般从1e-4开始试。调大λ,权重会更小,模型更平滑;调小了,正则化效果就不明显。

我的经验:L2正则化对大多数模型都有效,尤其是全连接层。我在做推荐系统时,把L2系数从1e-5调到1e-4,AUC直接涨了0.3个点。别小看这0.3,在工业界这就是一个版本迭代的收益。

L1正则化

L1正则化用的是权重的绝对值之和。它有个神奇的特性——会让部分权重变成0。

# L1正则化的损失函数
loss = 原始损失 + λ * Σ|w_i|

为什么会这样?因为L1的梯度是常数,不像L2那样越靠近0梯度越小。权重被推到0附近时,L1会一脚把它踹到0。说白了,L1在做特征选择。

选L1还是L2?

  • 如果你怀疑很多特征是噪声,用L1——它帮你把没用的特征权重直接干掉
  • 如果你觉得所有特征都有点用,只是别太极端,用L2
  • 两者可以结合,叫Elastic Net,我偶尔会用
对比项 L1正则化 L2正则化
惩罚形式 权重绝对值之和 权重平方和
效果 产生稀疏权重(部分为0) 权重整体变小,但不为0
适用场景 特征维度高,需要特征选择 大多数通用场景
梯度特性 常数梯度 梯度随权重减小而减小

3.2 Dropout机制详解:让神经元学会独立

Dropout这个想法,我第一次看到时觉得挺暴力的——训练时随机干掉一部分神经元,让它们不工作。

但仔细一想,这招其实很聪明。你想想看,如果每个神经元都知道队友随时可能「掉线」,它就不敢过度依赖别人,必须自己学会提取有效特征。

import torch
import torch.nn as nn

class MyModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc1 = nn.Linear(784, 256)
        self.dropout1 = nn.Dropout(p=0.5)  # 50%的神经元被随机丢弃
        self.fc2 = nn.Linear(256, 128)
        self.dropout2 = nn.Dropout(p=0.3)
        self.fc3 = nn.Linear(128, 10)

    def forward(self, x):
        x = torch.relu(self.fc1(x))
        x = self.dropout1(x)  # 训练时随机丢弃
        x = torch.relu(self.fc2(x))
        x = self.dropout2(x)
        return self.fc3(x)

注意:Dropout只在训练时生效。推理时,所有神经元都参与计算,但权重要乘以(1-p)做缩放。PyTorch和TensorFlow会自动处理这个,但如果你手写实现,别忘了这步。

Dropout的p值怎么设?我个人的习惯是:

  • 全连接层:p=0.5,效果比较稳
  • 卷积层:p=0.1~0.3,因为卷积层参数少,丢太多反而学不到东西
  • RNN/LSTM:p=0.2~0.3,而且一般只用在非循环连接上

我曾经在一个NLP项目里,把Dropout从0.3调到0.5,结果验证集准确率掉了2个点。后来才发现,对于小数据集,Dropout太强反而让模型欠拟合了。嗯,这里要注意——正则化不是越强越好。

3.3 早停法(Early Stopping)实践

早停法,说白了就是「见好就收」。

训练过程中,验证集的loss通常会先下降,然后触底,最后反弹。那个反弹的点,就是模型开始过拟合的时刻。早停法就是在这个时刻之前停下来。

class EarlyStopping:
    def __init__(self, patience=5, min_delta=0.001):
        self.patience = patience      # 容忍多少次没有改善
        self.min_delta = min_delta    # 最小改善阈值
        self.best_loss = float('inf')
        self.counter = 0
        self.early_stop = False

    def __call__(self, val_loss):
        if val_loss < self.best_loss - self.min_delta:
            self.best_loss = val_loss
            self.counter = 0
        else:
            self.counter += 1
            if self.counter >= self.patience:
                self.early_stop = True

我的建议:patience一般设5~10。设太小了,模型还没收敛就停了;设太大了,过拟合已经发生了才停。我通常先跑一轮完整的训练,看看验证集loss大概在第几个epoch触底,然后patience设成那个epoch数的1/3左右。

早停法还有一个好处——省时间。我做过一个对比实验,同样的模型,用早停法比固定epoch数训练,平均节省了40%的时间。在调参阶段,这个效率提升非常可观。

3.4 数据增强技巧

数据增强,是防止过拟合最「治本」的方法。因为过拟合的本质是数据不够,那我们就想办法「变」出更多数据。

对于图像任务,常用的增强手段包括:

  • 几何变换:随机旋转、平移、缩放、翻转
  • 颜色变换:调整亮度、对比度、饱和度、色相
  • 噪声注入:高斯噪声、椒盐噪声
  • 裁剪:Random Crop + Resize
from torchvision import transforms

# 一个典型的数据增强pipeline
train_transform = transforms.Compose([
    transforms.RandomResizedCrop(224),      # 随机裁剪并缩放
    transforms.RandomHorizontalFlip(p=0.5), # 随机水平翻转
    transforms.ColorJitter(                 # 颜色抖动
        brightness=0.2,
        contrast=0.2,
        saturation=0.2,
        hue=0.1
    ),
    transforms.RandomRotation(degrees=15),  # 随机旋转
    transforms.ToTensor(),
    transforms.Normalize(                   # 标准化
        mean=[0.485, 0.456, 0.406],
        std=[0.229, 0.224, 0.225]
    )
])

对于文本任务,数据增强稍微麻烦点:

  • 同义词替换:把句子中的词替换成同义词
  • 回译:中文→英文→中文,句子结构会变但语义不变
  • 随机插入/删除:小概率地插入或删除一些词

一个重要的原则:数据增强不能改变数据的标签。比如把「6」旋转180度就变成了「9」,这种增强会引入错误。我见过有人把医学影像左右翻转,结果心脏跑到右边去了——这显然不对。

数据增强的强度怎么控制?我一般这样试:先不加增强,看模型是否过拟合。如果过拟合了,逐步增加增强强度,直到验证集loss不再下降为止。记住,增强太强会让模型学不到有效信息,反而降低性能。

好了,这四种手段讲完了。它们可以单独用,也可以组合用。我个人最常用的组合是:L2正则化 + Dropout + 早停法,再配合适度的数据增强。这个组合在大多数场景下都能把过拟合控制得很好。

下一章,我们会聊优化器的选择——Adam、SGD、RMSprop这些到底该怎么选?到时候见。


专注资料整理