📊 第8章 机器学习 Scikit-Learn
📄 一句话概括

最小二乘不管系数多大,岭回归在旁边加一项惩罚,把系数压小。 alpha 是惩罚力度,越大压得越狠;alpha 等于 0 就退回最小二乘。 不标准化就上岭回归,惩罚会被量纲带偏——这次面积系数反而变大了。

📝 本章和前面几章的关系

上一篇的模型很干净。但特征一多、彼此相关,最小二乘的系数就会乱跳。 这一篇用同一份房价数据,演示岭回归怎么「上缰绳」,以及一个必须先做的前置动作。

一、最小二乘的毛病

它只干一件事:让误差平方和最小。至于系数多大,它不管。 特征之间相关时(比如面积和房间数),系数会正负乱跳,甚至大得离谱。 系数一大,模型就脆:新数据稍微一动,预测就飞出去。这就是过拟合。

所以需要在「误差小」之外,再加一条约束:系数别太大。

二、加一项惩罚

目标函数从「误差平方和」,变成「误差平方和 加 alpha 乘 系数的平方和」。 alpha 越大,惩罚越重,系数被压得越小。 alpha 是你自己定的数,它不在数据里。这种「要人定、不在数据里」的参数叫超参数。

它用的是系数的平方,所以叫 L2 惩罚。它会把系数压小,但很少压到正好 0。

三、先踩一个坑:不标准化

面积 62 到 128,房龄 1 到 18,量纲差了十倍。直接喂原始数据: 最小二乘面积系数 1.0489;岭 alpha=50 时变成 1.3197,反而变大了。 为什么?因为惩罚加在「系数的平方」上。量纲大的特征本来系数就小,罚它几乎没作用。

于是 alpha 一涨,系数在两个特征之间重新分活:一个被压下去,另一个顶上来。 不标准化的岭回归,等于让量纲决定谁被罚。这一步不补,后面的结论都不算数。

四、标准化之后再上岭

标准化就是每个特征减去均值、除以标准差,把量纲拉平。 这回 alpha 从 0.1 加到 1000,两个系数的绝对值一路单调变小。 面积系数从 18.47 掉到 0.37,房龄从 -11.06 掉到 -0.36。

这才是岭回归该有的样子:alpha 越大,系数越被压向 0。 但压过头就废了:alpha=1000 时测试 R 方是 -0.1798,比直接猜平均值还差。

五、什么时候该想到它

特征多、样本少的时候,最小二乘容易过拟合,岭回归能稳住。 特征之间高度相关时,最小二乘系数会乱跳,岭回归能压住。 普通任务先跑最小二乘,发现系数离谱再上岭回归。不必一上来就用。

名字里带 Ridge 是「山脊」的意思,因为系数被压成了一条窄窄的山脊。

六、动手 Demo:岭回归:给系数上缰绳

先看不标准化时 alpha 变大系数反而变大,再标准化重跑一遍。 最后把 alpha 从 0.1 到 1000 扫一排,看系数和 R 方怎么走。

import numpy as np
from sklearn.linear_model import LinearRegression, Ridge
from sklearn.metrics import r2_score
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

AREA = [62, 75, 88, 95, 105, 118, 128, 70, 82, 100, 112, 92,
        66, 79, 91, 108, 122, 86]
AGE = [15, 12, 8, 5, 3, 2, 1, 18, 10, 6, 4, 9,
       14, 11, 7, 5, 2, 9]
PRICE = [78, 92, 118, 128, 146, 163, 172, 76, 100, 134, 154, 118,
         82, 98, 124, 148, 168, 112]

X = np.array([[AREA[i], AGE[i]] for i in range(18)], dtype=float)
y = np.array(PRICE, dtype=float)
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.25, random_state=42)

m0 = LinearRegression().fit(Xtr, ytr)
m1 = Ridge(alpha=50.0).fit(Xtr, ytr)
print("不标准化 最小二乘:面积 %.4f,房龄 %.4f"
      % (m0.coef_[0], m0.coef_[1]))
print("不标准化 岭 a=50 :面积 %.4f,房龄 %.4f"
      % (m1.coef_[0], m1.coef_[1]))

sc = StandardScaler().fit(Xtr)
Xs_tr, Xs_te = sc.transform(Xtr), sc.transform(Xte)
for a in (0.1, 1.0, 10.0, 100.0, 1000.0):
    mm = Ridge(alpha=a).fit(Xs_tr, ytr)
    print("alpha %-6g 面积 %8.4f  房龄 %8.4f  R 方 %7.4f"
          % (a, mm.coef_[0], mm.coef_[1],
             r2_score(yte, mm.predict(Xs_te))))

实跑输出:

--------------------------------------------------------------------
岭回归:给系数上缰绳
--------------------------------------------------------------------

  ① 最小二乘的毛病
  --------------------------------------------------------------------
  最小二乘只干一件事:让误差平方和最小。它不管系数多大。
  特征一多、特征之间还相关的时候,系数会变得很大,甚至正负乱跳。
  系数一大,模型就脆:新数据稍微一动,预测就飞出去。这叫过拟合。
  ! 岭回归的思路很朴素:在目标函数里加一项,惩罚过大的系数。

  ② 加一个 alpha
  --------------------------------------------------------------------
  目标函数从「误差平方和」变成「误差平方和 + alpha 乘 系数的平方和」。
  alpha 越大,惩罚越重,系数被压得越小。alpha 等于 0 就退回最小二乘。
  alpha 是一个你要自己定的数,它不在数据里,得试。这叫超参数。
  ! 岭回归用的是系数的平方(L2),所以它会把系数压小,但很少压到正好 0。

  ③ 不标准化会出什么事
  --------------------------------------------------------------------
  面积 62 到 128,房龄 1 到 18,量纲差了十倍。直接喂原始数据:
  最小二乘:面积 1.0489,房龄 -2.2789。
  岭 alpha=1:面积 1.0631,房龄 -2.2193。
  岭 alpha=50:面积 1.3197,房龄 -1.0970。
  注意:alpha 加大之后,面积系数反而变大了。
  惩罚加在「系数的平方」上。量纲大的特征本来系数就小,罚它几乎没作用,
  于是 alpha 一涨,两个系数之间重新分活——一个被压,另一个就顶上去。
  ! 岭回归不标准化,等于让量纲决定谁被罚。这一步不补,后面的结论都不算数。

  ④ 标准化之后再上岭
  --------------------------------------------------------------------
  标准化之后,两个特征都变成均值 0、标准差 1,量纲拉平了。

| alpha | 面积系数 | 房龄系数 | 测试 R 方 |
|---:|---:|---:|---:|
| 0.1 | 18.4679 | -11.0552 | 0.9823 |
| 1 | 16.0851 | -12.4246 | 0.9767 |
| 10 | 10.9144 | -10.3105 | 0.8711 |
| 100 | 3.0173 | -2.9527 | 0.1956 |
| 1000 | 0.3678 | -0.3613 | -0.1798 |

  这下对了:alpha 越大,两个系数的绝对值都单调变小。
  面积系数始终为正、房龄始终为负,方向也没被搅乱。
  但测试 R 方从 0.9823 一路掉到 -0.1798——alpha 太大,模型就被压得只会猜平均值了。
  最后一行 R 方是负数,意思是「还不如直接拿训练集的均值去猜」。这就是惩罚过头的下场。
  ! 标准化之后系数可以直接比大小,因为两个特征的尺度一样了。这才是「谁更重要」的正确比法。

  ⑤ 什么时候该想到岭回归
  --------------------------------------------------------------------
  特征多、样本少的时候,最小二乘容易过拟合,岭回归能稳住。
  特征之间高度相关(比如「面积」和「房间数」)时,最小二乘系数会乱跳,岭回归能压住。
  普通预测任务,先跑最小二乘,发现系数离谱再上岭回归。不必一上来就用。
  ! 名字里带 Ridge 是「山脊」的意思,因为系数被压成了一条窄窄的山脊形状。

StandardScaler 用 fit 学训练集的均值和标准差,再用 transform 把它们用到训练集和测试集上。 测试集只能 transform,不能 fit。拿测试集学均值和标准差,等于提前偷看考卷。 alpha=1000 那一行 R 方是负的,不是 bug:模型比「直接猜平均值」还差,就会算负值。

30 秒实验

  1. 把 alpha 加到 100000,看系数是不是都接近 0,R 方掉到多少。
  2. 把 StandardScaler 去掉,只跑标准化后的表,对比两组系数。
  3. 想一想:为什么测试集不能参与 fit 标准化?偷看考卷会造成什么后果?

常见误区

  1. 不标准化直接上岭:惩罚被量纲带偏,系数会给出反直觉的结果。
  2. 在测试集上 fit 标准化器:数据泄漏,测试分虚高。
  3. alpha 越大越好:压过头 R 方变负,模型只会猜平均值。
  4. 以为岭回归能把系数压成 0:L2 只会压小,要压成 0 得用 Lasso。
自测三问(点开对答案) 1. 岭回归比最小二乘多加了什么? —— 目标函数里多一项 alpha 乘系数的平方和,用来惩罚过大的系数。 2. 为什么必须标准化? —— 惩罚加在系数平方上,量纲大的特征系数本来就小,罚它没作用,系数会重新分配。 3. alpha=1000 时 R 方是负的,说明了什么? —— 惩罚过重,模型比「直接猜平均值」还差。

回归讲完了。下一篇换个任务:分类,用一条最宽的分界线把两类点分开。

相关笔记