最近在kaggle上有一個(gè)調(diào)參神器非常熱門,在top方案中頻頻出現(xiàn),它就是OPTUNA。知道很多小伙伴苦惱于漫長的調(diào)參時(shí)間里,這次結(jié)合一些自己的經(jīng)驗(yàn),給大家?guī)硪粋€(gè)LGBM模型+OPTUNA調(diào)參的使用教程,這對可謂是非常實(shí)用且容易上分的神器組合了,實(shí)際工作中也可使用。
關(guān)于LightGBM不多說了,之前分享過很多文章,它是在XGBoost基礎(chǔ)上對效率提升的優(yōu)化版本,由微軟發(fā)布的,運(yùn)行效率極高,且準(zhǔn)確度不降。目前是公認(rèn)比較好,且廣泛使用的機(jī)器學(xué)習(xí)模型了,分類回歸均可滿足。
關(guān)于調(diào)參,也就是模型的超參數(shù)調(diào)優(yōu),可能你會想到GridSearch。確實(shí)最開始我也在用GridSearch,暴力美學(xué)雖然好,但它的缺點(diǎn)很明顯,運(yùn)行太耗時(shí),時(shí)間成本太高。相比之下,基于貝葉斯框架下的調(diào)參工具就舒服多了。這類開源工具也很多,常見的比如HyperOPT。當(dāng)然今天主角不是它,而是另外一個(gè)更香的OPTUNA,輕量級且功能更強(qiáng)大,速度也是快到起飛!
因?yàn)樾枰?LGBM 配合舉例講解,下面先從 LGBM 的幾個(gè)主要超參數(shù)開始介紹,然后再根據(jù)這些超參設(shè)置 Optuna 進(jìn)行調(diào)參。
LightGBM參數(shù)概述
通常,基于樹的模型的超參數(shù)可以分為 4 類:
- 影響決策樹結(jié)構(gòu)和學(xué)習(xí)的參數(shù)
- 影響訓(xùn)練速度的參數(shù)
- 提高精度的參數(shù)
- 防止過擬合的參數(shù)
大多數(shù)時(shí)候,這些類別有很多重疊,提高一個(gè)類別的效率可能會降低另一個(gè)類別的效率。如果完全靠手動(dòng)調(diào)參,那會比較痛苦。所以前期我們可以利用一些自動(dòng)化調(diào)參工具給出一個(gè)大致的結(jié)果,而自動(dòng)調(diào)參工具的核心在于如何給定適合的參數(shù)區(qū)間范圍。 如果能給定合適的參數(shù)網(wǎng)格,Optuna 就可以自動(dòng)找到這些類別之間最平衡的參數(shù)組合。
下面對LGBM的4類超參進(jìn)行介紹。
1、控制樹結(jié)構(gòu)的超參數(shù)
max_depth 和 num_leaves
在 LGBM 中,控制樹結(jié)構(gòu)的最先要調(diào)的參數(shù)是max_depth(樹深度) 和 num_leaves(葉子節(jié)點(diǎn)數(shù))。這兩個(gè)參數(shù)對于樹結(jié)構(gòu)的控制最直接了斷,因?yàn)?LGBM 是 leaf-wise 的,如果不控制樹深度,會非常容易過擬合。max_depth一般設(shè)置可以嘗試設(shè)置為3到8。
這兩個(gè)參數(shù)也存在一定的關(guān)系。由于是二叉樹,num_leaves最大值應(yīng)該是2^(max_depth)。所以,確定了max_depth也就意味著確定了num_leaves的取值范圍。
min_data_in_leaf
樹的另一個(gè)重要結(jié)構(gòu)參數(shù)是min_data_in_leaf,它的大小也與是否過擬合有關(guān)。它指定了葉子節(jié)點(diǎn)向下分裂的的最小樣本數(shù),比如設(shè)置100,那么如果節(jié)點(diǎn)樣本數(shù)量不夠100就停止生長。當(dāng)然,min_data_in_leaf的設(shè)定也取決于訓(xùn)練樣本的數(shù)量和num_leaves。對于大數(shù)據(jù)集,一般會設(shè)置千級以上。
2、提高準(zhǔn)確性的超參數(shù)
learning_rate 和 n_estimators
實(shí)現(xiàn)更高準(zhǔn)確率的常見方法是使用更多棵子樹并降低學(xué)習(xí)率。換句話說,就是要找到LGBM中n_estimators和learning_rate的最佳組合。
n_estimators控制決策樹的數(shù)量,而learning_rate是梯度下降的步長參數(shù)。經(jīng)驗(yàn)來說,LGBM 比較容易過擬合,learning_rate可以用來控制梯度提升學(xué)習(xí)的速度,一般值可設(shè)在 0.01 和 0.3 之間。一般做法是先用稍多一些的子樹比如1000,并設(shè)一個(gè)較低的learning_rate,然后通過early_stopping找到最優(yōu)迭代次數(shù)。
max_bin
除此外,也可以增加max_bin(默認(rèn)值為255)來提高準(zhǔn)確率。因?yàn)樽兞糠窒涞臄?shù)量越多,信息保留越詳細(xì),相反,變量分箱數(shù)量越低,信息越損失,但更容易泛化。這個(gè)和特征工程的分箱是一個(gè)道理,只不過是通過內(nèi)部的hist直方圖算法處理了。如果max_bin過高,同樣也存在過度擬合的風(fēng)險(xiǎn)。
3、更多超參數(shù)來控制過擬合
lambda_l1 和 lambda_l2
lambda_l1 和 lambda_l2 對應(yīng)著 L1 和 L2 正則化,和 XGBoost 的 reg_lambda 和 reg_alpha 是一樣的,對葉子節(jié)點(diǎn)數(shù)和葉子節(jié)點(diǎn)權(quán)重的懲罰,值越高懲罰越大。這些參數(shù)的最佳值更難調(diào)整,因?yàn)樗鼈兊拇笮∨c過擬合沒有直接關(guān)系,但會有影響。一般的搜索范圍可以在 (0, 100)。
min_gain_to_split
這個(gè)參數(shù)定義著分裂的最小增益。這個(gè)參數(shù)也看出數(shù)據(jù)的質(zhì)量如何,計(jì)算的增益不高,就無法向下分裂。如果你設(shè)置的深度很深,但又無法向下分裂,LGBM就會提示warning,無法找到可以分裂的了,說明數(shù)據(jù)質(zhì)量已經(jīng)達(dá)到了極限了。參數(shù)含義和 XGBoost 的 gamma 是一樣。比較保守的搜索范圍是 (0, 20),它可以用作大型參數(shù)網(wǎng)格中的額外正則化。
bagging_fraction 和 feature_fraction
這兩個(gè)參數(shù)取值范圍都在(0,1)之間。
feature_fraction指定訓(xùn)練每棵樹時(shí)要采樣的特征百分比,它存在的意義也是為了避免過擬合。因?yàn)橛行┨卣髟鲆婧芨?,可能造成每棵子樹分裂的時(shí)候都用同一個(gè)特征,這樣每個(gè)子樹就同質(zhì)化了。而如果通過較低概率的特征采樣,可以避免每次都遇到這些強(qiáng)特征,從而讓子樹的特征變得差異化,即泛化。
bagging_fraction指定用于訓(xùn)練每棵樹的訓(xùn)練樣本百分比。要使用這個(gè)參數(shù),還需要設(shè)置 bagging_freq,道理和feature_fraction一樣,也是讓沒棵子樹都變得好而不同。
4、在 Optuna 中創(chuàng)建搜索網(wǎng)格
Optuna 中的優(yōu)化過程首先需要一個(gè)目標(biāo)函數(shù),該函數(shù)里面包括:
- 字典形式的參數(shù)網(wǎng)格
- 創(chuàng)建一個(gè)模型(可以配合交叉驗(yàn)證
kfold)來嘗試超參數(shù)組合集 - 用于模型訓(xùn)練的數(shù)據(jù)集
- 使用此模型生成預(yù)測
- 根據(jù)用戶定義的指標(biāo)對預(yù)測進(jìn)行評分并返回
下面給出一個(gè)常用的框架,模型是5折的Kfold,這樣可以保證模型的穩(wěn)定性。最后一行返回了需要優(yōu)化的 CV 分?jǐn)?shù)的平均值。目標(biāo)函數(shù)可以自己設(shè)定,比如指標(biāo)logloss最小,auc最大,ks最大,訓(xùn)練集和測試集的auc差距最小等等。
import optuna # pip install optuna
from sklearn.metrics import log_loss
from sklearn.model_selection import StratifiedKFold
def 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)
下面是參數(shù)的設(shè)置,Optuna比較常見的方式suggest_categorical,suggest_int,suggest_float。其中,suggest_int和suggest_float的設(shè)置方式為(參數(shù),最小值,最大值,step=步長)。
def objective(trial, X, y):
# 字典形式的參數(shù)網(wǎng)格
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
),
}
5、創(chuàng)建 Optuna 自動(dòng)調(diào)起來
下面是完整的目標(biāo)函數(shù)框架,供參考:
from optuna.integration import LightGBMPruningCallback
def objective(trial, X, y):
# 參數(shù)網(wǎng)格
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折交叉驗(yàn)證
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")
],
)
# 模型預(yù)測
preds = model.predict_proba(X_test)
# 優(yōu)化指標(biāo)logloss最小
cv_scores[idx] = log_loss(y_test, preds)
return np.mean(cv_scores)
上面這個(gè)網(wǎng)格里,還添加了LightGBMPruningCallback,這個(gè)callback類很方便,它可以在對數(shù)據(jù)進(jìn)行訓(xùn)練之前檢測出不太好的超參數(shù)集,從而顯著減少搜索時(shí)間。
設(shè)置完目標(biāo)函數(shù),現(xiàn)在讓參數(shù)調(diào)起來!
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最大化。然后可以設(shè)置trials來控制嘗試的次數(shù),理論上次數(shù)越多結(jié)果越優(yōu),但也要考慮下運(yùn)行時(shí)間。
搜索完成后,調(diào)用best_value和bast_params屬性,調(diào)參就出來了。
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
得到這個(gè)參數(shù)組合后,我們就可以拿去跑模型了,看結(jié)果再手動(dòng)微調(diào),這樣就可以省很多時(shí)間了。
結(jié)語
本文給出了一個(gè)通過Optuna調(diào)參LGBM的代碼框架,使用及其方便,參數(shù)區(qū)間范圍需要根據(jù)數(shù)據(jù)情況自行調(diào)整,優(yōu)化目標(biāo)可以自定定義,不限于以上代碼的logloss。
關(guān)于Optuna的強(qiáng)大之處,后面會對比同類的調(diào)參工具介紹,敬請期待。