正则化完全指南:从L1/L2本质到软阈值推导与调参实战
2026/9/19 22:38:59 网站建设 项目流程

训练集上loss一路狂掉到0.8个点,测试集上一跑直接崩回两位数的场景,我见过太多次了。每次有学生发消息问我“模型是不是过拟合了”,我都会先反问一句:“你上正则化了吗?”正则化(Regularization)看起来是所有机器学习手段里最不起眼的一类操作,不就是往损失函数后面加一项 λ||w|| 吗?但我在实际带项目、辅导课设和面试候选人的过程中发现,大多数人就在这个看似简单的概念上卡壳:不知道 L1 和 L2 到底该选哪个、不知道为什么 L1 能把系数归零、更说不清软阈值算子(Soft Thresholding)和 L1 正则化到底有什么关系。如果你正处在这些困惑里,这篇就是专门写给你的。

这篇会把正则化从头到尾彻底撸一遍:先搞清楚它到底在“正则”什么,再拆 L1 和 L2 的本质区别,接着把软阈值算子这个最容易问懵的点一路推到公式级推导,然后讲 λ 怎么调才靠谱,最后聊一聊深度学习里的花式正则化手法,以及我在实际项目里踩过的几个大坑。无论你是刚接触机器学习的新手,还是正在被期末考试和面试题折磨的学生,又或者是项目里被过拟合按在地上摩擦的工程师,这篇都适用。

1. 先搞懂正则化到底在“正则”什么

1.1 过拟合是正则化存在的唯一理由

机器学习的核心目标不是让模型在训练集上表现好,而是让它在没见过的新数据上也能稳定输出。但现实是模型很容易走偏:训练集上表现接近完美,测试集上一验证就原形毕露。这就是过拟合(Overfitting)。过拟合的本质,是模型的复杂度超过了训练数据本身能提供的信息量。换句话说,模型把训练数据里的噪声也当成规律记下来了,而不是只记住背后真正的模式。

正则化存在的唯一理由,就是对冲这种“记噪声”的倾向。它的做法非常直接:告诉优化器,你不仅要把预测误差降到最低,还要控制参数本身的规模。如果参数太大、太乱,即使训练误差更低,模型也会因为对输入太敏感而无法泛化。所以正则化本质上是给优化过程加了一条额外的约束,让模型在“拟合数据”和“保持简单”之间做出平衡。

我习惯用一个生活化的类比来解释这件事:你把一张表格交给实习生整理,如果只要求“信息都填对”,他可能把字体调得五花八门、加各种颜色,看起来很努力但是没法用;如果你额外要求“排版必须简洁统一”,他就会放弃那些花哨的操作,把精力集中在真正的信息结构上。正则化就是那条“排版必须简洁统一”的要求。

1.2 从偏差-方差分解看模型的复杂度税

如果要给正则化一个更严谨的数学位置,就绕不开偏差-方差分解(Bias-Variance Decomposition)。在回归设定下,泛化误差可以分解成三项:

E[(y - f̂(x))²] = σ² + Bias[f̂(x)]² + Var[f̂(x)]

这里 σ² 是数据本身的噪声,属于不可约误差;Bias 是模型简化过头造成的系统性误差;Variance 是模型对不同训练集过于敏感、输出波动很大的随机误差。模型越复杂,Bias 通常越低,但 Variance 会快速升高;模型越简单,Variance 低,但 Bias 会升高。曲线在中间某个位置会有一个最优平衡点。

正则化干的事情,就是在不把模型结构改小的情况下,人为提高它的“复杂度税”。同样是几十个特征的线性模型,加入 L2 惩罚之后,参数的幅度被压缩,模型对单个特征的依赖变弱,Variance 显著下降,虽然会牺牲一点训练集上的 Bias,但换来了整体泛化误差的下降。如果你从统计学习理论的角度去看,正则化相当于在同一个假设空间里用约束缩小了有效模型容量,从而获得更紧的泛化误差界,代价是引入了一点可有可无的系统偏置。这个代价几乎总是值得的。

1.3 为什么用范数惩罚而不是硬限制模型

有人可能会问:与其在损失函数后面加惩罚项,为什么不直接限制模型只能用几个特征,或者限制神经网络的层数、神经元数量?这两种思路的差别很微妙,但非常重要。

硬限制(比如“最多只能用 3 个特征”或者“隐藏层不能超过 2 个”)的问题在于搜索空间是离散、不光滑的,优化起来非常麻烦,而且一旦限制错了,模型立刻欠拟合。范数惩罚则不同,它是在连续空间里给目标函数增加一个平滑的“倾斜力”,优化器可以在原有梯度方向上继续走,只不过每走一步都要额外付一点代价。结果是:模型最优解被拉向更小的参数区域,但并不会被硬性截断。这种柔性约束让优化过程非常自然,也更容易收敛,同时还能保留模型在数据确实需要复杂描述时的灵活性。

2. L2 正则化与 L1 正则化,差别远比表面大

2.1 岭回归:把所有参数一起按比例压缩

L2 正则化对应的经典模型是岭回归(Ridge Regression),目标函数是:

min_w (1/2)||Xw - y||₂² + λ||w||₂²

这里的惩罚项使用的是参数向量的 L2 范数平方,也就是所有系数的平方和。Ridge 最优雅的地方在于它有闭式解:

w = (XᵀX + λI)⁻¹Xᵀy

这个公式比普通最小二乘多出的“λI”非常值钱。在最小二乘里,如果特征之间高度相关,XᵀX 可能接近奇异,求逆会变得极不稳定;而加上 λI 之后,每一个特征值都被抬高了 λ,至少在数值层面让矩阵变得更加可逆。这也是为什么当特征共线性严重时,Ridge 往往比直接用普通线性回归稳定得多。

从解的形态看,Ridge 做的是对系数的等比压缩:每个系数都往 0 的方向收缩,但收缩的幅度和系数本身大小成比例。结果就是没有任何一个系数会被精确地压成 0,除非它本来就是 0。如果不做特征选择,只是想让模型更稳、更抗扰动,L2 是首选。

2.2 Lasso:不仅能压缩,还能直接清零

L1 正则化对应的经典模型是 Lasso(Least Absolute Shrinkage and Selection Operator),目标函数是:

min_w (1/2)||Xw - y||₂² + λ||w||₁

惩罚项是参数绝对值的和。和 Ridge 最大的区别,是 Lasso 的解会出现精确的 0。也就是说,Lasso 可以自动做特征选择:那些不重要的特征,系数会被直接砍成 0,而不是留一个小数在模型里捣乱。

这种“清零”能力来自 L1 范数在原点处的非光滑性。绝对值函数在 0 点的导数是不存在的,它的次梯度(Subgradient)是一个区间 [-1, 1]。正是因为有了这个“棱角”,当正则化强度足够大时,最优解会被卡在棱角上,也就是坐标轴处,此时对应的系数就变成了 0。下一章我会专门推导这个过程,这里你先记住一个直觉:L1 的解更喜欢“顶到墙角”,L2 的解则更像是“被吹气球一样均匀收缩”。

2.3 几何直觉:为什么菱形和球面给出的答案不一样

L1 和 L2 的差别,用约束视角看最直观。把目标函数写成原始形式加上一个约束条件的话:

  • L1 正则:min (1/2)||Xw - y||₂², s.t. ||w||₁ ≤ t
  • L2 正则:min (1/2)||Xw - y||₂², s.t. ||w||₂² ≤ t

也就是说,模型被限定在参数空间的一个区域内:L1 对应的是菱形(高维下是超多面体),L2 对应的是一个圆形(高维下是超球体)。无约束时最优的 w 很可能落在区域外面,现在只能沿着损失函数等高线和约束区域的边界相切的位置取解。

关键点在于菱形的角落在坐标轴上。当损失函数的等高线碰到菱形的顶点时,切点处的某个坐标天然就是 0;而圆形边界处处光滑,等高线与其相切的位置通常不会精确落在坐标轴上,所以 L2 的解很少出现严格的 0。这就是几何层面“为什么 L1 产生稀疏解、L2 不产生”的答案。

两个模型选哪个,取决于业务目标。如果特征动辄几百上千维,并且你怀疑里面大部分特征只是噪声,那 L1 能帮你把模型砍到只剩骨干特征,后续部署也更快。但如果特征本身都是经过筛选的有效变量,彼此之间还有较强的相关性,L1 反而可能因为“只能随机选一个”而导致结果不稳定,这时优先选 L2 或者下一章会讲的弹性网(Elastic Net)。

为了对照方便,我把几个主要差异整理成了表格:

维度L2 正则化(Ridge)L1 正则化(Lasso)
惩罚形式λ||w||₂²λ||w||₁
解的特点系数等比收缩,不归零系数可被精确压成 0
约束区域圆形(超球体)菱形(超多面体)
可微性处处可微原点不可微,需要次梯度/近端方法
特征选择能力
共线性鲁棒性较好较差,相关特征中只能随机挑一个
闭式解一般没有

3. 软阈值算子为什么是 L1 正则化的解

3.1 次梯度:处理绝对值不可导的第一步

L1 正则化没有闭式解,主要障碍就是绝对值函数在原点的不可导性。为了处理这种目标函数,数学上引入了次梯度的概念。对一个凸函数 f(x),在 x 处的次梯度是指所有满足下面条件的 g:

f(y) ≥ f(x) + g·(y - x),对所有 y 成立

对绝对值函数 f(x) = |x| 来说,它的次梯度非常简单:

  • 当 x > 0 时,导数就是 1;
  • 当 x < 0 时,导数是 -1;
  • 当 x = 0 时,次梯度是整个区间 [-1, 1],因为任何 -1 到 1 之间的斜率都能保证下方的支撑线不穿过函数图像。

有了次梯度的概念,我们就能把“梯度等于 0”的最优性条件推广成“0 属于次梯度集合”。这是清醒认识 L1 的钥匙:很多时候,L1 的正则化解就藏在这条“从非光滑点里求次梯度包含 0”的思路上。

3.2 一步步推:软阈值公式是怎么来的

现在我们来解决一个具体且核心的问题:对于一维情形,

min_x (1/2)(x - z)² + λ|x|

这个问题的解是什么?你可以把它理解为坐标下降法里“更新某一个系数时”的核心子问题,其中 z 是当前残差方向的某个值,λ 是正则化强度。答案是软阈值算子:

x* = S(z, λ) = sign(z) · max(|z| - λ, 0)

为什么是它?我们严格推导一遍。目标函数 F(x) = (1/2)(x - z)² + λ|x| 对 x 求次梯度:

∂F(x) ∋ x - z + λ · ∂|x|

一阶最优性条件要求 0 ∈ x - z + λ·∂|x|。分三种情况讨论:

  • 如果 x > 0,那么 ∂|x| = 1,条件变成 x - z + λ = 0,解出 x = z - λ。要让 x > 0 成立,需要 z > λ,此时解是 x = z - λ;
  • 如果 x < 0,那么 ∂|x| = -1,条件变成 x - z - λ = 0,解出 x = z + λ。要让 x < 0 成立,需要 z < -λ,此时解是 x = z + λ;
  • 如果 x = 0,次梯度条件变成 0 ∈ -z + λ·[-1, 1],等价于 |z| ≤ λ,此时解就是 x = 0。

把三种情况综合在一起,正好就是软阈值公式。这个推导过程就是“软阈值算子为什么是 L1 正则化的解”的完整答案:因为把绝对值函数的次梯度纳入最优性条件之后,最优解被卡在了三个区间的交叉处,边界正好是 ±λ。

我建议你在纸上把这个过程亲手推一遍,这比看十篇博客都管用。一旦你掌握了软阈值公式,后面看坐标下降法、近端梯度法(Proximal Gradient Method)都不会觉得陌生。

3.3 坐标下降:把 Lasso 训练真正跑起来

软阈值算子最有价值的应用之一,就是用坐标下降法(Coordinate Descent)求解 Lasso。核心思路是:每次只更新一个系数,把其他系数都当作常数。这样每一轮子问题的形式正好就是上一小节的一维问题,可以直接用软阈值算子求解。

我用 numpy 写了一个最小可用的坐标下降 Lasso,目标函数取 (1/2)||Xw - y||₂² + λ||w||₁,方便对照公式:

import numpy as np def soft_threshold(z, lam): return np.sign(z) * np.maximum(np.abs(z) - lam, 0.0) def lasso_cd(X, y, lam, n_iter=1000, tol=1e-6): n_samples, n_features = X.shape w = np.zeros(n_features) for _ in range(n_iter): w_old = w.copy() for j in range(n_features): # 去掉第 j 个系数贡献后的“部分残差” residual = y - X @ w + w[j] * X[:, j] rho = X[:, j] @ residual denom = X[:, j] @ X[:, j] if denom < 1e-12: continue # 注意这里分母对应目标函数里的 (1/2)||Xw-y||^2 形式 w[j] = soft_threshold(rho / denom, lam / denom) if np.max(np.abs(w - w_old)) < tol: break return w # 造一组数据验证:前 5 个特征有真实信号,其余为噪声 np.random.seed(42) X = np.random.randn(100, 20) true_w = np.zeros(20) true_w[:5] = [1.0, -2.0, 0.5, 1.5, -1.0] y = X @ true_w + 0.1 * np.random.randn(100) w_hat = lasso_cd(X, y, lam=0.01) print(np.round(w_hat, 3))

跑完这段代码你会发现,前 5 个系数的估计值大约在真实值附近,后面 15 个系数基本会被压到 0 附近,这就是 L1 的稀疏性在实际数值算法中的直观体现。实际工程中你不会手写这个,直接用 sklearn 的 Lasso 就行,但理解内部原理和手推一遍,对你排查问题、看源码、面试答推导题都有很大帮助。

4. 正则化系数 λ 怎么选?实战调参方法

4.1 为什么 λ 不能拍脑袋定

λ 太小,惩罚形同虚设,模型该过拟合还是过拟合;λ 太大,所有参数都被压到接近 0,模型欠拟合,几乎只剩一个常数预测。关键问题是:最优 λ 的位置完全取决于数据集的大小、特征噪声程度、特征之间的相关性,不存在一个万能默认值。

最稳妥的办法是交叉验证。把训练集拆成若干折,在每一折上搜索 λ,用验证集的误差来评估不同 λ 的好坏。sklearn 里最省事的做法是用 GridSearchCV 配合一个对数均匀分布的 λ 网格。注意这里不能拍脑袋随机取几个 0.1、0.01 完事,我一般会先尝试 np.logspace(-4, 1, 60),也就是从 0.0001 到 10 之间取 60 个点,覆盖三个数量级以上,才能看到从欠拟合到过拟合的完整变化过程。

实操代码大概长这样:

from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.linear_model import Lasso from sklearn.model_selection import GridSearchCV, train_test_split from sklearn.datasets import make_regression X, y = make_regression( n_samples=200, n_features=50, n_informative=8, noise=3.0, random_state=42 ) X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.25, random_state=42 ) # 关键:标准化放在 Pipeline 里,避免交叉验证时泄露测试集信息 model = Pipeline([ ("scaler", StandardScaler()), ("lasso", Lasso(max_iter=100000)) ]) param_grid = {"lasso__alpha": np.logspace(-4, 1, 60)} gs = GridSearchCV( model, param_grid, cv=5, scoring="neg_mean_squared_error" ) gs.fit(X_train, y_train) print("best alpha:", gs.best_params_) print("test RMSE:", mean_squared_error( y_test, gs.predict(X_test), squared=False ))

这里有一个长期被忽略的细节:标准化要放进 Pipeline 里,让它在每一折交叉验证内部重新计算均值和方差,这能避免数据泄露。很多人喜欢先在外面用全量训练集 fit 一个 StandardScaler 再传给 GridSearchCV,这种做法会让验证集的均值方差被污染,导致调出来的 λ 偏乐观。

4.2 Lasso 路径图:一眼看到最优区间

除了交叉验证之外,我强烈建议大家画一张正则化路径图(Regularization Path)。它展示的是:随着 λ 从大到小变化,每个特征的系数如何从 0 开始逐步被“释放”。sklearn 提供了现成的 lasso_path 函数:

import matplotlib.pyplot as plt from sklearn.linear_model import lasso_path from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_scaled = scaler.fit_transform(X_train) alphas_lasso, coefs_lasso, _ = lasso_path( X_scaled, y_train, alphas=np.logspace(-3, 1, 100) ) plt.figure(figsize=(8, 5)) plt.plot(np.log10(alphas_lasso), coefs_lasso.T, linewidth=1) plt.axvline(np.log10(gs.best_params_["lasso__alpha"]), color="black", linestyle="--") plt.xlabel("log10(alpha)") plt.ylabel("coefficients") plt.title("Lasso regularization path") plt.show()

这张图的价值在于,你能直观地看到“哪些系数在比较小的惩罚下就能被激活”“哪些特征直到 λ 很大时还在顽强存在”。实际项目里,我经常在画完这张图之后,把最终选出来的 λ 往路径图右端靠一点点,让模型更简单;如果测试集指标掉得不多,我会选择更保守的模型。这个习惯帮我省掉了很多线上调参的麻烦。

4.3 忘了标准化,正则化就白做了

这个坑我几乎每次带人调参都会遇到:在特征量纲差异很大的情况下直接加上 L1/L2。比如一个特征的范围是 0~1,另一个特征的范围是 0~100000,λ 惩罚的是参数的绝对值大小,模型为了减小损失,只能优先压缩大尺度特征对应的参数,实际上对小尺度特征几乎没有惩罚效果,等于整个正则化被量纲带偏了。

正确做法是先把每个特征做标准化(z-score 或 min-max 都行),再考虑正则化。这一点对 L1 尤其重要,因为 L1 的稀疏选择完全依赖惩罚的公平性,如果某个特征因为量纲大而被“提前惩罚”清掉了,这个特征本身可能是有用的,这就是典型的错误特征选择。

5. 进阶玩法:从弹性网到深度学习的正则化家族

5.1 弹性网:Lasso 遇到强共线性时的救星

Lasso 在特征之间高度相关时有一个明显的毛病:它会随机挑其中一个特征保留,剩下相关的特征全部清零。这就导致模型选出来的特征缺乏稳定性,换一批训练数据,选中的特征可能就完全变了。解决办法非常直接,把 L1 和 L2 的惩罚拼在一起用,这就是弹性网(Elastic Net):

min_w (1/2)||Xw - y||₂² + λ₁||w||₁ + (λ₂/2)||w||₂²

L1 负责稀疏选择,L2 负责把相关特征组成的小组一起收缩,而不是单个孤立地选一个。实际项目里,如果特征数量中等、相关性又复杂,我经常直接用 sklearn 的 ElasticNet,并让 l1_ratio 在 [0.1, 0.5, 0.7, 0.9] 之间搜一圈,而不是直接赌 L1 或 L2。

5.2 深度学习里的正则化家族

正则化在深度学习里不只有 weight decay 这一种形态,我梳理一下平时最常用的几类:

  • 权重衰减(Weight Decay):在梯度更新时直接把当前权重往 0 方向衰减,数学上在 SGD 下等价于 L2 正则化。但要注意,在使用 Adam 等自适应学习率优化器时,L2 正则化和 weight decay 不再严格等价,这也是近年 AdamW 被越来越多人使用的原因——AdamW 把权重衰减从梯度里单独拆出来应用,效果更稳定。
  • Dropout:训练时随机禁用一部分神经元,迫使网络不能过分依赖某几个节点。它的原理可以理解为在训练过程中隐式地集成了大量子网络。
  • Early Stopping:提前终止训练,防止模型在验证集误差不降反升后继续训练。
  • 数据增强:对输入做合法的变换(比如图像旋转、裁剪、加噪声),让模型见过的分布更广,本质上也是一种正则化。
  • Batch Normalization:主要目的不是正则化,但它会引入一层对中间输出的归一化约束,实测中常常带来轻微的正则化效果,所以不少框架里 BN 和 dropblock 等配合使用效果更好。

这些算子的共同逻辑都指向同一个方向:让模型不要过度地把权重分配到少数路径上,提高对输入扰动的鲁棒性。

5.3 面试和考试里会加分的“冷门”正则化方向

如果只是应付普通项目,上面这些已经够用了;但如果想往深走,还有两个方向值得了解一下。一个是半监督学习里的一致性正则化(Consistency Regularization),核心思想是:同一个输入做不同扰动,模型对它的预测要尽量一致,通过这种方式利用无标签数据来平滑决策边界。另一个是频域/傅里叶正则化,主要出现在图像超分辨率等任务里,通过在频域上约束高频分量的幅度或相位,抑制模型输出中出现伪影和过度锐化的问题。这些属于近几年的研究热点,面试时偶尔聊到会显得你视野比较宽。

6. 常见问题与排查技巧实录

6.1 高频问题速查表

现象可能原因处理建议
L1 一跑系数全变 0,测试集比 Ridge 还差λ 定得太大看正则化路径图,把 λ 往小调;改用 ElasticNet
加了正则化,测试集几乎没变化λ 太小,惩罚被忽略把 λ 上调一两个数量级,观察验证集误差变化
特征强相关时,Lasso 同一数据集两次训练选中特征不同L1 对成组相关特征只随机保留一个换 ElasticNet;或者先做相关性聚类再选特征
神经网络里 weight decay 加了效果反而差用 Adam 时 L2 与 weight decay 不等价换 AdamW;或者改用 SGD+weight decay
交叉验证每次选出的最优 λ 都不一样数据量少,CV 划分很不稳定增大 cv 折数,做重复交叉验证,固定随机种子

6.2 实操踩坑记录

说一个我上个月刚处理过的案例。有个师弟在做回归预测项目,特征有 80 多个,样本只有 200 条。他一开始用 Lasso,先把 α 设成 0.1,结果训练集上表现很差,他以为模型不行,换了 XGBoost 也没救回来。我让他先画路径图,一眼就看到 α=0.1 对应路径最右侧,几乎所有系数都被清零了,模型基本退化成均值预测。把 α 调到 0.001 附近之后,训练集和测试集都恢复正常。这个案例里问题不在模型,而在于正则化过强导致欠拟合,只盯着训练集上的指标看根本定位不到原因。

还有一个高频坑:加了 L1 之后训练不收敛。很多人在 sklearn 里不管 max_iter 默认值,遇到警告也不看,最后模型学出一堆非零系数。经验是先调大 max_iter,再在路径图上验证是否真的到达收敛区域。如果数据量很大但特征稀疏,配合使用稀疏矩阵存储能大幅提速。

最后再分享一个小技巧:如果正则化之后的模型质量仍然不理想,不要只盯着 λ 调。先检查特征有没有标准化、有没有离群值、有没有缺失值填充错误。正则化是锦上添花,不是雪中送炭。数据质量不过关,任何正则化算子都救不回来。这是我带过这么多项目之后最想强调的一点——先把数据基础打扎实,再来谈正则化的精细调节。

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

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

立即咨询