Skip to main content

支持向量机:寻找最优决策边界的艺术

· 22 min read
郭流芳
资深算法工程师

"在机器学习的众多算法中,支持向量机就像一位精密的几何学家,总是能找到分离数据的最优边界。" —— 2016年在老虎致远深入研究SVM时的感悟

开篇:一个分类问题的思考

想象你是一位城市规划师,需要在两个敌对社区之间建造一条隔离带。你的目标是:

  1. 完全分离两个社区
  2. 隔离带尽可能宽,以减少冲突
  3. 对未来扩张有良好的泛化能力

这个看似简单的问题,恰好就是支持向量机(SVM)要解决的核心问题:在高维空间中找到最优的分类超平面。

从感知机到支持向量机:历史的演进

感知机的局限性

1957年,Frank Rosenblatt提出了感知机算法,它能找到一个分离线性可分数据的超平面。但问题是:线性可分的数据有无数条分离线,哪一条最好?

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from sklearn.svm import SVC
from sklearn.linear_model import Perceptron
from sklearn.model_selection import train_test_split
import seaborn as sns

class SVMVisualizationDemo:
def __init__(self):
np.random.seed(42)
self.colors = ['red', 'blue', 'green', 'orange', 'purple']

def compare_decision_boundaries(self):
"""对比不同算法的决策边界"""
# 创建线性可分的2D数据
X, y = make_classification(n_samples=100, n_features=2, n_redundant=0,
n_informative=2, n_clusters_per_class=1,
random_state=42, class_sep=1.5)

# 只取一部分数据,确保线性可分
X = X[:60]
y = y[:60]

# 训练不同的分类器
models = {
'感知机': Perceptron(random_state=42, max_iter=1000),
'SVM': SVC(kernel='linear', C=1.0, random_state=42)
}

fig, axes = plt.subplots(1, 3, figsize=(18, 5))

# 绘制原始数据
axes[0].scatter(X[y==0, 0], X[y==0, 1], c='red', marker='o', s=50, alpha=0.7, label='类别 0')
axes[0].scatter(X[y==1, 0], X[y==1, 1], c='blue', marker='s', s=50, alpha=0.7, label='类别 1')
axes[0].set_title('原始数据分布', fontsize=14)
axes[0].legend()
axes[0].grid(True, alpha=0.3)
axes[0].set_xlabel('特征 1')
axes[0].set_ylabel('特征 2')

# 创建网格用于绘制决策边界
h = 0.02
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.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))

for i, (name, model) in enumerate(models.items()):
ax = axes[i + 1]

# 训练模型
model.fit(X, y)

# 绘制决策边界
if hasattr(model, "decision_function"):
Z = model.decision_function(np.c_[xx.ravel(), yy.ravel()])
else:
Z = model.predict_proba(np.c_[xx.ravel(), yy.ravel()])[:, 1]

Z = Z.reshape(xx.shape)

# 绘制等高线
contour = ax.contour(xx, yy, Z, levels=[0], colors='black', linestyles='--', linewidths=2)

# 如果是SVM,绘制支持向量和边距
if name == 'SVM':
# 绘制边距线
margin_contour = ax.contour(xx, yy, Z, levels=[-1, 1], colors='gray',
linestyles=':', linewidths=1, alpha=0.7)
ax.clabel(margin_contour, inline=True, fontsize=8, fmt='margin')

# 高亮支持向量
support_vectors = model.support_vectors_
ax.scatter(support_vectors[:, 0], support_vectors[:, 1],
s=200, facecolors='none', edgecolors='black', linewidths=2,
label='支持向量')

# 绘制数据点
ax.scatter(X[y==0, 0], X[y==0, 1], c='red', marker='o', s=50, alpha=0.7, label='类别 0')
ax.scatter(X[y==1, 0], X[y==1, 1], c='blue', marker='s', s=50, alpha=0.7, label='类别 1')

ax.set_title(f'{name} 决策边界', fontsize=14)
ax.legend()
ax.grid(True, alpha=0.3)
ax.set_xlabel('特征 1')
ax.set_ylabel('特征 2')

plt.tight_layout()
plt.show()

# 分析支持向量
svm_model = models['SVM']
print("=== SVM 分析 ===")
print(f"支持向量数量: {len(svm_model.support_vectors_)}")
print(f"总数据点数量: {len(X)}")
print(f"支持向量比例: {len(svm_model.support_vectors_)/len(X):.2%}")
print(f"决策函数系数: {svm_model.coef_}")
print(f"偏置项: {svm_model.intercept_}")

return models

# 演示不同决策边界
demo = SVMVisualizationDemo()
models = demo.compare_decision_boundaries()

SVM的核心思想:最大间隔

1963年,Vladimir Vapnik和Alexey Chervonenkis提出了最大间隔的概念。SVM不仅要找到一个分离超平面,还要找到使类别间距离最大化的超平面。

SVM的数学原理

线性SVM的优化问题

对于线性可分的数据,SVM要解决的是一个二次优化问题

class LinearSVMFromScratch:
"""从零实现线性SVM(简化版)"""

def __init__(self, C=1.0, max_iter=1000, tol=1e-6):
self.C = C # 正则化参数
self.max_iter = max_iter
self.tol = tol

def fit(self, X, y):
"""训练SVM模型"""
n_samples, n_features = X.shape

# 将标签转换为 {-1, 1}
y = np.where(y <= 0, -1, 1)

# 初始化参数
self.w = np.zeros(n_features)
self.b = 0

# 使用简化的SMO算法求解
self._solve_dual_problem(X, y)

return self

def _solve_dual_problem(self, X, y):
"""使用简化的SMO算法求解对偶问题"""
n_samples = X.shape[0]
alphas = np.zeros(n_samples)

# 计算Gram矩阵(核矩阵)
K = np.dot(X, X.T)

for iteration in range(self.max_iter):
alpha_prev = alphas.copy()

for i in range(n_samples):
# 计算误差
E_i = self._decision_function_single(X, y, alphas, i) - y[i]

# KKT条件检查
if (y[i] * E_i < -self.tol and alphas[i] < self.C) or \
(y[i] * E_i > self.tol and alphas[i] > 0):

# 选择第二个alpha(简化版)
j = self._select_second_alpha(i, n_samples, E_i, X, y, alphas)
if j == i:
continue

E_j = self._decision_function_single(X, y, alphas, j) - y[j]

# 保存旧的alpha值
alpha_i_old, alpha_j_old = alphas[i], alphas[j]

# 计算边界
if y[i] != y[j]:
L = max(0, alphas[j] - alphas[i])
H = min(self.C, self.C + alphas[j] - alphas[i])
else:
L = max(0, alphas[i] + alphas[j] - self.C)
H = min(self.C, alphas[i] + alphas[j])

if L == H:
continue

# 计算eta
eta = 2 * K[i, j] - K[i, i] - K[j, j]
if eta >= 0:
continue

# 更新alpha_j
alphas[j] -= y[j] * (E_i - E_j) / eta
alphas[j] = np.clip(alphas[j], L, H)

if abs(alphas[j] - alpha_j_old) < 1e-5:
continue

# 更新alpha_i
alphas[i] += y[i] * y[j] * (alpha_j_old - alphas[j])

# 检查收敛
if np.linalg.norm(alphas - alpha_prev) < self.tol:
break

# 计算权重和偏置
self.alphas = alphas
self.support_vector_indices = alphas > 1e-5
self.support_vectors = X[self.support_vector_indices]
self.support_vector_labels = y[self.support_vector_indices]
self.support_vector_alphas = alphas[self.support_vector_indices]

# 计算权重向量
self.w = np.sum((self.support_vector_alphas * self.support_vector_labels)[:, np.newaxis] *
self.support_vectors, axis=0)

# 计算偏置
if len(self.support_vectors) > 0:
self.b = np.mean(self.support_vector_labels - np.dot(self.support_vectors, self.w))

print(f"训练完成: {len(self.support_vectors)} 个支持向量")

def _decision_function_single(self, X, y, alphas, i):
"""计算单个样本的决策函数值"""
result = 0
for j in range(len(alphas)):
if alphas[j] > 0:
result += alphas[j] * y[j] * np.dot(X[j], X[i])
return result + self.b

def _select_second_alpha(self, i, n_samples, E_i, X, y, alphas):
"""选择第二个alpha(简化版随机选择)"""
candidates = list(range(n_samples))
candidates.remove(i)
return np.random.choice(candidates)

def decision_function(self, X):
"""计算决策函数值"""
return np.dot(X, self.w) + self.b

def predict(self, X):
"""预测"""
return np.sign(self.decision_function(X))

# 测试自实现的SVM
def test_custom_svm():
"""测试自实现的SVM"""
# 创建简单的2D数据
np.random.seed(42)
X_pos = np.random.multivariate_normal([2, 2], [[0.5, 0], [0, 0.5]], 20)
X_neg = np.random.multivariate_normal([-1, -1], [[0.5, 0], [0, 0.5]], 20)
X = np.vstack([X_pos, X_neg])
y = np.hstack([np.ones(20), np.zeros(20)])

# 训练自实现的SVM
custom_svm = LinearSVMFromScratch(C=1.0)
custom_svm.fit(X, y)

# 训练sklearn的SVM进行对比
sklearn_svm = SVC(kernel='linear', C=1.0)
sklearn_svm.fit(X, y)

# 可视化结果
fig, axes = plt.subplots(1, 2, figsize=(15, 6))

models = [
("自实现 SVM", custom_svm),
("Scikit-learn SVM", sklearn_svm)
]

for i, (title, model) in enumerate(models):
ax = axes[i]

# 绘制数据点
ax.scatter(X[y==1, 0], X[y==1, 1], c='red', marker='o', s=50, alpha=0.7, label='正类')
ax.scatter(X[y==0, 0], X[y==0, 1], c='blue', marker='s', s=50, alpha=0.7, label='负类')

# 绘制决策边界
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, 100),
np.linspace(y_min, y_max, 100))

grid_points = np.c_[xx.ravel(), yy.ravel()]

if hasattr(model, 'decision_function'):
Z = model.decision_function(grid_points)
else:
# 对于sklearn SVM
Z = sklearn_svm.decision_function(grid_points)

Z = Z.reshape(xx.shape)

# 绘制决策边界和边距
ax.contour(xx, yy, Z, levels=[0], colors='black', linestyles='-', linewidths=2)
ax.contour(xx, yy, Z, levels=[-1, 1], colors='gray', linestyles='--', linewidths=1)

# 标记支持向量
if title == "自实现 SVM":
if hasattr(model, 'support_vectors'):
ax.scatter(model.support_vectors[:, 0], model.support_vectors[:, 1],
s=200, facecolors='none', edgecolors='black', linewidths=2,
label='支持向量')
else:
support_vectors = sklearn_svm.support_vectors_
ax.scatter(support_vectors[:, 0], support_vectors[:, 1],
s=200, facecolors='none', edgecolors='black', linewidths=2,
label='支持向量')

ax.set_title(title, fontsize=14)
ax.legend()
ax.grid(True, alpha=0.3)
ax.set_xlabel('特征 1')
ax.set_ylabel('特征 2')

plt.tight_layout()
plt.show()

# 对比分析
print("=== 模型对比分析 ===")
if hasattr(custom_svm, 'support_vectors'):
print(f"自实现 SVM 支持向量数: {len(custom_svm.support_vectors)}")
print(f"Scikit-learn SVM 支持向量数: {len(sklearn_svm.support_vectors_)}")
print(f"自实现 SVM 权重: {custom_svm.w}")
print(f"Scikit-learn SVM 权重: {sklearn_svm.coef_[0]}")

# 运行测试
test_custom_svm()

核函数:处理非线性问题的魔法

核技巧(Kernel Trick)的原理

当数据在原空间不可线性分离时,SVM使用核函数将数据映射到高维空间,使其变得线性可分。

class KernelSVMDemo:
"""核函数SVM演示"""

def __init__(self):
self.kernels = {
'linear': '线性核',
'poly': '多项式核',
'rbf': 'RBF核',
'sigmoid': 'Sigmoid核'
}

def create_nonlinear_data(self):
"""创建非线性可分的数据"""
np.random.seed(42)

# 创建同心圆数据
from sklearn.datasets import make_circles
X_circles, y_circles = make_circles(n_samples=200, noise=0.1, factor=0.3, random_state=42)

# 创建月牙形数据
from sklearn.datasets import make_moons
X_moons, y_moons = make_moons(n_samples=200, noise=0.1, random_state=42)

# 创建异或数据
X_xor = np.random.randn(200, 2)
y_xor = np.logical_xor(X_xor[:, 0] > 0, X_xor[:, 1] > 0).astype(int)

return {
'同心圆': (X_circles, y_circles),
'月牙形': (X_moons, y_moons),
'异或': (X_xor, y_xor)
}

def compare_kernels(self):
"""对比不同核函数的效果"""
datasets = self.create_nonlinear_data()

fig, axes = plt.subplots(len(datasets), len(self.kernels) + 1,
figsize=(20, 15))

for i, (dataset_name, (X, y)) in enumerate(datasets.items()):
# 绘制原始数据
ax = axes[i, 0]
scatter = ax.scatter(X[:, 0], X[:, 1], c=y, cmap='RdYlBu', s=30, alpha=0.7)
ax.set_title(f'{dataset_name} - 原始数据')
ax.grid(True, alpha=0.3)
plt.colorbar(scatter, ax=ax)

# 测试每种核函数
for j, (kernel_name, kernel_label) in enumerate(self.kernels.items()):
ax = axes[i, j + 1]

# 设置核函数参数
if kernel_name == 'poly':
svm = SVC(kernel=kernel_name, degree=3, C=1.0)
elif kernel_name == 'rbf':
svm = SVC(kernel=kernel_name, gamma='scale', C=1.0)
else:
svm = SVC(kernel=kernel_name, C=1.0)

try:
svm.fit(X, y)

# 创建网格用于绘制决策边界
h = 0.02
x_min, x_max = X[:, 0].min() - 0.5, X[:, 0].max() + 0.5
y_min, y_max = X[:, 1].min() - 0.5, X[:, 1].max() + 0.5
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))

# 预测网格点
Z = svm.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)

# 绘制决策边界
ax.contourf(xx, yy, Z, alpha=0.4, cmap='RdYlBu')
scatter = ax.scatter(X[:, 0], X[:, 1], c=y, cmap='RdYlBu', s=30, alpha=0.8)

# 标记支持向量
support_vectors = svm.support_vectors_
ax.scatter(support_vectors[:, 0], support_vectors[:, 1],
s=100, facecolors='none', edgecolors='black', linewidths=1.5)

# 计算准确率
accuracy = svm.score(X, y)
ax.set_title(f'{kernel_label}\n准确率: {accuracy:.3f}\n支持向量: {len(support_vectors)}')

except Exception as e:
ax.text(0.5, 0.5, f'训练失败\n{str(e)}',
transform=ax.transAxes, ha='center', va='center')
ax.set_title(f'{kernel_label} - 失败')

ax.grid(True, alpha=0.3)
ax.set_xlabel('特征 1')
ax.set_ylabel('特征 2')

plt.tight_layout()
plt.show()

def kernel_function_visualization(self):
"""可视化不同核函数的特性"""
# 创建1D数据进行核函数演示
x = np.linspace(-3, 3, 100)

# 定义不同的核函数
def linear_kernel(x1, x2):
return np.dot(x1, x2)

def polynomial_kernel(x1, x2, degree=3):
return (1 + np.dot(x1, x2)) ** degree

def rbf_kernel(x1, x2, gamma=1.0):
return np.exp(-gamma * np.linalg.norm(x1 - x2) ** 2)

def sigmoid_kernel(x1, x2, gamma=1.0, coef0=0):
return np.tanh(gamma * np.dot(x1, x2) + coef0)

# 选择一个参考点
x_ref = np.array([0])

fig, axes = plt.subplots(2, 2, figsize=(15, 10))
axes = axes.ravel()

kernels_func = [
(linear_kernel, "线性核: K(x,0) = x·0", {}),
(polynomial_kernel, "多项式核: K(x,0) = (1+x·0)³", {'degree': 3}),
(rbf_kernel, "RBF核: K(x,0) = exp(-γ||x-0||²)", {'gamma': 1.0}),
(sigmoid_kernel, "Sigmoid核: K(x,0) = tanh(γx·0)", {'gamma': 1.0})
]

for i, (kernel_func, title, params) in enumerate(kernels_func):
y_values = []

for xi in x:
if kernel_func == linear_kernel:
y_values.append(xi * 0) # 与0的线性核
elif kernel_func == polynomial_kernel:
y_values.append((1 + xi * 0) ** params['degree'])
elif kernel_func == rbf_kernel:
y_values.append(np.exp(-params['gamma'] * (xi - 0) ** 2))
elif kernel_func == sigmoid_kernel:
y_values.append(np.tanh(params['gamma'] * xi * 0))

axes[i].plot(x, y_values, linewidth=2, color=f'C{i}')
axes[i].set_title(title, fontsize=12)
axes[i].grid(True, alpha=0.3)
axes[i].set_xlabel('x')
axes[i].set_ylabel('K(x, 0)')

# 标记参考点
axes[i].axvline(x=0, color='red', linestyle='--', alpha=0.7, label='参考点 x=0')
axes[i].legend()

plt.tight_layout()
plt.show()

# 演示RBF核的gamma参数影响
self._demo_rbf_gamma_effect()

def _demo_rbf_gamma_effect(self):
"""演示RBF核中gamma参数的影响"""
# 创建简单的2D数据
np.random.seed(42)
X = np.array([[1, 1], [2, 2], [1, 2], [4, 4], [5, 5], [4, 5]])
y = np.array([0, 0, 0, 1, 1, 1])

gammas = [0.1, 1.0, 10.0, 100.0]

fig, axes = plt.subplots(1, len(gammas), figsize=(20, 4))

for i, gamma in enumerate(gammas):
ax = axes[i]

# 训练SVM
svm = SVC(kernel='rbf', gamma=gamma, C=1.0)
svm.fit(X, y)

# 创建网格
h = 0.1
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.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))

# 预测网格点
Z = svm.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)

# 绘制决策边界
ax.contourf(xx, yy, Z, alpha=0.4, cmap='RdYlBu')
scatter = ax.scatter(X[:, 0], X[:, 1], c=y, cmap='RdYlBu', s=100, alpha=0.8)

# 标记支持向量
support_vectors = svm.support_vectors_
ax.scatter(support_vectors[:, 0], support_vectors[:, 1],
s=200, facecolors='none', edgecolors='black', linewidths=2)

ax.set_title(f'RBF核 γ={gamma}\n支持向量: {len(support_vectors)}')
ax.grid(True, alpha=0.3)
ax.set_xlabel('特征 1')
ax.set_ylabel('特征 2')

plt.tight_layout()
plt.show()

print("=== RBF核的gamma参数分析 ===")
print("γ 越小 -> 决策边界越平滑,泛化能力强,可能欠拟合")
print("γ 越大 -> 决策边界越复杂,对训练数据拟合好,可能过拟合")

# 运行核函数演示
kernel_demo = KernelSVMDemo()
kernel_demo.compare_kernels()
kernel_demo.kernel_function_visualization()

老虎致远项目中的SVM实战

在老虎致远的工作中,我在多个实际项目中应用了SVM:

案例1:文档分类系统

class DocumentClassificationCase:
"""文档分类实战案例"""

def __init__(self):
self.vectorizer = None
self.svm_model = None

def simulate_document_data(self):
"""模拟文档分类数据"""
# 模拟技术文档、业务文档、法律文档
documents = [
# 技术文档
"机器学习算法优化 神经网络 深度学习 Python 代码实现",
"数据库索引优化 SQL查询 性能调优 MySQL PostgreSQL",
"前端框架 React Vue.js 组件开发 JavaScript TypeScript",
"API设计 RESTful 微服务架构 Docker Kubernetes",
"算法复杂度分析 数据结构 排序算法 图论",

# 业务文档
"市场营销策略 客户获取 品牌推广 销售转化率",
"财务报表分析 成本控制 利润率 预算管理",
"产品需求分析 用户体验 功能设计 商业模式",
"项目管理 团队协作 进度控制 风险评估",
"商业分析 市场调研 竞争对手 SWOT分析",

# 法律文档
"合同条款 法律责任 知识产权 版权保护",
"劳动法规 员工权益 工作时间 薪酬福利",
"数据保护 隐私政策 GDPR合规 信息安全",
"公司章程 股东权益 董事会决议 企业治理",
"商标注册 专利申请 法律风险 合规审查"
]

labels = [0]*5 + [1]*5 + [2]*5 # 0:技术, 1:业务, 2:法律
label_names = ['技术文档', '业务文档', '法律文档']

return documents, labels, label_names

def train_svm_classifier(self):
"""训练SVM文档分类器"""
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import cross_val_score, GridSearchCV
from sklearn.metrics import classification_report, confusion_matrix

# 获取数据
documents, labels, label_names = self.simulate_document_data()

# 文本向量化
self.vectorizer = TfidfVectorizer(
max_features=1000,
stop_words='english',
ngram_range=(1, 2) # 使用1-gram和2-gram
)

X = self.vectorizer.fit_transform(documents)
y = np.array(labels)

print(f"特征维度: {X.shape}")
print(f"文档数量: {len(documents)}")
print(f"类别分布: {np.bincount(y)}")

# 网格搜索找最优参数
param_grid = {
'C': [0.1, 1, 10, 100],
'kernel': ['linear', 'rbf'],
'gamma': ['scale', 'auto', 0.001, 0.01, 0.1, 1]
}

# 只对RBF核搜索gamma
svm = SVC(random_state=42)
grid_search = GridSearchCV(
svm, param_grid, cv=3, scoring='accuracy', verbose=1
)

grid_search.fit(X, y)

self.svm_model = grid_search.best_estimator_

print(f"\n=== 最优参数 ===")
print(f"最优参数: {grid_search.best_params_}")
print(f"最优交叉验证分数: {grid_search.best_score_:.3f}")

# 详细评估
y_pred = self.svm_model.predict(X)

print(f"\n=== 分类报告 ===")
print(classification_report(y, y_pred, target_names=label_names))

# 混淆矩阵可视化
cm = confusion_matrix(y, y_pred)

plt.figure(figsize=(10, 8))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
xticklabels=label_names, yticklabels=label_names)
plt.title('文档分类混淆矩阵')
plt.ylabel('真实类别')
plt.xlabel('预测类别')
plt.show()

# 特征重要性分析(对于线性SVM)
if self.svm_model.kernel == 'linear':
self._analyze_feature_importance(label_names)

return self.svm_model

def _analyze_feature_importance(self, label_names):
"""分析特征重要性(仅适用于线性SVM)"""
feature_names = self.vectorizer.get_feature_names_out()

plt.figure(figsize=(15, 8))

for i, class_name in enumerate(label_names):
# 获取该类别的权重
weights = self.svm_model.coef_[i]

# 找出最重要的特征
top_indices = np.argsort(np.abs(weights))[-10:]
top_features = [feature_names[idx] for idx in top_indices]
top_weights = weights[top_indices]

plt.subplot(1, 3, i+1)
colors = ['red' if w < 0 else 'blue' for w in top_weights]
plt.barh(range(len(top_features)), top_weights, color=colors, alpha=0.7)
plt.yticks(range(len(top_features)), top_features)
plt.title(f'{class_name} - 关键特征')
plt.xlabel('权重')
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# 打印每个类别的关键词
print(f"\n=== 类别关键特征分析 ===")
for i, class_name in enumerate(label_names):
weights = self.svm_model.coef_[i]
top_indices = np.argsort(weights)[-5:] # 正权重最大的5个
bottom_indices = np.argsort(weights)[:5] # 负权重最大的5个

print(f"\n{class_name}:")
print(f" 正向指标词: {[feature_names[idx] for idx in top_indices]}")
print(f" 负向指标词: {[feature_names[idx] for idx in bottom_indices]}")

def test_new_documents(self):
"""测试新文档分类"""
if self.svm_model is None or self.vectorizer is None:
print("请先训练模型!")
return

# 测试文档
test_docs = [
"深度学习卷积神经网络图像识别算法",
"销售业绩分析客户满意度市场份额",
"合同法律条款知识产权保护措施"
]

# 向量化
X_test = self.vectorizer.transform(test_docs)

# 预测
predictions = self.svm_model.predict(X_test)
probabilities = self.svm_model.decision_function(X_test)

label_names = ['技术文档', '业务文档', '法律文档']

print(f"\n=== 新文档分类测试 ===")
for i, doc in enumerate(test_docs):
print(f"\n文档 {i+1}: {doc[:30]}...")
print(f"预测类别: {label_names[predictions[i]]}")
print(f"决策函数值: {probabilities[i]}")

# 运行文档分类案例
doc_classifier = DocumentClassificationCase()
model = doc_classifier.train_svm_classifier()
doc_classifier.test_new_documents()

案例2:异常检测系统

class AnomalyDetectionCase:
"""异常检测实战案例(使用One-Class SVM)"""

def __init__(self):
self.model = None
self.scaler = None

def generate_anomaly_data(self):
"""生成异常检测数据"""
np.random.seed(42)

# 正常数据:系统正常运行时的指标
n_normal = 1000
normal_cpu = np.random.normal(30, 10, n_normal) # CPU使用率
normal_memory = np.random.normal(50, 15, n_normal) # 内存使用率
normal_network = np.random.normal(100, 20, n_normal) # 网络流量

# 添加一些相关性
normal_memory += 0.3 * normal_cpu + np.random.normal(0, 5, n_normal)
normal_network += 0.2 * normal_cpu + 0.1 * normal_memory + np.random.normal(0, 10, n_normal)

# 确保在合理范围内
normal_cpu = np.clip(normal_cpu, 0, 100)
normal_memory = np.clip(normal_memory, 0, 100)
normal_network = np.clip(normal_network, 0, 500)

X_normal = np.column_stack([normal_cpu, normal_memory, normal_network])

# 异常数据:系统异常时的指标
n_anomaly = 50

# 异常类型1:CPU过载
anomaly_cpu_high = np.random.normal(85, 10, n_anomaly//3)
anomaly_memory_high = np.random.normal(80, 15, n_anomaly//3)
anomaly_network_high = np.random.normal(300, 50, n_anomaly//3)

# 异常类型2:内存泄露
anomaly_cpu_normal = np.random.normal(40, 10, n_anomaly//3)
anomaly_memory_leak = np.random.normal(95, 5, n_anomaly//3)
anomaly_network_normal = np.random.normal(120, 20, n_anomaly//3)

# 异常类型3:网络攻击
anomaly_cpu_attack = np.random.normal(60, 15, n_anomaly - 2*(n_anomaly//3))
anomaly_memory_attack = np.random.normal(70, 10, n_anomaly - 2*(n_anomaly//3))
anomaly_network_attack = np.random.normal(450, 50, n_anomaly - 2*(n_anomaly//3))

# 合并异常数据
X_anomaly = np.column_stack([
np.concatenate([anomaly_cpu_high, anomaly_cpu_normal, anomaly_cpu_attack]),
np.concatenate([anomaly_memory_high, anomaly_memory_leak, anomaly_memory_attack]),
np.concatenate([anomaly_network_high, anomaly_network_normal, anomaly_network_attack])
])

# 确保在合理范围内
X_anomaly = np.clip(X_anomaly, 0, [100, 100, 500])

return X_normal, X_anomaly

def train_one_class_svm(self):
"""训练One-Class SVM异常检测模型"""
from sklearn.svm import OneClassSVM
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import classification_report

# 获取数据
X_normal, X_anomaly = self.generate_anomaly_data()

print(f"正常样本数量: {len(X_normal)}")
print(f"异常样本数量: {len(X_anomaly)}")

# 数据标准化
self.scaler = StandardScaler()
X_normal_scaled = self.scaler.fit_transform(X_normal)
X_anomaly_scaled = self.scaler.transform(X_anomaly)

# 训练One-Class SVM(只使用正常数据)
self.model = OneClassSVM(
kernel='rbf',
gamma='scale',
nu=0.05 # 预期异常比例
)

self.model.fit(X_normal_scaled)

# 在训练数据上的表现
normal_pred = self.model.predict(X_normal_scaled)
anomaly_pred = self.model.predict(X_anomaly_scaled)

# 计算性能指标
normal_outlier_rate = np.mean(normal_pred == -1)
anomaly_detection_rate = np.mean(anomaly_pred == -1)

print(f"\n=== One-Class SVM 性能 ===")
print(f"正常数据中被误判为异常的比例: {normal_outlier_rate:.3f}")
print(f"异常数据中被正确检测的比例: {anomaly_detection_rate:.3f}")

# 合并数据进行整体评估
X_all = np.vstack([X_normal_scaled, X_anomaly_scaled])
y_true = np.hstack([np.ones(len(X_normal)), -np.ones(len(X_anomaly))])
y_pred = self.model.predict(X_all)

print(f"\n=== 整体分类报告 ===")
print(classification_report(y_true, y_pred, target_names=['异常', '正常']))

# 可视化结果
self._visualize_anomaly_detection(X_normal, X_anomaly)

return self.model

def _visualize_anomaly_detection(self, X_normal, X_anomaly):
"""可视化异常检测结果"""
fig, axes = plt.subplots(2, 2, figsize=(15, 12))

# 数据标准化用于可视化
X_normal_scaled = self.scaler.transform(X_normal)
X_anomaly_scaled = self.scaler.transform(X_anomaly)

# 预测
normal_pred = self.model.predict(X_normal_scaled)
anomaly_pred = self.model.predict(X_anomaly_scaled)

# 特征对比图
feature_names = ['CPU使用率', '内存使用率', '网络流量']
feature_pairs = [(0, 1), (0, 2), (1, 2)]

for i, (f1, f2) in enumerate(feature_pairs):
ax = axes[i//2, i%2]

# 绘制正常数据
normal_correct = X_normal_scaled[normal_pred == 1]
normal_wrong = X_normal_scaled[normal_pred == -1]

ax.scatter(normal_correct[:, f1], normal_correct[:, f2],
c='blue', alpha=0.6, s=20, label='正常(正确)')
ax.scatter(normal_wrong[:, f1], normal_wrong[:, f2],
c='lightblue', alpha=0.8, s=30, marker='x', label='正常(误判)')

# 绘制异常数据
anomaly_detected = X_anomaly_scaled[anomaly_pred == -1]
anomaly_missed = X_anomaly_scaled[anomaly_pred == 1]

ax.scatter(anomaly_detected[:, f1], anomaly_detected[:, f2],
c='red', alpha=0.8, s=30, label='异常(检测到)')
ax.scatter(anomaly_missed[:, f1], anomaly_missed[:, f2],
c='orange', alpha=0.8, s=40, marker='s', label='异常(漏检)')

ax.set_xlabel(f'{feature_names[f1]} (标准化)')
ax.set_ylabel(f'{feature_names[f2]} (标准化)')
ax.set_title(f'{feature_names[f1]} vs {feature_names[f2]}')
ax.legend()
ax.grid(True, alpha=0.3)

# 决策函数分布
ax = axes[1, 1]

normal_scores = self.model.decision_function(X_normal_scaled)
anomaly_scores = self.model.decision_function(X_anomaly_scaled)

ax.hist(normal_scores, bins=30, alpha=0.7, label='正常数据', color='blue', density=True)
ax.hist(anomaly_scores, bins=30, alpha=0.7, label='异常数据', color='red', density=True)
ax.axvline(x=0, color='black', linestyle='--', label='决策边界')
ax.set_xlabel('决策函数值')
ax.set_ylabel('密度')
ax.set_title('决策函数分布')
ax.legend()
ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

def real_time_monitoring_demo(self):
"""实时监控演示"""
if self.model is None or self.scaler is None:
print("请先训练模型!")
return

print(f"\n=== 实时异常监控演示 ===")

# 模拟实时数据流
monitoring_data = [
[25, 45, 95], # 正常
[40, 60, 120], # 正常
[90, 85, 350], # 异常:高CPU和内存
[35, 95, 110], # 异常:内存泄露
[55, 70, 480], # 异常:网络攻击
[30, 50, 100], # 正常
]

print("时间戳 CPU 内存 网络 状态 置信度")
print("-" * 55)

for i, data in enumerate(monitoring_data):
# 标准化
data_scaled = self.scaler.transform([data])

# 预测
prediction = self.model.predict(data_scaled)[0]
confidence = self.model.decision_function(data_scaled)[0]

status = "正常" if prediction == 1 else "异常"
timestamp = f"2024-01-01 10:{i:02d}:00"

print(f"{timestamp} {data[0]:3.0f}% {data[1]:3.0f}% {data[2]:3.0f} {status:4s} {confidence:6.3f}")

if prediction == -1:
print(f" ⚠️ 检测到异常! 建议立即检查系统状态")

# 运行异常检测案例
anomaly_detector = AnomalyDetectionCase()
model = anomaly_detector.train_one_class_svm()
anomaly_detector.real_time_monitoring_demo()

SVM的优缺点与选择指南

基于在老虎致远的实战经验,总结如下:

优点

  • 理论基础扎实:基于统计学习理论,有严格的数学证明
  • 泛化能力强:通过结构风险最小化,避免过拟合
  • 核函数灵活:可以处理非线性问题
  • 支持向量稀疏:只需要少量关键样本就能确定决策边界

缺点

  • 训练时间长:对大数据集不友好(O(n³)复杂度)
  • 参数敏感:需要仔细调优C、gamma等参数
  • 内存需求大:需要存储核矩阵
  • 缺乏概率输出:原生不提供类别概率

选择指南

场景推荐度原因
小到中等数据集⭐⭐⭐⭐⭐SVM的优势场景
高维数据⭐⭐⭐⭐⭐维度灾难下表现好
非线性问题⭐⭐⭐⭐核函数处理非线性
大数据集⭐⭐训练时间过长
需要概率输出⭐⭐⭐需要额外校准
实时预测⭐⭐⭐⭐预测速度快

延伸阅读与学习路径

想要深入了解SVM和机器学习?推荐阅读:

总结:寻找最优边界的哲学

在老虎致远研究SVM的经历让我深刻理解了一个道理:真正的智慧不在于使用复杂的模型,而在于找到问题的本质边界

SVM教会我们:

  1. 几何直觉:机器学习本质上是几何问题
  2. 最优原则:不是任意找到解,而是找到最优解
  3. 泛化思维:好的模型应该对未见数据有良好表现
  4. 权衡艺术:在偏差和方差之间找平衡

这种寻找最优边界的思维方式,不仅适用于机器学习,也适用于工程设计、商业决策和人生选择。正如SVM寻找最大间隔的超平面一样,我们在面对复杂问题时,也需要寻找那个能够最好分离不同选择的决策边界。


希望这篇文章能帮助你理解SVM的核心思想和实际应用。在下一篇文章中,我将分享在广联达时期的NLP项目经验,敬请期待!