ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

正则化回归全解析:Lasso、Ridge到Fused/Group与ADMM分布式求解

2026/9/13 15:31:29 拓冰建站 浏览量
正则化回归全解析:Lasso、Ridge到Fused/Group与ADMM分布式求解 简介面向机器学习与数据分析初学者的正则化回归Python实现压缩包聚焦L1Lasso与L2Ridge两类经典正则化方法帮助解决高维数据过拟合与特征选择问题。包体包含168个文件以110个Python脚本py/pyx和34个RST文档为主体另有8个Jupyter Notebook示例、少量文本与配置文件Notebook覆盖Fused LASSO、Group LASSO、ADMM分布式LASSO等进阶内容便于从原理到代码逐层拆解。压缩包整体仅1.28MB轻量易下载已有302人学习。资源提供从数据加载、预处理、训练测试划分到Lasso/Ridge建模、MSE评估的完整代码并包含交叉验证调参与超参数搜索实现读者可结合示例数据与脚本直接运行理解正则化强度alpha对模型的影响快速迁移到自己的回归任务中。1. 正则化回归不只有Lassoregreg里的Fused、Group与分布式求解当特征维度远高于样本量时普通最小二乘会给出方差极大的解L1和L2正则化是最先被想到的止血方案。但实际业务里更常见的是带有结构的稀疏需求基因表达谱中同一通路内的基因要一起被选中时间序列的相邻系数不应剧烈跳变或者设计矩阵大到无法放进单机内存。regreg-master这个压缩包里恰好覆盖了这些场景除了标准的LASSO demo还提供了Fused LASSO、Group LASSO以及基于ADMM的分布式求解notebook。它适合已经会用scikit-learn跑通Lasso/Ridge、但想深入惩罚项结构和优化器细节的开发者。2. L1与L2正则化回归的Python实现从Lasso、Ridge到alpha选择正则化回归之所以有效是因为它在原本只拟合训练误差的目标函数里加入了对模型复杂度的约束。最常见的两种约束分别是L1惩罚和L2惩罚对应Lasso和Ridge。Lasso会把一部分系数精确压缩到0相当于在做特征选择Ridge则让系数整体收缩到接近0但没有稀疏性。两者在Python里都有非常成熟的实现但在面对不同数据尺度时需要先理解惩罚项在目标函数中的位置否则调参容易失真。2.1 惩罚项在目标函数中的位置L1与L2的几何含义线性回归的目标函数可以写成min ||y - Xβ||²加入L1惩罚后变成min ||y - Xβ||² alpha * ||β||_1加入L2惩罚则变成min ||y - Xβ||² alpha * ||β||_2²。这里的alpha是正则化强度alpha越大模型越简单。从几何上看L1惩罚的解容易落在坐标轴上因为菱形约束域的顶点正好对应某些系数为0L2惩罚的圆形约束域会得到非零但很小的系数。在Python代码中这种差异会直接反映在输出的系数向量上。数据不标准化时量纲更大的特征会承受更小的惩罚压力导致Lasso错误地保留量纲大的噪声特征所以做正则化回归之前应该先对特征做标准化。2.2 用Scikit-Learn跑通Lasso与Ridge完整流程2.2.1 数据准备与标准化以下代码用模拟数据演示完整流程实际使用时把X和y替换成自己的数据即可。标准化这一步我会放在训练集上做而不是全量数据避免测试集信息泄漏到训练过程。import numpy as np import pandas as pd from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.linear_model import Lasso, Ridge from sklearn.metrics import mean_squared_error from sklearn.model_selection import GridSearchCV # 生成一份模拟数据前5个特征有真实信号其余全为噪声 rng np.random.default_rng(42) n, p 200, 30 X rng.normal(size(n, p)) beta np.zeros(p) beta[:5] [3, -2, 1.5, 0.8, 0.5] y X beta rng.normal(scale0.5, sizen) X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) scaler StandardScaler().fit(X_train) X_train scaler.transform(X_train) X_test scaler.transform(X_test)这里把数据拆成了训练集和测试集并且只用训练集拟合StandardScaler。如果先在整个数据集上标准化再切分测试集的均值和方差会通过缩放器提前参与训练导致后续交叉验证的结果偏乐观。对于高维稀疏特征还可以考虑StandardScaler之外的最大最小值缩放但线性回归的惩罚项通常默认输入已经中心化。2.2.2 模型训练与MSE对比lasso Lasso(alpha0.1) ridge Ridge(alpha0.1) lasso.fit(X_train, y_train) ridge.fit(X_train, y_train) y_pred_l lasso.predict(X_test) y_pred_r ridge.predict(X_test) print(Lasso MSE:, mean_squared_error(y_test, y_pred_l)) print(Ridge MSE:, mean_squared_error(y_test, y_pred_r)) print(Lasso non-zero coefs:, np.sum(lasso.coef_ ! 0))这段代码直接输出两个模型的MSE和Lasso的非零系数个数。alpha0.1是临时值实际项目中几乎不会直接采用而是通过交叉验证选择。运行后你会发现Lasso往往能把30个特征里大部分系数置零Ridge则保留全部系数但数值较小。这两者的选择逻辑不同如果业务上需要解释哪些特征是有效的Lasso更合适如果目标是降低预测方差而不关心特征个数Ridge更稳。2.2.3 用网格搜索调alphaparam_grid {alpha: np.logspace(-3, 1, 50)} grid GridSearchCV( Lasso(max_iter10000), param_grid, cv5, scoringneg_mean_squared_error ) grid.fit(X_train, y_train) best_alpha grid.best_params_[alpha] print(best alpha:, best_alpha)这里的np.logspace(-3, 1, 50)表示从0.001到10的对数均匀取50个值比线性网格更合理因为alpha对模型的影响通常是指数级的。max_iter10000是Lasso默认迭代次数的10倍避免在部分数据上出现坐标下降不收敛的警告。网格搜索的评分用了负MSE因为sklearn的GridSearchCV默认按越大越好排序负MSE越大越好。2.3 从sklearn到regreglagrange参数与alpha的对应regreg库的核心思路是把损失函数和惩罚项拆成独立对象再用simple_problem组合起来求解。在LASSO demo.ipynb里作者使用smooth_quadratic构造最小二乘损失用lasso构造L1惩罚最后调用solve()得到系数。regreg中的lagrange参数和sklearn中的alpha在数学上是同一个东西但regreg的惩罚项接口更接近通用优化框架可以灵活叠加Fused Lasso或Group Lasso。对比维度sklearn Lassoregreg.api.lasso目标函数MSE alpha * L1同左但可组合其他惩罚正则参数名alphalagrange默认求解器坐标下降FISTA或ADMM自定义扩展困难通过penalty对象组合下面这段代码模拟了LASSO demo.ipynb中的核心调用方式from regreg.api import smooth_quadratic, lasso, simple_problem loss smooth_quadratic(affineX_train, Yy_train) penalty lasso(X_train.shape[1], lagrange0.1) problem simple_problem(loss, penalty) coef problem.solve(max_its200, tol1e-6)smooth_quadratic接受特征矩阵和标签lasso的第一个参数是特征维度lagrange控制正则强度。solve(max_its, tol)中的max_its是最大迭代次数tol是目标误差阈值。如果你的regreg版本较老smooth_quadratic可能需要用位置参数smooth_quadratic(X_train, y_train)而不是affine以压缩包内notebook为准。这个接口的好处是损失函数和惩罚完全分离想换成其他惩罚只需替换penalty对象。3. 结构化正则项实战Fused Lasso与Group Lasso的建模、求解与调参标准Lasso只能对单个系数做稀疏化但在很多场景里系数之间存在已知的先后顺序或分组关系。比如脑电信号时间点上的系数应当平滑变化基因表达数据中同一信号通路内的基因应当被整体筛选。这时就需要把惩罚项改造成更复杂的结构Fused Lasso和Group Lasso就是两类最常用的结构化正则项。3.1 Fused Lasso相邻系数差分惩罚背后的物理意义Fused Lasso的目标函数在L1惩罚之外增加了相邻系数差分的L1惩罚。假设系数按时间顺序排列惩罚项可以写成alpha * ||β||_1 beta * Σ|β_i - β_{i-1}|。第一项让整个模型稀疏第二项让相邻系数尽量相等。这在变化点检测、光谱去噪、信号压缩中非常有用因为它能自动识别出信号只在少数位置跳变。与普通Lasso相比Fused Lasso更适合系数有连续结构的场景。在regreg的Fused LASSO.ipynb里作者把惩罚项拆成了两个部分一个是作用在系数本身上的L1惩罚另一个是作用在一阶差分上的L1惩罚。调参时需要注意两个惩罚强度是独立的不能只用一个alpha同时控制否则无法平衡稀疏性和平滑性。3.2 Group Lasso分组变量的整体稀疏Group Lasso把一组变量作为一个整体进行稀疏化惩罚项是每组系数的L2范数之和alpha * Σ_w ||β_group_w||_2。这里没有对组内系数做L1惩罚所以一组被选中后组内所有系数通常都不为0。这样设计的好处是尊重了变量间的自然结构比如one-hot编码的类别特征、同一通路上的多个基因、同一多项式基函数的多个阶次。一个常见的误用是把Group Lasso当作Lasso的简单扩展直接对整个特征空间分组。实际上分组方式对结果影响很大如果分组错误会破坏组内相关性导致大量组被错误收缩。更合理的做法是从业务知识出发把互为共线性高的特征放进同一组再设置每组权重为组大小的平方根避免大组被过度惩罚。3.3 在regreg中构造并求解结构化惩罚项3.3.1 Fused Lasso的最小复现代码import numpy as np from regreg.api import fused_lasso, smooth_quadratic, simple_problem n, p 100, 50 X np.random.standard_normal((n, p)) beta_true np.concatenate([np.ones(10), -np.ones(10), np.zeros(30)]) y X beta_true 0.2 * np.random.standard_normal(n) loss smooth_quadratic(affineX, Yy) penalty fused_lasso( positionsnp.arange(p), lagrange0.05, slope0.1 ) problem simple_problem(loss, penalty) sol problem.solve(max_its200, tol1e-6) # 查看解是否保持阶梯形状 print(sol[:15])positions参数表示系数的顺序它会决定哪些系数被视为相邻。lagrange0.05控制稀疏性slope0.1控制相邻差分的幅度。如果slope过大所有相邻系数都会被压成同样的值最后失去跳变点如果lagrange过大整个解会变成全零。我一般会先固定slope用交叉验证确定lagrange再微调slope因为稀疏性的影响比平滑性更明显。3.3.2 Group Lasso的稀疏解与结果解释from regreg.api import group_lasso groups [np.arange(0, 10), np.arange(10, 20), np.arange(20, 40), np.arange(40, 50)] penalty group_lasso( groups, lagrange0.2, weightsNone ) problem simple_problem(loss, penalty) coef problem.solve(max_its200, tol1e-6) for i, g in enumerate(groups): group_l2 np.linalg.norm(coef[g]) nz np.sum(np.abs(coef[g]) 1e-6) print(fgroup {i}: L2{group_l2:.3f}, nonzero{nz})groups是一个列表每个元素是原始特征的下标数组。weightsNone时regreg默认使用每组大小的平方根作为权重这会让更大的组更难被整体剔除。打印出来的L2范数接近0的组说明被完全稀疏化而组内 nonzero 数量会显示该组是否有部分系数被保留。如果发现某个大组经常整体被保留可以考虑提高它的权重惩罚更重。特性Fused LassoGroup Lasso惩罚形式系数绝对值 相邻差分绝对值每组系数的L2范数之和稀疏性单个系数稀疏差分稀疏整组稀疏组内通常全非零典型场景时间序列、图像、光谱分组特征、one-hot编码关键参数lagrange slopelagrange weights4. 分布式LASSO求解ADMM如何把大矩阵拆成子问题当设计矩阵大到单机内存放不下或者数据来源天然分布在不同节点时正则化回归就不能再走坐标下降或FISTA这类整体算法。ADMM的思路是把原始LASSO问题拆成两个互相耦合的变量用交替更新来求解天然适合分布式计算。regreg-master中专门有Distributed LASSO: ADMM.ipynb和Distributed LASSO: ADMM one value.ipynb两个notebook演示的正是这种思路。4.1 ADMM的增广拉格朗日形式LASSO问题可以改写为带等式约束的形式min 1/2 ||Ax - b||² lam ||z||_1, s.t. x z。增广拉格朗日函数加入一个对偶变量u和惩罚参数rho交替更新x、z、u。x更新是求解一个带L2正则的线性方程z更新是软阈值u更新是对偶残差的累积。这个结构中x的更新涉及矩阵A的转置乘A而A可以按行拆分成多个块分别在各节点计算局部梯度再汇总为全局解。4.2 一个极简的ADMM LASSO实现import numpy as np def soft_threshold(v, t): return np.sign(v) * np.maximum(np.abs(v) - t, 0) def admm_lasso(A, b, lam, rho1.0, max_iter300, tol1e-4): m, n A.shape x np.zeros(n) z np.zeros(n) u np.zeros(n) for k in range(max_iter): # x update等价于求解岭回归 x np.linalg.solve(A.T A rho * np.eye(n), A.T b rho * (z - u)) # z update软阈值收缩 z_new soft_threshold(x u, lam / rho) # u update对偶变量调整 u u x - z_new # 检查原始残差 primal_res np.linalg.norm(x - z_new) z z_new if primal_res tol: break return z代码中的rho是ADMM的步长惩罚参数lam是L1正则强度。x更新需要求解一个n x n的线性方程组这是计算瓶颈在分布式环境中A.T A会被拆成各个节点的A_i.T A_i之和再汇总到中心节点求逆。soft_threshold是软阈值函数参数lam/rho控制了收缩力度lam不变时rho越大z的稀疏性越弱。4.3 从单机模拟到真正分布式参数与时序用上面的函数在单机上模拟分布式时可以把A按行切分成多块每个节点只计算自己的A_i.T A_i和A_i.T b_i然后用MPI或Parameter Server做一次聚合。rho的选择对ADMM收敛速度影响很大。rho值收敛行为过小残差下降慢需要上千次迭代过大收敛快但预测精度可能变差1.0大多数场景的可靠起点在notebook中可以看到作者对不同rho值绘制了残差随迭代次数的变化。如果数据量很大同时希望保证收敛稳定可以设置rho为特征维度与样本量的比值或者使用自适应rho策略。对于单机复现来说rho1.0通常足够真正分布式部署时还需要考虑节点间只传递对偶部分的梯度信息不能直接传递原始数据。5. 进阶技巧验证ADMM残差曲线并自定义正则化项5.1 用残差曲线判断rho是否需要调整把ADMM放进循环里记录每次迭代的原始残差和对偶残差是判断收敛状态的直接方法。下面这段代码在原有admm_lasso基础上增加记录功能def admm_lasso_with_history(A, b, lam, rho1.0, max_iter300): m, n A.shape x np.zeros(n) z np.zeros(n) u np.zeros(n) history [] for _ in range(max_iter): x np.linalg.solve(A.T A rho * np.eye(n), A.T b rho * (z - u)) z_new soft_threshold(x u, lam / rho) u u x - z_new primal_res np.linalg.norm(x - z_new) dual_res rho * np.linalg.norm(z_new - z) history.append((primal_res, dual_res)) z z_new return z, np.array(history)用matplotlib画出的曲线上如果原始残差和对偶残差同时快速下降说明rho设置合理如果原始残差波动很大说明rho偏大可以按0.5倍缩小如果两条曲线都长期缓慢下降则应该加大max_iter或增大rho。这个技巧在复现Distributed LASSO notebook时会经常用到。5.2 在regreg中自定义正则化项的关键步骤regreg里的所有惩罚项都继承自atomic基类关键方法是prox也就是近端算子。ADMM、FISTA等求解器只通过prox与惩罚对象交互所以自定义正则项时只需要实现这个方法from regreg.api import atomic class MyPenalty(atomic): def __init__(self, shape, lagrange): super().__init__(shape, lagrange) def prox(self, t, x): # t是近端步长x是输入向量 return x # 替换为你的近端投影逻辑例如要实现一个惩罚非负系数的正则项prox就是np.maximum(x, 0)。实现后把它传入simple_problem(loss, MyPenalty(...))即可。regreg-master中的Fused LASSO和Group LASSO notebook都展示了这种可替换的接口设计理解这个机制后你可以把任何带近端算子的自定义惩罚接入现有求解器。本文还有配套的精品资源点击获取