🧮 第2章 有监督学习
📄 一句话概括

正则化就是在损失函数上再加一道罚款,罚系数太大。 加系数平方和叫岭回归(L2),系数会一起变小但不为零; 加系数绝对值和叫 Lasso(L1),一部分系数会被压成恰好零。 实测 8 个特征里 3 个真有用、还故意造了共线,不罚款时系数冒到 −7.9453 与 +8.9080 一对; 岭回归把测试 MSE 从 0.2458 压到 0.1373;Lasso 在 λ = 4 时剩下 5 个非零系数。

📝 本节和前面几节的关系

2.1 节的线性回归在「特征都独立、样本足够」时没问题。 但真实数据常常是特征彼此相关(共线性)、特征比样本还多,这时最小二乘的解会变得极不稳定。 正则化就是给这种病开的药。 它也是全部十七个算法里唯一一个「不改变模型形式、只改损失函数」的算法—— 所以它可以挂到逻辑回归、神经网络上,成为通用技巧。

一、先看它治的是什么病

教材举的例子是「特征之间高度相关」。这话听着抽象,但有个非常具体的后果:系数的解不再唯一。 设想两个特征几乎一模一样(比如「身高厘米」和「身高毫米」)。 模型完全可以用 1.0 的系数配第一个、−9.0 的配第二个,合起来仍然正确。 数学上这无数个解都一样好,但算法只会挑一个给你,挑到哪个取决于数值细节——换个随机种子, 系数就换一套。 更糟的是噪声。共线性让矩阵接近奇异,求逆时把极小的数值误差放大成巨大的系数。 Demo 里就出现了这一对:−7.9453 与 +8.9080,一个正一个负、几乎抵消。 合起来看模型没问题,单个系数已经毫无意义。 正则化的思路很直接:既然「系数大」是病根,那就在损失里加一项惩罚系数大小。 系数想变大多解释一点数据,就得先交罚款——最后停在一个折中点。

二、岭回归:罚平方,把系数一起按小

岭回归(Ridge)在残差平方和后面加上 λ 乘系数平方和。 λ 是人工设的强度,等于 0 就退回普通最小二乘,越大罚得越狠。 这个改动让解从 w = (XᵀX)⁻¹Xᵀy 变成 w = (XᵀX + λI)⁻¹Xᵀy。 差别只在那个 +λI—— 它在矩阵对角线上加了一个正数,让本来接近奇异的矩阵变得可逆。 这是岭回归在数值上稳的根本原因。 注意公式里的 I 是单位矩阵,但截距那一项通常不罚(Demo 里就是 A[0, 0] -= lam)。 因为截距只负责平移,罚它只会让模型整体偏移,没有正则的意义。 岭回归的一个关键性质:系数会一起变小,但不会变成 0。 因为罚的是平方,系数越小罚得越轻, 梯度越接近 0 越平缓,所以它只敢把系数往小里压,不敢直接删掉。 这也意味着岭回归不能用来选特征。8 个特征进去,8 个系数出来,只是都小了一圈。 要知道哪个特征没用,得看 Lasso。

三、Lasso:罚绝对值,把没用的直接删掉

Lasso 把罚项换成系数绝对值之和。 看起来只改了一个平方,性质却完全不同:它会把一部分系数压成恰好零。 为什么?教材的解释很形象:L1 的等值线是菱形,L2 的等值线是圆。 损失函数的等高线(椭圆)往外扩,第一次碰到约束区域的点, 在菱形上很容易正好落在尖角——而尖角就在坐标轴上,坐标轴上意味着某个系数正好是 0。 圆没有尖角,所以 L2 的解一般落不到坐标轴上。这一句是整节的核心:尖角产生稀疏,圆弧不产生稀疏。 稀疏的代价是求解变难。 L1 不可导(0 点是个尖),不能直接求导令零,常用的是坐标下降:每次只调一个系数,其他固定, 用「软阈值」这个操作一步到位。Demo 里的 soft() 就是软阈值:把值往 0 的方向推 λ,推过头就停在 0。 实测 λ = 4 时 8 个系数只剩 5 个非零。 真值只有 3 个——该删的删了,但没删干净。 λ 再往上加,噪声特征会继续掉,但真信号也会被削, 测试误差重新上升。这就是要调 λ 的原因。

四、λ 怎么选:只能在验证集上试

λ 是超参数——它不由训练过程决定,得人来定。 定的办法只有一个:换几个 λ 各训练一次,看谁在验证集上最好。 Demo 里岭回归的 λ 从 0 走到 100,测试 MSE 从 0.2458 一路降到 0.1373, 然后到 λ = 100 时反弹到 1.9035。这条 U 形曲线是正则化的标准形状:左边过拟合,右边欠拟合。 注意「反弹」那一段有多陡:λ 从 10 到 100,MSE 涨了 14 倍。 λ 的量级比它的精确值重要得多,所以实际调参时按 10 的倍数扫,而不是 0.1、0.2、0.3 这样细调。 教材还提到「弹性网」(Elastic Net),就是把 L1 和 L2 加权混起来。 它适合「特征是成组相关」的场景:一组相关的特征,Lasso 会随便留一个删其他, 弹性网倾向于把这一组一起留或一起删,更稳。

五、动手 Demo:给系数加一道罚款,压住它的脾气

数据是 60 个样本、8 个特征,只有前 3 个真有用(系数 1.5、−2.0、1.0),后 5 个是噪声。 另外故意造了两组共线:第 6 个特征是第 2 个的翻版,第 7 个是第 3 个的两倍。 这些设定都写死在代码里,读者可以自己改倍数看结果怎么变。

import numpy as np

Z = (X - X.mean(axis=0)) / X.std(axis=0)          # 先标准化
Z = np.column_stack([np.ones(len(Z)), Z])         # 第一列放截距

def ridge(Z, y, lam):
    p = Z.shape[1]
    A = Z.T @ Z + lam * np.eye(p)                  # 对角线上加 lam
    A[0, 0] -= lam                                 # 截距不罚
    return np.linalg.solve(A, Z.T @ y)

def soft(v, lam):                                  # Lasso 的软阈值
    if v > lam:
        return v - lam
    if v < -lam:
        return v + lam
    return 0.0

print("lam=0   岭回归系数", np.round(ridge(Z, y, 0.0), 3))
print("lam=10  岭回归系数", np.round(ridge(Z, y, 10.0), 3))

实跑输出:

--------------------------------------------------------------------
正则化:给系数加一道罚款,压住它的脾气
--------------------------------------------------------------------

  造 60 个样本、8 个特征,但**只有前 3 个是真的有作用**
  (系数 1.5、−2.0、1.0),后 5 个是噪声。另外故意做两件事:第 6 个特征是第 2 个的翻版,
  第 7 个是第 3 个的两倍——**这叫共线性**。

  【一】不罚款:系数会乱跳
  八个系数(已经标准化过,所以能横向比大小):
      1.1903  -1.7994  -7.9453   0.0676   0.0818   0.0024   8.9080  -0.0801
  真值应该是 [1.5, −2.0, 1.0, 0, 0, 0, 0, 0] 附近。实际解出来,
  系数里出现了 -7.9453 和 +8.9080 这样一对——一个正一个负、几乎抵消。这正是共线性的招牌症状:
  **两个几乎一样的特征,模型只能用一正一负去凑,合起来看着没事,单个系数已经毫无意义。**

  【二】岭回归:加一项 λ 乘系数平方和
  λ         系数长度    测试 MSE      共线那位的系数
  ------------------------------------------------------
  0.0       12.131      0.2458        0.002
  0.1       2.130       0.2099        -0.578
  1.0       1.887       0.1952        -0.837
  10.0      1.744       0.1373        -0.743
  100.0     1.413       1.9035        -0.296
  λ 往大里走,系数长度一路缩,测试误差先降后升。**中间那个最少的位置就是该选的 λ**
  ——λ 太大模型被压扁(欠拟合),λ 太小共线性的噪声又回来了(过拟合)。
  这就是「偏差和方差的权衡」。
  注意岭回归的系数**会一起变小,但不会变成 0**。
  这是 L2 罚款的特点:它罚的是平方,系数越小罚得越轻,所以它只敢把系数压小,不敢直接删掉。

  【三】Lasso:加一项 λ 乘系数绝对值之和
  λ         非零系数个数    系数长度    测试 MSE
  --------------------------------------------------
  0.0       8               2.454       0.1853
  0.5       7               2.323       0.1741
  1.0       7               2.300       0.1603
  2.0       7               2.255       0.1420
  4.0       5               2.177       0.1438
  Lasso 的 λ 一上去,系数会被压成**恰好为 0**
  ——这叫稀疏。λ = 4.0 时还剩 5 个非零,而真值只有 3 个——
  **该删的删了,但没删干净,λ 再大就轮到真信号被削。**
  接着问:为什么 L1 能删、L2 不能删?因为 L1 的罚款在 0 附近是个尖角,
  最优点容易正好落在尖角上;L2 在 0 附近是光滑的碗底,最优点只会滑到碗底附近,落不到正中间。

  【四】一句话分工
  叫法      罚什么          系数会怎样          什么时候用
  ----------------------------------------------------------------------
  岭回归    系数平方和      一起变小,不为 0    特征都有点用、彼此相关
  Lasso     系数绝对值和    一部分变成 0        特征很多,想顺手选特征
  弹性网    两者加权混合    介于两者之间        特征是成组相关时更稳
  **正则化不是「让模型更准」,是「让模型别太自信」。**
   它的收益在测试集上,不在训练集上——训练误差一定会因为罚款而变大。

第一段先看「不罚款」有多糟。八个系数里出现了 −7.9453 和 +8.9080 这一对, 而它们对应的两个特征本来就是同一个东西的两种写法。这不是算法算错了,是这个问题本身没有唯一解。 岭回归那张表要看两列:系数长度和测试 MSE。λ 从 0 到 10,长度从 12.131 掉到 1.744, MSE 从 0.2458 降到 0.1373。λ = 100 时长度 1.413、MSE 反弹到 1.9035——压过头了,模型被压扁。 注意 λ = 0 那一行的 MSE 是 0.2458,而 2.1 节同样的最小二乘在干净数据上是 0.0727。 差了 3 倍多,就是因为这份数据有共线性。 这就是正则化存在的理由。 Lasso 那张表的「非零系数个数」是关键。λ = 0 时 8 个全在,λ = 0.5 掉到 7,λ = 4 掉到 5。 系数是被压成精确的 0,不是「很小」——这是 L1 和 L2 最本质的区别。 最后那张三行表是这一节的总结:岭回归留下所有特征但都变小,Lasso 删掉一部分,弹性网介于两者之间。 选哪个不取决于哪个更先进,取决于你要不要那份稀疏。

30 秒实验

  1. 把 Lasso 的 λ 改成 8 和 16,非零系数个数会怎么变?真信号还在吗?
  2. 把第 6 个特征改成「和第 2 个完全一样」(去掉那 0.05 的噪声),不罚款的解会变成什么?
  3. 岭回归的 λ 一直加到 10000,所有系数会趋近于什么?模型还在预测什么?

常见误区

  1. 把正则化当成「提升精度」的技巧。它的收益在测试集上,训练误差一定会因为罚款而变大。 如果你只看训练分数,会觉得它有害。
  2. 标准化之前就加正则。罚的是系数大小,而系数大小取决于特征尺度——一个以毫米为单位的特征, 系数天然比以米为单位的小一千倍,不标准化就罚,等于专挑小单位的特征罚。
  3. 以为 Lasso 选出来的特征一定「真有用」。共线的两个特征里它只会留一个,留哪个几乎是随机的。 所以 Lasso 的结果要结合业务判断,不能当成因果结论。
自测三问(点开对答案) 1. 为什么岭回归的系数不会变成 0,而 Lasso 会? —— 因为 L2 的约束区域是圆、L1 是菱形。 椭圆等高线外扩第一次碰到的点,在菱形上容易落在尖角(就在坐标轴上),在圆上一般落不到。 尖角对应某个系数为 0,这叫稀疏。 2. λ 从 10 加到 100,测试 MSE 从 0.1373 弹到 1.9035,这叫什么? —— 欠拟合。罚得太狠,系数被压得接近 0,模型几乎只剩下截距,预测值全挤在均值附近,误差自然大。 3. 为什么加正则项之前一定要标准化? —— 因为罚的是系数的绝对大小,而系数大小和特征尺度成反比。 不标准化的话,单位小的特征会被罚得更狠,罚款变成了一种偏见。

线性回归输出的是连续值。 要输出「是/不是」这种类别,得在它外面套一个把实数压进 0 到 1 的函数——2.3 节讲逻辑回归。

相关笔记