支持向量机与核方法:从最大间隔到 RBF 核实战

支持向量机用最大间隔把分类问题写成凸二次规划,再借核技巧把线性不可分数据映射到高维求解。本文讲清硬间隔与软间隔、铰链损失与 C 参数、对偶问题与支持向量的含义、线性/多项式/RBF 核的差异与 gamma 调参、SVR 回归与多分类策略、核近似与大规模线性 SVM,并给出 scikit-learn 的完整实战流程与调参清单。

引言

在深度学习流行之前,支持向量机(Support Vector Machine, SVM)是中小规模表格与文本分类任务的首选。它的魅力在于两点:一是把「找一条最好的分界线」这个模糊目标,严格化成最大化间隔的凸二次规划,有唯一全局最优解;二是用**核技巧(Kernel Trick)**在不动声色间完成高维甚至无穷维映射,让线性模型具备非线性表达能力。

即使今天,在样本量几千到几万、特征维度高、且需要稳定可解释边界的场景里,SVM 仍然是一个极难被超越的强基线。本文从几何直觉出发,讲清软间隔与 C 参数、对偶与支持向量、四类常用核的选择与调参,最后落到 scikit-learn 的完整工程实践。

前置:分类任务的评估指标与不平衡处理见 /ml-supervised-classification/;多分类拆解策略见 /ml-multiclass-multilabel/;若偏好树模型可对比 /ml-ensemble-learning/。

目录

1. 最大间隔的几何直觉

给定线性可分数据集,能把两类分开的超平面有无穷多个。SVM 的选择标准是:让离分界线最近的点到分界线的距离尽可能大。这个距离叫间隔(Margin),落在间隔边界上的样本叫支持向量(Support Vector)。

超平面写作 w·x + b = 0,点到超平面的函数间隔是 y(w·x + b),几何间隔要再除以 ||w||。把两类样本规范化到 y(w·x + b) >= 1,最大化几何间隔 2/||w|| 就等价于最小化 ||w||²:

minimize     (1/2) ||w||²
subject to   y_i (w·x_i + b) >= 1,   i = 1..n

这是一个凸二次规划(QP),约束是线性的,因此有全局最优解。间隔越大,模型对噪声的容忍度越高,泛化能力通常越好——这就是「最大化间隔」背后的统计学习理论基础(VC 维与结构风险最小化)。

import numpy as np
from sklearn.datasets import make_blobs

X, y = make_blobs(n_samples=200, centers=2, cluster_std=1.2, random_state=0)
y = np.where(y == 0, -1, 1)          # SVM 习惯用 -1/+1 标签

def margin_width(w):
    return 2 / np.linalg.norm(w)

w = np.array([1.0, 1.0])
print("间隔宽度:", round(margin_width(w), 4))

2. 软间隔与铰链损失

真实数据几乎不可能严格线性可分,硬间隔会因一个离群点而完全失效。软间隔引入松弛变量 ξ_i >= 0,允许少量样本越界:

minimize     (1/2) ||w||² + C · Σ ξ_i
subject to   y_i (w·x_i + b) >= 1 - ξ_i,   ξ_i >= 0

它等价于最小化**铰链损失(Hinge Loss)**加 L2 正则:

L(w) = Σ max(0, 1 - y_i (w·x_i + b))  +  λ ||w||²

铰链损失在 y(w·x + b) >= 1 时为 0,越界后线性增长。这正是 SVM 与逻辑回归的关键差异:逻辑回归对所有点都有梯度,SVM 只惩罚间隔内的点,因此对远离边界的样本不敏感,解由少数支持向量决定。

2.1 C 参数的权衡

C 是正则强度的倒数,控制「最大化间隔」与「最小化分类错误」的取舍:

C 取值行为后果
很小强烈追求宽间隔允许大量越界,偏差高、方差低,可能欠拟合
很大强烈追求分类正确间隔窄,几乎硬间隔,方差高,易过拟合
适中平衡需要交叉验证选择
import numpy as np

def hinge_loss(y, scores):
    return np.maximum(0, 1 - y * scores).mean()

y_true = np.array([1, -1, 1, -1])
scores = np.array([1.2, -0.3, 0.4, 0.9])   # 第三个点越界
print("铰链损失:", round(hinge_loss(y_true, scores), 4))

C 应当在对数尺度上搜索(如 [0.01, 0.1, 1, 10, 100]),并始终配合标准化,因为间隔计算依赖特征尺度。

3. 对偶问题与支持向量

原始问题可以转成拉格朗日对偶形式,引入乘子 α_i >= 0:

maximize     Σ α_i - (1/2) ΣΣ α_i α_j y_i y_j (x_i · x_j)
subject to   0 <= α_i <= C,   Σ α_i y_i = 0

对偶形式有两个深刻结论:

  1. 解只依赖样本间的内积 x_i · x_j——这正是核技巧的入口。
  2. KKT 条件决定哪些 α_i > 0:只有这些样本参与决策,它们就是支持向量。

决策函数只需求新样本与支持向量的内积:

f(x) = sign( Σ_{i ∈ SV} α_i y_i (x_i · x) + b )
from sklearn.svm import SVC

svm = SVC(kernel="linear", C=1.0)
svm.fit(X, y)
print("支持向量数:", svm.n_support_)            # 每类各有多少个
print("支持向量索引:", svm.support_[:5])
print("对偶系数 alpha:", svm.dual_coef_[0][:5])

支持向量通常只占样本的很小比例,这也是 SVM 预测阶段内存占用低的原因。对偶问题规模随样本数平方增长,因此核 SVM 在样本超过约十万时开始吃力。

4. 核技巧

对偶形式里数据只以内积出现。若定义映射 φ(x) 把数据投到高维空间,只需知道 φ(x_i)·φ(x_j) 即可,无需显式计算 φ。核函数 K(x_i, x_j) = φ(x_i)·φ(x_j) 直接给出这个内积——这就是核技巧。

以二次多项式核为例,二维输入映射到六维:

K(x, z) = (x·z + 1)²
       = (x1 z1 + x2 z2 + 1)²
       = 1 + 2 x1 z1 + 2 x2 z2 + x1²z1² + 2 x1x2z1z2 + x2²z2²

右边展开的每一项对应六维空间的一个内积,但我们从未真正构造那六个坐标。RBF 核更极端,它对应无穷维映射,却只需一次指数运算。

import numpy as np

def poly_kernel(X, Z, degree=2, coef0=1.0):
    return (X @ Z.T + coef0) ** degree

def rbf_kernel(X, Z, gamma=1.0):
    sq = (np.sum(X**2, axis=1)[:, None] + np.sum(Z**2, axis=1)[None, :]
          - 2 * X @ Z.T)
    return np.exp(-gamma * sq)

X2 = np.array([[1.0, 2.0]])
print("多项式核:", poly_kernel(X2, X2))
print("RBF 核:", rbf_kernel(X2, X2))

核矩阵(Gram 矩阵)必须是半正定的,才能保证对应某个特征映射。Mercer 定理给出了这一条件,实践中常用的核都满足。

5. 常用核函数与调参

scikit-learn 内置四类核:

核公式适用关键参数
linearx·z高维稀疏(文本)、线性可分无(只调 C)
poly(γ x·z + r)^d多项式关系、图像degree、gamma、coef0
rbfexp(-γ‖x-z‖²)通用默认,低维非线性gamma、C
sigmoidtanh(γ x·z + r)类神经网络,少见gamma、coef0

5.1 gamma 的含义

RBF 核的 gamma 控制单个样本影响的「半径」:

  • gamma 大:影响半径小,每个点只影响自身附近,决策边界曲折,易过拟合。
  • gamma 小:影响半径大,边界平滑,易欠拟合。
  • 经验起点:gamma ≈ 1 / n_features,然后在对数尺度搜索。
小 gamma         中 gamma        大 gamma
──────────       ────────        ────────
平滑边界          合适           边界碎片化,包围每个点

5.2 与 C 的联合调参

C 与 gamma 相互影响,必须联合搜索:

from sklearn.model_selection import GridSearchCV
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC

pipe = make_pipeline(StandardScaler(), SVC(kernel="rbf"))
param_grid = {
    "svc__C": [0.1, 1, 10, 100],
    "svc__gamma": ["scale", 0.01, 0.1, 1.0],
}
grid = GridSearchCV(pipe, param_grid, cv=5, scoring="f1_macro", n_jobs=-1)
grid.fit(X, y)
print("最优参数:", grid.best_params_)
print("最优分数:", round(grid.best_score_, 4))

标准化必须放进 Pipeline,否则交叉验证的每折会泄露全量数据的均值方差。

6. 回归、多分类与概率输出

6.1 支持向量回归 SVR

SVR 把「间隔」改成 epsilon 不敏感带:落在预测值 ±ε 内的误差不计损失,只有超出部分才惩罚。

minimize  (1/2)||w||² + C Σ (ξ_i + ξ_i*)
from sklearn.svm import SVR
from sklearn.datasets import make_regression

Xr, yr = make_regression(n_samples=200, n_features=5, noise=10, random_state=0)
svr = SVR(kernel="rbf", C=10, epsilon=0.1, gamma="scale")
svr.fit(Xr, yr)
print("R²:", round(svr.score(Xr, yr), 4))

ε 越大,容忍的误差越宽,支持向量越少、模型越平滑;C 越大,越不允许超出 ε 的误差。

6.2 多分类策略

SVM 原生只做二分类,多分类需拆解:

策略做法分类器数特点
OvR一对其余k简单,类别不平衡
OvO一对一投票k(k-1)/2每对只用小样本,训练快

scikit-learn 默认 OvO(decision_function_shape="ovr" 可改决策输出形状)。类别很多时 OvO 的投票可能平票,需按决策值加权。

6.3 概率输出

SVM 输出的是到超平面的距离,不是概率。要概率需用 Platt 缩放(在决策值上拟合一个 sigmoid),代价是额外交叉验证与训练时间:

svm_prob = SVC(kernel="rbf", C=1.0, probability=True, random_state=0)
svm_prob.fit(X, y)
proba = svm_prob.predict_proba(X[:3])
print("概率:", proba.round(3))

probability=True 会触发内部 5 折,训练耗时约增 5 倍,非必要不开。

7. 大规模数据与核近似

核 SVM 训练复杂度约 O(n² ~ n³),样本上万后就明显变慢。三条出路:

  1. 线性 SVM:LinearSVC 用 liblinear,SGDClassifier(loss="hinge") 支持在线与核外学习,适合超高维稀疏数据(文本)。
  2. 核近似:用随机特征把核映射显式化,再用线性模型。
from sklearn.kernel_approximation import Nystroem, RBFSampler
from sklearn.linear_model import SGDClassifier
from sklearn.pipeline import make_pipeline

# Nystroem:用采样点构造低秩核近似
nys = make_pipeline(
    Nystroem(kernel="rbf", gamma=0.1, n_components=300, random_state=0),
    SGDClassifier(loss="log_loss", max_iter=1000, random_state=0),
)
nys.fit(X, y)
print("Nystroem 近似精度:", round(nys.score(X, y), 4))

Nystroem 用 n_components 个地标点近似核矩阵,把 O(n²) 降到 O(n·m)。RBFSampler 则用随机傅里叶特征逼近 RBF 核。

rbf_feat = RBFSampler(gamma=0.1, n_components=500, random_state=0)
X_feat = rbf_feat.fit_transform(X)
lin = SGDClassifier(loss="hinge", max_iter=1000, random_state=0).fit(X_feat, y)
print("随机傅里叶特征精度:", round(lin.score(X_feat, y), 4))

8. scikit-learn 实战

一个完整的端到端流程:标准化、网格搜索、评估、可视化决策边界。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_moons
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.svm import SVC
from sklearn.metrics import classification_report

X, y = make_moons(n_samples=500, noise=0.25, random_state=42)
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.25, stratify=y,
                                          random_state=42)

pipe = make_pipeline(StandardScaler(), SVC(kernel="rbf"))
grid = GridSearchCV(pipe, {"svc__C": [0.1, 1, 10, 100],
                           "svc__gamma": [0.1, 0.5, 1, 2]},
                    cv=5, scoring="accuracy", n_jobs=-1)
grid.fit(X_tr, y_tr)
print("最优:", grid.best_params_)
print(classification_report(y_te, grid.predict(X_te)))

绘制决策边界能直观看出 C 与 gamma 的作用:

def plot_boundary(model, X, y):
    xx, yy = np.meshgrid(np.linspace(X[:, 0].min() - 0.5, X[:, 0].max() + 0.5, 200),
                         np.linspace(X[:, 1].min() - 0.5, X[:, 1].max() + 0.5, 200))
    Z = model.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)
    plt.contourf(xx, yy, Z, alpha=0.3, cmap="coolwarm")
    plt.scatter(X[:, 0], X[:, 1], c=y, cmap="coolwarm", edgecolors="k")
    plt.title(f"C={model[-1].C}, gamma={model[-1].gamma}")
    plt.show()

plot_boundary(grid.best_estimator_, X, y)

8.1 特征缩放的重要性

SVM 对尺度极度敏感:若一个特征范围是 01、另一个是 010000,后者会主导距离计算,间隔与核函数全部失真。任何 SVM 都必须先标准化(StandardScaler)或归一化。树模型不需要,这是两者工程习惯的核心差异。

8.2 类别不平衡

与逻辑回归一样,SVM 可用 class_weight="balanced" 按类频自动加权:

svm_bal = SVC(kernel="rbf", C=1.0, class_weight="balanced", random_state=0)

9. 常见坑与选型

现象根因处理
训练集精度 100%、测试集很差C 或 gamma 过大减小 gamma,降低 C,做交叉验证
精度普遍偏低未标准化加 StandardScaler
训练极慢样本量太大用了核 SVM换 LinearSVC / SGD / 核近似
概率输出不可靠未开 probability开 probability=True 或改用逻辑回归
类别不平衡下召回低未加权class_weight=“balanced” 或调阈值
结果每次略变随机性固定 random_state,或多次交叉验证

9.1 什么时候该用 SVM

样本量 < 10 万 且 特征维度高 / 需要清晰间隔?
  ├── 是 → 优先 RBF 核 SVM(先标准化 + 网格搜索)
  └── 否
        ├── 超大规模稀疏文本 → 线性 SVM / SGD
        ├── 需要概率与可解释系数 → 逻辑回归
        └── 表格数据、追求精度 → 梯度提升树(见集成学习)

10. 总结

10.1 核心要点

  • SVM 用最大化间隔把分类变成凸二次规划,有唯一最优解。
  • 软间隔的 C 参数控制「宽间隔」与「少犯错」的权衡;铰链损失让解只由支持向量决定。
  • 核技巧让线性模型获得非线性能力,代价是 O(n²) 以上的训练复杂度。
  • RBF 核的 gamma 与 C 必须联合调参,且特征缩放不可省略。

10.2 与相邻方法的关系

SVM 与逻辑回归同属线性判别模型,差异在损失函数(铰链 vs 对数)与解的稀疏性;与树模型相比,SVM 在中小样本、高维、需要平滑非线性边界时更稳,而树模型在混合类型特征、需要特征重要性时更优。进一步理解优化过程可参考 /ml-gradient-descent-optimizers/,若想深入算法与大规模工程实现,可延伸阅读 AI/ML 专题 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「ml」更多文章

  1. 高斯过程回归与分类:不确定性建模实战
  2. MLOps 全生命周期实践:从实验到生产闭环
  3. 公平性与偏差缓解:从度量到去偏实战