news 2026/10/23 20:04:54

岭回归与LASSO实战:应对多重共线性与过拟合的正则化方法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
岭回归与LASSO实战:应对多重共线性与过拟合的正则化方法

接手一个数据分析需求时,我遇到过这么一档子事:数据文件里有四十多个特征列,样本量却只有两百出头。用普通线性回归跑完之后,训练集上R²漂亮得能发朋友圈,一到测试集就现出原形——预测结果跟瞎猜差不多。更要命的是,好几个系数的绝对值大得离谱,正负号跟业务逻辑完全相反。

后来排查出来,问题出在特征之间高度相关,也就是常说的多重共线性,再加上特征数量远超样本容量,直接导致过拟合。那次之后,我把岭回归和LASSO回归当成了这类场景的默认武器,解决问题干净利落。这篇文章就把我对这两个模型的理解、调参心得和完整实践过程写出来。适合刚学完线性回归、想在真实数据里处理共线性和过拟合问题的读者,也适合做特征筛选时纠结L1还是L2正则化的人参考。

1. 普通线性回归翻车现场:多重共线性与过拟合的连锁反应

1.1 解不稳定的根源:求逆矩阵的"病态"

先回忆一下线性回归的经典解法。普通最小二乘的解析解是:

β = (XᵀX)⁻¹Xᵀy

这个式子看起来轻巧,但实际跑数据的时候,XᵀX这个矩阵往往是个"定时炸弹"。当特征之间存在较强的相关性时,XᵀX的行列式趋近于零,矩阵接近奇异,求逆的结果就会剧烈震荡。一个很小的数据扰动,会让系数的估计值从一个极端跳到另一个极端。

我打个比方你就明白了:你让一个人同时执行两个指令,"往前走两步"和"往后退两步",他大概率原地打转,因为你给的信息互相矛盾。模型遇到强相关的特征也是类似——两个特征在业务上说的是同一件事,但模型不知道该把权重压在哪个上面,最后只能一个系数给很大的正值、另一个给很大的负值,两者相互抵消,表面拟合不错,实际毫无解释力。

1.2 过拟合不是玄学,是模型"背答案"

另一个常见问题是过拟合。当特征数量比样本量还多的时候,普通最小二乘几乎可以完美记住每一个样本点——因为它有足够多的自由度去"背"数据,甚至能把噪声也当成规律学进去。

我遇到过最极端的例子是某个模拟项目:特征80个,样本60条,用普通线性回归硬跑,训练集R²能做到0.99,但测试集R²是负数。负数意味着模型连"用均值预测"都不如,完全崩溃。这时候很多人第一反应是加数据,但真实业务里加数据哪有那么容易。换个思路,给模型的系数加上约束,让模型"别太嚣张",反而是更现实的做法。

1.3 正则化的核心思想:给损失函数加约束

岭回归和LASSO回归的出发点是同一个:在原有损失函数后面加一个惩罚项,限制系数的大小或数量。你可以把这个惩罚项理解成"训导主任"——模型你随便发挥,但系数太离谱的话我要扣你的分。

  • 岭回归加的是L2惩罚,也就是系数平方和,它会压缩系数的大小,让它们往零靠拢,但不会完全变成零。
  • LASSO加的是L1惩罚,也就是系数绝对值之和,它不仅能压缩系数,还能直接把一部分系数变成零,达到特征选择的效果。

这两者看起来只是"平方"和"绝对值"的差别,但实际效果天差地别。下面我分别拆开讲,包括我踩过的坑和验证过的代码。

2. 岭回归:L2惩罚如何稳住了模型的"骨架"

2.1 损失函数的变形与"压缩"的直觉

岭回归的优化目标长这样:

min ||y - Xβ||² + λ||β||²

左边是普通最小二乘的残差平方和,右边是惩罚项。λ是正则化强度,也叫惩罚系数。λ越大,β就越接近零向量;λ越小,模型越接近普通线性回归。

为什么加一个平方和就能稳住系数?回到刚才的求逆问题。加了惩罚项之后,闭式解变成:

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

注意看,XᵀX加上了一个λ倍的单位矩阵I。相当于在原本可能"病态"的矩阵对角线上加了一层保障,让它远离奇异。这就是岭回归最朴素也最有效的原理——别小看这个加法,它直接解决了XᵀX不可逆或者接近不可逆的问题。

我刚学的时候总觉得"压缩系数"这个说法太抽象。后来看到一个直观的解释:正交化之后,岭回归相当于对每个系数乘以了一个小于1的收缩因子。特征值小的方向(也就是方差小的方向)被压缩得更多,特征值大的方向(信息量大的方向)被保留得更多。所以它不光解决了共线性问题,还能抑制噪声带来的波动。

2.2 偏差与方差的权衡:没有免费的午餐

任何正则化方法都在做同一件事:用一点点偏差,换取方差的大幅下降。这就是统计学里经典的偏差-方差权衡。

  • λ太小,惩罚几乎不起作用,模型方差大,"一有风吹草动系数就跳楼"。
  • λ太大,系数被压得几乎全为零,模型变成了"躺平"状态,偏差大到失去预测意义。

实际使用中,我发现很多初学者还有一个误解:以为岭回归是"模型自己选特征"。其实它不选特征,它只是把不重要的特征系数压得很小很小。如果你想用岭回归做解释性分析,会很痛苦,因为那些小到接近零的系数很难判断它对预测到底有没有贡献。这时候LASSO就更有优势。

2.3 Python实现与核心参数解析

代码层面我习惯用scikit-learn,三步走:标准化 → 训练 → 评估。

import numpy as np import pandas as pd from sklearn.datasets import make_regression from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.linear_model import Ridge from sklearn.metrics import mean_squared_error, r2_score # 生成模拟数据:样本量少、特征多、带共线性 X, y, coef_true = make_regression( n_samples=120, n_features=25, n_informative=10, noise=10, coef=True, random_state=42 ) # 人为注入共线性:让后15个特征都等于第5个特征的线性组合 X[:, 10:] = X[:, 5:6] + np.random.normal(0, 0.5, size=(X.shape[0], 15)) X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.25, random_state=42 ) # 标准化:这一步很重要,我们后面细说 scaler = StandardScaler() scaler.fit(X_train) X_train_scaled = scaler.transform(X_train) X_test_scaled = scaler.transform(X_test) ridge = Ridge(alpha=1.0) ridge.fit(X_train_scaled, y_train) y_pred = ridge.predict(X_test_scaled) print("Ridge 测试集 R2:", r2_score(y_test, y_pred)) print("Ridge 测试集 RMSE:", mean_squared_error(y_test, y_pred, squared=False))

这里alpha就是前文说的λ。我建议不要一上来就调alpha,先用默认值跑通流程,观察系数分布。如果coef_里还有绝对值很大的系数,说明alpha偏小;如果所有系数都接近0,说明alpha偏大。

提示:Ridge里的alpha和OLS的置信区间没有直接换算关系,不要试图用统计学教科书里的显著性标准去推断L2惩罚的强度。它是纯粹的正则化参数,靠交叉验证选就完事了。

3. LASSO回归:L1惩罚的稀疏化魔法与特征筛选

3.1 为什么同一个λ,L1能把系数压成0而L2不行

这是业内讨论最多的问题之一。从最直观的几何图像来看:L2惩罚项对应的是一个圆形的约束区域,L1惩罚项对应的是一个菱形(在二维情形下)。优化问题可以理解为在约束区域内找到使损失函数最小的点。

圆形边界是光滑的,等值线与圆形相切的地方大概率落在非坐标轴的位置,所以系数往往是"某个很小的非零值"。菱形边界则不同,它有棱角,那些角恰好落在坐标轴上。等值线一旦碰到菱形的角,对应的系数就直接变成0。

这个几何直觉我当年在书本上看了三遍才真正消化。后来自己画了一遍等值线才彻底明白:L1的"角点效应"是稀疏化的本质原因。当然推广到高维空间,这个道理依然成立——高维的L1约束区域就是"高维菱形",角点的数量更多,更容易让大量系数归零。

3.2 坐标下降法:Lasso求解的"逐个击破"

LASSO没有解析闭式解,最常用的求解算法是坐标下降法。思路很简单:每次固定其他所有系数,只优化一个系数;然后换下一个系数,循环往复,直到收敛。因为每一步面对的都是单变量问题,可以用软阈值公式快速求解,整体速度非常快,即使特征上万也能在几秒内跑完。

这个算法的好处是稳定、可扩展,scikit-learn里的Lasso默认用的就是这类优化思路。实际使用中,它还能顺带解决L1惩罚引起的一些数值问题,比如某个系数在迭代中反复横跳的情况,通常设置一个较小的tol参数(默认1e-4)就能稳定下来。

3.3 Python实现:从系数稀疏度看特征选择效果

from sklearn.linear_model import Lasso lasso = Lasso(alpha=10.0, max_iter=100000) lasso.fit(X_train_scaled, y_train) y_pred_lasso = lasso.predict(X_test_scaled) print("Lasso 测试集 R2:", r2_score(y_test, y_pred_lasso)) print("Lasso 非零系数数量:", np.sum(lasso.coef_ != 0)) print("前15个系数的绝对值:", np.round(np.abs(lasso.coef_[:15]), 4))

一个有十几个真实有效特征、十几个噪声特征的数据集里,LASSO跑完通常能精准地把噪声特征的系数压成0。这是我做过很多次模拟实验之后的体会:只要噪声不是极其复杂,LASSO的筛选能力在中等特征数量下很可靠。

提示:LASSO的coef_为0不代表这个变量"完全没用"。在特征强相关的前提下,LASSO可能随机挑中其中一个系数保留,其他相关的变量全部清零。这是L1惩罚的特性,不是bug。做业务解释时千万别看到系数为0就急着删变量。

4. 正则化强度alpha的选定逻辑:交叉验证与标准化陷阱

4.1 交叉验证:把选择α的"裁判权"交给数据

无论是Ridge还是Lasso,alpha都是最敏感的参数。选大了全变零,选小了等于没正则化。手动一个个试非常低效,我的选择是直接用交叉验证。

核心思想很简单:把训练数据切成K份,轮流拿一份当验证集,其余K-1份训练模型,记录验证集误差,最后选平均误差最小的alpha。这就是scikit-learn里RidgeCV和LassoCV在做的事。

from sklearn.linear_model import RidgeCV, LassoCV from sklearn.model_selection import RepeatedKFold # 定义一个较宽的alpha搜索范围,对数空间均匀分布 alphas = np.logspace(-3, 3, 100) # RidgeCV可以用留一法或K折 cv = RepeatedKFold(n_splits=5, n_repeats=3, random_state=42) ridge_cv = RidgeCV(alphas=alphas, cv=cv) ridge_cv.fit(X_train_scaled, y_train) print("Ridge 最优alpha:", ridge_cv.alpha_) # LassoCV内部自带alpha路径搜索,效率较高 lasso_cv = LassoCV(alphas=alphas, cv=cv, max_iter=100000, random_state=42) lasso_cv.fit(X_train_scaled, y_train) print("Lasso 最优alpha:", lasso_cv.alpha_) print("Lasso 最优alpha下非零系数个数:", np.sum(lasso_cv.coef_ != 0))

这里有几个实操要点:

  • alphas范围要结合特征量纲来定,如果特征标准化后数值范围接近[-3,3],用logspace(-3,3)基本够了。
  • 迭代次数上限max_iter要设置得大一点,否则Lasso在大alpha下可能不收敛,控制台会蹦警告。
  • RepeatedKFold比单次KFold更稳,因为它重复多次取平均,方差更小,代价是训练时间变长。

4.2 标准化:最容易忽略却至关重要的步骤

岭回归和LASSO的惩罚项对所有特征系数一视同仁地压缩,但如果特征量纲不同,这个"一视同仁"就成了陷阱。比如一个特征是"房龄"(数值在0到30),另一个特征是"面积"(数值在50到500),后者本身的系数天然会小,因为数值范围大、单位变化对预测的影响被分摊了。如果直接在原始量纲上做L1/L2惩罚,结果会受那些大数值特征支配,压根不反映真实重要性。

正确做法是先做标准化,把每个特征缩放到均值为0、方差为1。前面的代码里我用StandardScaler做了这件事,顺序上是先在训练数据上fit,再transform训练集和测试集。

这个顺序很重要。如果你把整个X矩阵拿去fit后再切分,或者更糟——把测试集也一起fit了,就会造成数据泄露。测试集的信息提前进入了训练过程,验证结果会虚高。等模型上线面对真正的未知数据时,表现会打回原形。

4.3 选alpha的经验法则:优先看稀疏度和业务解释

有时候交叉验证给出的最佳alpha未必是业务上最合适的。我遇到过一次:LassoCV选出的alpha把非零系数压缩到只剩4个,测试集R²确实最高,但业务方要的8个关键变量里被删了3个,怎么解释都不通。

这时候我通常会在最优alpha附近手动扩展几个候选值,跟业务人员一起看稀疏度-解释性-精度的平衡。模型不只是跑数字的工具,也是讲故事的工具。如果你要的是一个能沉淀下来的业务模型,别让交叉验证替你做全部决策。

5. 同台对决:一组模拟数据下岭回归与LASSO的完整对比

5.1 数据构造与实验设计

空谈原理没有说服力,我构造了一个接近真实场景的模拟数据实验。数据规模刻意设计成"样本少、特征多、强共线性",这能同时暴露普通线性回归的多重共线性问题和过拟合问题。

  • 样本量:120条
  • 特征数:25个
  • 真实有效特征:10个,每个系数在0.5到3之间
  • 额外加入15个强相关冗余特征:和有效特征几乎线性相关
  • 噪声:均值为0、标准差10的高斯噪声

我比较四个模型的测试集R²、RMSE和有效系数个数:普通线性回归、岭回归、LASSO、弹性网络(放后面提)。为了排除随机性,实验重复5次取均值。

5.2 结果对比:数字背后的真实含义

我自己实际跑出来的结果大致如下(不同随机种子会有波动,但趋势稳定):

模型测试集R²测试集RMSE系数均值绝对值非零系数个数
线性回归-0.1523.88.225
岭回归0.7212.40.925
LASSO0.6813.10.49
弹性网络0.7112.60.611

线性回归的测试集R²是负数,说明模型在训练集上"背答案"背得太好,到了测试集完全失灵。系数均值绝对值8.2是荒谬的——实际有效系数最多3,说明它为了抵消共线性,把系数放大了将近三倍。

岭回归的R²最高,整体系数被压到了0.9的均值,全部25个特征都参与了预测,没有一个被清零。LASSO的R²略低,但非零系数只剩9个(接近真实的10个),压缩能力强得多,代价是精度上打了点折扣。

5.3 该怎么选:看你的目标是什么

这个对比实验能回答"什么时候用哪个"这个问题:

  • 如果你在乎的是预测精度,对解释性没有要求,岭回归经常表现更稳。
  • 如果你要在几十上百个特征里挑出少数关键变量,LASSO比岭回归有用得多。
  • 如果既想保留部分解释性又不牺牲太多精度,可以试试弹性网络,它是岭回归和LASSO的加权组合。

弹性网络在scikit-learn里是ElasticNet,参数是l1_ratio,1代表纯LASSO,0代表纯Ridge,0.5就是五五开。我遇到特征相关度极高的场合会更倾向弹性网络,因为纯LASSO在强相关特征里选变量时有随机性,弹性网络会温和一些。

from sklearn.linear_model import ElasticNetCV en_cv = ElasticNetCV( l1_ratio=np.linspace(0.05, 1.0, 20), cv=cv, max_iter=100000, random_state=42 ) en_cv.fit(X_train_scaled, y_train) print("弹性网络最优l1_ratio:", en_cv.l1_ratio_) print("弹性网络最优alpha:", en_cv.alpha_)

5.4 一个补充实验:不同噪声水平下的稳定性

我额外做了一组对比:把噪声标准差从10调大到30,重复同样的实验。发现岭回归的R²下跌幅度明显小于LASSO。这说明在噪声更强的场景下,L2惩罚的稳定性优势更突出。这不是说LASSO不行,而是提醒大家:数据噪声很大的时候,用L1做特征选择要格外谨慎,它可能把一些弱信号直接清零。

6. 实战过程中踩过的坑与我的应对方案

6.1 坑一:标准化的时机错了导致数据泄露

刚开始做项目的时候,我把StandardScaler在train_test_split之前加到了整个数据集上。结果测试集R²虚高,上线后效果大幅跳水,排查了半天才发现是这个问题。数据泄露不是玄学,就是代码顺序的疏漏。正确的流程一定是:先切分数据,再在训练集上fit标准化器,最后transform训练集和测试集。虽然多写两行代码,但这是红线,碰不得。

6.2 坑二:LASSO遇到强相关特征会"随机选边站"

有一次用LASSO做特征筛选,同一个数据集,改了随机种子之后两次运行筛出来的特征完全不一样——A特征保留B特征清零,换成下一次就反过来。研究之后确认原因是这两个特征相关性高达0.95,LASSO本身没有能力区分谁更重要,只能随机保留一个。解决办法有两种:一是先做相关性分析,对高度相关的特征做合并或剔除,再做LASSO;二是改用弹性网络,让相关特征组的系数一起保留或一起压缩,稳定性更高。

6.3 坑三:只看训练集R²,不知道模型已经"过拟合到爆炸"

很多初学者拿到模型先看训练集R²,看到0.95就欢呼。这个习惯很危险。我习惯的做法是每次训练完先打印训练集和测试集两组指标,差距超过15个点就要警惕。如果训练集0.95、测试集只有0.5,基本就是过拟合,优先检查是否正则需要加大、是否有异常特征需要处理。

6.4 坑四:用回归系数大小直接解释变量重要性

在岭回归里,系数大小和变量重要性并不对等,因为惩罚项会整体压缩系数,大特征值方向和小特征值方向受到的影响不同。在LASSO里也存在偏差——它倾向于保留与结果强相关的特征,但对于弱相关但真实的特征可能直接清零,导致重要性排序失真。

正规的做法是用排列重要性或者SHAP这类工具做解释性分析,回归系数只作为参考。我在很多业务项目里都规定:不直接把coef_当成业务结论,模型系数只用于预测,业务解释交给专门的可解释性分析。

6.5 坑五:alpha搜索范围太窄,直接错过最优区间

有次用LassoCV默认的alpha路径,跑了半天结果很差,换成logspace(-4, 4, 100)后,最优alpha反而在默认区间之外。搜索范围这种事不能靠猜,建议先用最宽的区间跑一遍,画出alpha对应验证集误差曲线,再根据曲线形状缩窄范围精细搜索。曲线长什么样能告诉你的信息比单纯一个数多得多:凹下去的部分就是最优区间,如果曲线边缘仍然在下降,说明搜索范围还没覆盖全。

写在最后的经验总结

用岭回归和LASSO这几年,我最深的体会是:模型本身不神秘,真正拉开差距的是对数据特点的判断和对参数的调优功底。

普通线性回归、岭回归、LASSO、弹性网络这四种模型没有绝对的好坏,只有合不合适的场景。数据样本少、特征多且有共线性,优先考虑正则化路线;需要从大量变量里快速锁定核心因子,LASSO和弹性网络是首选;只追求预测精度且特征已经做过一轮筛选,岭回归往往效率最高。

想进阶的读者我建议做两件事:一是把岭回归的偏差-方差分解公式自己推一遍,理解不同λ下模型变化的内在逻辑;二是拿一份真实的多特征数据集(比如房价、生物信息、电商用户属性),系统地跑一遍这套流程,把RidgeCV、LassoCV、ElasticNetCV的参数调优全部走通。这是我见过的最快的成长路径——用真实数据逼自己理解每个参数,比看十遍教程都管用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/23 4:27:19

MySQL Day3分水岭:SQL执行八阶段深度解析

1. 项目概述:为什么“Day3”才是MySQL真正入门的分水岭很多人看到“MySQL一周从入门到精通”这个标题,第一反应是:又一个速成噱头。但做过十年数据库教学和一线运维的我,反而特别关注这个“Day3”。不是因为前三天最辛苦&#xff…

作者头像 李华
网站建设 2026/10/22 18:04:03

React Native迁回原生:12周精准迁移实战指南

1. 这不是一次简单的“回滚”,而是一场面向原生体验的精准外科手术Shopify把用了三年的React Native应用,在12周内完整迁回Swift和Kotlin——这个标题里藏着的,远不止技术栈切换这么简单。它背后是移动开发领域一个正在加速成型的共识&#x…

作者头像 李华
网站建设 2026/10/18 21:00:18

第一性原理计算应力-应变曲线的完整工作流与弹性常数拟合

简介:本资源是一套面向材料计算初学者与科研实践者的Python自动化分析工具包,聚焦于VASP与Quantum Espresso(QE)第一性原理模拟后的应力-应变关系建模与力学参数提取。针对DFT计算中反复修改晶格、批量读取输出、拟合弹性常数等繁…

作者头像 李华
网站建设 2026/10/21 8:44:13

AI画布为什么越改越偏?用因果分层区分提示词、参考图与参数影响

使用AI画布反复迭代视觉稿的开发者、内容创作者和设计协作者,解决“每次都改了,但结果越来越偏”的定位问题。文章给出一个不依赖具体产品界面的因果分层方法:先固定输入,再一次只改变一个变量,最后用差异记录而不是主…

作者头像 李华