引言
在深度学习流行之前,支持向量机(Support Vector Machine, SVM)是中小规模表格与文本分类任务的首选。它的魅力在于两点:一是把「找一条最好的分界线」这个模糊目标,严格化成最大化间隔的凸二次规划,有唯一全局最优解;二是用**核技巧(Kernel Trick)**在不动声色间完成高维甚至无穷维映射,让线性模型具备非线性表达能力。
即使今天,在样本量几千到几万、特征维度高、且需要稳定可解释边界的场景里,SVM 仍然是一个极难被超越的强基线。本文从几何直觉出发,讲清软间隔与 C 参数、对偶与支持向量、四类常用核的选择与调参,最后落到 scikit-learn 的完整工程实践。
前置:分类任务的评估指标与不平衡处理见 /ml-supervised-classification/;多分类拆解策略见 /ml-multiclass-multilabel/;若偏好树模型可对比 /ml-ensemble-learning/。
目录
- 1. 最大间隔的几何直觉
- 2. 软间隔与铰链损失
- 3. 对偶问题与支持向量
- 4. 核技巧
- 5. 常用核函数与调参
- 6. 回归、多分类与概率输出
- 7. 大规模数据与核近似
- 8. scikit-learn 实战
- 9. 常见坑与选型
- 10. 总结
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
对偶形式有两个深刻结论:
- 解只依赖样本间的内积
x_i · x_j——这正是核技巧的入口。 - 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 内置四类核:
| 核 | 公式 | 适用 | 关键参数 |
|---|---|---|---|
| linear | x·z | 高维稀疏(文本)、线性可分 | 无(只调 C) |
| poly | (γ x·z + r)^d | 多项式关系、图像 | degree、gamma、coef0 |
| rbf | exp(-γ‖x-z‖²) | 通用默认,低维非线性 | gamma、C |
| sigmoid | tanh(γ 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³),样本上万后就明显变慢。三条出路:
- 线性 SVM:
LinearSVC用 liblinear,SGDClassifier(loss="hinge")支持在线与核外学习,适合超高维稀疏数据(文本)。 - 核近似:用随机特征把核映射显式化,再用线性模型。
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 专题 。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。