资讯动态

MATLAB中LASSO回归实现特征选择全攻略

发布时间:2026/10/21 15:38:00 来源:尧图企业网站定制
做数据分析的同行应该都有过这种体验好不容易从业务系统里翻出来几十上百个候选特征扔进模型一看效果没涨多少训练时间倒是翻了几倍更难受的是几个特征之间高度相关系数东倒西歪换个样本就翻脸。这时候我就会祭出LASSO回归这套特征选择方案配合MATLAB的lasso函数基本能够把一股脑塞进去的变量池快速压缩成几个真正有用的特征。这篇文章不绕弯子直接说清楚LASSO回归做特征选择的原理、MATLAB实现细节、Lambda怎么选以及我踩过的一些坑。先说人话版本的结论LASSO回归就是给普通线性回归的损失函数加了一个L1惩罚项逼着那些不重要的特征系数变成0。系数都变成0了特征就被自动剔除了。这个思路在工程里极其好用尤其适合特征多、样本少、还掺杂一堆无效变量的场景。这篇文章覆盖了从数据预处理、模型训练、Lambda调参到最终特征清单生成的全流程适合正在做回归建模、需要特征筛选或者被共线性折磨的数据分析从业者参考。1. LASSO回归凭什么能在特征选择里挑大梁1.1 特征太多的时候模型先“病”先聊一个常见的业务场景某公司想预测产品销量候选特征里既有历史销量、广告投入、促销力度这种明显相关的变量也有网页浏览数、客服咨询量、天气温度这种边缘特征加起来小一百个。如果直接丢进多元线性回归马上会遇到三个麻烦。第一个是维度灾难。特征数量和样本量不成比例的时候模型参数过多拟合的方差暴涨训练集表现很好一上测试集就崩。第二个是共线性问题。业务特征之间经常存在隐藏的相关性比如广告投入和网页浏览数本身就在互相影响回归系数会变得极不稳定甚至出现正负号翻转这种离谱结果。第三个是解释成本太高。一百个特征全塞进模型业务方问你“影响销量的核心因素到底是哪几个”你说“都重要”这显然没法通过业务评审。LASSO回归解决的就是这些事。它不像普通回归那样一味追求训练误差最小化而是在目标函数里加了一个L1惩罚牺牲一点点拟合精度换取模型的稀疏性和稳定性让一堆特征里真正有效的那些浮出水面。1.2 L1惩罚的数学直觉LASSO的优化目标长这样min (1/2n) * ||y - X*w||^2 λ * ||w||_1前一项是误差平方和衡量预测值和真实值的差距后一项是L1范数也就是所有系数的绝对值之和λ控制惩罚强度。关键在于这个绝对值。绝对值函数在0点有个尖角导数是突变的。在寻优过程中某个特征的系数一旦被压缩到接近0的位置梯度方向就会把它直接推过0变成严格等于0。你可以理解为普通回归是在平地上找最低点LASSO是在有棱角的地形上找最低点那些小坡直接就把小球卡在坡底了。对比一下岭回归的L2惩罚也就是系数的平方和它会把系数压缩得很小但很少真正压成0。L2像用弹簧拽着参数只能缩小距离不能锁死位置。L1像在参数周围画了个方形笼子系数一旦碰壁就贴在上面不动表现为很多系数正好是0。这就是稀疏性的来源。1.3 Lambda参数是整盘棋的开关λ在这个公式里是惩罚力度它决定模型愿意牺牲多少拟合度去换取稀疏性。λ越小惩罚越弱选出来的特征越多模型越贴近普通回归。λ越大惩罚越强压缩力度越大趋近于无穷大的时候所有系数都变成0一个特征都留不下来。实际使用的时候就是把λ从0到很大的区间内取一串值分别训练模型观察不同λ下面哪些特征先被踢出局、哪些最后还在模型里。这个过程用图表示出来就是特征轨迹图——每一条线代表一个特征的系数随λ变化而变化的路径线掉到0越早说明该特征越早被判为不重要。L2惩罚的系数路径是渐进接近0但不到0LASSO的系数路径则是直线掉到0并停在0。这个微小差别在特征选择场景里就是天壤之别。2. 先确认场景你的数据适不适合用LASSO2.1 适用场景清单LASSO不是万能的我用下来感觉这几种场景最合适特征数量比样本量还大典型的p n问题需要快速压缩维度。特征之间相关性较高普通回归系数不稳定LASSO会自动在相关变量里挑一个有代表性的留下。建模目标中有明确的“挑选关键特征”诉求比如要交付给业务方一份精简直观的解释模型。做了全变量回归之后想用LASSO的结果作为二次建模前的预筛步骤。不太合适的场景也有几种。如果所有特征都已经确认重要且样本量充足LASSO的压缩反而会带来不必要的偏差。如果特征之间是高度非线性关系纯线性LASSO效果有限需要考虑扩展模型或者换非线性算法。2.2 几种特征选择方法的对照我一直建议团队在做方法选型的时候先拉一个对照表省得选错路子。方法稀疏性共线性处理计算开销特征选择方式稳定程度LASSO强较好低系数压到0自动剔除中岭回归无较好低系数缩小但不归零较好弹性网络强很好中结合了两种惩罚较好逐步回归弱较差高逐步增删变量差逐步回归是我最早用的方法后来逐渐弃用了。它的问题在于每一步的选择都依赖前一步的结果路径依赖性太强样本一变选择的变量就变了。LASSO本质上是全局优化所有变量放在同一套惩罚框架下竞争结果相对更稳定、更客观。2.3 数据预处理决定后续成败的动作很多人在这个环节翻车。MATLAB的lasso函数调用非常简单但在调用之前数据结构如果不处理好后面全是问题。数值型特征必须标准化。LASSO的惩罚是针对系数幅度的如果某个特征的量纲特别大比如几千几万的数值那它的系数天然就小容易被误杀。中心化加缩放把所有特征放到同一尺度惩罚才能公平。缺失值要么删、要么填不能直接丢给lasso。MATLAB默认不支持缺失值通常用均值、中位数填充或者用fillmissing做线性插值。注意填充方式会影响结果最好先看缺失比例再做决策。类别型特征要处理成哑变量。如果你有“渠道类型”这种分类变量直接编码成0/1哑变量再参与标准化。顺带提醒一个类别拆成n个哑变量之后LASSO可能只选取其中一两个这其实是合理的说明只有那几个类别对目标有区分度。离群值要提前识别。LASSO对极端值相对敏感个别离谱的样本可能会把某个无关特征的系数强行拉高。可以用箱线图或者MAD方式简单检查严重的直接剔除或做截尾处理。3. 3步完成MATLAB下的LASSO特征选择实战3.1 准备模拟数据并加载工具箱MATLAB做LASSO需要Statistics and Machine Learning Toolbox绝大多数学校和企业授权版本都有。如果你的环境没有这个工具箱后面讲的lasso和lassoglm函数都没法用可以先检查一下。为了演示我们造一组模拟数据100个样本10个真实特征里只有3个和标签线性相关再加上90个纯噪声特征总共100个特征模拟“特征多、有效少”的真实困境。rng(42); n 100; % 样本数 p 100; % 特征总数 X randn(n, p); beta_true zeros(p, 1); beta_true(1) 3; beta_true(2) -2; beta_true(10) 1.5; y X * beta_true 0.5 * randn(n, 1);这里把真实重要的特征放在第1、2、10维上其余97个都是噪声。这样后面就能对照检验LASSO有没有把真正相关的特征挑出来。3.2 核心调用一行命令跑完交叉验证MATLAB的lasso函数封装得相当到位常见的调用方式是X_std zscore(X); % 特征标准化 [B, FitInfo] lasso(X_std, y, CV, 10);解释一下输出B是一个p×L的矩阵p代表特征数量L代表测试过的Lambda数量。B的第j列就是使用第j个Lambda时训练出的全部特征系数。FitInfo是结构体里面存了每一次Lambda对应的交叉验证均方误差MSE还有最优Lambda的索引。具体要看的字段是FitInfo.LambdaLambda序列、FitInfo.IndexMinMSE最小MSE对应的Lambda索引、FitInfo.Index1SE最简模型索引。交叉验证次数我默认用了10折这个参数可以按样本量调整。样本量少于100的时候建议用5折避免每折数据太少导致验证结果抖动。用一组返回值里最关键的IndexMinMSE对应的Lambda连起来就能找到预测误差最小的模型。但注意一个细节IndexMinMSE对应的模型虽然误差最小但未必是最高效的。交叉验证误差曲线在最小值附近通常比较平缓你可以选择一个稍大一点的Lambda获得几乎一样的精度却少很多特征。这就是Index1SE的用处。3.3 读图轨迹图和交叉验证曲线怎么配合单独看数值容易懵我强烈建议先把图画出来再决策。下面两行代码可以完成lassoPlot(B, FitInfo, PlotType, Lambda, XScale, log); lassoPlot(B, FitInfo, PlotType, CV);第一张是特征轨迹图横轴是对数尺度的Lambda纵轴是系数值。密密麻麻的折线里那些从头到尾都贴着0走的线就是全程无关的特征。真正有用的特征会在图中占据明显位置系数有一段远离0的轨迹。第二张是交叉验证MSE曲线图。横轴同样是Lambda纵轴是MSE图上一般还会标出最小MSE位置和最简模型位置。我习惯先看第二张确定Lambda的有效区间再看第一张确认哪些特征在这个区间内非零。实操时有个经验不要盯着IndexMinMSE不放优先看Index1SE。1SE的含义是在最小MSE一个标准误范围内选择最精简的模型这种做法能显著降低选入无效特征的概率模型稳定性更好。3.4 提取最终特征清单确定Lambda之后把它对应的系数取出来然后筛出非零系数对应的特征序号。idx FitInfo.Index1SE; lambda_opt FitInfo.Lambda(idx); beta_selected B(:, idx); selected_idx find(abs(beta_selected) 1e-6); selected_features selected_idx; selected_coeffs beta_selected(selected_idx); disp(选中的特征序号:); disp(selected_features); disp(对应系数:); disp(selected_coeffs);注意比较系数时要留一点容差。因为数值计算的浮点误差理论上应该等于0的系数可能留下一个1e-12级别的残余值用abs(beta) 1e-6做阈值比较稳妥。我手头这个模拟数据的典型输出会选中第1、2、10个特征偶尔参数扰动下可能多选一个噪声特征。这个多选的情况很常见完全不用慌后续可以用线性回归再验证一次。到这里特征清单已经出来了LASSO回归特征选择的核心流程就跑完了。4. 进阶操作让LASSO选出的特征真正可靠4.1 用信息准则自动选择Lambda交叉验证是选Lambda的默认方案但它依赖随机折分每次跑出来Index可能略有变化。如果你想更稳定可以改用BIC贝叶斯信息准则选择Lambda。[B_full, FitInfo_full] lasso(X_std, y); n length(y); k sum(B_full ~ 0, 1); mse_vals FitInfo_full.MSE; % 注意这里不是交叉验证MSE bic n * log(mse_vals) k * log(n); [~, idx_bic] min(bic); lambda_bic FitInfo_full.Lambda(idx_bic);BIC对模型复杂度有更强的惩罚选出来的模型往往比交叉验证更稀疏。我实际对比过很多数据集BIC的结果在可解释性上通常更友好但预测精度会略微牺牲。如果你做特征选择的目的偏向“解释归因”我推荐BIC如果偏向“预测效果”交叉验证更稳妥。4.2 稳定性检验看看哪些特征是真的稳LASSO在样本量有限的情况下容易挑出假相关特征。一种靠谱的处理方式是做多轮子采样检验每次随机抽取80%样本跑一次lasso记录被选中的特征重复100次然后统计每个特征的出现频率。n_bootstrap 100; selected_freq zeros(1, p); for b 1:n_bootstrap idx_sample randsample(n, round(n * 0.8), true); [B_boot, FitInfo_boot] lasso(X_std(idx_sample, :), y(idx_sample), CV, 5); beta_boot B_boot(:, FitInfo_boot.Index1SE); selected_freq selected_freq (abs(beta_boot) 1e-6); end selected_freq selected_freq / n_bootstrap;出现频率超过80%的特征可以视为稳定特征低于50%的多半是随机噪声带起来的假信号。这招在学术论文和内部交付里都非常加好感因为别人问起“这个特征怎么选出来的”你可以拿出一张频率表而不是一句“我用lasso跑出来的”。4.3 分类问题中的Lasso变种如果你的标签不是连续值而是0/1分类就要用lassoglm替代lasso。基础调用如下[B_glm, FitInfo_glm] lassoglm(X_std, y_binary, binomial, CV, 10);lassoglm背后的拟合对象是广义线性模型损失函数换成了二分类交叉熵选择的特征系数意义和二项Logistic回归一致。选出来的特征再喂给fitglm做一次Logistic回归模型就有完整的p值和置信区间了。这套流程在信用评分、客户响应预测这类场景里非常成熟。4.4 选完特征之后务必再走一遍完整回归有个常见误区是把lasso给出的系数直接当作最终模型。LASSO的系数是有偏估计它为了压缩特征牺牲了无偏性。因此用LASSO选特征再用普通最小二乘回归训练选出来的特征集合组合拳效果最好。X_final X_std(:, selected_features); mdl fitlm(X_final, y); disp(mdl);这一步相当于给LASSO的初筛结果做一次“精修”选出的特征保留下来但是把系数还回标准的、无偏的值。实际交付模型的系数解释、显著性检验都基于这个精修结果。我做过很多次对比精修之后的测试集误差往往比直接用lasso系数还要小一些。5. 踩坑实录LASSO特征选择的几个关键坑5.1 标准化丢掉了“业务可解释性”标准化之后模型系数对应的是“特征每变化一个标准差标签变化多少”而不是“特征每变化一个原始单位标签变化多少”。业务方看到系数3.2可能会问这个3.2是什么单位所以我通常会在最终交付时进行一次系数还原把标准化系数的值乘以原始特征的标准差得到可解释的原始尺度系数。如果后续用的是fitlm重新拟合那么在训练之前可以基于原始尺度直接拟合一次并输出系数解释给业务时会更顺。5.2 量纲敏感与特殊特征处理某些特征方差特别大标准化后会被无意中放大权重。我遇到过一个场景广告曝光量原始值上千万标准化后依然是一个强信号但业务方知道这个指标波动大、记录质量差并不希望它进入模型。这种属于先验的业务约束预先在候选特征池里剔除就好不要指望lasso帮你做主观决策。5.3 Lambda的搜索范围有时候不够默认Lambda序列的最大值可能不够大导致交叉验证曲线看不到右边上升段。这时可以手动指定Lambda序列比如lambda_seq logspace(log10(0.001), log10(0.5), 100); [B_manual, FitInfo_manual] lasso(X_std, y, Lambda, lambda_seq, CV, 10);注意Lambda序列需要从非常小的值到足够大的值覆盖模型从过拟合到欠拟合的过渡区间。看不到曲线右侧上扬说明最大Lambda还没把特征压干净。5.4 类别变量没处理成哑变量直接把类别型变量硬编码成1到k的整数会让LASSO把类别之间的顺序当作数值信息来用结果毫无意义。正确做法是转成哑变量矩阵。假设渠道类型有A、B、C三种则编码为三列0/1通常留一列作为基准列即可否则会引入冗余。channels categories(categorical(channel_data)); dummy zeros(n, length(channels) - 1); for i 2:length(channels) dummy(:, i - 1) double(channel_data channels(i)); end5.5 小心高相关特征组的“随机挑选”当两个特征高度相关时LASSO倾向于只选其中一个。但这个“选哪个”有一定随机性可能两组相关变量在不同的子样本里被交替选中。如果业务方坚持要看到某个业务上非常重要的特征出现在最终模型中可以先用组结构的方式处理或者使用弹性网络把相关变量的系数拉向平均减少这种随机性。MATLAB里弹性网络要在lassoglm中用Alpha参数设置lasso函数本身不直接支持弹性网络需要一点额外处理。5.6 别把“特征被剔除”直接解读为“特征无效”这是很多人在项目汇报时被问倒的地方。LASSO没选某个特征不代表它对标签没有影响只代表在当前特征集合、当前惩罚强度下它对模型没有足够的边际贡献。尤其当两个特征高度相关时被剔除的那个可能仍然很重要只是它的信息可以被另一个特征完全替代。汇报的时候用“共线性替代”这个说法会更准确也容易应对业务方的追问。回归到整体方法论上我现在处理特征选择问题的标准化流程已经固定下来先做数据清洗和标准化然后跑一次带交叉验证的lasso画出轨迹图和CV曲线以1SE准则圈定候选特征用子采样频率验证稳定性最后用普通回归精修系数并输出业务结果。这套流程在回归、分类、时间序列预处理里都能复用。每次遇到一张巨大的特征表我都会想起最开始说的那个画面——一堆特征拥挤不堪LASSO像个挑剔的厨师只把真正提味的那几味料留下来。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑