day11 python超参数调整
- 模型组成:模型 = 算法 + 实例化设置的外参(超参数)+ 训练得到的内参
- 调参评估:调参通常需要进行两次评估。若不使用交叉验证,需手动划分验证集和测试集;但许多调参方法自带交叉验证功能,实际中可省略该步骤
- 超参数学习:每个模型都有对应超参数且各具意义。若仅为追求精度或完成科研,可直接使用调参工具,无需深入研究每个超参数原理
一、数据预处理
首先执行数据预处理代码,为后续模型训练做准备:
import pandas as pd # 用于数据处理和分析,支持表格数据操作
import numpy as np # 用于高效的数值计算,提供数组操作功能
import matplotlib.pyplot as plt # 用于绘制各类图表
import seaborn as sns # 基于matplotlib的高级绘图库,生成美观的统计图形# 设置中文字体,解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei'] # Windows系统常用黑体字体
plt.rcParams['axes.unicode_minus'] = False # 确保负号正常显示
data = pd.read_csv('data.csv') # 读取数据# 筛选字符串类型变量
discrete_features = data.select_dtypes(include=['object']).columns.tolist()# 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 独热编码,并将bool类型转换为数值
data = pd.get_dummies(data, columns=['Purpose'])
data2 = pd.read_csv("data.csv") # 重新读取原始数据,用于列名对比
list_final = [] # 新建空列表,存储独热编码后新增的特征名
for i in data.columns:if i not in data2.columns:list_final.append(i) # 保存独热编码后的特征名
for i in list_final:data[i] = data[i].astype(int) # 将独热编码后的特征转换为整数类型# Term 0 - 1 映射
term_mapping = {'Short Term': 0,'Long Term': 1
}
data['Term'] = data['Term'].map(term_mapping)
data.rename(columns={'Term': 'Long Term'}, inplace=True) # 重命名列# 筛选连续型特征列名
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) # 用众数填充缺失值,直接修改原数据
二、数据集划分
2.1 两次划分数据集(演示)
由于调参需两次评估,这里演示如何两次划分数据集(因相关函数一次只能划分一次):
from sklearn.model_selection import train_test_split
X = data.drop(['Credit Default'], axis=1) # 提取特征,按列删除指定列
y = data['Credit Default'] # 提取标签# 第一次划分:80%训练集,20%临时集
X_train, X_temp, y_train, y_temp = train_test_split(X, y, test_size=0.2, random_state=42)# 第二次划分:临时集再分为50%验证集和50%测试集
X_val, X_test, y_val, y_test = train_test_split(X_temp, y_temp, test_size=0.5, random_state=42)print("Data shapes:")
print("X_train:", X_train.shape)
print("y_train:", y_train.shape)
print("X_val:", X_val.shape)
print("y_val:", y_val.shape)
print("X_test:", X_test.shape)
print("y_test:", y_test.shape)
运行结果:
Data shapes:
X_train: (6000, 31)
y_train: (6000,)
X_val: (750, 31)
y_val: (750,)
X_test: (750, 31)
y_test: (750,)
2.2 一次划分数据集(实际使用)
考虑到多数调参函数自带交叉验证,实际中常进行一次划分(8:2 划分训练集和测试集):
from sklearn.model_selection import train_test_split
X = data.drop(['Credit Default'], axis=1) # 提取特征
y = data['Credit Default'] # 提取标签# 80%训练集,20%测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
三、超参数调整方法概述
常见的超参数调整方法包括随机搜索、网格搜索和贝叶斯优化。在正式调参前,先构建基线模型(使用默认参数的 RandomForestClassifier
),作为性能对比基准。
3.1 网格搜索 (GridSearchCV)
- 原理:定义参数网格(
param_grid
),穷举所有参数组合进行评估 - 缺点:计算成本高,参数组合随参数数量呈指数级增长,通常需缩小搜索范围或结合预筛选
3.2 随机搜索 (RandomizedSearchCV)
- 原理:定义参数分布,在指定次数内随机采样评估
- 优势:在高维参数空间中效率更高,较少迭代次数即可找到较优参数
3.3 贝叶斯优化 (BayesSearchCV from skopt)
- 原理:基于历史评估结果构建概率模型(如高斯过程),预测最优参数组合
- 优势:尤其适用于模型训练耗时场景,能用更少迭代次数达到更好性能
选择建议:计算资源充足选网格搜索;资源有限选贝叶斯优化;随机搜索应用相对较少。
四、模型训练与调参实战
4.1 默认参数的随机森林(基线模型)
import time
print("--- 1. 默认参数随机森林 (训练集 -> 测试集) ---")
start_time = time.time() # 记录开始时间from sklearn.ensemble import RandomForestClassifier # 导入随机森林分类器
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score # 评估指标
from sklearn.metrics import classification_report, confusion_matrix # 生成分类报告和混淆矩阵
import warnings # 用于忽略警告信息
warnings.filterwarnings("ignore") # 忽略所有警告rf_model = RandomForestClassifier(random_state=42)
rf_model.fit(X_train, y_train) # 在训练集上训练
rf_pred = rf_model.predict(X_test) # 在测试集上预测end_time = time.time() # 记录结束时间
print(f"训练与预测耗时: {end_time - start_time:.4f} 秒")
print("\n默认随机森林 在测试集上的分类报告:")
print(classification_report(y_test, rf_pred))
print("默认随机森林 在测试集上的混淆矩阵:")
print(confusion_matrix(y_test, rf_pred))
运行结果:
--- 1. 默认参数随机森林 (训练集 -> 测试集) ---
训练与预测耗时: 0.9972 秒默认随机森林 在测试集上的分类报告:precision recall f1-score support0 0.77 0.97 0.86 10591 0.79 0.30 0.43 441accuracy 0.77 1500macro avg 0.78 0.63 0.64 1500
weighted avg 0.77 0.77 0.73 1500默认随机森林 在测试集上的混淆矩阵:
[[1023 36][ 309 132]]
4.2 网格搜索优化随机森林
print("\n--- 2. 网格搜索优化随机森林 (训练集 -> 测试集) ---")
from sklearn.model_selection import GridSearchCV# 定义参数搜索网格
param_grid = {'n_estimators': [50, 100, 200],'max_depth': [None, 10, 20, 30],'min_samples_split': [2, 5, 10],'min_samples_leaf': [1, 2, 4]
}# 创建网格搜索对象
grid_search = GridSearchCV(estimator=RandomForestClassifier(random_state=42), # 随机森林分类器param_grid=param_grid, # 参数网格cv=5, # 5折交叉验证n_jobs=-1, # 使用所有可用CPU核心并行计算scoring='accuracy' # 以准确率为评分标准
)start_time = time.time()
grid_search.fit(X_train, y_train) # 在训练集上执行网格搜索
end_time = time.time()print(f"网格搜索耗时: {end_time - start_time:.4f} 秒")
print("最佳参数: ", grid_search.best_params_) # 获取最佳参数组合# 使用最佳参数模型进行预测
best_model = grid_search.best_estimator_ # 获取最佳模型
best_pred = best_model.predict(X_test) # 在测试集上预测print("\n网格搜索优化后的随机森林 在测试集上的分类报告:")
print(classification_report(y_test, best_pred))
print("网格搜索优化后的随机森林 在测试集上的混淆矩阵:")
print(confusion_matrix(y_test, best_pred))
运行结果:
--- 2. 网格搜索优化随机森林 (训练集 -> 测试集) ---
网格搜索耗时: 56.7938 秒
最佳参数: {'max_depth': 20, 'min_samples_leaf': 1, 'min_samples_split': 2, 'n_estimators': 200}网格搜索优化后的随机森林 在测试集上的分类报告:precision recall f1-score support0 0.76 0.97 0.86 10591 0.80 0.28 0.42 441accuracy 0.77 1500macro avg 0.78 0.63 0.64 1500
weighted avg 0.77 0.77 0.73 1500网格搜索优化后的随机森林 在测试集上的混淆矩阵:
[[1028 31][ 317 124]]
4.3 贝叶斯优化随机森林
print("\n--- 3. 贝叶斯优化随机森林 (训练集 -> 测试集) ---")
from skopt import BayesSearchCV
from skopt.space import Integer
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, confusion_matrix
import time# 定义参数搜索空间
search_space = {'n_estimators': Integer(50, 200),'max_depth': Integer(10, 30),'min_samples_split': Integer(2, 10),'min_samples_leaf': Integer(1, 4)
}# 创建贝叶斯优化搜索对象
bayes_search = BayesSearchCV(estimator=RandomForestClassifier(random_state=42),search_spaces=search_space,n_iter=32, # 迭代次数cv=5, # 5折交叉验证n_jobs=-1,scoring='accuracy'
)start_time = time.time()
bayes_search.fit(X_train, y_train) # 在训练集上执行贝叶斯优化
end_time = time.time()print(f"贝叶斯优化耗时: {end_time - start_time:.4f} 秒")
print("最佳参数: ", bayes_search.best_params_)# 使用最佳参数模型进行预测
best_model = bayes_search.best_estimator_
best_pred = best_model.predict(X_test)print("\n贝叶斯优化后的随机森林 在测试集上的分类报告:")
print(classification_report(y_test, best_pred))
print("贝叶斯优化后的随机森林 在测试集上的混淆矩阵:")
print(confusion_matrix(y_test, best_pred))
运行结果:
--- 3. 贝叶斯优化随机森林 (训练集 -> 测试集) ---
贝叶斯优化耗时: 43.6849 秒
最佳参数: OrderedDict([('max_depth', 21), ('min_samples_leaf', 3), ('min_samples_split', 4), ('n_estimators', 85)])贝叶斯优化后的随机森林 在测试集上的分类报告:precision recall f1-score support0 0.76 0.97 0.85 10591 0.78 0.27 0.40 441accuracy 0.76 1500macro avg 0.77 0.62 0.63 1500
weighted avg 0.77 0.76 0.72 1500贝叶斯优化后的随机森林 在测试集上的混淆矩阵:
[[1026 33][ 321 120]]
4.4 另一种贝叶斯优化实现(拓展)
此方法可自定义目标函数、灵活控制交叉验证,通过 verbose
参数输出中间过程:
print("\n--- 3. 贝叶斯优化随机森林 (训练集 -> 测试集) ---")
from bayes_opt import BayesianOptimization
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
from sklearn.metrics import classification_report, confusion_matrix
import time
import numpy as np# 假设 X_train, y_train, X_test, y_test 已定义# 定义目标函数,使用交叉验证评估模型性能
def rf_eval(n_estimators, max_depth, min_samples_split, min_samples_leaf):n_estimators = int(n_estimators)max_depth = int(max_depth)min_samples_split = int(min_samples_split)min_samples_leaf = int(min_samples_leaf)model = RandomForestClassifier(n_estimators=n_estimators,max_depth=max_depth,min_samples_split=min_samples_split,min_samples_leaf=min_samples_leaf,random_state=42)scores = cross_val_score(model, X_train, y_train, cv=5, scoring='accuracy')return np.mean(scores)# 定义参数搜索空间
pbounds_rf = {'n_estimators': (50, 200),'max_depth': (10, 30),'min_samples_split': (2, 10),'min_samples_leaf': (1, 4)
}# 创建贝叶斯优化对象,设置verbose=2显示详细迭代信息
optimizer_rf = BayesianOptimization(f=rf_eval, # 目标函数pbounds=pbounds_rf, # 参数空间random_state=42, # 随机种子verbose=2 # 显示详细迭代信息
)start_time = time.time()
# 开始贝叶斯优化
optimizer_rf.maximize(init_points=5, # 初始随机采样点数n_iter=32 # 迭代次数
)
end_time = time.time()print(f"贝叶斯优化耗时: {end_time - start_time:.4f} 秒")
print("最佳参数: ", optimizer_rf.max['params'])# 使用最佳参数的模型进行预测
best_params = optimizer_rf.max['params']
best_model = RandomForestClassifier(n_estimators=int(best_params['n_estimators']),max_depth=int(best_params['max_depth']),min_samples_split=int(best_params['min_samples_split']),min_samples_leaf=int(best_params['min_samples_leaf']),random_state=42
)
best_model.fit(X_train, y_train)
best_pred = best_model.predict(X_test)print("\n贝叶斯优化后的随机森林 在测试集上的分类报告:")
print(classification_report(y_test, best_pred))
print("贝叶斯优化后的随机森林 在测试集上的混淆矩阵:")
print(confusion_matrix(y_test, best_pred))
通过实践掌握了随机森林模型超参数调整方法,网格搜索虽能找到较优解但计算耗时久,随机搜索效率高但稳定性不足,贝叶斯优化结合历史数据预测最优参数组合,在效率与效果间取得平衡,同时也意识到数据预处理和数据集划分对模型性能的重要影响。
@浙大疏锦行