大数跨境

导师:“会调SVM参数吗?”我:“用Codex跑了网格搜索!”导师:“着重看支持向量,边界稳不稳全靠它!”

导师:“会调SVM参数吗?”我:“用Codex跑了网格搜索!”导师:“着重看支持向量,边界稳不稳全靠它!” 机器学习和人工智能AI
2026-08-15
3

哈喽,大家好~

今儿和大家再来聊聊SVM,让大家彻底看懂~

先来举个例子,想象你要把红色和蓝色的点在平面上分开,用一条线划开两类。

SVM,不仅要分开,还要把“这条线”放在两类之间,尽可能让两边的“最近点”离它远一点。

换句话说,SVM 在找一条(或一个面)能够把两个类别分开的边界,同时把边界两边到最近样本点的距离(margin)最大化。这样做的好处是:在训练噪声不太大的情况下,更能泛化到新样本。

如果两类点不是线性可分的,SVM 有两套“武器”:

  • 软间隔(soft margin):允许个别点违反分隔(有松弛变量),加个惩罚系数   控制违例的代价;
  • 核技巧(kernel):把数据隐式映射到高维空间,在高维空间里可能线性可分,然后再转回原空间得到非线性边界。

另外,经典 SVM 最终会选出一小部分关键样本(支持向量),这些点决定了边界的位置。概率上,这通常意味着模型相对稀疏、鲁棒。

关键数学

设训练样本 

线性 SVM 的软间隔原始形式是:

这等价于最小化带正则化的 hinge 损失(对每个样本):

整体目标(经验风险 + 正则化):

核方法的核心是通过核函数   计算内积,省去显式映射。常见核:

  • 线性核:
  • 多项式:
  • RBF(高斯):

今儿内容,为了仍用 PyTorch 的线性模块并支持核化,我们将使用 Random Fourier Features(RFF)去近似 RBF 核,把非线性问题转成更高维的线性问题来学习,这样仍然能用类似线性模型的训练流程。

完整案例

我们使用 sklearn 生成数据集,线性可分、同心圆、月牙、以及复杂重叠类~

用 PyTorch 实现两类模型:

  1. 线性 SVM(原始形式,用 hinge 损失 + L2)
  2. 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(02*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.00.01.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(42, figsize=(1220))
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 核的方式。

【声明】内容源于网络
0
0
机器学习和人工智能AI
让我们一起期待 AI 带给我们的每一场变革!推送最新行业内最新最前沿人工智能技术!
内容 381
粉丝 0
机器学习和人工智能AI 让我们一起期待 AI 带给我们的每一场变革!推送最新行业内最新最前沿人工智能技术!
总阅读5.0k
粉丝0
内容381