☰
支持向量机SVM:从最大间隔原理到核技巧的完整推导与工程实践
2026/10/21 17:42:03 网站建设 项目流程

1. 从线性分类器到“最大间隔”的直觉

1.1 为什么感知机学到的超平面可能不够好

SVM(支持向量机)在我刚开始学的时候,总觉得它是一堆拉格朗日乘子、KKT条件、对偶问题组成的“公式大礼包”。后来才意识到,SVM的出发点其实特别朴素:找到一个能把两类样本分开的超平面,并且让这个超平面离两边的样本都尽量远。

先说为什么感知机不够。感知机也是找一个超平面 w·x + b = 0,把正负样本分到两侧。但感知机的优化目标是“只要分对就行”,它不会管这个超平面离样本有多近。同样的数据集,可能有无数个超平面都能把训练样本全部分开,感知机随机选一个,结果很可能出现下面这种尴尬情况:超平面正好贴着某几个样本,新的样本稍微一扰动,就直接被分到错误一侧。

这就好比足球比赛中的防守站位,你站在两个进攻球员中间,但到底是站在偏左一点还是偏右一点?如果只是“挡在中间”就完事,对手往旁边一传就漏过去。更合理的策略是站在“中间位置”并且保持一定“缓冲距离”,让两边都难以突破。SVM干的事情,就是把这条“中间位置”正式定义为:离最近的正样本和最近的负样本距离都最大的超平面。这个“离最近样本的距离”被称为间隔,而SVM的全称“支持向量机”中所谓的支持向量,就是那些恰好压在这个距离边界的样本点。

1.2 函数间隔与几何间隔:先从概念上厘清

要推导SVM,先得把“距离”用数学表达清楚。假设超平面是 w·x + b = 0,对任意一个样本 x_i,我们关心它到超平面的距离。

一个直接的想法是用 |w·x_i + b| 来表示点到超平面的“远近距离”,再乘上类别标记 y_i(取 +1 或 -1),就有了函数间隔:

\hat{\gamma}_i = y_i (w \cdot x_i + b)

函数间隔有一个问题:如果 w 和 b 同时放大两倍,函数间隔也会放大两倍,但超平面本身并没有改变。也就是说,函数间隔受参数比例影响,不是一种“几何上”稳定的距离。

于是引入几何间隔,其实就是把函数间隔除以 w 的模长:

\gamma_i = \frac{y_i (w \cdot x_i + b)}{|w|}

几何间隔才是样本点到超平面的真实欧氏距离。举个具体例子,二维平面上直线 2x_1 + x_2 - 1 = 0,某个点 (2, 1) 的函数间隔是 2*2 + 1 - 1 = 4,但几何距离应该是 4 / sqrt(5)。二者差了一个归一化因子。

理解了这两个概念,SVM的目标就清晰了:在所有能把样本正确分类的超平面中,让所有样本的几何间隔的最小值最大化。用数学语言写就是:

\max_{w,b} ; \min_i \frac{y_i(w \cdot x_i + b)}{|w|}

这就是最大间隔原理的原始表达。

1.3 最大间隔的优化目标为什么是“最小化 ||w||^2”

上面的原始目标带了一个 min,直接优化比较麻烦。一个经典技巧是,几何间隔具有缩放不变性:如果固定函数间隔为一个常数,那么最大化几何间隔就变成了最小化 |w|。

怎么固定?因为 w 和 b 同时缩放 k 倍,超平面不变,函数间隔也缩放 k 倍,但几何间隔保持不变。所以我们可以人为约定:距离超平面最近的样本的函数间隔正好等于 1。也就是说:

\min_i ; y_i(w \cdot x_i + b) = 1

这个约束可以拆成对每个样本的不等式:

y_i(w \cdot x_i + b) \ge 1, \quad i = 1,\ldots,n

此时几何间隔的最小值就是:

\gamma_{\min} = \frac{1}{|w|}

最大化 \frac{1}{|w|} 等价于最小化 |w|,为了方便求导,通常写成最小化 \frac{1}{2}|w|^2。为什么加个 1/2?纯粹是为了后面求导消掉系数。于是得到硬间隔SVM的原问题:

\min_{w,b} ; \frac{1}{2}|w|^2

\text{s.t.} \quad y_i(w \cdot x_i + b) \ge 1, \quad i=1,\ldots,n

这里有个很多人疑惑的点:为什么能假设最小函数间隔是 1?不是每个数据集都能满足这个约束吗?其实这个“1”只是一个归一化约定。如果某个解的最小函数间隔是 0.5,我们可以把 w 和 b 同时放大 2 倍,让最小函数间隔变成 1,几何间隔仍然不变。所以没有丧失一般性。

到这里,SVM的核心优化问题已经出现了。接下来就是如何求解这个带约束的凸优化问题。

2. 硬间隔SVM:拉格朗日对偶推导的完整流程

2.1 原问题的转化与约束条件处理

直接对 \frac{1}{2}|w|^2 在不等式约束下求最小值,可以用拉格朗日乘子法。把约束 y_i(w \cdot x_i + b) - 1 \ge 0 改写成 \ge 0 的形式,然后引入乘子 \alpha_i \ge 0。

拉格朗日函数写为:

L(w,b,\alpha) = \frac{1}{2}|w|^2 - \sum_{i=1}^{n} \alpha_i \left[ y_i(w \cdot x_i + b) - 1 \right]

其中 \alpha_i \ge 0。你可能会问:为什么这里是“减号”?因为约束是 y_i(w \cdot x_i + b) - 1 \ge 0,在不等式约束的标准形式中,如果写成 c_i(w,b) \ge 0,则拉格朗日函数是 f(w,b) - \sum \alpha_i c_i(w,b)。如果写成 c_i \le 0,则是加号。符号只要统一就行,但后面推导出的KKT条件会有一点点差异。我这里采用“减号”惯例,这样可以更方便地得到对偶问题。

原问题等价于:

\min_{w,b} \max_{\alpha \ge 0} L(w,b,\alpha)

为什么等价?如果某个 (w,b) 不满足约束,即 y_i(w \cdot x_i + b) - 1 < 0,那么 \alpha_i 趋近于无穷大时,整个 L 也会趋近于无穷大,max 会把它推开。如果满足约束,则减去的项非负,\alpha_i 取 0 就是最优,L 回到 \frac{1}{2}|w|^2。所以 min-max 和原问题等价。

但直接求解 min-max 很难。在满足凸优化和一定程度正则性的条件下(Slater条件,通俗说存在可行解),强对偶成立,可以交换 min 和 max:

\max_{\alpha \ge 0} \min_{w,b} L(w,b,\alpha)

这就是拉格朗日对偶问题。交换顺序之后,我们先对 w, b 求极小,会得到非常漂亮的表达式。

2.2 拉格朗日函数与KKT条件

先对 w 求偏导并令其等于 0:

\frac{\partial L}{\partial w} = w - \sum_{i=1}^n \alpha_i y_i x_i = 0

所以:

w = \sum_{i=1}^n \alpha_i y_i x_i

再对 b 求偏导:

\frac{\partial L}{\partial b} = - \sum_{i=1}^n \alpha_i y_i = 0

也就是说:

\sum_{i=1}^n \alpha_i y_i = 0

把这两个等式代回 L(w,b,\alpha),消去 w 和 b。这里需要一个一个算,避免出错。

先看 \frac{1}{2}|w|^2。把 w 的表达式代入:

\frac{1}{2}\left(\sum_i \alpha_i y_i x_i\right)\cdot\left(\sum_j \alpha_j y_j x_j\right) = \frac{1}{2}\sum_i\sum_j \alpha_i \alpha_j y_i y_j (x_i \cdot x_j)

然后看第二项:

\sum_i \alpha_i y_i (w \cdot x_i + b) = \sum_i \alpha_i y_i (w \cdot x_i) + b \sum_i \alpha_i y_i

第二项因为 \sum_i \alpha_i y_i = 0 直接消失。第一项展开:

\sum_i \alpha_i y_i \left(\sum_j \alpha_j y_j x_j \cdot x_i\right) = \sum_i\sum_j \alpha_i \alpha_j y_i y_j (x_i \cdot x_j)

注意 L 中第二项前面有个负号,于是:

L = \frac{1}{2}\sum_i\sum_j \alpha_i \alpha_j y_i y_j (x_i\cdot x_j) - \sum_i\sum_j \alpha_i \alpha_j y_i y_j (x_i\cdot x_j) + \sum_i \alpha_i

简化一下:

L = \sum_i \alpha_i - \frac{1}{2}\sum_i\sum_j \alpha_i \alpha_j y_i y_j (x_i\cdot x_j)

对偶问题就成了:

\max_{\alpha} ; \sum_{i=1}^n \alpha_i - \frac{1}{2}\sum_{i=1}^n\sum_{j=1}^n \alpha_i \alpha_j y_i y_j x_i \cdot x_j

\text{s.t.} \quad \alpha_i \ge 0, \quad \sum_{i=1}^n \alpha_i y_i = 0

到这里,SVM的对偶问题已经出现了。注意里面出现了 x_i \cdot x_j 的内积形式,这一点很关键,后面核函数就是从这里切入的。

关于KKT条件,完整写下来是:

  • 原可行性:y_i(w \cdot x_i + b) - 1 \ge 0
  • 对偶可行性:\alpha_i \ge 0
  • 互补松弛:\alpha_i [y_i(w \cdot x_i + b) - 1] = 0

互补松弛条件告诉我们一件非常重要的事:如果 \alpha_i > 0,那么 y_i(w \cdot x_i + b) - 1 = 0,也就是该样本落在“间隔边界”上,它是支持向量。如果 y_i(w \cdot x_i + b) > 1,则对应的 \alpha_i = 0,该样本对模型没有任何贡献。这就是SVM稀疏性的来源。

2.3 从对偶问题反解 w 与 b

求解对偶问题得到 \alpha 之后,w 可以直接用:

w = \sum_{i=1}^n \alpha_i y_i x_i

需要特别强调,这个求和里的项只有 \alpha_i > 0 的支持向量参与了,其余样本的 \alpha_i = 0,所以 w 本质上是由支持向量线性组合而成的,计算上也非常便宜。

b 怎么求?利用互补松弛条件,对任意一个支持向量,即 \alpha_k > 0 的样本,有:

y_k(w \cdot x_k + b) = 1

两边乘以 y_k(注意 y_k^2 = 1):

w \cdot x_k + b = y_k

所以:

b = y_k - w \cdot x_k

实际计算时,为了数值稳定,通常对所有支持向量计算 b,然后取平均值。我在手写代码时,发现直接用第一个支持向量算 b 也凑合,但偶尔会出现浮点误差导致偏移,所以工程上建议用均值。

于是最终的决策函数是:

f(x) = \text{sign}\left(\sum_{i=1}^n \alpha_i y_i (x_i \cdot x) + b\right)

又是一个内积运算。这意味着训练和预测都只需要样本之间的内积,而不需要显式使用特征坐标,为核技巧埋下了伏笔。

2.4 支持向量与几何意义

有了上面的解,可以更直观地看SVM的几何意义。假设二维平面上有三个支持向量,两个正类一个负类,那么 w 就是这三个样本的线性组合。超平面的位置由这些支持向量“撑”起来,移动其他非支持向量,只要不越过间隔边界,模型都不会改变。

这也带来一个重要的实践直觉:SVM模型实际上只“记住”了少量支持向量样本。我曾在某电商用户分类项目里统计过,训练集 8 万条样本,最终支持向量只有 1200 多个,占比不到 2%。这种稀疏性使得SVM在推断时非常快,也便于分析模型到底被哪些“难分样本”主导。

如果你去画决策边界和间隔边界,会发现间隔边界正好经过支持向量,而且两条间隔边界之间没有其他样本。理解了这张图,后面软间隔里“允许少量样本跑到间隔内部”的放松方式就非常自然。

3. 软间隔SVM:当数据不是完美可分时

3.1 松弛变量的引入与损失函数

硬间隔要求所有样本都满足 y_i(w \cdot x_i + b) \ge 1,这在真实数据上太苛刻了。噪声、离群点都可能导致训练集根本不可分,或者即便线性可分,强行找一个巨大间隔的超平面也可能让模型过拟合。

解决办法是允许部分样本不满足原始约束,但要对这种“违约”进行惩罚。经典做法是引入非负松弛变量 \xi_i,约束变成:

y_i(w \cdot x_i + b) \ge 1 - \xi_i, \quad \xi_i \ge 0

分析一下这个约束:当 \xi_i = 0 时,样本落在间隔边界之外;当 0 < \xi_i < 1 时,样本落在间隔边界与超平面之间,但分类还是正确的;当 \xi_i > 1 时,样本被错分到了另一边。所以松弛变量不仅能处理线性不可分,还能处理“间隔内”的样本。

目标函数变为:

\min_{w,b,\xi} ; \frac{1}{2}|w|^2 + C\sum_{i=1}^n \xi_i

这里 C 是惩罚参数。C 越大,对违约样本的惩罚越重,模型越倾向于把所有样本都分对,但间隔会变小;C 越小,模型越容忍错误,间隔更大,但训练误差可能变大。

其实这一项可以写成更熟悉的形式:软间隔SVM等价于在经验损失上加 L2 正则,其中损失函数是 ls(p) = \max(0, 1 - p),也就是hinge loss。展开看,当 y_i(w\cdot x_i + b) \ge 1 时,\xi_i = 0;否则 \xi_i = 1 - y_i(w\cdot x_i + b)。因此目标就是:

\min_{w,b} ; \sum_i \max(0, 1 - y_i(w\cdot x_i + b)) + \frac{1}{2C}|w|^2

这解释了为什么SVM自带正则化:最大化间隔本质上是控制模型复杂度,和对偶空间中的范数约束是同一件事。

3.2 带软间隔的对偶推导

写出带松弛变量的拉格朗日函数:

L = \frac{1}{2}|w|^2 + C\sum_i \xi_i - \sum_i \alpha_i [y_i(w\cdot x_i + b) - 1 + \xi_i] - \sum_i \mu_i \xi_i

其中 \alpha_i \ge 0, \mu_i \ge 0。

对 w 求导:

w - \sum_i \alpha_i y_i x_i = 0 \Rightarrow w = \sum_i \alpha_i y_i x_i

对 b 求导:

\sum_i \alpha_i y_i = 0

对 \xi_i 求导:

C - \alpha_i - \mu_i = 0 \Rightarrow \alpha_i + \mu_i = C

因为 \mu_i \ge 0,所以 \alpha_i \le C。这是软间隔和硬间隔最关键的区别:拉格朗日乘子 \alpha_i 被限制在 [0, C] 区间,而不是无上界。

代回拉格朗日函数后,含 \xi_i 的项会相互抵消。推导过程跟硬间隔几乎一样,最后得到对偶问题:

\max_{\alpha} ; \sum_i \alpha_i - \frac{1}{2}\sum_i\sum_j \alpha_i\alpha_j y_i y_j (x_i \cdot x_j)

\text{s.t.} \quad 0 \le \alpha_i \le C, \quad \sum_i \alpha_i y_i = 0

所以从硬间隔到软间隔,唯一的区别就是对偶变量多了一个上界 C。这一点在实现时非常有用:你不需要重写整套优化算法,只需要在投影操作时把 \alpha 限制在 [0, C] 内。

KKT条件也更复杂一点。对支持向量要分情况讨论:

  • 如果 0 < \alpha_i < C,则 \xi_i = 0,样本在间隔边界上。
  • 如果 \alpha_i = C,则 \xi_i \ge 0,样本在间隔内部或者被错分。具体是间隔内还是错分,取决于 \xi_i 是否大于 1。

b 的计算不能简单用任意支持向量,最好只用满足 0 < \alpha_i < C 的“自由支持向量”,因为它们的 \xi_i 恰好为 0,最稳定。这也是很多开源库内部实现的一个细节。

3.3 参数C的语义与调参心得

C 是软间隔SVM最重要的超参数之一。我在实践中常被新手问:“C设成多少合适?”答案永远是:试,但在试之前理解C的取值范围。

C 越小(比如 0.01),模型越重视“大间隔”,训练误差可能偏高,但泛化性可能更好。C 越大(比如 1000),模型越倾向于把所有训练点都分类正确,结果就是间隔变窄,对噪声极其敏感。极端情况下 C 无穷大,就变成硬间隔SVM。

我曾在一个客户流失预测任务里做对比实验,C 从 0.01 到 100 每隔一个数量级跑一遍,发现验证集准确率不是单调变化,而是在 C=1 附近最好。这说明C对泛化误差的曲线往往是U形或者倒U形,不能拍脑袋决定。

实操建议:先对特征做标准化,然后用对数均匀网格搜索,比如 C 取 2^{-5}, 2^{-3}, ..., 2^{15}。配合交叉验证,比瞎猜靠谱得多。另外,若使用带内置交叉验证的库,可以直接传参 C=10.0 这样手动搜索,也可以用网格搜索工具。

还有一个容易忽略的点:当类别不平衡时,正负样本数量差别很大,C 实际上是对所有样本统一施加惩罚,少数类被忽略的风险很高。解决办法是给不同类别分配不同惩罚,比如某库中的 class_weight 参数,或者手动设置正类的 C 大一些。这一点后面还会展开。

4. 核技巧:让SVM从线性走向非线性

4.1 为什么需要核函数:特征映射与内积

线性SVM只能处理线性可分的数据。如果数据在原始空间呈现环形分布、异或模式,直线根本分不开。一个经典想法是:把原始特征映射到高维空间,在高维空间里做线性划分。

假设映射函数是 \phi(x),映射后的数据为 \phi(x_i),线性SVM的决策函数就变成:

f(x) = \text{sign}\left(\sum_i \alpha_i y_i \phi(x_i) \cdot \phi(x) + b\right)

对偶问题中的内积也变成 \phi(x_i) \cdot \phi(x_j)。如果 \phi 的维度很高,直接计算内积的计算量会爆炸。核函数就是用来解决这个问题的:它能在不显式计算 \phi(x) 的情况下,直接算出高维空间的内积。

例如,考虑二维向量 x = (x_1, x_2),映射到三维空间:

\phi(x) = (x_1^2, x_2^2, \sqrt{2} x_1 x_2)

那么 \phi(a)\cdot\phi(b) = a_1^2b_1^2 + a_2^2b_2^2 + 2a_1a_2b_1b_2 = (a_1b_1 + a_2b_2)^2 = (a\cdot b)^2。

所以这里的核函数 K(a,b) = (a\cdot b)^2 对应着某个三维特征映射。更神奇的是,这个技巧可以推广到无穷维,比如高斯核 RBF:

K(x, z) = \exp\left(-\frac{|x-z|^2}{2\sigma^2}\right)

它对应的特征映射是无穷维的,但计算核函数只需要一个指数运算。这就是核技巧的优雅之处。

4.2 常用核函数与选择建议

先放一个常用核函数对照表,后面再详细说选择思路。

核函数表达式特点适用场景
线性核K(x,z)=x·z无额外参数,计算最快特征维度高、样本量大、数据接近线性可分
多项式核K(x,z)=(γ x·z + r)^d能表达交互特征,但参数多图像、文本中低维特征,需要一定程度非线性
高斯核/RBFK(x,z)=exp(-γ||x-z||^2)非线性能力强,是默认首选大多数中小规模数据,特征维度适中
Sigmoid核K(x,z)=tanh(γ x·z + r)和神经网络相关,但可能非正定少用,一般不如RBF稳定

RBF 核几乎是实践中的“万金油”。为什么大家对它偏爱有加?因为它只有 gamma 一个参数,而且对应的高斯特征空间具有很好的局部性质:距离近的样本相似度高,距离远的样本相似度迅速衰减。这种局部性让RBF能拟合很复杂的边界,但也容易过拟合,尤其是 gamma 很大时。

我的个人经验是:

  • 先尝试线性核。如果特征维度非常稀疏(比如文本的TF-IDF上万维),线性核往往又快又好。
  • 如果线性核效果不行,换RBF核,配合交叉验证调 gamma。
  • 多项式核通常用在你知道数据有明确的低阶交互关系时,否则用它很容易出现“核矩阵数值不稳定”的问题,高阶多项式还可能输出无穷大。
  • Sigmoid核真正用到的场景很少,它不是正定核,某些优化器的收敛没有保证。

4.3 Mercer条件与核矩阵的直觉

不是随便一个函数都能当核函数。有效核函数要求对任意有限个样本,核矩阵 K,其中 K_{ij}=K(x_i,x_j),是半正定的,这个条件叫 Mercer 条件。

理解这一点有个更实惠的角度:核矩阵本质上是一个相似度矩阵,必须满足“任意两个样本之间的相似度能构成一个有效的内积”。如果核矩阵不是半正定,对偶问题可能变成非凸,优化不收敛。

实操中你会遇到一个问题:直接用自定义核函数,可能在某组数据上直接报错或者收敛极慢,大概率是核矩阵不正定。一个简单的检查方法是:对训练数据计算核矩阵,用 numpy 检查它的最小特征值。如果出现明显负特征值,就不要用这个核函数。

不过,某些情况下我们可以“构造”新核。比如核函数的非负线性组合还是核函数,核函数相乘还是核函数。我曾在某个推荐系统项目里把“用户特征核”和“物品特征核”相加,效果比单核好。这个技巧叫“多核学习”的初级版,原理就是保证核矩阵仍然半正定。

4.4 核函数与模型泛化的一点体会

经常有人把核函数当成“魔法”,以为选了RBF就一定能拟合任意数据。实际上核函数的选择暴露了我们先验知识:你希望样本在高维空间里以什么方式相似。RBF假设相似性随欧氏距离平滑衰减;线性核假设原始空间已经可以线性划分。没有免费的午餐。

我在某图像分类Demo里试过,把像素值直接丢给RBF-SVM,gamma 调到很大,训练准确率可以逼近 1,但测试准确率还不如一个简单的线性模型。原因是像素空间的高维非线性映射产生了大量无意义的“记忆单元”,把噪声也当成了模式。

所以一个反直觉但重要的经验:先做特征工程,后调核函数。如果特征本身区分度差,核函数救不了你。只有当特征表达合理,但线性边界确实不够时,再考虑非线性核。而且用核方法前,强烈建议对每个特征做标准化,否则不同量纲下的欧氏距离会被量纲大的特征主导,RBF核会失效。

4.5 核函数是“隐式特征工程”还是“隐式参数爆炸”

这里补充一个我踩过的坑。刚接触核方法时,我以为核函数不用显式算高维特征,所以模型参数量会很少。其实不是。RBF核对应的特征空间是无穷维,虽然对偶变量的数量还是等于训练样本数,但模型的“有效容量”可能远远大于线性SVM。换句话说,核函数让模型拥有了接近无限的表达能力,限制模型复杂度的重要工具又变回了 C 和 gamma。

gamma 和 C 的关系也需要特别说明。很多人只调 C,忽略 gamma。在RBF-SVM中,gamma 控制单个样本的影响半径:gamma 越小,影响半径越大,决策边界越平滑;gamma 越大,每个样本只影响很近的区域,决策边界越弯曲,容易过拟合。C 控制“对错误的容忍度”。两者共同决定模型的复杂度。我的经验是:先用一个较大的 C(比如 10),再根据偏差/方差调整 gamma;等 gamma 找到合理范围后,重新细调 C。不要同时大范围乱试,否则搜索空间太大。

5. 从推导到实现:SMO算法的关键思路

5.1 SMO在解决什么问题

对偶问题虽然形式漂亮,但它是一个带约束的二次规划(QP)问题,变量数等于样本数 n。当 n 上万时,通用QP求解器非常慢,内存开销也大。SMO(Sequential Minimal Optimization,序列最小优化)把大QP问题拆成一系列只有两个变量的子问题,每个子问题都有解析解,从而绕开了数值优化。

为什么选择两个变量而不是一个?因为 \alpha 必须满足等式约束 \sum_i \alpha_i y_i = 0。如果只更新一个 \alpha,等式约束大概率立刻被破坏。所以至少要同时更新两个变量,让它们的改变量相互抵消。

SMO算法的外层循环负责选“最违反KKT条件”的第一变量,内层循环选一个与之配对并且“步长最大”的第二变量。整个过程重复迭代,直到所有变量都满足KKT条件的容忍范围。

5.2 两个变量的二次规划解析

假设选中变量为 \alpha_1 和 \alpha_2,其他 \alpha_i 固定。由于 \sum_i \alpha_i y_i = 0,固定其他变量后,\alpha_1 和 \alpha_2 之间满足:

\alpha_1 y_1 + \alpha_2 y_2 = -\sum_{i\ne 1,2} \alpha_i y_i = \text{常数}

因此 \alpha_2 可以用 \alpha_1 表示,问题变成单变量二次函数。再考虑 0 \le \alpha_i \le C 的边界约束,可行域通常是一个区间 [L, H]。对二次函数在区间内求极值,如果未约束极值点落在区间外,就裁剪到边界。更新公式不需要我在这里完整抄一遍,但理解几个要点对排查问题很有帮助:

  • 更新步长分母是核函数组合:\eta = K_{11} + K_{22} - 2K_{12},当 \eta = 0 时无法更新,需要特殊处理。
  • 如果 \eta < 0,理论上目标函数是凹的,需要检查KKT违反程度,选择边界值 L 或 H 中最优的。
  • 裁剪时要认真处理 y_1 和 y_2 同号还是异号,这影响 L 和 H 的边界计算。

我在实现SMO时,最容易被坑的地方是边界 L 和 H 的计算。如果 y_1 \ne y_2,L = max(0, \alpha_2 - \alpha_1),H = min(C, C + \alpha_2 - \alpha_1);如果 y_1 = y_2,L = max(0, \alpha_1 + \alpha_2 - C),H = min(C, \alpha_1 + \alpha_2)。这个边界必须通过画图来理解:先画 \alpha_1 和 \alpha_2 在 [0,C]\times[0,C] 方块内的可行区域,再叠加上线性等式约束,可行域就是一条线段截断后的区间。

5.3 实现中容易踩的坑

手写SMO是理解SVM的绝佳训练,但有几个常见的坑,我当年都踩过:

第一个坑:更新 b 时用了所有支持向量。前面说过,最好只用自由支持向量(0 < \alpha_i < C)。如果 \alpha_i 在边界上,它对应的 \xi_i 不一定为 0,用它算 b 会带进松弛量的误差。手写时如果发现 b 震荡,大概率是这里没处理好。

第二个坑:停止条件只看目标函数差值。目标函数变化很小不代表KKT条件一定满足,尤其在高维下。更稳妥的做法是同时检查所有样本的KKT条件违反程度,比如允许误差 tol=1e-3。

第三个坑:缓存核矩阵时内存爆掉。SMO需要反复计算 K_{ij},如果不用缓存,每次迭代都重新计算核函数,训练会慢到怀疑人生;如果缓存全部 n×n 核矩阵,n=10万 时就需要几十GB内存。折中方案是只缓存最近用到的子块,或者用核函数近似、稀疏化等手段。

第四个坑:数值稳定。计算 \eta 时可能出现 (K_{11}+K_{22}-2K_{12}) 接近 0 甚至为负,这通常由相同样本重复、或者核矩阵不正定引起。先检查数据中是否有重复样本,再检查核参数是否过大。

5.4 一个手写SVM的简单验证流程

如果你也想从零手写SVM验证推导,我建议按这个顺序来:

  1. 生成一个小规模线性可分数据集,比如二维高斯点,样本数 50 左右。
  2. 用硬间隔对偶问题实现,故意去掉 C 上界,验证拉格朗日乘子是否只在支持向量上非零。
  3. 加软间隔,设置 C=1,然后画决策边界和间隔边界,观察松弛变量为正的样本位置。
  4. 换RBF核,在小规模非线性数据集上测试,再对比 scikit-learn 或类似库的结果。
  5. 用分层交叉验证调 C 和 gamma,看准确率和F1的变化。

这几个步骤走完,你对SVM的理解会比看十遍推导都要牢。我在教A同学时用这个流程,他一开始也是一堆公式看不懂,后来自己把二维可视化做出来,突然就通了。

6. 实战中的常见问题与排查技巧

6.1 不收敛或者收敛很慢怎么办

SVM训练不收敛,最常见的原因是数据没标准化。原始特征量纲差异大,比如一列是 0.001 量级,另一列是 10000 量级,RBF核计算的距离会被大数值特征主导,核矩阵退化严重,SMO的 \eta 数值也很不稳定。解决办法很简单:先做标准化,通常用z-score或者归一化到 [0,1] 区间。

另一个原因是参数设置不合理。C 太小可能导致模型过于保守,对偶问题的解在可行域内部,收敛慢;gamma 太大可能导致核矩阵接近单位矩阵,对角线元素为 1,非对角线几乎为 0,会让对偶问题非常尖锐,难以稳定收敛。我的经验是先把 gamma 取一个普通值,比如 1/特征维度,然后观察收敛曲线,再逐步调整。

如果你用的是通用SMO库,可以调大收敛容忍度 tol,比如从 1e-3 调到 1e-4 或者反过来,要根据实际数据规模决定。太小的 tol 在十万级样本上会让迭代次数爆炸,效果却没提升多少。

6.2 过拟合与C、gamma的调节

SVM过拟合的表现是训练准确率接近 100%,验证集准确率明显下降,而且支持向量的数量占比高,比如超过 50%。为什么支持向量占比高就意味着过拟合?因为模型需要用大量样本撑起一个极其复杂的边界,相当于“记住”了训练集的细节。

调节的核心逻辑是:

  • C 越大,越容易过拟合。降低 C 会强制模型接受更多误分类,换取更大间隔。
  • gamma 越大,决策边界越复杂,越容易过拟合。降低 gamma 会让内积相似度更平滑,边界更简单。

实际操作中,我会画一个二维热力图,横轴是 C,纵轴是 gamma,颜色是交叉验证F1。你会发现一组“对角线”模式:很大 C 配很小 gamma 或者很小 C 配很大 gamma 都能得到不错结果,但不一定同时最优。这种图上有一个“眼状区域”,选其中的中心位置最稳妥。

6.3 数据标准化对SVM的影响

这个问题我没有一次讲透的欲望,但值得再强调。对线性核SVM,标准化主要影响各特征在超平面法向量中的相对权重。如果一个特征的数值是百万级,它对应的 w 分量会被压得极小,但在判断时仍可能主导模型。对RBF核,标准化更关键,因为RBF依赖欧氏距离,不同量纲的特征会让距离计算失真。

标准化通常有两种方案:

  • z-score:\frac{x - \mu}{\sigma},适用于特征近似正态分布。
  • min-max 归一化:\frac{x - min}{max-min},适用于特征有明确上下界。

我测试过不同数据分布下的效果,结论是:如果特征包含较多离群值,min-max 后数据可能挤在一个小区域内,此时 z-score 更稳;如果特征是稀疏的,z-score 会破坏稀疏性,可以考虑用 max-abs 缩放。没有绝对最好,但“不做标准化直接跑RBF-SVM”几乎是必坑。

6.4 类别不平衡与样本权重

实际分类任务里,正负样本比例经常失衡,比如欺诈检测里正样本只占 1%。SVM本来对不平衡比较敏感,因为它的目标是最大化所有样本的间隔,少数类样本如果离群,很容易被当成噪声忽略。

处理办法有几个层次:

  • 第一层,使用 class_weight,相当于对不同类别的 C 设置不同倍率。某开源库内部是让正样本的惩罚乘上权重倍数。
  • 第二层,对少数类做上采样或者SMOTE。但要注意,过采样后再用SVM容易把合成样本当成支持向量,边界可能变得奇怪,我曾遇到SMOTE之后支持向量几乎全是合成样本的情况,效果反而变差。
  • 第三层,调整决策阈值。SVM输出的原始决策值并不一定是校准概率,可以用 Platt scaling 或者直接看决策值分布,选择一个更合理的阈值,而不是死用 0 作为分界。

我个人比较推荐第一层和第三层组合使用。category权重用于训练,阈值用于预测,两者解耦,调参更灵活。

7. 最后再分享几个我用SVM的私人技巧

7.1 用支持向量占比判断模型健康度

训练完SVM后,我会第一时间看两个数:支持向量占比和交叉验证得分。如果支持向量占比小于 10%,模型通常很稀疏、泛化能力也不错;如果大于 50%,基本可以判定模型过拟合或者数据噪声太大。

支持向量占比高还有一种可能:C 太大导致你几乎在用硬间隔,所有离群点都变成了支持向量。这时候先降 C,看占比降不降。如果降 C 后占比还是高,就要回到特征和数据质量层面找原因。

7.2 核函数不是越多越好

很多人喜欢在比赛中堆一堆核函数,然后做加权平均。这不是不行,但如果你刚开始学SVM,我建议先用单一RBF核跑通全流程,再考虑多核。多核学习有一个前提:每个核函数都应有明确含义。比如一个核用于文本语义相似度,另一个核用于交互式特征,它们互补才有意义。盲目堆叠只会增加调参难度,还容易出现过拟合。

7.3 SVM的适用范围与局限

SVM在中小样本、高维特征、非线性边界问题上表现出色,但它不是银弹。当样本量超过百万级,即使使用高效线性SVM,也要非常小心内存和训练时间;非线性SVM的核矩阵开销是 O(n^2),样本量过万后,普通机器的内存可能就撑不住了。这种场景下,我更推荐先用线性模型(比如逻辑回归或线性SVM)做Baseline,如果效果确实不够,再考虑采样后用核SVM,或者转向树模型、神经网络。

7.4 从SVM推导中我学到的最重要的一件事

最后说点题外话。SVM的整个推导过程教会我一个更通用的思维模式:当你面对一个复杂的约束优化问题时,先看能不能转化为对偶问题,再看有没有解析子问题可以利用。这个思路不仅适用于SVM,也适用于很多带正则化的模型。每次拿到新的损失函数,我都会习惯性地写一下它的对偶形式,哪怕实际求解用的是原问题梯度下降。这种“原问题-对偶问题”的视角,会帮你更清晰地看出模型的本质约束在哪里。

SVM的公式推导并不是终点,它只是理解结构化风险最小化思想的一把钥匙。希望你读完这篇梳理后,能自己动手把推导每一步都走一遍,把符号对应的几何意义在纸上画出来。等哪一天你不再需要翻笔记,也能把拉格朗日函数写出来的时候,你才算真正把SVM内化成自己的东西。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询