专栏首页小数志还在当调参侠?推荐这三个超参优化库【含示例代码】

还在当调参侠?推荐这三个超参优化库【含示例代码】

导读

在传统的算法建模过程中,影响算法性能的一个重要环节、也可能是最为耗时和无趣的一项工作就是算法的调参,即超参数优化(Hyper-parameter Optimization,HPO),因此很多算法工程师都会调侃的自称"调参侠"。近期在研究一些AutoML相关的论文和实现,而在AutoML中的一个核心组件就是HPO。借此机会,本文梳理总结Python中三种常见的可实现HPO的库,并提供一个简单的示例。

HPO,全称Hyper-parameter Optimization,即超参优化。之所以做这项工作是出于机器学习领域的两个基本事实:

  • no free lunch。即天下没有免费午餐,用在机器学习领域是指没有一种算法可以适用于所有的机器学习问题,换言之A算法可能在这个数据集上表现最优,但在另一个数据集上表现最好的则是另一个B算法。
  • 对于同一算法,不同的超参数可能对算法性能影响很大。例如线性模型中的正则化系数、决策树模型中树的最大深度等,这些都属于模型拟合之外的参数,需要认为指定,故而称之为超参数。

正因如此,所以算法工程师们在提升算法性能时常常需要对比多个模型,同时在各模型内部又要调整多组超参,以期实现最优效果。在这个超参调优过程中,当前的实现方式主要是如下三种:

  • 最为简单也最为熟知的莫过于网格搜索,在sklearn中的实现是GridSearch,通过对各超参数提供所有可能的候选值,该算法会自动暴力尝试所有可能的超参组合,并给出最佳结果。该实现方法直观易懂,但缺点也很明显,那就是效率不高,而且只能接受离散取值
  • 与网格搜索类似、但不再暴力枚举的一种方法是随机搜索,其优化过程其实也更为简单:即对每个超参数均随机选取一个候选值,而后组成一次随机抽选的超参组合。最后返回所有随机尝试后的最优组合。这种方法实现简单,搜索次数可大可小,但却往往能取得不错的效果。但所得到的最好结果可能不是最优解。
  • 贝叶斯优化。除了网格搜索和随机搜索外,贝叶斯优化可能是目前最为理想和高效的超参优化(从其名字可以看出,这类方法跟贝叶斯大神有一定关系,大概是由于其中要用到的代理函数与贝叶斯后验概率有关吧)。基于贝叶斯优化算法实现的HPO,其一般形式可抽象为如下SMBO的过程:

《Bayesian Optimization Primer》

其中,各符号及变量的含义如下:

  • f:目标函数,在机器学习场景中即为根据超参数组合xi得到评估指标yi的过程
  • X:超参搜索空间,其中每个xi即为X中的一组取值;
  • yi:目标函数的得分,在机器学习场景中即为评估指标结果,例如accuracy_score
  • D:所有(xi, yi)组成的数据集
  • M:代理函数,即要得到的由xi得到yi的映射方法。从机器学习的视角来理解,既然是由一组超参数(可理解为特征)拟合一个连续的得分结果(回归目标),所以可用一个回归模型来实现。这里之所以称之为代理函数,则是因为正常情况下,应该是真正的用对应的算法模型+超参数进行实际的训练和评估,得到真实的评估结果,而此时为了避免这种大计算量的过程(expensive),所以才选择用一个简单快速的函数加以拟合替代
  • S:采集函数,即根据当前得到的代理函数M和超参搜索空间X,如何获取下一组可能带来性能提升的超参组合。

基于上述符号定义,SMBO过程如下:

  • 指定输入参数f、X、M、S,给定一组初始的(xi, yi),作为初始训练集完成代理函数M的评估;
  • 根据采集函数S和代理函数M,得到当前情况下可能获得最优得分的超参组合xi+1
  • 将新一组超参数带入待优化的机器学习,得到真实的yi+1
  • 将最新数据(xi+1, yi+1)加入到数据集D中,重新拟合代理函数M
  • 如此迭代执行T次,或者达到目标效果结束

这一优化过程是逐一选取潜在的最优超参数,并将其结果加入到数据集中继续完成代理函数的优化过程,所以这也就是其称之为Sequential的原因,代理函数M则呼应model-based。而毫无疑问,这其中有两个重要细节实现:一个是代理函数M的选取和建模;另一个是采集函数S的设计。这两个过程的差异,也决定了具体的贝叶斯优化实现的不同。

这里简单介绍几种主流的代理函数M的选取:

  • 高斯过程,即将K个超参数到评分的映射关系抽象为K维联合高斯分布,从而每次都根据数据集D来计算该联合高斯分布的均值和方差即可。但这种方式的一个显著缺点是仅适用于连续性超参,且一般仅在较低维度下适用;
  • TPE,tree-structured Parzen estimator,主要思想是用到核密度函数估计(KDE,kernel density estimator),会根据yi的取值高低将数据集划分为两个区域,从而在两个区域分别用kde方法拟合其分布。最后的目标就是尽可能的最大化高分的概率g(x)同时最小化低分的概率l(x)(实际用到的是最小化比值:l(x)/g(x));
  • Random Forest,即将代理函数M用一个随机森林回归模型加以拟合,其中每棵子树均通过在数据集D的随机子集进行拟合确保随机性。当拿到一组新的超参组合时,即可通过该随机森林模型中每棵子树的评分结果的均值作为衡量该组超参数的潜力。

至于采集函数的选取,则也有不同的设计,例如PI(Probability of improvement)和EI(Expected Improvement)等,这里不再展开。

对于这三种代理函数的抽象实现,Python中均有相应的库可直接调用。本文选取三个库,分别对应一种代理函数的贝叶斯优化方法:

  • GP,对应python库bayes_opt
  • TPE,对应python库hyperopt
  • RandomForest,对应python库scikit-optimizer,简称skopt

这里以sklearn中提供的经典二分类数据集breast_cancer为例,给出三个优化库的基本实现方法:

  • 数据集加载和默认参数随机森林的baseline
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
  • bayes_opt实现
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
  • hyperopt实现
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
  • skopt实现
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

在上述超参优化过程中,由于所用数据集较小,所以在制定相应的目标函数时均采用交叉验证的方式以提高泛华性能。同时,三种超参优化方式所得到最优优化结果相同,这一方面源于数据集较小造成的,另一方面其本身也有一定的随机性。但无论如何,三个优化库在具体使用上是相近的,在优化效果方面也算相当的。

本文分享自微信公众号 - 小数志(Datazhi),作者:luanhz

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-08-01

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Vitis指南 | Xilinx Vitis 系列(六)

    大侠好,欢迎来到“艮林子”专栏,本次为艮林子首次和大侠见面,新春佳节之际,略备薄礼,不成敬意,给大侠带来“Xilinx Vitis 系列连载”,给大侠提供参考学...

    FPGA技术江湖
  • 实操|如何进阶深度学习工程师(下篇)

    这里所定义的中阶选手,是深度学习理论基础扎实,实操方面尝试扩展深度学习更多应用场景的侠客。由此,从初阶→中阶,基本流程保持不变,变化的是每个步骤的深入程度。

    用户1386409
  • 如何写好状态机(三)

    今天给大侠带来如何写好状态机(三),由于篇幅比较长,如何写好状态机分成三篇呈现。前两篇已经说了状态机的基本概念以及如何写好状态机,此篇带来使用 Synplify...

    FPGA技术江湖
  • 这届年轻人,口口声声说享受单身,偷偷摸摸上婚恋网站!

    全国有超过2亿“单身人士”,相亲市场越发庞大。进入信息爆炸时代以来,交友网站以及社交APP不断渗透人们生活,大数据在相亲行业的应用也多了起来,机器根据你的各类标...

    DT数据侠
  • 百度SEO,该什么时间使用Nofollow?

    在百度SEO过程中,每一个SEO人员,手中可利用的资源都是有限的,因此,在页面优化的时候,特别对于新站而言,我们应该学会善用权重。

    蝙蝠侠IT
  • FPGA Xilinx Zynq 系列(八)Zynq 设计指南( 如何使用?) 之 ISE 和 Vivado 设计套件​

    今天给大侠带来FPGA Xilinx Zynq 系列第八篇,本篇内容目录简介如下:

    FPGA技术江湖
  • 【文末送书】调参太费力?自动化机器学习来帮你!

    在项目实战中,这些步骤还需要反复迭代,以实现最优的效果。待到机器学习模型训练完毕,参数都确定后,还要进行最后的部署,让模型在生产系统中上线工作。

    CV君
  • Vitis指南 | Xilinx Vitis 系列(二)

    大侠好,欢迎来到FPGA技术江湖,江湖偌大,相见即是缘分。大侠可以关注FPGA技术江湖,在“闯荡江湖”、"行侠仗义"栏里获取其他感兴趣的资源,或者一起煮酒言欢。...

    FPGA技术江湖
  • 神经网络与推荐系统初步简介

    深度神经网络(Deep Neural Networks, DNN)在过去的数年已经在图像分类、语音识别、自然语言处理中取得了突破性的进展。在实...

    流川疯
  • 新谈:为什么你觉得FPGA难学?如何入门?

    大侠好,欢迎来到FPGA技术江湖,江湖偌大,相见即是缘分。大侠可以关注FPGA技术江湖,在“闯荡江湖”、"行侠仗义"栏里获取其他感兴趣的资源,或者一起煮酒言欢。...

    FPGA技术江湖
  • 大话卷积神经网络CNN(干货满满)

    本文创作的主要目的,是对时下最火最流行的深度学习算法的基础知识做一个简介,本人看过许多教程,感觉对小白不是特别友好,尤其是在踩过好多坑之后,于是便有了写这篇文章...

    我是管小亮
  • FPGA Xilinx Zynq 系列(十四)应用和机会 之 图像和视频处理,及计算机视觉

    今天给大侠带来FPGA Xilinx Zynq 系列第十四篇,本篇内容目录简介如下:

    FPGA技术江湖
  • 《手把手教你》系列技巧篇(二十四)-java+ selenium自动化测试-三大延时等待(详细教程)

    前边讲解完八大元素定位大法,今天宏哥讲解和分享一下三大延时等待。宏哥这里简称“三等八定”。很多人在群里问,这个下拉框定位不到、那个弹出框定位不到…各种定位不到,...

    北京-宏哥
  • 你到底懂不懂什么是Linux运维工程师?

    作为互联网的幕后英雄,Linux运维工程师长期隐匿在大众认知范围之外,关于运维的讨论仍旧是一片无人涉足的荒漠。在某知名行业研究调查结果中,非互联网从业者对于运维...

    马哥linux运维
  • FPGA Xilinx Zynq 系列(六)Zynq 设计指南( 如何使用?) 之 入门

    今天给大侠带来FPGA Xilinx Zynq 系列第六篇,本篇内容目录简介如下:

    FPGA技术江湖
  • 电商为什么知道你想买啥?

    一直以来都被高度曝光的人工智能领域相关应用,总是引来巨大关注。在电商搜索领域,人工智能发挥着怎样的作用?

    DT数据侠
  • R vs Python:R是现在最好的数据科学语言吗

    Sharp Sight Labs 近日在 r-bloggers 上发表了一篇文章,论述了为什么当今的数据科学工作者应该学习 R 语言的原因。为了给大家提供一个明...

    华章科技
  • R vs Python:R是现在最好的数据科学语言吗?

    点击上方蓝色字体关注「顶级程序员」 转自机器之心 Sharp Sight Labs 近日在 r-bloggers 上发表了一篇文章,论述了为什么当今的数据科学工...

    顶级程序员
  • 探秘|R vs Python:谁是现在最好的数据科学语言?

    我写这篇文章是为了向那些声称掌握 R 语言浪费时间的人致辞。(因为它最终会变得过时)。但同时,我想要消除你的恐惧: R 语言仍然很流行。 当我认为 R 语言 ...

    灯塔大数据

扫码关注云+社区

领取腾讯云代金券