1. 程式人生 > >在Hyperopt框架下使用XGboost與交叉驗證

在Hyperopt框架下使用XGboost與交叉驗證

H y p e r o p t  

t u t o r i a l s
X g b o o s t + C V
Hyperopt \ tutorials: Xgboost + CV

前言

Xgboost中內建了交叉驗證,如果我們需要在Hyperopt中使用交叉驗證的話,只需要直接呼叫即可。前邊我們依舊採用第一篇教程使用過的程式碼。如果你已經看過前一篇文章,那麼我建議你直接跳到交叉驗證部分。

第一篇教程-如何使用hyperopt對xgboost進行自動調參相同的是,本處教程中的程式碼也可以很好的被套用。並且可以很好的遷移到lightgbm與catboost上面。原始碼請前往Github教程地址下載下載。

載入資料

讀取資料並分割

因為我們這裡使用的是交叉驗證因此我們也就不再需要,將資料集分割為三份了,只需要分割出百分之十的資料用於預測就好。注意隨機數的問題。

from hyperopt import fmin, tpe, hp, partial
import numpy as np
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import mean_squared_error, zero_one_loss
import xgboost as xgb
import pandas as pd

def GetNewDataByPandas():
    wine = pd.read_csv("../data/wine.csv")
    wine['alcohol**2'] = pow(wine["alcohol"], 2)
    wine['volatileAcidity*alcohol'] = wine["alcohol"] * wine['volatile acidity']
    y = np.array(wine.quality)
    X = np.array(wine.drop("quality", axis=1))

    columns = np.array(wine.columns)

    return X, y, columns

分割資料並轉換

首先將資料分割為三份,一部分用於預測,訓練資料則同樣分成額外的兩部分用於evallist引數。

同時為了加快速度和減少記憶體,我們將資料轉換為xgboost自帶的讀取格式。

# Read wine quality data from file
X, y, wineNames = GetNewDataByPandas()

# split data to [[0.8,0.2],01]
x_train_all, x_predict, y_train_all, y_predict = train_test_split(X, y, test_size=0.10, random_state=100)

x_train, x_test, y_train, y_test = train_test_split(x_train_all, y_train_all, test_size=0.2, random_state=100)

dtrain = xgb.DMatrix(data=x_train,label=y_train,missing=-999.0)
dtest = xgb.DMatrix(data=x_test,label=y_test,missing=-999.0)

evallist = [(dtest, 'eval'), (dtrain, 'train')]

利用交叉驗證訓練

訓練模型

在之前的程式碼中,我將資料分割為 6:3:1,其分別為,訓練資料,效能監視用資料,和最後的預測資料。這個比例只是為了示例用,並不具有代表性。

本處則主要介紹交叉驗證方法。

交叉驗證與Hyperopt結合

xgboost進行交叉驗證與Hyperopt結合有兩種方案,第一種方案是使用本身自帶的CV方法,但是這種方案的存在一個問題,就是CV中無法直接傳遞分開的引數,而只能傳遞唯一引數params,因此我們需要先生成一個model,然後通過get_params()來獲取引數,這種方式程式碼會稍微多幾行。不過相較於與sklearn結合的形式,計算時間上則有很大的提升。

第二種方案:也可以直接使用sklearn.model_selection中的多種交叉驗證方案,只將xgboost作為單個模型傳入。但是個人並不建議這樣做。首先是和sklearn的互動會一定程度上導致計算效能的下降,而且計算時間上差距可能會很大。所以個人建議只有在必要的時候,可以採用最傳統的train_test_split方案時使用類似的程式碼結構,但是還是不適用和sklearn結合形式的交叉驗證,具體怎麼做會在其他例子中介紹。

使用CV方法進行交叉驗證

直接使用xgboost中的cv方法進行交叉驗證應該是最好的方案。唯一的問題是如何傳入唯一的params引數。不知道為什麼在我翻譯玩hyperopt中文文件的這近一年,都沒有見到有人寫這一類的。有可能是沒想到如何解決吧這個問題吧。

import hyperopt

def hyperopt_objective(params):
    
    model = xgb.XGBRegressor(
        max_depth=int(params['max_depth'])+5,
        learning_rate=params['learning_rate'],
        silent=1,
        objective='reg:linear',
        eval_metric='rmse',
        seed=619,
        nthread=-1,
    )
     
    res = xgb.cv(model.get_params(), dtrain, num_boost_round=10, nfold=5,
             callbacks=[xgb.callback.print_evaluation(show_stdv=False),
                        xgb.callback.early_stop(3)])
    
    return np.min(res['test-rmse-mean']) # as hyperopt minimises
from numpy.random import RandomState

params_space = {
    'max_depth': hyperopt.hp.randint('max_depth', 6),
    'learning_rate': hyperopt.hp.uniform('learning_rate', 1e-3, 5e-1),
}

trials = hyperopt.Trials()

best = hyperopt.fmin(
    hyperopt_objective,
    space=params_space,
    algo=hyperopt.tpe.suggest,
    max_evals=50,
    trials=trials,
    rstate=RandomState(123)
)

print("\n展示hyperopt獲取的最佳結果,但是要注意的是我們對hyperopt最初的取值範圍做過一次轉換")
print(best)
[0]	train-rmse:3.24774	test-rmse:3.24823
Multiple eval metrics have been passed: 'test-rmse' will be used for early stopping.

Will train until test-rmse hasn't improved in 3 rounds.
[1]	train-rmse:2.07359	test-rmse:2.0891
[2]	train-rmse:1.36012	test-rmse:1.40987
[3]	train-rmse:0.928331	test-rmse:1.02378
[4]	train-rmse:0.657333	test-rmse:0.827554
[5]	train-rmse:0.488443	test-rmse:0.732572
[6]	train-rmse:0.382361	test-rmse:0.690592
[7]	train-rmse:0.310456	test-rmse:0.669706
[8]	train-rmse:0.257609	test-rmse:0.661822
[9]	train-rmse:0.216769	test-rmse:0.657073
[0]	train-rmse:3.09603	test-rmse:3.09669
Multiple eval metrics have been passed: 'test-rmse' will be used for early stopping.

...

[5]	train-rmse:0.511304	test-rmse:0.670014
[6]	train-rmse:0.473608	test-rmse:0.65754
[7]	train-rmse:0.450189	test-rmse:0.647339
[8]	train-rmse:0.428634	test-rmse:0.642954
[9]	train-rmse:0.410831	test-rmse:0.64032

展示hyperopt獲取的最佳結果,但是要注意的是我們對hyperopt最初的取值範圍做過一次轉換
{'learning_rate': 0.44307009444016143, 'max_depth': 0}

使用sklearn進行交叉驗證

這裡要注意的是運算速度上也是有一定差距的。而且可能很大。本例運行於我個人的筆記本時候,與sklearn結合的方案約是完全使用xgboost的3-6倍

def XGBRegressor_CV(params):
    from sklearn.model_selection import cross_val_score
    
    model = xgb.XGBRegressor(
        max_depth=int(params['max_depth'])+5,
        learning_rate=params['learning_rate'],
        silent=1,
        objective='reg:linear',
        eval_metric='rmse',
        seed=619,
        nthread=-1,
        early_stopping_rounds=3
    )

#     x_train, x_predict, y_train, y_predict
    metric = cross_val_score(model, x_train, y_train, cv=5, scoring="neg_mean_squared_error")

    return min(-metric)
trials_2 = hyperopt.Trials()

best_2 = hyperopt.fmin(
    XGBRegressor_CV,
    space=params_space,
    algo=hyperopt.tpe.suggest,
    max_evals=50,
    trials=trials_2,
    rstate=RandomState(123)
)

print("\n展示hyperopt獲取的最佳結果,但是要注意的是我們對hyperopt最初的取值範圍做過一次轉換")
print(best_2)
展示hyperopt獲取的最佳結果,但是要注意的是我們對hyperopt最初的取值範圍做過一次轉換
{'learning_rate': 0.06047936704428278, 'max_depth': 4}