贝叶斯优化对lightGBM最优超参数进行估计
相对遗传算法和模拟退火来说,利用贝叶斯估计的算法对超参数调参有着明显的速度优势,因为是对历史运行参数进行高斯过程或者TEP的方法去估计,所以不需要在空间里反复随机的搜索,所以很快就能估计出一个比较不错的参数解。
但是相对前两种方法和粒子群优化之类的进化算法来说,贝叶斯估计容易受到历史参数统计的限制,难以像传统优化算法一样通过随机过程跳出局部空间。且对参数搜索空间的最优区间设置比较依赖,因为在有限的迭代此书中,设置的最优区间幅度会决定随机采样在全局最优解附近的概率,所以在逼近模型极限上往往不如传统的启发算法,但是这是不一定的。且贝叶斯优化的准确性依赖于采样的情况,所以也存在由于采样的分布距离最优参数太远而造成优化结果选择的参数远远偏离最优参数的情况。
在最优参数的产生上稳定性不如传统的启发算法。
import pandas as pd
import numpy as np
import lightgbm as lgb
from sko.tools import set_run_mode
from sklearn.model_selection import train_test_split
from sklearn import metrics
from log_color import log,LogLevel
from sklearn import metrics
import time
import datetime
from hyperopt import hp
from sklearn.model_selection import KFold, cross_validate
from hyperopt import hp, fmin, tpe, Trials, partial
from hyperopt.early_stop import no_progress_loss
from sklearn.metrics import classification_report
from matplotlib import pyplot as plt
train_df = pd.read_csv('./train_v2.csv', index_col=0)
test_df = pd.read_csv('./test_v2.csv', index_col=0)
print(train_df)
x = train_df.drop(['user_id','merchant_id','label'],axis=1)
y = train_df['label']
x_train, x_val, y_train, y_val = train_test_split(x, y, test_size=0.2, random_state = 42)
####TODO:自动计算alpha值的取值范围 取负例的比例
# train_Y = y_train
train_positive = (y_train==1).sum()
train_negative = (y_train==0).sum()
train_y_counter = y_train.size
alpha = train_negative/train_y_counter
log(f"""训练数据中,正例有【{train_positive}】个占比【{train_positive/train_y_counter}】
,负例有【{train_negative}】个占比【{train_negative/train_y_counter}】
,alpha值为【{alpha}】,""",LogLevel.INFO)
test_positive = (y_val==1).sum()
test_negative = (y_val==0).sum()
test_y_counter = y_val.size
log(f"""测试数据中,正例有【{test_positive}】个占比【{test_positive/test_y_counter }】
,负例有【{test_negative}】个占比【{test_negative/test_y_counter }】
,alpha值为【{test_negative/test_y_counter}】,""",LogLevel.INFO)
def plot_roc(y_test, y_score):
fpr,tpr,threshold = metrics.roc_curve(y_test, y_score)
roc_auc = metrics.auc(fpr,tpr)
plt.stackplot(fpr, tpr, color='steelblue', alpha = 0.5, edgecolor = 'black')
plt.plot(fpr, tpr, color='black', lw = 1)
plt.plot([0,1],[0,1], color = 'red', linestyle = '--')
plt.text(0.5,0.3,'ROC curve (area = %0.10f)' % roc_auc)
plt.xlabel('FPR')
plt.ylabel('TPR')
plt.title('ROC Curve')
plt.show()
def hyperopt_objective(hyperopt_params):
func_start = time.time()
params = {
'verbose':-1,
'min_data_in_leaf': int(hyperopt_params['min_data_in_leaf']),#一片叶子中的数据数量最少。可以用来处理过拟合注意:这是基于 Hessian 的近似值,因此有时您可能会观察到分裂产生的叶节点的观测值少于这么多
'objective': 'binary',
'metric': 'auc',
'num_leaves': int(hyperopt_params['num_leaves']), #一棵树的最大叶子数
"boosting": hyperopt_params["boosting"],#"gbdt",#gbdt 传统的梯度提升决策树,rf随机森林,dartDropout 遇到多个可加回归树,
'n_estimators':int(hyperopt_params['n_estimators']),#2000,#基学习器
"tree_learner":hyperopt_params["tree_learner"],#"feature",#serial:单机树学习器,feature:特征并行树学习器,data:数据并行树学习器,voting:投票并行树学习器
'max_bin': int(hyperopt_params['max_bin']), #直方图分箱特征值的将被存储的最大箱数,少量的 bin 可能会降低训练准确性,但可能会增加处理过度拟合的能力
"min_data_in_bin":int(hyperopt_params["min_data_in_bin"]), #一个 bin 内的数据最少数量.使用它可以避免一数据一箱(潜在的过度拟合)
'max_depth':int(hyperopt_params['max_depth']), #限制树模型的最大深度
#"min_data_in_leaf":int(hyperopt_params["min_data_in_leaf"]),#一片叶子中的数据数量最少。可以用来处理过拟合
"learning_rate": hyperopt_params["learning_rate"],#学习率
#"colsample_bytree": 0.8,
"bagging_fraction": hyperopt_params["bagging_fraction"], # 每次迭代时用的数据比例,但这将随机选择部分数据而不重新采样,可用于加速训练可以用来处理过拟合
"feature_fraction":hyperopt_params["feature_fraction"], # 每次迭代中随机选择特征的比例,lightGBM 将在每次迭代(树)上随机选择特征子集1.0。例如,如果将其设置为0.8,LightGBM 将在训练每棵树之前选择 80% 的特征
"lambda_l1":hyperopt_params["lambda_l1"], #L1正则化 0-正无穷
"lambda_l2":hyperopt_params["lambda_l2"],
'n_jobs': -1,
#'silent': 1, # 信息输出设置成1则没有信息输出
'seed': int(hyperopt_params['seed']),
'bagging_freq':int(hyperopt_params['bagging_freq']),#装袋频率,0表示禁用装袋;k意味着在每次迭代时执行装袋k。每次k迭代,LightGBM 都会随机选择用于下一次迭代的数据bagging_fraction * 100 %k
'is_unbalance':hyperopt_params['is_unbalance'], #是否为不平衡数据
"early_stopping_rounds":int(hyperopt_params["early_stopping_rounds"]),#早停法 如果一个验证数据的一个指标在最后几轮中没有改善,将停止训练
"device_type":"cpu"#"cuda"
#'scale_pos_weight': wt
} #设置出参数
log(f"本次参数为:[{params}]",LogLevel.INFO)
gbm = lgb.LGBMClassifier(**params)
### 样本极度不均衡,直接K折交叉验证很有可能出现数据正负类一致而引起的lightGMB抛出错误
# cv = KFold(n_splits=5,shuffle=True,random_state=1412)
# roc_auc = cross_validate(gbm,X,y
# ,scoring='roc_auc'
# ,cv=cv
# ,verbose=False
# ,n_jobs=-1
# ,error_score='raise'
# )
gbm.fit(x_train, y_train,
# verbose_eval=True ,
eval_metric='auc',
eval_set=[(x_train, y_train), (x_val, y_val)]
# ,early_stopping_rounds=30
)
gbm_pred = gbm.predict(x_val)
gbm_proba = gbm.predict_proba(x_val)[:,1]
fpr,tpr,threshold = metrics.roc_curve(y_val, gbm_proba)
roc_auc = metrics.auc(fpr,tpr)
func_end = time.time()
global NOW_FUC_RUN_ITER
NOW_FUC_RUN_ITER += 1
log(f"""本次迭代AUC分数为:[{roc_auc}],
用时:[{func_end-func_start}]秒,
当前优化第:[{NOW_FUC_RUN_ITER}]次,
已运行:[{NOW_FUC_RUN_ITER}]次,
用时总计:[{datetime.timedelta(seconds=(func_end-Bayes_start_time))}]秒,
""",LogLevel.PASS)
return -roc_auc
##TODO: 域空间,也就是给定超参数搜索的范围。这个可以通过 hyperopt 的 hp 方法实现,hp 方法有很多个参数选择的方式如
# hp.loguniform(对数空间搜索)
# 、hp.lognormal(对数正态分布)
# 、hp.normal(正态分布)
# 、hp.choice(列表选项搜索)
# 、hp.uniform(连续均匀分布搜索)
# 、hp.quniform(连续均匀分布整数搜索)
tree_learners = ["feature","serial","data","voting"]
boostings = ["gbdt","rf"]
is_unbalances = [True,False]
param_grid_hp = {
'min_data_in_leaf': hp.quniform('min_data_in_leaf',5,1000,1),
'num_leaves': hp.quniform("num_leaves",20,1000,1), #一棵树的最大叶子数
"boosting": hp.choice("boosting",boostings),#gbdt 传统的梯度提升决策树,rf随机森林,dart Dropout 遇到多个可加回归树,
'n_estimators':hp.quniform('n_estimators',80,2000,1),#基学习器
"tree_learner": hp.choice("tree_learner",tree_learners),#serial:单机树学习器,feature:特征并行树学习器,data:数据并行树学习器,voting:投票并行树学习器
'max_bin': hp.quniform("max_bin",20,1000,1),#50, #直方图分箱特征值的将被存储的最大箱数,少量的 bin 可能会降低训练准确性,但可能会增加处理过度拟合的能力
"min_data_in_bin":hp.quniform("min_data_in_bin",3,100,1),#一个 bin 内的数据最少数量,使用它可以避免一数据一箱(潜在的过度拟合)
'max_depth':hp.quniform('max_depth',5,100,1),#15, #限制树模型的最大深度
#"min_data_in_leaf":hp.quniform("min_data_in_leaf",3,1000,1),#100,#一片叶子中的数据数量最少。可以用来处理过拟合
"learning_rate": hp.uniform("learning_rate",0,1),#0.01,#学习率
"bagging_fraction":hp.uniform("bagging_fraction",0.01,1),# 0.8, # 每次迭代时用的数据比例,但这将随机选择部分数据而不重新采样,可用于加速训练可以用来处理过拟合
"feature_fraction":hp.uniform("feature_fraction",0.01,1),#0.8, # 每次迭代中随机选择特征的比例,lightGBM 将在每次迭代(树)上随机选择特征子集1.0。例如,如果将其设置为0.8,LightGBM 将在训练每棵树之前选择 80% 的特征
"lambda_l1":hp.randint("lambda_l1",1000),#20, #L1正则化 0-正无穷
"lambda_l2":hp.randint("lambda_l2",1000),#20 ,
'seed': hp.randint('seed',100),#42,
'bagging_freq':hp.quniform("bagging_freq",0,100,1),#3,#0表示禁用装袋;k意味着在每次迭代时执行装袋k。每次k迭代,LightGBM 都会随机选择用于下一次迭代的数据
'is_unbalance':hp.choice('is_unbalance',is_unbalances),#True, #是否为不平衡数据
"early_stopping_rounds":hp.quniform("early_stopping_rounds",1,1000,1),#30,#早停法 如果一个验证数据的一个指标在最后几轮中没有改善,将停止训练
} #设置出参数
def parsing_bayes_params_for_LightGBM(hyperopt_params):
"""
解析估计出的beyes最优参数
"""
boostings = ["gbdt","rf","dart"]
tree_learners = ["serial","feature","data","voting"]
is_unbalances = [True,False]
params = {
'verbose':-1,
'min_data_in_leaf': int(hyperopt_params['min_data_in_leaf']),#一片叶子中的数据数量最少。可以用来处理过拟合注意:这是基于 Hessian 的近似值,因此有时您可能会观察到分裂产生的叶节点的观测值少于这么多
'objective': 'binary',
'metric': 'auc',
'num_leaves': int(hyperopt_params['num_leaves']), #一棵树的最大叶子数
"boosting": boostings[hyperopt_params["boosting"]],#"gbdt",#gbdt 传统的梯度提升决策树,rf随机森林,dartDropout 遇到多个可加回归树,
'n_estimators':int(hyperopt_params['n_estimators']),#2000,#基学习器
"tree_learner":tree_learners[hyperopt_params["tree_learner"]],#"feature",#serial:单机树学习器,feature:特征并行树学习器,data:数据并行树学习器,voting:投票并行树学习器
'max_bin': int(hyperopt_params['max_bin']), #直方图分箱特征值的将被存储的最大箱数,少量的 bin 可能会降低训练准确性,但可能会增加处理过度拟合的能力
"min_data_in_bin":int(hyperopt_params["min_data_in_bin"]), #一个 bin 内的数据最少数量.使用它可以避免一数据一箱(潜在的过度拟合)
'max_depth':int(hyperopt_params['max_depth']), #限制树模型的最大深度
#"min_data_in_leaf":int(hyperopt_params["min_data_in_leaf"]),#一片叶子中的数据数量最少。可以用来处理过拟合
"learning_rate": hyperopt_params["learning_rate"],#学习率
#"colsample_bytree": 0.8,
"bagging_fraction": hyperopt_params["bagging_fraction"], # 每次迭代时用的数据比例,但这将随机选择部分数据而不重新采样,可用于加速训练可以用来处理过拟合
"feature_fraction":hyperopt_params["feature_fraction"], # 每次迭代中随机选择特征的比例,lightGBM 将在每次迭代(树)上随机选择特征子集1.0。例如,如果将其设置为0.8,LightGBM 将在训练每棵树之前选择 80% 的特征
"lambda_l1":hyperopt_params["lambda_l1"], #L1正则化 0-正无穷
"lambda_l2":hyperopt_params["lambda_l2"],
'n_jobs': -1,
#'silent': 1, # 信息输出设置成1则没有信息输出
'seed': int(hyperopt_params['seed']),
'bagging_freq':int(hyperopt_params['bagging_freq']),#装袋频率,0表示禁用装袋;k意味着在每次迭代时执行装袋k。每次k迭代,LightGBM 都会随机选择用于下一次迭代的数据bagging_fraction * 100 %k
'is_unbalance':is_unbalances[hyperopt_params['is_unbalance']], #是否为不平衡数据
"early_stopping_rounds":int(hyperopt_params["early_stopping_rounds"]),#早停法 如果一个验证数据的一个指标在最后几轮中没有改善,将停止训练
"device_type":"cpu"#"cuda"
#'scale_pos_weight': wt
} #设置出参数
return params
def param_hyperopt(max_evals=100):
#保存迭代过程
trials = Trials()
#设置提前停止
## 如果损失没有增加,将在 X 次迭代后停止的停止函数
early_stop_fn = no_progress_loss(100)
#定义代理模型
#algo = partial(tpe.suggest, n_startup_jobs=20, n_EI_candidates=50)
params_best = fmin(hyperopt_objective #目标函数
, space = param_grid_hp #参数空间
, algo = tpe.suggest #代理模型
#, algo = algo
, max_evals = max_evals #允许的迭代次数
, verbose=True
, trials = trials
, early_stop_fn = early_stop_fn
)
#打印最优参数,fmin会自动打印最佳分数
print("\n","\n","best params: ", params_best,
"\n")
return params_best, trials
t = 0
for params_name,obj in param_grid_hp.items():
t += 1
log(f"已准备优化的第{t}个参数,名称:{params_name},类型:{obj}",LogLevel.PASS)
Bayes_start_time = time.time()
NOW_FUC_RUN_ITER = 0
NUM_EVALS = 600
params_best, trials = param_hyperopt(NUM_EVALS)
log(f"所有搜索相关记录:{trials.trials[0]}",LogLevel.INFO)
bayes_opt_parser = parsing_bayes_params_for_LightGBM(params_best)
log(f"解析参数得到结果:{bayes_opt_parser}",LogLevel.PASS)
params = bayes_opt_parser
gbm = lgb.LGBMClassifier(**params)
gbm.fit(x_train, y_train,
# verbose_eval=True ,
eval_metric='auc',
eval_set=[(x_train, y_train), (x_val, y_val)]
# ,early_stopping_rounds=30
)
gbm_pred = gbm.predict(x_val)
gbm_proba = gbm.predict_proba(x_val)
print('模型的评估报告:\n',classification_report(y_val, gbm_pred))
plot_roc(y_val, gbm_proba[:,1])