ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

过拟合怎么办?详解L1/L2正则化与参数范数惩罚实战

过拟合怎么办?详解L1/L2正则化与参数范数惩罚实战

模型在训练集上损失一路狂跌,甚至逼近零,可一上测试集就原形毕露,精度惨不忍睹——这种经历我猜大多数做机器学习的人都碰到过。我第一次带某跨平台视觉识别项目时,就栽在这上面:训练了上百轮,训练集准确率接近百分百,但验证集上的指标却始终上不去,一开始我还以为是数据划分出了问题,白折腾了好几天。后来才反应过来,问题是模型“记性太好”,把训练集里的噪声和特例全都背下来了。要收拾这种局面,正则化里的参数范数惩罚是最直接的武器,也是我后来每个项目里几乎必做的功课。

这篇文章我不打算讲太多抽象的数学推导,而是从“为什么模型会不乖”开始,把L1、L2这两种最常见的参数范数惩罚掰开揉碎讲清楚,再结合实操场景聊一聊怎么选、怎么调λ,以及我在多个项目里实实在在踩过的坑。适合正在被过拟合折磨的工程师,也适合刚学完理论但不知道怎么下手的同学。

1. 先理解过拟合,才能理解范数惩罚

很多初学者拿到正则化就急着看公式,结果越看越晕。我觉得不如先把问题倒过来想:正则化到底在对抗什么?

1.1 模型“背答案”不等于学会了

机器学习模型本质是一个函数拟合器,它的目标是在训练数据上找到一组参数,让损失函数最小。但“最小化训练损失”和“学到真实规律”是两码事。当参数数量足够多、模型容量足够大时,模型完全可以在训练集上做到几乎零误差——代价是它把每个样本的个体噪声都当成了规律。

举例来说,某个人脸识别模型如果在训练集里见过某位戴眼镜的用户,它可能学到的是“眼镜框边缘的像素组合对应这个身份”,而不是“这个人脸的五官结构对应这个身份”。这种模型换到新环境、新光线、新角度下,表现就立刻崩塌。

用一个生活化的类比:两个学生在准备考试。学生A记住了每一道练习题的答案,但考试题目稍一变他就不会了;学生B从练习题中提炼了解题思路,哪怕题目换了个包装也能做出来。前者就是过拟合,后者才是泛化。正则化的本质,就是逼着模型做学生B,而不是学生A。

1.2 过拟合的三种救法,为什么先选正则化

要缓解过拟合,常见手段有三类:

  • 增加训练数据量,让模型见更多样本,自然就不容易把个别样本当规律。但数据不是凭空变出来的,成本高、周期长。
  • 降低模型复杂度,比如换更小的网络或者更少的特征。但如果模型本来就不够强,强行砍复杂度会导致欠拟合。
  • 正则化,在保持模型容量不变的前提下,约束模型“别太狂”——它允许你有千万个参数,但不允许你把每个参数都用到极致。

实际工程里,前两种方案往往受制于资源和时间,正则化就成了性价比最高的选择。它不仅适用于线性模型,还适用于神经网络、支持向量机、逻辑回归等几乎所有参数化模型。更妙的是,正则化还可以和前面两种方法叠加使用,并不冲突。

1.3 参数范数惩罚到底在罚什么

所谓“参数范数惩罚”,简单说就是在原始损失函数后面加一个与模型参数大小有关的惩罚项,让训练过程在减小损失的同时,还要兼顾“参数别太大”。数学上长这样:

[ J(\theta) = L(\theta) + \lambda \cdot \Omega(\theta) ]

其中 (L(\theta)) 是原始损失函数(比如交叉熵、均方误差),(\Omega(\theta)) 是对参数的惩罚函数,(\lambda) 是控制惩罚强度的系数。

这里要注意一个细节:惩罚项只作用于权重参数,而不包括偏置参数。偏置的作用是调整神经元的激活阈值,它的大小并不影响模型在特征方向上的复杂程度,所以通常不参与惩罚。

(\lambda) 越大,模型越“怂”,参数会被压得越小,模型就越简单;(\lambda) 越小,模型的自由度越高,表现力越强,但过拟合风险也越高。如何选这个 (\lambda),就是整个调参过程的核心。

2. L2与L1:两种流派,两种性格

参数范数惩罚里,最常见的两种实现就是L2正则化和L1正则化。它们表面上看只是差了一个平方和与绝对值求和,但实际效果截然不同。

2.1 L2正则化:温柔的平滑派

L2正则化的惩罚项是所有权重平方和的一半,即:

[ \Omega(\theta) = \frac{1}{2}|\theta|_2^2 = \frac{1}{2}\sum_i \theta_i^2 ]

之所以加 (1/2),纯粹是为了后续求导方便——去掉这个系数,(L2) 对 (\theta) 的导数就是 (\theta) 而不是 (2\theta)。

在梯度下降过程中,加上L2惩罚项后,权重的更新规则变成:

[ \theta \leftarrow \theta - \eta(\nabla L(\theta) + \lambda \theta) = (1 - \eta\lambda)\theta - \eta\nabla L(\theta) ]

这个式子很有意思。它相当于在每次更新时,先把权重整体往零的方向缩小一小截——所以L2正则化在深度学习里也常常被叫作“权重衰减”。只要 (\eta\lambda) 小于1,每步更新权重就会额外乘一个小于1的系数,参数越大,被衰减得越狠。

L2正则化的效果非常“温柔”:它会让权重朝零方向收敛,但很少直接把某个权重推到精确的零。原因在于接近零时,梯度中的 (\lambda\theta) 项也在同步变小,相当于拉扯力越来越弱,最后权重只是被压缩到一个很小但非零的数值。

从几何上看,L2惩罚在参数空间里限定了一个圆形的可行区域,最优解往往落在圆的内侧,让参数在可控范围内均匀地缩小。这种特性让模型对单个特征的微小扰动不那么敏感,整体变得更平滑。

2.2 L1正则化:强硬的稀疏派

L1正则化的惩罚项是所有权重绝对值之和:

[ \Omega(\theta) = |\theta|_1 = \sum_i |\theta_i| ]

它对参数的梯度是符号函数 (\text{sign}(\theta)),也就是说,不管参数本身是多少,只要它为正就减一个固定量,为负就加一个固定量,逼近零的“速度”是恒定的。这个特性带来一个非常关键的行为:参数可以在更新过程中被精确地压到零,而且一旦到了零附近,梯度更新不再有平滑的“逃逸通道”,很多参数就此钉在零的位置上。

因此L1正则化最大的价值就是产生稀疏解——大量不重要的特征权重变成0,剩下的特征权重非零。对于高维特征空间,这直接起到了特征选择的作用。

还是用生活化类比:L2像是给行李箱加了个规定尺寸的框,所有行李往里塞,塞不下的就压扁一点;L1像是规定只能带有限数量的物品,多余的直接扔掉,最后留下的都是少量必要的装备。

2.3 为什么L1更“激进”:梯度和OUP的视角

很多初学者容易混淆L1和L2的效果,只背结论“L1稀疏、L2平滑”。我建议从梯度入手去理解,这样记忆特别牢:

  • L2的梯度是 (\lambda\theta),它是一个与当前权重大小成比例的“拉扯力”——权重越大,往回拽的力越大,所以它善于把很大的权重按比例压缩,但这个力在权重接近0时会趋近于0,于是很难真正到0。
  • L1的梯度是 (\lambda \cdot \text{sign}(\theta)),它是一个固定强度的“收回力”——无论权重是100还是0.01,每步都削减一个固定的量,所以它有能力把权重一直推到0。

这两个梯度行为的差异,直接决定了两种正则化的性格:L2是“按比例收税”,L1是“每人收固定人头税”。按比例收税对谁都公平,但不会消灭最穷的人;收固定人头税对小额权重是致命的,会把它们彻底清零。

这也能解释为什么实际任务中L1往往用在特征维度特别高、希望做稀疏化的场景,而L2更常用在“想抑制过拟合但保留所有特征表达”的场景。

3. 实操:归一化、λ和选型

知道了L1和L2的区别,接下来就要动手选型和调参了。这一节我直接把项目里的经验总结成三条可落地的规则。

3.1 特征必须先做归一化/标准化

这一点无论用L1还是L2都是铁律。因为范数惩罚把所有权重放在同一个尺度下比较,如果某个特征的单位是千克(数值范围几百),另一个特征是百分比(数值范围0到1),那它们在参数空间里的“地位”天生就不平等。

举一个具体的例子:某电商推荐模型的特征里,用户年龄范围是0到100,用户历史购买金额范围是0到几万。如果直接喂给L1正则化的模型,模型会发现“购买金额”这个特征的权重稍微动一点,损失函数变化就很剧烈,于是L1惩罚会优先压缩它的权重甚至将其置零——但这并不是因为购买金额不重要,而是因为它的数值尺度太大。

我在实操中都是先用StandardScaler或者MinMaxScaler把特征统一到均值为0、方差为1的分布上,然后再考虑加正则化。做了这一步之后,L1/L2选出来的特征才是真正对预测有贡献的特征,而不是对尺度敏感的伪显著性。

3.2 三个实用选型法则

根据我自己的项目经验,选L1还是L2大致可以按下面三条来:

  • 特征维度高(比如上万个),且你怀疑大部分特征都是噪声时,优先选L1。它能把无效特征权重直接置零,节省存储和计算,模型部署后体积也更小。
  • 特征数量适中,且你已经通过业务知识确认了大部分特征是有用的,优先选L2。它在压缩参数、抑制过拟合的同时不会丢失特征,预测稳定性和平滑度更好。
  • 既想要稀疏性,又想在保留一组相关特征时更稳定,可以考虑ElasticNet(L1+L2组合)。它在高相关特征组面前不会像纯L1那样只随机挑一个,泛化能力更好。

在不同模型框架里,这个选择也有对应的映射:线性回归里就是岭回归(L2)和套索回归(L1);逻辑回归里通常是C参数,C是正则化强度 (\lambda) 的倒数;神经网络里常见的是weight decay,就是L2惩罚;支持向量机里的C是误分类惩罚系数,但约束复杂度的思路异曲同工。

3.3 λ怎么定:粗调到细调的三步法

(\lambda) 的选择没有捷径,但有个比较高效的套路,我在好几个项目里都这么干:

第一步,先在数量级上扫一圈。把 (\lambda) 设为 [0.0001, 0.001, 0.01, 0.1, 1, 10],用较少的训练轮次快速训练,画出验证集上的误差曲线。这一步的目的是找到“有效区间”——误差明显下降的那一段。

第二步,在有效区间内细调。比如0.001有效,那就试 [0.0003, 0.001, 0.003, 0.01],找到验证集误差最低的点。这里建议用对数坐标而不是线性坐标,因为 (\lambda) 的最佳值在数量级上往往跨好几个十倍。

第三步,用学习曲线验证。固定住候选的 (\lambda),画出训练误差和验证误差随轮数的变化曲线。观察关键特征:如果训练误差和验证误差最终收敛到接近,说明 (\lambda) 合适;如果训练误差明显低于验证误差,说明欠约束,可以适度加大 (\lambda);如果两者都很高,说明过约束,需要减小 (\lambda)。

还有一点要注意:(\lambda) 和优化器的学习率是有耦合关系的。调大 (\lambda) 之后,权重衰减的幅度会变大,这相当于额外改变了参数的更新步长,有时需要同时把学习率微调一下。这也解释了为什么很多人发现只调 (\lambda) 很难稳定复现验证集结果。

4. 实战中容易踩的坑

这一段我特意放在后面写,因为都是我在实际项目中真实踩过、或者帮别人排查过的坑。有些坑不看原型代码很难发现,但一旦踩了,模型的表现就是不对劲。

4.1 损失函数与惩罚项的顺序问题

在不少框架里,总损失被写成一个包含惩罚项的复合表达式。比如PyTorch里常见的写法是在loss上直接加weight_decay项,但有些人在自定义损失时喜欢自己写正则化项。这里的坑有两个:

第一个坑:惩罚项的系数忘加1/2。对L2来说,这会导致梯度变成 (2\lambda\theta),相当于λ实际值翻倍,你去调参时就会觉得模型特别容易欠拟合。

第二个坑:框架内置的weight_decay是作用在参数更新阶段的,和手动在loss里加惩罚项在数值上等价,但如果你同时开了内置weight_decay又手动加了惩罚项,等于做了两次正则化,模型大概率会欠拟合。

我建议新手在初始化阶段就把惩罚项的工作交给框架内置实现,不要自己手写,省心还少出错。等理解了背后的原理,再考虑自定义实现。

4.2 归一化的时机和对象

很多人在数据预处理阶段做了标准化,但忘了对预处理后的特征再做一次检查。如果某些特征在标准化之后仍然有明显的离群点——比如某些长尾分布的特征——那L1正则化还是会被这些离群点带偏。

另外还有一个隐蔽的问题:如果用的是神经网络,中间层的输出分布可能很不稳定,这种情况下直接在整个网络上施加L2约束效果会打折扣。一个常见的补充方案是对中间层做BatchNorm/LayerNorm,保证每层的输出在合理范围内,再叠加weight decay才有意义。

4.3 早停更像是另一种正则化

早停(early stopping)在训练过程中监控验证集误差,一旦误差开始上升就停止训练。它本质上也是一种对参数范数的约束——训练轮数少,权重没有机会长到很大,所以它和L2正则化有类似的效果。

这两种正则化可以同时用,但我见过不少同学用早停的时候就把λ调得很小,理由是“已经早停了不需要正则化”,结果验证集误差在下探过程中出现波动,模型不稳定。我的习惯是:先用早停确定大致的迭代轮数,再用λ做第二层防护,两者配合。

4.4 什么时候正则化会失效

正则化不是万能的,下面几种情况它几乎帮不上忙:

  • 训练数据本身就存在系统性偏差。比如训练集是白天拍摄的照片,测试集是夜间照片,再强的L2也没法让模型学会它没见过的东西。
  • 模型本身欠拟合。训练集误差都下不去,还有心思去管权重?这时候应该先加容量、加特征,而不是急着正则化。
  • 特征已经做了极度的筛选,剩余特征都是强特征,正则化的收益往往有限。这时提升模型能力更重要。

5. 一次完整的调参过程实录

理论讲了一堆,我用一个具体的虚构项目X走一遍流程,这样读者可以直接参考。

5.1 项目背景和初始状态

某跨平台用户行为预测模型,输入特征是30维(用户基础信息、行为统计、环境特征),输出是二分类标签(是否转化)。模型是一个两层的多层感知机,隐藏层64个单元。

初始状态:没有加正则化,训练集准确率98%,验证集准确率81%。训练曲线显示,在第15轮之后训练损失持续下降,但验证损失拐头向上——典型的过拟合。

5.2 先把特征标准化,再看λ扫描结果

我先把所有特征做StandardScaler标准化,然后选L2正则化跑了一个λ扫描,大致得到以下结果:

  • (\lambda=0.0001) 时,验证集准确率只有81.5%,改善非常有限,说明惩罚太弱,模型依然过拟合。
  • (\lambda=0.01) 时,验证集准确率升到87%,此时训练准确率降到94%,说明模型有所收敛但还有余力。
  • (\lambda=0.5) 时,验证集准确率反而降到83%,训练准确率也降到91%,惩罚过强,模型开始欠拟合。
  • (\lambda=0.1) 附近,验证集准确率最高,在88%左右。

这个扫描过程很快,每个λ跑不到几十轮就能看出趋势。找到0.1附近后,我在[0.05, 0.1, 0.2]之间细调,最终确定 (\lambda=0.1) 是当前组合下的最优点。

5.3 换成L1后的对比和选型

我后来又试了一次L1正则化。让20%的权重精确变成0,验证集准确率87.5%,和L2相当。但特征选择效果明显:30维特征中,L2保留下全部特征,L1直接把11个特征的权重清零,模型推理时省掉了这几路的计算量。

在那个项目里,特征的业务含义是人工整理过的,每一条都有明确意义,丢掉哪一路都不太合适,所以我最终选了L2。但如果项目特征数量上千、来源杂乱,L1带来的稀疏化和部署收益就更值得优先考虑。

这一段实操里最让我惊讶的是,λ从0.0001到0.01的跳跃,只改变了不到1%的训练误差,但验证集提升了接近6个百分点。调节正则化强度,确实是工程里性价比极高的一步。

6. 给新手的几条测试建议

最后分享几点实操体会。

第一,网格搜索λ时,优先用验证集而不是测试集做选择。λ是超参数,在验证集上选,选完再在测试集上做最终报告,这样测试集才保值,不会因为反复调参被“污染”。

第二,调试时先把可视化做好。每次训练都记录训练损失和验证损失,还要记录权重的范数值。有时候你会发现,λ调了但验证损失纹丝不动,那是因为模型的学习率已经太大,权重震荡太剧烈,正则化那点约束根本不顶用。这时候应该先调低学习率。

第三,不要指望正则化修复所有数据问题。如果特征本身没有辨识度、标签噪声过大,正则化再多也只是让模型“有礼貌地犯错”。先把数据和特征质量搞上去,正则化才能锦上添花。

我在实际项目中,通常把参数范数惩罚当作最后一个“兜底”手段,而不是第一选择。先做特征工程、数据增强、模型结构选型,等这些稳定了之后,再用L1/L2把参数空间约束到合理范围。顺序反了,你会花很多时间在调λ上,但模型该差还是差。

有一点几乎每次都很灵验:看到训练集和验证集损失曲线开始分道扬镳,脑海里立刻想到“加一点L2”,然后做个λ扫描,大多数情况下问题都能缓解一大半。这套经验,我在从传统机器学习模型到深度网络的多个项目里都用过,至今依然觉得是最值得先试的方案之一。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进