OPTUNA+LIGHTGBM自動(dòng)化調(diào)參

最近在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 類:

  1. 影響決策樹結(jié)構(gòu)和學(xué)習(xí)的參數(shù)
  2. 影響訓(xùn)練速度的參數(shù)
  3. 提高精度的參數(shù)
  4. 防止過擬合的參數(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)?LGBMleaf-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í)率。換句話說,就是要找到LGBMn_estimatorslearning_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_l1lambda_l2 對應(yīng)著 L1L2 正則化,和 XGBoostreg_lambdareg_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ù)含義和 XGBoostgamma 是一樣。比較保守的搜索范圍是 (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_intsuggest_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_valuebast_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)參工具介紹,敬請期待。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容