哈喽,大家好~
今儿和大家再来聊聊SVM,让大家彻底看懂~
先来举个例子,想象你要把红色和蓝色的点在平面上分开,用一条线划开两类。
SVM,不仅要分开,还要把“这条线”放在两类之间,尽可能让两边的“最近点”离它远一点。
换句话说,SVM 在找一条(或一个面)能够把两个类别分开的边界,同时把边界两边到最近样本点的距离(margin)最大化。这样做的好处是:在训练噪声不太大的情况下,更能泛化到新样本。
如果两类点不是线性可分的,SVM 有两套“武器”:
-
软间隔(soft margin):允许个别点违反分隔(有松弛变量),加个惩罚系数 控制违例的代价; -
核技巧(kernel):把数据隐式映射到高维空间,在高维空间里可能线性可分,然后再转回原空间得到非线性边界。
另外,经典 SVM 最终会选出一小部分关键样本(支持向量),这些点决定了边界的位置。概率上,这通常意味着模型相对稀疏、鲁棒。
关键数学
设训练样本 , 。
线性 SVM 的软间隔原始形式是:
这等价于最小化带正则化的 hinge 损失(对每个样本):
整体目标(经验风险 + 正则化):
核方法的核心是通过核函数 计算内积,省去显式映射。常见核:
-
线性核: -
多项式: -
RBF(高斯):
今儿内容,为了仍用 PyTorch 的线性模块并支持核化,我们将使用 Random Fourier Features(RFF)去近似 RBF 核,把非线性问题转成更高维的线性问题来学习,这样仍然能用类似线性模型的训练流程。
完整案例
我们使用 sklearn 生成数据集,线性可分、同心圆、月牙、以及复杂重叠类~
用 PyTorch 实现两类模型:
-
线性 SVM(原始形式,用 hinge 损失 + L2) -
RFF-SVM(先做 RFF 特征映射,再用线性 SVM 学习),近似 RBF 核
训练后在平面上用网格计算模型输出并画出决策边界、margin 区域、支持向量(近似:其 hinge 是非零或 |y f(x)| ≤ 1 + eps)等。
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from sklearn import datasets
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
torch.manual_seed(0)
np.random.seed(0)
# 工具函数:训练 SVM(线性或在特征空间)
def train_svm_primal(X, y, lr=0.1, n_epochs=200, C=1.0, device='cpu'):
# X: (n_samples, d) numpy
# y: (n_samples,) in {-1,1}
X_t = torch.tensor(X, dtype=torch.float32, device=device)
y_t = torch.tensor(y, dtype=torch.float32, device=device).view(-1,1)
n, d = X.shape
# 模型参数 w, b
w = torch.zeros((d,1), dtype=torch.float32, device=device, requires_grad=True)
b = torch.zeros(1, dtype=torch.float32, device=device, requires_grad=True)
opt = optim.SGD([w,b], lr=lr, weight_decay=0.0)
for epoch in range(n_epochs):
opt.zero_grad()
logits = X_t @ w + b # (n,1)
# hinge loss: max(0, 1 - y * logits)
hinge = torch.clamp(1 - y_t * logits, min=0)
loss = 0.5 * torch.sum(w**2) + C * torch.sum(hinge)
loss.backward()
opt.step()
return w.detach().cpu().numpy(), b.detach().cpu().numpy()
# Random Fourier Features (RFF) for RBF kernel
class RFF:
def __init__(self, D=256, sigma=1.0, input_dim=2):
self.D = D
self.sigma = sigma
self.W = np.random.normal(loc=0.0, scale=1.0/sigma, size=(input_dim, D))
self.b = np.random.uniform(0, 2*np.pi, size=(D,))
self.scale = np.sqrt(2.0 / D)
def transform(self, X):
# X: (n, d)
proj = X @ self.W + self.b # (n, D)
return self.scale * np.cos(proj)
# 可视化:绘制决策边界与数据
def plot_decision(X, y, model_func, ax, title, cmap='plasma'):
# model_func: function taking (n_grid,2) -> scores (n_grid,)
x_min, x_max = X[:,0].min()-1, X[:,0].max()+1
y_min, y_max = X[:,1].min()-1, X[:,1].max()+1
xx, yy = np.meshgrid(np.linspace(x_min,x_max,300), np.linspace(y_min,y_max,300))
grid = np.c_[xx.ravel(), yy.ravel()]
scores = model_func(grid).reshape(xx.shape)
# 热力填充
cf = ax.contourf(xx, yy, scores, levels=50, cmap=cmap, alpha=0.8)
# 决策边界(score=0)与 margin(±1)
ax.contour(xx, yy, scores, levels=[-1.0, 0.0, 1.0], colors=['#2b9aa3', '#111111', '#e4007c'],
linestyles=['--','-','--'], linewidths=[1,2,1])
# 散点
colors = np.array(['#ff5c5c' if yy_i==1 else '#5c9cff' for yy_i in y])
ax.scatter(X[:,0], X[:,1], c=colors, s=40, edgecolors='k', linewidths=0.5)
ax.set_title(title, fontsize=12)
return cf
# 支持向量识别(近似):hinge > 1e-3 or |y*score| <= 1+eps
def find_support_vectors(X, y, score_func, eps=1e-2):
# score_func: X -> scores
scores = score_func(X)
margins = y * scores
sv_mask = (margins <= 1.0 + eps)
return sv_mask
# 主流程:生成数据并训练两个模型(线性 / RFF)
datasets_list = []
# 1. 线性可分(带噪声)
X1, y1 = datasets.make_classification(n_samples=1000, n_features=2, n_redundant=0,
n_clusters_per_class=1, class_sep=2.0, random_state=1)
y1 = 2*y1 - 1
datasets_list.append(('Linear-separable', X1, y1))
# 2. 同心圆(非线性)
X2, y2 = datasets.make_circles(n_samples=300, factor=0.4, noise=0.08, random_state=2)
y2 = 2*y2 - 1
datasets_list.append(('Circles', X2, y2))
# 3. Moons(非线性,较常见)
X3, y3 = datasets.make_moons(n_samples=300, noise=0.08, random_state=3)
y3 = 2*y3 - 1
datasets_list.append(('Moons', X3, y3))
# 4. 复杂重叠簇(cluster overlap)
X4, y4 = datasets.make_blobs(n_samples=300, centers=[(-2,-2),(2,2),(0,3)], cluster_std=[0.8,0.8,1.2], random_state=4)
# Convert to binary by grouping some centers
y4 = (y4!=2).astype(int)
y4 = 2*y4 - 1
datasets_list.append(('Overlapping-clusters', X4, y4))
# 训练并绘图
fig, axes = plt.subplots(4, 2, figsize=(12, 20))
for i, (name, X, y) in enumerate(datasets_list):
# 线性 SVM
w, b = train_svm_primal(X, y, lr=0.05, n_epochs=1000, C=1.0)
def linear_score(grid):
return grid @ w + b
# 找支持向量(近似)
sv_mask_lin = find_support_vectors(X, y, lambda Xp: (Xp @ w + b).flatten())
# RFF-SVM
rff = RFF(D=512, sigma=1.0, input_dim=2)
Z = rff.transform(X)
w_rff, b_rff = train_svm_primal(Z, y, lr=0.1, n_epochs=800, C=1.0)
def rff_score(grid):
return (rff.transform(grid) @ w_rff + b_rff).flatten()
sv_mask_rff = find_support_vectors(X, y, lambda Xp: rff_score(Xp))
# Plot linear
ax1 = axes[i,0]
plot_decision(X, y, linear_score, ax1, f"{name} - Linear SVM", cmap='viridis')
# mark support vectors
ax1.scatter(X[sv_mask_lin,0], X[sv_mask_lin,1], s=120, facecolors='none', edgecolors='#ffffff', linewidths=1.5)
# Plot RFF
ax2 = axes[i,1]
plot_decision(X, y, rff_score, ax2, f"{name} - RFF (RBF approx) SVM", cmap='plasma')
ax2.scatter(X[sv_mask_rff,0], X[sv_mask_rff,1], s=120, facecolors='none', edgecolors='#ffffff', linewidths=1.5)
plt.tight_layout()
plt.show()
核心步骤
数据生成:
-
make_classification:生成线性可分(调整 class_sep 控制间隔) -
make_circles:同心圆,典型线性方法失败的例子 -
make_moons:月牙形,两半月,很适合展示非线性边界 -
make_blobs:多簇,做成二分类并制造重叠
train_svm_primal:在原始或映射后的特征空间中,以原始 SVM 的目标用 SGD(或 Adam)优化:
-
参数是 w,b,损失 = 0.5 * ||w||^2 + C * sum hinge -
训练若干 epoch 即可。注意:这是原始(primal)优化的近似解,与传统二次规划求解的严格解略有差别,但实践中对可视化/学习规律已经足够。
RFF(Random Fourier Features):
-
用来把输入 x 映射为高维特征 z(x),使得 z(x)·z(x') ≈ k(x,x'),这里近似 RBF。 -
这样就可以在映射后的线性模型上训练,达到“核化”的效果,但仍在 PyTorch 中直接训练线性权重。
plot_decision:
-
在二维网格上计算模型分数(f(x)),画出热力图、决策边界(f=0)和 margin(f=±1),并将训练点绘出。 -
支持向量:在原始 SVM 中,数学定义是那些对应非零拉格朗日乘子 α_i 的点。这里我们用近似办法:margin ≤ 1 + eps 的点视为支持向量并高亮(用空心圈标出)。
可视化分析
线性可分数据上的 Linear SVM:
决策边界一条清晰直线,两侧 margin 对称;支持向量在靠近边界的点上(用空心白圈表示)。
SVM 最大化 margin,使得决策边界对于边界附近的小扰动更鲁棒。线性 SVM 在这类数据上表现很好,RFF/SVM 本质上也能学出类似结果。
同心圆数据上的 Linear SVM:
线性 SVM 无法将内外圆分开,决策面是一条直线,分类准确率低,很多点被误分类。
说明了“线性假设”的局限性,真实数据常常是非线性的,此时需要核方法或非线性映射。
同心圆数据上的 RFF-SVM:
RFF 映射后训练的线性分类器能在原空间划出环状边界,把内外圆分开。margin 轮廓围绕内圈/外圈,支持向量分布在环附近。
RFF 实现了对 RBF 核的近似,使得原本线性不可分的数据变为线性可分(在特征映射空间)。这是核机器学习的实用化表现:非线性边界能被捕获,同时仍保持基于线性模型的训练流程。
Moons 数据上的决策分数热力图:
-
观察:绘制决策函数 f(x) 的值为热力图,显示正/负区域的连续过渡。可以看到 margin(f=±1)带,以及在两个类接触区域决策边界的曲折。 -
含义:决策函数的连续值可以作为置信度指标:|f(x)| 越大表示越“确信”预测;靠近决策边界(|f| small)表示不确定。用于后续阈值、置信度筛选、主动学习等场景。
额外观察:
在 Overlapping-clusters(重叠簇)上,RFF-SVM 的边界会更复杂,margin 受噪声影响较大。可以通过调节 C(惩罚系数)来控制“对错误的容忍”:
-
大 C:更不容忍错误,margin 可能更窄并追求训练准确率; -
小 C:容忍更多错误,margin 更宽,泛化可能更好。
总结
SVM 的精髓是“最大化 margin”,从几何角度解释其泛化能力。
当数据线性不可分时,核方法(或特征映射)把问题变成线性问题来解决。RFF 提供了在深度学习框架下高效近似 RBF 核的方式。

