Skip to content

支持向量机 (SVM)

支持向量机(Support Vector Machines, SVMs)是一种强大而多用途的监督学习算法,可用于分类、回归和异常检测(outlier detection)。然而,它们最广为人知和最常用于分类任务。

SVMs 在 20 世纪 90 年代开发出来,在高维空间以及维度数量超过样本数量的情况下尤其有效。

SVM 用于分类的核心思想是在特征空间中找到最优的超平面(hyperplane),该超平面能最好地分隔属于不同类别的数据点。

关键概念:

  • 超平面(Hyperplane): 分隔类别的决策边界。在二维特征空间中,它是一条线。在三维空间中,它是一个平面。在更高维度中,它被称为超平面。
  • 支持向量(Support Vectors): 距离超平面最近的、来自每个类别的数据点。这些点至关重要,因为它们“支持”或定义了超平面的位置和方向。如果这些点移动,超平面很可能会改变。
  • 间隔(Margin): 超平面与来自任一类别的最近支持向量之间的距离。SVM 旨在找到使这个间隔最大化的超平面。

通过最大化间隔,SVM 在类别之间创建了最大的可能分离(“街道”)。人们认为这个最大间隔超平面可以在未见过的数据上获得更好的泛化性能(generalization performance),因为它为误差提供了最大的“空间”。

从概念上讲,想象用尺子分隔纸上的两组点。SVM 试图将尺子准确地放置在每组最近点之间的间隙的中间,从而最大化尺子与这些关键点(支持向量)的距离。

软间隔分类(Soft Margin Classification)

Section titled “软间隔分类(Soft Margin Classification)”

在实际数据中,完美的分离通常是不可能的(数据重叠)或不可取的(对异常值敏感)。SVM 使用**软间隔分类(soft margin classification)**来处理这种情况。它允许一些数据点位于间隔内甚至超平面的错误一侧(间隔违规 margin violations),但会对此进行惩罚。

超参数 C 控制这种权衡:

  • 小的 C 值: 间隔更宽,允许更多间隔违规(对错误分类的容忍度更高,正则化更强,可能导致欠拟合 underfitting)。
  • 大的 C 值: 间隔更窄,试图最小化间隔违规(容忍度更低,正则化更弱,可能导致过拟合 overfitting)。

处理非线性数据的核技巧(Kernel Trick)

Section titled “处理非线性数据的核技巧(Kernel Trick)”

如果数据在原始特征空间中不是线性可分的怎么办?SVM 可以使用**核技巧(kernel trick)**来处理这个问题。

核函数(Kernels)是一种函数,它们可以在一个可能更高维度的空间中计算数据点之间的相似性,而无需显式地将数据转换到那个空间。这避免了显式转换带来的计算成本。

通过使用合适的核函数,SVM 可以在转换后的高维空间中找到一个线性超平面,这在原始特征空间中对应着一个非线性决策边界。

  • 线性核(Linear Kernel,kernel='linear'): 计算简单的点积。相当于在原始空间中找到一个线性超平面。在数据可能是线性可分时使用。
  • 多项式核(Polynomial Kernel,kernel='poly'): 将数据映射到多项式特征空间。需要调优 degree(阶数)超参数(以及 coef0)。
  • 径向基函数核(Radial Basis Function (RBF) Kernel,kernel='rbf'): 一个流行的默认选择。将数据映射到无限维空间。可以处理复杂的非线性关系。需要调优 gamma 超参数,它控制单个数据点的影响范围(低 gamma = 影响范围广,高 gamma = 影响范围局部)。
  • Sigmoid 核(Sigmoid Kernel,kernel='sigmoid'): 类似于神经网络中的激活函数。常用于特定领域。

核函数及其超参数(如 C、gamma、degree)的选择至关重要,通常需要使用网格搜索(Grid Search)或随机搜索(Randomized Search)等技术进行调优。

在 Python 中实现 SVM(Scikit-learn)

Section titled “在 Python 中实现 SVM(Scikit-learn)”

我们将使用 Scikit-learn 的 SVC(Support Vector Classifier)类。

首先,让我们在简单的、线性可分的人造数据上可视化 SVM。

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import make_blobs
from sklearn.svm import SVC # Support Vector Classifier
# 1. Create linearly separable data
X, y = make_blobs(n_samples=100, centers=2, random_state=0, cluster_std=0.60)
# 2. Create and fit the SVM model (Linear Kernel)
# Large C value enforces a stricter separation (harder margin)
model_linear = SVC(kernel='linear', C=1E10)
model_linear.fit(X, y)
# 3. Plotting function (Helper)
def plot_svc_decision_boundary(model, ax=None, plot_support=True):
"""Plot the decision function for a 2D SVC"""
if ax is None:
ax = plt.gca()
xlim = ax.get_xlim()
ylim = ax.get_ylim()
# Create grid to evaluate model
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 = model.decision_function(xy).reshape(XX.shape)
# Plot decision boundary and margins
ax.contour(XX, YY, Z, colors='k', levels=[-1, 0, 1], alpha=0.5,
linestyles=['--', '-', '--'])
# Plot support vectors
if plot_support:
ax.scatter(model.support_vectors_[:, 0], model.support_vectors_[:, 1],
s=100, linewidth=1, facecolors='none', edgecolors='k', label='Support Vectors')
ax.set_xlim(xlim)
ax.set_ylim(ylim)
# 4. Visualize the result
plt.figure(figsize=(8, 6))
sns.scatterplot(x=X[:, 0], y=X[:, 1], hue=y, s=50, palette='viridis', legend=False)
plot_svc_decision_boundary(model_linear)
plt.title('Linear SVM Decision Boundary and Margins')
plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
plt.legend()
plt.grid(True)
plt.show()
# Print support vectors
print("Support Vectors:")
print(model_linear.support_vectors_)

(该图显示了按类别着色的数据点,实线黑色线条是最大间隔超平面,虚线代表间隔,圆圈标记的点是支持向量。)

Support Vectors:
[[0.5323772 3.31338909]
[2.11114739 3.57660449]
[1.46870582 1.86947425]]

示例 2:非线性可分数据(使用 RBF 核的 Iris 数据集)

Section titled “示例 2:非线性可分数据(使用 RBF 核的 Iris 数据集)”

现在让我们在 Iris 数据集上使用 RBF 核(使用两个特征进行可视化)。

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn import datasets
from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# 1. Load Iris dataset (using only Sepal features for this example)
iris = datasets.load_iris()
X = iris.data[:, :2] # Sepal length, Sepal width
y = iris.target
feature_names = iris.feature_names[:2]
# 2. Split and Scale Data (Important for SVM!)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 3. Create and Fit SVM with RBF Kernel
# C and gamma are important hyperparameters to tune
model_rbf = SVC(kernel='rbf', C=1.0, gamma='scale', probability=True) # gamma='scale' is often a good default
model_rbf.fit(X_train_scaled, y_train)
# 4. Predict and Evaluate
y_pred = model_rbf.predict(X_test_scaled)
accuracy = accuracy_score(y_test, y_pred)
print(f"Accuracy (RBF Kernel): {accuracy:.4f}")
# 5. Visualize Decision Boundary (Helper function needed)
def plot_decision_regions(X, y, classifier, resolution=0.02, test_idx=None):
from matplotlib.colors import ListedColormap
markers = ('s', 'x', 'o', '^', 'v')
colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan')
cmap = ListedColormap(colors[:len(np.unique(y))])
x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1
x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution),
np.arange(x2_min, x2_max, resolution))
Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
Z = Z.reshape(xx1.shape)
plt.contourf(xx1, xx2, Z, alpha=0.3, cmap=cmap)
plt.xlim(xx1.min(), xx1.max())
plt.ylim(xx2.min(), xx2.max())
for idx, cl in enumerate(np.unique(y)):
plt.scatter(x=X[y == cl, 0], y=X[y == cl, 1],
alpha=0.8, c=colors[idx],
marker=markers[idx], label=f'Class {cl}', edgecolor='black')
# Highlight test samples
if test_idx:
X_test, y_test = X[test_idx, :], y[test_idx]
plt.scatter(X_test[:, 0], X_test[:, 1],
c='', edgecolor='black', alpha=1.0,
linewidth=1, marker='o', s=100, label='Test set')
# Combine scaled training and testing data for plotting boundaries across the whole range
X_combined_scaled = np.vstack((X_train_scaled, X_test_scaled))
y_combined = np.hstack((y_train, y_test))
plt.figure(figsize=(10, 7))
plot_decision_regions(X=X_combined_scaled, y=y_combined, classifier=model_rbf)
plt.title('SVM with RBF Kernel Decision Boundary (Iris Sepal Features)')
plt.xlabel(f'{feature_names[0]} (Scaled)')
plt.ylabel(f'{feature_names[1]} (Scaled)')
plt.legend(loc='upper left')
plt.grid(True)
plt.show()
Accuracy (RBF Kernel): 0.8222

准确率显示了在测试集上使用 RBF 核的性能。该图可视化了 RBF 核 SVM 为基于缩放后的萼片特征(scaled sepal features)分隔三个 Iris 类别而创建的非线性决策边界。

  • 在高维空间中有效: 即使特征数量很大,也能很好地工作。
  • 内存效率高: 在决策函数中只使用训练点的一个子集(支持向量)。
  • 多用途: 可以为决策函数指定不同的核函数。
  • 在类别有明确间隔时有效: 当类别之间存在明确间隔时表现良好。
  • 计算密集: 在非常大的数据集上训练可能很慢(训练时间复杂度在 O(N²) 到 O(N³) 之间)。
  • 对超参数敏感: 性能高度依赖于核函数和超参数(C、gamma)的选择。需要仔细调优。
  • 处理重叠类别时性能不佳: 当类别严重重叠时,性能表现不佳。
  • 非直接概率输出: 基本的 SVM 不直接提供概率估计(尽管 Scikit-learn 的实现可以通过 Platt 定标 Platt scaling 来估计,需要设置 probability=True)。
  • 需要缩放: SVM 对特征缩放敏感;训练前通常需要对特征进行缩放(例如,使用 StandardScaler)。
  • 图像分类: 识别图像中的对象。
  • 文本分类: 将文档分类到主题。
  • 生物信息学: 蛋白质分类、癌症分类。
  • 手写识别: 识别手写字符。
  • 人脸检测: 检测图像中的人脸。