當GridSearch遇上XGBoost 一段程式碼解決調參問題

香橙雲子發表於2018-08-17

寫在最前

資料比賽,GBM(Gredient Boosting Machine)少不了,我們最常見的就是XGBoost和LightGBM。

模型是在資料比賽中尤為重要的,但是實際上,在比賽的過程中,大部分朋友在模型上花的時間卻是相對較少的,大家都傾向於將寶貴的時間留在特徵提取與模型融合這些方面。在實戰中,我們會先做一個baseline的demo,儘可能快儘可能多的挖掘出模型的潛力,以便後期將精力花在特徵和模型融合上。這裡就需要一些調參功底。

本文從這兩種模型的一共百餘引數中選取重要的十餘個進行探討研究。並給大家展示快速輕量級的調參方式。當然,有更高一步要求的朋友,還是得戳LightGBMXGBoost這兩個官方文件連結。

為了更好的試驗,我將預處理後的資料放在百度雲上。大家戳連結下載。

XGBoost 的一些重要引數

XGBoost的引數一共分為三類:

  1. 通用引數:巨集觀函式控制。
  2. Booster引數:控制每一步的booster(tree/regression)。booster引數一般可以調控模型的效果和計算代價。我們所說的調參,很這是大程度上都是在調整booster引數。
  3. 學習目標引數:控制訓練目標的表現。我們對於問題的劃分主要體現在學習目標引數上。比如我們要做分類還是迴歸,做二分類還是多分類,這都是目標引數所提供的。

Note: 我下面介紹的引數都是我覺得比較重要的, 完整引數請戳官方文件

通用引數

  1. booster:我們有兩種引數選擇,gbtreegblinear。gbtree是採用樹的結構來執行資料,而gblinear是基於線性模型。
  2. silent:靜默模式,為1時模型執行不輸出。
  3. nthread: 使用執行緒數,一般我們設定成-1,使用所有執行緒。如果有需要,我們設定成多少就是用多少執行緒。

Booster引數

  1. n_estimator: 也作num_boosting_rounds

    這是生成的最大樹的數目,也是最大的迭代次數。

  2. learning_rate: 有時也叫作eta,系統預設值為0.3,。

    每一步迭代的步長,很重要。太大了執行準確率不高,太小了執行速度慢。我們一般使用比預設值小一點,0.1左右就很好。

  3. gamma:系統預設為0,我們也常用0

    在節點分裂時,只有分裂後損失函式的值下降了,才會分裂這個節點。gamma指定了節點分裂所需的最小損失函式下降值。 這個引數的值越大,演算法越保守。因為gamma值越大的時候,損失函式下降更多才可以分裂節點。所以樹生成的時候更不容易分裂節點。範圍: [0,∞]

  4. subsample:系統預設為1

    這個引數控制對於每棵樹,隨機取樣的比例。減小這個引數的值,演算法會更加保守,避免過擬合。但是,如果這個值設定得過小,它可能會導致欠擬合。 典型值:0.5-10.5代表平均取樣,防止過擬合. 範圍: (0,1]注意不可取0

  5. colsample_bytree:系統預設值為1。我們一般設定成0.8左右。

    用來控制每棵隨機取樣的列數的佔比(每一列是一個特徵)。 典型值:0.5-1範圍: (0,1]

  6. colsample_bylevel:預設為1,我們也設定為1.

    這個就相比於前一個更加細緻了,它指的是每棵樹每次節點分裂的時候列取樣的比例

  7. max_depth: 系統預設值為6

    我們常用3-10之間的數字。這個值為樹的最大深度。這個值是用來控制過擬合的。max_depth越大,模型學習的更加具體。設定為0代表沒有限制,範圍: [0,∞]

  8. max_delta_step:預設0,我們常用0.

    這個引數限制了每棵樹權重改變的最大步長,如果這個引數的值為0,則意味著沒有約束。如果他被賦予了某一個正值,則是這個演算法更加保守。通常,這個引數我們不需要設定,但是當個類別的樣本極不平衡的時候,這個引數對邏輯迴歸優化器是很有幫助的。

  9. lambda:也稱reg_lambda,預設值為0

    權重的L2正則化項。(和Ridge regression類似)。這個引數是用來控制XGBoost的正則化部分的。這個引數在減少過擬合上很有幫助。

  10. alpha:也稱reg_alpha預設為0,

    權重的L1正則化項。(和Lasso regression類似)。 可以應用在很高維度的情況下,使得演算法的速度更快。

  11. scale_pos_weight:預設為1

    在各類別樣本十分不平衡時,把這個引數設定為一個正值,可以使演算法更快收斂。通常可以將其設定為負樣本的數目與正樣本數目的比值。

學習目標引數

objective [預設值=reg:linear]

  • reg:linear– 線性迴歸
  • reg:logistic – 邏輯迴歸
  • binary:logistic – 二分類邏輯迴歸,輸出為概率
  • binary:logitraw – 二分類邏輯迴歸,輸出的結果為wTx
  • count:poisson – 計數問題的poisson迴歸,輸出結果為poisson分佈。在poisson迴歸中,max_delta_step的預設值為0.7 (used to safeguard optimization)
  • multi:softmax – 設定 XGBoost 使用softmax目標函式做多分類,需要設定引數num_class(類別個數)
  • multi:softprob – 如同softmax,但是輸出結果為ndata*nclass的向量,其中的值是每個資料分為每個類的概率。

eval_metric [預設值=通過目標函式選擇]

  • rmse: 均方根誤差
  • mae: 平均絕對值誤差
  • logloss: negative log-likelihood
  • error: 二分類錯誤率。其值通過錯誤分類數目與全部分類數目比值得到。對於預測,預測值大於0.5被認為是正類,其它歸為負類。 error@t: 不同的劃分閾值可以通過 ‘t’進行設定
  • merror: 多分類錯誤率,計算公式為(wrong cases)/(all cases)
  • mlogloss: 多分類log損失
  • auc: 曲線下的面積
  • ndcg: Normalized Discounted Cumulative Gain
  • map: 平均正確率

一般來說,我們都會使用xgboost.train(params, dtrain)函式來訓練我們的模型。這裡的params指的是booster引數。

兩種基本的例項

我們要注意的是,在xgboost中想要進行二分類處理的時候,我們僅僅在 objective中設定成 binary,會發現輸出仍然是一堆連續的值。這是因為它輸出的是模型預測的所有概率中最大的那個值。我們可以後續對這些概率進行條件處理得到最終類別,或者直接呼叫xgboost中的XGBClassifier()類,但這兩種函式的寫法不太一樣。大家看我下面的例子。

import xgboost as xgb
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score

train_data = pd.read_csv('train.csv')   # 讀取資料
y = train_data.pop('30').values   # 用pop方式將訓練資料中的標籤值y取出來,作為訓練目標,這裡的‘30’是標籤的列名
col = train_data.columns   
x = train_data[col].values  # 剩下的列作為訓練資料
train_x, valid_x, train_y, valid_y = train_test_split(x, y, test_size=0.333, random_state=0)   # 分訓練集和驗證集
train = xgb.DMatrix(train_x, train_y)
valid = xgb.DMatrix(valid_x, valid_y)   # train函式下需要傳入一個Dmatrix值,具體用法如程式碼所示

params = {
            'max_depth': 15,
            'learning_rate': 0.1,
            'n_estimators': 2000,
            'min_child_weight': 5,
            'max_delta_step': 0,
            'subsample': 0.8,
            'colsample_bytree': 0.7,
            'reg_alpha': 0,
            'reg_lambda': 0.4,
            'scale_pos_weight': 0.8,
            'silent': True,
            'objective': 'binary:logistic',
            'missing': None,
            'eval_metric': 'auc',
            'seed': 1440,
            'gamma': 0
}    # 這裡的params特指booster引數,注意這個eva_metric是評估函式

xlf = xgb.train(params, train, evals=[(valid, 'eval')], 
                num_boost_round=2000, early_stopping_rounds=30, verbose_eval=True)   
                # 訓練,注意驗證集的寫法, 還有early_stopping寫法,這裡指的是30輪迭代中效果未增長便停止訓練
y_pred = xlf.predict(valid_x, ntree_limit=xlf.best_ntree_limit) 
# xgboost沒有直接使用效果最好的樹作為模型的機制,這裡採用最大樹深限制的方法,目的是獲取剛剛early_stopping效果最好的,實測效能可以
auc_score = roc_auc_score(valid_y, y_pred)  # 算一下預測結果的roc值

複製程式碼

以上是xgboost.train()寫法,這是xgboost最原始的封裝函式。這樣訓練我們預測輸出的是一串連續值,是xgboost在這幾個類別上概率最大的概率值。我們如果想要得到我們的分類結果,還需要進行其他操作。

幸運的是,xgboost為了貼合sklearn的使用,比如gridsearch這些實用工具,又開發了XGBoostClassifier()XGBoostRegression()兩個函式。可以更加簡單快捷的進行分類和迴歸處理。注意xgboost的sklearn包沒有 feature_importance 這個量度,但是get_fscore()函式有相同的功能。當然,為了和sklearn保持一致,寫法也發生變化,具體請看下面程式碼:

import xgboost as xgb
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score

train_data = pd.read_csv('train.csv')   # 讀取資料
y = train_data.pop('30').values   # 用pop方式將訓練資料中的標籤值y取出來,作為訓練目標,這裡的‘30’是標籤的列名
col = train_data.columns   
x = train_data[col].values  # 剩下的列作為訓練資料
train_x, valid_x, train_y, valid_y = train_test_split(x, y, test_size=0.333, random_state=0)   # 分訓練集和驗證集
# 這裡不需要Dmatrix

xlf = xgb.XGBClassifier(max_depth=10,
			learning_rate=0.01,
			n_estimators=2000,
			silent=True,
			objective='binary:logistic',
			nthread=-1,
			gamma=0,
			min_child_weight=1,
			max_delta_step=0,
			subsample=0.85,
			colsample_bytree=0.7,
			colsample_bylevel=1,
			reg_alpha=0,
			reg_lambda=1,
			scale_pos_weight=1,
			seed=1440,
			missing=None)
xlf.fit(train_x, train_y, eval_metric='error', verbose=True, eval_set=[(valid_x, valid_y)], early_stopping_rounds=30)
# 這個verbose主要是調節系統輸出的,如果設定成10,便是每迭代10次就有輸出。
# 注意我們這裡eval_metric=‘error’便是準確率。這裡面並沒有accuracy命名的函式,網上大多例子為auc,我這裡特意放了個error。
y_pred = xlf.predict(valid_x, ntree_limit=xlf.best_ntree_limit)
auc_score = roc_auc_score(valid_y, y_pred)
y_pred = xlf.predict(valid_x, ntree_limit=xlf.best_ntree_limit) 
# xgboost沒有直接使用效果最好的樹作為模型的機制,這裡採用最大樹深限制的方法,目的是獲取剛剛early_stopping效果最好的,實測效能可以
auc_score = roc_auc_score(valid_y, y_pred)  # 算一下預測結果的roc值

複製程式碼

那麼我們介紹了這麼多,重點就來了:如何又快又好的調參?首先我們需要了解grid search是個什麼原理。

GridSearch 簡介

這是一種調參手段;窮舉搜尋:在所有候選的引數選擇中,通過迴圈遍歷,嘗試每一種可能性,表現最好的引數就是最終的結果。其原理就像是在陣列裡找最大值。(為什麼叫網格搜尋?以有兩個引數的模型為例,引數a有3種可能,引數b有4種可能,把所有可能性列出來,可以表示成一個3*4的表格,其中每個cell就是一個網格,迴圈過程就像是在每個網格里遍歷、搜尋,所以叫grid search)

其實這個就跟我們常用的遍歷是一樣的。建議大家使用sklearn裡面的GridSearch函式,簡潔速度快。

import xgboost as xgb
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score

train_data = pd.read_csv('train.csv')   # 讀取資料
y = train_data.pop('30').values   # 用pop方式將訓練資料中的標籤值y取出來,作為訓練目標,這裡的‘30’是標籤的列名
col = train_data.columns   
x = train_data[col].values  # 剩下的列作為訓練資料
train_x, valid_x, train_y, valid_y = train_test_split(x, y, test_size=0.333, random_state=0)   # 分訓練集和驗證集
# 這裡不需要Dmatrix

parameters = {
              'max_depth': [5, 10, 15, 20, 25],
              'learning_rate': [0.01, 0.02, 0.05, 0.1, 0.15],
              'n_estimators': [500, 1000, 2000, 3000, 5000],
              'min_child_weight': [0, 2, 5, 10, 20],
              'max_delta_step': [0, 0.2, 0.6, 1, 2],
              'subsample': [0.6, 0.7, 0.8, 0.85, 0.95],
              'colsample_bytree': [0.5, 0.6, 0.7, 0.8, 0.9],
              'reg_alpha': [0, 0.25, 0.5, 0.75, 1],
              'reg_lambda': [0.2, 0.4, 0.6, 0.8, 1],
              'scale_pos_weight': [0.2, 0.4, 0.6, 0.8, 1]

}

xlf = xgb.XGBClassifier(max_depth=10,
			learning_rate=0.01,
			n_estimators=2000,
			silent=True,
			objective='binary:logistic',
			nthread=-1,
			gamma=0,
			min_child_weight=1,
			max_delta_step=0,
			subsample=0.85,
			colsample_bytree=0.7,
			colsample_bylevel=1,
			reg_alpha=0,
			reg_lambda=1,
			scale_pos_weight=1,
			seed=1440,
			missing=None)
			
# 有了gridsearch我們便不需要fit函式
gsearch = GridSearchCV(xlf, param_grid=parameters, scoring='accuracy', cv=3)
gsearch.fit(train_x, train_y)

print("Best score: %0.3f" % gsearch.best_score_)
print("Best parameters set:")
best_parameters = gsearch.best_estimator_.get_params()
for param_name in sorted(parameters.keys()):
    print("\t%s: %r" % (param_name, best_parameters[param_name]))

複製程式碼

我們需要注意的是,Grid Search 需要交叉驗證支援的。這裡的cv=3,是個int數,就代表3-折驗證。實際上cv可以是一個物件,也可以是其他型別。分別代表不同的方式驗證。具體的大家可看下面這段表述。

Possible inputs for cv are:
        - None, to use the default 3-fold cross-validation,
        - integer, to specify the number of folds.
        - An object to be used as a cross-validation generator.
        - An iterable yielding train/test splits.
複製程式碼

參考文獻:

機器學習之-xgboost

相關文章