
训练集上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 正则化对应的经典模型是 LassoLeast 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||₁ ≤ tL2 正则min (1/2)||Xw - y||₂², s.t. ||w||₂² ≤ t也就是说模型被限定在参数空间的一个区域内L1 对应的是菱形高维下是超多面体L2 对应的是一个圆形高维下是超球体。无约束时最优的 w 很可能落在区域外面现在只能沿着损失函数等高线和约束区域的边界相切的位置取解。关键点在于菱形的角落在坐标轴上。当损失函数的等高线碰到菱形的顶点时切点处的某个坐标天然就是 0而圆形边界处处光滑等高线与其相切的位置通常不会精确落在坐标轴上所以 L2 的解很少出现严格的 0。这就是几何层面“为什么 L1 产生稀疏解、L2 不产生”的答案。两个模型选哪个取决于业务目标。如果特征动辄几百上千维并且你怀疑里面大部分特征只是噪声那 L1 能帮你把模型砍到只剩骨干特征后续部署也更快。但如果特征本身都是经过筛选的有效变量彼此之间还有较强的相关性L1 反而可能因为“只能随机选一个”而导致结果不稳定这时优先选 L2 或者下一章会讲的弹性网Elastic Net。为了对照方便我把几个主要差异整理成了表格维度L2 正则化RidgeL1 正则化Lasso惩罚形式λ||w||₂²λ||w||₁解的特点系数等比收缩不归零系数可被精确压成 0约束区域圆形超球体菱形超多面体可微性处处可微原点不可微需要次梯度/近端方法特征选择能力无有共线性鲁棒性较好较差相关特征中只能随机挑一个闭式解有一般没有3. 软阈值算子为什么是 L1 正则化的解3.1 次梯度处理绝对值不可导的第一步L1 正则化没有闭式解主要障碍就是绝对值函数在原点的不可导性。为了处理这种目标函数数学上引入了次梯度的概念。对一个凸函数 f(x)在 x 处的次梯度是指所有满足下面条件的 gf(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_iter1000, tol1e-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, lam0.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_samples200, n_features50, n_informative8, noise3.0, random_state42 ) X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.25, random_state42 ) # 关键标准化放在 Pipeline 里避免交叉验证时泄露测试集信息 model Pipeline([ (scaler, StandardScaler()), (lasso, Lasso(max_iter100000)) ]) param_grid {lasso__alpha: np.logspace(-4, 1, 60)} gs GridSearchCV( model, param_grid, cv5, scoringneg_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), squaredFalse ))这里有一个长期被忽略的细节标准化要放进 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, alphasnp.logspace(-3, 1, 100) ) plt.figure(figsize(8, 5)) plt.plot(np.log10(alphas_lasso), coefs_lasso.T, linewidth1) plt.axvline(np.log10(gs.best_params_[lasso__alpha]), colorblack, 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 Netmin_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或者改用 SGDweight decay交叉验证每次选出的最优 λ 都不一样数据量少CV 划分很不稳定增大 cv 折数做重复交叉验证固定随机种子6.2 实操踩坑记录说一个我上个月刚处理过的案例。有个师弟在做回归预测项目特征有 80 多个样本只有 200 条。他一开始用 Lasso先把 α 设成 0.1结果训练集上表现很差他以为模型不行换了 XGBoost 也没救回来。我让他先画路径图一眼就看到 α0.1 对应路径最右侧几乎所有系数都被清零了模型基本退化成均值预测。把 α 调到 0.001 附近之后训练集和测试集都恢复正常。这个案例里问题不在模型而在于正则化过强导致欠拟合只盯着训练集上的指标看根本定位不到原因。还有一个高频坑加了 L1 之后训练不收敛。很多人在 sklearn 里不管 max_iter 默认值遇到警告也不看最后模型学出一堆非零系数。经验是先调大 max_iter再在路径图上验证是否真的到达收敛区域。如果数据量很大但特征稀疏配合使用稀疏矩阵存储能大幅提速。最后再分享一个小技巧如果正则化之后的模型质量仍然不理想不要只盯着 λ 调。先检查特征有没有标准化、有没有离群值、有没有缺失值填充错误。正则化是锦上添花不是雪中送炭。数据质量不过关任何正则化算子都救不回来。这是我带过这么多项目之后最想强调的一点——先把数据基础打扎实再来谈正则化的精细调节。