Optuna自动调参使用指南
在日常工作中用到的比较多的还是树回归模型,由于LightGBM不需要的类别数据进行预处理所以用得特别多,中间涉及到超参数优化时通常使用随机参数优化方法。在 算法模型自动超参数优化方法中有提到了Optuna,平时工作中也会使用到,今天主要对如何使用Optuna进行整理。
Optuna简介
Optuna是一种机器学习自动超参优化框架,前支持的模型:
- AllenNLP
- Catalyst
- Catboost
- Chainer
- FastAI (V1, V2)
- Keras
- LightGBM
- MXNet
- PyTorch
- PyTorch Ignite
- PyTorch Lightning
- TensorFlow
- keras
- XGBoost
Optuna使用指南
Optuna中的三个核心概念:
- Study: 基于目标函数的优化过程
- Trial: 目标函数的单次执行过程
- Objective:目标函数
Objective 负责定义待优化函数并指定参/超参数数范围,trial 对应着 objective 的单次执行,而 study 则负责管理优化,决定优化的方式,总试验的次数、试验结果的记录等功能。
一个简单的示例:
import optuna import sklearn import sklearn.datasets # Define an objective function to be minimized. def objective(trial): # Invoke suggest methods of a Trial object to generate hyperparameters. regressor_name = trial.suggest_categorical('classifier', ['SVR', 'RandomForest']) if regressor_name == 'SVR': svr_c = trial.suggest_loguniform('svr_c', 1e-10, 1e10) regressor_obj = sklearn.svm.SVR(C=svr_c) else: rf_max_depth = trial.suggest_int('rf_max_depth', 2, 32) regressor_obj = sklearn.ensemble.RandomForestRegressor(max_depth=rf_max_depth) X, y = sklearn.datasets.load_boston(return_X_y=True) X_train, X_val, y_train, y_val = sklearn.model_selection.train_test_split(X, y, random_state=0) regressor_obj.fit(X_train, y_train) y_pred = regressor_obj.predict(X_val) error = sklearn.metrics.mean_squared_error(y_val, y_pred) return error # An objective value linked with the Trial object. study = optuna.create_study() # Create a new study. study.optimize(objective, n_trials=100) # Invoke optimization of the objective function. print(study.best_params) print(study.best_value)
上面我们定义了一个objective,在它内部,模型的均方误差被作为返回值,具体使用时可以用优化指标作为返回。中间还使用trial.suggest_*设置了参数空间和随机分布。支持的方法有:
- suggest_categorical(name, choices):从枚举值中随机取一个值
- suggest_uniform(name, low, high):随机一个小数,范围为[low,high)。
- suggest_discrete_uniform(name, low, high, q):随机离散化取值,随机中low,low+q,low+2q,…,low+nq<high中获取一个值。
- suggest_loguniform(name, low, high):以log分布,随机一个小数
- suggest_float(name, low, high, *[, step, log]):随机一个小数,功能包含了suggest_uniform、suggest_discrete_uniform、suggest_loguniform
- suggest_int(name, low, high[, step, log]):随机一个整数,同上
optuna.create_study()
用来创建学习,中间主要涉及到数据库存储和优化方向设置。
函数原型:
optuna.study.create_study(storage: Union[str, optuna.storages._base.BaseStorage, None] = None, sampler: Optional[samplers.BaseSampler] = None, pruner: Optional[optuna.pruners._base.BasePruner] = None, study_name: Optional[str] = None, direction: Optional[str] = None, load_if_exists: bool = False, *, directions: Optional[Sequence[str]] = None)
参数说明:
- storage:默认情况下,Optuna 使用内存存储来记录试验过程。但是如果在创建 study 时添加一个 storage 参数,Optuna 可以根据你的参数类型使用 SQLite, MySQL 或者 Redis 等流行的数据库来记录你的试验历史。
- sampler: 实现值建议的背景算法的sampler对象。如果未指定,则在单目标优化过程中使用TPESampler,在多目标优化过程中使用nsgaisampler。通常无需设置。
- pruner:一个pruner对象,它决定是否提前停止没有希望的试验。如果未指定任何值,则使用MedianPruner作为默认值。通常无需设置。
- pruners.SuccessiveHalvingPruner 实现的 Asynchronous Successive Halving 算法。
- pruners.HyperbandPruner 实现的 Hyperband 算法。
- pruners.MedianPruner 实现的中位数剪枝算法
- pruners.ThresholdPruner 实现的阈值剪枝算法
- study_name:默认为空,使用数据库存储时,用来标识不同的学习。 存储在同一个数据库中时,作为区分不同 study 的标识符。
- direction:优化方向,可选值minimiz、maximize。默认为maximize。
- directions:如果有多个优化目标,可以设置多个方向。
- load_if_exists:控制处理研究名称冲突的行为的标志。如果存储中已存在名为study_name的研究,则如果将load_if_exists设置为False,则会引发DuplicatedStudyError。否则,将跳过创建研究,并返回现有研究
使用SQLite 存储的示例:
study_name = 'example-study' study = optuna.create_study(study_name=study_name, storage='sqlite:///example.db') study.optimize(objective, n_trials=300)
假如 ‘sqlite:///example.db’ 这一 URL 对应的数据库文件不存在,Optuna将创建一个对应的数据库文件并开始新的优化过程。假设优化过程被打断了,只要 optuna 监测到`’sqlite:///example.db’ 在路径上存在且该数据库中有 study_name 为 ‘example-study’ 的记录,它就会继续未完成的优化过程。如需查看数据库中的数据,只需:
df = study.trials_dataframe(attrs=('number', 'value', 'params', 'state'))
study.optimize()
启动优化。函数原型:
optimize(func: Callable[[optuna.trial._trial.Trial], Union[float, Sequence[float]]], n_trials: Optional[int] = None, timeout: Optional[float] = None, n_jobs: int = 1, catch: Tuple[Type[Exception], …] = (), callbacks: Optional[List[Callable[[Study, optuna.trial._frozen.FrozenTrial], None]]] = None, gc_after_trial: bool = False, show_progress_bar: bool = False)
参数说明:
- func:objective函数
- n_trials:试验次数。如果此参数设置为“无”,则对试验次数没有限制。如果timeout也设置为None,研究将继续创建试验,直到收到终止信号,如Ctrl+C或SIGTERM。
- timeout:在给定的秒数后停止学习。如果此参数设置为“无”,则执行研究时不受时间限制。如果n_trials也设置为None,则研究将继续创建试验,直到收到终止信号,如Ctrl+C或SIGTERM。
- n_jobs:并行作业的数量。如果此参数设置为-1,则数字设置为CPU计数。
- catch:当执行到特定错误的时候即停止学习,默认为空。
- callbacks:每次试用结束时调用的回调函数的列表。每个函数必须接受以下类型的两个参数:Study和FrozenTrial。
- gc_after_trial:用于确定是否在每次尝试后自动运行垃圾回收的标志。设置为True以运行垃圾收集,否则设置为False。当它运行时,它通过内部调用collect(). 如果在几次试验中发现内存消耗增加,请尝试将此标志设置为True。
- show_progress_bar:是否显示进度条的标志。要禁用进度条,请将此设置为False。目前,进度条是实验性功能,当n_jobs≠1时禁用。
剪枝
为了用最简单的形式实现剪枝算法,Optuna 为以下库提供了集成模块。关于 Optuna 集成模块的完整列表,参见 optuna.integration.比如,XGBoostPruningCallback 在没有改变训练迭代过程的逻辑的情况下引入了剪枝。
pruning_callback = optuna.integration.XGBoostPruningCallback(trial, 'validation-error') bst = xgb.train(param, dtrain, evals=[(dvalid, 'validation')], callbacks=[pruning_callback])
Optuna实战:LightGBM调参
import pandas as pd import numpy as np import lightgbm as lgb import optuna from sklearn.model_selection import train_test_split from sklearn.metrics import roc_auc_score def objective(trial): df_data = pd.read_csv("example.csv") data = df_data.iloc[:, :-1] target = df_data.iloc[:, -1] train_x, valid_x, train_y, valid_y = train_test_split(data, target, test_size=0.25) dtrain = lgb.Dataset(train_x, label=train_y, categorical_feature=cols_to_encode) dvalid = lgb.Dataset(valid_x, label=valid_y, categorical_feature=cols_to_encode) param = { "objective": "binary", "is_unbalance": True, "metric": "auc", "verbosity": -1, "lambda_l1": trial.suggest_float("lambda_l1", 1e-8, 10.0, log=True), "lambda_l2": trial.suggest_float("lambda_l2", 1e-8, 10.0, log=True), "num_leaves": trial.suggest_int("num_leaves", 2, 256), "feature_fraction": trial.suggest_float("feature_fraction", 0.4, 1.0), "bagging_fraction": trial.suggest_float("bagging_fraction", 0.4, 1.0), "bagging_freq": trial.suggest_int("bagging_freq", 1, 7), 'learning_rate': trial.suggest_loguniform("learning_rate", 1e-4, 1), "min_child_samples": trial.suggest_int("min_child_samples", 5, 100), "cat_smooth": trial.suggest_int("cat_smooth", 0, 100), } pruning_callback = optuna.integration.LightGBMPruningCallback(trial, "auc") gbm = lgb.train( param, dtrain, valid_sets=[dvalid], verbose_eval=False, callbacks=[pruning_callback] ) preds = gbm.predict(valid_x) recall = roc_auc_score(valid_y, pred_labels) return recall study = optuna.create_study(pruner=optuna.pruners.MedianPruner(n_warmup_steps=10), direction="maximize", study_name='example', storage='sqlite:///example.db') study.optimize(objective, n_trials=50) print("Number of finished trials: {}".format(len(study.trials))) print("Best trial:") trial = study.best_trial print(" Value: {}".format(trial.value)) print(" Params: ") for key, value in trial.params.items(): print(" {}: {}".format(key, value))
Optuna的visualization 采用 plotly 来创建图表,但是 JupyterLab 无法在默认情况下渲染这些图表。目前还没尝试出解决方案。
参考链接: