第10章:支持向量机:找到最佳边界

在机器学习的众多算法中,支持向量机(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的目标是找到一个超平面,使得:

  1. 正确分类所有样本
  2. 边界宽度最大

数学上,决策边界可以表示为:

复制代码
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聚类,它不需要标签,能够自动发现数据中的模式和结构。

相关推荐
木非哲2 小时前
AB实验高级必修课(四):逻辑回归的“马甲”、AUC的概率本质与阈值博弈
算法·机器学习·逻辑回归·abtest
兩尛2 小时前
45. 跳跃游戏 II
c++·算法·游戏
执风挽^2 小时前
Python_func_basic
开发语言·python·算法·visual studio code
我和我导针锋相队2 小时前
国自然5页纸装下“多机制复杂问题”:用“主线+支线”逻辑,把乱麻理成渔网
大数据·人工智能·机器学习
努力d小白2 小时前
leetcode438.找到字符串中所有字母异位词
java·javascript·算法
tangchao340勤奋的老年?2 小时前
ADS通信 C++ 设置通知方式读取指定变量
开发语言·c++·算法
wangluoqi2 小时前
26.2.5练习总结
数据结构·算法
jiang_changsheng2 小时前
工作流agent汇总分析 2
java·人工智能·git·python·机器学习·github·语音识别
落羽的落羽2 小时前
【Linux系统】从零实现一个简易的shell!
android·java·linux·服务器·c++·人工智能·机器学习