DAY 12
浙大疏锦行
一、整体逻辑思路(分步拆解)
整个程序围绕“利用贝叶斯优化自动搜索 SVM 最佳超参数”这一目标展开,分为三个层次:
1. 数据准备与预处理
- 加载数据:从指定路径读取 CSV 文件(信用违约数据集)。
- 特征工程:
- 对分类变量(如
Home Ownership、Years in current job)进行标签编码(映射为数字)。 - 对
Purpose进行独热编码(One-Hot Encoding),生成多个二值特征。 - 对
Term进行 0/1 映射(Short Term → 0,Long Term → 1)。
- 对分类变量(如
- 缺失值处理:对所有数值型特征,用众数填补缺失值(
fillna(mode))。但为了保险,后续在 Pipeline 中再次用SimpleImputer确保无遗漏。 - 划分数据集:按 8:2 分割训练集和测试集(测试集最终用于评估,但本次优化仅使用训练集进行交叉验证)。
2. 贝叶斯优化核心流程
- 定义目标函数
svm_eval(C, gamma):- 构建一个Pipeline:
SimpleImputer(填补)→StandardScaler(标准化)→SVC(RBF 核)。 - 在训练集上进行5 折交叉验证,返回平均准确率作为模型性能指标。
- 构建一个Pipeline:
- 设定搜索空间:
C(正则化强度):0.1 ~ 100gamma(RBF 核参数):0.001 ~ 1
- 创建贝叶斯优化器(
BayesianOptimization):- 初始随机探索
init_points=10次(用于建立高斯过程先验)。 - 随后进行
n_iter=30次贝叶斯迭代(利用概率代理模型指导后续采样)。
- 初始随机探索
- 记录与计时:记录每次迭代的参数和得分,计算总耗时。
3. 结果可视化与分析
- 左图(收敛曲线):
- 蓝色点线:每次迭代的交叉验证准确率。
- 红色虚线:累计最优值(历史最佳),反映优化进程。
- 绿色水平线:最终找到的最优准确率。
- 右图(探索 vs 利用):
- 蓝色点:前 10 次随机探索的结果。
- 绿色点:后 30 次贝叶斯优化的结果。
- 红色竖线:划分探索与利用阶段的分界线。
- 统计输出:最低/最高/平均得分、标准差、提升幅度,以及最优参数组合。
二、核心知识点总结
1. 数据预处理与特征工程
| 知识点 | 本代码应用 |
|---|---|
| 标签编码 | 将有序/无序分类变量转换为整数(如Own Home→ 1) |
| 独热编码 | 将Purpose扩展为多个 0/1 特征,避免模型误认为类别间有大小关系 |
| 众数填补 | 用出现频率最高的值填充缺失数据,适用于分类或离散特征 |
| 标准化(StandardScaler) | 使特征均值为 0、方差为 1,SVM 对尺度敏感,这是必须的预处理 |
2. SVM(支持向量机)核心思想
- 目标:找到一个超平面,使两类样本之间的几何间隔最大化。
- 核技巧:通过
kernel='rbf'(高斯径向基核)将数据映射到高维空间,解决非线性分类问题。 - 关键参数:
C:惩罚系数,控制对误分类样本的容忍度。C越大,模型越偏向于拟合训练集(可能过拟合);C越小,更注重间隔宽度(可能欠拟合)。gamma:RBF 核的带宽,决定单个样本的影响力范围。gamma越大,决策边界越复杂(容易过拟合);gamma越小,决策边界更平滑(偏向欠拟合)。
- SVM 的优缺点:在小样本、高维数据上表现优异,但对缺失值敏感(本代码用
SimpleImputer解决),且计算复杂度随样本量增加而显著上升。
3. 贝叶斯优化(Bayesian Optimization)
- 与网格搜索/随机搜索的区别:
- 网格搜索:穷举所有组合,成本极高。
- 随机搜索:随机采样,无方向性。
- 贝叶斯优化:基于高斯过程建立目标函数的概率模型(代理模型),通过采集函数(如 UCB、EI)在“探索”(探索不确定区域)和“利用”(开发已知最优区域)之间平衡,高效找到最优参数。
- 实现库:
bayesian-optimization,它要求目标函数最大化某个指标(我们最大化准确率)。 - 两个阶段:
- 随机探索(
init_points):用少量随机采样初始化代理模型。 - 贝叶斯迭代(
n_iter):利用代理模型指导后续采样,逐步逼近全局最优。
- 随机探索(
4. Python 基础语法强化
| 语法特性 | 代码示例 |
|---|---|
| 元组(tuple) | 不可变有序容器,用作函数多返回值、字典键等 |
| 字典 items() 方法 | 返回(key, value)视图,可用于迭代解包 |
| enumerate() | 遍历可迭代对象时同时获取索引和元素 |
| 解包(Unpacking) | for param, (low, high) in pbounds.items()一次解包两层结构 |
| Pipeline | 将多个预处理步骤和模型串联,保证交叉验证时数据不泄露 |
| try/except(未显式使用,但可加入) | 可增强鲁棒性 |
5. 可视化与结果解读
- 收敛曲线:若累计最优曲线快速上升并趋于平稳,说明优化过程有效收敛。
- 探索 vs 利用:观察贝叶斯阶段是否比随机探索阶段获得更高准确率,体现贝叶斯优化的“智能”之处。
- 统计指标:标准差可反映结果的稳定性;提升幅度说明优化带来的增益。
三、SVM 模型思想深度解读(作业专用)
我选择的模型:SVM(支持向量机)
核心思想:SVM 寻找一个最优超平面,使得不同类别样本之间的间隔(Margin)最大化。对于线性不可分数据,通过核函数将原始特征映射到高维空间,在高维空间中寻找线性分隔超平面。
与随机森林的对比:随机森林是集成学习(Bagging),通过多棵树投票降低方差;SVM 是几何模型,基于结构风险最小化,更注重分类边界的稳定性。
为何选择 SVM 进行贝叶斯优化:SVM 的性能强烈依赖C和gamma,且两者相互制约,传统网格搜索成本高,贝叶斯优化能高效找到二者的平衡点。
从可视化中得到的启示:左图显示,经过约 15 次迭代后,累计最优值已接近最终最优,说明贝叶斯优化能快速锁定优质参数区域,大幅减少调参时间。
四、代码中容易被忽略的关键细节
- Pipeline 中的
SimpleImputer:即便数据预处理已做填补,在交叉验证中仍使用SimpleImputer,确保每一折的训练集和验证集均无缺失值,且填补参数仅从训练集学习,避免数据泄露。 - 标准化必须在交叉验证内部进行:如果先对整个数据集标准化再划分,会引入未来信息。本代码通过 Pipeline 保证每一折都独立标准化。
- 参数空间的选择:
C和gamma通常使用对数尺度(如 0.01~100),但为了演示线性范围也能看到效果。实际应用中,建议使用(0.01, 100)并用log-uniform采样(本例未做,但可以修改)。 - 迭代次数调整:为了兼顾运行速度,本例仅用 40 次迭代(10+30),若电脑性能允许,可增大至 100 次以获得更优结果。
五、运行结果分析(以你成功运行为例)
- 观察左图:若累计最优曲线呈现“阶梯上升”状,说明优化器在不断发现更好的参数。
- 观察右图:绿色点(贝叶斯阶段)普遍高于蓝色点(随机探索),证明贝叶斯优化确实在“利用”历史信息进行智能采样。
- 输出统计信息中的“得分提升” = 最高得分 - 第一次迭代得分,反映优化效益。
- 最终打印的最优参数组合,可用于后续在测试集上评估模型泛化能力(该步骤未在代码中实现,可自行添加)。
六、延伸思考(与课程笔记呼应)
你的笔记中提到:“决策树是预测模型,但优化问题与预测问题的边界越来越模糊”。本例正是通过优化超参数来提升预测性能,将调参本身转化成一个优化问题,体现了这种趋势。同时,贝叶斯优化不仅可用于 SVM,也可用于深度学习、强化学习等场景,是一种通用的自动化调参工具。
完整代码如下:
DAY12 贝叶斯优化可视化 —— SVM 版本(完整可运行)
作者:疏锦行(按作业要求将模型改为 SVM)
数据路径:C:\Python Study\Python60DaysChallenge-main\data.csv
==================== 一、元组类型(保留教学) ====================
old_tuple = (“张三”, 25, 92.5)
print(f"原始元组: {old_tuple}“)
print(f"原始类型: {type(old_tuple)}”)
temp_list = list(old_tuple)
temp_list[1] = 26
new_tuple = tuple(temp_list)
print(f"新元组: {new_tuple}\n")
==================== 二、字典的items方法(保留教学) ====================
pbounds_demo = {‘n_estimators’: (10, 3000), ‘max_depth’: (3, 500)}
for param, (low, high) in pbounds_demo.items():
print(f"参数: {param} , 搜索范围: [{low}, {high}]")
my_dict = {‘A’: 10, ‘B’: 20}
for index, (key, value) in enumerate(my_dict.items()):
print(f"索引: {index}, 键: {key}, 值: {value}“)
print(”\n" + “=”*60 + “\n”)
==================== 三、贝叶斯优化(SVM) ====================
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings(‘ignore’)
设置中文字体
plt.rcParams[‘font.sans-serif’] = [‘SimHei’]
plt.rcParams[‘axes.unicode_minus’] = False
----- 读取数据(修改为你的真实路径)-----
file_path = r"C:\Python Study\Python60DaysChallenge-main\data.csv"
data = pd.read_csv(file_path)
print(f"数据形状: {data.shape}")
print(data.head())
========== 数据预处理(完全复制你的原始流程)==========
Home Ownership 标签编码
home_ownership_mapping = {
‘Own Home’: 1,
‘Rent’: 2,
‘Have Mortgage’: 3,
‘Home Mortgage’: 4
}
data[‘Home Ownership’] = data[‘Home Ownership’].map(home_ownership_mapping)
Years in current job 标签编码
years_in_job_mapping = {
‘< 1 year’: 1, ‘1 year’: 2, ‘2 years’: 3, ‘3 years’: 4, ‘4 years’: 5,
‘5 years’: 6, ‘6 years’: 7, ‘7 years’: 8, ‘8 years’: 9, ‘9 years’: 10, ‘10+ years’: 11
}
data[‘Years in current job’] = data[‘Years in current job’].map(years_in_job_mapping)
Purpose 独热编码
data = pd.get_dummies(data, columns=[‘Purpose’])
处理新增的独热编码列(转为int)
data2 = pd.read_csv(file_path) # 重新读原始数据用于列差集
list_final = [i for i in data.columns if i not in data2.columns]
for i in list_final:
data[i] = data[i].astype(int)
Term 映射
term_mapping = {‘Short Term’: 0, ‘Long Term’: 1}
data[‘Term’] = data[‘Term’].map(term_mapping)
data.rename(columns={‘Term’: ‘Long Term’}, inplace=True)
连续特征用众数补全(但可能仍有残留NaN,后续在Pipeline中再次确保)
continuous_features = data.select_dtypes(include=[‘int64’, ‘float64’]).columns.tolist()
for feature in continuous_features:
mode_value = data[feature].mode()[0]
data[feature].fillna(mode_value, inplace=True)
print(“✅ 数据预处理完成!”)
print(f"最终特征数量: {data.shape[1]}")
划分训练集和测试集
from sklearn.model_selection import train_test_split
X = data.drop([‘Credit Default’], axis=1)
y = data[‘Credit Default’]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
print(f"训练集大小: {X_train.shape}“)
print(f"测试集大小: {X_test.shape}”)
========== 引入 SVM 及必要的预处理(加入缺失值填补) ==========
from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.model_selection import cross_val_score
from bayes_opt import BayesianOptimization
import time
定义目标函数:使用 Pipeline 包含填补、标准化、SVM
def svm_eval(C, gamma):
“”"
SVM 目标函数,最大化交叉验证准确率
C: 正则化参数(惩罚系数)
gamma: RBF核的带宽参数
“”"
# 创建包含填补、标准化和SVM的Pipeline
pipeline = Pipeline([
(‘imputer’, SimpleImputer(strategy=‘most_frequent’)), # 众数填补缺失值
(‘scaler’, StandardScaler()),
(‘svm’, SVC(kernel=‘rbf’, C=C, gamma=gamma, random_state=42))
])
# 5折交叉验证
scores = cross_val_score(pipeline, X_train, y_train, cv=5, scoring=‘accuracy’)
return np.mean(scores)
定义SVM超参数搜索空间(常用范围)
pbounds = {
‘C’: (0.1, 100), # 对数尺度常用,但为了演示用线性范围
‘gamma’: (0.001, 1) # 同样线性范围
}
print(“\nSVM 参数搜索范围:”)
for param, (low, high) in pbounds.items():
print(f" {param}: [{low}, {high}]")
创建贝叶斯优化器
optimizer = BayesianOptimization(
f=svm_eval,
pbounds=pbounds,
random_state=42,
verbose=2
)
start_time = time.time()
运行优化(迭代次数可调,此处使用 10次随机探索 + 30次贝叶斯优化)
optimizer.maximize(
init_points=10,
n_iter=30
)
end_time = time.time()
print(f"优化完成!总耗时: {end_time - start_time:.2f} 秒".center(80))
========== 可视化==========
iterations = []
scores = []
for i, res in enumerate(optimizer.res):
iterations.append(i + 1)
scores.append(res[‘target’])
best_scores = []
current_best = -np.inf
for score in scores:
if score > current_best:
current_best = score
best_scores.append(current_best)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 5))
左图
ax1.plot(iterations, scores, ‘o-’, label=‘每次迭代得分’, alpha=0.7, markersize=6)
ax1.plot(iterations, best_scores, ‘r–’, label=‘累计最优得分’, linewidth=2)
ax1.axhline(y=optimizer.max[‘target’], color=‘green’, linestyle=‘:’,
label=f’最终最优: {optimizer.max[“target”]:.4f}')
ax1.set_xlabel(‘迭代次数’, fontsize=12)
ax1.set_ylabel(‘准确率’, fontsize=12)
ax1.set_title(‘SVM 贝叶斯优化收敛曲线’, fontsize=14, fontweight=‘bold’)
ax1.legend()
ax1.grid(True, alpha=0.3)
右图(随机探索 vs 贝叶斯优化)
init_points = 10 # 与上方的 init_points 保持一致
ax2.plot(iterations[:init_points], scores[:init_points], ‘bo-’,
label=f’随机探索 (前{init_points}次)‘, markersize=8, alpha=0.7)
ax2.plot(iterations[init_points:], scores[init_points:], ‘go-’,
label=f’贝叶斯优化 (后{len(iterations)-init_points}次)’, markersize=8, alpha=0.7)
ax2.axvline(x=init_points, color=‘red’, linestyle=‘–’, alpha=0.5, label=‘探索→利用’)
ax2.set_xlabel(‘迭代次数’, fontsize=12)
ax2.set_ylabel(‘准确率’, fontsize=12)
ax2.set_title(‘探索阶段 vs 利用阶段 (SVM)’, fontsize=14, fontweight=‘bold’)
ax2.legend()
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
输出统计信息
print(f"\n总迭代次数: {len(scores)}“)
print(f"最低得分: {min(scores):.4f}”)
print(f"最高得分: {max(scores):.4f}“)
print(f"平均得分: {np.mean(scores):.4f}”)
print(f"得分标准差: {np.std(scores):.4f}“)
print(f"得分提升: {max(scores) - scores[0]:.4f}”)
print(“\n最优参数组合:”, optimizer.max[‘params’])
