Machine Learning: SVM支持向量机 — 最大间隔与核技巧分类指南
SVM找的是"最宽的马路"——让分类边界离两边数据都尽可能远,自然更稳健。
1. 你将学到
- 最大间隔分类器:支持向量、间隔边界、硬间隔与软间隔(C参数)
- 核技巧:线性核/RBF核/多项式核的直觉与选择策略
- SVM回归(SVR):ε-不敏感损失函数
- 标准化必要性:SVM对特征尺度的敏感性
- Charlie的用户分层:用SVM对欧洲市场的客户价值等级分类
2. 一个市场分析师的真实故事
(1) 痛点:客户价值分层边界模糊
Charlie负责欧洲市场的客户分层,需要将客户分为High/Medium/Low三个价值等级来分配服务资源。但High和Medium的边界很难划——用单一指标(如消费额)分层,边界附近20%的客户频繁被误分。线性边界无法捕捉客户多维度特征的复杂关系。
(2) SVM核技巧的解法
SVM通过核技巧将数据映射到高维空间,在原来不可分的数据中找到清晰边界。
PYTHON
from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
# RBF kernel maps to high-dimensional space
pipe = Pipeline([
("scaler", StandardScaler()),
("svm", SVC(kernel="rbf", C=1.0, gamma="scale")),
])
pipe.fit(X_train, y_train)
print(f"Accuracy: {pipe.score(X_test, y_test):.4f}")
(3) 收益:边界客户分类准确率从65%提升到89%
Charlie用SVM-RBF替代线性分层后,边界附近客户的分类准确率从65%提升到89%,服务资源错配率降低40%。
3. 最大间隔分类器
(1) SVM核心思想
SVM寻找使间隔(margin)最大的分类超平面,只有落在间隔边界上的样本(支持向量)决定分类结果。
graph TB
INPUT[Input Data] --> MAP[Kernel Mapping<br/>Low-D → High-D]
MAP --> HYPER[Find Max-Margin<br/>Hyperplane]
HYPER --> SV[Support Vectors<br/>Define the boundary]
SV --> MARGIN[Margin Width<br/>Larger = More Robust]
MARGIN --> CLASSIFY[Classification<br/>Decision Function]
▶ 示例:线性SVM可视化
PYTHON
from sklearn.svm import SVC
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
import numpy as np
X, y = make_blobs(n_samples=100, centers=2, random_state=42, cluster_std=1.5)
# Train SVM with linear kernel
svm = SVC(kernel="linear", C=1.0)
svm.fit(X, y)
fig, ax = plt.subplots(figsize=(8, 6))
# Plot data points
ax.scatter(X[:, 0], X[:, 1], c=y, cmap="bwr", s=30, edgecolors="black")
# Plot decision boundary and margins
xlim = ax.get_xlim()
ylim = ax.get_ylim()
xx = np.linspace(xlim[0], xlim[1], 30)
yy = np.linspace(ylim[0], ylim[1], 30)
YY, XX = np.meshgrid(yy, xx)
xy = np.vstack([XX.ravel(), YY.ravel()]).T
Z = svm.decision_function(xy).reshape(XX.shape)
ax.contour(XX, YY, Z, colors="k", levels=[-1, 0, 1], alpha=0.5, linestyles=["--", "-", "--"])
# Highlight support vectors
ax.scatter(svm.support_vectors_[:, 0], svm.support_vectors_[:, 1],
s=100, facecolors="none", edgecolors="k", linewidths=2)
ax.set_title(f"Linear SVM (C={svm.C})\nSupport vectors: {len(svm.support_vectors_)}")
plt.tight_layout()
plt.savefig("svm_linear.png", dpi=150)
输出:
TEXT
📖 仅展示
# 执行成功
(2) 硬间隔 vs 软间隔(C参数)
| C值 | 间隔 | 误分类容忍 | 过拟合风险 | 适用场景 |
|---|---|---|---|---|
| 很大(100+) | 窄 | 几乎不容忍 | 高 | 数据几乎线性可分 |
| 中等(1) | 适中 | 少量容忍 | 低 | 一般情况 |
| 很小(0.01) | 宽 | 高容忍 | 低 | 有噪声/重叠 |
▶ 示例:C参数影响对比
PYTHON
from sklearn.svm import SVC
from sklearn.datasets import make_blobs
from sklearn.model_selection import cross_val_score
import numpy as np
X, y = make_blobs(n_samples=200, centers=2, random_state=42, cluster_std=2.5)
for C in [0.01, 0.1, 1.0, 10.0, 100.0]:
svm = SVC(kernel="rbf", C=C, gamma="scale")
scores = cross_val_score(svm, X, y, cv=5, scoring="accuracy")
svm.fit(X, y)
print(f"C={C:6.2f}: Accuracy={scores.mean():.3f} +/- {scores.std():.3f}, "
f"Support vectors={len(svm.support_vectors_)}")
输出:
TEXT
📖 仅展示
# 执行成功
4. 核技巧
(1) 核函数选择
▶ 示例:不同核函数对比
PYTHON
from sklearn.svm import SVC
from sklearn.datasets import make_moons, make_circles
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
import numpy as np
# Non-linear data (moons)
X_moons, y_moons = make_moons(n_samples=500, noise=0.2, random_state=42)
kernels = {
"Linear": SVC(kernel="linear", C=1),
"Polynomial (deg=3)": SVC(kernel="poly", degree=3, C=1),
"RBF": SVC(kernel="rbf", C=1, gamma="scale"),
}
for name, svm in kernels.items():
pipe = Pipeline([("scaler", StandardScaler()), ("svm", svm)])
scores = cross_val_score(pipe, X_moons, y_moons, cv=5, scoring="accuracy")
print(f"{name:20s}: Accuracy={scores.mean():.3f}")
输出:
TEXT
📖 仅展示
# 执行成功
| 核函数 | 公式 | 适用场景 | 关键参数 |
|---|---|---|---|
| Linear | $\langle x, x' \rangle$ | 高维、线性可分 | C |
| RBF | $e^{-\gamma|x-x'|^2}$ | 通用,非线性 | C, gamma |
| Polynomial | $(\langle x, x' \rangle + c)^d$ | 特征交互 | C, degree, coef0 |
| Sigmoid | $\tanh(\gamma\langle x, x' \rangle + c)$ | 类神经网络 | C, gamma, coef0 |
(2) gamma参数
▶ 示例:gamma参数对RBF核的影响
PYTHON
from sklearn.svm import SVC
from sklearn.datasets import make_moons
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
X, y = make_moons(n_samples=500, noise=0.15, random_state=42)
for gamma in [0.01, 0.1, 1.0, 10.0, "scale", "auto"]:
pipe = Pipeline([
("scaler", StandardScaler()),
("svm", SVC(kernel="rbf", C=1, gamma=gamma)),
])
scores = cross_val_score(pipe, X, y, cv=5, scoring="accuracy")
print(f"gamma={str(gamma):6s}: Accuracy={scores.mean():.3f}")
输出:
TEXT
📖 仅展示
# 执行成功
| gamma | 决策边界 | 效果 |
|---|---|---|
| 很小(0.01) | 平滑 | 欠拟合 |
| 适中(1) | 适度弯曲 | 通常最佳 |
| 很大(10+) | 极不规则 | 过拟合 |
| "scale" | 1/(n_features * X.var()) | sklearn默认 |
| "auto" | 1/n_features | 旧版默认 |
5. 标准化必要性与SVR
(1) SVM对特征尺度的敏感性
▶ 示例:标准化对SVM的影响
PYTHON
from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_iris
X, y = load_iris(return_X_y=True)
# Without scaling
svm_raw = SVC(kernel="rbf", C=1, gamma="scale")
scores_raw = cross_val_score(svm_raw, X, y, cv=5, scoring="accuracy")
# With scaling
pipe = Pipeline([("scaler", StandardScaler()), ("svm", SVC(kernel="rbf", C=1, gamma="scale"))])
scores_scaled = cross_val_score(pipe, X, y, cv=5, scoring="accuracy")
print(f"Without scaling: {scores_raw.mean():.4f}")
print(f"With scaling: {scores_scaled.mean():.4f}")
输出:
TEXT
📖 仅展示
# 执行成功
⚠️ 注意: SVM使用距离度量计算间隔,如果特征尺度差异大(如年龄0-100 vs 收入0-1000000),大尺度特征会主导间隔计算。SVM必须配合StandardScaler。
(2) SVR回归
▶ 示例:SVR销售预测
PYTHON
from sklearn.svm import SVR
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_absolute_error, r2_score
import numpy as np
rng = np.random.default_rng(42)
X = rng.uniform(0, 100, (200, 3))
y = 50 + 0.8 * X[:, 0] + rng.normal(0, 5, 200)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# SVR with RBF kernel
pipe = Pipeline([
("scaler", StandardScaler()),
("svr", SVR(kernel="rbf", C=100, epsilon=5)),
])
pipe.fit(X_train, y_train)
y_pred = pipe.predict(X_test)
print(f"SVR R²: {r2_score(y_test, y_pred):.4f}")
print(f"SVR MAE: {mean_absolute_error(y_test, y_pred):.2f}")
输出:
TEXT
📖 仅展示
# 执行成功
| 参数 | 含义 | 效果 |
|---|---|---|
| C | 正则化强度 | 大C→少容忍误差→可能过拟合 |
| epsilon (ε) | 不敏感带宽度 | 大ε→更多点被忽略→更平滑 |
| kernel | 核函数类型 | 同分类SVM |
6. Charlie的欧洲客户价值分层
▶ 示例:完整SVM分类项目
PYTHON
from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.metrics import classification_report
import pandas as pd
import numpy as np
rng = np.random.default_rng(42)
n = 2000
df = pd.DataFrame({
"annual_spending_eur": rng.exponential(5000, n),
"purchase_frequency": rng.poisson(8, n),
"avg_order_value_eur": rng.exponential(150, n),
"tenure_months": rng.integers(1, 60, n),
"support_tickets": rng.poisson(3, n),
})
# Value tier logic (EUR-based)
score = (df["annual_spending_eur"] / 5000 * 0.3
+ df["purchase_frequency"] / 20 * 0.25
+ df["avg_order_value_eur"] / 200 * 0.2
+ df["tenure_months"] / 60 * 0.15
+ rng.normal(0, 0.1, n))
df["tier"] = pd.cut(score, bins=[0, 0.3, 0.6, 1.5], labels=["Low", "Medium", "High"])
X = df.drop(columns=["tier"])
y = df["tier"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)
# Grid search for best SVM parameters
pipe = Pipeline([("scaler", StandardScaler()), ("svm", SVC())])
param_grid = {
"svm__kernel": ["linear", "rbf"],
"svm__C": [0.1, 1, 10],
"svm__gamma": ["scale", 0.1, 1],
}
grid = GridSearchCV(pipe, param_grid, cv=3, scoring="accuracy", n_jobs=-1)
grid.fit(X_train, y_train)
print(f"Best params: {grid.best_params_}")
print(f"Best CV accuracy: {grid.best_score_:.4f}")
print(f"\nTest report:\n{classification_report(y_test, grid.predict(X_test))}")
输出:
TEXT
📖 仅展示
# 执行成功
❓ 常见问题
Q SVM为什么必须标准化?
A SVM基于距离度量计算间隔。如果特征A范围0-1,特征B范围0-1000000,B会完全主导间隔计算。StandardScaler让所有特征贡献均衡。
Q RBF核的gamma和C哪个更重要?
A 都重要,且相互影响。建议用GridSearchCV同时搜索。典型搜索范围:C=[0.1,1,10,100],gamma=[0.001,0.01,0.1,1,"scale"]。
Q SVM适合大数据集吗?
A 不太适合。SVM训练复杂度O(n²)到O(n³),超过100 thousand样本会非常慢。大数据集用LinearSVC(线性核)或随机森林/XGBoost。
Q SVM能输出概率吗?
A 默认不能。设置probability=True会额外训练一个Platt Scaling模型来输出概率,但训练变慢且概率不如逻辑回归校准。
Q 线性核和逻辑回归有什么区别?
A 两者都是线性分类器。区别——SVM最大化间隔(只关注边界样本),逻辑回归最大化似然(关注所有样本)。SVM在边界附近更稳健,逻辑回归概率更准。
Q SVR的epsilon参数怎么选?
A epsilon是不敏感带宽度,小于ε的误差不计入损失。典型值取目标变量的噪声水平估计。如销售预测误差在±5k USD,设ε=5。
📖 小节
- SVM寻找最大间隔超平面,只有支持向量决定边界,其余样本不影响
- C参数控制间隔与误分类的权衡:大C→窄间隔少误分,小C→宽间隔多容忍
- 核技巧通过映射到高维空间解决非线性分类,RBF核是默认首选
- gamma控制RBF核的影响范围:小gamma→平滑边界,大gamma→过拟合
- SVM必须标准化——距离度量对特征尺度极其敏感
- SVR用ε-不敏感损失做回归,小于ε的预测误差不计入损失
📝 作业
- 基础题(难度⭐):用SVC(kernel="rbf")对make_moons数据集分类,对比有/无StandardScaler的accuracy。提示:Pipeline vs 直接fit。
- 进阶题(难度⭐⭐):用GridSearchCV搜索SVM最优参数(kernel, C, gamma),在Iris数据集上找到最佳组合。提示:Pipeline内嵌SVC + param_grid。
- 挑战题(难度⭐⭐⭐):实现Charlie的客户分层——生成3类客户数据,对比SVM/LogisticRegression/RandomForest的分类报告,分析哪种模型最适合边界模糊的多分类场景。提示:用classification_report对比precision/recall/f1。