今天开始超参数调整的专题,回顾之前课程的核心知识点:
- 模型= 算法 + 实例化设置的外参(超参数) + 训练得到的内参
- 只要调参就需要考2次
如果不做交叉验证,就需要划分验证集和测试集,但是很多调参方法中都默认有交叉验证,所以实际中可以省去划分验证集和测试集的步骤。
每个模型都有自己的超参数,每个超参数都有一定的意义。为了精度和科研只需要用好调参工具即可。
数据预处理
import pandas as pd # 用于数据处理和分析,可处理表格数据。
import matplotlib.pyplot as plt # 用于绘制各种类型的图表。
import seaborn as sns # 基于matplotlib的高级绘图库,绘制更美观的统计图
import numpy as np # 用于数值计算,提供高效数组操作
# 设置中文字体(解决中文显示问题)
plt.rcParams['font.sans-serif'] = ['SimHei'] # Windows系统常用黑体字体
plt.rcParams['axes.unicode_minus'] = False # 正常显示负号
# 读取数据
dt = pd.read_csv('data.csv')
# 定义嵌套映射字典,用于标签编码
mapping = {
'Home Ownership':{
'Home Mortgage': 3,
'Rent': 1,
'Own Home': 0,
'Have Mortgage': 2
},
'Years in current job':{
'< 1 year': 0,
'1 year': 1,
'2 years': 2,
'3 years': 3,
'4 years': 4,
'5 years': 5,
'6 years': 6,
'7 years': 7,
'8 years': 8,
'9 years': 9,
'10+ years': 10
},
'Term':{
'Short Term': 0,
'Long Term': 1
}
}
# map()方法进行映射
dt['Home Ownership'] = dt['Home Ownership'].map(mapping['Home Ownership'])
dt['Years in current job'] = dt['Years in current job'].map(mapping['Years in current job'])
# 对特征Purpose进行独热编码,get_dummies()
# pd.get_dummies(待处理数据集, columns= ['待处理列'])
dt = pd.get_dummies(dt, columns=['Purpose'])
# 接下来找到"独热编码"生成的新特征,将bool型转换为int型
data = pd.read_csv('data.csv')
for i in dt.columns:
if i not in data.columns:
dt[i] = dt[i].astype(int)
# Term 0-1映射
dt['Term'] = dt['Term'].map(mapping['Term'])
dt.rename(columns={'Term': 'Long Term'}, inplace=True) # 重命名列
# 填补缺失值
for i in dt.columns:
if dt[i].isnull().sum() > 0:
mode = dt[i].mode()[0] # 众数
# median = dt[i].median() # 中位数
dt[i].fillna(mode, inplace=True) # 缺失值填补
例:划分训练集、验证集和测试集
# 划分训练集、验证集和测试集,因为需要考2次
# 所以这里划分两次,因为这个函数只能划分一次所以需要调用两次才能划分成
from sklearn.model_selection import train_test_split
X = dt.drop(['Credit Default'], axis=1) # 特征
y = dt['Credit Default'] # 标签
# 按照8:1: 1划分训练集、验证集和测试集
X_train, X_temp, y_train, y_temp = train_test_split(X, y, test_size=0.2, random_state=42) # 80%训练集,20%临时集
X_val, X_test , y_val, y_test = train_test_split(X_temp, y_temp, test_size=0.5, random_state=42) # 50%验证集,50%测试集
并查看数据集形状:
# X_train, y_train
# X_val, y_val
# X_test, y_test
print("Data shapes:")
print(f"X_train:{X_train.shape}")
print(f"y_train:{y_train.shape}")
print(f"X_val:{X_val.shape}")
print(f"y_val{y_val.shape}")
print(f"X_test{X_test.shape}")
print(f"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,)
可以看出,训练集:验证集:测试集的大小是8:1:1
正片开始:
1、数据预处理(上边有)
最开始也说了 很多调参函数自带交叉验证,甚至是必选的参数,想要不交叉验证反而会麻烦许多,所以这里还是只划分一次数据集即可。
2、只划分训练集、测试集
from sklearn.model_selection import train_test_split
X = dt.drop(['Credit Default'], axis=1) # 特征,axis=1表示按列删除
y = dt['Credit Default'] # 标签
# 按照8:2划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 查看形状
print(X_train.shape)
print(X_test.shape)
输出:
(6000, 31)
(1500, 31)
3、训练和调参
简单的调参方法:
基线模型(基准模型):首先运行一个使用默认参数的RandomForestClassifier,记录其性能作为比较的基准。
1. 网格搜索(GridSearchCV)
- 需要定义参数的网格(param_grid),包含所有你想要尝试的特定值的列表。它会尝试网格中所有可能的参数组合。
- 缺点: 计算成本非常高,参数和值的数量稍多,组合数就会呈指数级增长(维度灾难)。因此,网格通常设置得比较小或集中在认为最优参数可能存在的区域(可能基于随机搜索的初步结果)。
2. 随机搜索 (RandomizedSearchCV):
- 需要定义参数的分布,而不是固定的列表。这是它与网格搜索的主要区别,它不会尝试所有组合,而是在指定次数内随机采样。通常,用相对较少的迭代次数(如 50-100)就能找到相当好的参数。
- 对于给定的计算预算,随机搜索通常比网格搜索更有效,尤其是在高维参数空间中。
3. 贝叶斯优化(BayesSearchCV from skopt):
- 需要定义参数的搜索空间,与随机搜索类似,当搜索空间非常大时,它通常比网格搜索和随机搜索更有效。
- 核心优势: 它不是随机选择下一个点,而是根据先前评估的结果建立一个概率模型(通常是高斯过程),预测哪些参数组合可能产生更好的结果,并据此选择下一个评估点。这使得它在寻找最优解方面通常比随机搜索更高效(用更少的迭代次数达到相似或更好的性能),特别是当模型训练(单次评估)非常耗时的时候。
正常情况下,计算资源够用网格,计算资源不够用贝叶斯优化。随机搜索没什么人用。
(1)默认参数随机森林
# ---1. 默认参数随机森林---
from sklearn.ensemble import RandomForestClassifier # 随机森林分类器
from sklearn.metrics import classification_report, confusion_matrix # 分类报告、混淆矩阵
# 评估基准模型,这里不需要验证集
print("---1. 默认参数随机森林(训练集->测试集)---")
import time # 介绍一个新的库,time库,主要用于时间相关的操作,因为调参需要很长时间,记录下会帮助后人知道大概时长
start_time = time.time() # 记录开始时间
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("\n默认随机森林 在测试集上的混淆矩阵:")
print(confusion_matrix(y_test, rf_pred))
输出:
---1. 默认参数随机森林(训练集->测试集)---
训练与预测耗时1.5417秒。
默认随机森林 在测试集上的分类报告:
precision recall f1-score support
0 0.77 0.97 0.86 1059
1 0.79 0.30 0.43 441
accuracy 0.77 1500
macro avg 0.78 0.63 0.64 1500
weighted avg 0.77 0.77 0.73 1500
默认随机森林 在测试集上的混淆矩阵:
[[1023 36]
[ 309 132]]
# 贝叶斯优化所需要安装scikit-optimize这个库
# pip install scikit-optimize -i https://pypi.tuna.tsinghua.edu.cn/simple
(2)网格搜索优化随机森林
# --- 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_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. 网格搜索优化随机森林(训练集 -> 测试集) ---
网格搜索耗时:46.3326秒
最佳参数: {'max_depth': 20, 'min_samples_leaf': 1, 'min_samples_split': 2, 'n_estimators': 200}
网格搜索优化后的随机森林 在测试集上的分类报告:
precision recall f1-score support
0 0.76 0.97 0.86 1059
1 0.80 0.28 0.42 441
accuracy 0.77 1500
macro avg 0.78 0.63 0.64 1500
weighted avg 0.77 0.77 0.73 1500
网格搜索优化后的随机森林 在测试集上的混淆矩阵:
[[1028 31]
[ 317 124]]
(3)贝叶斯优化随机森林
# --- 2. 贝叶斯优化随机森林 ---
print("\n--- 2. 贝叶斯优化随机森林 (训练集 -> 测试集) ---")
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_spaces = {
'n_estimators': Integer(50, 200),
'max_depth': Integer(10, 30), # 决策树最大深度范围10-30
'min_samples_split': Integer(2, 10), # 节点分裂最小样本数范围2-10
'min_samples_leaf': Integer(1, 4) # 叶节点最小样本数范围1-4
}
# 创建贝叶斯优化搜索对象
bayes_search = BayesSearchCV(
estimator=RandomForestClassifier(random_state=42),
search_spaces=search_spaces,
n_iter=32, # 迭代次数
cv=5, # 5折交叉验证,这个参数是必须的,不能设置为1,否则就是在训练集上做预测了
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_
bayes_pred = best_model.predict(X_test)
print("\n贝叶斯优化后的随机森林 在测试集上的分类报告:")
print(classification_report(y_test, bayes_pred))
print("\n贝叶斯优化后的随机森林 在测试集上的混淆矩阵:")
print(confusion_matrix(y_test, bayes_pred))
输出:
--- 2. 贝叶斯优化随机森林 (训练集 -> 测试集) ---
贝叶斯优化耗时:61.6268秒。
最佳参数: OrderedDict([('max_depth', 23), ('min_samples_leaf', 3), ('min_samples_split', 5), ('n_estimators', 125)])
贝叶斯优化后的随机森林 在测试集上的分类报告:
precision recall f1-score support
0 0.76 0.97 0.85 1059
1 0.80 0.27 0.40 441
accuracy 0.76 1500
macro avg 0.78 0.62 0.63 1500
weighted avg 0.77 0.76 0.72 1500
贝叶斯优化后的随机森林 在测试集上的混淆矩阵:
[[1029 30]
[ 324 117]]
注:Integer(10, 30)表示在[10 , 30]之间取值。