🤖 第8章 支持向量机分类
📄 一句话概括

二维多项式核 (1 加 x 点乘 z) 的平方,和它对应的六维显式映射做对比: 核矩阵与内积矩阵的最大偏差是 7.105e-15,就是浮点舍入。 同心圆数据上线性核只有 75.0%,高斯核 100.0%; 线性核在那份数据上有 49 个支持向量,高斯核只有 15 个。 两个核矩阵的最小特征值分别是负 3.0e-14 和 2.4e-09,都是数值上的零。

📝 本节和前面几节的关系

8.1 到 8.3 讲的边界都是直线(高维里叫超平面)。 遇到同心圆这种数据,直线怎么画都分不开——这不是参数没调好,是模型容量不够。 这一节的办法是 2.2 讲过的核模型:不动算法,只把「点乘」换成「核值」。 换完之后,边界在原空间里是弯的,但整个求解过程一行都不用改。

一、核技巧:只算内积

先说清核技巧到底省了什么。 如果先把每个点映射到高维、再在高维里做 SVM,那要算高维坐标、存高维向量。 而整个对偶问题里,样本只以「两点内积」的形式出现——这一点 8.2 已经埋下了。 所以只要能直接算出高维内积,就不需要真的进高维。 二维多项式核 (1 加 x 点乘 z) 的平方,对应一个六维的显式映射: 分量依次是 1、根号 2 乘 x1、根号 2 乘 x2、x1 平方、根号 2 乘 x1x2、x2 平方。 Demo 直接把两个矩阵算出来对比,最大偏差 7.105e-15。 这个数是这一节最硬的证据。 它说明「用核等于在六维空间里做内积」不是比喻、不是近似,是可以逐元对上的等式。 而整个过程我们没算过任何一个六维向量,只算了原空间的点乘再取平方。 所以核技巧省掉的那一步是「显式构造映射」,不是「映射本身」。 映射还在,只是绕过去了。 这也是为什么核函数不能随便造:它必须真的对应某个特征空间, 而这个「真的对应」的判据是核矩阵半正定。

二、实测:同心圆上的两个核

核 原始维度 特征维度 训练准确率 支持向量 最小特征值
线性核 2 2 75.0% 49 负 3.0e-14
高斯核 2 无穷维 100.0% 15 2.4e-09
同心圆这份数据,内圈半径 0.6 标负、外圈半径 2.0 标正。
判别规则其实只有一条:离原点远还是近。可这一条在平面上不是直线。
线性核只能到 75.0%——它把平面切了一刀,怎么切都会切掉内圈的一半或者外圈的一半。
高斯核到 100.0%。
它把「离得近不近」直接当成相似度,于是「半径大小」这个隐含的变量被它抓住了。
注意这不是它「看懂了半径」,是它在高维里找到了一个能把远近分开的方向。
支持向量那一列很有意思:线性核 49 个,高斯核 15 个。
按理说线性核分不开、支持向量多是正常的;
但更值得记的是「核化之后 SVM 往往不再稀疏」——15 个已经占了四分之一。
换成更小的带宽,支持向量还会更多,极端情况下会逼近全样本。

三、决策函数长什么样

高斯核下面已经写不出 w 了。 因为它的特征空间是无穷维的,w 是一个无穷维向量,存都存不下。 但决策函数不需要 w:它只要知道「新点和每个支持向量的核值」。 形式是:f(x) 等于「每个支持向量的乘子乘标签乘核值」求和,再加 b。 Demo 里参与求和的只有 15 个点,另外 45 个乘子是零,乘子范围从 0.0312 到 1.7646, b 等于 0.634535。 这件事带来一个实际后果:核模型必须带着训练样本一起走。 第 3 章的线性模型学完就能把系数存下来、样本全扔;这里不行,样本本身就是模型的一部分。 所以核模型的文件体积随训练集增长,预测时间也随支持向量个数增长。 如果你在部署时发现模型文件几百兆、单次预测要几十毫秒,八成就是支持向量太多。 常规的解法有两条:换带宽让支持向量变少, 或者做「模型压缩」——用一个线性模型去近似核模型的输出。

四、半正定不是装饰

两个核矩阵的最小特征值:线性核负 3.0e-14,高斯核 2.4e-09。 都是数值上的零,也就是说两个矩阵都是半正定的。 这不是巧合,是核函数能被使用的必要条件。 为什么必须有这一条?因为核矩阵就是「特征空间里这些向量的内积矩阵」。 而任何内积矩阵都半正定:对任意系数 c, c 转置乘 K 乘 c 等于「c 加权求和后那个向量的长度平方」,长度平方不可能为负。 反过来,一个半正定的核矩阵也一定能找到对应的特征映射,这是默塞尔定理的内容。 实践意义:随手造一个「看起来像相似度」的函数,很可能不是合法的核。 比如「负的欧氏距离」就不是——它的矩阵不正定。 用了非法核,SMO 里的曲率可能变成负数,求解会崩或者给出荒谬的解。 顺便说清带宽这个参数。 高斯核的 gamma 控制「多近算近」:gamma 大,只有非常近的点才有核值,边界会变得很碎; gamma 小,所有点都算「比较近」,模型退回接近线性。 它是 SVM 里最需要调的一个参数,重要程度和 C 相当。

五、动手 Demo:核映射:同心圆上线性核 75.0%,高斯核 100.0%

先做一件与数据无关的事:把二维多项式核 (1 加 x 点乘 z) 的平方,和它对应的六维显式映射逐元对比, 看偏差有多大——这是核技巧成立与否的直接证据。 然后换到同心圆数据(60 个点,内圈半径 0.6 标负、外圈半径 2.0 标正),分别用线性核和高斯核求解, 比较训练准确率、支持向量个数和核矩阵的最小特征值。

def phi(Z):                                # 二维多项式核的显式映射
    x1, x2 = Z[:, 0], Z[:, 1]
    return np.column_stack([np.ones(len(Z)), np.sqrt(2) * x1,
                            np.sqrt(2) * x2, x1 * x1,
                            np.sqrt(2) * x1 * x2, x2 * x2])

K = (1.0 + X @ X.T) ** 2                   # 核矩阵,六维内积的替身
print(np.abs(K - phi(X) @ phi(X).T).max())

print(np.linalg.eigvalsh(K).min())         # 必须不小于零

实跑输出:

--------------------------------------------------------------------
核映射:同心圆上线性核 75.0%,高斯核 100.0%
--------------------------------------------------------------------
  数据                     60 个点、二维:内圈半径 0.6(标负)、外圈半径 2.0(标正)
  关键                     同心圆在平面上没有任何一条直线能分开,维度不够就是不够

  先验第一件事:核函数真的等于「先映射到高维、再做内积」吗?

  二维多项式核 (1 加 x 点乘 z) 的平方,对应的显式映射是六维:

  phi(x) = [1, 根号2 乘 x1, 根号2 乘 x2, x1 平方, 根号2 乘 x1x2, x2 平方]

  核矩阵大小               10 乘 10
  最大偏差                 7.105e-15(核矩阵 减 映射后的内积矩阵)

  **7.105e-15,就是浮点的舍入误差。**
  **所以「用核就等于在六维空间里做内积」这句话不是比喻,是算得出来的等式。而我们从头到尾没算过任何一个六维向量,只算了原空间的点乘再取平方。**
  **这就是核技巧的全部内容:省掉的那一步叫「显式构造映射」,不是省掉了映射本身。**

  再看同心圆:

  核       原始维度   特征维度   训练准确率   支持向量   核最小特征值
  ------------------------------------------------------------------
  线性核          2          2        75.0%         49       -3.0e-14
  高斯核          2     无穷维       100.0%         15        2.4e-09

  **线性核只能到 75.0%,因为它在平面里画直线,同心圆要求的是「离原点多远」这一个条件,直线给不出来。**
  **高斯核到 100.0%,它把「离得近不近」直接当成相似度,半径大小自然被它抓到了。**
  **最小特征值那一列:线性核 -3.0e-14、高斯核 2.4e-09,都是数值上的 0。核矩阵半正定不是装饰条件,它是「这个核真的对应某个特征空间」的判据——半正定保证了存在那样的映射。**

  高斯核下面,决策函数长这样:

  f(x) = 求和 乘子乘标签乘 K(x 与那个点的距离) 加 b

  参与求和的点             15 个,其余 45 个乘子是 0
  乘子范围                 0.0312 ~ 1.7646
  b                        0.634535

  **注意这里已经写不出 w 了。高斯核的特征空间是无穷维的,w 是一个无穷维向量,存都存不下。**
  **但决策函数不需要 w:它只要知道「新点和每个支持向量的核值」。这就是为什么核模型必须带着训练样本一起走,不能压缩成一个系数向量。**
  **对比第 3 章的线性模型:那里学完就能把系数存下来、样本全扔;这里不行,样本本身就是模型的一部分。**

代码里 (1.0 + X @ X.T) 再取平方,就是全部。 先算 n 乘 n 的点乘矩阵,再加 1、取平方。没有循环、没有显式映射。 而等号右边那个六维内积要算两个 n 乘 6 的矩阵再相乘,代价大得多。 注意那两个星号在 Python 里是幂运算,不是别的意思。 但写进笔记时它会和排版用的加粗标记打架,所以正文里我一律写「平方」。 这也是写这种技术笔记的一个小坑:代码里的符号和排版符号会撞车。 np.sqrt(2) 那三个系数不能省。 因为 (a 加 b 加 c) 的平方展开后,交叉项系数是 2,写成「根号 2 乘 根号 2」才对得上。 漏掉它们,核矩阵和映射后的内积矩阵会差一倍,偏差会变成 1 的量级而不是 1e-15。 所以这个偏差值本身就是个很好的自检:差 1e-15 说明映射写对了。 np.linalg.eigvalsh 用的是对称矩阵专用算法,比通用的 eigvals 快也更稳。 核矩阵一定是对称的,所以应该用 eigvalsh。 算出来负 3.0e-14 这种极小的负数不用担心,那是舍入误差; 如果出现负 1 以上的特征值,那才说明核不合法。 要说明:同心圆这份数据是我按极坐标造的,半径 0.6 与 2.0、噪声 0.16 与 0.22。 它的「真实规律」只有一条:离原点远近。 高斯核能满分是因为它恰好适合这种规律,换成螺旋线数据它也会吃力。 「核能解决非线性」不等于「核能解决一切非线性」。

30 秒实验

  1. 把高斯核的 gamma 从 1.0 改成 0.1、0.5、5、20,各跑一遍。 准确率怎么变?支持向量个数怎么变?gamma 太大时边界会变成什么样?
  2. 造一份螺旋线数据(两类各绕原点一圈半),分别用线性核、多项式核、高斯核跑。 哪个能分开?需要多大的 gamma?
  3. 把 phi 里的根号 2 全去掉,重算偏差。偏差会变成多少?这说明那三个系数在做什么?

常见误区

  1. 「核技巧就是先把数据升到高维」。 不是。核技巧恰恰是「不升维」。 它只算高维内积,从来不算高维坐标。 真升维的话,高斯核对应无穷维,根本升不上去。
  2. 「任何相似度函数都能当核」。 必须半正定。 「负的欧氏距离」看起来很像个相似度,但它的矩阵不正定,用了会崩。 检查办法:算核矩阵的特征值,看有没有明显的负数。
  3. 「高斯核一定比线性核好」。 不一定。 如果数据本身就近似线性可分,高斯核只会增加过拟合风险和计算量。 而且高斯核多一个 gamma 要调,调不好还不如线性核。 先跑线性核当基线,是稳妥的顺序。
  4. 「核化之后还是稀疏的,所以核模型也很轻」。 核化之后支持向量往往明显增多。 实测里同心圆数据线性核 49 个、高斯核 15 个,看着高斯核更少,那是因为线性核根本分不开它、 边界附近的点全成了支持向量。 换个能分开的数据,高斯核的支持向量比例常常是三成以上。
自测三问(点开对答案) 1. 为什么核技巧能让 SVM 处理非线性边界,而算法一行都不用改? —— **因为整个对偶问题和求解过程里,样本只以「两点内积」的形式出现。** **把内积换成核值,等于换了一个度量,但优化问题的结构完全不变。** **边界在原空间里变弯了,是因为那个度量下的「直线」对应原空间的曲线。** 2. 为什么核矩阵必须半正定? —— **因为核矩阵就是「特征空间里这些向量的内积矩阵」。** **内积矩阵必然半正定:任意系数组合下,二次型等于某个向量的长度平方,不可能是负的。** **反过来,半正定也保证了真的存在那样的特征映射(默塞尔定理)。** **所以半正定是「这个核合法」的充要判据,也是数值稳定性的来源。** 3. 高斯核的 gamma 调得特别大会发生什么? —— **只有非常近的点之间才有非零核值,相当于每个点各自成岛。** **模型能把训练集完全记住,但边界碎成一片一片,换个测试点就崩。** **同时支持向量个数会暴涨,因为每个点都变成「独一无二」的,都被需要。**

8.1 到 8.4 讲的都是「几何」:间隔、边界、核映射。 下一节换一个角度重新看整个 SVM——不算几何,只算损失。 你会发现它就是「hinge 损失 加 L2 正则」,和第 4 章、第 6 章那些东西是同一套语言。

相关笔记