权重衰退
给损失函数加入惩罚项,限制权重不能过大,避免模型过度拟合训练数据中的噪声。
直觉:过拟合的模型,参数都在干嘛?
如果把过拟合模型与正常模型的参数 分布 画出来, 会观察到一个共同的现象:
过拟合的模型,参数往往非常大 。
为什么会这样?为了精确拟合每一个训练样本(包括其中的 噪声 ), 模型需要在某些方向上把权重推到很大的数值,这正是权重被推向极端的代价。
反过来,如果 限制权重不能太大 ,模型就没法再靠极端权重拟合 那些噪声,转而学到更 本质 的规律,泛化能力反而会更好。
问题就转化成了: 怎样在训练过程中限制权重过大? 最直接的做法是在原来的损失上加一项,专门惩罚过大的权重。
L2 正则化:在损失上加一个惩罚项
实现"惩罚大权重"的最直接做法:
其中 是所有权重的平方和, (lambda)是一个新的超参数,控制惩罚力度。 因为用的是 L2 范数(平方和),这个方法叫做 L2 正则化 。
这一项的作用:
- 权重越大,惩罚项越大,loss 也越大,优化器会朝着减小权重的方向更新参数
- 控制惩罚力度大小: = 0 等于不正则; 很大时权重会被压得很小
为什么叫"权重 衰退 "?
把刚才得到的总损失 拿来对 求梯度, 的梯度照旧;新增的那一项 (下文称为 L2 正则项 ) 贡献 。 惯例上把这个 直接吸收进 ( 本就是要靠实验定的超参,整体差一个常数因子 不影响方法本身 ), L2 项的梯度就写成简洁的 。 本节后面所有公式与代码都默认采用这个约定。
把总梯度代入梯度下降并合并同类项:
除了正常的梯度更新,每一步还 额外 把 乘上一个略小于 1 的因子 。 相当于让权重每步都 衰退 一点点,这就是 权重衰退 (Weight Decay)这个名字的来源。
的影响规律
也是一个需要权衡的超参数:
- :完全不正则,回到原始模型, 可能继续过拟合
- 太小(如 1e-6):惩罚几乎可忽略, 基本无效
- 适中(如 1e-3 ~ 1e-2): 权重被适度约束,过拟合明显缓解,泛化能力提升
- 太大(如 10):所有权重都被压到 0 附近,模型表达力丧失,掉到欠拟合
典型经验值: 1e-4 ~ 1e-2 ,需要在验证集上调。
实践 1:手写 L2 正则项
我们先把 L2 项手动加到 loss 里,这样能直观看到每一步多了哪几行代码、 以及惩罚项是怎么计算出来的。
提示:手写 L2 一般作为 教学用途 。 实际项目中我们直接用下一节介绍的内置参数,代码更简洁、运行更快。
实践 2:使用 optim 内置的 weight_decay
实际项目中没人会手写 L2 项,PyTorch 所有 optimizer 都内置了 weight_decay 参数:
把 L2 系数当作 weight_decay 传进去, optimizer 在每一步 step() 时会自动对每个参数做 , 效果与上一节"手写 L2"完全一致,只是少写了几行代码。
下面用一段对照实验来 验证 这种等价性:
Playground:扫描 看正则效果
下面这个 playground 在浏览器里实时训练一个小 MLP, 训练数据只有 个点(特意取很少,方便诱发过拟合), 而验证集有 个点。 切换不同的 , 观察决策边界与 train / val loss 曲线的变化:
依次切换 (每次切换后点重置再训到收敛),可以观察到一条由弱到强的渐变曲线:
- (不加正则):决策边界紧贴着每一个训练点弯曲,形状极不规则, val loss 显著高于 train loss,是典型的过拟合形态
- (很弱):惩罚项相比 BCE 小一两个量级, 边界相比 略有平滑,但仍能看到训练点附近的局部凸起,依然偏过拟合
- (适中):边界变得平滑, val loss 与 train loss 之间的间隙明显收窄,是最接近"良好拟合"的档位
- (偏强):权重被压得很小, 边界进一步简化、靠近一条直线,开始牺牲表达力
- (过强):权重几乎被压到 0, 模型给不出有意义的边界,掉到欠拟合