导读
在传统的算法建模过程中,影响算法性能的一个重要环节、也可能是最为耗时和无趣的一项工作就是算法的调参,即超参数优化(Hyper-parameter Optimization,HPO),因此很多算法工程师都会调侃的自称"调参侠"。近期在研究一些AutoML相关的论文和实现,而在AutoML中的一个核心组件就是HPO。借此机会,本文梳理总结Python中三种常见的可实现HPO的库,并提供一个简单的示例。
HPO,全称Hyper-parameter Optimization,即超参优化。之所以做这项工作是出于机器学习领域的两个基本事实:
正因如此,所以算法工程师们在提升算法性能时常常需要对比多个模型,同时在各模型内部又要调整多组超参,以期实现最优效果。在这个超参调优过程中,当前的实现方式主要是如下三种:
《Bayesian Optimization Primer》
其中,各符号及变量的含义如下:
基于上述符号定义,SMBO过程如下:
这一优化过程是逐一选取潜在的最优超参数,并将其结果加入到数据集中继续完成代理函数的优化过程,所以这也就是其称之为Sequential的原因,代理函数M则呼应model-based。而毫无疑问,这其中有两个重要细节实现:一个是代理函数M的选取和建模;另一个是采集函数S的设计。这两个过程的差异,也决定了具体的贝叶斯优化实现的不同。
这里简单介绍几种主流的代理函数M的选取:
至于采集函数的选取,则也有不同的设计,例如PI(Probability of improvement)和EI(Expected Improvement)等,这里不再展开。
对于这三种代理函数的抽象实现,Python中均有相应的库可直接调用。本文选取三个库,分别对应一种代理函数的贝叶斯优化方法:
这里以sklearn中提供的经典二分类数据集breast_cancer为例,给出三个优化库的基本实现方法:
from sklearn.datasets import load_breast_cancer
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score, train_test_split
X, y = load_breast_cancer(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y)
rf = RandomForestClassifier()
rf.fit(X_train, y_train)
rf.score(X_test, y_test)
# 默认参数RF得分:0.958041958041958
from bayes_opt import BayesianOptimization
# bayes_opt中的目标函数
def fun_bo(n_estimators, max_depth, min_samples_split, min_samples_leaf):
rf = RandomForestClassifier(n_estimators=int(n_estimators),
max_depth=int(max_depth),
min_samples_split=int(min_samples_split),
min_samples_leaf=int(min_samples_leaf))
score = cross_val_score(rf, X_train, y_train)
return score.mean()
# 贝叶斯优化中的搜索空间
space_bo = {
'n_estimators': (10, 300),
'max_depth': (1, 21),
'min_samples_split': (2, 20),
'min_samples_leaf': (2, 20)
}
bo = BayesianOptimization(
fun_bo,
space_bo
)
bo.maximize() # 一键完成优化
# 得到最优超参结果
param = {k:int(v) for k, v in bo.max['params'].items()}
rf_hp = RandomForestClassifier(**param)
rf_hp.fit(X_train, y_train)
rf_hp.score(X_test, y_test)
# bayes_opt优化得分:0.965034965034965
from hyperopt import fmin, hp, tpe, Trials
# hyperopt中的目标函数
def fun_hp(param):
rf = RandomForestClassifier(**param, random_state=3)
score = cross_val_score(rf, X_train, y_train)
return 1-score.mean()
# hyperopt中的搜索空间
space_hp = {
"n_estimators":hp.uniformint("n_estimators", 10, 300),
"max_depth":hp.uniformint("max_depth", 1, 21),
"min_samples_split":hp.uniformint("min_samples_split", 2, 20),
"min_samples_leaf":hp.uniformint("min_samples_leaf", 2, 20)
}
# 记录优化过程,fmin实现一键优化,采用优化算法是tpe
trials = Trials()
param = fmin(fun_hp, space_hp, tpe.suggest, max_evals=100, trials=trials)
param = {k:int(v) for k, v in param.items()} # 最优超参数
rf_hp = RandomForestClassifier(**res)
rf_hp.fit(X_train, y_train)
rf_hp.score(X_test, y_test)
# hyperopt优化得分:0.965034965034965
from skopt import forest_minimize, space
# skopt中的目标函数
def fun_sk(param):
param = dict(zip(['n_estimators', 'max_depth', 'min_samples_split', 'min_samples_leaf'], param))
rf = RandomForestClassifier(**param)
score = cross_val_score(rf, X_train, y_train)
return 1 - score.mean()
# skopt中的搜索空间
space_sk = [
space.Integer(10, 300, name='n_estimators'),
space.Integer(1, 21, name='max_depth'),
space.Integer(2, 20, name='min_samples_split'),
space.Integer(2, 20, name='min_samples_leaf')
]
# 采用RF进行优化,得到最优超参结果
res = forest_minimize(fun_sk, space_sk)
param = dict(zip(['n_estimators', 'max_depth', 'min_samples_split', 'min_samples_leaf'], res.x))
rf_hp = RandomForestClassifier(**param)
rf_hp.fit(X_train, y_train)
rf_hp.score(X_test, y_test)
# skopt优化得分:0.965034965034965
在上述超参优化过程中,由于所用数据集较小,所以在制定相应的目标函数时均采用交叉验证的方式以提高泛华性能。同时,三种超参优化方式所得到最优优化结果相同,这一方面源于数据集较小造成的,另一方面其本身也有一定的随机性。但无论如何,三个优化库在具体使用上是相近的,在优化效果方面也算相当的。