Day 12 编程实战:SVM 金融预测与调参
实战目标
- 理解 SVM 的最大间隔原理
- 掌握 C 参数(软间隔)的作用
- 对比线性核与 RBF 核的效果
- 使用网格搜索调优 C 和 gamma
- 应用到金融涨跌预测
1. 导入必要的库
python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import time
from pathlib import Path
from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split, TimeSeriesSplit, GridSearchCV
from sklearn.metrics import (
accuracy_score, precision_score, recall_score, f1_score,
roc_auc_score, roc_curve, classification_report, confusion_matrix
)
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import make_classification, make_circles, make_moons
import warnings
warnings.filterwarnings('ignore')
#启用LaTeX渲染(如果系统安装了LaTeX)
plt.rcParams['text.usetex'] = False # 设为False避免LaTeX依赖
# 设置中文显示
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
2. SVM 原理可视化
2.1 最大间隔可视化
python
def visualize_svm_margin():
"""可视化 SVM 的最大间隔原理"""
from sklearn.datasets import make_blobs
# 生成线性可分数据
X, y = make_blobs(n_samples=50, centers=2, n_features=2,
cluster_std=0.8, random_state=42)
y = 2 * y - 1 # 转换为 -1, 1
# 训练线性 SVM
svm = SVC(kernel='linear', C=1e10) # 大C近似硬间隔
svm.fit(X, y)
# 获取超平面参数
w = svm.coef_[0]
b = svm.intercept_[0]
# 创建网格
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, 0.02),
np.arange(y_min, y_max, 0.02))
# 决策边界
Z = svm.decision_function(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
plt.figure(figsize=(10, 8))
# 绘制决策边界和间隔边界
plt.contour(xx, yy, Z, levels=[-1, 0, 1],
colors=['blue', 'black', 'blue'],
linestyles=['--', '-', '--'],
linewidths=[1, 2, 1])
# 绘制数据点
plt.scatter(X[y == -1, 0], X[y == -1, 1], c='blue',
label='类别 -1', s=60, edgecolors='k')
plt.scatter(X[y == 1, 0], X[y == 1, 1], c='red',
label='类别 1', s=60, edgecolors='k')
# 标记支持向量
support_vectors = svm.support_vectors_
plt.scatter(support_vectors[:, 0], support_vectors[:, 1],
s=200, facecolors='none', edgecolors='green',
linewidths=2, label='支持向量')
plt.xlabel('特征 X1')
plt.ylabel('特征 X2')
plt.title('SVM 最大间隔原理(线性可分)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.axis('tight')
plt.show()
print(f"支持向量数量: {len(support_vectors)}/{len(X)}")
print("只有支持向量决定决策边界!")
visualize_svm_margin()

支持向量数量: 2/50
只有支持向量决定决策边界!
2.2 C 参数(软间隔)的影响可视化
python
def visualize_c_effect():
"""可视化 C 参数对决策边界的影响"""
from sklearn.datasets import make_blobs
# 生成线性不可分数据(有重叠)
X, y = make_blobs(n_samples=100, centers=2, n_features=2,
cluster_std=3, random_state=42)
y = 2 * y - 1
C_values = [0.01, 0.1, 1, 10, 100]
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
axes = axes.ravel()
for idx, C in enumerate(C_values):
svm = SVC(kernel='linear', C=C)
svm.fit(X, y)
# 创建网格
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, 0.02),
np.arange(y_min, y_max, 0.02))
Z = svm.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
axes[idx].contourf(xx, yy, Z, alpha=0.3, cmap='RdYlBu')
axes[idx].scatter(X[y == -1, 0], X[y == -1, 1],
c='blue', label='-1', s=40, alpha=0.7)
axes[idx].scatter(X[y == 1, 0], X[y == 1, 1],
c='red', label='1', s=40, alpha=0.7)
# 标记支持向量
axes[idx].scatter(svm.support_vectors_[:, 0],
svm.support_vectors_[:, 1],
s=100, facecolors='none', edgecolors='green',
linewidths=2)
axes[idx].set_title(f'C = {C}\n支持向量数: {len(svm.support_vectors_)}')
axes[idx].set_xlabel('特征 X1')
axes[idx].set_ylabel('特征 X2')
axes[idx].legend(loc='upper right')
axes[idx].grid(True, alpha=0.3)
# 隐藏多余的子图
axes[5].set_visible(False)
plt.suptitle('C 参数对 SVM 决策边界的影响(C 越大,间隔越小)', fontsize=14)
plt.tight_layout()
plt.show()
print("观察结论:")
print("- C 小(0.01):间隔大,允许较多误分类(欠拟合)")
print("- C 大(100):间隔小,争取分类正确(可能过拟合)")
print("- 支持向量数随 C 增大而减少")
visualize_c_effect()

观察结论:
- C 小(0.01):间隔大,允许较多误分类(欠拟合)
- C 大(100):间隔小,争取分类正确(可能过拟合)
- 支持向量数随 C 增大而减少
2.3 核函数可视化对比
python
def visualize_kernel_comparison():
"""对比不同核函数的效果"""
# 生成非线性数据(同心圆)
X, y = make_circles(n_samples=200, factor=0.5, noise=0.1, random_state=42)
y = 2 * y - 1
kernels = [
('线性核 (linear)', {'kernel': 'linear', 'C': 1.0}),
('RBF核 (γ=0.5)', {'kernel': 'rbf', 'gamma': 0.5, 'C': 1.0}),
('RBF核 (γ=5)', {'kernel': 'rbf', 'gamma': 5, 'C': 1.0}),
('RBF核 (γ=0.1)', {'kernel': 'rbf', 'gamma': 0.1, 'C': 1.0}),
('多项式核 (deg=3)', {'kernel': 'poly', 'degree': 3, 'C': 1.0}),
('多项式核 (deg=5)', {'kernel': 'poly', 'degree': 5, 'C': 1.0})
]
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
axes = axes.ravel()
for idx, (title, params) in enumerate(kernels):
svm = SVC(**params)
svm.fit(X, y)
# 创建网格
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, 0.02),
np.arange(y_min, y_max, 0.02))
Z = svm.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
axes[idx].contourf(xx, yy, Z, alpha=0.3, cmap='RdYlBu')
axes[idx].scatter(X[y == -1, 0], X[y == -1, 1],
c='blue', s=30, alpha=0.6)
axes[idx].scatter(X[y == 1, 0], X[y == 1, 1],
c='red', s=30, alpha=0.6)
axes[idx].set_title(f'{title}\n准确率: {svm.score(X, y):.3f}')
axes[idx].set_xlabel('特征 X1')
axes[idx].set_ylabel('特征 X2')
axes[idx].grid(True, alpha=0.3)
plt.suptitle('不同核函数对非线性数据的分类效果', fontsize=14)
plt.tight_layout()
plt.show()
print("观察结论:")
print("- 线性核:无法处理非线性数据")
print("- RBF 核(γ=0.5):效果最好")
print("- γ 太大(5):过拟合(决策边界复杂)")
print("- γ 太小(0.1):欠拟合(决策边界过于平滑)")
visualize_kernel_comparison()

观察结论:
- 线性核:无法处理非线性数据
- RBF 核(γ=0.5):效果最好
- γ 太大(5):过拟合(决策边界复杂)
- γ 太小(0.1):欠拟合(决策边界过于平滑)
3. 生成金融数据
python
def generate_financial_data(ts_code):
"""生成金融数据"""
data_path = Path(r"E:\AppData\quant_trade\klines\kline2014-2024")
kline_file = data_path / f"{ts_code}.csv"
df = pd.read_csv(kline_file, usecols=["trade_date", "close", "vol"],
parse_dates=["trade_date"])\
.rename(columns={"vol": "volume"})\
.sort_values(by=["trade_date"])\
.reset_index(drop=True)
df['return'] = df['close'].pct_change()
# 技术指标
# RSI
delta = df['return'].fillna(0)
gain = delta.where(delta > 0, 0).rolling(14).mean()
loss = -delta.where(delta < 0, 0).rolling(14).mean()
rs = gain / (loss + 1e-10)
df['rsi'] = 100 - (100 / (1 + rs))
# MACD
ema12 = df['close'].ewm(span=12, adjust=False).mean()
ema26 = df['close'].ewm(span=26, adjust=False).mean()
df['macd'] = ema12 - ema26
df['macd_signal'] = df['macd'].ewm(span=9, adjust=False).mean()
# 均线比率
df['ma5'] = df['close'].rolling(5).mean()
df['ma20'] = df['close'].rolling(20).mean()
df['ma_ratio'] = df['ma5'] / df['ma20'] - 1
# 波动率
df['volatility'] = df['return'].rolling(20).std()
# 成交量比率
df['volume_ratio'] = df['volume'] / df['volume'].rolling(10).mean()
# 动量指标
for lag in [1, 2, 3, 5, 10]:
df[f'momentum_{lag}'] = df['return'].shift(lag).fillna(0)
# 目标变量:3日收盘是否上涨
df['target'] = (df['close'].shift(-5) > df['close']).astype(int)
# 删除缺失值
df = df.dropna()
return df
# 生成数据
ts_code = "000001.SZ"
df = generate_financial_data(ts_code)
print(f"数据形状: {df.shape}")
# 特征选择
feature_cols = ['rsi', 'macd', 'macd_signal', 'ma_ratio', 'volatility',
'volume_ratio', 'momentum_1', 'momentum_2', 'momentum_3',
'momentum_5', 'momentum_10']
X = df[feature_cols].values
y = df['target'].values
print(f"特征数量: {len(feature_cols)}")
print(f"样本数量: {len(X)}")
print(f"目标分布: {pd.Series(y).value_counts(normalize=True)}")
# 按时间划分
split_idx = int(len(X) * 0.7)
X_train = X[:split_idx]
X_test = X[split_idx:]
y_train = y[:split_idx]
y_test = y[split_idx:]
print(f"\n训练集: {len(X_train)} 样本")
print(f"测试集: {len(X_test)} 样本")
数据形状: (2451, 18)
特征数量: 11
样本数量: 2451
目标分布: 0 0.515708
1 0.484292
Name: proportion, dtype: float64
训练集: 1715 样本
测试集: 736 样本
4. 数据标准化(SVM 必需!)
python
print("="*60)
print("数据标准化的重要性")
print("="*60)
# 未标准化的特征统计
print("\n原始特征统计:")
for i, col in enumerate(feature_cols):
print(f" {col}: 均值={X_train[:, i].mean():.4f}, 标准差={X_train[:, i].std():.4f}")
# 标准化
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
print("\n标准化后特征统计:")
for i, col in enumerate(feature_cols):
print(f" {col}: 均值={X_train_scaled[:, i].mean():.4f}, 标准差={X_train_scaled[:, i].std():.4f}")
# 对比未标准化和标准化的 SVM 性能
svm_unscaled = SVC(kernel='rbf', C=1.0, gamma='scale')
svm_unscaled.fit(X_train, y_train)
acc_unscaled = accuracy_score(y_test, svm_unscaled.predict(X_test))
svm_scaled = SVC(kernel='rbf', C=1.0, gamma='scale')
svm_scaled.fit(X_train_scaled, y_train)
acc_scaled = accuracy_score(y_test, svm_scaled.predict(X_test_scaled))
print(f"\n未标准化 SVM 准确率: {acc_unscaled:.4f}")
print(f"标准化后 SVM 准确率: {acc_scaled:.4f}")
print(f"提升: {acc_scaled - acc_unscaled:+.4f}")
print("\n注意:SVM 基于距离计算,必须进行特征标准化!")
============================================================
数据标准化的重要性
============================================================
原始特征统计:
rsi: 均值=52.9140, 标准差=17.0086
macd: 均值=0.0608, 标准差=0.2631
macd_signal: 均值=0.0580, 标准差=0.2433
ma_ratio: 均值=0.0062, 标准差=0.0404
volatility: 均值=0.0189, 标准差=0.0089
volume_ratio: 均值=1.0127, 标准差=0.4035
momentum_1: 均值=0.0010, 标准差=0.0210
momentum_2: 均值=0.0010, 标准差=0.0210
momentum_3: 均值=0.0010, 标准差=0.0210
momentum_5: 均值=0.0010, 标准差=0.0210
momentum_10: 均值=0.0010, 标准差=0.0208
标准化后特征统计:
rsi: 均值=-0.0000, 标准差=1.0000
macd: 均值=0.0000, 标准差=1.0000
macd_signal: 均值=0.0000, 标准差=1.0000
ma_ratio: 均值=0.0000, 标准差=1.0000
volatility: 均值=0.0000, 标准差=1.0000
volume_ratio: 均值=-0.0000, 标准差=1.0000
momentum_1: 均值=-0.0000, 标准差=1.0000
momentum_2: 均值=0.0000, 标准差=1.0000
momentum_3: 均值=0.0000, 标准差=1.0000
momentum_5: 均值=0.0000, 标准差=1.0000
momentum_10: 均值=-0.0000, 标准差=1.0000
未标准化 SVM 准确率: 0.4212
标准化后 SVM 准确率: 0.4837
提升: +0.0625
注意:SVM 基于距离计算,必须进行特征标准化!
5. 线性核 vs RBF 核对比
python
def compare_linear_rbf(X_train, X_test, y_train, y_test):
"""对比线性核和 RBF 核的性能"""
# 线性核 SVM
svm_linear = SVC(kernel='linear', C=1.0, probability=True, random_state=42)
svm_linear.fit(X_train, y_train)
# RBF 核 SVM
svm_rbf = SVC(kernel='rbf', C=1.0, gamma='scale', probability=True, random_state=42)
svm_rbf.fit(X_train, y_train)
# 评估
models = {'线性核': svm_linear, 'RBF核': svm_rbf}
print("="*60)
print("线性核 vs RBF 核对比")
print("="*60)
print(f"\n{'模型':<12} {'准确率':<10} {'精确率':<10} {'召回率':<10} {'F1':<10} {'AUC':<10}")
print("-"*62)
results = {}
for name, model in models.items():
y_pred = model.predict(X_test)
y_proba = model.predict_proba(X_test)[:, 1]
acc = accuracy_score(y_test, y_pred)
prec = precision_score(y_test, y_pred)
rec = recall_score(y_test, y_pred)
f1 = f1_score(y_test, y_pred)
auc = roc_auc_score(y_test, y_proba)
results[name] = (y_proba, auc)
print(f"{name:<12} {acc:<10.4f} {prec:<10.4f} {rec:<10.4f} {f1:<10.4f} {auc:<10.4f}")
# ROC 曲线
plt.figure(figsize=(10, 8))
for name, (y_proba, auc) in results.items():
fpr, tpr, _ = roc_curve(y_test, y_proba)
plt.plot(fpr, tpr, linewidth=2, label=f'{name} (AUC={auc:.4f})')
plt.plot([0, 1], [0, 1], 'k--', linewidth=1, label='随机分类器')
plt.xlabel('假阳性率 (FPR)')
plt.ylabel('真阳性率 (TPR)')
plt.title('线性核 vs RBF 核 ROC 曲线')
plt.legend(loc='lower right')
plt.grid(True, alpha=0.3)
plt.show()
return results
results_compare = compare_linear_rbf(X_train_scaled, X_test_scaled, y_train, y_test)
============================================================
线性核 vs RBF 核对比
============================================================
模型 准确率 精确率 召回率 F1 AUC
--------------------------------------------------------------
线性核 0.4402 0.4141 0.8241 0.5512 0.4766
RBF核 0.4837 0.4103 0.5440 0.4678 0.4739

6. 网格搜索调优 C 和 gamma
6.1 定义网格搜索函数
python
def grid_search_svm(X_train, y_train, X_test, y_test):
"""使用时间序列交叉验证进行网格搜索"""
# 参数网格(对数尺度)
param_grid = {
'C': [0.001, 0.01, 0.1, 1, 10, 100],
'gamma': [0.001, 0.01, 0.1, 1, 10],
'kernel': ['rbf']
}
# 时间序列交叉验证
tscv = TimeSeriesSplit(n_splits=3)
# SVM 模型
svm = SVC(probability=True, random_state=42)
# 网格搜索
grid_search = GridSearchCV(
svm, param_grid,
cv=tscv,
scoring='roc_auc',
n_jobs=-1,
verbose=1
)
print("开始网格搜索...")
start_time = time.time()
grid_search.fit(X_train, y_train)
elapsed = time.time() - start_time
print(f"\n搜索完成,耗时: {elapsed:.2f}秒")
print(f"搜索组合数: {len(grid_search.cv_results_['params'])}")
print(f"最佳参数: {grid_search.best_params_}")
print(f"最佳CV AUC: {grid_search.best_score_:.4f}")
# 测试集评估
y_pred_best = grid_search.predict(X_test)
y_proba_best = grid_search.predict_proba(X_test)[:, 1]
print(f"\n测试集准确率: {accuracy_score(y_test, y_pred_best):.4f}")
print(f"测试集AUC: {roc_auc_score(y_test, y_proba_best):.4f}")
return grid_search
# 执行网格搜索(使用子集加速)
print("使用子集进行网格搜索(2000样本)...")
X_sample = X_train_scaled[:2000]
y_sample = y_train[:2000]
grid_search = grid_search_svm(X_sample, y_sample, X_test_scaled, y_test)
使用子集进行网格搜索(2000样本)...
开始网格搜索...
Fitting 3 folds for each of 30 candidates, totalling 90 fits
搜索完成,耗时: 10.19秒
搜索组合数: 30
最佳参数: {'C': 0.001, 'gamma': 0.01, 'kernel': 'rbf'}
最佳CV AUC: 0.5213
测试集准确率: 0.4171
测试集AUC: 0.4756
6.2 可视化网格搜索结果
python
def visualize_grid_search_results(grid_search):
"""可视化网格搜索结果的热力图"""
results = grid_search.cv_results_
# 提取参数和分数
C_values = sorted(set(results['param_C'].data))
gamma_values = sorted(set(results['param_gamma'].data))
# 创建分数矩阵
score_matrix = np.zeros((len(C_values), len(gamma_values)))
for i, C in enumerate(C_values):
for j, gamma in enumerate(gamma_values):
mask = (results['param_C'].data == C) & (results['param_gamma'].data == gamma)
if np.any(mask):
score_matrix[i, j] = results['mean_test_score'][mask][0]
# 绘制热力图
plt.figure(figsize=(10, 8))
sns.heatmap(score_matrix,
xticklabels=[f'{g:.3f}' for g in gamma_values],
yticklabels=[f'{c:.3f}' for c in C_values],
annot=True, fmt='.4f', cmap='RdYlGn', center=0.5)
plt.xlabel('gamma')
plt.ylabel('C')
plt.title('网格搜索结果热力图(CV AUC)')
plt.tight_layout()
plt.show()
# 最佳参数位置
best_idx = grid_search.best_index_
best_C = grid_search.best_params_['C']
best_gamma = grid_search.best_params_['gamma']
best_score = grid_search.best_score_
print(f"最佳参数: C={best_C}, gamma={best_gamma}")
print(f"最佳CV AUC: {best_score:.4f}")
visualize_grid_search_results(grid_search)

最佳参数: C=0.001, gamma=0.01
最佳CV AUC: 0.5213
6.3 不同 C 和 gamma 组合的决策边界
python
def visualize_c_gamma_decision_boundary():
"""可视化不同 C 和 gamma 组合的决策边界"""
# 生成简单的非线性数据
from sklearn.datasets import make_moons
X, y = make_moons(n_samples=200, noise=0.1, random_state=42)
X_scaled = StandardScaler().fit_transform(X)
# 参数组合
C_values = [0.1, 1, 10]
gamma_values = [0.1, 1, 10]
fig, axes = plt.subplots(3, 3, figsize=(12, 12))
for i, C in enumerate(C_values):
for j, gamma in enumerate(gamma_values):
svm = SVC(kernel='rbf', C=C, gamma=gamma)
svm.fit(X_scaled, y)
# 创建网格
x_min, x_max = X_scaled[:, 0].min() - 0.5, X_scaled[:, 0].max() + 0.5
y_min, y_max = X_scaled[:, 1].min() - 0.5, X_scaled[:, 1].max() + 0.5
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02),
np.arange(y_min, y_max, 0.02))
Z = svm.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
axes[i, j].contourf(xx, yy, Z, alpha=0.3, cmap='RdYlBu')
axes[i, j].scatter(X_scaled[y == 0, 0], X_scaled[y == 0, 1],
c='blue', s=30, alpha=0.6)
axes[i, j].scatter(X_scaled[y == 1, 0], X_scaled[y == 1, 1],
c='red', s=30, alpha=0.6)
axes[i, j].set_title(f'C={C}, gamma={gamma}')
axes[i, j].set_xlabel('特征1')
axes[i, j].set_ylabel('特征2')
axes[i, j].grid(True, alpha=0.3)
plt.suptitle('C 和 gamma 对决策边界的影响', fontsize=14)
plt.tight_layout()
plt.show()
print("观察结论:")
print("- 左上角(C小, gamma小):决策边界平滑,可能欠拟合")
print("- 右下角(C大, gamma大):决策边界复杂,可能过拟合")
print("- 对角线附近(C=1, gamma=1):通常效果较好")
visualize_c_gamma_decision_boundary()

观察结论:
- 左上角(C小, gamma小):决策边界平滑,可能欠拟合
- 右下角(C大, gamma大):决策边界复杂,可能过拟合
- 对角线附近(C=1, gamma=1):通常效果较好
7. 调参前后性能对比
python
# 默认参数模型
svm_default = SVC(kernel='rbf', C=1.0, gamma='scale', probability=True, random_state=42)
svm_default.fit(X_train_scaled, y_train)
y_pred_default = svm_default.predict(X_test_scaled)
y_proba_default = svm_default.predict_proba(X_test_scaled)[:, 1]
# 最佳参数模型
svm_best = grid_search.best_estimator_
y_pred_best = svm_best.predict(X_test_scaled)
y_proba_best = svm_best.predict_proba(X_test_scaled)[:, 1]
print("="*60)
print("调参前后性能对比")
print("="*60)
print(f"\n{'指标':<12} {'默认参数':<12} {'调优后':<12} {'提升':<12}")
print("-"*48)
for metric_name, default_val, best_val in [
('准确率', accuracy_score(y_test, y_pred_default), accuracy_score(y_test, y_pred_best)),
('AUC', roc_auc_score(y_test, y_proba_default), roc_auc_score(y_test, y_proba_best))
]:
improvement = best_val - default_val
print(f"{metric_name:<12} {default_val:<12.4f} {best_val:<12.4f} {improvement:+.4f}")
# ROC 曲线对比
plt.figure(figsize=(10, 8))
fpr_default, tpr_default, _ = roc_curve(y_test, y_proba_default)
fpr_best, tpr_best, _ = roc_curve(y_test, y_proba_best)
plt.plot(fpr_default, tpr_default, 'b-', linewidth=2,
label=f'默认参数 (AUC={roc_auc_score(y_test, y_proba_default):.4f})')
plt.plot(fpr_best, tpr_best, 'r-', linewidth=2,
label=f'调优后 (AUC={roc_auc_score(y_test, y_proba_best):.4f})')
plt.plot([0, 1], [0, 1], 'k--', linewidth=1, label='随机分类器')
plt.xlabel('假阳性率 (FPR)')
plt.ylabel('真阳性率 (TPR)')
plt.title('SVM 调参前后 ROC 曲线对比')
plt.legend(loc='lower right')
plt.grid(True, alpha=0.3)
plt.show()
============================================================
调参前后性能对比
============================================================
指标 默认参数 调优后 提升
------------------------------------------------
准确率 0.4837 0.4171 -0.0666
AUC 0.4739 0.4756 +0.0017

8. SVM vs 随机森林对比
python
# 随机森林
rf = RandomForestClassifier(n_estimators=100, max_depth=10, random_state=42)
rf.fit(X_train_scaled, y_train)
rf_proba = rf.predict_proba(X_test_scaled)[:, 1]
# 最佳 SVM
svm_proba = svm_best.predict_proba(X_test_scaled)[:, 1]
print("="*60)
print("SVM vs 随机森林对比")
print("="*60)
print(f"\n{'模型':<12} {'准确率':<10} {'精确率':<10} {'召回率':<10} {'F1':<10} {'AUC':<10}")
print("-"*62)
for name, proba in [('SVM', svm_proba), ('随机森林', rf_proba)]:
pred = (proba > 0.5).astype(int)
print(f"{name:<12} {accuracy_score(y_test, pred):<10.4f} "
f"{precision_score(y_test, pred):<10.4f} {recall_score(y_test, pred):<10.4f} "
f"{f1_score(y_test, pred):<10.4f} {roc_auc_score(y_test, proba):<10.4f}")
# ROC 曲线
plt.figure(figsize=(10, 8))
fpr_svm, tpr_svm, _ = roc_curve(y_test, svm_proba)
fpr_rf, tpr_rf, _ = roc_curve(y_test, rf_proba)
plt.plot(fpr_svm, tpr_svm, 'b-', linewidth=2,
label=f'SVM (AUC={roc_auc_score(y_test, svm_proba):.4f})')
plt.plot(fpr_rf, tpr_rf, 'r-', linewidth=2,
label=f'随机森林 (AUC={roc_auc_score(y_test, rf_proba):.4f})')
plt.plot([0, 1], [0, 1], 'k--', linewidth=1, label='随机分类器')
plt.xlabel('假阳性率 (FPR)')
plt.ylabel('真阳性率 (TPR)')
plt.title('SVM vs 随机森林 ROC 曲线')
plt.legend(loc='lower right')
plt.grid(True, alpha=0.3)
plt.show()
============================================================
SVM vs 随机森林对比
============================================================
模型 准确率 精确率 召回率 F1 AUC
--------------------------------------------------------------
SVM 0.4402 0.4026 0.7068 0.5130 0.4756
随机森林 0.4769 0.4005 0.5114 0.4492 0.4849

9. 今日总结
-
SVM 核心原理:
- 最大间隔超平面(硬间隔)
- 松弛变量 ξ(软间隔)
- C 参数:控制间隔大小与分类错误的权衡
-
核函数:
- 线性核:适合线性可分数据
- RBF 核:最常用,能处理非线性问题
- gamma 参数:控制单个样本的影响范围
-
关键参数:
- C:惩罚系数(大→复杂,小→平滑)
- gamma:RBF 核参数(大→过拟合,小→欠拟合)
-
预处理要求:
- 必须进行特征标准化(StandardScaler)
- 对异常值相对鲁棒
-
量化交易应用:
- 适合中小规模数据集
- 需要仔细调参(C, gamma)
- RBF 核通常优于线性核
- 可与随机森林等模型集成
-
扩展作业
- 作业1:尝试不同的核函数(poly, sigmoid),对比性能
- 作业2:实现 SVM 的概率校准(CalibratedClassifierCV)
- 作业3:使用 SVM 做多分类预测(如涨/跌/平)
- 作业4:在实际股票数据上测试 SVM 策略(使用 yfinance)
- 作业5:对比 SVM 与 XGBoost 在不同数据规模下的表现
-
量化思考
- SVM 对特征缩放敏感,标准化是必需步骤
- RBF 核通常优于线性核
- 使用时间序列交叉验证避免前视偏差
- 样本量大时训练较慢,可考虑子采样