🤖 第5章 稀疏学习
📄 一句话概括

同一个 50 维问题,看「选得准不准」:λ 等于 2.0 时选中 18 个,精确率 28%、召回率 100%。 λ 等于 8.0 时选中 5 个,精确率 100%、召回率 100%,但测试误差从 0.126 涨到 0.172。 相关系数法取前 5 个,命中 4 个——它是个很强的对手,别小看单变量筛选。

📝 本节和前面几节的关系

5.1 讲「ℓ1 会把系数压成零」,5.2 讲「怎么算」。这一节问的是「压成零的那些,压对了吗」。 答案是不一定。 选得少和选得准是两个目标,λ 只能同时压一个——这一节就是这条权衡曲线的实证。

一、「选变量」到底在选什么

ℓ1 的解里,非零系数的下标集合叫支持集。 特征选择就是「相信这个支持集等于真变量的集合」。 这句话听着自然,其实要求很苛刻:它要求真值真的稀疏,而且信号强到能被分辨出来。 两个指标衡量它:精确率等于选中的里面有多少是真的,召回率等于真的里面有多少被选中。 它们是一对矛盾:λ 小,选得多,召回高、精确低;λ 大,选得少,精确高、召回低。 很多人只看「选出来几个」,这是不够的。 选出 5 个、其中 3 个是噪声,和选出 18 个、其中 5 个是真的,哪个更好? 要看下游任务——如果下游只是预测,18 个那个误差更低; 如果下游要花钱做实验验证,5 个里错 3 个就很贵。

二、实测:一条权衡曲线

λ 选中个数 精确率 召回率 测试 MSE
2.0 18 28% 100% 0.126
4.0 9 56% 100% 0.130
6.0 7 71% 100% 0.145
8.0 5 100% 100% 0.172
12.0 5 100% 100% 0.248
20.0 5 100% 100% 0.484
看召回率那一列:六个 λ 全是 100%,一个真变量都没漏。
所以这里唯一的问题是多选,不是漏选。
这题对 ℓ1 太友好了——特征独立、信号强(系数 0.7 到 2.0)、样本 60 个对 50 维。
λ 从 8 加到 20,选中个数一直是 5、精确率一直是 100%,但测试误差从 0.172 涨到 0.484。
为什么?因为「选中了谁」没变,「选中的系数多大」变了。
这五行里 λ 在持续压缩那 5 个系数的幅度,于是预测越来越钝。
所以「精确率 100%」不等于「模型好」。
这是本节最该记住的一句话:选对了变量,还有一个「系数估得准不准」的问题,后者靠 λ 调不了。
最优的测试误差在 λ 等于 2,精确率只有 28%。
也就是说:为了预测最准,你得多留 13 个噪声变量。
预测和解释在这里是冲突的——这两件事要分开做,不能指望一个 λ 同时满足。

三、和相关系数法比

相关系数法:算每一列和 y 的相关系数,取绝对值最大的前 5 个。 Demo 里它命中 4 个,漏了 1 个。 漏的那个是系数 0.7 的那个——信号最弱。 这个方法看着土,但在「特征独立」的场景里非常强,而且计算量是 O(nb)、不需要迭代。 它的致命弱点在特征相关时:两个高度相关的列,只要其中一个和 y 相关,另一个也会被选上, 而 ℓ1 在这种情况下至少只会挑一个。 更本质的差别:相关系数法只看「单个特征和 y 的关系」,ℓ1 看的是「在已有变量的基础上, 这个特征还能带来多少」。 前者会重复选同一类信息,后者不会。 所以真要选变量,两个都跑、对比着看,比只信一个稳。

四、陷阱:先筛后回归

最自然的做法是「先用 ℓ1 选出 5 个,再用这 5 个做普通最小二乘」。 听起来合理,其实有偏:筛的时候用了 y,第二步又用同一个 y, 第二遍等于在「已知答案」的数据上评估。 结果一定是训练误差很漂亮、测试误差没改善。 这个偏差有个名字叫选择偏差。 严格的解法是在独立的验证集上筛,或者干脆别做两步——ℓ1 本身就是「同时拟合和选择」。 这一点和 4.3 的数据泄漏是同一个病根:决策用过的数据,不能再用来评估。 还有一个更隐蔽的问题:λ 是怎么选的。 如果在测试集上挑 λ,那报出来的精确率也是乐观的。 Demo 里「最优 λ 等于 2」这个结论,本来就是我在测试集上挑的——真实项目里得用交叉验证。

五、什么时候 l1 选变量会失灵

三种情况:特征高度相关、真值不稀疏、信号太弱。 第一种,ℓ1 在相关的一群里随机挑一个,换种子结果就变;第二种,它硬要稀疏,等于在撒谎; 第三种,弱信号和噪声分不开。 还有一个「维数」问题:特征数 b 远大于样本数 n 时,ℓ1 最多只能选出 n 个非零。 这不是算法缺陷,是线性代数的硬约束——n 个方程解不出 b 个未知数。 想突破它,得靠弹性网(5.5)或者先做降维。

六、动手 Demo:特征选择:λ 等于 8 时选出 5 个全中,相关系数法 5 个中 4 个

还是那份 60 点、50 维的数据,真变量在下标 2、7、15、23、41。 对六个 λ 分别求 ℓ1 解,把「绝对值大于 1e-8」的下标当作选中集,和真值集合比对,算精确率、召回率、 测试误差。另外用「和 y 的相关系数绝对值」排序取前 5 个,作为对照。

corr = np.abs((_X * _Y[:, None]).mean(0) / (_X.std(0) * _Y.std()))
top = set(np.argsort(-corr)[:5])                    # 相关系数法:取前 5 个

for lam in (2.0, 4.0, 6.0, 8.0, 12.0, 20.0):
    sel = set(np.where(np.abs(lasso_cd(_X, _Y, lam)) > 1e-8)[0])
    tp = len(sel & set(_IDX))                        # 选中且是真的
    print(lam, len(sel), tp / len(sel), tp / len(_IDX),
          mse(_XT @ lasso_cd(_X, _Y, lam), _YT))

实跑输出:

--------------------------------------------------------------------
特征选择:l1 在 λ 等于 2.0 时选出 18 个、全中;相关系数法 5 个中 4 个
--------------------------------------------------------------------
  数据                     50 维里只有 5 个真变量,其余 45 个纯噪声
  评价                     精确率等于选中的真变量占选中数的比例
  相关系数法               按 |x 与 y 的相关| 排序取前 5 个,命中 4 个

     λ   选中个数   精确率   召回率   测试 MSE
  -------------------------------------------
   2.0         18      28%     100%      0.126
   4.0          9      56%     100%      0.130
   6.0          7      71%     100%      0.145
   8.0          5     100%     100%      0.172
  12.0          5     100%     100%      0.248
  20.0          5     100%     100%      0.484

  **相关系数法的结果先摆出来:取相关最强的 5 个,命中 4 个。**
  **它看着朴素,其实是个很强的对手——因为这份数据的特征互相独立,单变量相关就是最直接的信号。**
  **l1 最好的一行是 λ 等于 2.0:选中 18 个,精确率 28%、召回率 100%,测试 MSE 0.126。**
  **注意 λ 小的时候选中个数远多于 5,精确率掉下去;λ 大的时候又开始漏掉真变量。**
  **所以「选出几个」这件事本身也要调,而调它的依据只能是留出集或者交叉验证。**
  **这里有个重要区别:l1 是「同时」做拟合和选择,不是「先筛变量再回归」。**
  **先筛后回归有个致命问题:筛的时候用了 y,回归的时候又用同一个 y,误差估计会偏乐观,而且筛错一次就再也补不回来。**
  要说明:**这份数据是特意造的「干净题」:特征独立、真值稀疏、样本充足。**
  **真实数据里特征相关,l1 会在相关的一群里随机挑一个,选出来的下标每次都不一样。**

阈值 1e-8 是「算不算零」的分界线。 软阈值输出的零是精确的零(np.maximum 直接给 0),所以 1e-8 足够区分。 但如果你用的是次梯度法,系数会在 1e-5 附近晃, 这个阈值就得放大——那时「非零个数」这个指标就不可靠了。 相关系数那行有个细节:我用了中心化的内积除以标准差乘积。 省掉中心化(直接用均值内积)在 y 均值不为零时会整体偏移,排序就变了。 这类小地方错了不报错,只是结果悄悄不对。 lasso_cd 在循环里被调了两次(算指标一次、算误差一次)。 这是为了代码短,真实代码里应该只算一次、存下来。 这里两次结果完全一样,因为坐标下降是确定性的、初值恒为零。 注意 λ 从 8 到 20 那三行:选中个数、精确率、召回率一模一样,测试误差却翻了三倍。 如果你只监控「选了哪些变量」, 这三次迭代在你的报表里会显示「没有任何变化」——而模型其实已经不行了。 所以变量选择的监控指标,和对预测的监控指标,必须分开看。 要说明:这份数据是特意造的干净题(特征独立、真值稀疏、系数 0.7 到 2.0)。 真实数据里相关特征会让 l1 随机挑一个,选出来的下标换个种子就变, 那时候「精确率」这个指标本身就有很大方差。

30 秒实验

  1. 把真值改成 5 个系数都是 0.15(信号很弱),再跑一遍。λ 到多少时召回率开始掉?
  2. 造两列高度相关的特征(相关 0.99),都放进真值。ℓ1 会选中几个?换个随机种子还是同一个吗?
  3. 把样本数从 60 降到 30,维数保持 50。ℓ1 最多能选出几个非零?和这个上限对得上吗?

常见误区

  1. 「精确率和召回率都 100%,说明模型完美」。 Demo 里 λ 等于 8 到 20 都是双 100%,测试误差却从 0.172 涨到 0.484。 因为变量选对了,系数被压小了。 指标只覆盖「选了谁」,不覆盖「估得准不准」。
  2. 「l1 选出的变量比相关系数法可信」。 不一定。特征独立时相关系数法又便宜又准(Demo 里 5 中 4);特征相关时 ℓ1 反而更稳。 两个都跑、对比着看,比只信一个稳。
  3. 「先用 l1 选变量,再拿选出来的做最小二乘,效果更好」。 这是选择偏差:两步都用了同一批 y,第二步的误差估计会偏乐观。 要这么做,必须在一份没参与筛选的数据上做第二步。
  4. 「选出的变量越多,模型越可靠」。 反了。选得多意味着精确率低,λ 小的时候 18 个里有 13 个是噪声。 多选不会让预测更差(Demo 里 λ 等于 2 的预测最好),但会让解释彻底失真。
自测三问(点开对答案) 1. 为什么 λ 从 8 加到 20,测试误差涨了三倍而选中个数不变? —— **因为 λ 还在继续压那 5 个非零系数的幅度。** **软阈值对非零系数是「减掉 λ」,λ 越大减得越多,系数越小,预测越钝。** **所以「非零」有两种状态:还活着,但快被压死了。** 2. 相关系数法在什么情况下会输给 ℓ1? —— **特征高度相关的时候。** **两个相关列只要其中一个和 y 相关,另一个的相关系数也会很高,** **于是两列都被选上——信息重复,等于白占名额。** **ℓ1 在同样情况下至少只挑一个,因为它看的是「增量贡献」。** 3. 如果 b 是 1000、n 是 100,ℓ1 最多能选出几个变量? —— **最多 100 个,也就是 n 个。** **因为 ℓ1 的解是「用尽量少的列拼出 y」,而 100 个方程最多确定 100 个未知数。** **想突破这个上限,得用弹性网(ℓ2 部分让解在相关方向上摊开)或者先降维。**

ℓ1 用 p 等于 1 这个指数换来了稀疏和凸。如果把 p 再往下调,稀疏性会更强—— 但代价是问题不再是凸的。下一节就来看这条「稀有度与可解性」的分界线。

相关笔记