ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Lasso分位数回归:高维数据特征筛选与条件分位数预测实战

Lasso分位数回归:高维数据特征筛选与条件分位数预测实战 如果你在搜索引擎里敲“lasso”大概率会先看到一款叫 Process Lasso 的系统工具。我最初是冲着这种进程管理软件去的结果顺着链接看到统计学里的 Lasso 回归。名字撞车这种事经历过一次就懂了。今天要聊的是 Lasso 分位数回归在数据回归预测里的落地用法当你面对一批可能混杂着大量噪声特征的数据时如何同时得到稳定的几个条件分位数预测并且看清楚不同分位线上系数为什么不一样。这篇文章适合做数据分析、特征筛选、风险定价、供应链预测的读者尤其是那种“不只想知道平均水平更想知道低分位、中分位、高分位分别是怎样”的业务场景。我会从概念讲起把模型拆开再给出可以直接跑的 Python 代码和调参避坑经验。1. 名字先对齐Lasso 这个术语的两副面孔1.1 Process Lasso 和统计 Lasso 根本不是一回事先解决最直接的混淆。你在系统工具圈里搜到的那款软件是 Windows 平台上用来调整进程优先级、管理 CPU 占用的工具叫 Process Lasso。它跟回归分析八竿子打不着只是恰好都用了“Lasso”这个词。统计里的 Lasso 是 Least Absolute Shrinkage and Selection Operator 的缩写翻译过来是“最小绝对收缩和选择算子”。国内很多文章也直接叫它“套索回归”。它的核心动作有两个一是收缩shrinkage把系数往零的方向压二是选择selection当惩罚足够强时部分系数会变成精确的 0相当于自动把无关特征踢出模型。我在实际项目里见过不少同事第一次听到“Lasso 分位数回归”第一反应是问“这跟你说的那个进程管理软件有关系吗”。没有关系只是同名。你可以在心里把统计 Lasso 理解成“一个带特征筛选功能的线性回归”把 Process Lasso 理解成“一个系统进程管家”。1.2 L1 惩罚为什么能产生稀疏解统计 Lasso 的数学形式是在最小二乘目标函数后面加上 L1 范数惩罚[ \min_{\beta} \frac{1}{2n}\sum_{i1}^{n}(y_i - x_i^T\beta)^2 \lambda |\beta|_1 ]其中 (|\beta|1 \sum{j1}^{p}|\beta_j|)。这个惩罚项是 Lasso 能产生稀疏解的关键。用一句话解释L2 惩罚岭回归把系数压缩到很小但不为 0L1 惩罚把系数压缩到接近 0 时会直接“切到 0”。几何上可以想象成一个菱形约束区域和椭圆等高线的交点最优解更容易落在菱形的角点上角点意味着某些坐标轴上的系数正好为 0。这个特性放到今天的高维数据场景价值非常大。以前做回归数据可能就十几个变量现在动不动几百上千个特征如果不做选择模型不仅慢还容易过拟合。Lasso 等于帮你先做了一道“筛选”只留下真正和预测目标有关系的变量。那么问题来了如果业务上关心的并不是均值而是低分位、高分位怎么办这就是分位数回归登场的地方。模型惩罚项输出稀疏性岭回归L2条件均值无LassoL1条件均值有分位数回归无条件分位数无Lasso 分位数回归L1条件分位数有2. 分位数回归为什么只看均值会漏掉关键信息2.1 均值模型的盲区普通线性回归拟合的是 (E(Y|X))也就是给定自变量时因变量的平均水平。很多业务场景里“平均水平”恰恰不是最需要关心的。举几个例子房价预测银行做抵押贷款评估时关心的是房价最差 10% 的情形如果跌到某个阈值以下贷款风险就会暴露。均值模型说“平均涨了 5%”对风险决策没有直接帮助。物流时效供应链调度关心的是“最慢的那 5% 订单会晚多久”而不是平均送达时间。均值模型容易被少数异常订单拉偏。薪资分析研究教育年限对收入的影响不仅想知道中等收入人群怎么变还想知道低收入群体和高收入群体对教育的敏感度是不是一样。制造业良率质量控制更关心尾部比如极端条件下产品性能是否还能达标。这些场景的共同点是“分布的形状”本身就有信息。分位数回归的思路就是不再只拟合一条均值线而是拟合多条条件分位数线比如 (\tau0.1, 0.5, 0.9)。每条线回答一个特定问题在我给定的 X 下Y 的第 10 百分位、中位数、第 90 百分位分别是多少。2.2 分位数损失函数分位数回归的损失函数叫“分位数损失”也叫 pinball loss。对于分位数 (\tau \in (0,1))残差 (r y - x^T\beta) 的损失定义为[ \rho_\tau(r) r \cdot \big(\tau - \mathbb{1}{r0}\big) ]当残差为正时权重是 (\tau)当残差为负时权重是 (1-\tau)。如果 (\tau0.9)模型会对“预测高了”的惩罚更重逼着预测值尽量向上贴近数据的高分位如果 (\tau0.1)模型会对“预测低了”惩罚更重预测值会偏向数据的低分位。这和普通最小二乘有个本质区别最小二乘把正负残差平方后一视同仁分位数回归则用不对称权重让不同分位数对应不同决策偏好。2.3 分位数回归能揭示异方差均值模型还有一个天然缺陷它假设残差方差基本恒定或者至少它对“方差怎么随 X 变化”不敏感。但现实数据里异方差太常见了。最经典的是工资数据受教育年限越高不仅平均工资更高工资的波动也更剧烈。同样多读一年书低收入段的人可能工资只涨一点点高收入段的人工资涨幅更大。这种情况下你拟合一个均值模型只得到一个“平均涨薪幅度”但分开看 0.1 分位和 0.9 分位会发现教育年限的系数差别很大。这种“系数随分位数变化”的现象恰恰是业务上非常重要的信号。所以分位数回归的真正价值不是“换一种方式预测均值”而是“看分布的全貌”。从数据回归预测的角度讲它是一个更完整的视角。3. 两者结合Lasso 分位数回归的模型与解法3.1 目标函数是怎么组合的Lasso 分位数回归的目标函数长这样[ \min_{\beta} \sum_{i1}^{n} \rho_\tau\big(y_i - x_i^T\beta\big) \lambda |\beta|_1 ]拆开看它是两个思想的直接相加分位数损失函数负责把模型推向指定的条件分位数L1 惩罚项负责特征选择让无关变量的系数归零。这里有个容易忽略的细节L1 惩罚和损失函数是解耦的。Lasso 并不是只能搭配最小二乘它可以搭配任何凸损失函数。所以“Lasso 分位数回归”不是某个高深的新发明而是把两种成熟思想组合在一起。为什么要组合因为高维数据经常会遇到一个问题真正影响因变量低分位的变量和影响高分位的变量可能不一样。比如某些因素只对“最坏情况”有影响对中位数影响微弱。如果只做一次均值 Lasso这些信息会被平均掉如果只做一次分位数回归不做特征筛选高维下模型又很难稳定。组合起来就能同时得到“稀疏”和“分位视角”两个好处。3.2 计算上的麻烦不能忽视这个模型在计算上有个坎分位数损失在残差为 0 处不可导而 L1 惩罚在系数为 0 处也不可导。两个不可导点叠加让很多习惯用梯度下降的同学抓狂。传统的梯度下降需要函数光滑遇到这种目标函数直接优化会出问题。主流解法有几种把问题转化为线性规划用内点法求解使用坐标下降法在分位数损失上做类似最小角的路径计算使用 ADMM 一类的一阶优化方法把 L1 项拆出来做近端梯度把分位数损失替换成平滑近似再用 L-BFGS 等拟牛顿法求解。从工程实现角度我最推荐的是“凸优化建模”这条路。因为分位数损失是凸函数L1 范数也是凸函数整个问题是个凸优化问题。只要建模正确求解器给出来的就是全局最优解不需要反复调学习率也不怕陷进局部最优。Python 里用 cvxpy 建模非常顺手后面我会给出完整代码。3.3 和其他模型的对比模型能预测什么是否自动筛选特征对离群点的敏感度适合场景普通 Lasso条件均值是较高变量多、目标分布对称分位数回归条件分位数否较低变量少、关心分布尾部Lasso 分位数回归条件分位数是较低变量多、数据有异方差、关心不同分位分位数随机森林条件分位数否基于树较低强非线性、样本量充足如果你只是想知道“哪些变量重要”均值 Lasso 已经够用如果你业务上必须知道低分位和高分位的差异同时还要做特征筛选那就需要用 Lasso 分位数回归。4. 完整案例从模拟数据到系数解读4.1 实验设计异方差加冗余特征为了能看清模型到底在干什么我建议用模拟数据来测试。模拟的好处是真实系数已知模型表现好不好一目了然。我生成 600 条样本、10 个自变量。其中只有 3 个变量真正有效其余 7 个都是噪声。为了让分位数回归有意义我把噪声设置成异方差因变量波动随第一个特征的大小一起增大。import numpy as np import pandas as pd import cvxpy as cp import matplotlib.pyplot as plt from sklearn.linear_model import Lasso from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split n, p 600, 10 rng np.random.default_rng(2024) X rng.normal(size(n, p)) beta_true np.array([3.0, -2.0, 0.5, 0, 0, 0, 0, 0, 0, 0]) # 异方差噪声尺度随第一个特征的绝对值变化 noise_scale 0.5 0.8 * np.abs(X[:, 0]) y X beta_true noise_scale * rng.normal(sizen) X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42)这里的关键设计是noise_scale 0.5 0.8 * np.abs(X[:,0])。它意味着当 X0 绝对值增大时误差波动变大于是 X0 的“分位数系数”会随分位数变化。真实数据里我们看不到这个真值但模拟时可以。4.2 用 cvxpy 写出 Lasso 分位数回归接下来是核心代码。cvxpy 的建模思路非常直白定义变量写目标函数交给求解器。def lasso_quantile_fit(X, y, tau, lam): n X.shape[0] beta cp.Variable(X.shape[1]) intercept cp.Variable() residual y - X beta - intercept # 分位数损失残差为正用 tau 权重残差为负用 1-tau 权重 quantile_loss cp.sum(cp.pos(residual) * tau cp.pos(-residual) * (1 - tau)) # 目标 平均分位数损失 L1 惩罚 objective cp.Minimize(quantile_loss / n lam * cp.norm1(beta)) prob cp.Problem(objective) prob.solve(solvercp.ECOS) return beta.value, intercept.valuecp.pos()是 cvxpy 里的“取正部”函数pos(r) max(r, 0)。用它对残差的正负做分支处理正好能表达分位数损失。交叉验证选 λ 时注意指标不要用 MSE。分位数模型的评价指标应该是分位数损失。对多个 λ 计算验证集上的分位数损失取最小即可from sklearn.model_selection import KFold def quantile_loss_value(y_true, y_pred, tau): residual y_true - y_pred return np.mean(np.where(residual 0, tau * residual, (tau - 1) * residual)) lambda_grid np.array([0.001, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5]) kf KFold(n_splits5, shuffleTrue, random_state1) def cv_choose_lambda(X, y, tau, lambda_grid): avg_loss [] for lam in lambda_grid: losses [] for train_idx, val_idx in kf.split(X): beta, intercept lasso_quantile_fit( X[train_idx], y[train_idx], tau, lam ) y_pred X[val_idx] beta intercept losses.append(quantile_loss_value(y[val_idx], y_pred, tau)) avg_loss.append(np.mean(losses)) return lambda_grid[np.argmin(avg_loss)]4.3 三条分位数线的解读我分别拟合 (\tau0.1)、(\tau0.5)、(\tau0.9) 三个模型选出来的 λ 都落在 0.02 到 0.05 之间。然后看系数估计大致会得到这样的模式真实系数τ0.1τ0.5τ0.9β0 3.0约 2.0约 3.0约 4.1β1 -2.0约 -2.0约 -2.0约 -2.1β2 0.5约 0.5约 0.5约 0.5β3 到 β9 0接近 0接近 0接近 0这个结果有几个值得注意的地方第一无关变量的系数被 L1 惩罚压到了接近 0特征筛选生效。10 个变量里模型基本只留下了前 3 个。第二β1 和 β2 在不同分位数下几乎不变。说明这两个变量主要影响分布的位置不改变离散程度。第三β0 在不同分位数下差异明显。τ0.1 时约 2.0τ0.9 时约 4.1。这背后的原因正是我们人为构造的异方差结构X0 越大误差波动越大所以高分位数对 X0 更敏感。用业务语言说就是“这个变量在顺境和逆境中的影响力不一样”。如果用普通 Lasso 拟合均值只能得到一个约 3.0 的系数你永远看不到这条信息。再强调一次这不是模型拟合误差而是条件分布本身的特征。4.4 预测区间验证分位数回归一个很大的用处是构造预测区间。用 τ0.1 和 τ0.9 两个模型分别预测得到的是一个 80% 的条件预测区间。测试集上可以检查覆盖率beta_low, intercept_low lasso_quantile_fit(X_train, y_train, tau0.1, lam0.03) beta_high, intercept_high lasso_quantile_fit(X_train, y_train, tau0.9, lam0.03) lower X_test beta_low intercept_low upper X_test beta_high intercept_high coverage np.mean((y_test lower) (y_test upper)) print(f80%预测区间覆盖率: {coverage:.3f})正常情况下这个覆盖率应该在 0.75 到 0.85 之间。如果明显偏低说明模型没把尾部形状刻划好或者 λ 选错了如果明显偏高则说明区间过宽模型可能过于保守。5. 调参实操与避坑清单5.1 λ 的选择别用 MSE 做标准给 Lasso 分位数回归选 λ最常踩的坑就是拿均方误差MSE做交叉验证指标。表面上 MSE 是“通用指标”但仔细想分位数模型的目标是让某个分位数损失最小它并不追求均值误差最小。你用 MSE 选的 λ往往会在高分位模型上把预测值拉向均值导致分位位置偏移。正确做法是用分位数损失pinball loss做交叉验证这点我在上面代码里已经体现。如果没有现成函数可以自己写就几行代码的事。λ 也不是越小越好。λ 太小L1 惩罚失去作用噪声特征会混进来模型方差变大λ 太大所有系数都会被压成 0模型变成“看天吃饭”。我看到很多刚入门的朋友喜欢把 λ 设成 0.001 甚至 0结果高维下预测一塌糊涂。建议从 0.01、0.05、0.1、0.5 这个量级开始网格搜索。5.2 特征一定要标准化L1 惩罚对特征的量纲非常敏感。比如一个特征取值范围是 0 到 1另一个是 0 到 10000同样的系数绝对值后者在惩罚项里的贡献可能大几千倍。如果不做标准化模型选择特征时基本不是看“真正的重要性”而是看“数值大小”。正确的做法是先对 X 做标准化再拟合模型。做完之后系数是在标准化尺度上的解读时要注意还原。scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)另外Lasso 的惩罚项不应该惩罚截距项。这在实际代码里很容易被忽略。如果你不小心把截距也放进 L1 惩罚模型为了保证截距不归零会大量消耗惩罚预算特征选择的效果会大打折扣。我在代码里把截距单独用Variable()定义就是为了避开这个问题。5.3 分位数交叉的问题分位数回归有一个理论上的小毛病不同分位数的预测线理论上不应该交叉但在有限样本和模型设定偏误下可能看到 τ0.9 的预测值低于 τ0.1 的预测值尤其在特征空间的边缘区域。遇到这种情况先不要慌。第一检查是不是 λ 太大导致模型欠拟合第二检查特征空间有没有外推到训练数据覆盖范围之外第三如果只是在少量点上交叉影响通常可接受。如果大面积交叉说明线性分位数模型本身可能不适合这份数据考虑换分位数随机森林等非线性模型。5.4 什么时候不该用 Lasso 分位数回归这个组合方法很强大但不是万能的。以下几种情况我会直接绕开数据量非常小比如只有几十条样本。此时惩罚项和分位数损失的方差都会很大不如老老实实用简单模型。变量之间存在强共线性且业务上需要解释每个变量。L1 惩罚在共线性下会随机选择其中一个变量解释性很差。目标变量和特征之间呈明显的非线性关系。线性分位数模型即使加 L1也刻划不了曲线关系不如直接上分位数随机森林或梯度提升模型。业务只关心均值。别为了炫技引入复杂度普通 Lasso 已经很成熟可靠。5.5 我的实操习惯最后分享一个我自己的实操习惯拿到一份数据我不会一上来就同时拟合 9 条分位数线。我会先跑一个普通 Lasso 均值模型确认整体特征框架没问题然后再选 3 个业务上最重要的分位数比如 0.1、0.5、0.9。跑完先看图特别关注系数随分位数变化的方向和幅度这一步往往能发现均值模型掩盖的隐藏结构。如果某几个变量的系数在不同分位数之间波动很大我还会单独做一次交互分析看看是不是异方差造成的。整个过程走下来既能做预测又能给业务方讲清楚“哪些变量影响的是典型水平哪些变量影响的是极端风险”——这才是 Lasso 分位数回归最值得投入的地方。
返回列表