1. 为什么需要正则化:从过拟合说起
做机器学习模型,最让人头疼的问题之一就是过拟合。模型在训练集上表现很好,准确率甚至能达到99%,但一到验证集就露馅,准确率掉到80%甚至更低。很多人第一反应是数据不够,或者模型太复杂,其实这两个说法都不完全对。数据不够确实会放大过拟合问题,但即便数据量充足,只要模型容量过大,过拟合依然会出现。
我把过拟合形象地理解为“背题”和“做题”的区别。一个模型如果参数太多、自由度太高,它完全有能力把训练集里每个样本的细节都背下来,包括噪声和异常点。表面上看它记住了所有答案,实际上它并没有学到数据背后的规律。这种模型到了新数据面前就废了,因为它只会背题,不会举一反三。
正则化(Regularization)就是用来解决这个问题的。它不是限制模型学多少东西,而是限制模型“学得太死”。通过给模型的复杂程度施加约束,让模型必须在拟合数据和保持简单之间做一个折中。参数范数惩罚是其中最常见、最基础的一种正则化手段,我平时项目里用到的大部分正则化技巧,本质上都是在做这件事。
2. 参数范数惩罚的核心原理
2.1 损失函数里加一笔“复杂度税”
正则化的基本思路很简单:在原始损失函数后面加一个惩罚项,让模型在优化的时候不仅要考虑预测误差,还要考虑参数的大小。
如果用公式表达,原来的损失函数是:
J(θ) = L(θ)加了参数范数惩罚之后变成:
J'(θ) = L(θ) + α·Ω(θ)其中Ω(θ)就是参数范数惩罚项,α是惩罚系数,控制惩罚的强度。L1范数和L2范数是最常用的两种选择,它们虽然都叫范数惩罚,但效果差异很大,下面我分别来说。
2.2 L2正则化:最常用的权重衰减
L2正则化也叫权重衰减(Weight Decay),它的惩罚项是参数平方和的一半:
Ω(θ) = (1/2)·||w||² = (1/2)·Σ(w_i²)为什么要加1/2?主要是方便求导。对权重w求梯度的时候,(1/2)·w²的导数刚好是w,这样梯度更新公式会非常清爽。
加入L2正则化后,梯度下降的更新公式变成:
w_new = w_old - η·(∂L/∂w + α·w) = (1 - η·α)·w_old - η·∂L/∂w看到没有,每次更新的时候,权重会先乘以一个小于1的系数(1 - η·α),这就是“衰减”这个名字的由来。权重被持续压缩,模型自然就更简单了。
L2正则化有几个很关键的特性:
- 它不会把参数推成绝对的0,而是让参数整体变小
- 它对大参数的惩罚力度更大,因为梯度是线性的,参数越大,被拉回来的力度就越大
- 它对所有特征一视同仁,不会特别“偏爱”某些特征
2.3 L1正则化:把参数打成稀疏
L1正则化的惩罚项是参数的绝对值之和:
Ω(θ) = ||w||₁ = Σ|w_i|这个看起来只是把平方换成绝对值,但效果天差地别。L1正则化会让很多参数严格等于0,产生稀疏解。
为什么L1能做到L2做不到的事?这里用几何直觉解释一下。假设模型只有两个参数w1和w2,没加正则化之前,最优解可能在某个非零的位置。加了L1惩罚后,约束条件是一个菱形,菱形的角正好落在坐标轴上。如果最优解落在坐标轴上,那么另一个参数就是0了。L2的约束是圆形,它在坐标轴上的切点很少,参数倾向于同时收缩但不归零。
在工程上,L1的这种稀疏性很有用:
- 特征选择:参数为0意味着对应特征不起作用,直接可以删掉
- 模型压缩:稀疏模型占用的存储空间更小,推理速度更快
- 可解释性:只保留少数关键特征,模型更容易向非技术背景的人解释
2.4 L1和L2的特性对比
| 对比维度 | L1正则化 | L2正则化 |
|---|---|---|
| 惩罚形式 | Σ | w |
| 解的特性 | 稀疏,很多参数为0 | 参数整体缩小但不归零 |
| 梯度 | 恒定,±α | 线性,α·w |
| 优化稳定性 | 近零点时梯度不稳定 | 梯度平滑稳定 |
| 主要用途 | 特征选择、压缩模型 | 防止过拟合、提高泛化 |
| 对异常值 | 更鲁棒 | 受大参数影响更大 |
我在实际项目里,通常会先用L2把过拟合压住,如果业务方明确要求模型可解释、需要筛特征,再考虑换L1或者用弹性网(Elastic Net,L1+L2组合)。
3. 参数范数惩罚的数学直觉与为什么要选λ
3.1 惩罚系数λ怎么理解
前面公式里的α,在不少文章里也叫λ。这个系数是正则化强度的总开关。λ太大,模型被压得太狠,所有参数都趋近于0,模型相当于什么都没学,欠拟合;λ太小,惩罚形同虚设,过拟合问题照旧。
我习惯用一个简单的光谱图来帮人理解:
λ=0 λ适中 λ→∞ 过拟合 → 最佳泛化 → 欠拟合λ的选取没有万能公式,但有一些实操经验可以参考。我在训练的时候会先跑一轮不带正则化的基线,看它的过拟合程度有多严重,然后按数量级去尝试λ,比如0.1、0.01、0.001,观察验证集指标的变化趋势。再根据结果微调。
3.2 从贝叶斯视角理解参数范数惩罚
如果对数学细节有兴趣,这里有个值得深入了解的视角:参数范数惩罚可以从贝叶斯推断的角度来理解。
在没有先验信息的情况下,最大似然估计就是单纯最小化损失函数。如果给参数加上一个先验分布,比如假设参数服从高斯分布,那么最大后验估计推导出来就是L2正则化;假设参数服从拉普拉斯分布,推导出来就是L1正则化。
这意味着L2正则化隐含的假设是参数应该集中在0附近、呈高斯分布;L1正则化隐含的假设是参数应该稀疏、呈拉普拉斯分布。选择哪种正则化,某种程度上就是在选择你认为参数应该呈现什么样的分布。
这个角度对理解不同任务的选型很有帮助。比如我在做推荐系统的特征筛选时,面对几千维稀疏特征,理性判断是大部分特征本来就没用,所以选L1更合理;而在图像分类这类场景,每个像素都有一定信息量,压缩但不归零的L2更合适。
3.3 参数范数惩罚和BN、Dropout的关系
很多初学者会混淆正则化的不同手段。参数范数惩罚是直接对目标函数加限制,而Batch Normalization(批归一化)和Dropout是通过网络结构层面的手段来正则化。
BN通过让每层输入分布保持稳定来间接正则化,它同时解决了梯度消失问题。Dropout则是在训练时随机丢弃一部分神经元,强迫网络学习冗余表达。
这些方法可以和参数范数惩罚叠加使用,但要注意λ的选择。我在实际训练中发现,用了Dropout和BN之后,模型过拟合程度本身就会降低,这时候如果还保留较大的L2惩罚系数,容易过约束,导致欠拟合。建议叠加使用的时候适当调低λ。
4. 实操中的完整流程与细节
4.1 基线实验:先确定过拟合程度
我一般的做法是先把其他正则化手段全关掉,只留权重初始化,训练一个基线模型。记录训练集指标和验证集指标的差距。这个差距就是过拟合程度的量化标准。
举个例子,假设一个分类任务,基线模型训练集准确率97%,验证集准确率82%。这个15个百分点的差距就是需要正则化去压缩的。如果只差2~3个百分点,说明过拟合不严重,λ可以设小一点。如果差距超过20个百分点,说明模型复杂度远超出数据承载量,除了加正则化,可能还需要考虑简化网络结构或者增加数据增强。
4.2 在PyTorch中实现L2正则化
PyTorch里实现L2正则化有现成的方案,就是在优化器里直接设置weight_decay参数:
import torch.optim as optim optimizer = optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-4)这里weight_decay就是前面公式里的λ。PyTorch之所以叫weight decay而不是L2,是因为它实现的方式略有不同,但数学本质是一样的,都是在梯度更新时对权重做衰减。
需要留意的是,这个设置会对所有传入优化器的参数生效。如果只想对部分层加正则化,需要手动分组:
optimizer = optim.Adam([ {'params': model.features.parameters()}, {'params': model.classifier.parameters(), 'weight_decay': 1e-3} ], lr=1e-3)这个做法在迁移学习中很实用。模型的特征提取层已经在大规模数据上预训练过了,不需要太多正则化;而分类头是随机初始化的,需要更强的正则化来防止过拟合。
4.3 在TensorFlow/Keras中实现L2正则化
Keras的写法更直观,可以在层级别直接指定:
import tensorflow as tf model = tf.keras.Sequential([ tf.keras.layers.Dense(256, activation='relu', kernel_regularizer=tf.keras.regularizers.l2(1e-4)), tf.keras.layers.Dense(128, activation='relu', kernel_regularizer=tf.keras.regularizers.l2(1e-4)), tf.keras.layers.Dense(10, activation='softmax') ])这里注意,Keras的l2(1e-4)对应的是损失函数中的λ/2·||w||²,因为Keras内部的实现带了1/2系数。这跟PyTorch的weight_decay在数值上略有差异,换算时要留意。
4.4 L1正则化的实现细节
PyTorch原生没有提供L1 weight decay,因为L1的梯度是不连续的,直接在优化器里做衰减会引入麻烦。我用得多的是手动加到损失函数里:
l1_lambda = 1e-5 l1_norm = sum(p.abs().sum() for p in model.parameters()) loss = ce_loss + l1_lambda * l1_norm这种写法的缺点是需要额外维护loss,而且对bias也做了惩罚。如果只想对权重做L1,可以加一个过滤条件:
l1_norm = sum(p.abs().sum() for name, p in model.named_parameters() if 'weight' in name)Keras里L1的写法跟L2类似:
tf.keras.regularizers.l1(1e-5)4.5 手动实现权重衰减的方式
有些老框架没有内置weight decay,需要手动实现。其实原理很简单,就是在梯度更新之后,直接对权重做一次收缩:
with torch.no_grad(): for p in model.parameters(): p.mul_(1 - lr * weight_decay)注意这里的手动实现和optimizer.weight_decay有个坑:实现位置不同,效果在SGD和Adam上表现不一样。SGD的weight decay等价于L2正则化,但Adam的weight decay不完全等价于严格L2。这也是为什么后来有论文专门讨论AdamW的原因。
4.6 用AdamW代替Adam加权重衰减
AdamW是Adam的改进版,它的核心改进就是把权重衰减从梯度计算中分离出来,在参数更新时单独执行。
我用AdamW的体感是:模型训练更稳定,尤其是配合较大的学习率时,收敛效果比Adam + L2更好。PyTorch里直接可以调用:
optimizer = optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-2)如果只是用默认配置,我建议优先选AdamW而不是Adam,因为它在很多任务上都有明显提升,而且不需要额外的调参成本。
5. 参数选择与调优经验
5.1 λ怎么搜:从粗到细
搜索λ的实操套路,我总结出一个顺序:
- 先确定量级范围,比如1e-2到1e-5
- 在这个范围内,每隔一个数量级取一个值,跑少量轮次观察验证集趋势
- 锁定性能最好的那个数量级,比如1e-3,然后尝试1e-3、3e-3、1e-2这几个值
- 观察训练集和验证集的差距,差距大就加大λ,差距小就减小λ
每次试验跑全量epoch太浪费时间,我通常是先用1/5的数据跑10轮,找出量级,再用全量数据精调。这个流程看起来粗糙,但实际效果不错,比直接网格搜索效率高很多。
5.2 不同量级λ下的行为特征
| λ值 | 训练损失 | 验证损失 | 典型表现 |
|---|---|---|---|
| 过大(>1e-2) | 收敛慢,损失高 | 同样高 | 欠拟合,模型过于简单 |
| 适中(1e-4~1e-3) | 略高但可接受 | 最优 | 泛化能力最强 |
| 过小(<1e-6) | 接近0 | 逐渐升高 | 过拟合没有缓解 |
这个表不是绝对标准,因为最佳λ跟任务、数据量、模型容量都有关系。但它能给你一个判断方向的参考。
5.3 与其他正则化手段的组合策略
正则化不是越多越好,关键是要找到组合的平衡点。
我常用的一种组合方案是:
- Dropout概率设到0.3~0.5
- L2的λ设到1e-4左右
- 用Early Stopping兜底
这三个组合起来,比单独加大任何一个正则化手段效果都稳定。我自己的理解是,它们从不同角度限制模型复杂度:Dropout打乱参数之间的协同关系,L2压缩参数幅度,Early Stopping限制训练时间。三者互补的维度不同,所以叠加效果好过单一手段堆强度。
5.4 稀疏模型与稠密模型的选择
特征维度很高、大部分特征是噪声的时候,选L1或者弹性网效果更好。做模型压缩、部署到端侧的时候,L1生成的稀疏权重可以直接剪枝存储,优势非常明显。但精度优先的任务,L2更稳妥,因为它压缩参数但不削除信息,不会损失模型容量。
弹性网是L1和L2的结合:
α·(λ1·||w||₁ + λ2·||w||₂²)它在实际项目里比纯L1更常用,既能做特征选择,又能处理特征之间存在相关性的情况。
6. 常见问题与排查技巧
6.1 为什么加了L2正则化,训练损失反而更高
这是正常现象。L2正则化本质上是在损失函数里多加了一项,这个额外惩罚会让整体损失上升。关键是看验证集的表现:如果验证集损失下降、泛化能力提升,那这个上升是值得的;只有训练损失和验证损失同时升高,才说明λ设大了。
6.2 训练集准确率和验证集准确率都很低
这是欠拟合,说明约束过头了。解决办法很简单:降低λ,同时看是不是模型本身容量不够。如果加了L2之后准确率反而明显下跌,先检查λ,再看是否因为数据量太少导致模型本来就学不动。
6.3 不同框架的weight_decay数值为什么对不上
PyTorch的weight_decay和Keras的l2()的数值含义不同。Keras的l2(λ)在损失函数里是λ/2·Σw²,PyTorch的weight_decay=λ直接用在梯度更新上:
w_new = (1 - λ·lr)·w_old - lr·∂L/∂w两者有微妙的系数差异。迁移代码时不要直接照搬参数值,最好通过验证集结果对齐。
6.4 Adam家族对权重衰减的坑
Adam优化器本身会自适应调整每个参数的学习率,这会削弱L2正则化的直接效应。理论上是这样,因为L2的梯度会被Adam的缩放机制影响。
行业里比较经典的修正方案就是AdamW。如果你发现Adam配合weight_decay效果不理想,可以直接换AdamW试试,大部分深度学习框架都已经内置了。
6.5 对bias和BN层要不要正则化
bias参数通常不建议做正则化。因为bias的作用是平移激活函数,它的大小本身不反映模型复杂度,对bias做惩罚反而可能导致模型欠拟合。常见的做法是只对权重做正则化,bias直接忽略。
PyTorch优化器默认会对所有参数施加weight_decay,包括bias和BN层的gamma/beta。想排除它们,需要手动分组:
decay_params = [] no_decay_params = [] for name, param in model.named_parameters(): if param.requires_grad: if len(param.shape) == 1: # bias、BN层的gamma/beta no_decay_params.append(param) else: decay_params.append(param) optimizer = optim.AdamW([ {'params': decay_params}, {'params': no_decay_params, 'weight_decay': 0.0} ], lr=1e-3, weight_decay=1e-4)这个分组逻辑在训练大模型时尤其重要,这是我踩过坑之后总结出来的。
6.6 训练不收敛,损失震荡怎么办
如果加入参数范数惩罚之后训练震荡,先看是不是λ设置的量级过大导致梯度更新过于激进。另外,L1正则化在参数接近0的地方梯度不连续,也可能导致震荡。可以先调小学习率,或者换L2,或者用弹性网折中。
6.7 迁移学习中的weight_decay设置
做迁移学习时,预训练部分和新增分类层的正则化强度应该不同。预训练权重是从大规模数据中学习的,本身已经具备很好的泛化性,不需要强正则化;分类层是随机初始化,过拟合风险很高,正则化应该拉满。
我用PyTorch分组的代码写法可以这样说:
optimizer = optim.AdamW([ {'params': model.backbone.parameters(), 'weight_decay': 1e-5}, {'params': model.classifier.parameters(), 'weight_decay': 1e-3} ], lr=1e-4)这种差异化设置在参数规模较大的预训练模型上效果很明显。
7. 扩展思考:参数范数惩罚的深层价值
7.1 它与权重初始化、学习率的关系
参数范数惩罚的强度选择不是孤立的。如果权重初始化规范良好,参数一开始就在合理范围内,那么L2正则化的作用就相对温和。反过来,如果初始化不当初始权重过大,L2惩罚的压力就会很大,训练早期可能不稳定。
学习率也是同样的道理。学习率大会放大每一步梯度更新的幅度,如果对应的权重衰减也强,可能出现参数来回震荡。一般调参时会把学习率和λ放在一起观察,而不是单独调一个。
7.2 泛化误差分解的视角
从偏差-方差分解的角度看,L2正则化主要是通过减小模型方差来提升泛化性能。模型越复杂,方差越大但偏差越小;加了正则化后,模型复杂度被压缩,方差下降,虽然偏差略有上升,但整体泛化误差还是下降了。这个代价需要靠合理的λ来平衡。
做Kaggle比赛时经常看到排行榜上L2不work的情况,其实不是L2没用,而是这些人用的复杂集成模型本身方差已经很低了,再加L2只会增加偏差。
7.3 不同模型容器下的参数范数惩罚
在CNN里,L2正则化通常配合Dropout一起用。在Transformer里,权重衰减直接作用在attention层的QKV矩阵上,默认设置下顺序衰减的效果对收敛有帮助。在推荐系统中,Embedding表往往很大且面临稀疏数据,L2正则化需要特别轻,过强会把Embedding项压成均匀分布,反而丢失了区分度。
我的经验是:Embedding层的weight_decay通常要比其他层小一个量级,甚至不设置。推荐系统的Embedding表动辄几百上千万参数,本来就是欠拟合重灾区,暴力正则化只会让模型丢失细节信息。
我的实操体会
断断续续用了好几年参数范数惩罚,我最大的感受是,它不像很多教程里说的那样只是一个“防过拟合的小技巧”,它其实是模型容量控制体系里最基础、最容易理解、也最好控制的一个旋钮。
正则化的本质是在模型的记忆能力和理解能力之间取平衡。参数范数惩罚就是通过限制参数大小,让模型必须去总结数据中的规律,而不是去背数据。它或许不是最高级的正则化手段,但它是构建稳定模型不可缺失的地基。
入门深度学习时,先把L1和L2这两个最基础的权重惩罚吃透,搞清楚它们的原理、区别和适用场景,再去学Dropout、BN或者更高级的正则化方法,思路就会清晰很多。如果读到这里你都看明白了,找个数据集亲手跑一遍,调一调λ,对比一下训练集和验证集的曲线,相信你会对正则化有更直观的感悟。