实践教程 | LGBM 模型+OPTUNA调参组合
共 12394字,需浏览 25分钟
·
2021-09-27 18:12
极市导读
本文是作者使用LGBM模型+OPTUNA调参的经验教程,这对可谓是非常实用且容易上分的神器组合了,实际工作中也可使用。 >>加入极市CV技术交流群,走在计算机视觉的最前沿
大家好,最近在kaggle
上有一个调参神器非常热门,在top方案中频频出现,它就是OPTUNA
。知道很多小伙伴苦恼于漫长的调参时间里,这次结合一些自己的经验,给大家带来一个LGBM
模型+OPTUNA
调参的使用教程,这对可谓是非常实用且容易上分的神器组合了,实际工作中也可使用。
关于LightGBM
不多说了,之前分享过很多文章,它是在XGBoost
基础上对效率提升的优化版本,由微软发布的,运行效率极高,且准确度不降。目前是公认比较好,且广泛使用的机器学习模型了,分类回归均可满足。
关于调参,也就是模型的超参数调优,可能你会想到GridSearch
。确实最开始我也在用GridSearch
,暴力美学虽然好,但它的缺点很明显,运行太耗时,时间成本太高。相比之下,基于贝叶斯框架下的调参工具就舒服多了。这类开源工具也很多,常见的比如HyperOPT
。当然今天主角不是它,而是另外一个更香的OPTUNA
,轻量级且功能更强大,速度也是快到起飞!
因为需要用 LGBM
配合举例讲解,下面先从 LGBM
的几个主要超参数开始介绍,然后再根据这些超参设置 Optuna
进行调参。
LightGBM参数概述
通常,基于树的模型的超参数可以分为 4 类:
影响决策树结构和学习的参数 影响训练速度的参数 提高精度的参数 防止过拟合的参数
大多数时候,这些类别有很多重叠,提高一个类别的效率可能会降低另一个类别的效率。如果完全靠手动调参,那会比较痛苦。所以前期我们可以利用一些自动化调参工具给出一个大致的结果,而自动调参工具的核心在于如何给定适合的参数区间范围。 如果能给定合适的参数网格,Optuna
就可以自动找到这些类别之间最平衡的参数组合。
下面对LGBM
的4类超参进行介绍。
控制树结构的超参数
max_depth 和 num_leaves
在 LGBM
中,控制树结构的最先要调的参数是max_depth
(树深度) 和 num_leaves
(叶子节点数)。这两个参数对于树结构的控制最直接了断,因为 LGBM
是 leaf-wise
的,如果不控制树深度,会非常容易过拟合。max_depth
一般设置可以尝试设置为3到8
。
这两个参数也存在一定的关系。由于是二叉树,num_leaves
最大值应该是2^(max_depth)
。所以,确定了max_depth
也就意味着确定了num_leaves
的取值范围。
min_data_in_leaf
树的另一个重要结构参数是min_data_in_leaf
,它的大小也与是否过拟合有关。它指定了叶子节点向下分裂的的最小样本数,比如设置100,那么如果节点样本数量不够100就停止生长。当然,min_data_in_leaf
的设定也取决于训练样本的数量和num_leaves
。对于大数据集,一般会设置千级以上。
提高准确性的超参数
learning_rate 和 n_estimators
实现更高准确率的常见方法是使用更多棵子树并降低学习率。换句话说,就是要找到LGBM
中n_estimators
和learning_rate
的最佳组合。
n_estimators
控制决策树的数量,而learning_rate
是梯度下降的步长参数。经验来说,LGBM
比较容易过拟合,learning_rate
可以用来控制梯度提升学习的速度,一般值可设在 0.01 和 0.3
之间。一般做法是先用稍多一些的子树比如1000,并设一个较低的learning_rate
,然后通过early_stopping
找到最优迭代次数。
max_bin
除此外,也可以增加max_bin
(默认值为255)来提高准确率。因为变量分箱的数量越多,信息保留越详细,相反,变量分箱数量越低,信息越损失,但更容易泛化。这个和特征工程的分箱是一个道理,只不过是通过内部的hist
直方图算法处理了。如果max_bin
过高,同样也存在过度拟合的风险。
更多超参数来控制过拟合
lambda_l1 和 lambda_l2
lambda_l1
和 lambda_l2
对应着 L1
和 L2
正则化,和 XGBoost
的 reg_lambda
和 reg_alpha
是一样的,对叶子节点数和叶子节点权重的惩罚,值越高惩罚越大。这些参数的最佳值更难调整,因为它们的大小与过拟合没有直接关系,但会有影响。一般的搜索范围可以在 (0, 100)
。
min_gain_to_split
这个参数定义着分裂的最小增益。这个参数也看出数据的质量如何,计算的增益不高,就无法向下分裂。如果你设置的深度很深,但又无法向下分裂,LGBM
就会提示warning
,无法找到可以分裂的了。参数含义和 XGBoost
的 gamma
是一样,说明数据质量已经达到了极限了。比较保守的搜索范围是 (0, 20)
,它可以用作大型参数网格中的额外正则化。
bagging_fraction 和 feature_fraction
这两个参数取值范围都在(0,1)
之间。
feature_fraction
指定训练每棵树时要采样的特征百分比,它存在的意义也是为了避免过拟合。因为有些特征增益很高,可能造成每棵子树分裂的时候都会用到同一个特征,这样每个子树就同质化了。而如果通过较低概率的特征采样,可以避免每次都遇到一样的强特征,从而让子树的特征变得差异化,即泛化。
bagging_fraction
指定用于训练每棵树的训练样本百分比。要使用这个参数,还需要设置 bagging_freq
,道理和feature_fraction
一样,也是让没棵子树都变得好而不同。
在 Optuna 中创建搜索网格
Optuna
中的优化过程首先需要一个目标函数,该函数里面包括:
字典形式的参数网格 创建一个模型(可以配合交叉验证 kfold
)来尝试超参数组合集用于模型训练的数据集 使用此模型生成预测 根据用户定义的指标对预测进行评分并返回
下面给出一个常用的框架,模型是5折的Kfold
,这样可以保证模型的稳定性。最后一行返回了需要优化的 CV 分数的平均值。目标函数可以自己设定,比如指标logloss
最小,auc
最大,ks
最大,训练集和测试集的auc
差距最小等等。
import optuna # pip install optunafrom sklearn.metrics import log_lossfrom sklearn.model_selection import StratifiedKFolddef objective(trial, X, y): # 后面填充 param_grid = {} cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=1121218) cv_scores = np.empty(5) for idx, (train_idx, test_idx) in enumerate(cv.split(X, y)): X_train, X_test = X.iloc[train_idx], X.iloc[test_idx] y_train, y_test = y[train_idx], y[test_idx] model = lgbm.LGBMClassifier(objective="binary", **param_grid) model.fit( X_train, y_train, eval_set=[(X_test, y_test)], eval_metric="binary_logloss", early_stopping_rounds=100, ) preds = model.predict_proba(X_test) cv_scores[idx] = preds return np.mean(cv_scores)
下面是参数的设置,Optuna
比较常见的参数设置方式有suggest_categorical
,suggest_int
,suggest_float
。其中,suggest_int
和suggest_float
的设置方式为(参数,最小值,最大值,step=步长)
。
def objective(trial, X, y): # 字典形式的参数网格 param_grid = { "n_estimators": trial.suggest_categorical("n_estimators", [10000]), "learning_rate": trial.suggest_float("learning_rate", 0.01, 0.3), "num_leaves": trial.suggest_int("num_leaves", 20, 3000, step=20), "max_depth": trial.suggest_int("max_depth", 3, 12), "min_data_in_leaf": trial.suggest_int("min_data_in_leaf", 200, 10000, step=100), "max_bin": trial.suggest_int("max_bin", 200, 300), "lambda_l1": trial.suggest_int("lambda_l1", 0, 100, step=5), "lambda_l2": trial.suggest_int("lambda_l2", 0, 100, step=5), "min_gain_to_split": trial.suggest_float("min_gain_to_split", 0, 15), "bagging_fraction": trial.suggest_float( "bagging_fraction", 0.2, 0.95, step=0.1 ), "bagging_freq": trial.suggest_categorical("bagging_freq", [1]), "feature_fraction": trial.suggest_float( "feature_fraction", 0.2, 0.95, step=0.1 ), }
创建 Optuna 自动调起来
下面是完整的目标函数框架,供参考:
from optuna.integration import LightGBMPruningCallbackdef objective(trial, X, y): # 参数网格 param_grid = { "n_estimators": trial.suggest_categorical("n_estimators", [10000]), "learning_rate": trial.suggest_float("learning_rate", 0.01, 0.3), "num_leaves": trial.suggest_int("num_leaves", 20, 3000, step=20), "max_depth": trial.suggest_int("max_depth", 3, 12), "min_data_in_leaf": trial.suggest_int("min_data_in_leaf", 200, 10000, step=100), "lambda_l1": trial.suggest_int("lambda_l1", 0, 100, step=5), "lambda_l2": trial.suggest_int("lambda_l2", 0, 100, step=5), "min_gain_to_split": trial.suggest_float("min_gain_to_split", 0, 15), "bagging_fraction": trial.suggest_float("bagging_fraction", 0.2, 0.95, step=0.1), "bagging_freq": trial.suggest_categorical("bagging_freq", [1]), "feature_fraction": trial.suggest_float("feature_fraction", 0.2, 0.95, step=0.1), "random_state": 2021, } # 5折交叉验证 cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=1121218) cv_scores = np.empty(5) for idx, (train_idx, test_idx) in enumerate(cv.split(X, y)): X_train, X_test = X.iloc[train_idx], X.iloc[test_idx] y_train, y_test = y[train_idx], y[test_idx] # LGBM建模 model = lgbm.LGBMClassifier(objective="binary", **param_grid) model.fit( X_train, y_train, eval_set=[(X_test, y_test)], eval_metric="binary_logloss", early_stopping_rounds=100, callbacks=[ LightGBMPruningCallback(trial, "binary_logloss") ], ) # 模型预测 preds = model.predict_proba(X_test) # 优化指标logloss最小 cv_scores[idx] = log_loss(y_test, preds) return np.mean(cv_scores)
上面这个网格里,还添加了LightGBMPruningCallback
,这个callback
类很方便,它可以在对数据进行训练之前检测出不太好的超参数集,从而显着减少搜索时间。
设置完目标函数,现在让参数调起来!
study = optuna.create_study(direction="minimize", study_name="LGBM Classifier")func = lambda trial: objective(trial, X, y)study.optimize(func, n_trials=20)
direction
可以是minimize
,也可以是maximize
,比如让auc
最大化。然后可以设置trials
来控制尝试的次数,理论上次数越多结果越优,但也要考虑下运行时间。
搜索完成后,调用best_value
和bast_params
属性,调参就出来了。
print(f"\tBest value (rmse): {study.best_value:.5f}")print(f"\tBest params:")for key, value in study.best_params.items(): print(f"\t\t{key}: {value}") -----------------------------------------------------Best value (binary_logloss): 0.35738 Best params: device: gpu lambda_l1: 7.71800699380605e-05 lambda_l2: 4.17890272377219e-06 bagging_fraction: 0.7000000000000001 feature_fraction: 0.4 bagging_freq: 5 max_depth: 5 num_leaves: 1007 min_data_in_leaf: 45 min_split_gain: 15.703519227860273 learning_rate: 0.010784015325759629 n_estimators: 10000
得到这个参数组合后,我们就可以拿去跑模型了,看结果再手动微调,这样就可以省很多时间了。
结语
本文给出了一个通过Optuna
调参LGBM
的代码框架,使用及其方便,参数区间范围需要根据数据情况自行调整,优化目标可以自定定义,不限于以上代码的logloss
。
关于Optuna
的强大之处,后面会对比同类的调参工具介绍,敬请期待。
如果觉得有用,就请分享到朋友圈吧!
公众号后台回复“CVPR21检测”获取CVPR2021目标检测论文下载~
# CV技术社群邀请函 #
备注:姓名-学校/公司-研究方向-城市(如:小极-北大-目标检测-深圳)
即可申请加入极市目标检测/图像分割/工业检测/人脸/医学影像/3D/SLAM/自动驾驶/超分辨率/姿态估计/ReID/GAN/图像增强/OCR/视频理解等技术交流群
每月大咖直播分享、真实项目需求对接、求职内推、算法竞赛、干货资讯汇总、与 10000+来自港科大、北大、清华、中科院、CMU、腾讯、百度等名校名企视觉开发者互动交流~