☰
正则化回归Python实战:从Lasso到分布式ADMM全解析
2026/10/1 12:19:15 网站建设 项目流程

简介:正则化回归的Python算法源码包,面向机器学习与数据科学初学者及需要处理高维数据的分析人员,围绕L1(Lasso)与L2(Ridge)正则化,系统展示防过拟合、特征选择及模型调参的完整代码实践。资源共168个文件,包含110个py脚本、34个rst说明文档、8个ipynb交互式笔记本,以及pyx扩展、配置文件等,压缩包仅1.28MB,轻量但结构完整。已有302人学习下载,适合快速上手与对照练习。压缩包内代码示例覆盖LASSO demo、Fused LASSO、Group LASSO、逻辑回归、分布式LASSO(ADMM)等场景,并配有安装说明与目录文档,可直接在Jupyter中运行观察效果。通过阅读这些代码,可理解不同正则化项的数学作用、alpha参数对稀疏性的影响,以及交叉验证搜索最佳超参数的实现思路,为后续在实际项目中灵活选用正则化方法打下扎实基础。

1. 正则化回归的 Python 算法打包:从 Lasso 到分布式 ADMM 的一次性资源

做回归模型的同学应该都有过这种经历:训练集上 R² 飘到 0.95,测试集直接崩到 0.6,典型的过拟合。正则化回归就是干这个的——通过在损失函数里加惩罚项,把模型参数按到合理范围,顺带把不重要的特征权重压成零。这份名为 regreg 的 Python 源码包,把 Lasso、Group Lasso、Fused Lasso 和分布式 ADMM 求解器全部打包进了 Jupyter Notebook,不是那种贴一个 sklearn 调用的敷衍示例,而是带完整推导和对比实验的工程实现。适合两类人:正在做特征选择的数据分析师,和需要在大规模数据上用分布式优化求解稀疏模型的算法工程师。下载后按 INSTALL 装依赖,逐个 notebook 跑,基本能把正则化回归的全流程摸透。

2. 算法矩阵拆解:六个 Notebook 分别解决什么

2.1 目录拆解:哪些文件是你真正要跑的

拿到压缩包解压后,第一眼看到 regreg.css、.gitignore、layout.html 这些文件有点懵。其实 css 和 layout.html 是文档页面的样式和模板,对跑通实验没有直接影响;.gitignore 是版本控制辅助文件;真正有价值的是 INSTALL 和那六个 .ipynb 文件。

INSTALL 文件写的是依赖安装方式,常见内容是 pip install numpy scipy scikit-learn matplotlib,以及通过源码安装 regreg 的方式。老版本 regreg 需要从 GitHub clone 下来之后执行 python setup.py install,新版本直接 pip 就能装。装之前建议先建一个干净的虚拟环境,避免和系统 Python 的包冲突。这一步值得花半小时,后面跑分布式 notebook 时如果出现 OpenMP 相关的报错,多半是环境里 numpy 和 scipy 的二进制不兼容导致。

这六个 Notebook 按功能可以分成三组:

Notebook核心算法适用场景
LASSO demo.ipynbL1 正则化入门、特征选择
Fused LASSO.ipynb融合惩罚突变检测、分段常数信号
Group LASSO.ipynb分组惩罚组特征、基因通路
Newsgroup logistic regression.ipynbL1 逻辑回归高维文本分类
Distributed LASSO: ADMM one value.ipynbADMM 单步演示理解迭代原理
Distributed LASSO: ADMM.ipynbADMM 分布式优化大规模稀疏求解

我的建议是从 LASSO demo.ipynb 开始跑,确认环境没问题后再按分组深入。不要一上来就碰分布式 notebook,ADMM 的收敛判据和 rho 参数调起来相当费时间,新手容易在这里卡住。如果你已经有 sklearn 的使用基础,从 Fused LASSO 和 Group LASSO 开始也完全可以,这两个 notebook 会让你看到 sklearn 没覆盖的惩罚项长什么样。

2.2 Lasso 与 Group Lasso:惩罚项的结构差异

正则化回归的核心差异不在损失函数,而在惩罚项的结构。Lasso 用的是 L1 范数,惩罚项是模型参数绝对值之和,求解结果是大部分系数精确等于零,做的是特征级别筛选。Group Lasso 把特征预先分成若干组,惩罚项变成各组系数的 L2 范数之和,结果是整组系数一起被选中或被压零,做的是组级别筛选。

用公式表达更直观。设损失函数为 L(w),Lasso 和 Group Lasso 的目标函数分别是:

Lasso: min L(w) + λ * Σ|w_j| Group Lasso: min L(w) + λ * Σ_g ||w_g||_2

下标 g 代表一个特征组,||w_g||_2 是组内系数的欧氏范数。做基因表达分析时,同一个通路的基因天然是一组,用 Group Lasso 比用 Lasso 更贴合业务逻辑。这个结构差异直接影响求解器的选择——Lasso 的惩罚项可分离,每个坐标方向可以独立求近端映射,坐标下降法收敛快;Group Lasso 的惩罚项在组内不可分离,要用块坐标下降或近端梯度法。

还有个经常被忽视的中间地带是 ElasticNet,它在惩罚项里同时放 L1 和 L2,公式是 λ1 * Σ|w_j| + λ2 * Σ w_j²。它的行为介于 Lasso 和 Ridge 之间:保留稀疏性的同时,对已选中的特征组做收缩,解决 Lasso 在特征高度相关时会随机丢弃其一的问题。这份源码包里没有单列 ElasticNet notebook,但理解了 Lasso 和 Group Lasso 的结构,再看 ElasticNet 就很容易。

regreg 库的通用做法是用 simple_problem 把损失函数和惩罚项组合起来。损失函数继承自平滑函数类,惩罚项继承自原子类(atom),求解器在后台自动匹配近端算子。你不需要手动推导每个惩罚项的近端映射,但理解惩罚结构仍然必要,它决定了你该选哪个 solver,以及在数据规模变大时该换哪种优化策略。

2.3 ADMM 求解器:分布式 Lasso 的迭代逻辑

ADMM(交替方向乘子法)是处理分布式正则化回归的常用手段。核心思路是引入辅助变量 z,把原来只含 w 的优化问题拆成两个子问题:一个更新 w,一个更新 z,再用拉格朗日乘子 u 把两者拉近。每轮迭代先在本地节点计算 w,再汇总更新 z,这样特征矩阵太大时可以把样本拆到不同机器上算。

两个分布式 Lasso notebook 的区别在于:one value 版本只做了一次 ADMM 迭代,适合理解原理,你能看到每个变量在单步内怎么被更新的;完整版本包含迭代循环和收敛判断,适合实际使用。实际工作中我一般把 ADMM 的 rho 设为 1.0 到 1.5,然后用原始残差和对偶残差双重判据控制迭代次数。rho 太小时迭代慢,rho 太大时容易震荡,调这个参数基本靠看残差曲线,多跑几个值对比才有感觉。

ADMM 的收敛行为值得多看几遍 notebook。你会发现原始残差和对偶残差的下降速度不一样,对偶残差往往慢一些。如果只看原始残差就提前终止,解会偏向某个方向,和目标函数最优值差不少。正确做法是两条残差曲线都低于阈值才算收敛。这份源码包的完整版 notebook 大概率画了两条曲线,跑一遍认真看一下,比你单独背 ADMM 公式有用得多。

3. 跑通 Lasso 与 Ridge:从数据预处理到 alpha 选参

3.1 数据准备与标准化:不做这一步结果没法看

很多人拿过来就 fit,结果 Lasso 的系数完全不可解释。原因是不做标准化时,量纲大的特征在惩罚项里天然吃亏。比如一个特征单位是元,数值在几千到几万,另一个是比例值在 0 到 1 之间,Lasso 的惩罚项对系数绝对值求和,前者稍微有一点系数就产生巨大的惩罚贡献,于是被优先压到零。这跟特征重要性完全无关,纯粹是尺度问题。

标准做法是先 StandardScaler 再进模型,完整代码如下:

import numpy as np import pandas as pd from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split data = pd.read_csv('your_dataset.csv') y = data['target_column'].values X = data.drop(columns=['target_column']).values scaler = StandardScaler() X_scaled = scaler.fit_transform(X) X_train, X_test, y_train, y_test = train_test_split( X_scaled, y, test_size=0.2, random_state=42, shuffle=True )

这里shuffle=True是 train_test_split 的默认行为,但我习惯显式写出来,防止有人误以为切分不洗牌。random_state=42固定随机种子,保证每次跑结果一致,这是所有可复现实验的前提。不用随机种子的话,换台机器或换个时间跑,结果就对不上,排查问题时会很痛苦。

标准化之后 Lasso 的系数大小可以直接比较,系数绝对值越大说明特征对预测的贡献越强。注意 sklearn 的 Lasso 默认fit_intercept=True,标准化后可以让模型自己学截距项,不需要手动把截距从特征里剔除。

提示:fit_transform只能用在训练集上,测试集必须调用同一个 scaler 的transform,不能重新 fit,否则会造成信息泄漏,评估结果虚高。

3.2 Lasso 与 Ridge 的实现:从默认参数到手动调参

先看最基础的实现。sklearn 里 Lasso 和 Ridge 的接口几乎一样,调用时只差一个类名,但背后的代价函数完全不同,一个是 L1,一个是 L2。

from sklearn.linear_model import Lasso, Ridge from sklearn.metrics import mean_squared_error lasso = Lasso(alpha=1.0, max_iter=10000, tol=1e-4) lasso.fit(X_train, y_train) ridge = Ridge(alpha=1.0) ridge.fit(X_train, y_train) for name, model in [('lasso', lasso), ('ridge', ridge)]: pred = model.predict(X_test) mse = mean_squared_error(y_test, pred) print(f"{name} MSE: {mse:.4f}") if name == 'lasso': print(f"lasso 非零系数个数: {np.sum(model.coef_ != 0)}")

两个细节容易踩坑。第一,alpha=1.0是库默认值,它对你的数据未必合适。如果 Lasso 跑出来所有系数全是零,不是模型写错了,而是 alpha 太大、惩罚过度,需要调小。第二,Lasso 的默认求解器是坐标下降,max_iter默认 1000,特征维度高或共线性强时可能不收敛,常见做法是设到 10000,同时把tol设为 1e-4。如果迭代不够,sklearn 会弹出 ConvergenceWarning,系数结果也不稳定。

Ridge 没有稀疏化效果,所有系数都趋向小值但不会为零。所以做特征选择时 Ridge 不合适,它的价值是处理多重共线性——两个特征强相关时 Lasso 可能随机只保留一个,Ridge 会把权重分散到两个特征上,模型更稳定。实际业务里我常用的组合是“Lasso 选特征 + Ridge 精调权重”:先用 Lasso 找到关键特征子集,再用 Ridge 在这些特征上拟合最终系数。

评估指标的选取也要多说一句。MSE 对量纲敏感,和业务指标对不上时可用 R²。R² 越接近 1 越好,但 R² 在测试集上为负也不罕见,说明模型比直接用均值预测还差,这时候去查一下是不是训练测试分布差异过大,比继续调 alpha 更有意义。

3.3 alpha 与正则化路径:怎么判断当前值是否合理

alpha 是正则化强度,控制惩罚项在损失函数里的占比。alpha 越大,系数压缩得越狠,Lasso 的非零特征越少,Ridge 系数普遍变小;alpha 趋近零,模型退化为普通最小二乘。理解这一点的最好方式是画正则化路径。

import numpy as np from sklearn.linear_model import lasso_path import matplotlib.pyplot as plt alphas, coefs, _ = lasso_path(X_train, y_train, eps=1e-3, n_alphas=100) for coef in coefs: plt.plot(np.log10(alphas), coef) plt.xlabel("log(alpha)") plt.ylabel("coefficients") plt.title("Lasso regularization path") plt.show()

lasso_path会从大到小扫一串 alpha 值,返回每个 alpha 下的完整系数矩阵。画出来之后,你会看到曲线从左边大 alpha 的零位置开始,随着 alpha 变小依次“长出来”。那条曲线离开零点的位置就是该特征开始进入模型的临界点,对解释特征重要性非常有用——临界 alpha 越小,说明这个特征越重要,需要更大的惩罚才能把它压掉。

看路径图还有一个用处:确认 alpha 的取值范围。如果你的数据让最优 alpha 落在路径的末端或起始端,说明你设的范围不合适,需要扩展。regreg 的 LASSO demo notebook 里就有类似的正则化路径绘图,比只报一个 MSE 数字信息量大得多。另外,lasso_path里的eps=1e-3控制最小 alpha 与最大 alpha 的比值,如果曲线最右端还没收敛到零,把这个值调大一点。

4. Fused Lasso 与 Group Lasso:结构惩罚项的进阶实操

4.1 Fused Lasso:让相邻系数差也进惩罚

Fused Lasso 是 Lasso 的变体,适合系数存在顺序结构的场景,比如时间序列突变点检测、基因组拷贝数变异分析。它的惩罚项有两部分,一部分是系数自身的 L1 范数,另一部分是相邻系数之差的 L1 范数:

min L(w) + λ1 * Σ|w_j| + λ2 * Σ|w_j - w_{j-1}|

第二项强制相邻系数尽量相等,因此求解出来的系数具有明显的分段常数结构——一段一段的平台,平台之间是突变。这对检测“哪个时间点发生了结构变化”天然合适。如果你拿普通 Lasso 来做同样的任务,得到的系数是杂乱稀疏的,没有分段平台这个概念,解释起来也很费劲。

在 regreg 库里,组合惩罚的表达方式大致是这样的:

import regreg.api as rr loss = rr.squared_error(X, y) sparsity = rr.l1norm(X.shape[1], lagrange=0.5) fused = rr.fused_lasso(X.shape[1], lagrange=0.5, spacing=1) penalty = sparsity + fused problem = rr.simple_problem(loss, penalty) solution = problem.solve()

fused_lasso的lagrange对应公式里的 λ2,spacing=1表示只对相邻系数作差。两个惩罚项相加在 regreg 里是允许的,因为两者都属于可分结构的原子。求解器会根据惩罚项结构自动选择广义近端梯度法。需要提醒的是,regreg 各版本 API 有少量变动,如果导入报错,先按 INSTALL 文件检查库版本,再查对应版本的 API 文档。

sklearn 里没有内置 Fused Lasso,所以这个 notebook 就是这份源码包的核心价值之一。跑的时候把 λ1 和 λ2 的比例多试几个值,λ2 远大于 λ1 时,解趋近于一个全局常数;λ2 趋近零时,退化成普通 Lasso。理解这个比例变化的连续过渡,比跑通一遍代码本身收获更大。

4.2 Group Lasso:分组惩罚与组的权重策略

Group Lasso 的组团方式直接决定结果。regreg 里用 group_lasso 原子手工定义组索引:

import numpy as np import regreg.api as rr loss = rr.squared_error(X, y) groups = [[0, 1, 2], [3, 4], [5, 6, 7, 8]] penalty = rr.group_lasso(groups, weights=None, lagrange=1.0) problem = rr.simple_problem(loss, penalty) solution = problem.solve()

groups 是组索引列表,每个子列表是一组特征的列索引。weights 可以按组设置不同的惩罚权重;默认是每组权重取组大小的平方根,这样惩罚强度不会因为组的大小而失真。如果业务上知道某些组的特征更可靠,调小这些组的权重,能让它们更容易被保留,而不是被大组的惩罚淹没。

跑完 Group Lasso 之后检查系数的方式和 Lasso 不一样。Lasso 看单个系数是否为零,Group Lasso 必须看整组是否全零。我一般用np.linalg.norm(solution[group], ord=2) < 1e-6来判断该组是否被选中。阈值不要设太小,近端梯度法得到的解不会严格为零,数量级小于 1e-6 就被视为零。

组的划分是这个模型最需要业务知识的环节。如果你只是为了演示,手写几个组索引没问题;真实项目里组的边界一定来自业务定义,比如电商场景中商品类目的层级关系、风控场景中同一用户的多个行为特征。不要自动聚类定组,那样得到的组没有解释性,后续模型评审很难通过。

4.3 Newsgroup 逻辑回归:高维文本分类的 L1 惩罚

Newsgroup logistic regression.ipynb 把场景从回归换到分类,用 L1 惩罚的逻辑回归在 20 Newsgroups 数据集上做文本分类。文本分类的特征维度通常有几万维,L1 正则化在这里的核心价值是特征选择——把无关词对应的权重压成零,只留下有区分度的词。

sklearn 里 L1 逻辑回归的实现是:

from sklearn.linear_model import LogisticRegression model = LogisticRegression( penalty='l1', solver='liblinear', C=1.0, max_iter=2000, random_state=42 ) model.fit(X_train, y_train)

注意这里的超参数不是 alpha 而是 C,C 是正则化强度的倒数。C 越大正则化越弱,越容易过拟合;C 越小正则化越强,系数越稀疏。solver 的选择有硬约束:liblinear 和 saga 支持 L1,lbfgs 不支持。数据量在万级以下用 liblinear,更大规模用 saga,saga 还能配合 l1_ratio 做 ElasticNet 的弹性惩罚。

在跑这个 notebook 之前,文本数据一般要经过 TF-IDF 向量化,把原始文本转成词频或词权重矩阵。你可以在 notebook 里验证一下稀疏率——L1 逻辑回归在两万维词袋特征上通常能把 80% 以上的系数压成零,这个稀疏率直接影响了线上预测的推理开销,是你在工程落地时最关心的指标。另一个观察点是类别数量对结果的影响,20 Newsgroups 有 20 个类别,决策边界相当复杂,L1 逻辑回归在这种情况下往往要选足够大的 C 才能保证模型有足够的表达能力。

5. 避坑:正则化回归实战中的五个经典翻车现场

5.1 不做标准化就上 Lasso,系数全乱

现象:特征里既有几千量级的数值变量,又有 0-1 的哑变量,跑完 Lasso 后数值变量的系数几乎全为 0,哑变量的系数大得离谱,业务上完全解释不通。

原因:L1 惩罚对所有系数绝对值求和,量纲大的特征哪怕系数只有 0.01,惩罚贡献远比量纲小的特征大,于是数值变量被优先压到零。这纯粹是尺度问题,不是相关性或者重要性的反映。

解决:训练前先 StandardScaler 标准化,把每个特征变为零均值、单位方差,再进入 Lasso。标准化后的系数绝对值才有可比性。注意测试集要用训练集上拟合好的 scaler 做 transform,不要重新 fit,否则数据泄漏会让评估结果虚高。

5.2 alpha 用默认值,结果要么全零要么不稀疏

现象:Lasso(alpha=1.0) 跑出来系数全为零,换一个数据集又几乎全不为零,怎么看都不对,手动试几个 alpha 又毫无章法。

原因:alpha=1.0 是 sklearn 默认的占位值,不代表适合你的数据。正则化强度要跟特征维度、样本量、噪声水平匹配,固定值必然翻车。手动试出来的 alpha 没有统计学依据,也难说服同事和评审。

解决:不要手动试 alpha,交给 LassoCV 沿正则化路径自动搜索。LassoCV(cv=5, n_alphas=100)会从大到小扫 100 个 alpha,用交叉验证选出最优值。拿到结果之后再手动单独跑一次 Lasso,逻辑更清楚,也方便保存模型参数。cv 设 5 是经验值,数据量小可以降到 3,数据量大可以升到 10,但运行时间会明显增加。

5.3 ADMM 收敛判据太松,分布式结果跟单机对不上

现象:Distributed LASSO 的 notebook 跑完,目标函数数值和单机 Lasso 求解的结果对不上,误差明显偏大,甚至系数分布都不一样。

原因:ADMM 被提前截断了。很多实现只检查原始残差,忽略对偶残差,而原始残差下降快、对偶残差下降慢,只看前者的提前终止会让解停留在目标函数梯度还没消失的位置。

解决:确认 notebook 里是否同时计算原始残差和对偶残差,把收敛阈值从默认的 1e-3 收紧到 1e-5,同时限制最大迭代次数防止死循环。更稳的验证方法是把 ADMM 的解当作初始值,用坐标下降法再优化几十步,如果目标函数还能下降,说明 ADMM 没算到位。

5.4 Group Lasso 的组权重没调,重要小组被整组吞掉

现象:业务上明确某几个特征组成组很重要,但 Group Lasso 跑完这一组系数全是零,怎么调 lagrange 都出不来。

原因:默认组权重和组大小挂钩,大组惩罚重,容易被淘汰。业务重要的组如果内部冗余度高,信息分散在多个系数上,连续 L2 惩罚让整组在未被选中前就被压至零。

解决:把 weights 参数从默认值调低,比如 0.5,减轻这组特征的惩罚。调权的尺度要从 0.5 到 1.5 之间做小范围对比,调太小会退化成普通 Lasso,失去分组约束,调太大又把这组特征完全压死。

5.5 L1 逻辑回归选错 solver,直接报错或告警

现象:LogisticRegression(penalty='l1') 刚跑就报错,提示 lbfgs 不支持 L1;换到 saga 后又出现迭代不收敛的告警。

原因:sklearn 里不是所有 solver 都支持 L1,lbfgs 和 newton-cg 只支持 L2,liblinear 支持 L1 但只适合小数据,saga 支持 L1 且适合大数据。另外逻辑回归的 max_iter 默认只有 100,高维数据上迭代数不够就会告警。

解决:小数据集用 solver='liblinear',大数据集用 solver='saga',同时把 max_iter 调到 1000 以上,tol 设 1e-4。跑文本分类这种几万维特征的任务,我一般 saga 加 max_iter=2000 起步,先看收敛曲线再决定要不要继续加。

6. 用交叉验证锁定 alpha:一套可复用的三步调参流程

最后落到一个可以反复使用的干活流程。之前讲了那么多原理和坑,核心就一句话:alpha 不要拍脑袋,让数据自己回答。下面这三步我每次做正则化回归基本都走一遍。

第一步,粗扫定数量级。用 LassoCV 在默认正则化路径上跑一次,确定最优 alpha 大约在什么量级,以及此时保留了多少个非零特征。

from sklearn.linear_model import LassoCV lasso_cv = LassoCV(cv=5, n_alphas=100, random_state=42, max_iter=10000) lasso_cv.fit(X_train, y_train) print(f"粗扫最优 alpha: {lasso_cv.alpha_:.4f}") print(f"非零系数个数: {np.sum(lasso_cv.coef_ != 0)}")

第二步,精扫锁定区间。以粗扫得到的 alpha 为中心,在它 0.5 到 1.5 倍的区间里用 GridSearchCV 做 21 个点的细网搜索,得到更精确的最优值。这一步缓解了 LassoCV 默认网格比较稀疏的问题,能让最终选出的参数更贴合当前数据集。

from sklearn.model_selection import GridSearchCV from sklearn.linear_model import Lasso best_alpha = lasso_cv.alpha_ grid = GridSearchCV( Lasso(max_iter=10000, tol=1e-4), {'alpha': np.linspace(best_alpha * 0.5, best_alpha * 1.5, 21)}, cv=5, scoring='neg_mean_squared_error' ) grid.fit(X_train, y_train) print(f"精扫最优 alpha: {grid.best_params_['alpha']:.4f}")

第三步,验证稳定性。把最优 alpha 附近的测试集 MSE 画出来,观察曲线在最小值附近是否平缓。如果曲线收得很尖,说明模型对 alpha 极其敏感,上线后数据稍微漂移性能就大幅波动,这比 MSE 本身高低更要命。遇到尖峰我一般会往较大 alpha 一侧偏移一点,让模型更保守,牺牲少量训练精度换取稳健性。

从那以后我每次跑 Lasso 都强制走一遍这个流程,先粗扫、再精扫、最后看稳定性,不再凭感觉填 alpha 就提交结果。这套流程同样适用于 Ridge、ElasticNet 和 Group Lasso,只是把模型类、参数名和评估指标换成对应的配置。正则化回归的坑大多出在超参数选择上,这套流程能把最多的不确定性消除掉,希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询