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

SVM 找的不是「能分开的线」,而是「离两边都最远的那条线」。 只有支持向量在决定这条线,其他点删掉线也不动。 C 控制容忍度:C 小边界宽、肯放过错点;C 大边界窄、硬要贴合。

📝 本章和前面几章的关系

前两篇是回归,预测一个数。这一篇开始是分类,预测一个类别。 数据用 make_blobs 造,种子写死,谁跑都是同一份。

一、它想干什么

两类点中间能画无穷多条线,选哪条? SVM 的答案:选那条离两边最近点都尽量远的。这条线叫最大间隔分界线。 离分界线最近的那几个点叫支持向量,它们决定了线画在哪。

其他点删掉,线一点不动。这就是它名字的来历,也是它的性格。

二、线性核:一条直线

八十个点分两团,训练 60、测试 20。 支持向量只有 3 个,占训练集的 5%。 训练准确率 1.0000,测试准确率也是 1.0000。

数据分得开的时候,SVM 又快又准,支持向量还特别少。 kernel 是「用什么形状的线」。linear 是直线,rbf 是弯曲的。

三、故意标错六个点

真实数据总有噪声,甚至还有录错的。这次把前六个点的标签写反。 这种点谁都分不对,问题是:模型该不该为了它们把边界拧歪? 放宽靠参数 C:C 大表示「一个都不许错」,C 小表示「允许几个错,换更宽的间隔」。

C 从 0.01 加到 100,支持向量从 22 个降到 16 个。 训练准确率一直是 0.9333——那六个点本来就该分不对。 但测试准确率从 0.9000 掉到 0.8500。训练分没涨,测试分却降了。

C 小时模型愿意放弃那六点,换一条更宽的边界;C 大了边界被它们拽歪,新数据就吃亏。

四、弯的边界:rbf 核

两个交错的半月形,一条直线怎么画都分不开。 线性核测试准确率 0.9600,rbf 核 1.0000。差的就是那条弯曲的本事。 rbf 把边界掰弯了,所以能分开。这是核函数的作用,不是参数调出来的。

rbf 有两个参数:C 和 gamma。gamma 控制「弯得多细」,越大越贴着点走。

五、用它之前先想两件事

第一,所有特征先标准化。SVM 靠距离算,量纲不一会让大数特征压倒一切。 第二,样本别太大。训练复杂度接近平方级,几万条就开始慢。 样本几十万、特征几百万的场子,是线性模型和树模型的天下。

标准化这一步很多人省掉,结果模型全靠一个特征在撑,还以为是 SVM 不行。

六、动手 Demo:支持向量机:画一条最宽的分界

先看一份干净数据的 SVM,再看故意标错六个点之后 C 的影响。 最后用两个半月形对比线性核和 rbf 核。

from sklearn.datasets import make_blobs, make_moons
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC

X, y = make_blobs(n_samples=80, centers=2, cluster_std=1.0, random_state=42)
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.25, random_state=42)
sv = SVC(kernel="linear", C=1.0).fit(Xtr, ytr)
print("干净数据:支持向量 %d 个,训练 %.4f,测试 %.4f"
      % (len(sv.support_vectors_), sv.score(Xtr, ytr), sv.score(Xte, yte)))

X2, y2 = make_blobs(n_samples=80, centers=2, cluster_std=2.0, random_state=42)
y2[0:6] = 1 - y2[0:6]
Xtr2, Xte2, ytr2, yte2 = train_test_split(X2, y2, test_size=0.25,
                                           random_state=42)
for c in (0.01, 0.1, 1.0, 100.0):
    mc = SVC(kernel="linear", C=c).fit(Xtr2, ytr2)
    print("C=%-7g 支持向量 %2d  训练 %.4f  测试 %.4f"
          % (c, len(mc.support_vectors_), mc.score(Xtr2, ytr2),
             mc.score(Xte2, yte2)))

Xm, ym = make_moons(n_samples=100, noise=0.15, random_state=42)
Xtr3, Xte3, ytr3, yte3 = train_test_split(Xm, ym, test_size=0.25,
                                           random_state=42)
print("半月形 线性核 %.4f,rbf 核 %.4f"
      % (SVC(kernel="linear").fit(Xtr3, ytr3).score(Xte3, yte3),
         SVC(kernel="rbf").fit(Xtr3, ytr3).score(Xte3, yte3)))

实跑输出:

--------------------------------------------------------------------
支持向量机:画一条最宽的分界
--------------------------------------------------------------------

  ① 它想干什么
  --------------------------------------------------------------------
  二分类问题,要在两类点中间画一条线。能画的线有无穷多条,选哪条?
  SVM 的答案:选那条离两边最近点都尽量远的。这条线叫最大间隔分界线。
  离分界线最近的那几个点叫支持向量,它们决定了线画在哪。
  ! 只有支持向量在起作用,其他点删掉,线也不动。这就是它名字的来历。

  ② 线性核:一条直线
  --------------------------------------------------------------------
  八十个点,两类,每类四十个。训练集 60,测试集 20。
  支持向量 3 个,占训练集的 5.0%。
  训练准确率 1.0000,测试准确率 1.0000。
  ! kernel 是「用什么形状的线」。linear 是直线,rbf 是弯曲的。

  ③ 分不开的时候
  --------------------------------------------------------------------
  真实数据里总有「你中有我」的点,甚至还有标错的。一条直线怎么画都分不干净。
  两个办法:换个弯的核函数,或者放宽一点容忍几个错分的点。
  放宽靠参数 C:C 大表示「一个都不许错」,C 小表示「允许几个错,换更宽的间隔」。
  八十个点分两团,再故意把前六个点的标签写反,模拟记录错误。
  这种点谁都分不对。问题是:模型该不该为了它们把边界拧歪?

| C | 支持向量个数 | 训练准确率 | 测试准确率 |
|---:|---:|---:|---:|
| 0.01 | 22 | 0.9333 | 0.9000 |
| 0.1 | 17 | 0.9333 | 0.9000 |
| 1 | 16 | 0.9333 | 0.8500 |
| 100 | 16 | 0.9333 | 0.8500 |

  C 从 0.01 加到 100,支持向量从 22 个降到 16 个。
  训练准确率一直是 0.9333——那六个错标点始终分不对,这本来就该分不对。
  但测试准确率从 0.9000 掉到 0.8500。训练分没涨,测试分却降了。
  C 小的时候,模型愿意放弃那六个点,换一条更宽的边界;C 大了,边界被它们拽歪,新数据就吃亏。
  ! 这就是过拟合的样子:不是训练分变高,而是测试分变低。

  ④ 弯的边界:rbf 核
  --------------------------------------------------------------------
  两个半月形,交错的,一条直线分不开。
  线性核测试准确率 0.9600,rbf 核 1.0000。
  差的就是那条弯曲的本事。rbf 能把边界掰弯,所以能分开。
  ! rbf 有两个参数:C 和 gamma。gamma 控制「弯得多细」,越大越贴着点走。

  ⑤ 用它之前先想两件事
  --------------------------------------------------------------------
  第一,所有特征先标准化。SVM 靠距离算,量纲不一样会让大数特征压倒一切。
  第二,样本量别太大。SVM 训练复杂度接近平方级,几万条就开始慢。
  样本几十万、特征几百万的场子,是线性模型和树模型的天下。
  ! 标准化这一步很多人省掉,结果模型全靠一个特征在撑,还以为是 SVM 不行。

y2[0:6] = 1 - y2[0:6] 是把前六个标签 0 变 1、1 变 0。这是「造噪声」的标准手法。 support_vectors_ 是支持向量本身的坐标,len(...) 才是个数。 盯着测试准确率看,别盯训练准确率。这次 C 变大时训练分没动,测试分掉了 5 个点。

30 秒实验

  1. 把翻转的六个点改成二十个,看 C 的影响还明显不明显。
  2. 在 rbf 核上加 gamma 参数,试 0.1、1、10,看测试准确率怎么变。
  3. 想一想:为什么 SVM 对量纲特别敏感?它算的到底是什么距离?

常见误区

  1. 只看训练准确率:C 大能把训练分刷上去,测试分反而掉。
  2. 不标准化就上 SVM:量纲大的特征会主导距离计算。
  3. 以为支持向量越多越好:越多说明边界越宽、容错越多,不一定是好事。
  4. 拿直线核硬啃弯形状:该换 rbf,不是调 C 能救的。
自测三问(点开对答案) 1. SVM 在众多分界线里选哪一条? —— 离两边最近点都最远的那条,也就是间隔最大的那条。 2. C 变大意味着什么? —— 对错分的容忍变小,边界变窄、贴着点走,容易过拟合。 3. 为什么 SVM 必须先标准化? —— 它靠距离计算,量纲大的特征会把距离主导掉,其他特征等于没参与。

有标签的分类讲完了。下一篇反过来:没有标签,怎么把点分成几堆。

相关笔记