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

60 个样本、50 维,真值只有 5 个非零。 加 ℓ1 罚项,λ 等于 0.2 时 48 个非零,λ 等于 4 时只剩 9 个。 同一份数据换 ℓ2,四个 λ 跑下来非零个数全是 50,一个都没关掉。 测试误差最低是 0.126(λ 等于 2),再往上压到 0.130,因为真值被压没了。

📝 本节和前面几节的关系

4.2 的 ℓ2 只能收缩:所有系数都变小,没有一个是零。 这一节只把那个指数从 2 换成 1,结果就完全不同——系数会被压成精确的零。 这就是稀疏学习。它不只是正则化,它顺带把「选变量」这件事做完了。 第 5 章剩下的四节,全部在回答「这个零是怎么来的、能不能更稀疏、有什么代价」。

一、从球到菱形

ℓ1 约束的写法是:在「系数绝对值之和不超过 t」这个范围里找误差最小的点。 二维时这个范围是一个菱形,四个顶点正好落在坐标轴上。 几何上的关键差别只有一句:球的边界处处光滑,菱形的边界有棱角。 误差曲面的等高线一圈圈缩小时,一旦碰到棱角,最优点就停在那里——而棱角意味着「某个坐标为零」。 这就是稀疏性的全部来源,不需要任何概率假设。 换个说法:ℓ2 把系数往原点拽,拽到一半停在半路; ℓ1 也往原点拽,但它的「路」是沿着坐标轴铺的,所以容易正好停在轴上。 维数一高,这个差别被放大。 高维空间里菱形的顶点数是 2 的 d 次方量级,而球的边界只有一个光滑曲面。 所以 50 维时 ℓ1 几乎必然会关掉一大批系数,这不是巧合,是几何。

二、目标函数与那张表

把约束写成罚项:目标等于残差平方和的一半,加上 λ 乘系数绝对值之和。 λ 越大,罚得越狠,非零个数越少。 它和 4.2 的岭回归只差一个指数,形式上一模一样。 但性质上差了一个根本性的东西:绝对值函数在零点不可导。 所以 ℓ1 最小二乘没有闭式解,4.2 那个 (A 转置 A 加 λI) 的逆在这里不存在。 求解要靠迭代,下一节专门讲。

λ 训练 MSE 测试 MSE 非零个数
0.2 0.014 0.191 48
0.5 0.021 0.149 41
1.0 0.035 0.133 32
2.0 0.058 0.126 18
4.0 0.090 0.130 9
无约束最小二乘是 50 个非零、测试误差 0.287。
所以这里每一行都比它好,最好的那一行好了一倍多。
原因很简单:50 维、60 个样本,无约束解把噪声也拟合进去了。
看 λ 从 2 到 4:非零从 18 掉到 9,测试误差从 0.126 微涨到 0.130。
真值只有 5 个,所以 9 个里还有 4 个是噪声。
再往上压,噪声掉光了,真值也开始掉——这就是误差曲线的谷底。
对照 ℓ2 那张表:λ 从 0.5 加到 32,非零个数一直是 50。
λ 等于 32 时训练误差 1.019、测试 2.075,模型已经废了,但系数一个都没归零。
「只收缩、不选择」这六个字,看这一列最清楚。

三、稀疏不等于正确

Demo 里 λ 等于 4 时留下 9 个非零,而真值只有 5 个。 多出来的 4 个是噪声列——它们和 y 偶然有点相关,被 l1 当成了信号。 反过来,λ 再大一点,真值也会被误杀。 尤其当两个真变量大小差很多时,小的那个先死。 这不是算法有 bug,这是「用偏差换稀疏」的必然账单。 所以「l1 选出来的变量就是重要变量」这句话不能信。 它选的是「在这个 λ 下、对拟合最划算的那几个」,不是「物理上真正重要的那几个」。 要看重要性,得看整条 λ 路径上系数怎么变,而不是某一个 λ 的解。 还有一件事必须说:这份数据是我造的「干净题」——特征彼此独立、真值稀疏、样本充足。 三个条件缺一个,l1 的表现就会掉。 特征相关时它会在相关的一群里随机挑一个,选出来的下标每次都不一样; 真值不稀疏时它硬要稀疏,等于在撒谎。

四、和 4.2 的分工

ℓ2 负责稳:它压所有方向,专治共线和病态,但永远不给你零。 ℓ1 负责选:它把不重要的直接归零,代价是不可导、要迭代、结果不稳。 两个都要的时候,把它们加起来——那是 5.5 的弹性网。 实践里怎么选?先看你要什么。 要「从 5000 个基因里挑 20 个」——用 ℓ1。 要「5000 个特征全留着,但别让它过拟合」——用 ℓ2。 既要又要——弹性网。

五、动手 Demo:ℓ1 与 ℓ2:λ 等于 4 时 ℓ1 只剩 9 个非零,ℓ2 一个都没关掉

60 个训练样本、50 维特征,真值只在下标 2、7、15、23、41 上非零,系数是 2.0、负 1.6、1.2、负 0.9、 0.7,其余 45 维是纯噪声。另留 300 个点算测试误差。 对 ℓ1 和 ℓ2 各扫一组 λ,记录训练误差、测试误差、以及系数绝对值大于 1e-8 的个数。

def soft(z, t):
    return np.sign(z) * np.maximum(np.abs(z) - t, 0.0)

def lasso_cd(A, y, lam, it=400):
    b = A.shape[1]
    w = np.zeros(b)
    col = (A * A).sum(0) + 1e-12      # 每列的平方和,一维子问题的分母
    r = y - A @ w
    for _ in range(it):
        for j in range(b):
            r += A[:, j] * w[j]        # 先把第 j 个系数的影响加回残差
            rho = float(A[:, j] @ r)
            w[j] = soft(rho, lam) / col[j]   # 一维子问题的闭式解
            r -= A[:, j] * w[j]        # 再减掉新值的影响
    return w

实跑输出:

--------------------------------------------------------------------
l1 与 l2:l1 在 λ 等于 2 时只剩 18 个非零,l2 一个都没关掉
--------------------------------------------------------------------
  数据                     60 个样本、50 维,真值只有 5 个非零
  真值下标                 2、7、15、23、41
  无约束最小二乘           非零系数 50 个,测试 MSE 0.287
  求解方式                 l1 用坐标下降,l2 用闭式解,都不需要调步长

  l1 约束(λ 是罚项强度):

    λ   训练 MSE   测试 MSE   非零个数
  -----------------------------------
  0.2      0.014      0.191         48
  0.5      0.021      0.149         41
  1.0      0.035      0.133         32
  2.0      0.058      0.126         18
  4.0      0.090      0.130          9

  l2 约束(同一个 λ 的量级不可比,只看趋势):

     λ   训练 MSE   测试 MSE   非零个数
  ------------------------------------
   0.5      0.014      0.237         50
   2.0      0.031      0.245         50
   8.0      0.183      0.610         50
  32.0      1.019      2.075         50

  **先看 l2 那张表的最后一列:4 个 λ,非零个数全是 50。**
  **这就是 4.2 讲的「只收缩、不选择」——λ 加到 32,每个系数都还在,只是都变小。**
  **再看 l1:λ 等于 0.2 时非零 48 个,λ 等于 4 时只剩 9 个。**
  **l1 会把系数压成精确的零,所以它顺带做完了「选变量」这件事。**
  **为什么差一个指数就完全不同?**
  **因为 l1 的约束域是菱形,顶点在坐标轴上;l2 的是球,边界处处光滑。**
  **误差曲面等高线一碰到「有棱角」的约束域,最优点就落在棱角上,而棱角意味着某些坐标为零。**
  **这是稀疏性的几何来源,也是第 5 章全部内容的起点。**
  **注意 λ 等于 4 时测试 MSE 0.130,比 λ 等于 2 的 0.126 差——压得太狠,真值被压没了。**
  要说明:**数据是我造的**,特征彼此独立,所以这题对 l1 特别友好。
  **特征一相关,l1 的表现就掉——那正是 5.5 节弹性网要处理的问题。**

soft 这个函数是整个第 5 章的中心。 它做的事:把绝对值小于 λ 的数直接摁成零,大于 λ 的往零的方向挪 λ。 「摁成零」这一步就是稀疏的来源——连续函数做不到这件事,只有带拐点的函数能做到。 注意残差 r 的增量维护:加回旧值、算内积、写新值、减掉新值。 这套写法让每次更新只花 O(n) 而不是 O(nb),是一个纯粹的工程优化。 它不影响结果,但让 400 轮乘 50 个坐标能在毫秒级跑完。 col 那个 1e-12 是防零列。 如果某列全是零(真实数据里的常数列),平方和是 0,除下去会得到 inf 或 nan,整批系数全毁。 这类小防护比算法本身更容易被漏掉。 ℓ1 那张表的训练误差一路涨:0.014、0.021、0.035、0.058、0.090。 这是罚项的必然结果——λ 越大,越不允许你贴数据。 而测试误差先降后升,谷底在 0.126。 两条曲线的分歧,就是「欠拟合与过拟合」的完整样子。 ℓ2 那张表的 λ 量级不能和 ℓ1 比。 因为 ℓ1 罚的是绝对值之和(量级 10 左右),ℓ2 罚的是平方和(量级 100 左右), 同一个 λ 对两者的压力差一个数量级。 要比只能比趋势,不能比数值。 要说明:数据是我造的(50 维独立高斯,真值稀疏)。 真实数据里特征相关,l1 的表现会比这张表难看,那是 5.5 要处理的问题。

30 秒实验

  1. 把 λ 网格加密到 0.1 到 6 之间 20 个点,画出「非零个数」和「测试误差」两条曲线。 它们的最低点在同一格吗?
  2. 把真值改成 5 个系数都很小(0.1 量级),再跑一遍。λ 到多少时这 5 个真值全被压没了?
  3. 把样本数从 60 降到 20,维数不变,再跑。无约束解会发生什么?ℓ1 的优势扩大了多少倍?

常见误区

  1. 「ℓ1 就是加了绝对值罚项的岭回归」。 差一个指数,性质完全不同:ℓ2 有闭式解、永远不稀疏;ℓ1 没有闭式解、会把系数压成零。 一个是收缩,一个是选择,别当成同一类工具。
  2. 「l1 选出来的就是重要变量」。 它选的是「这个 λ 下最划算的一批」,不是「物理上最重要的几个」。 特征相关时它随机挑;真值不稀疏时它硬凑。 要判断重要性,看 λ 路径上系数的变化,别看单点解。
  3. 「λ 越大越稀疏,所以越大越好」。 λ 大到一定程度,所有系数归零,模型输出恒等于零,那是「什么都不预测」。 稀疏是手段,不是目的;目的是测试误差低。
  4. 「先做 l1 选出变量,再拿这几个变量做普通最小二乘,误差会更小」。 这是两步法,也叫先筛后回归。 它有两个毛病:筛的时候用了 y,第二步又用同一个 y,误差估计会偏乐观; 而且筛错一次就再也补不回来。 要用两步法,得在独立的验证集上筛。
自测三问(点开对答案) 1. 为什么 ℓ1 的约束域有棱角,解就容易落在坐标轴上? —— **因为最优点出现在「误差等高线与约束域相切」的位置。** **光滑曲面上切点是任意的,一般不在坐标轴上;** **而棱角是一个尖点,等高线缩到那里时会被「卡住」,只能停在这个尖点上。** **尖点的坐标里天然有零。** 2. ℓ1 最小二乘为什么没有闭式解? —— **因为绝对值函数在零点不可导,求导之后得不到一个线性方程组。** **ℓ2 能出闭式解,靠的是「平方的导数是线性的」,** **最后化成一个 (A 转置 A 加 λI) 乘 θ 等于 A 转置 y。** **ℓ1 的导数在零点是一个区间(次梯度),方程变成不等式组,只能迭代求。** 3. 如果业务要「既稳又稀疏」,该怎么办? —— **把 ℓ1 和 ℓ2 加起来,就是弹性网(5.5)。** **ℓ1 负责把不重要的压成零,ℓ2 负责在相关特征之间稳定地分配、** **并在特征数超过样本数时保住解的唯一性。** **代价是多了一个超参,调参成本翻倍。**

ℓ1 没有闭式解,那到底怎么算?下一节给两种解法:坐标下降和近端梯度。 两者八竿子打不着,却收敛到同一个点——参数差 1e-10 量级。

相关笔记