什么是超参数?
学习器模型中一般有两类参数,一类是可以从数据中学习估计得到,我们称为参数(Parameter)。还有一类参数时无法从数据中估计,只能靠人的经验进行设计指定,我们称为超参数(Hyperparameter)。超参数是在开始学习过程之前设置值的参数。相反,其他参数的值通过训练得出。
超参数:
- 定义关于模型的更高层次的概念,如复杂性或学习能力
- 不能直接从标准模型培训过程中的数据中学习,需要预先定义
- 可以通过设置不同的值,训练不同的模型和选择更好的测试值来决定
参数空间的搜索一般由以下几个部分构成:
- 一个estimator(回归器or分类器)
- 一个参数空间
- 一个搜索或采样方法来获得候选参数集合
- 一个交叉验证机制
- 一个评分函数
Scikit-Learn中的超参数优化方法
在机器学习模型中,比如随机森林中决策树的个数,人工神经网络模型中的隐藏层层数和每层的节点个数,正则项中常数大小等等,它们都需要事先指定。超参数选择不恰当,就会出现欠拟合或者过拟合的问题。在Scikit-Learn中,超参数是在学习过程开始之前设置其值的参数。典型的例子包括支持向量机里的C、kernel、gamma等。
class sklearn.svm.SVC(*, C=1.0, kernel='rbf', degree=3, gamma='scale', coef0=0.0, shrinking=True, probability=False, tol=0.001, cache_size=200, class_weight=None, verbose=False, max_iter=-1, decision_function_shape='ovr', break_ties=False, random_state=None)
使用过程中可以使用estimator.get_params()获得学习器模型的超参数列表和当前取值。
Sklearn提供了两种通用的超参数优化方法:网格搜索与随机搜索。
交叉验证(Cross-Validation)-CV简介
在机器学习里,通常来说我们不能将全部用于数据训练模型,否则我们将没有数据集对该模型进行验证,从而评估我们的模型的预测效果。为了解决这一问题,有如下常用的方法:
The Validation Set Approach(验证集方案)
这种是方法最简单的,也是很容易就想到的。我们可以把整个数据集分成两部分,一部分用于训练,一部分用于验证,这也就是我们经常提到的训练集(training set)和测试集(test set)。
不过,这个简单的方法存在两个弊端:
- 最终模型与参数的选取将极大程度依赖于你对训练集和测试集的划分方法。在不同的划分方法下,test MSE的变动是很大的,而且对应的最优degree也不一样。所以如果我们的训练集和测试集的划分方法不够好,很有可能无法选择到最好的模型与参数。
- 该方法只用了部分数据进行模型的训练。当用于模型训练的数据量越大时,训练出来的模型通常效果会越好。所以训练集和测试集的划分意味着我们无法充分利用我们手头已有的数据,所以得到的模型效果也会受到一定的影响。
基于这样的背景,有人就提出了Cross-Validation方法,也就是交叉验证。
Cross-Validation
LOOCV(留一法)
LOOCV即(Leave-one-out cross-validation)。像Test set approach一样,LOOCV方法也包含将数据集分为训练集和测试集这一步骤。但是不同的是,我们只用一个数据作为测试集,其他的数据都作为训练集,并将此步骤重复N次(N为数据集的数据数量)。
假设我们现在有n个数据组成的数据集,那么LOOCV的方法就是每次取出一个数据作为测试集的唯一元素,而其他n-1个数据都作为训练集用于训练模型和调参。结果就是我们最终训练了n个模型,每次都能得到一个MSE。而计算最终test MSE则就是将这n个MSE取平均。
$$CV(n)=\frac{1}{n}\sum_{i=1}^{n}MSE_i$$
比起test set approach,LOOCV有很多优点。首先它不受测试集合训练集划分方法的影响,因为每一个数据都单独的做过测试集。同时,其用了n-1个数据训练模型,也几乎用到了所有的数据,保证了模型的bias更小。不过LOOCV的缺点也很明显,那就是计算量过于大,是test set approach耗时的n-1倍。
K-fold Cross Validation(k折交叉验证)
K折交叉验证,和LOOCV的不同在于,我们每次的测试集将不再只包含一个数据,而是多个,具体数目将根据K的选取决定。比如,如果K=5,那么我们利用五折交叉验证的步骤就是:
- 将所有数据集分成5份
- 不重复地每次取其中一份做测试集,用其他四份做训练集训练模型,之后计算该模型在测试集上的MSE
- 将5次的MSE取平均作为最后而得到MSE
$$CV(k)=\frac{1}{k}\sum_{i=1}^{k}MSE_i$$
不难理解,其实LOOCV是一种特殊的K-fold Cross Validation(K=N)。最后K的选取是一个Bias和Variance的trade-off。K越大,每次投入的训练集的数据越多,模型的Bias越小。但是K越大,又意味着每一次选取的训练集之前的相关性越大(考虑最极端的例子,当k=N,也就是在LOOCV里,每次都训练数据几乎是一样的)。而这种大相关性会导致最终的test error具有更大的Variance。一般K值选择5或10。
网格搜索GridSearchCV
我们在选择超参数有两个途径:1)凭经验;2)选择不同大小的参数,带入到模型中,挑选表现最好的参数。通过途径2选择超参数时,人力手动调节注意力成本太高,非常不值得。For循环或类似于for循环的方法受限于太过分明的层次,不够简洁与灵活,注意力成本高,易出错。GridSearchCV称为网格搜索交叉验证调参,它通过遍历传入的参数的所有排列组合,通过交叉验证的方式,返回所有参数组合下的评价指标得分。
GridSearchCV听起来很高大上,其实就是暴力搜索。注意的是,该方法在小数据集上很有用,数据集大了就不太适用了。数据量比较大的时候可以使用一个快速调优的方法——坐标下降。它其实是一种贪心算法:拿当前对模型影响最大的参数调优,直到最优化;再拿下一个影响最大的参数调优,如此下去,直到所有的参数调整完毕。这个方法的缺点就是可能会调到局部最优而不是全局最优,但是省时间省力。
GridSearchCV使用说明
class sklearn.model_selection.GridSearchCV(estimator, param_grid, scoring=None, n_jobs=None, refit=True, cv='warn', verbose=0, pre_dispatch='2*n_jobs', error_score='raise-deprecating', return_train_score='warn')
参数详解:
- estimator:所使用的模型,传入除需要确定最佳的参数之外的其他参数。模型都需要一个score方法,或传入scoring参数。
- param_grid:需要搜索调参的参数字典,参数值类型为字典(dict)或由字典组成的列表(list)。用于设置待评测参数和对应的参数值。
scoring:模型评价标准,默认 None,这时需要使用 score 函数;或者如 scoring=’roc_auc’,根据所选模型不同,评价准则不同。字符串(函数名),或是可调用对象,需要其函数签名形如:scorer(estimator, X, y);如果是 None,则使用 estimator 的误差估计函数。下文表格中详细指定了 score 可取的值和函数形式。
- n_jobs:并行计算线程个数,1:默认值,可以设置为 -1(跟 CPU 核数一致),这样可以充分使用机器的所有处理器。
- refit:默认为 True,程序将会以交叉验证训练集得到的最佳参数。即在搜索参数结束后,用最佳参数结果再次 fit 一遍全部数据集。
- cv:交叉验证参数,可接受的参数:
- 默认 None,使用 3 折交叉验证。
- 指定 fold 数量
- CV splitter
- yield 训练、测试数据的生成器。
- verbose:日志冗长度
- 0:不输出训练过程
- 1:偶尔输出
- >1:对每个子模型都输出
- pre_dispatch:指定总共分发的并行任务数。当 n_jobs 大于 1 时,数据将在每个运行点进行复制,这可能导致内存问题,而设置 pre_dispatch 参数,则可以预先划分总共的 job 数量,使数据最多被复制 pre_dispatch 次。
- error_score:拟合中发生错误时分配的值,如果设置为 ’raise’ 则会引发错误。如果设置的是一个数字,则为引发 FitFailedWarning 的警告信息,默认值将在 22 版本其由原先的 ‘raise’ 更改为 np.nan。
- return_train_score:如果“False”,cv_results_ 属性将不包括训练分数。
GridSearchCV 对象
- cv_results_: 用来输出 cv 结果的,可以是字典形式也可以是 numpy 形式,还可以转换成 DataFrame 格式
- best_estimator_:通过搜索参数得到的最好的估计器,当参数 refit=False 时该对象不可用
- best_score_:float 类型,输出最好的成绩
- best_params_:通过网格搜索得到的 score 最好对应的参数
- best_index_:对应于最佳候选参数设置的索引(cv_results_ 数组)。cv_results_[‘params’][search.best_index_] 中的 dict 给出了最佳模型的参数设置,给出了最高的平均分数(best_score_)。
- scorer_:评分函数
- n_splits_:交叉验证的数量
- refit_time_:refit 所用的时间,当参数 refit=False 时该对象不可用
GridSearchCV 方法
- decision_function(X):返回决策函数值(比如 svm 中的决策距离)
- fit(X, y=None, groups=None, fit_params):在数据集上运行所有的参数组合
- get_params(deep=True):返回估计器的参数
- inverse_transform(Xt):Call inverse_transform on the estimator with the best found params.
- predict(X):返回预测结果值(0/1)
- predict_log_proba(X):Call predict_log_proba on the estimator with the best found parameters.
- predict_proba(X): 返回每个类别的概率值(有几类就返回几列值)
- score(X, y=None): 返回函数
- set_params(**params):Set the parameters of this estimator.
- transform(X): 在 X 上使用训练好的参数
使用示例:
from sklearn.model_selection import GridSearchCV from sklearn.svm import SVR from sklearn import datasets dataset = datasets.load_iris() X = dataset.data y = dataset.target grid = GridSearchCV( estimator=SVR(kernel='rbf'), param_grid={ 'C': [0.1, 1, 10, 100], 'epsilon': [0.0001, 0.001, 0.01, 0.1, 1, 10], 'gamma': [0.001, 0.01, 0.1, 1] }, cv=5, scoring='neg_mean_squared_error', verbose=0, n_jobs=-1) grid.fit(X, y) print(grid.best_score_) print(grid.best_params_)
随机搜索 RandomizedSearchCV
我们在搜索超参数的时候,如果超参数个数较少(三四个或者更少),那么我们可以采用网格搜索,一种穷尽式的搜索方法。但是当超参数个数比较多的时候,我们仍然采用网格搜索,那么搜索所需时间将会指数级上升。所以有人就提出了随机搜索的方法,随机在超参数空间中搜索几十几百个点,其中就有可能有比较小的值。这种做法比上面稀疏化网格的做法快,而且实验证明,随机搜索法结果比稀疏网格法稍好。
RandomizedSearchCV 使用方法和类 GridSearchCV 很相似,但他不是尝试所有可能的组合,而是通过选择每一个超参数的一个随机值的特定数量的随机组合,这个方法有两个优点:
- 相比于整体参数空间,可以选择相对较少的参数组合数量。如果让随机搜索运行,它会探索每个超参数的不同的值
- 可以方便的通过设定搜索次数,控制超参数搜索的计算量。添加参数节点不会影响性能,不会降低效率。
RandomizedSearchCV 的使用方法其实是和 GridSearchCV 一致的,但它以随机在参数空间中采样的方式代替了 GridSearchCV 对于参数的网格搜索,在对于有连续变量的参数时,RandomizedSearchCV 会将其当做一个分布进行采样进行这是网格搜索做不到的,它的搜索能力取决于设定的 n_iter 参数。
RandomizedSearchCV 使用说明
class sklearn.model_selection.RandomizedSearchCV(estimator, param_distributions, *, n_iter=10, scoring=None, n_jobs=None, refit=True, cv=None, verbose=0, pre_dispatch='2*n_jobs', random_state=None, error_score=nan, return_train_score=False)
与 GridSearchCV 不同的主要有以下两参数:
- param_distributions:参数分布,字典格式。将我们所传入模型当中的参数组合为一个字典。其搜索策略如下:
- 对于搜索范围是 distribution 的超参数,根据给定的 distribution 随机采样
- 对于搜索范围是 list 的超参数,在给定的 list 中等概率采样
- n_iter:训练 300 次,数值越大,获得的参数精度越大,但是搜索时间越长
使用示例:
from scipy.stats import randint as sp_randint from sklearn.model_selection import RandomizedSearchCV from sklearn.datasets import load_digits from sklearn.ensemble import RandomForestClassifier # 载入数据 digits = load_digits() X, y = digits.data, digits.target # 建立一个分类器或者回归器 clf = RandomForestClassifier(n_estimators=20) # 给定参数搜索范围:list or distribution param_dist = {"max_depth": [3, None], # 给定list "max_features": sp_randint(1, 11), # 给定distribution "min_samples_split": sp_randint(2, 11), # 给定distribution "bootstrap": [True, False], # 给定list "criterion": ["gini", "entropy"]} # 给定list # 用RandomSearch+CV选取超参数 n_iter_search = 20 random_search = RandomizedSearchCV(clf, param_distributions=param_dist, n_iter=n_iter_search, cv=5, iid=False) random_search.fit(X, y) print(random_search.best_score_) print(random_search.best_params_)
自动超参数优化方法
贝叶斯优化方法(Bayesian Optimization)
贝叶斯优化用于机器学习调参由J. Snoek(2012)提出,主要思想是,给定优化的目标函数(广义的函数,只需指定输入和输出即可,无需知道内部结构以及数学性质),通过不断地添加样本点来更新目标函数的后验分布(高斯过程,直到后验分布基本贴合于真实分布。简单的说,就是考虑了上一次参数的信息,从而更好的调整当前的参数。
贝叶斯优化与常规的网格搜索或者随机搜索的区别是:
- 贝叶斯调参采用高斯过程,考虑之前的参数信息,不断地更新先验;网格搜索未考虑之前的参数信息
- 贝叶斯调参迭代次数少,速度快;网格搜索速度慢,参数多时易导致维度爆炸
- 贝叶斯调参针对非凸问题依然稳健;网格搜索针对非凸问题易得到局部最优
贝叶斯优化提供了一个优雅的框架来尽可能少的步骤中找到全局最小值。
让我们构造一个函数c(x)或者一个接收输入x的模型,如下图所示为c(x)的形状。当然,优化器并不知道该函数,称之为“目标函数”。
贝叶斯优化通过代理优化的方式来完成任务。代理函数通过采样点模拟构造(见下图)。
根据代理函数,我们大致可以确定哪些点是可能的最小值。然后再这些点附近做更多的采样,并随之更新代理函数。
每一次迭代,我们都会继续观察当前的代用函数,通过采样了解更多感兴趣的区域,并更新函数。需要注意的是,代用函数在数学上的表达方式将大大降低评估成本。经过一定的迭代次数后,我们注定要到达一个全局最小值,除非函数的形状非常诡异。
让我们仔细看看代用函数,通常用高斯过程来表示,它可以被认为是掷骰子,返回与给定数据点(如sin、log)拟合的函数,而不是1到6的数字。这个过程会返回几个函数,这些函数都附有概率。为什么用高斯过程,而不是其他的曲线拟合方法来模拟代用函数,有一个很好的理由:它是贝叶斯性质的。代用函数–表示为概率分布,即先验–被更新为”获取函数”。这个函数负责在勘探和开发的权衡中提出新的测试点。
- “开发”力求在代用模型预测的目标好的地方采样。这就是利用已知的有希望的点。但是,如果我们已经对某一区域进行了足够的探索,那么不断地利用已知的信息就不会有什么收获。
- “探索”力求在不确定性较高的地点进行采样。这就确保了空间的任何主要区域都不会未被探索–全局最小值可能恰好就在那里。
一个鼓励过多的开发和过少探索的获取函数将导致模型只停留在它首先发现的最小值(通常是局部的–“只去有光的地方”)。一个鼓励相反的获取函数将不会首先停留在一个最小值,本地或全球。在微妙的平衡中产生良好的结果。acquisition函数,我们将其表示为a(x),必须同时考虑开发和探索。常见的获取函数包括预期改进和最大改进概率,所有这些函数都是在给定先验信息(高斯过程)的情况下,衡量特定投入在未来可能得到回报的概率。
让我们把这些东西整合起来。贝叶斯优化可以这样进行。
- 初始化一个高斯过程”代用函数”的先验分布。
- 选择几个数据点x,使在当前先验分布上运行的获取函数a(x)最大化。
- 评估目标成本函数c(x)中的数据点x,得到结果,y。
- 用新的数据更新高斯过程先验分布,以产生一个后验(它将成为下一步的先验)。
- 重复步骤2-5进行多次迭代。
- 解释当前的高斯过程分布(这是非常便宜的),以找到全局最小值。
贝叶斯优化就是把概率论的思想放在代入优化的思想后面。综上所述:
- 代用优化利用代用函数或近似函数通过抽样来估计目标函数。
- 贝叶斯优化将代用优化置于概率框架中,将代用函数表示为概率分布,可以根据新的信息进行更新。
- 获取函数用于评估在当前已知的先验条件下,探索空间中某一点会产生”好”收益的概率,平衡探索与开发
- 主要在目标函数评估成本很高的时候使用贝叶斯优化,常用于超参数调整。
Hyperopt
Hyperopt是一个强大的Python库,用于超参数优化,由jamesbergstra开发。Hyperopt使用贝叶斯优化的形式进行参数调整,允许你为给定模型获得最佳参数。它可以在大范围内优化具有数百个参数的模型。
Hyperopt包含4个重要的特性
1、搜索空间
hyperopt有不同的函数来指定输入参数的范围,这些是随机搜索空间。选择最常用的搜索选项:
- choice(label, options)-这可用于分类参数,它返回其中一个选项,它应该是一个列表或元组。示例: hp.choice(“criterion”, [“gini”, “entropy”, ])
- randint(label, upper)-可用于整数参数,它返回范围(0,upper)内的随机整数。示例:hp.randint(“max_features”, 50)
- uniform(label, low, high)-它返回一个介于low和high之间的值。示例:hp.uniform(“max_leaf_nodes”, 1, 10)
你可以使用的其他选项包括:
- normal(label, mu, sigma)-这将返回一个实际值,该值服从均值为mu和标准差为sigma的正态分布
- qnormal(label, mu, sigma, q)-返回一个类似round(normal(mu, sigma)/q)*q的值
- lognormal(label, mu, sigma)-返回exp(normal(mu, sigma))
from sklearn import datasets from hyperopt import fmin, tpe, hp, STATUS_OK, Trials from sklearn.neighbors import KNeighborsClassifier from sklearn.model_selection import cross_val_score iris = datasets.load_iris() X = iris.data y = iris.target def hyperopt_train_test(params): clf = KNeighborsClassifier(**params) return cross_val_score(clf, X, y).mean() # 定义参数空间 space_knn = { 'n_neighbors': hp.choice('n_neighbors', range(1, 100)) } # 定义最小化函数(目标函数) def fn_knn(params): acc = hyperopt_train_test(params) return {'loss': -acc, 'status': STATUS_OK} # hyperopt最小化函数,所以在acc中添加了负号 # 实例化Trial对象,对模型进行微调,然后用其超参数值打印出最佳损失 trials = Trials() best = fmin(fn_knn, space_knn, algo=tpe.suggest, max_evals=100, trials=trials) print("Best: {}".format(best)) print(trials.results) # 搜索期间“objective”返回的词典列表。
from tpot import TPOTClassifier from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split iris = load_iris() X = iris.data y = iris.target X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2) tpot = TPOTClassifier(generations=5, population_size=50, verbosity=2, n_jobs=-1) tpot.fit(X_train, y_train) print(tpot.score(X_test, y_test))
TPOT的主要参数:
- generations – 确定创建子代(新个体)的迭代次数
- population_size – 创建个体的初始数量(这些用于创建后代)
- offspring_size – 每一代所需创造的新个体数
- mutation_rate – 出现属性值随机更改的概率(包括新参数的方法,在初始群体中可能不可用)
- crossover_rate – 用于创造后代的个体所占的百分比
使用这个迭代过程,我们选出最佳配置。准备遗传算法的结果一般取决于初始状态。因此,它随机产生的初始种群影响输出,重新运行相同的设置可能会输出不同的结果。
其他超参数调优工具
是一个超参数优化库,包括随机搜索、贝叶斯搜索、决策森林和梯度提升树。这个库包含一些理论成熟且可靠的优化方法,但是这些模型在小型搜索空间和良好的初始估计下效果最好。
Auto-sklearn提供了开箱即用的监督型自动机器学习。从名字可以看出,auto-sklearn是基于机器学习库scikit-learn构建的,可为新的数据集自动搜索学习算法,并优化其超参数。因此,它将机器学习使用者从繁琐的任务中解放出来,使其有更多时间专注于实际问题。
一个优化库,可作为贝叶斯优化的替代方法。Simple(x)和贝叶斯搜索一样,试图以尽可能少的样本进行优化,但也将计算复杂度从n³降低到log(n),这对大型搜索空间非常有用。这个库使用单形(n维三角形),而不是超立方体(n维立方体),来模拟搜索空间,这样做可以避开贝叶斯优化中具有高计算成本的高斯过程。
是一个超参数优化库,主要适用于深度学习和强化学习模型。它结合了许多先进算法,如Hyperband算法(最低限度地训练模型来确定超参数的影响)、基于群体的训练算法(Population Based Training,在共享超参数下同时训练和优化一系列网络)、Hyperopt方法和中值停止规则(如果模型性能低于中等性能则停止训练)。这些都运行在Ray分布式计算平台上,这让它具有很强的扩展性。
是一个分布式超参数优化库(支持计算机集群的并行运算且无需中央主机),它使用通用数据库来联合执行各个任务。它还支持网格搜索、随机搜索、准随机搜索、贝叶斯搜索和自适应协方差矩阵进化策略。它的优势体现在它支持受约束的搜索空间和多损失函数优化(多目标优化)。
一个基于GpFlow库的高斯过程优化器,可使用TensorFlow在GPU上运行高斯过程任务。你如果要用到贝叶斯优化且有可用的GPU计算资源,那GpFlowOpt库应该是理想之选。
运行在TensorFlow上,包含一系列基于梯度的优化器,包括Reverse-HG和Forward-HG。这个库旨在构建TensorFlow中基于梯度的超参数优化器的访问,允许在GPU或其他张量优化计算环境中进行深度学习模型的训练和超参数优化。
支持大规模模型开发、执行和集成。它的优势在于能够在单个GUI界面中管理多个机器学习模型的训练、执行和评估。它具有多个集成工具来组合这些模型,以实现最佳性能。它包括一个贝叶斯搜索参数优化器,这个优化器支持高级别并行计算,还支持与TPOT库的集成。
一个用于超参数优化的独立算法,它能为需要优化的黑盒模型生成一个代理函数,并用它来生成最接近理想状态的超参数组合,以减少对整个模型的评估。与Tree Parzen Estimators、SMAC和高斯过程方法相比,它始终具有更高的一致性和更低的错误率,而且这个方法特别适用于极高维数据分析。
可在pytorch中实现高效的深度学习结构搜索。它使用参数共享来构建更高效的网络,使其适用于深度学习结构搜索。
Keras官方出品,是一个易于使用、可分布式的超参数优化框架,用于解决执行超参数搜索的痛点。KerasTuner可以很容易地定义搜索空间,并利用所包含的算法来查找最佳超参数值。KerasTuner内置了贝叶斯优化(Bayesian Optimization)、Hyperband和随机搜索(Random Search)算法,同时也便于研究人员扩展,进行新搜索算法的试验。
是将hyperopt与keras相结合的库。
Talos的优势在于原样暴露了Keras,没有引进任何新语法。Talos把超参数优化的过程从若干天缩短到若干分钟,也使得优化过程更有意思,而不是充满了痛苦的重复。
一个自动机器学习的框架,通过PyTorch实现神经网络体系架构的自动搜索。它源自Mendoza等人的工作Towards Automatically-Tuned Deep Neural Networks。
是对Google Vizier的开源实现,因此也遵循其中对问题的抽象模型:Study,Trial和Suggestion。
HyperparameterHunterRL Baselines Zoo
是一系列用Stable Baselines预训练的强化学习agents。它也提供用于训练、评估agents、微调超参数、记录视频的基础脚本。
参考链接: