引言
正则化(Regularization)在深度学习中的核心作用是什么?简单来说,它通过人为添加约束或惩罚项,防止模型将训练数据中的噪声也一并学习。缺乏约束时,模型容易过度拟合训练集,导致在新数据上的表现大打折扣。正则化的根本目标在于提升模型的泛化能力,让模型在真实场景中更具鲁棒性。
超参数
在探讨正则化之前,首先需要理解“超参数”这一概念。在深度学习中,“学习”的基本流程是:先设计模型结构,然后利用已标注的数据(例如标记图片A为猫、图片B为狗),借助数学工具(如损失函数、梯度等)不断调整模型内部参数,使模型的输出与标注尽可能一致。
模型输出与标注之间的差距通过损失函数的值来衡量——值越大表明偏差越大。算法的目标是通过梯度确定调整方向,再结合学习率(即每次更新的步长系数)来更新参数。没错,学习率本身就是一种超参数。
超参数需要在模型训练之前手动设定,无法通过模型自身的训练获得。除了学习率,本文即将讨论的L1和L2正则化强度也属于超参数,其他常见示例还包括Dropout比率、训练迭代次数(epochs)、批大小(batch size)等。
L1、L2正则化的数学定义
L1正则化是在原始损失函数基础上增加一个与模型权重的绝对值成正比的项(即L1范数),其数学表达式如下:
\[ L = L_{\text{original}} + \lambda \sum_i |w_i| \]
其中\( L_{\text{original}} \)为原始损失(例如均方误差MSE),\( \lambda \)是人工设置的正则化强度。该惩罚项促使模型产生更少的非零权重——换句话说,部分权重会被推向0(至于为什么是“推向”而非直接置零,暂时保留悬念)。
L2正则化,也称为权重衰减,它添加的是权重的平方和项:
\[ L = L_{\text{original}} + \lambda \sum_i w_i^2 \]
与L1的唯一区别在于将绝对值替换为了平方。L2倾向于惩罚较大的权重值,从而促使模型偏好更小的权值(那么它为什么不直接置零部分权重呢?)。
为什么L1、L2能提高泛化能力、减少过拟合?
L1和L2正则化的核心在于降低模型复杂度,减少对过多参数的依赖。过拟合为何发生?根本原因在于参数过多——参数越多,模型的表达能力越强,就越容易将预测值拟合到与实际值非常接近,甚至将噪声也一并学习。以线性回归为例:若数据点分布杂乱,用直线分割效果有限,而采用曲线(更高阶多项式)能更好地贴合训练数据,但模型同时也变得更加复杂,泛化能力反而下降。
L1正则化倾向于生成稀疏的权重矩阵,即许多权重直接被置零。这相当于自动进行特征选择——只保留对输出贡献最大的特征,剔除那些贡献较小的特征,从而简化模型,提升可解释性和泛化能力。
L2正则化则鼓励权重趋向较小的值,但不会完全置零——这是与L1的关键区别。它使权重分布更加平滑,减少模型对单一特征的依赖,从而使模型更加稳健。通过惩罚大权重,L2帮助模型避免对少数极端值过度敏感,从而在新数据上表现更佳。
为什么L1和L2对参数的约束表现不同?
从数学原理来看,两者在反向传播求梯度时的行为差异解释了这一区别。L1对权重 \( w_i \) 求偏导的结果是 \( \lambda \cdot \text{sign}(w_i) \),并且导数在 \( w_i=0 \) 处不连续;L2求偏导则得到 \( 2\lambda w_i \),函数光滑连续。
对于L1而言,权重更新时根据其符号减去或加上固定的\( \lambda \)。若\( w_i > 0 \),则更新为\( w_i = w_i - \lambda \);若\( w_i < 0 \),则更新为\( w_i = w_i + \lambda \)。这样一来,很小的权重很容易被更新到0,或越过0点改变符号。而损失函数在\( w_i=0 \)处刚好取得最小值,一旦跨过0,损失反而增大,因此模型倾向于将权重停在0。
但L2则不同,由于其梯度连续,每次更新后损失总是朝着减小的方向移动,没有突变点。这就解释了为何L2能保留很小的参数,却不像L1那样将它们彻底归零。
PyTorch中如何实现L1、L2?
直接展示核心代码,因为本文重点在于理解概念而非代码细节。
L1正则化:
import torch
import torch.nn as nn
import torch.optim as optim
lambda_l1 = 0.05 # 正则化强度
for epoch in range(100): # 训练100轮
optimizer.zero_grad()
outputs = model(x_train)
mse_loss = criterion(outputs, y_train)
l1_reg_loss = 0
for param in model.parameters():
l1_reg_loss += torch.norm(param, 1) # 计算L1范数
loss = mse_loss + lambda_l1 * l1_reg_loss
loss.backward()
optimizer.step()
if epoch % 10 == 0:
print(f'Epoch {epoch}, Loss: {loss.item()}')
L2正则化:
# 在SGD优化器中直接设置 weight_decay 参数,它就是 λ
optimizer = optim.SGD(model.parameters(), lr=0.01, weight_decay=0.01)
PyTorch为L2正则化提供了非常便捷的接口——只需在优化器的weight_decay参数中直接指定正则化强度即可,无需像L1那样手动遍历参数计算。两种方法均能有效抑制过拟合,具体选择取决于任务类型和特征维度:L1适合用于特征选择,而L2更适合控制模型复杂度。
