【机器学习】如何使用Bayes_opt、HyperOpt、Optuna优化网格搜索?如何使用贝叶斯搜索调参?

本章内容:

  1. 如何使用Bayes_opt实现参数优化,及案例?
  2. 如何使用HyperOpt实现参数优化,及案例?
  3. 如何使用Optuna实现参数优化,及案例?
HPO库优劣评价推荐指数
bayes_opt✅实现基于高斯过程的贝叶斯优化 ✅当参数空间由大量连续型参数构成时⛔包含大量离散型参数时避免使用⛔算力/时间稀缺时避免使用⭐⭐
hyperopt✅实现基于TPE的贝叶斯优化✅支持各类提效工具✅进度条清晰,展示美观,较少怪异警告或报错✅可推广/拓展至深度学习领域⛔不支持基于高斯过程的贝叶斯优化⛔代码限制多、较为复杂,灵活性较差⭐⭐⭐⭐
optuna✅(可能需结合其他库)实现基于各类算法的贝叶斯优化✅代码最简洁,同时具备一定的灵活性✅可推广/拓展至深度学习领域⛔非关键性功能维护不佳,有怪异警告与报错⭐⭐⭐⭐

📖 以上三个库都不支持基于Python环境的并行或加速,大多数优化算法库只能够支持基于数据库(如MangoDB,mySQL)的并行或加速,但以上库都可以被部署在分布式计算平台。


关于贝叶斯参数优化实现方式,需要了解的几点:

  1. 贝叶斯优化需要自定义目标函数、参数空间、优化器,通常不直接调库;
  2. 不同的贝叶斯方式下,定义目标函数、参数空间、优化器的规则不同,各有自己的规则

基于以上两点,下面介绍三种HPO库时,主要内容为①介绍自定义的规则,②案例的整个流程;

另外以下案例中,弱评估器均采用随机森林。

1 基于Bayes_opt实现GP优化

📖 使用Bayes_opt时,通常为以下情况:

  1. 当且仅当必须要实现基于高斯过程的贝叶斯优化时;
  2. 算法的参数空间中有大量连续型参数;

因为bayes_opt对参数空间的处理方法较原始,缺乏相应的提升/监控供销,对算力的要求较高,往往不是调参的第一选择


📖 bayes_opt特点

  1. 运行时间(越小越好):bayes_opt<随机网格搜索<网格搜索
  2. 模型效果(越大越好):bayes_opt>随机网格搜索>网格搜索
  3. 优化过程无法复现,但优化结果可以复现
  4. 效率不足。
    实际上在迭代到170次时,贝叶斯优化就已经找到了最小损失,但由于没有提前停止机制,模型还持续地迭代了130次才停下,如果bayes_opt支持提前停止机制,贝叶斯优化所需的实际迭代时间可能会更少。
    同时,由于Bayes_opt只能够在参数空间提取浮点数,bayes_opt在随机森林上的搜索效率是较低的,即便在10次不同的迭代中分别取到了[88.89, 88.23……]等值,在取整后只能获得一个备选值88,但bayes_opt无法辨别这种区别,因此可能取出了众多无效的观测点。如果使用其他贝叶斯优化器,贝叶斯优化的效率将会更高。
  5. 支持灵活修改采集函数与高斯过程中的种种参数,详细介绍可参考:https://github.com/fmfn/BayesianOptimization/blob/master/examples/advanced-tour.ipynb

1.1 📖 bayes_opt对目标函数的规则

  1. 目标函数的输入必须是具体的超参数,而不能是整个超参数空间,更不能是数据、算法等超参数以外的元素。因此在定义目标函数时,我们需要让超参数作为目标函数的输入。

    示例:括号内必须是弱评估器的超参数

    def bayesopt_objective(n_estimators,max_depth):
    
  2. 超参数的输入值只能是浮点数,不支持整数与字符串。因此当算法的实际参数需要输入字符串时,该参数不能使用bayes_opt进行调整,当算法的实际参数需要输入整数时,则需要在目标函数中规定参数的类型。

    示例:括号里的超参数只能是浮点数:比如随机森林的参数criterion输入内容为‘gini’,这就是字符串,所以criterion这个参数不能放到里面。

    def bayesopt_objective(n_estimators,max_depth):
    

    示例:整数参数需要在设定时改为浮点数格式,如红色部分所示:树的棵数只能是整数,所以需要在设定时使用int()改为浮点数。

    def bayesopt_objective(n_estimators):
            	model=RFR(n_estimators=int(n_estimators)
    
  3. bayes_opt只支持寻找𝑓(𝑥)的最大值,不支持寻找最小值。因此当定义的目标函数是某种损失时,目标函数的输出需要取负(即,如果使用RMSE,则应该让目标函数输出负RMSE,这样最大化负RMSE后,才是最小化真正的RMSE。)当定义的目标函数是准确率,或者auc等指标,则可以让目标函数的输出保持原样。


1.2 📖 bayes_opt对参数空间的规则

  1. 必须使用字典方式来定义参数空间,其中键为参数名称,值为参数的取值范围;
  2. 只支持填写参数空间的上界与下界,不支持填写步长等参数,且为双向闭区间;
  3. 会将所有参数都当作连续型超参进行处理,因此bayes_opt会直接取出闭区间中任意浮点数作为备选参数(这也是设定目标函数时,为什么要int()的原因)。

由于以上规则,输入bayes_opt的参数空间天生会比其他贝叶斯优化库更大/更密,因此需要的迭代次数也更多。


1.3 📖 bayes_opt的随机性注意事项

  1. 随机性无法控制。即使填写随机数种子,优化算法每次运行一定都会不一样,即优化算法无法被复现。
  2. 最佳超参数的结果可以被复现。
    取出最佳参数组合以及最佳分数,该最佳参数组合被输入到交叉验证中后,是一定可以复现其最佳分数的。
    如果没能复现最佳分数,则是交叉验证过程的随机数种子设置存在问题,或者优化算法的迭代流程存在问题。

1.4 🗣 案例:bayes_opt参数优化_房价数据集_python

# pip install bayesian-optimization
from bayes_opt import BayesianOptimization
from sklearn.ensemble import RandomForestRegressor as RFR
from sklearn.model_selection import KFold,cross_validate
  1. 🗣 自定义随机森林模型和交叉验证模型,返回测试集的根均方误差
# 自定义随机森林模型和交叉验证模型,返回测试集的根均方误差

def bayesopt_objective(n_estimators,max_depth,max_features,min_impurity_decrease):
    model=RFR(n_estimators=int(n_estimators)
            ,max_depth=int(max_depth)
            ,max_features=int(max_features)
            ,min_impurity_decrease=min_impurity_decrease
            ,random_state=7
            ,n_jobs=-1
           )
    cv=KFold(n_splits=5,shuffle=True,random_state=7)
    validation_loss=cross_validate(model
                                   ,X,y
                                   ,scoring='neg_root_mean_squared_error'
                                   ,cv=cv
                                   ,n_jobs=-1
                                   ,error_score='raise' # 出错时报错,但不停止,如果设置为nan,将停止迭代
                                  )
    return np.mean(validation_loss['test_score'])

  1. 🗣 自定义优化器
# 自定义优化器
def param_bayes_opt(init_points,n_iter):
    opt=BayesianOptimization(bayesopt_objective
                             ,param_grid_simple
                             ,random_state=7)
    
    # 使用优化器
    opt.maximize(init_points=init_points # 抽取多少个初始观测值
                 ,n_iter=n_iter # 总共观测/迭代次数
                )
    
    # 返回优化结果
    params_best=opt.max['params'] # 返回最佳参数
    score_best=opt.max['target'] # 返回最佳分数
    
    # 打印结果
    print("\n","best params: ", params_best,
          "\n","best cvscore: ", score_best)
    
    return params_best,score_best

  1. 🗣 自定义最优参数验证
# 自定义验证函数,返回bayes_opt最优参数的RMSE

def bayes_opt_validation(params_best):
    model=RFR(n_estimators=int(params_best['n_estimators'])
            ,max_depth=int(params_best['max_depth'])
            ,max_features=int(params_best['max_features'])
            ,min_impurity_decrease=int(params_best['min_impurity_decrease'])
            ,random_state=7
            ,n_jobs=-1
           )
    cv=KFold(n_splits=5,shuffle=True,random_state=7)
    validation_loss=cross_validate(model
                                   ,X,y
                                   ,scoring='neg_root_mean_squared_error'
                                   ,cv=cv
                                   ,n_jobs=-1
                                  )
    return np.mean(validation_loss['test_score'])

  1. 🗣 运行
data=pd.read_csv(r'C:\Users\EDZ\test\ML-2 courseware\Lesson 9.随机森林模型\datasets\House Price\train_encode.csv',index_col=0)
X=data.iloc[:,:-1]
y=data.iloc[:,-1]

param_grid_simple={'n_estimators':(80,100)
                  ,'max_depth':(15,25)
                  ,'max_features':(10,20)
                  ,'min_impurity_decrease':(20,24)
                  }

params_best,score_best=param_bayes_opt(20,280)

params_best # 打印最优参数组合
score_best # 打印最优参数评分
validation_score=bayes_opt_validation(params_best) # 参数组合验证
validation_score # day


2 基于HyperOpt实现TPE优化

📖 HyperOpt特点

  1. 最通用优化器;
  2. 运行时间(越小越好):HyperOpt<bayes_opt<随机网格搜索<网格搜索
  3. 模型效果(越大越好):HyperOpt>bayes_opt>随机网格搜索>网格搜索;
  4. 代码精密度要求较高、灵活性较差,略微的改动就可能让代码疯狂报错难以跑通。
  5. 相比基于高斯过程的贝叶斯优化,基于高斯混合模型的TPE在大多数情况下以更高效率获得更优结果;
  6. HyperOpt所支持的优化算法也不够多。如果专注地使用TPE方法,则掌握HyperOpt即可,更深入可接触Optuna库。

2.1 📖 HyperOpt对目标函数的规则

  • 目标函数的参数空间输入必须是符合hyperopt规定的字典
  • Hyperopt只支持寻找𝑓(𝑥)的最小值,不支持寻找最大值

2.2 📖 HyperOpt对参数空间的规则

📖 HyperOpt定义参数空间,有如下几种字典形式

  • hp.quniform(“参数名称”, 下界, 上界, 步长) - 适用于均匀分布的浮点数
  • hp.uniform(“参数名称”,下界, 上界) - 适用于随机分布的浮点数
  • hp.randint(“参数名称”,上界) - 适用于[0,上界)的整数,区间为前闭后开
  • hp.choice(“参数名称”,[“字符串1”,“字符串2”,…]) - 适用于字符串类型,最优参数由索引表示
  • hp.choice(“参数名称”,[*range(下界,上界,步长)]) - 适用于整数型,最优参数由索引表示
  • hp.choice(“参数名称”,[整数1,整数2,整数3,…]) - 适用于整数型,最优参数由索引表示
  • hp.choice(“参数名称”,[“字符串1”,整数1,…]) - 适用于字符与整数混合,最优参数由索引表示

如无特殊说明,hp中的参数空间定义方法应当都为前闭后开区间。


HyperOpt定义参数空间,选择字典形式的思路:

  • 对于需要取整数的参数值,采用quniform方式构筑参数空间。
    • quniform能获得均匀分布的浮点数来替代整数;
    • 需要在目标函数中使用int函数限定输入类型。例如,在范围[0,5]中取值时,可以取出[0.0, 1.0, 2.0, 3.0,…]这种均匀浮点数,在输入目标函数时,则必须确保参数值前存在int函数。如果使用hp.choice则不会存在该问题。
  • hp.choice最终会返回最优参数的索引,容易与数值型参数的具体值混淆,用于字符串;
  • hp.randint只能够支持从0开始进行计数。

2.3 📖HyperOpt的优化器介绍

📖 HyperOpt优化目标函数时,涉及的功能/库

  • fmin:用于优化的基础功能
    • 在fmin中,我们可以自定义使用的代理模型(参数algo),一般来说我们有tpe.suggest以及rand.suggest两种选项,前者指代TPE方法,后者指代随机网格搜索方法。
  • partial:修改算法涉及到的具体参数
    • 包括模型具体使用了多少个初始观测值(参数n_start_jobs),以及在计算采集函数值时究竟考虑多少个样本(参数n_EI_candidates)。
  • trials:记录整个迭代过程
    • 一般输入从hyperopt库中导入的方法Trials()
    • 当优化完成之后,可以从保存好的trials中查看损失、参数等各种中间信息;
  • early_stop_fn:提前停止
    • 一般输入从hyperopt库导入的方法no_progress_loss()
    • 这个方法中可以输入具体的数字n,表示当损失连续n次没有下降时,让算法提前停止。
    • 由于贝叶斯方法的随机性较高,当样本量不足时需要多次迭代才能够找到最优解,因此一般no_progress_loss()中的数值不会设置得太高。在我们的课程中,由于数据量较少,我设置了一个较高的值来避免迭代停止太早。

2.4 🗣 案例:HyperOpt参数优化_房价数据集_python

# pip install hyperopt
# pip install optuna

import optuna
print(optuna.__version__)

import hyperopt
print(hyperopt.__version__)

from sklearn.ensemble import RandomForestRegressor as RFR
from sklearn.model_selection import KFold,cross_validate
from bayes_opt import BayesianOptimization
from hyperopt import hp,fmin,tpe,Trials,partial
from hyperopt.early_stop import no_progress_loss

  1. 🗣 设定参数空间
	# 设定参数空间
param_grid_simple={'n_estimators':hp.quniform('n_estimators',80,100,1)
                   ,'max_depth':hp.quniform('max_depth',10,25,1)
                   ,'max_features':hp.quniform('max_features',10,20,1)
                   ,'min_impurity_decrease':hp.quniform('min_impurity_decrease',20,25,1)
                  }

# 计算参数空间的大小
len([*range(80,100,1)])*len([*range(10,25,1)])*\
len([*range(10,20,1)])*len([range(20,25,1)])

  1. 🗣 设定目标函数
# 设定目标函数_基评估器选择随机森林

def hyperopt_objective(params):
    model=RFR(n_estimators=int(params['n_estimators'])
              ,max_depth=int(params['max_depth'])
              ,max_features=int(params['max_features'])
              ,min_impurity_decrease=params['min_impurity_decrease']
              ,random_state=7
              ,n_jobs=4)
    
    cv=KFold(n_splits=5,shuffle=True,random_state=7)
    validate_loss=cross_validate(model,X,y
                                   ,cv=cv
                                   ,scoring='neg_root_mean_squared_error'
                                   ,n_jobs=-1
                                   ,error_score='raise')
    
    return np.mean(abs(validate_loss['test_score']))

  1. 🗣 设定优化过程
# 设定优化过程

def param_hyperopt(max_evals=100):
    # 记录迭代过程
    trials=Trials()
    
    # 提前停止
    early_stop_fn=no_progress_loss(100) # 当损失函数的连续迭代100次都没有下降时,则停止;正常10-50即可
    
    # 定义代理模型
    # algo=partial(tpe.suggest # 设置代理模型的算法
		#	,n_sratup_jobs=20 # 设置初始样本量
		#	,n_EI_candidates=50) # 设置使用多少样本点来计算采集函数
    params_best=fmin(hyperopt_objective # 设定目标函数
                     ,space=param_grid_simple # 设定参数空间
                     ,algo=tpe.suggest # 设定代理模型,如果需要自定义代理模型,使用前面algo=……的代码
                     ,max_evals=max_evals # 设定迭代次数
                     ,trials=trials 
                     ,early_stop_fn=early_stop_fn # 控制提前停止
                    )
    
    print('best parmas:',params_best)
    return params_best,trials

  1. 🗣 设定验证函数
# 设定验证函数(和设定目标函数的代码一致)
def hyperopt_validation(params):
    model=RFR(n_estimators=int(params['n_estimators'])
              ,max_depth=int(params['max_depth'])
              ,max_features=int(params['max_features'])
              ,min_impurity_decrease=params['min_impurity_decrease']
              ,random_state=7
              ,n_jobs=4)
        
    cv=KFold(n_splits=5,shuffle=True,random_state=7)
    validate_loss=cross_validate(model,X,y
                                   ,cv=cv
                                   ,scoring='neg_root_mean_squared_error'
                                   ,n_jobs=-1
                                  )
    
    return np.mean(abs(validate_loss['test_score']))

  1. 🗣 执行实际优化流程
# 执行实际优化流程

# 1. 计算1%空间时的优化过程,返回最佳参数组合和迭代过程
params_best,trials=param_hyperopt(30) 

#2. 计算3%空间时的优化过程,返回最佳参数组合和迭代过程
params_best, trials = param_hyperopt(100)

#3. 计算10%空间时的优化过程,返回最佳参数组合和迭代过程
params_best, trials = param_hyperopt(300)

# 根据最佳参数组合验证模型,返回RMSE
hyperopt_validation(params_best)

#打印所有搜索相关的记录
trials.trials[0]

#打印全部搜索的目标函数值
trials.losses()[:10]

3 基于Optuna实现多种贝叶斯优化

📖 Optuna的特点

  • Optuna的优势在于,可以无缝衔接到PyTorch、Tensorflow等深度学习框架上,也可以与sklearn的优化库scikit-optimize结合使用,因此Optuna可以被用于各种各样的优化场景。
  • 基于高斯过程的贝叶斯优化比基于TPE的贝叶斯优化运行更加缓慢。
  • 不支持提前停止;
  • Optuna可能存在抽样BUG,即持续抽到曾经被抽到过的参数组合,并显示警告,这时的迭代可能无效。可以考虑增大参数空间的范围或密度以消除该问题。

3.1 📖 Optuna对目标函数、参数空间的规则

  • 不需要将参数或参数空间输入目标函数,而是需要直接在目标函数中定义参数空间
  • Optuna优化器会生成一个指代备选参数的变量trial,该变量无法被用户获取或打开,但该变量在优化器中生存,并被输入目标函数。在目标函数中,我们可以通过变量trail所携带的方法来构造参数空间。
  • 既可以输出 f ( x ) f(x) f(x)的最大值,也可以输出最小值

3.2 📖 Optuna的优化器介绍

  • 调整参数algo来自定义用于执行贝叶斯优化的具体算法;
  • 设置样本抽样的算法为TPE,比GP(高斯)迭代速度更快。

3.3 🗣 案例:Optuna参数优化_房价数据集_python

# 准备数据及库

# pip install optuna
# pip install scikit-optimize

import optuna
optuna.__version__

data=pd.read_csv(r'E:\jupyter_notebook\机器学习二期课程课件\Lesson 9.随机森林模型\datasets\House Price\train_encode.csv',index_col=0)
X=data.iloc[:,:-1]
y=data.iloc[:,-1]
X.head()

from sklearn.ensemble import RandomForestRegressor as RFR
from sklearn.model_selection import KFold,cross_validate
# 定义目标函数

def optuna_objective(trial):
    n_estimators=trial.suggest_int('n_estimators',80,100,1) # 整数型:suggest_int('参数名称',下界,上界,步长) 
    max_depth=trial.suggest_int('max_depth',10,25,1)
    max_features=trial.suggest_int('max_features',10,20,1)
#     max_features=trial.suggest_categorical('max_features',['log2','sqrt','auto']) # 字符型
    min_impurity_decrease=trial.suggest_int('min_impurity_decrease',20,25,1)
#     min_impurity_decrease=trial.suggest_float('min_impurity_decrease',20,25,log=False) # 浮点型

    model=RFR(n_estimators=n_estimators
              ,max_depth=max_depth
              ,max_features=max_features
              ,min_impurity_decrease=min_impurity_decrease
              ,random_state=7
              ,n_jobs=12
             )
    
    cv=KFold(n_splits=5,shuffle=True,random_state=7)
    validate_loss=cross_validate(model,X,y
                                 ,cv=cv
                                 ,scoring='neg_root_mean_squared_error'
                                 ,n_jobs=12
                                 ,error_score='raise'
                                )
    
    return np.mean(abs(validate_loss['test_score']))
# 定义优化过程

def optimizer_optuna(n_trials,algo):
    if algo=='TPE':
        algo=optuna.samplers.TPESampler(n_strarup_trials=10,n_ei_candidates=24)
    elif algo=='GP':
        from optuna.integration import SkoptSampler
        import skopt
        algo=SkoptSampler(skopt_kwargs={'base_estimator':'GP'
                                        ,'n_initial_points':10
                                        ,'acq_func':'EI'
                                       }
                         )
        
    study=optuna.create_study(sampler=algo # 定义样本抽样的算法
                              ,direction='minimize' # 定义目标函数优化方向是最大值,还是最小值
                             )
    
    study.optimize(optuna_objective # 目标函数
                   ,n_trials=n_trials # 设定最大迭代次数(包括最初观测值)
                   ,show_progress_bar=True # 要不要展示进度条
                  )
    print('best parmas:',study.best_trial.params,
         '\n','best score:',study.best_trial.values)
    
    return study.best_trial.params,study.best_trial.values
# 执行流程
import warnings
warnings.filterwarnings('ignore',message='The objective has been evaluated at this point before.')

best_params,best_score=optimizer_optuna(10,'GP') # 小迭代次数代码测试

optuna.logging.set_verbosity(optuna.logging.ERROR) # 关闭打印迭代过程
best_params,best_score=optimizer_optuna(300,'GP')
  • 16
    点赞
  • 109
    收藏
    觉得还不错? 一键收藏
  • 23
    评论
评论 23
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值