在机器学习的众多算法中,支持向量机(Support Vector Machine,简称SVM)以其优雅的理论和强大的性能而著称。与逻辑回归、决策树等算法不同,SVM的核心思想非常直观:找到一个最佳的决策边界,使得不同类别的样本尽可能远离这个边界。
想象一下,你需要在一个操场上画一条线,把穿红色球衣的球员和穿蓝色球衣的球员分开。你可以画很多条线,但哪条线最好?SVM的答案是:与双方球员距离最远的那条线。因为这样的边界最稳健,即使有新的球员加入,也更容易正确分类。
这个"距离最远"的思想,就是SVM的精髓所在。
从几何视角看分类问题
在第7章,我们学习了逻辑回归,它通过sigmoid函数将线性组合映射到概率。但逻辑回归关注的是"如何最大化似然",而SVM关注的是"如何最大化边界"。
让我们用一个简单的二维例子来说明:
python
import numpy as np
import matplotlib.pyplot as plt
# 生成两类数据
np.random.seed(42)
X1 = np.random.randn(20, 2) + [2, 2]
X2 = np.random.randn(20, 2) + [-2, -2]
X = np.vstack([X1, X2])
y = np.hstack([np.ones(20), -np.ones(20)])
# 绘制
plt.figure(figsize=(10, 8))
plt.scatter(X1[:, 0], X1[:, 1], c='red', label='类别1', s=100)
plt.scatter(X2[:, 0], X2[:, 1], c='blue', label='类别-1', s=100)
# 绘制决策边界
x_range = np.linspace(-5, 5, 100)
plt.plot(x_range, -x_range, 'k--', linewidth=2, label='决策边界')
plt.plot(x_range, -x_range + 2, 'k:', linewidth=1, label='边界1')
plt.plot(x_range, -x_range - 2, 'k:', linewidth=1, label='边界2')
plt.xlabel('特征1', fontsize=12)
plt.ylabel('特征2', fontsize=12)
plt.title('SVM的最大边界思想', fontsize=14)
plt.legend(fontsize=12)
plt.grid(True, alpha=0.3)
plt.axis('equal')
plt.show()
在这个图中,黑色虚线是决策边界,两条细实线称为"支持向量边界",两条边界之间的距离称为"边界宽度"。SVM的目标就是找到使边界宽度最大的决策边界。
硬间隔SVM
基本原理
对于线性可分的数据,硬间隔SVM的目标是找到一个超平面,使得:
- 正确分类所有样本
- 边界宽度最大
数学上,决策边界可以表示为:
w^T x + b = 0
其中w是法向量(垂直于决策边界),b是偏置项。
分类规则:
y = 1, 如果 w^T x + b ≥ 1
y = -1, 如果 w^T x + b ≤ -1
边界宽度为:
Width = 2 / ||w||
因此,最大化边界等价于最小化 ||w||。
优化问题
硬间隔SVM可以表述为以下优化问题:
最小化:
||w||² / 2
约束条件:
y_i (w^T x_i + b) ≥ 1, 对于所有i
这是一个二次规划问题,有高效的求解算法。
软间隔SVM
现实中的数据往往不是线性可分的。有些样本可能被噪声污染,或者两类样本混杂在一起。在这种情况下,硬间隔SVM无法找到满足所有约束的解。
软间隔SVM通过引入"松弛变量"来允许一些样本被错误分类或位于边界内部。
引入松弛变量
我们为每个样本引入一个非负的松弛变量 ξ_i:
y_i (w^T x_i + b) ≥ 1 - ξ_i
当 ξ_i = 0 时,样本位于正确的一侧且在边界之外。
当 0 < ξ_i < 1 时,样本被正确分类,但位于边界内部。
当 ξ_i > 1 时,样本被错误分类。
新的优化问题
最小化:
||w||² / 2 + C * Σ ξ_i
约束条件:
y_i (w^T x_i + b) ≥ 1 - ξ_i
ξ_i ≥ 0
其中C是一个正则化参数,控制对错误分类的惩罚程度:
- C很大:不允许错误分类,接近硬间隔
- C很小:允许更多错误分类,模型更灵活
核函数
SVM最强大的功能之一是可以通过核函数将数据映射到高维空间,从而解决非线性可分问题。
核技巧
假设原始数据是二维的,我们无法用一条直线分开。但如果我们用某种方式将数据映射到三维空间,可能就能用一个平面将它们分开。
关键是我们不需要显式地计算映射后的数据,只需要计算内积。核函数的作用就是直接计算映射后的内积。
常用核函数
1. 线性核
K(x, x') = x^T x'
适用于线性可分数据,等价于原始空间中的线性SVM。
2. 多项式核
K(x, x') = (x^T x' + c)^d
其中d是多项式的次数,c是常数项。可以捕捉多项式关系。
3. 高斯核(RBF核)
K(x, x') = exp(-γ ||x - x'||²)
其中γ控制核的宽度。RBF核是最常用的核函数,可以拟合任意复杂的决策边界。
4. Sigmoid核
K(x, x') = tanh(α x^T x' + c)
类似于神经网络中的激活函数。
python
# 比较不同核函数
from sklearn.svm import SVC
from sklearn.datasets import make_circles
# 生成圆形数据
X, y = make_circles(n_samples=300, noise=0.1, factor=0.3, random_state=42)
# 训练不同核函数的SVM
kernels = ['linear', 'poly', 'rbf', 'sigmoid']
plt.figure(figsize=(16, 4))
for i, kernel in enumerate(kernels):
svm = SVC(kernel=kernel, random_state=42)
svm.fit(X, y)
# 绘制决策边界
plt.subplot(1, 4, i+1)
plt.scatter(X[:, 0], X[:, 1], c=y, cmap='RdBu', s=50)
plt.title(f'{kernel.capitalize()}核', fontsize=12)
plt.xlabel('特征1')
plt.ylabel('特征2' if i == 0 else '')
plt.tight_layout()
plt.show()
print("核函数选择建议:")
print(" - 线性核: 特征数量很大,样本数量相对较少")
print(" - 多项式核: 特征之间的关系接近多项式")
print(" - RBF核: 通用选择,适用于大多数情况")
print(" - Sigmoid核: 类似神经网络,实际应用较少")
SVM的实现
1. 使用Scikit-learn实现线性SVM
python
from sklearn.svm import SVC
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.preprocessing import StandardScaler
# 生成数据
X, y = make_classification(
n_samples=1000, n_features=20, n_informative=15,
n_redundant=5, n_classes=2, random_state=42
)
# 划分数据集
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# 特征缩放(SVM对特征尺度敏感)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 训练线性SVM
svm_linear = SVC(kernel='linear', random_state=42)
svm_linear.fit(X_train_scaled, y_train)
# 预测
y_pred = svm_linear.predict(X_test_scaled)
# 评估
accuracy = accuracy_score(y_test, y_pred)
print(f"线性SVM准确率: {accuracy:.4f}")
2. 使用RBF核
python
# 训练RBF核SVM
svm_rbf = SVC(kernel='rbf', random_state=42)
svm_rbf.fit(X_train_scaled, y_train)
y_pred_rbf = svm_rbf.predict(X_test_scaled)
accuracy_rbf = accuracy_score(y_test, y_pred_rbf)
print("=== 线性核 vs RBF核 ===")
print(f"线性核准确率: {accuracy:.4f}")
print(f"RBF核准确率: {accuracy_rbf:.4f}")
SVM的关键参数
1. C参数(正则化参数)
C参数控制对错误分类的惩罚程度:
python
# 比较不同C值
C_values = [0.01, 0.1, 1, 10, 100]
print("=== 不同C值对比 ===")
for C in C_values:
svm = SVC(kernel='rbf', C=C, random_state=42)
svm.fit(X_train_scaled, y_train)
train_acc = accuracy_score(y_train, svm.predict(X_train_scaled))
test_acc = accuracy_score(y_test, svm.predict(X_test_scaled))
print(f"C={C:>6.2f} | 训练集准确率: {train_acc:.4f} | 测试集准确率: {test_acc:.4f}")
解读:
- C很小:允许更多错误分类,边界更宽,模型更简单,可能欠拟合
- C很大:不允许错误分类,边界更窄,模型更复杂,可能过拟合
2. gamma参数(RBF核的宽度)
gamma参数控制RBF核的影响范围:
python
# 比较不同gamma值
gamma_values = [0.001, 0.01, 0.1, 1, 10]
print("\n=== 不同gamma值对比 ===")
for gamma in gamma_values:
svm = SVC(kernel='rbf', gamma=gamma, random_state=42)
svm.fit(X_train_scaled, y_train)
train_acc = accuracy_score(y_train, svm.predict(X_train_scaled))
test_acc = accuracy_score(y_test, svm.predict(X_test_scaled))
print(f"gamma={gamma:>6.3f} | 训练集准确率: {train_acc:.4f} | 测试集准确率: {test_acc:.4f}")
解读:
- gamma很小:每个样本的影响范围大,决策边界平滑,可能欠拟合
- gamma很大:每个样本的影响范围小,决策边界复杂,容易过拟合
支持向量
支持向量是位于边界上的样本,它们是唯一影响决策边界的样本。其他样本可以被删除而不影响模型。
python
# 查看支持向量
svm = SVC(kernel='rbf', C=1, random_state=42)
svm.fit(X_train_scaled, y_train)
print("=== 支持向量信息 ===")
print(f"总样本数: {len(X_train)}")
print(f"支持向量数量: {len(svm.support_vectors_)}")
print(f"支持向量比例: {len(svm.support_vectors_) / len(X_train) * 100:.1f}%")
print(f"\n每个类别的支持向量数量:")
for i, count in enumerate(svm.n_support_):
print(f" 类别{i}: {count}")
这个特点使得SVM具有很好的稀疏性,模型只依赖于少量的支持向量。
SVM vs 其他分类算法
| 特性 | SVM | 逻辑回归 | 决策树 | 随机森林 |
|---|---|---|---|---|
| 线性可分问题 | 优秀 | 优秀 | 中等 | 中等 |
| 非线性问题 | 优秀(需核函数) | 中等 | 优秀 | 优秀 |
| 小样本 | 优秀 | 中等 | 中等 | 中等 |
| 高维数据 | 优秀 | 优秀 | 中等 | 中等 |
| 特征缩放 | 需要 | 需要 | 不需要 | 不需要 |
| 解释性 | 中等 | 高 | 高 | 中等 |
| 训练速度 | 慢(大样本) | 快 | 快 | 中等 |
| 预测速度 | 快(支持向量少) | 快 | 快 | 慢 |
| 超参数 | C, kernel, gamma | C | depth等 | n_estimators等 |
SVM的特点:
- 适用于小样本、高维数据
- 通过核函数解决非线性问题
- 决策边界是全局最优的
- 只依赖于支持向量,模型稀疏
SVM的实际应用
1. 文本分类
SVM在文本分类任务中表现出色,如垃圾邮件检测、新闻分类等。文本数据通常是高维稀疏的,SVM能够很好地处理。
2. 图像识别
在手写数字识别(MNIST)等任务中,SVM曾经是主流算法之一。
3. 生物信息学
在基因分类、蛋白质结构预测等任务中,SVM被广泛应用。
4. 金融风控
SVM可以用于信用评分、欺诈检测等金融场景。
SVM回归
SVM不仅可以用于分类,也可以用于回归。SVR(Support Vector Regression)的目标是找到一个函数,使得所有样本与该函数的偏差不超过ε,同时最大化边界。
python
from sklearn.svm import SVR
from sklearn.metrics import mean_squared_error, r2_score
# 生成回归数据
X_reg, y_reg = make_regression(
n_samples=500, n_features=10, n_informative=8,
noise=0.1, random_state=42
)
# 划分数据集
X_train_reg, X_test_reg, y_train_reg, y_test_reg = train_test_split(
X_reg, y_reg, test_size=0.2, random_state=42
)
# 特征缩放
scaler_reg = StandardScaler()
X_train_reg_scaled = scaler_reg.fit_transform(X_train_reg)
X_test_reg_scaled = scaler_reg.transform(X_test_reg)
# 训练SVR
svr = SVR(kernel='rbf', C=1.0)
svr.fit(X_train_reg_scaled, y_train_reg)
# 预测和评估
y_pred_reg = svr.predict(X_test_reg_scaled)
mse = mean_squared_error(y_test_reg, y_pred_reg)
r2 = r2_score(y_test_reg, y_pred_reg)
print("=== SVM回归 ===")
print(f"均方误差: {mse:.4f}")
print(f"R²分数: {r2:.4f}")
实战技巧与最佳实践
1. 特征缩放
SVM对特征尺度非常敏感,必须进行特征缩放:
python
from sklearn.preprocessing import StandardScaler, MinMaxScaler
# 标准化(推荐)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 归一化
scaler = MinMaxScaler()
X_scaled = scaler.fit_transform(X)
2. 核函数选择
核函数选择建议:
- 线性核:特征数量很大(>10000),样本数量相对较少
- RBF核:大多数情况下的首选
- 多项式核:特征之间存在已知的多项式关系
3. 网格搜索调优
使用网格搜索寻找最佳超参数:
python
from sklearn.model_selection import GridSearchCV
param_grid = {
'C': [0.1, 1, 10, 100],
'gamma': ['scale', 'auto', 0.001, 0.01, 0.1],
'kernel': ['rbf', 'linear']
}
grid_search = GridSearchCV(
SVC(random_state=42),
param_grid,
cv=5,
scoring='accuracy',
n_jobs=-1
)
grid_search.fit(X_train_scaled, y_train)
print("=== 最佳参数 ===")
print(grid_search.best_params_)
print(f"最佳准确率: {grid_search.best_score_:.4f}")
best_svm = grid_search.best_estimator_
4. 处理类别不平衡
当数据类别不平衡时,可以使用class_weight参数:
python
# 自动平衡类别权重
svm_balanced = SVC(kernel='rbf', class_weight='balanced', random_state=42)
svm_balanced.fit(X_train_scaled, y_train)
5. 大样本数据处理
对于大样本数据(>10000),SVM的训练时间会很长。可以考虑:
- 使用线性SVM(LinearSVC)
- 使用随机梯度下降版本的SVM(SGDClassifier)
- 降维后使用SVM
python
from sklearn.svm import LinearSVC
# 线性SVM(更快)
linear_svc = LinearSVC(random_state=42)
linear_svc.fit(X_train_scaled, y_train)
本章小结
支持向量机是一种强大的分类算法,其核心思想是找到使边界最大的决策边界。本章我们学习了:
- 硬间隔SVM:适用于线性可分数据,最大化边界宽度
- 软间隔SVM:通过松弛变量允许错误分类,提高泛化能力
- 核函数:通过核技巧将数据映射到高维空间,解决非线性问题
- 常用核函数:线性核、多项式核、RBF核、Sigmoid核
- 关键参数:C(正则化强度)、gamma(RBF核宽度)
- 支持向量:唯一影响决策边界的样本
- SVM回归:SVR用于回归问题
SVM在理论上是优雅的,在实践中是强大的。特别适用于小样本、高维数据的分类任务。虽然在深度学习兴起后,SVM在图像、语音等领域的应用减少了,但在文本分类、生物信息学等领域仍然被广泛使用。
下一章,我们将学习无监督学习的入门算法------K-Means聚类,它不需要标签,能够自动发现数据中的模式和结构。