☰
L1与L2正则化详解:从原理到工程实践
2026/10/15 13:55:20 网站建设 项目流程

1. 正则化到底在解决什么问题

做模型训练的人迟早会撞上一个很尴尬的局面:模型在训练集上的表现好得离谱,损失曲线一路往下掉,准确率刷到99%以上,结果一放到验证集或者线上真实数据上,表现直接崩盘。这种情况我见过太多次了,尤其是样本量不大、特征维度又高的场景,模型几乎是在“背答案”而不是“学规律”。这就是典型的过拟合。

正则化损失函数就是冲着过拟合去的。它的核心思路特别朴素:在原本的损失函数后面加一个惩罚项,让模型在拟合数据的同时,还得为“参数太大”这件事付出代价。你可以把它理解成给模型加了一条约束——别把某些权重调得过于极端,别对某几个特征过度依赖,保持整体参数的“克制”。

L1和L2是两种最常见的正则化形式,分别对应参数绝对值和参数平方和的惩罚。别看只是加了一项,它们对模型行为的影响差别非常大:L1倾向于把一部分权重直接压到0,产生稀疏解,天然带特征选择的效果;L2倾向于把所有权重都压小但不会归零,让模型更平滑、更稳定。理解这两者的差异,直接决定了你在实际项目里该选哪个、怎么调参、以及为什么调完之后模型表现会变。

这篇文章我会从原理、公式推导、代码实现、调参经验、踩坑记录几个角度把L1和L2讲透。不管你是刚接触机器学习的新手,还是已经调过不少模型但一直没搞明白正则化细节的老手,都能从这里拿到可以直接用的东西。全文基于常见的工程实践展开,涉及具体参数的地方我会把计算过程写清楚,方便你复现。

2. L1与L2正则化的原理拆解

2.1 从损失函数说起:惩罚项是怎么加进去的

先看没有正则化的原始损失。以线性回归为例,均方误差损失长这样:

L(w) = (1/n) * Σ(y_i - w^T x_i)^2

模型训练的目标就是找到一组权重w,让这个损失最小。问题在于,当特征之间存在共线性,或者样本量远小于特征维度时,能让损失很小的w不止一组,甚至有无穷多组。模型会挑一组“恰好”拟合训练数据的解,但这组解往往对噪声极其敏感。

加入正则化之后,损失函数变成:

L_total(w) = L_data(w) + λ * R(w)

其中R(w)就是正则项,λ是正则化系数,控制惩罚力度。λ越大,惩罚越重,模型越倾向于把权重压小;λ越小,越接近原始损失。这个λ是超参数,需要你自己调。

L1和L2的区别就在R(w)的形式上:

  • L1正则:R(w) = Σ|w_i|,所有权重绝对值的和
  • L2正则:R(w) = Σw_i^2,所有权重平方的和

有些实现里L2会写成(1/2)Σw_i^2,主要是为了求导时把系数2约掉,方便计算,本质上没区别。

2.2 为什么L1能产生稀疏解

这是L1最被人津津乐道的特性,也是面试高频问题。要理解它,得从两个角度看:几何角度和梯度角度。

几何角度更直观。假设只有两个权重w1和w2,把损失函数的等高线和正则项的约束区域画在一起。L2的约束区域是一个圆(高维是球),L1的约束区域是一个菱形(高维是超菱形,顶点在坐标轴上)。损失等高线往外扩,第一次碰到约束区域的那个点就是最优解。圆是光滑的,切点通常落在某个象限内部,两个权重都不为0;而菱形有尖角,尖角正好在坐标轴上,等高线更容易在尖角处相切,这时候就有一个权重被压成0了。维度越高,尖角越多,被压成0的权重就越多,稀疏性越明显。

梯度角度更工程化。L1的导数在w_i>0时是+1,w_i<0时是-1,w_i=0时不可导(次梯度在[-1,1]之间)。这意味着无论权重多小,只要不为0,梯度就是一个恒定大小的值,会持续把权重往0推。而L2的导数是2w_i,权重越小梯度越小,越接近0推力越弱,所以永远推不到0,只能无限接近。这就是L1能真正归零、L2只能缩小的根本原因。

注意:L1在0点不可导,所以用梯度下降直接优化L1会有点麻烦,工程上通常用近端梯度法(proximal gradient)或者坐标下降法来处理,比如sklearn的Lasso就是用坐标下降实现的。

2.3 L2为什么让模型更平滑

L2的惩罚是权重的平方和,对大权重的惩罚远大于小权重。一个权重从1变成2,惩罚从1变成4,翻了4倍;从10变成11,惩罚从100变成121,只涨了21%。这种非线性增长意味着L2对异常大的权重特别敏感,会优先把它们拉下来。

从数学上看,加入L2正则后,最优解的闭式形式会多出一项。以线性回归为例,原始最小二乘解是:

w = (X^T X)^(-1) X^T y

加入L2后变成:

w = (X^T X + λI)^(-1) X^T y

这个λI加在对角线上,直接改善了矩阵的条件数。当X^T X接近奇异(特征共线)时,原始解会爆炸,而加了λI之后矩阵变得可逆且数值稳定。这就是为什么L2也叫“岭回归”(Ridge Regression)——它像在山脊上行走,把解稳定在合理范围内。

实际效果上,L2让所有权重都变小但保持非零,模型对单个特征的依赖被削弱,整体预测函数更平滑,方差更小。代价是偏差会略微增大,但在过拟合场景下这个交换非常划算。

2.4 两者的核心差异对照

维度L1正则L2正则
惩罚形式Σ|w_i|Σw_i^2
解的特性稀疏,部分权重为0稠密,权重都小但不为0
几何形状菱形/超菱形圆/超球
梯度行为恒定大小,持续推向0与权重成正比,越接近0推力越小
特征选择自带特征选择能力无特征选择能力
数值稳定性一般改善条件数,更稳定
适用场景高维稀疏、需要选特征特征共线、需要稳定解
典型别名LassoRidge

这张表建议存下来,选型的时候对着看基本不会错。

3. 实操中的参数计算与代码实现

3.1 手推一个简单例子看清惩罚效果

光看公式容易飘,我们拿一组具体数字算一遍。假设有两个权重w1=3,w2=0.1,λ=0.1。

L1惩罚项:0.1 * (|3| + |0.1|) = 0.1 * 3.1 = 0.31 L2惩罚项:0.1 * (3^2 + 0.1^2) = 0.1 * (9 + 0.01) = 0.901

再看一组w1=1.5,w2=1.5,λ还是0.1:

L1惩罚项:0.1 * (1.5 + 1.5) = 0.3 L2惩罚项:0.1 * (2.25 + 2.25) = 0.45

对比两组数据能发现:第一组权重分布极不均匀(3和0.1),L1惩罚0.31,L2惩罚0.901;第二组权重均匀(1.5和1.5),L1惩罚0.3,L2惩罚0.45。L1对两组几乎一视同仁(0.31 vs 0.3),而L2对不均匀分布的惩罚(0.901)远大于均匀分布(0.45)。这直观说明了L2偏好“雨露均沾”的权重分布,而L1不在乎分布是否均匀,只在乎总量。

3.2 用PyTorch实现带正则化的训练

PyTorch里加正则化有两种方式。第一种是手动在损失里加惩罚项,适合你想精确控制惩罚形式或者做实验对比:

import torch import torch.nn as nn model = nn.Linear(10, 1) optimizer = torch.optim.SGD(model.parameters(), lr=0.01) criterion = nn.MSELoss() lambda_l1 = 1e-4 lambda_l2 = 1e-3 for x, y in dataloader: optimizer.zero_grad() pred = model(x) loss = criterion(pred, y) # 手动加L1和L2惩罚 l1_penalty = sum(p.abs().sum() for p in model.parameters()) l2_penalty = sum(p.pow(2).sum() for p in model.parameters()) loss = loss + lambda_l1 * l1_penalty + lambda_l2 * l2_penalty loss.backward() optimizer.step()

第二种是用优化器自带的weight_decay参数,但要注意:weight_decay实现的其实是L2正则,而且它和手动加L2在数学上等价、在实现上略有差异(涉及Adam等自适应优化器时行为不同,后面会讲):

optimizer = torch.optim.SGD(model.parameters(), lr=0.01, weight_decay=1e-3)

提示:weight_decay只对权重生效,不对偏置项生效,这是常见做法。因为偏置项通常不需要正则化,惩罚它反而可能让模型欠拟合。手动实现时记得把bias排除在外。

3.3 用sklearn快速验证L1和L2效果

如果只是想快速看效果,sklearn更省事。Lasso对应L1,Ridge对应L2,ElasticNet是两者混合:

from sklearn.linear_model import Lasso, Ridge, ElasticNet from sklearn.datasets import make_regression from sklearn.model_selection import train_test_split X, y = make_regression(n_samples=200, n_features=100, n_informative=10, noise=10, random_state=42) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) lasso = Lasso(alpha=0.1) lasso.fit(X_train, y_train) print("Lasso非零权重数量:", (lasso.coef_ != 0).sum()) ridge = Ridge(alpha=1.0) ridge.fit(X_train, y_train) print("Ridge非零权重数量:", (ridge.coef_ != 0).sum())

跑下来你会看到Lasso的非零权重数量明显少于100,很多特征被直接剔除了;而Ridge的权重几乎全非零,只是数值都变小了。这个对比实验我建议每个新手都亲手跑一遍,比看十篇文章都管用。

3.4 λ的选取:从粗调到精调

λ是正则化里最关键的超参数,选不好要么没效果要么欠拟合。我的经验是分两步走。

第一步粗调,用对数尺度扫一遍。比如从1e-5到1e2,每个数量级取一个值,看验证集表现:

import numpy as np from sklearn.linear_model import Ridge from sklearn.model_selection import cross_val_score alphas = np.logspace(-5, 2, 20) scores = [] for a in alphas: ridge = Ridge(alpha=a) score = cross_val_score(ridge, X_train, y_train, cv=5, scoring='neg_mean_squared_error').mean() scores.append(score) best_alpha = alphas[np.argmax(scores)] print("粗调最优alpha:", best_alpha)

第二步精调,在最优值附近缩小范围再扫一遍。比如粗调得到0.01,就在0.005到0.05之间再取20个点。

这里有个经验:λ的合理范围和数据尺度强相关。如果特征都做过标准化,λ在1e-4到1e1之间通常够用;如果特征没标准化,λ的选取会非常难,因为不同特征的尺度差异会让惩罚变得不公平。所以做正则化之前,标准化几乎是必须的。

注意:标准化要用训练集的均值和方差,然后应用到验证集和测试集,不能各自标准化,否则会造成数据泄露。

4. 工程实践中的关键细节与踩坑记录

4.1 L1和L2能不能一起用

能,这就是ElasticNet。它的惩罚项是:

R(w) = α * ρ * Σ|w_i| + α * (1-ρ) * Σw_i^2 / 2

其中ρ控制L1和L2的比例。ρ=1退化成Lasso,ρ=0退化成Ridge。ElasticNet的好处是既有L1的稀疏性,又有L2的稳定性,在特征高度相关时表现比纯Lasso好——因为Lasso在相关特征里会随机挑一个保留,行为不稳定,而ElasticNet倾向于把相关特征一起保留或一起剔除。

实际项目里如果特征维度高又存在共线性,我一般直接上ElasticNet,省得纠结。

4.2 weight_decay和L2正则不是完全等价

这是个容易踩的坑。在SGD里,weight_decay和L2正则数学上等价。但在Adam这类自适应优化器里,两者不等价。原因在于Adam会对梯度做自适应缩放,如果L2惩罚是加在损失里的,它会被自适应缩放影响;而weight_decay是直接作用在权重更新上,不受缩放影响。后来有人提出了AdamW,就是把weight_decay从梯度里解耦出来,单独作用在权重上,效果通常更好。

所以如果你用Adam或AdamW,建议直接用优化器的weight_decay参数,别手动加L2惩罚,否则行为和预期可能不一致。

4.3 正则化不是万能的

我见过有人一遇到过拟合就加正则化,结果调了半天没效果。正则化只对“参数过大导致的过拟合”有效。如果过拟合是因为数据量太少、特征里有噪声、或者模型结构本身不合理,加正则化帮助有限。这时候该做的是加数据、清洗特征、或者换更简单的模型。

判断方法很简单:先看训练损失和验证损失的差距。差距大说明过拟合,可以试正则化;如果训练损失本身就很高,那是欠拟合,加正则化只会更糟。

4.4 常见问题速查表

问题现象可能原因排查方向
加正则后验证集变差λ太大导致欠拟合减小λ,观察训练损失是否上升
L1没产生稀疏解λ太小或特征未标准化增大λ,检查标准化
训练不稳定、损失震荡λ过大或学习率过高降低λ和学习率
L2效果不明显特征共线性不严重尝试L1或ElasticNet
Adam下L2行为异常weight_decay与L2不等价改用AdamW或手动实现
偏置项被惩罚正则化未排除bias手动实现时排除bias参数

4.5 几个我踩过的坑

第一个坑是标准化顺序。有次我先把整个数据集标准化再划分训练测试集,结果验证集表现虚高,上线后崩了。正确做法是先划分,再用训练集统计量去变换验证集。

第二个坑是λ的尺度。不同框架对λ的定义可能差一个系数,比如有的L2写成Σw^2,有的写成(1/2)Σw^2,导致同样的λ值效果差一倍。换框架时一定要重新调λ,别直接搬。

第三个坑是L1的稀疏性被优化器破坏。用近端梯度法时如果步长设得不对,稀疏解可能出不来。这时候可以换坐标下降法,sklearn的Lasso就是坐标下降,稀疏性很稳定。

5. 不同场景下的选型建议

5.1 高维稀疏场景优先L1

文本分类、基因表达分析这类场景,特征维度动辄上万,但真正有用的可能就几十个。这时候L1的特征选择能力就是刚需,它能直接把无用特征的权重压成0,模型体积小、推理快、可解释性强。我做过一个文本分类项目,原始特征两万多个,用L1之后非零权重只剩三百多个,准确率几乎没降,推理速度提升了一个数量级。

5.2 特征共线场景优先L2

金融风控、传感器数据这类场景,特征之间相关性很高,比如多个指标其实在测同一个东西。这时候L1会随机保留其中一个,导致模型不稳定,换一批数据选出来的特征就变了。L2则会把相关特征的权重一起缩小,保持稳定。如果既要稳定又要稀疏,用ElasticNet。

5.3 深度学习里的默认选择

深度学习模型参数动辄百万千万,全连接层和卷积层通常默认加L2正则(weight_decay),因为L1在深度网络里容易导致训练不稳定,而且稀疏性对深度模型的意义不如线性模型那么大。不过近几年也有研究用L1做模型剪枝,把不重要的权重压成0然后裁掉,能显著压缩模型体积。这个方向值得关注。

5.4 小样本场景的取舍

样本量小的时候,正则化几乎是必选项。但λ要调得比较小心,因为小样本下验证集本身就不稳定,调出来的λ可能过拟合验证集。我的做法是用交叉验证,并且重复多次取平均,减少随机性带来的偏差。

6. 一些进阶思考

正则化的本质是在假设空间里加约束,把解限制在一个更小的范围内。L1和L2只是最基础的两种约束形式,背后其实对应着不同的先验假设:L1对应拉普拉斯先验,L2对应高斯先验。从贝叶斯角度看,正则化就是最大后验估计,惩罚项就是先验的对数。

理解了这一层,你就能举一反三。比如Group Lasso把一组权重作为一个整体惩罚,适合特征有分组结构的场景;弹性网是L1和L2的混合;还有Total Variation正则化适合图像去噪,惩罚的是相邻像素的差异而不是权重本身。这些变体的核心思想都一样:把你对问题的先验知识编码进惩罚项里。

我在实际项目里的体会是,正则化参数调优花的时间往往比模型结构设计还多,但收益是实打实的。一个调好λ的简单模型,经常能打赢一个没调正则化的复杂模型。所以别嫌麻烦,该扫的参数老老实实扫,该做的交叉验证认真做,这些功夫最后都会体现在线上指标上。

最后分享一个小技巧:如果你不确定该用L1还是L2,可以先两个都跑一遍,看验证集表现和权重分布。如果权重分布很稀疏且验证集表现好,说明L1合适;如果权重分布均匀且验证集稳定,说明L2合适。这个判断方法虽然土,但很有效。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询