Day 11 超参数调整——网格搜索、随机搜索、贝叶斯优化

@浙大疏锦行

今日任务:

  1. 网格搜索
  2. 随机搜索(简单介绍,非重点 实战中很少用到,可以不了解)
  3. 贝叶斯优化(2种实现逻辑,以及如何避开必须用交叉验证的问题)
  4. time库的计时模块,方便后人查看代码运行时长
  5. 对于信贷数据的其他模型,如LightGBM和KNN 尝试用贝叶斯优化和网格搜索调参

在完成了简单的建模和评估后,知道了模型的表现如何。但可能效果不理想(如过拟合或欠拟合),在更换模型前,可以考虑先调参

一、数据读取与预处理

使用信贷数据集,读取数据:

import pandas as pd
#读取数据
data = pd.read_csv(r'data.csv')
data.info()

对数据进行简单预处理(数据类型转换、缺失值处理):

#数据预处理
#数据类型转换
discrete_features = data.select_dtypes(include=['object']).columns.tolist()
#标签编码
mapping_dict = {
    'Home Ownership':{
        'Own Home': 0,
        'Rent': 1,
        'Have Mortgage': 2,
        'Home Mortgage': 3
    },
    'Years in current job':{
        '< 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
    },
    'Term':{
        'Short Term':0,
        "Long Term":1
    }
}
for key,value in mapping_dict.items():
    data[key] = data[key].map(mapping_dict[key])

#独热编码
data = pd.get_dummies(data=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) # 这里的i就是独热编码后的特征名

data.dtypes

#缺失值补全
continous_features = data.select_dtypes(include=['float64','int64']).columns.tolist()
for col in continous_features:
    mode_filled = data[col].mode()[0]
    data[col] = data[col].fillna(mode_filled)
data.isnull().sum()

二、数据集的划分

在调参的过程中,模型在间接地学习数据集的特征来调整超参数,如果只进行一次划分,那么最终得到的参数也只是碰巧在测试集表现很好,偶然性太大。故而在调参时,需要“考”2次,划分训练集、验证集和测试集。

  • 训练集:用于训练模型。模型在这里学习数据中的内在规律和模式。
  • 验证集:用于调参和模型选择。你在训练集上训练出多个不同参数或不同结构的模型,然后在验证集上评估它们,根据验证集的表现来选择最好的那一个。
  • 测试集:用于最终评估。在你确定最终模型(包括结构和所有超参数)后,有且仅有一次地用测试集来评估它的性能,这个性能才被认为是模型泛化能力的真实反映。
#数据集的划分
from sklearn.model_selection import train_test_split
#标签与特征地确定
X = data.drop(['Credit Default'],axis=1)
y = data['Credit Default']

#划分两次数据集,得到训练集、验证集、训练集,比例为8:1:1
X_train,X_temp,y_train,y_temp = train_test_split(X,y,train_size=0.8,random_state=42)
X_val,X_test,y_val,y_test = train_test_split(X_temp,y_temp,train_size=0.5,random_state=42)

但实际上,也可以选择使用‘交叉验证’的方法来提高模型的说服力,这个时候不需要划分验证集。常用的是K折交叉验证:将数据集均分为k份,每次取1份作为测试集,k-1份作为训练集,然后评估,共循环k次。最终取分数的平均值,作为模型的最终估计。

在很多调参方法中都默认有交叉验证(cross_val),实际中可只划分一次数据集:

#数据集的划分
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,train_size=0.7,random_state=42)

三、调参

调参能够为特定的数据集和任务,找到一组能使机器学习模型性能最佳最稳定超参数组合。

通过之前的学习,知道了模型与算法的区别,具体而言:模型 = 算法 + 实例化设置的外参 + 训练得到的内参。换言之,算法就像是一个通用的公式,而模型则是将具体数值代入公式后得到的结果。此外,在调参的过程中,要注意区分内参和外参,后者即超参数,是可以调整的参数:

  • 参数:是模型内部根据数据自动学习得到的变量。例如,线性回归中的权重系数和偏置项,神经网络中的节点权重。我们不需要手动设置它们。
  • 超参数:是模型外部的配置,在训练开始之前就必须由开发者手动设定。它们控制着模型的训练过程和行为。如学习率、树的最大深度等。

另外,在调参之前,需要确定一个基线模型,作为后续优化的比较基准:

  • 未经任何优化、使用最常规设置(默认参数)的、能直接运行的模型。
  • 设立一个科学、量化的“起点”,避免在后续工作中盲目优化,并展示出每一步工作的价值。

此处使用默认参数的RandomForestClassifier ():

#选择基线模型
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") # 忽略所有警告信息

import time #引入时间库,用于查看运行时间
print('默认随机森林训练结果:')
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('默认RF的训练时间:{:.2f}s'.format(end_time-start_time))
print('RF 分类报告:')
print(classification_report(y_true=y_test,y_pred=rf_pred))
print('RF 混淆矩阵:')
print(confusion_matrix(y_test,rf_pred))

3.1 网格搜索(GridSearchCV)

指定一个超参数网格(param_grid),穷举所有可能的组合。效果可靠,但计算成本极高。因此,网格通常设置得比较小或集中在认为最优参数可能存在的区域(可能基于随机搜索的初步结果),适合在计算资源够用的情况。

使用网格搜索进行超参数调参的步骤:

  • 定义参数网格param_grid:包含所有你想要尝试的特定值的列表,离散的;常见参数有n_estimators,max_depth,min_samples_split,min_samples_leaf
  • 创建网格搜索对象:GridSearchCV();estimator,para_grid,cv,n_jobs,scoring参数含义看注释
  • 训练集上进行网格搜索:grid_search.fit(X_train, y_train)
  • 查看最佳参数(best_params_)、最佳模型(best_estimator_)
  • 预测,查看评估指标
#网格搜索法调参
from sklearn.model_selection import GridSearchCV
start_time = time.time()
#定义参数网格
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, #K折交叉验证
    n_jobs=-1, #可用的CPU核心,-1为全选
    scoring='accuracy' #评分标准:准确率
)
#训练集上进行网格搜索
grid_search.fit(X_train,y_train)

end_time = time.time()
print('网格搜索调参用时:{:.2f}s'.format(end_time-start_time))

print("最佳参数:",grid_search.best_params_) #best_params_获取最佳参数

best_rf_model = grid_search.best_estimator_ #获得最佳模型
best_rf_pred = best_rf_model.predict(X_test) #测试集上进行预测

#评估
print('RF 分类报告:')
print(classification_report(y_true=y_test,y_pred=best_rf_pred))
print('RF 混淆矩阵:')
print(confusion_matrix(y_test,best_rf_pred))

3.2 随机搜索(RandomizedSearchCV)

需要定义参数的分布,而不是固定的列表。在指定的参数空间中随机采样进行尝试,而不是尝试所有组合。对于给定的计算预算,随机搜索通常比网格搜索更有效,尤其是在高维参数空间中。

但是,随机搜索不常用。

3.3 贝叶斯优化(BayesSearchCV from skopt)

需要定义参数的搜索空间,根据之前的评估结果,建立概率模型来预测哪些超参数组合可能表现更好,并集中搜索这些区域。在寻找最优解方面通常比随机搜索更高效(用更少的迭代次数达到相似或更好的性能),特别是当模型训练(单次评估)非常耗时的时候,适合在计算资源不够用的情况

使用贝叶斯优化进行调参的步骤与网格搜索类似:

  • 定义要搜索的参数空间search_space,参数空间是连续的
  • 创建贝叶斯优化搜索对象BayesSearchCV(),参数与网格类似,多了一个迭代次数的调整
  • 训练集上进行贝叶斯优化搜索
  • 最佳参数best_params_,最佳模型best_estimator_
  • 预测,查看评估指标

#贝叶斯优化
from skopt import BayesSearchCV
from skopt.space import Integer

start_time = time.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, #交叉验证
    n_jobs=-1, #使用所有可用的CPU核心进行并行计算
    scoring='accuracy'#使用准确率作为评分标准
)
#训练集上进行网格搜索
bayes_search.fit(X_train,y_train)

end_time = time.time()
print('网格搜索调参用时:{:.2f}s'.format(end_time-start_time))

print("最佳参数:",bayes_search.best_params_)#best_params_属性返回最佳参数组合

best_rf_model = bayes_search.best_estimator_ #获取最佳模型
best_rf_pred = best_rf_model.predict(X_test) #在测试集上进行预测

#评估
print('RF 分类报告:')
print(classification_report(y_true=y_test,y_pred=best_rf_pred))
print('RF 混淆矩阵:')
print(confusion_matrix(y_test,best_rf_pred))

此处应该为贝叶斯优化:

3.4 贝叶斯优化的其它实现方法(了解)

下面介绍一种贝叶斯优化方法的其他实现代码,他的优势在于:

  • 可以自己定义目标函数,也可以借助他不使用交叉验证,因为评估指标修改为不是交叉验证的结果即可,更加自由
  • 有verbose参数,可以输出中间过程
# --- 2. 贝叶斯优化随机森林 ---
print("\n--- 2. 贝叶斯优化随机森林 (训练集 -> 测试集) ---")
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))

四、作业:对LightGBM模型进行调参

使用网格搜索和贝叶斯优化对LightGBM模型进行调参。

网格搜索

#LGBM网格搜索法调参
from lightgbm import LGBMClassifier 
from sklearn.model_selection import GridSearchCV
start_time = time.time()
#定义参数网格
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=LGBMClassifier(random_state=42),
    param_grid=param_grid,
    cv=5,
    n_jobs=-1,
    scoring='accuracy'
)
#训练集上进行网格搜索
grid_search.fit(X_train,y_train)

end_time = time.time()
print('网格搜索调参用时:{:.2f}s'.format(end_time-start_time))

print("最佳参数:",grid_search.best_params_)

best_rf_model = grid_search.best_estimator_
best_rf_pred = best_rf_model.predict(X_test)

#评估
print('RF 分类报告:')
print(classification_report(y_true=y_test,y_pred=best_rf_pred))
print('RF 混淆矩阵:')
print(confusion_matrix(y_test,best_rf_pred))

此处应为LGBM:

贝叶斯优化

#LGBM贝叶斯优化
from skopt import BayesSearchCV
from skopt.space import Integer

start_time = time.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=LGBMClassifier(random_state=42),
    search_spaces=search_space,
    n_iter=32,  # 迭代次数,可根据需要调整
    cv=5,
    n_jobs=-1,
    scoring='accuracy'
)
#训练集上进行网格搜索
bayes_search.fit(X_train,y_train)

end_time = time.time()
print('网格搜索调参用时:{:.2f}s'.format(end_time-start_time))

print("最佳参数:",bayes_search.best_params_)

best_rf_model = bayes_search.best_estimator_
best_rf_pred = best_rf_model.predict(X_test)

#评估
print('RF 分类报告:')
print(classification_report(y_true=y_test,y_pred=best_rf_pred))
print('RF 混淆矩阵:')
print(confusion_matrix(y_test,best_rf_pred))

此处应该为贝叶斯优化与LGBM:

根据对LGBM采用网格搜索和贝叶斯优化进行调参对比后发现,虽然两者整体思路类似,但是贝叶斯优化方法比网格搜索快了一倍,更加高效。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值