L1与L2正则化从原理到调参:彻底解决神经网络过拟合

📅 发布时间:2026/9/7 3:42:52
L1与L2正则化从原理到调参:彻底解决神经网络过拟合
“训练集上loss已经降到0.01了可验证集上的准确率还在原地踏步甚至还在往下掉。”——这种场景凡是亲手调过神经网络的人应该都撞见过。模型在训练集上表现得像个“学霸”能把每一道题背得滚瓜烂熟一上考场换了题立马现出原形连基础题都开始瞎蒙。这就是过拟合。而要对付过拟合L1和L2正则化是最基础、也最绕不开的两把刀。很多人只是听说过“L1能产生稀疏解L2能防止过拟合”但真到用的时候要么不知道往代码哪里加要么加了之后发现验证集精度还不如不加要么搞不清weight decay和L2到底是不是一回事。这篇文章就围绕“L1和L2正则化在神经网络里到底有什么用”这件事把原理、实操、调参、踩坑一次性讲透。适合谁看刚入门深度学习、正在被过拟合折磨的初学者以及已经把模型跑起来但训练曲线一直不理想、想系统理解正则化原理的工程师。读完你会发现L1和L2没那么玄乎关键是搞清楚它们在“约束什么”以及在你自己的任务里应该怎么选、怎么调。1. 正则化到底在解决什么问题1.1 过拟合的本质是“参数自由度太大”神经网络的过拟合本质上是一个“记忆”和“理解”之间的失衡。一个典型的神经网络参数量动辄几百万甚至几千亿而你手里的训练样本可能只有几千张图、几万条文本。模型完全有能力把训练样本里的每一个细节、每一条噪声、每一个偶然出现的离群点都“背”下来——它不需要真正理解规律只要把权重调成能精确复现训练集答案的形状就行。这里有一个容易忽略的点当参数量远大于样本量时优化问题本身就是欠定的。也就是说能同时满足训练集损失最小的解有无穷多个模型选择哪一个完全取决于初始化和优化轨迹。这种情况下你得到的模型大概率是一个“死记硬背型”选手——它对训练集分布中的任何细微波动都极度敏感训练损失越低往往泛化能力越差。解决过拟合的思路有两条。一条是从数据入手比如扩充数据集、做数据增强让模型看到更多变化另一条是从模型容量入手比如减小网络层数、减少通道数。但这两条路都有成本数据不是你想要就能有的模型变小也可能影响拟合能力。正则化走的是第三条路——不改变模型结构不增加数据而是直接给优化过程加约束让模型在“拟合数据”和“保持简单”之间被迫做一个折中。1.2 正则化是给“权重”戴上的枷锁正则化的核心思路非常直观如果模型是因为把权重调得太大、太高频才记住了噪声那我们就在损失函数里加一项“对权重大小的惩罚”让大权重付出代价。原始训练目标是最小化损失函数L(y, f(x))加上正则化之后变成原始目标min L(θ)正则化目标min L(θ) λ·R(θ)其中R(θ)就是惩罚项λ是正则化系数控制惩罚的力度。L1和L2的区别就体现在R(θ)的写法上L1惩罚项λ × Σ|w_i|对权重的绝对值求和L2惩罚项λ × Σ(w_i)²对权重的平方和求和有些实现写成λ/2 × Σ(w_i)²多一个1/2纯粹是为了求导后变成λw好看一点不影响本质为什么光看这个写法就能理解它“有用”因为优化器在最小化损失的同时还要让惩罚项尽量小。惩罚项小就意味着权重绝对值不能太大。模型不能再肆无忌惮地靠某一个特征“一票定生死”因为那个特征对应的权重一旦过大惩罚项就会剧烈上升。模型只能在“把loss降下来”和“把权重控制住”之间寻找平衡这个平衡点上的模型通常对噪声更鲁棒、泛化更好。初看可能觉得这只是个工程技巧但深挖一层它背后其实是奥卡姆剃刀原则在所有能解释训练数据的解里更平滑、更简单的那个解更可能在未知数据上表现良好。这不是某个人的拍脑袋结论而是统计学习理论里“结构风险最小化”的基本思想。理解了这一层你就明白为什么“正则化”这三个字在机器学习里无处不在——它不只是在神经网络里能用线性回归里叫岭回归和LassoSVM里叫软间隔决策树里叫剪枝本质都是同一件事控制模型复杂度。2. L1与L2的数学原理一眼看穿为什么结果不同2.1 从等高线图看L1和L2的区别很多人背过结论“L1会得到稀疏解L2只会让权重变小但不会变成0。”但为什么光看公式不容易理解换成几何视角就一目了然。先想象一个只有两个权重w1和w2的模型。原始损失函数的等高线在参数平面上是一圈一圈的椭圆你的优化目标是走到椭圆中心——损失最小的地方。现在加入正则化项相当于对优化加了一个“可行区域”L2的约束区域是圆盘w1² w2² ≤ C因为惩罚项是平方和L1的约束区域是菱形|w1| |w2| ≤ C因为惩罚项是绝对值这样问题就变成了在菱形或圆形区域内找到离原始损失中心最近的点。学过几何的都知道最优点通常是原始损失等高线和约束区域边界第一次相切的位置。关键区别在边界形状圆形边界光滑圆润切点大概率落在某个非零坐标处所以L2解出来的权重通常都绕开零菱形边界有四个“尖角”尖角正好落在坐标轴上也就是某一个权重为0的位置。当损失等高线被“推”到接近菱形角点时最优点就会直接落在角点上对应某个权重被压成0。权重越多这个现象越明显——L1会倾向于把大量不重要的权重直接清零只保留少数关键特征L2则是把所有权重都均匀地缩小一圈但谁也不归零。2.2 导数行为决定了L1和L2的不同“力道”几何解释之外还有一个更直观的视角——从梯度更新的角度看两种惩罚对权重的作用力。L2惩罚项对权重w的梯度是2λw如果写成λ/2 Σw²梯度就是λw。这意味着梯度正比于权重本身权重大的参数受到的“回拉”力就大权重小的参数受到的“回拉”力也小。参数越接近0推力越弱所以L2能把权重压得很小但越到最后越使不上劲永远到不了精确的0。这就像一个弹簧拉得越远回弹力越大回到平衡点附近就几乎不动了。L1惩罚项对权重w的梯度则是w 0时是λw 0时是-λw 0时不可导。注意它的梯度大小是恒定的λ与权重本身的大小无关。这意味着无论权重是100还是0.001L1施加的“回拉”力都一样大。在这种情况下一个很小的权重很容易被这个恒定的力直接“推过”零点变成0而且一旦变成0在0处如果梯度方向保持它会一直停留在0上。这就是L1能真正把权重清零的原因——它给了一个“恒定推力”而L2只是“比例收紧”。这里有一个工程上的细节顺带说一下L1在0处不可导实际训练时怎么处理常见做法是使用次梯度subgradient把0处的梯度定为0或者用近端梯度法proximal gradient来处理。在PyTorch这类框架里如果你手动给损失加L1项反向传播时框架会直接按次梯度的方式处理0处的导数会取一个子梯度值训练依然能跑只是收敛行为会有一些微妙的不稳定。这也是L1在深层神经网络里用得少的原因之一。2.3 稀疏性对深度学习到底意味着什么理解了L1为什么稀疏再来看它“有用”在哪儿。第一特征选择。在传统机器学习里比如基因表达数据、用户行为日志这种“特征成千上万、样本只有几百”的场景L1能把绝大多数无关特征权重直接清零留下的特征就是模型判定的“关键因子”。这比人为筛特征省事得多结果也更客观。第二模型压缩和推理加速。神经网络里如果大量权重是0网络在工程实现上就有做稀疏化的潜力——只存储非零权重跳过零值乘法从而显著减少内存和计算量。这也是模型剪枝技术的前置思路之一。很多做端侧部署的团队会先用L1正则化把网络练“瘦”再做结构化剪枝效果比直接剪枝稳定。第三可解释性。权重为0意味着“这个特征对输出没贡献”神经网络虽然依然是个黑盒但稀疏化之后至少能告诉你“模型依赖的信息集中在哪些通道、哪些特征上”这对分析模型行为、排查badcase都有帮助。不过坦率说在深层卷积网络里L1带来的“精确稀疏”并不像在线性模型里那么立竿见影。因为深层网络的特征高度冗余卷积核之间相互纠缠优化过程也更复杂L1训练完之后的权重分布往往是“很多值接近0但没完全归0”。如果想做真正的稀疏化更可靠的做法仍然是训练后修剪加微调。后面第4节我会再聊这个实操话题。3. 在神经网络里正确使用L1/L2的实操方案3.1 最简单的做法把惩罚项直接加到loss上如果你用的是PyTorch想给模型加L1正则化最直接的办法就是在计算loss的时候手动加一项。假设你已经定义好了模型model、损失函数criterion和样本数据import torch l1_lambda 1e-4 def l1_penalty(params): return sum(p.abs().sum() for p in params) logits model(x) loss criterion(logits, y) l1_lambda * l1_penalty(model.parameters()) loss.backward() optimizer.step()这段代码做的事很简单遍历模型所有参数把它们的绝对值之和乘以λ加到原始损失上。反向传播时每个参数都会额外收到一个大小为λ方向指向0的梯度。L2正则化在PyTorch里更简单因为优化器的weight_decay参数默认实现的就是L2效果optimizer torch.optim.SGD(model.parameters(), lr0.01, weight_decay1e-4)这行代码等价于在loss上加了λ × Σ(w_i)²严格说PyTorch源码里是梯度加上了weight_decay * w和λ/2 Σw²等价只是实现方式不是在loss层面而是直接在梯度更新时操作。看到这里你可能会问那weight_decay和L2到底是不是一回事这是个好问题但答案分成两种情况下面单独说。3.2 weight decay 与 L2 正则化的等价关系以及那个大坑先推导一下为什么SGD下weight decay等价于L2正则化。SGD的更新公式是w ← w - η·(梯度 weight_decay·w)把梯度里那项权重衰减单独拎出来相当于w ← (1 - η·weight_decay)·w - η·梯度你会发现这一多出来的项就是每次更新前先把所有权重按(1 - η·λ)的比例缩小一点点。这正好和L2正则化的效果一致——L2在权重更新时也会产生一个“把权重向0拉一把”的力。所以在SGD优化器下weight_decay就是L2正则化两个名字可以互换。但换成Adam就出问题了。Adam的更新规则里每个参数的梯度会被除以该参数历史梯度平方和的平方根也就是自适应学习率。如果L2正则化是通过“往梯度里加惩罚项”实现的那这个惩罚项的梯度同样会被自适应学习率缩放导致不同参数的惩罚强度被扭曲——原本惩罚力度对所有权重是一视同仁的现在变成了“谁的梯度变化大谁的惩罚就弱”。这在实践中会让L2正则化的效果变得很不稳定甚至出现权重衰减失效的情况。这也正是AdamWAdam with Decoupled Weight Decay出现的原因。AdamW的做法是把“权重衰减”从梯度计算里拆出来不参与自适应学习率的缩放直接在更新完参数后再做一个固定比例的缩小# 正确用 AdamW 来解耦权重衰减 optimizer torch.optim.AdamW(model.parameters(), lr1e-3, weight_decay0.01) # 错误用 Adam / AdamW 加大weight_decay不一定等于L2正则化 optimizer torch.optim.Adam(model.parameters(), lr1e-3, weight_decay0.01) # 在原始Adam里weight_decay实现的其实是“L2正则化”而不是解耦的weight decay如果你用了Adam又想让权重衰减真正起作用强烈建议直接换AdamW。这在Transformer、BERT等大规模模型的训练里已经是默认操作了不少开源代码把AdamW的weight_decay设成0.01或0.1效果稳定。3.3 实操配置举例几组可以直接参考的组合口说无凭我给几组在实际任务里验证过的配置算是“抄作业”模板。注意这些数值是起点不是终点但至少能让你少走一半弯路。场景优化器正则化方式建议初始值中小规模CNN图像分类CIFAR-10级别SGDmomentum0.9weight_decay 0.0001先固定这个值跑50个epoch观察ResNet系列在ImageNet等大规模数据上SGD momentumweight_decay 0.0001 ~ 0.0005参考torchvision官方实现Transformer类任务NLPAdamWweight_decay 0.01 ~ 0.1HuggingFace训练脚本里常见0.01需要特征稀疏化SGD或AdamW手动加L1惩罚项l1_lambda从1e-5开始试同时要稀疏和平滑SGD或AdamWElastic NetL1L2l1_lambda 1e-5l2_lambda 1e-4再说一个容易忽略的操作细节weight_decay是作用在所有参数上的包括全连接层、卷积核也包括BatchNorm里的γ和β、偏置项bias。实际上对bias和BN参数做惩罚往往没有好处甚至会抑制BN层本应做的数据分布归一化。PyTorch里如果用的是SGD/Adam的weight_decay参数它会作用到所有参数上而像HuggingFace的Trainer在实现时会默认把bias和一些LN/BatchNorm层的参数排除在weight_decay之外。手动做时也建议自己构造分组decay_params [p for n, p in model.named_parameters() if p.requires_grad and p.dim() 2] nodecay_params [p for n, p in model.named_parameters() if p.requires_grad and p.dim() 2] optimizer torch.optim.AdamW([ {params: decay_params, weight_decay: 0.01}, {params: nodecay_params, weight_decay: 0.0} ], lr1e-3)这个分组规则是“维度小于2的参数不衰减”实践中可以覆盖大部分bias和1D的BN参数够用且好理解。3.4 为什么实际训练中大家更常说weight decay而不是“L2正则化”一个有意思的现象是在深度学习圈子里大家聊L2正则化往往直接说weight decay甚至在PyTorch官方文档里weight_decay也写的是“L2 penalty”。原因很简单SGD下两者等价weight_decay实现起来只需要一行代码而手动改loss还要多写几行。但在看了3.2的分析后你应该明白了这个等价关系只在SGD这类没有自适应学习率的优化器下成立。现在主流模型大多用Adam/AdamW这时候“L2正则化”和“weight decay”就必须区分开了。如果你看到某篇论文里写“we use L2 regularization with weight decay 0.01”大概率他们实际做的是decoupled weight decay也就是AdamW。这些都是细节但魔鬼就在细节里——代码对不上论文复现就会莫名其妙翻车。4. 正则化系数λ怎么调一个调参工程师的实战记录4.1 第一步永远是先跑一个“不过正则化”的基线很多人一上来就纠结λ取多少甚至花一整天把λ从1e-6调到1然后看曲线没变化就开始怀疑人生。这其实是个方法论问题正则化是为了解决过拟合而判断有没有过拟合必须先看“不加正则化”时训练集和验证集之间的误差gap有多大。我的建议是先跑一组完全不加重衰减的模型以确定两件事第一模型的capacity够不够拟合训练数据如果训练loss都降不下来说明模型欠拟合加大权重衰减只会雪上加霜第二确认训练loss和验证loss之间确实有“分叉”——训练loss持续下降、验证loss开始回升这才是正则化要介入的信号。只有当这个gap确实存在而且足够大λ才有调的意义。如果训练和验证误差同步下降你需要做的不是加正则化而是加数据、加深网络、加大训练轮数。把“正则化”当成默认动作别管三七二十一先加0.0001这不是好习惯——有时它会让一个本来没过拟合的模型变得欠拟合白白丢掉精度。4.2 网格搜索的方法先定量级再定细粒度确定了要加入正则化之后λ的搜索可以用“数量级优先”的思路。对数坐标上网格搜索。别在1、2、3这种线性刻度上找λ要用1e-5、1e-4、1e-3这种指数级刻度。常用做法是先用1e-4、1e-3、1e-2、1e-1这几档粗筛一轮每档跑正常epoch的1/3或1/4看验证集精度的变化曲线。定位到性能最优的一个数量级后再在这个数量级内细分比如粗筛发现1e-3最好那就再试3e-4、1e-3、3e-3三档。为什么不用太小的λ因为λ过小时惩罚项在总loss里占比几乎可以忽略权重衰减的效果约等于无这和白跑一遍基线没区别。也不用一上来就试很大的λ——λ过大会让模型把所有权重都压到极小值训练loss下不去验证集自然也好不了除非你的模型容量本来就过剩。评估指标不要只盯最终精度。训练过程中需要同时观察三个信号训练loss、验证loss、以及模型参数的整体范数比如所有参数的L2范数。如果加了weight_decay之后参数范数确实下降说明正则化在生效如果参数范数没变化说明λ太小或者weight_decay因为某些原因没作用到参数上。这个Log里的细节比只看准确率曲线更能定位问题。4.3 λ 和其他超参数是“联动”的不是独立的调λ时常见的错误是把它当独享超参来调忽略了它和学习率、batch size、训练epoch之间的联动。先说学习率。权重衰减相当于一个“拉力”拉力越大等价的模型容量越小。如果你把λ调大模型收敛速度可能变慢此时适当增大学习率往往能让训练回到正常节奏。反过来如果λ调小了模型容量变大学习率过大可能导致训练震荡。所以λ和学习率的搜索最好放在一起进行先粗配学习率再固定学习率调λ最后回到学习率微调。再说epoch。正则化本质上是一种对“训练时间”的约束正则化越强模型达到收敛所需的epoch越多因为每一步更新都在被往一个更保守的方向推。跑同样的epoch数λ大的模型可能还没跑到理想位置验证集自然表现差。调λ的时候确保每个配置都跑够了epoch否则你看到的差异可能只是收敛进度不同不能说明正则化好坏。最后说batch size。batch size越小梯度噪声越大模型越容易在训练集上过拟合此时适当的weight decay帮助会更明显batch size越大梯度估计越稳模型天然更少过拟合weight decay的边际收益也不那么明显。如果你换了大batch size训练别惊讶weight_decay效果变弱了这不是bug是梯度噪声变了。4.4 实操小工具如何监控正则化是否真正生效想监控weight decay起没起作用我一般会在TensorBoard里同时记录三张曲线训练loss和验证loss曲线观察gap模型参数的L2范数随epoch变化的曲线观察权重整体走势权重直方图或分布变化观察权重究竟是向0收缩还是均匀分布判断逻辑是这样的如果验证精度提升的同时权重范数也在下降说明正则化帮你找到了一个“更简单”的解这通常是一个良性变化如果验证精度没动但权重范数猛降到极低大概率λ搞大了模型容量被过度压缩下一步就降λ如果权重范数几乎没动那就要检查weight_decay是不是真的作用在了所有目标参数上尤其是手动分组时是不是不小心把所有参数都丢进了不衰减的组里。权重分布变化也很直观。L1正则化会让权重直方图在0附近出现一个明显的尖峰L2正则化则是整个分布的峰值向0方向移动但不会在0处堆积太多完全相等的值。你可以在训练完成后画一次权重分布图对比加/不加正则化的区别几乎一眼就能看出用的是哪种正则化在起作用。5. 常见误区、工程坑与排查速查表5.1 为什么加了L2之后测试精度反而变差了这是最常遇到的困惑。“不是说L2能防过拟合吗怎么我加了之后验证集更差”可能原因按优先级排查现象可能原因排查方法训练loss也升高了λ太大模型欠拟合降低λ一个数量级再试训练loss没变验证loss变差基线本来就没有过拟合先看基线训练/验证gap别盲目加正则化加了weight decay后loss开始震荡学习率偏大正则强化了不稳定性学习率减半或降低λ验证精度下降但训练loss也很高训练epoch不够λ拖慢了收敛让模型再跑多一点epoch权重范数几乎没变化weight_decay没作用到目标参数检查optimizer参数分组顺着这个表排查大多数“越调越差”的问题都能定位到原因。关键还是那句正则化是治“病”的先确认有病再吃药别把药当补品。5.2 L1、L2和Dropout、Early Stopping怎么选到了现代深度学习里很多人会问既然有Dropout、有BatchNorm、有Early StoppingL1/L2还有必要吗答案是各管一摊不是替代关系。Dropout通过随机“屏蔽神经元”来防止神经元之间复杂的共适应作用是让网络更鲁棒。它和L2都防过拟合但机制完全不同可以叠加使用。BatchNorm它的主要作用是稳定训练、加速收敛顺带有轻微的正则化效果因为它引入了mini-batch内的随机统计噪声。但BN并不能替代L2尤其是在小batch size或任务本身分布偏移时L2还是重要的容量控制手段。Early Stopping在验证loss开始回升时提前终止训练相当于隐式地对训练步数做正则化。它和L2完全兼容实践中我通常会同时用。L1/L2直接约束权重本身的解空间是显式的、可控的容量约束。一个很实用的经验是如果你用SGD weight_decay解决大部分过拟合问题再叠加早停和适当的数据增强就已经能覆盖绝大多数中小规模任务。Dropout在模型容量大、难以过拟合时表现更明显但要注意现在很多新模型已经默认不使用Dropout了比如用LayerNorm等来做隐式正则化。不要迷信“正则化叠加得越多越好”每加一层正则化都可能压掉一部分模型表达能力最后变成“四平八稳但不聪明”。5.3 L1和L2组合Elastic Net在神经网络里的用法L1和L2不是非此即彼的关系。Elastic Net就是把两者加在一起的惩罚项λ1 × Σ|w| λ2 × Σw²。它同时具备L1的稀疏化能力和L2的稳定收缩能力。在神经网络里Elastic Net的用处主要体现在当你既想让模型不过拟合又希望权重尽量稀疏时单用L1训练不稳定因为绝对值项在0附近的梯度突变单用L2又没有稀疏性。加上L2项后优化轨迹更平稳L1的稀疏化效果也更容易体现出来。实操上我一般把l2_lambda固定得比l1_lambda大一个数量级比如l11e-5、l21e-4再分别微调。这个组合在风控模型、信用评分这类“既要预测好、又想把关键特征挑出来”的任务里非常实用。5.4 深坑提醒别把weight decay无意中施加给了BatchNorm和bias前面提过一次这里再展开说因为它太容易踩了。PyTorch里直接写optimizer SGD(model.parameters(), weight_decay0.0001)框架会对所有可学习参数都施加权重衰减包括BatchNorm里的gamma和beta、以及全连接层的bias。对bias做惩罚尤其没必要——bias不参与特征的“放大缩小”约束它对模型capacity几乎没有正向作用反而限制了模型的平移自由。BatchNorm的gamma被约束的话会更直接地影响normalization的表达能力。所以实际操作时强烈建议把参数分成两组或三组param_groups [ {params: [p for n, p in model.named_parameters() if p.dim() 2], weight_decay: 0.0001}, {params: [p for n, p in model.named_parameters() if p.dim() 2], weight_decay: 0.0}, ] optimizer torch.optim.SGD(param_groups, lr0.01, momentum0.9)这样对卷积核、全连接层的矩阵参数做weight decay对bias和BN参数不做。实验下来相同λ下这种分组通常比“一刀切”的收敛更稳、精度微高。这个细节在一些开源代码里已经成约定俗成自己写模型时别漏了。5.5 稀疏模型训练的后续动作裁剪与微调最后说一下如果用了L1做稀疏化训练完之后通常还要做一步裁剪和微调。L1训完的权重分布会有很多值“接近0但还没到0”直接部署还是一堆接近0的数存储和计算上的收益不明显。做法是设定一个阈值比如权重绝对值小于1e-3的全部置0先观察置0后模型精度掉多少。如果掉得不多说明这些权重本来就是冗余的裁剪成功如果掉得明显说明你设置的阈值太大或L1强度不够需要调参数重训。裁剪完之后的模型最好再用极小的学习率在训练集上微调几个epoch让剩下的非零权重重新适应“被砍掉一部分通道”后的状态这能挽回大部分精度损失。PyTorch里可以用mask简单实现threshold 1e-3 with torch.no_grad(): mask model.fc.weight.abs() threshold model.fc.weight.data * mask实际操作里相比“直接拿L1训练完就部署”我更推荐“训练 → 裁剪 → 微调”三步走这也是目前主流剪枝论文的标准流程。L1在这里的价值是让剪枝的起点更合理让“哪些权重该剪、哪些该留”这个问题有据可依。个人在实际项目里的体会是L1和L2看起来只是两个简单的数学表达式但它们背后代表的是两种完全不同的“约束哲学”L1更激进直接告诉你“这也不重要那也不重要砍掉”L2更温和让所有参数都收敛到一个更平滑的状态。选哪种取决于你的目标、任务和瓶颈在哪。如果只是想让模型泛化更好L2/weight_decay已经很够如果你做的是特征筛选、模型压缩或者希望模型决策路径更精简那L1就值得认真入了。先确认过拟合再选正则化方式最后用上面几节的方法调λ和监控效果踩坑概率会小很多。