支持向量机:在两类之间画最宽的街 本节摘要:支持向量机(Support Vector Machine, SVM)的核心理念只有一句——在两类数据之间找到最宽的「街」。给你两类点,能分开它们的直线有无穷多条,该选哪一条?选间隔(Margin)最大的那条:决策边界到两侧最近点的距离越宽,分类器越自信、泛化越好。SVM 是深度学习崛起前统治 ML 的算法,在 20 世纪 90 年代末到 2010 年代初称王。今天它在小数据集、高维稀疏数据(如 TF-IDF 文本)、需要数学保证的场景仍是首选。
本节摘要:支持向量机(Support Vector Machine, SVM)的核心理念只有一句——在两类数据之间找到最宽的「街」。给你两类点,能分开它们的直线有无穷多条,该选哪一条?选间隔(Margin)最大的那条:决策边界到两侧最近点的距离越宽,分类器越自信、泛化越好。SVM 是深度学习崛起前统治 ML 的算法,在 20 世纪 90 年代末到 2010 年代初称王。今天它在小数据集、高维稀疏数据(如 TF-IDF 文本)、需要数学保证的场景仍是首选。本节将从零实现线性 SVM(铰链损失 + L2 正则 + 梯度下降的原始形式),讲透最大间隔、支持向量、软间隔与 C 参数的权衡、铰链损失与对数损失之别,再用对偶形式引出核技巧(Kernel Trick)——如何不显式映射到高维空间就能学到任意非线性边界。
阅读完本节,你应当能够:
你有两类数据点,需要画一条线(或超平面)把它们分开。能用的线有无穷多条,该选哪条?
选间隔最大的那条。间隔是决策边界到两侧最近点的距离。间隔越宽,分类器越自信,对未见数据泛化越好。
这个直觉引出 SVM,ML 里数学上最优雅的算法之一。深度学习之前,SVM 是统治性的分类方法,如今仍是小数据集、高维数据、需要原理清晰且有理论保证的问题的最佳选择。
SVM 直接呼应第 1 章:优化是凸的(第 18 节),间隔用范数度量(第 14 节),核技巧利用点积处理非线性边界,却从不在高维空间里计算。
给定线性可分数据,标签 y_i ∈ {-1, +1},特征向量 x_i,我们要一个超平面 w^T x + b = 0 分开两类。
点 x_i 到超平面的距离:
distance = |w^T x_i + b| / ||w||
对正确分类的点:y_i * (w^T x_i + b) > 0。间隔是超平面到两侧最近点距离的两倍。
优化问题:
maximize 2 / ||w|| (间隔宽度) subject to y_i * (w^T x_i + b) >= 1 对所有 i
等价地(最小化 ||w||² 更好优化):
minimize (1/2) ||w||^2 subject to y_i * (w^T x_i + b) >= 1 对所有 i
这是凸二次规划,有唯一全局解。恰好落在间隔边界上(y_i * (w^T x_i + b) = 1)的数据点是支持向量。只有它们决定决策边界。移动或删除任何非支持向量点,边界都不变。
大多数训练点无关紧要,只有支持向量算数。这就是 SVM 预测时省内存的原因:只需存支持向量,不用存整个训练集。
支持向量的数量还给出泛化误差上界。相对于数据集规模,支持向量越少,泛化越好。
真实数据极少完美可分。有些点可能在边界错误一侧,或在间隔内部。软间隔形式用松弛变量允许违规。
minimize (1/2) ||w||^2 + C * sum(xi_i) subject to y_i * (w^T x_i + b) >= 1 - xi_i xi_i >= 0 对所有 i
松弛变量 xi_i 度量点 i 多大程度违反间隔。C 控制权衡:
| C 值 | 行为 |
|---|---|
| 大 C | 重罚违规,间隔窄,误分少,过拟合 |
| 小 C | 容忍更多违规,间隔宽,误分多,欠拟合 |
C 是正则化强度的倒数。大 C = 弱正则,小 C = 强正则。
软间隔 SVM 可改写为无约束优化:
minimize (1/2) ||w||^2 + C * sum(max(0, 1 - y_i * (w^T x_i + b)))
max(0, 1 - y_i * f(x_i)) 就是铰链损失。点正确分类且在间隔外时它为零;在间隔内或被误分时它线性增长。
单点铰链损失: loss | | \ | \ | \ | \ | \_______________ | +-----|-----|--------> y * f(x) 0 1 y*f(x) >= 1(正确分类且在间隔外)时损失为 0。 y*f(x) < 1 时线性惩罚。
对比逻辑回归的逻辑损失:
铰链: max(0, 1 - y*f(x)) 间隔处硬截断 逻辑: log(1 + exp(-y*f(x))) 光滑,永不为零
铰链损失产生稀疏解(只有支持向量贡献非零)。逻辑损失用到所有点。这让 SVM 预测时更省内存。
可以在铰链损失加 L2 正则上用梯度下降训练线性 SVM,无需解约束 QP:
L(w, b) = (lambda/2) * ||w||^2 + (1/n) * sum(max(0, 1 - y_i * (w^T x_i + b))) 对 w 的梯度: 若 y_i * (w^T x_i + b) >= 1: dL/dw = lambda * w 若 y_i * (w^T x_i + b) < 1: dL/dw = lambda * w - y_i * x_i 对 b 的梯度: 若 y_i * (w^T x_i + b) >= 1: dL/db = 0 若 y_i * (w^T x_i + b) < 1: dL/db = -y_i
这叫原始形式(Primal)。每个 epoch O(n * d),n 是样本数、d 是特征数。对大型稀疏高维数据(文本分类)很快。
SVM 问题的拉格朗日对偶(第 1 章第 18 节 KKT 条件)是:
maximize sum(alpha_i) - (1/2) * sum_ij(alpha_i * alpha_j * y_i * y_j * (x_i . x_j)) subject to 0 <= alpha_i <= C sum(alpha_i * y_i) = 0
对偶只涉及数据点之间的点积 x_i . x_j。这是关键洞见:把每个点积换成核函数 K(x_i, x_j),SVM 就能学非线性边界,却从不必显式计算那个变换。
线性核: K(x, z) = x . z 多项式核: K(x, z) = (x . z + c)^d RBF(高斯): K(x, z) = exp(-gamma * ||x - z||^2)
RBF 核把数据映射到无穷维空间。输入空间里靠近的点核值接近 1,远离的点核值接近 0。它能学任何光滑决策边界。
核技巧在高维空间算点积,却从不去那里。对 D 维的 d 次多项式核,显式特征空间有 O(D^d) 维,但 K(x, z) 只用 O(D) 时间算出来。
支持向量回归(SVR)在数据周围拟合一个宽度为 epsilon 的管。管内的点损失为零,管外的点被线性惩罚。
minimize (1/2) ||w||^2 + C * sum(xi_i + xi_i*) subject to y_i - (w^T x_i + b) <= epsilon + xi_i (w^T x_i + b) - y_i <= epsilon + xi_i* xi_i, xi_i* >= 0
epsilon 控制管宽。管越宽 = 支持向量越少 = 拟合越光滑;管越窄 = 支持向量越多 = 拟合越紧。
SVM 在 20 世纪 90 年代末到 2010 年代初统治 ML。深度学习因以下原因超越它:
| 因素 | SVM | 深度学习 |
|---|---|---|
| 特征工程 | 需要 | 自学特征 |
| 可扩展性 | 核方法是 O(n²)~O(n³) | SGD 每 epoch O(n) |
| 图像/文本/音频 | 需手工特征 | 从原始数据学 |
| 大数据集(>10 万) | 慢 | 扩展性好 |
| GPU 加速 | 收益有限 | 巨大加速 |
SVM 在这些情况仍胜:
基础。为一批数据算铰链损失及其梯度。
def hinge_loss(X, y, w, b): n = len(X) total_loss = 0.0 for i in range(n): margin = y[i] * (dot(w, X[i]) + b) total_loss += max(0.0, 1.0 - margin) return total_loss / n
最小化正则化铰链损失,无需 QP 求解器。
class LinearSVM: def __init__(self, lr=0.001, lambda_param=0.01, n_epochs=1000): self.lr = lr self.lambda_param = lambda_param self.n_epochs = n_epochs self.w = None self.b = 0.0 def fit(self, X, y): n_features = len(X[0]) self.w = [0.0] * n_features self.b = 0.0 for epoch in range(self.n_epochs): for i in range(len(X)): margin = y[i] * (dot(self.w, X[i]) + self.b) if margin >= 1: self.w = [wj - self.lr * self.lambda_param * wj for wj in self.w] else: self.w = [wj - self.lr * (self.lambda_param * wj - y[i] * X[i][j]) for j, wj in enumerate(self.w)] self.b -= self.lr * (-y[i]) def predict(self, X): return [1 if dot(self.w, x) + self.b >= 0 else -1 for x in X]
实现线性、多项式、RBF 核。
def linear_kernel(x, z): return dot(x, z) def polynomial_kernel(x, z, degree=3, c=1.0): return (dot(x, z) + c) ** degree def rbf_kernel(x, z, gamma=0.5): diff = [xi - zi for xi, zi in zip(x, z)] return math.exp(-gamma * dot(diff, diff))
训练后,识别哪些点是支持向量,并算间隔宽度。
def find_support_vectors(X, y, w, b, tol=1e-3): support_vectors = [] for i in range(len(X)): margin = y[i] * (dot(w, X[i]) + b) if abs(margin - 1.0) < tol: support_vectors.append(i) return support_vectors
完整实现(含全部演示)见 code/svm.py。
用 scikit-learn:
from sklearn.svm import SVC, LinearSVC, SVR from sklearn.preprocessing import StandardScaler from sklearn.pipeline import Pipeline clf = Pipeline([ ("scaler", StandardScaler()), ("svm", SVC(kernel="rbf", C=1.0, gamma="scale")), ]) clf.fit(X_train, y_train) print(f"Accuracy: {clf.score(X_test, y_test):.4f}") print(f"Support vectors: {clf['svm'].n_support_}")
⚠️ 训练 SVM 前务必缩放特征。SVM 对特征量级敏感,因为间隔依赖 ||w||,未缩放的特征扭曲几何。
对大数据集,用 LinearSVC(原始形式,每 epoch O(n))而非 SVC(对偶形式,O(n²)~O(n³)):
from sklearn.svm import LinearSVC clf = Pipeline([ ("scaler", StandardScaler()), ("svm", LinearSVC(C=1.0, max_iter=10000)), ])
| 维度 | 手写 LinearSVM | scikit-learn |
|---|---|---|
| 形式 | 原始 + 梯度下降 | 原始(LinearSVC)和对偶(SVC,SMO 求解) |
| 核 | 仅线性 | 线性/多项式/RBF/自定义核 |
| 适用 | 理解铰链损失与间隔 | 生产,大数据集用 LinearSVC |
本节的从零实现位于 code/svm.py,含铰链损失、线性 SVM 训练器、三种核函数、支持向量识别和可视化演示。把它当模板,套用到自己的二分类问题上,观察 C、gamma、核选择如何影响决策边界和支持向量数量。
y = sin(x) + 噪声,画出预测周围的 epsilon 管,高亮支持向量(管外的点)。max(0,1-y*f(x)),间隔外为零、间隔内线性;逻辑损失光滑且永不为零,SVM 因稀疏更省内存。K(x,z) 都只需 O(D) 时间。下一节,我们换一种「懒」思路——k 近邻不学习任何模型,预测时直接查最近的邻居,顺便吃透欧氏、曼哈顿、余弦等距离度量的本质。