三种常见的超参调优方法及代码


超参数调优方法:网格搜索(GridSearch),随机搜索(RandomSearch),贝叶斯优化(BO)等算法。

参考资料:三种超参数优化方法详解,以及代码实现

实验基础代码

import numpy as np
import pandas as pd
from lightgbm.sklearn import LGBMRegressor
from sklearn.metrics import mean_squared_error
import warnings

warnings.filterwarnings('ignore')
from sklearn.datasets import load_diabetes
from sklearn.model_selection import KFold, cross_val_score
from sklearn.model_selection import train_test_split
import timeit
import os
import psutil

# 在sklearn.datasets的糖尿病数据集上演示和比较不同的算法,加载它。
diabetes = load_diabetes()
data = diabetes.data
targets = diabetes.target
n = data.shape[0]
random_state = 42
# 时间占用 s
start = timeit.default_timer()
# 内存占用 mb
info_start = psutil.Process(os.getpid()).memory_info().rss / 1024 / 1024
train_data, test_data, train_targets, test_targets = train_test_split(data, targets,
                                                                      test_size=0.20, shuffle=True,
                                                                      random_state=random_state)
num_folds = 2
kf = KFold(n_splits=num_folds, random_state=random_state, shuffle=True)
model = LGBMRegressor(random_state=random_state)
score = -cross_val_score(model, train_data, train_targets, cv=kf, scoring="neg_mean_squared_error", n_jobs=-1).mean()
print('score:', score)

end = timeit.default_timer()
info_end = psutil.Process(os.getpid()).memory_info().rss / 1024 / 1024

print('此程序运行占内存' + str(info_end - info_start) + 'mB')
print('Running time:%.5fs' % (end - start))

实验结果为:
最小平方误差:3897.5550693355276
此程序运行占内存0.5mB
Running time:1.48060s

出于对比目的,我们将优化仅调整以下三个参数的模型:
n_estimators:从100到2000
max_depth:2到20
learning_rate:从10e-5到1

一、网格搜索(GridSearch)

网格搜索可能是最简单,应用最广泛的超参数搜索算法,他通过查找搜索范围内的所以的点来确定最优值。如果采用较大的搜索范围及较小的步长,网格搜索很大概率找到全局最优值。然而这种搜索方案十分消耗计算资源和时间,特别是需要调优的超参数比较多的时候。
因此在实际应用过程中,网格搜索法一般会先使用较广的搜索范围和较大的步长,来找到全局最优值可能的位置;然后再缩小搜索范围和步长,来寻找更精确的最优值。这种操作方案可以降低所需的时间和计算量,但由于目标函数一般是非凸的,所以很可能会错过全局最优值。
网格搜素对应于sklearn中的GridSearchCV模块:sklearn.model_selection.GridSearchCV
(estimator, param_grid, , scoring=None, n_jobs=None, iid=‘deprecated’, refit=True, cv=None, verbose=0, pre_dispatch='2n_jobs’, error_score=nan, return_train_score=False). 详情见博客

1.1 GridSearch算法代码

from sklearn.model_selection import GridSearchCV
# 时间占用 s
start = timeit.default_timer()
# 内存占用 mb
info_start = psutil.Process(os.getpid()).memory_info().rss / 1024 / 1024

param_grid = {'learning_rate': np.logspace(-3, -1, 3),
              'max_depth': np.linspace(5, 12, 8, dtype=int),
              'n_estimators': np.linspace(800, 1200, 5, dtype=int),
              'random_state': [random_state]}
gs = GridSearchCV(model, param_grid, scoring='neg_mean_squared_error',
                  n_jobs=-1, cv=kf, verbose=False)
gs.fit(train_data, train_targets)
gs_test_score = mean_squared_error(test_targets, gs.predict(test_data))

end = timeit.default_timer()
info_end = psutil.Process(os.getpid()).memory_info().rss / 1024 / 1024

print('此程序运行占内存' + str(info_end - info_start) + 'mB')
print('Running time:%.5fs' % (end - start))
print("Best MSE {:.3f} params {}".format(-gs.best_score_, gs.best_params_))

实验结果:
此程序运行占内存12.79296875mB
Running time:6.35033s
Best MSE 3696.133 params {‘learning_rate’: 0.01, ‘max_depth’: 5, ‘n_estimators’: 800, ‘random_state’: 42}

1.2 可视化解释

import matplotlib.pyplot as plt
gs_results_df = pd.DataFrame(np.transpose([-gs.cv_results_['mean_test_score'],
                                           gs.cv_results_['param_learning_rate'].data,
                                           gs.cv_results_['param_max_depth'].data,
                                           gs.cv_results_['param_n_estimators'].data]),
                             columns=['score', 'learning_rate', 'max_depth',
                                      'n_estimators'])
gs_results_df.plot(subplots=True, figsize=(10, 10))
plt.show()

在这里插入图片描述

我们可以看到,例如max_depth是最不重要的参数,它不会显着影响得分。 但是,我们正在搜索max_depth的8个不同值,并且在其他参数上搜索了任何固定值。 显然浪费时间和资源。

二、随机搜索(RandomSearch)

https://www.cnblogs.com/cgmcoding/p/13634531.html
随机搜索的思想与网络搜索比较相似,只是不再测试上界和下界之间所有值,而是在搜索范围内随机选取样本点。他的理论依据是,如果样本点集足够大,那么通过随机采样也能大概率地找到全局最优值或近似值。随机搜索一般会比网络搜索要快一些。我们在搜索超参数的时候,如果超参数个数较少(三四个或者更少),那么我们可以采用网格搜索,一种穷尽式的搜索方法。但是当超参数个数比较多的时候,我们仍然采用网格搜索,那么搜索所需时间将会指数级上升。
  所以有人就提出了随机搜索的方法,随机在超参数空间中搜索几十几百个点,其中就有可能有比较小的值。这种做法比上面稀疏化网格的做法快,而且实验证明,随机搜索法结果比稀疏网格法稍好。RandomizedSearchCV使用方法和类GridSearchCV 很相似,但他不是尝试所有可能的组合,而是通过选择每一个超参数的一个随机值的特定数量的随机组合,这个方法有两个优点:
如果你让随机搜索运行, 比如1000次,它会探索每个超参数的1000个不同的值(而不是像网格搜索那样,只搜索每个超参数的几个值)
你可以方便的通过设定搜索次数,控制超参数搜索的计算量。
  RandomizedSearchCV的使用方法其实是和GridSearchCV一致的,但它以随机在参数空间中采样的方式代替了GridSearchCV对于参数的网格搜索,在对于有连续变量的参数时,RandomizedSearchCV会将其当做一个分布进行采样进行这是网格搜索做不到的,它的搜索能力取决于设定的n_iter参数,同样的给出代码。
在这里插入图片描述

随机搜索对于于sklearn中的sklearn.model_selection.RandomizedSearchCV
estimatorparam_distributions*n_iter = 10得分= Nonen_jobs = Noneiid =‘deprecated’refit = Truecv = Noneverbose = 0pre_dispatch =‘2 * n_jobs’random_state = Noneerror_score = nan,_return_train_score = False _)参数与GridSearchCV大致相同,但是多了一个n_iter,为迭代轮数。

from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint

param_grid_rand = {'learning_rate': np.logspace(-5, 0, 100),
                   'max_depth': randint(2, 20),
                   'n_estimators': randint(100, 2000),
                   'random_state': [random_state]}
# 时间占用 s
start = timeit.default_timer()
# 内存占用 mb
info_start = psutil.Process(os.getpid()).memory_info().rss / 1024 / 1024
rs = RandomizedSearchCV(model, param_grid_rand, n_iter=50, scoring='neg_mean_squared_error',
                        n_jobs=-1, cv=kf, verbose=False, random_state=random_state)

rs.fit(train_data, train_targets)

rs_test_score = mean_squared_error(test_targets, rs.predict(test_data))
end = timeit.default_timer()
info_end = psutil.Process(os.getpid()).memory_info().rss / 1024 / 1024

print('此程序运行占内存' + str(info_end - info_start) + 'mB')
print('Running time:%.5fs' % (end - start))
print("Best MSE {:.3f} params {}".format(-rs.best_score_, rs.best_params_))

此程序运行占内存17.90625mB
Running time:3.85010s
Best MSE 3516.383 params {‘learning_rate’: 0.0047508101621027985, ‘max_depth’: 19, ‘n_estimators’: 829, ‘random_state’: 42}

由此可见在运行50轮的时候效果已经比GridSearchCV效果要好了,而且用时更短。

三、贝叶斯优化(BO)

  网格搜索速度慢,但在搜索整个搜索空间方面效果很好,而随机搜索很快,但可能会错过搜索空间中的重要点。幸运的是,还有第三种选择:贝叶斯优化。本文我们将重点介绍贝叶斯优化的一个实现,一个名为hyperopt的 Python 模块。贝叶斯优化算法在寻找最优和最值参数时。采用了与网格搜索和随机搜索完全不同的方法。网格搜素和随机搜索在测试一个新点时,会忽略前一个点的信息,而贝叶斯优化算法则充分利用了之前的信息。贝叶斯优化算法通过对目标函数形状进行学习,找到使目标函数向全局最优值提升的参数。
  具体来说,学习目标函数的方法是,首先根据先验分布,假设一个搜索函数;然后,每一次使用新的采样点来测试目标函数时,利用这个信息来更新目标函数的先验分布;最后,算法测试由后验分布给出的全局最值可能出现的位置的点。对于贝叶斯优化算法,有一个需要注意的是,一旦找到可一个局部最优值,他会在该区域不断采样,所以很容易陷入局部最优值。为了弥补这个缺点,贝叶斯算法会在探索和利用之间找到一个平衡点,探索就是在还未取样的区域获取采样点,而利用则根据后验分布在最可能出现的全局最值区域进行采样。
  我们将使用[hyperopt库](http://hyperopt.github.io/hyperopt/#documentation)来处理此算法。 它是超参数优化最受欢迎的库之一。详细介绍看博客。

(1)TPE算法:
algo=tpe.suggest
TPE是Hyperopt的默认算法。 它使用贝叶斯方法进行优化。 它在每一步都试图建立函数的概率模型,并为下一步选择最有希望的参数。 这类算法的工作方式如下:

  1. 生成随机初始点x
  2. 计算F(x)
  3. 利用试验历史尝试建立条件概率模型P(F|x)
  4. 根据P(F|x)选择xi最有可能导致更好的F(xi)
  5. 计算F(xi)的实际值
  6. 重复步骤3-5,直到满足停止条件之一,例如i>max_evals
from hyperopt import fmin, tpe, hp, anneal, Trials
def gb_mse_cv(params, random_state=random_state, cv=kf, X=train_data, y=train_targets):
    # the function gets a set of variable parameters in "param"
    params = {'n_estimators': int(params['n_estimators']), 
              'max_depth': int(params['max_depth']), 
             'learning_rate': params['learning_rate']}
    
    # we use this params to create a new LGBM Regressor
    model = LGBMRegressor(random_state=random_state, **params)
    
    # and then conduct the cross validation with the same folds as before
    score = -cross_val_score(model, X, y, cv=cv, scoring="neg_mean_squared_error", n_jobs=-1).mean()

    return score
# 状态空间,最小化函数的params的取值范围
space={'n_estimators': hp.quniform('n_estimators', 100, 2000, 1),
       'max_depth' : hp.quniform('max_depth', 2, 20, 1),
       'learning_rate': hp.loguniform('learning_rate', -5, 0)
      }

# trials 会记录一些信息
trials = Trials()
#时间占用 s
start=timeit.default_timer()
#内存占用 mb
info_start = psutil.Process(os.getpid()).memory_info().rss/1024/1024
best=fmin(fn=gb_mse_cv, # function to optimize
          space=space, 
          algo=tpe.suggest, # optimization algorithm, hyperotp will select its parameters automatically
          max_evals=50, # maximum number of iterations
          trials=trials, # logging
          rstate=np.random.RandomState(random_state) # fixing random state for the reproducibility
         )

# computing the score on the test set
model = LGBMRegressor(random_state=random_state, n_estimators=int(best['n_estimators']),
                      max_depth=int(best['max_depth']),learning_rate=best['learning_rate'])
model.fit(train_data,train_targets)
tpe_test_score=mean_squared_error(test_targets, model.predict(test_data))
end=timeit.default_timer()
info_end = psutil.Process(os.getpid()).memory_info().rss/1024/1024

print('此程序运行占内存'+str(info_end-info_start)+'mB')
print('Running time:%.5fs'%(end-start))
print("Best MSE {:.3f} params {}".format( gb_mse_cv(best), best))

实验结果:
此程序运行占内存2.5859375mB
Running time:52.73683s
Best MSE 3186.791 params {‘learning_rate’: 0.026975706032324936, ‘max_depth’: 20.0, ‘n_estimators’: 168.0}

四、结论

我们可以看到,即使在以后的步骤中,TPE和退火算法实际上仍会随着时间的推移不断改善搜索结果,而随机搜索在开始时就随机地找到了一个很好的解决方案,然后仅稍微改善了结果。 TPE和RandomizedSearch结果之间的当前差异很小,但是在某些具有超参数范围更加多样化的现实应用中,hyperopt可以显着改善时间/得分
在这里插入图片描述

  • 3
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值