一、xgboost類庫實用小結
在XGBoost算法原理小結中,我們討論了XGBoost的算法原理,這一片我們討論如何使用XGBoost的Python類庫,以及一些重要參數的意義和調參思路。
本文主要參考了XGBoost的Python文檔 和 XGBoost的參數文檔。
1. XGBoost類庫概述
XGBoost除了支持Python外,也支持R,Java等語言。本文關注於Python的XGBoost類庫,安裝使用"pip install xgboost"即可,目前使用的是XGBoost的0.90版本。XGBoost類庫除了支持決策樹作為弱學習器外,還支持線性分類器,以及帶DropOut的決策樹DART,不過通常情況下,我們使用默認的決策樹弱學習器即可,本文也只會討論使用默認決策樹弱學習器的XGBoost。
XGBoost有2種Python接口風格。一種是XGBoost自帶的原生Python API接口,另一種是sklearn風格的API接口,兩者的實現是基本一樣的,僅僅有細微的API使用的不同,主要體現在參數命名上,以及數據集的初始化上面。
2. XGBoost類庫的基本使用方式
完整示例參見我的Github代碼。
2.1 使用原生Python API接口
XGBoost的類庫的2種接口風格,我們先來看看原生Python API接口如何使用。
原生XGBoost需要先把數據集按輸入特征部分,輸出部分分開,然后放到一個DMatrix數據結構里面,這個DMatrix我們不需要關心里面的細節,使用我們的訓練集X和y初始化即可。
import pandas as pd import numpy as np import xgboost as xgb import matplotlib.pylab as plt %matplotlib inline from sklearn.model_selection import GridSearchCV from sklearn.model_selection import train_test_split
from sklearn.datasets.samples_generator import make_classification # X為樣本特征,y為樣本類別輸出, 共10000個樣本,每個樣本20個特征,輸出有2個類別,沒有冗余特征,每個類別一個簇 X, y = make_classification(n_samples=10000, n_features=20, n_redundant=0, n_clusters_per_class=1, n_classes=2, flip_y=0.1)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=1)
dtrain = xgb.DMatrix(X_train,y_train) dtest = xgb.DMatrix(X_test,y_test)
上面的代碼中,我們隨機初始化了一個二分類的數據集,然后分成了訓練集和驗證集。使用訓練集和驗證集分別初始化了一個DMatrix,有了DMatrix,就可以做訓練和預測了。簡單的示例代碼如下:
param = {'max_depth':5, 'eta':0.5, 'verbosity':1, 'objective':'binary:logistic'} raw_model = xgb.train(param, dtrain, num_boost_round=20)
from sklearn.metrics import accuracy_score pred_train_raw = raw_model.predict(dtrain) for i in range(len(pred_train_raw)): if pred_train_raw[i] > 0.5: pred_train_raw[i]=1 else: pred_train_raw[i]=0 print (accuracy_score(dtrain.get_label(), pred_train_raw))
訓練集的准確率我這里輸出是0.9664。再看看驗證集的表現:
pred_test_raw = raw_model.predict(dtest) for i in range(len(pred_test_raw)): if pred_test_raw[i] > 0.5: pred_test_raw[i]=1 else: pred_test_raw[i]=0 print (accuracy_score(dtest.get_label(), pred_test_raw))
驗證集的准確率我這里的輸出是0.9408,已經很高了。
不過對於我這樣用慣sklearn風格API的,還是不太喜歡原生Python API接口,既然有sklearn的wrapper,那么就盡量使用sklearn風格的接口吧。
2.2 使用sklearn風格接口,使用原生參數
對於sklearn風格的接口,主要有2個類可以使用,一個是分類用的XGBClassifier,另一個是回歸用的XGBRegressor。在使用這2個類的使用,對於算法的參數輸入也有2種方式,第一種就是仍然使用和原始API一樣的參數命名集合,另一種是使用sklearn風格的參數命名。我們這里先看看如何使用和原始API一樣的參數命名集合。
其實就是使用XGBClassifier/XGBRegressor的**kwargs參數,把上面原生參數的params集合放進去,代碼如下:
sklearn_model_raw = xgb.XGBClassifier(**param) sklearn_model_raw.fit(X_train, y_train, early_stopping_rounds=10, eval_metric="error", eval_set=[(X_test, y_test)])
里面的param其實就是2.1節里面定義的:
param = {'max_depth':5, 'eta':0.5, 'verbosity':1, 'objective':'binary:logistic'}
使用sklearn風格的接口,卻使用原始的參數名定義,感覺還是有點怪,所以我一般還是習慣使用另一種風格接口,sklearn風格的參數命名。
2.3 使用sklearn風格接口,使用sklearn風格參數
使用sklearn風格的接口,並使用sklearn風格的參數,是我推薦的方式,主要是這樣做和GBDT之類的sklearn庫使用起來沒有什么兩樣了,也可以使用sklearn的網格搜索。
不過這樣做的話,參數定義命名和2.1與2.2節就有些不同了。具體的參數意義我們后面講,我們看看分類的算法初始化,訓練與調用的簡單過程:
sklearn_model_new = xgb.XGBClassifier(max_depth=5,learning_rate= 0.5, verbosity=1, objective='binary:logistic',random_state=1)
可以看到,參數定義直接放在了XGBClassifier的類參數里,和sklearn類似。大家可以看到之前兩節我們定義的步長eta,這里變成了另一個名字learning_rate。
在初始化后,訓練和預測的方法就和2.2節沒有區別了。
sklearn_model_new.fit(X_train, y_train, early_stopping_rounds=10, eval_metric="error", eval_set=[(X_test, y_test)])
3. XGBoost類庫參數
在第二節我們已經嘗試使用XGBoost類庫了,但是對於XGBoost的類庫參數並沒有過多討論。這里我們就詳細討論下,主要以2.3節的sklearn風格參數為主來進行討論。這些參數我會和之前講的scikit-learn 梯度提升樹(GBDT)調參小結中的參數定義對應,這樣如果大家對GBDT的調參很熟悉了,那么XGBoost的調參也就掌握90%了。
XGBoost的類庫參數主要包括boosting框架參數,弱學習器參數以及其他參數。
3.1 XGBoost框架參數
對於XGBoost的框架參數,最重要的是3個參數: booster,n_estimators和objectve。
1) booster決定了XGBoost使用的弱學習器類型,可以是默認的gbtree, 也就是CART決策樹,還可以是線性弱學習器gblinear以及DART。一般來說,我們使用gbtree就可以了,不需要調參。
2) n_estimators則是非常重要的要調的參數,它關系到我們XGBoost模型的復雜度,因為它代表了我們決策樹弱學習器的個數。這個參數對應sklearn GBDT的n_estimators。n_estimators太小,容易欠擬合,n_estimators太大,模型會過於復雜,一般需要調參選擇一個適中的數值。
3) objective代表了我們要解決的問題是分類還是回歸,或其他問題,以及對應的損失函數。具體可以取的值很多,一般我們只關心在分類和回歸的時候使用的參數。
在回歸問題objective一般使用reg:squarederror ,即MSE均方誤差。二分類問題一般使用binary:logistic, 多分類問題一般使用multi:softmax。
3.2 XGBoost 弱學習器參數
這里我們只討論使用gbtree默認弱學習器的參數。 要調參的參數主要是決策樹的相關參數如下:
1) max_depth: 控制樹結構的深度,數據少或者特征少的時候可以不管這個值。如果模型樣本量多,特征也多的情況下,需要限制這個最大深度,具體的取值一般要網格搜索調參。這個參數對應sklearn GBDT的max_depth。
2) min_child_weight: 最小的子節點權重閾值,如果某個樹節點的權重小於這個閾值,則不會再分裂子樹,即這個樹節點就是葉子節點。這里樹節點的權重使用的是該節點所有樣本的二階導數的和,即XGBoost原理篇里面的HtjHtj:
這個值需要網格搜索尋找最優值,在sklearn GBDT里面,沒有完全對應的參數,不過min_samples_split從另一個角度起到了閾值限制。
3) gamma: XGBoost的決策樹分裂所帶來的損失減小閾值。也就是我們在嘗試樹結構分裂時,會嘗試最大數下式:
這個最大化后的值需要大於我們的gamma,才能繼續分裂子樹。這個值也需要網格搜索尋找最優值。
4) subsample: 子采樣參數,這個也是不放回抽樣,和sklearn GBDT的subsample作用一樣。選擇小於1的比例可以減少方差,即防止過擬合,但是會增加樣本擬合的偏差,因此取值不能太低。初期可以取值1,如果發現過擬合后可以網格搜索調參找一個相對小一些的值。
5) colsample_bytree/colsample_bylevel/colsample_bynode: 這三個參數都是用於特征采樣的,默認都是不做采樣,即使用所有的特征建立決策樹。colsample_bytree控制整棵樹的特征采樣比例,colsample_bylevel控制某一層的特征采樣比例,而colsample_bynode控制某一個樹節點的特征采樣比例。比如我們一共64個特征,則假設colsample_bytree,colsample_bylevel和colsample_bynode都是0.5,則某一個樹節點分裂時會隨機采樣8個特征來嘗試分裂子樹。
6) reg_alpha/reg_lambda: 這2個是XGBoost的正則化參數。reg_alpha是L1正則化系數,reg_lambda是L2正則化系數,在原理篇里我們討論了XGBoost的正則化損失項部分:
上面這些參數都是需要調參的,不過一般先調max_depth,min_child_weight和gamma。如果發現有過擬合的情況下,再嘗試調后面幾個參數。
3.3 XGBoost 其他參數
XGBoost還有一些其他的參數需要注意,主要是learning_rate。
learning_rate控制每個弱學習器的權重縮減系數,和sklearn GBDT的learning_rate類似,較小的learning_rate意味着我們需要更多的弱學習器的迭代次數。通常我們用步長和迭代最大次數一起來決定算法的擬合效果。所以這兩個參數n_estimators和learning_rate要一起調參才有效果。當然也可以先固定一個learning_rate ,然后調完n_estimators,再調完其他所有參數后,最后再來調learning_rate和n_estimators。
此外,n_jobs控制算法的並發線程數, scale_pos_weight用於類別不平衡的時候,負例和正例的比例。類似於sklearn中的class_weight。importance_type則可以查詢各個特征的重要性程度。可以選擇“gain”, “weight”, “cover”, “total_gain” 或者 “total_cover”。最后可以通過調用booster的get_score方法獲取對應的特征權重。“weight”通過特征被選中作為分裂特征的計數來計算重要性,“gain”和“total_gain”則通過分別計算特征被選中做分裂特征時帶來的平均增益和總增益來計算重要性。“cover”和 “total_cover”通過計算特征被選中做分裂時的平均樣本覆蓋度和總體樣本覆蓋度來來計算重要性。
4. XGBoost網格搜索調參
XGBoost可以和sklearn的網格搜索類GridSeachCV結合使用來調參,使用時和普通sklearn分類回歸算法沒有區別。具體的流程的一個示例如下:
gsCv = GridSearchCV(sklearn_model_new, {'max_depth': [4,5,6], 'n_estimators': [5,10,20]}) gsCv.fit(X_train,y_train)
print(gsCv.best_score_) print(gsCv.best_params_)
我這里的輸出是:
0.9533333333333334
{'max_depth': 4, 'n_estimators': 10}
接着嘗試在上面搜索的基礎上調learning_rate :
sklearn_model_new2 = xgb.XGBClassifier(max_depth=4,n_estimators=10,verbosity=1, objective='binary:logistic',random_state=1) gsCv2 = GridSearchCV(sklearn_model_new2, {'learning_rate ': [0.3,0.5,0.7]}) gsCv2.fit(X_train,y_train)
print(gsCv2.best_score_) print(gsCv2.best_params_)
我這里的輸出是:
0.9516
{'learning_rate ': 0.3}
當然實際情況這里需要繼續調參,這里假設我們已經調參完畢,我們嘗試用驗證集看看效果:
sklearn_model_new2 = xgb.XGBClassifier(max_depth=4,learning_rate= 0.3, verbosity=1, objective='binary:logistic',n_estimators=10) sklearn_model_new2.fit(X_train, y_train, early_stopping_rounds=10, eval_metric="error", eval_set=[(X_test, y_test)])
最后的輸出是:
[9] validation_0-error:0.0588
也就是驗證集的准確率是94.12%。
我們可以通過驗證集的准確率來判斷我們前面網格搜索調參是否起到了效果。實際處理的時候需要反復搜索參數並驗證。
以上就是XGBoost的類庫使用總結了,希望可以幫到要用XGBoost解決實際問題的朋友們。
二、xgboost類庫重要參數
數據比賽Kaggle,天池中最常見的就是XGBoost和LightGBM。
模型是在數據比賽中尤為重要的,但是實際上,在比賽的過程中,大部分朋友在模型上花的時間卻是相對較少的,大家都傾向於將寶貴的時間留在特征提取與模型融合這些方面。在實戰中,我們會先做一個baseline的demo,盡可能快盡可能多的挖掘出模型的潛力,以便后期將精力花在特征和模型融合上。這里就需要一些調參功底。
本文從這兩種模型的一共百余參數中選取重要的十余個進行探討研究。並給大家展示快速輕量級的調參方式。當然,有更高一步要求的朋友,還是得戳LightGBM和XGBoost這兩個官方文檔鏈接。
XGBoost 的重要參數
XGBoost的參數一共分為三類:
-
通用參數:宏觀函數控制。
-
Booster參數:控制每一步的booster(tree/regression)。booster參數一般可以調控模型的效果和計算代價。我們所說的調參,很這是大程度上都是在調整booster參數。
-
學習目標參數:控制訓練目標的表現。我們對於問題的划分主要體現在學習目標參數上。比如我們要做分類還是回歸,做二分類還是多分類,這都是目標參數所提供的。
通用參數
-
booster:我們有兩種參數選擇,
gbtree
和gblinear
。gbtree是采用樹的結構來運行數據,而gblinear是基於線性模型。 -
silent:靜默模式,為
1
時模型運行不輸出。 -
nthread: 使用線程數,一般我們設置成
-1
,使用所有線程。如果有需要,我們設置成多少就是用多少線程。
Booster參數
-
n_estimator: 也作
num_boosting_rounds
這是生成的最大樹的數目,也是最大的迭代次數。
-
learning_rate: 有時也叫作
eta
,系統默認值為0.3
,。每一步迭代的步長,很重要。太大了運行准確率不高,太小了運行速度慢。我們一般使用比默認值小一點,
0.1
左右就很好。 -
gamma:系統默認為
0
,我們也常用0
。在節點分裂時,只有分裂后損失函數的值下降了,才會分裂這個節點。
gamma
指定了節點分裂所需的最小損失函數下降值。 這個參數的值越大,算法越保守。因為gamma
值越大的時候,損失函數下降更多才可以分裂節點。所以樹生成的時候更不容易分裂節點。范圍:[0,∞]
-
subsample:系統默認為
1
。這個參數控制對於每棵樹,隨機采樣的比例。減小這個參數的值,算法會更加保守,避免過擬合。但是,如果這個值設置得過小,它可能會導致欠擬合。 典型值:
0.5-1
,0.5
代表平均采樣,防止過擬合. 范圍:(0,1]
,注意不可取0 -
colsample_bytree:系統默認值為1。我們一般設置成0.8左右。
用來控制每棵隨機采樣的列數的占比(每一列是一個特征)。 典型值:
0.5-1
范圍:(0,1]
-
colsample_bylevel:默認為1,我們也設置為1.
這個就相比於前一個更加細致了,它指的是每棵樹每次節點分裂的時候列采樣的比例
-
max_depth: 系統默認值為
6
我們常用
3-10
之間的數字。這個值為樹的最大深度。這個值是用來控制過擬合的。max_depth
越大,模型學習的更加具體。設置為0
代表沒有限制,范圍:[0,∞]
-
max_delta_step:默認
0
,我們常用0
.這個參數限制了每棵樹權重改變的最大步長,如果這個參數的值為
0
,則意味着沒有約束。如果他被賦予了某一個正值,則是這個算法更加保守。通常,這個參數我們不需要設置,但是當個類別的樣本極不平衡的時候,這個參數對邏輯回歸優化器是很有幫助的。 -
lambda:也稱
reg_lambda
,默認值為0
。權重的L2正則化項。(和Ridge regression類似)。這個參數是用來控制XGBoost的正則化部分的。這個參數在減少過擬合上很有幫助。
-
alpha:也稱
reg_alpha
默認為0
,權重的L1正則化項。(和Lasso regression類似)。 可以應用在很高維度的情況下,使得算法的速度更快。
-
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()
類,但這兩種函數的寫法不太一樣。大家看我下面的例子。
from numpy import loadtxt from xgboost import XGBClassifier from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score # 導入數據 dataset = loadtxt('pima-indians-diabetes.csv', delimiter=",") # split data into X and y X = dataset[:, 0:8] Y = dataset[:, 8] # split data into train and test sets seed = 7 test_size = 0.33 X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=test_size, random_state=seed) # 設置參數 model = XGBClassifier(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) model.fit(X_train, y_train) # 進行預測 y_pred = model.predict(X_test) predictions = [round(value) for value in y_pred] # 查看准確率 accuracy = accuracy_score(y_test, predictions) print("Accuracy: %.2f%%" % (accuracy * 100.0))
以上是xgboost.train()
寫法,這是xgboost最原始的封裝函數。這樣訓練我們預測輸出的是一串連續值,是xgboost在這幾個類別上概率最大的概率值。我們如果想要得到我們的分類結果,還需要進行其他操作。
幸運的是,xgboost為了貼合sklearn的使用,比如gridsearch這些實用工具,又開發了XGBoostClassifier()
和XGBoostRegression()
兩個函數。可以更加簡單快捷的進行分類和回歸處理。注意xgboost的sklearn包沒有 feature_importance
這個量度,但是get_fscore()
函數有相同的功能。當然,為了和sklearn保持一致,寫法也發生變化,具體請看下面代碼:
import xgboost as xgb from sklearn.model_selection import train_test_split from sklearn.metrics import roc_auc_score from sklearn.datasets import load_breast_cancer # 二分類解決乳腺癌 cancer = load_breast_cancer() x = cancer.data y = cancer.target 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 from sklearn.model_selection import train_test_split from sklearn.datasets import load_breast_cancer from sklearn.model_selection import GridSearchCV cancer = load_breast_cancer() x = cancer.data[:50] y = cancer.target[:50] 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': [50, 100, 200, 300, 500], '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.
cv的可能輸入包括:
None,使用默認的3倍交叉驗證,
用作交叉驗證生成器的對象。
三、通過early-stop實現過擬合
本文翻譯自Avoid Overfitting By Early Stopping With XGBoost In Python,講述如何在使用XGBoost建模時通過Early Stop手段來避免過擬合。全文系作者原創,僅供學習參考使用,轉載授權請私信聯系,否則將視為侵權行為。碼字不易,感謝支持。以下為全文內容:
過擬合問題是在使用復雜的非線性學習算法時會經常碰到的,比如gradient boosting算法。
在這篇博客中你將發現如何通過Early Stop方法使得我們在使用Python中的XGBoost模型時可以盡可能地避免過擬合問題:
讀完這篇博客后,你將學到:
- Early Stop可以減少訓練集上的過擬合
- 在使用XGBoost模型時如何監控訓練過程中模型的表現,如何繪制學習曲線
- 如何使用Early Stop方法在模型表現最好的時候停止訓練
讓我們開始吧。
使用Early Stop避免過擬合
Early Stop是訓練復雜機器學習模型以避免其過擬合的一種方法。
它通過監控模型在一個額外的測試集上的表現來工作,當模型在測試集上的表現在連續的若干次(提前指定好的)迭代中都不再提升時它將終止訓練過程。
它通過嘗試自動選擇拐點來避免過擬合,在拐點處,測試數據集的性能開始下降,而訓練數據集的性能隨着模型開始過擬合而繼續改善。
性能的度量可以是訓練模型時正在使用的損失函數(例如對數損失),或通常意義上用戶感興趣的外部度量(例如分類精度)。
在XGBoost中監控模型的表現
XGBoost模型在訓練時可以計算並輸入在某個指定的測試數據集的性能表現。
在調用model.fit()
函數時,可以指定測試數據集和評價指標,同時設置verbose
參數為True
,這樣就可以在訓練過程中輸出模型在測試集的表現。
例如,我們可以通過下面的方法在使用XGBoost訓練二分類任務時輸出分類錯誤率(通過“error”指定):
eval_set = [(X_test, y_test)] model.fit(X_train, y_train, eval_metric="error", eval_set=eval_set, verbose=True)
XGBoost提供了一系列的模型評價指標,包括但不限於:
- “rmse” 代表均方根誤差
- “mae” 代表平均絕對誤差
- “logloss” 代表二元對數損失
- “mlogloss” 代表m-元對數損失
- “error” 代表分類錯誤率
- “auc” 代表ROC曲線下面積
完整的列表見XGBoost文檔中的“Learning Task Parameters””章節。
例如,我們可以演示如何監控使用UCI機器學習存儲庫(更新:從這里下載)的關於Pima糖尿病發病數據集的XGBoost模型在訓練過程中的性能指標。
完整代碼清單如下:
# monitor training performance from numpy import loadtxt from xgboost import XGBClassifier from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score # load data dataset = loadtxt('pima-indians-diabetes.csv', delimiter=",") # split data into X and y X = dataset[:,0:8] Y = dataset[:,8] # split data into train and test sets X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=0.33, random_state=7) # fit model no training data model = XGBClassifier() eval_set = [(X_test, y_test)] model.fit(X_train, y_train, eval_metric="error", eval_set=eval_set, verbose=True) # make predictions for test data y_pred = model.predict(X_test) predictions = [round(value) for value in y_pred] # evaluate predictions accuracy = accuracy_score(y_test, predictions) print("Accuracy: %.2f%%" % (accuracy * 100.0))
運行這段代碼將會在67%的數據集上訓練模型,並且在每一輪迭代中使用剩下的33%數據來評估模型的性能。
每次迭代都會輸出分類錯誤,最終將會輸出最后的分類准確率。
...
[89] validation_0-error:0.204724 [90] validation_0-error:0.208661 [91] validation_0-error:0.208661 [92] validation_0-error:0.208661 [93] validation_0-error:0.208661 [94] validation_0-error:0.208661 [95] validation_0-error:0.212598 [96] validation_0-error:0.204724 [97] validation_0-error:0.212598 [98] validation_0-error:0.216535 [99] validation_0-error:0.220472 Accuracy: 77.95%
觀察所有的輸出,我們可以看到,在訓練快要結束時測試集上的模型性能的變化是平緩的,甚至變得更差。
使用學習曲線來評估XGBoost模型
我們可以提取出模型在測試數據集上的表現並繪制成圖案,從而更好地洞察到在整個訓練過程中學習曲線是如何變化的。
在調用XGBoost模型時我們提供了一個數組,數組的每一項是一個X和y的配對。在測試集之外,我們同時將訓練集也作為輸入,從而觀察在訓練過程中模型在訓練集和測試集上各自的表現。
例如:
eval_set = [(X_train, y_train), (X_test, y_test)] model.fit(X_train, y_train, eval_metric="error", eval_set=eval_set, verbose=True)
模型在各個數據集上的表現可以在訓練結束后通過model.evals_result()
函數獲取,這個函數返回一個dict
包含了評估數據集的代碼和對應的分數列表,例如:
results = model.evals_result() print(results)
這將輸出如下的結果:
{ 'validation_0': {'error': [0.259843, 0.26378, 0.26378, ...]}, 'validation_1': {'error': [0.22179, 0.202335, 0.196498, ...]} }
“validation_0”和“validation_1”代表了在調用fit()
函數時傳給eval_set
參數的數組中數據集的順序。
一個特定的結果,比如第一個數據集上的分類錯誤率,可以通過如下方法獲取:
results['validation_0']['error']
另外我們可以指定更多的評價指標,從而同時獲取多種評價指標的變化情況。
接着我們可以使用收集到的數據繪制曲線,從而更直觀地了解在整個訓練過程中模型在訓練集和測試集上的表現究竟如何。
下面是一段完整的代碼,展示了如何將收集到的數據繪制成學習曲線:
# plot learning curve from numpy import loadtxt from xgboost import XGBClassifier from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score from matplotlib import pyplot # load data dataset = loadtxt('pima-indians-diabetes.csv', delimiter=",") # split data into X and y X = dataset[:,0:8] Y = dataset[:,8] # split data into train and test sets X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=0.33, random_state=7) # fit model no training data model = XGBClassifier() eval_set = [(X_train, y_train), (X_test, y_test)] model.fit(X_train, y_train, eval_metric=["error", "logloss"], eval_set=eval_set, verbose=True) # make predictions for test data y_pred = model.predict(X_test) predictions = [round(value) for value in y_pred] # evaluate predictions accuracy = accuracy_score(y_test, predictions) print("Accuracy: %.2f%%" % (accuracy * 100.0)) # retrieve performance metrics results = model.evals_result() epochs = len(results['validation_0']['error']) x_axis = range(0, epochs) # plot log loss fig, ax = pyplot.subplots() ax.plot(x_axis, results['validation_0']['logloss'], label='Train') ax.plot(x_axis, results['validation_1']['logloss'], label='Test') ax.legend() pyplot.ylabel('Log Loss') pyplot.title('XGBoost Log Loss') pyplot.show() # plot classification error fig, ax = pyplot.subplots() ax.plot(x_axis, results['validation_0']['error'], label='Train') ax.plot(x_axis, results['validation_1']['error'], label='Test') ax.legend() pyplot.ylabel('Classification Error') pyplot.title('XGBoost Classification Error') pyplot.show()
運行這段代碼將會在每一次訓練迭代中輸出模型在訓練集和測試集上的分類錯誤率。我們可以通過設置verbose=False
來關閉輸出。
我們繪制了兩張圖,第一張圖表示的是模型在每一輪迭代中在兩個數據集上的對數損失:

第二張圖表示分類錯誤率:

從第一張圖來看,似乎有機會可以進行Early Stop,大約在20到40輪迭代時比較合適。
從第二張圖可以得到相似的結果,大概在40輪迭代時效果比較理想。
在XGBoost中進行Early Stop
XGBoost提供了在指定輪數完成后提前停止訓練的功能。
除了提供用於評估每輪迭代中的評價指標和數據集之外,還需要指定一個窗口大小,意味着連續這么多輪迭代中模型的效果沒有提升。這是通過early_stopping_rounds
參數來設置的。
例如,我們可以像下面這樣設置連續10輪中對數損失都沒有提升:
eval_set = [(X_test, y_test)] model.fit(X_train, y_train, early_stopping_rounds=10, eval_metric="logloss", eval_set=eval_set, verbose=True)
如果同時指定了多個評估數據集和多個評價指標,early_stopping_rounds
將會使用數組中的最后一個作為依據。
下面提供了一個使用early_stopping_rounds
的詳細例子:
# early stopping from numpy import loadtxt from xgboost import XGBClassifier from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score # load data dataset = loadtxt('pima-indians-diabetes.csv', delimiter=",") # split data into X and y X = dataset[:,0:8] Y = dataset[:,8] # split data into train and test sets seed = 7 test_size = 0.33 X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=test_size, random_state=seed) # fit model no training data model = XGBClassifier() eval_set = [(X_test, y_test)] model.fit(X_train, y_train, early_stopping_rounds=10, eval_metric="logloss", eval_set=eval_set, verbose=True) # make predictions for test data y_pred = model.predict(X_test) predictions = [round(value) for value in y_pred] # evaluate predictions accuracy = accuracy_score(y_test, predictions) print("Accuracy: %.2f%%" % (accuracy * 100.0))
運行這段代碼將得到如下的輸出(部分):
...
[35] validation_0-logloss:0.487962 [36] validation_0-logloss:0.488218 [37] validation_0-logloss:0.489582 [38] validation_0-logloss:0.489334 [39] validation_0-logloss:0.490969 [40] validation_0-logloss:0.48978 [41] validation_0-logloss:0.490704 [42] validation_0-logloss:0.492369 Stopping. Best iteration: [32] validation_0-logloss:0.487297
我們可以看到模型在迭代到42輪時停止了訓練,在32輪迭代后觀察到了最好的效果。
通常將early_stopping_rounds
設置為一個與總訓練輪數相關的函數(本例中是10%),或者通過觀察學習曲線來設置使得訓練過程包含拐點,這兩種方法都是不錯的選擇。
總結
在這篇博客中你發現了如何監控模型的表現以及怎么做Early Stop。
你學會了:
- 使用Early Stop手段在模型過擬合之前停止訓練
- 在使用XGBoost模型時如何監控模型的表現並繪制出模型的學習曲線
- 在訓練XGBoost模型時如何設置Early Stop參數
關於Early Stop或者這篇博客你還有什么想問的問題嗎?歡迎在下方的評論區留言,我將盡我最大的努力來解答。
以上就是本文的全部內容,如果您喜歡這篇文章,歡迎將它分享給朋友們。
感謝您的閱讀,祝您生活愉快!
四、代碼
1、數據
啊你告訴他一聲,好吧,因為他這邊目前為止的話,從來也沒有接聽過銀行的電話,因為這邊的話多次的話發了短信,多次打的電話,包括郵件的話已經發送給他了。讓他看一下手機中信銀行蓋章發的短信,然后的話讓他趕在九點半之前,務必要把它中信銀行的支付信用卡賬戶信息給您核實一下,好嗎? 0 啊不是五號,您這個逾期的,這個最早一期逾期的這個部分是您今天要還上的。呃,剩余的這個欠款的。您可以就是講稍后的話,您這邊需要時間的話,我們都可以去給您記錄下來,后面可以給您申請的。 0 哦那您現在這個兩千兩千兩百五十塊錢的一個資金,現在手上有嗎? 0 嗯您這邊的話如果說,呃,就是想要處理這個逾期金額的話,因為你之前的話也是一直都是處於處理的。這個一期最低,如果說你想要通過這個兩千多塊錢來解決這個問題的話,您務必的話就是說,呃,在趕在這個最遲十一點鍾之前把它處理進來。我們這邊把材料給你保留下來,然后剩下的款項可以給您申請一下可以吧。 0 沒有其他事也已經讓您申請,讓你小時候還一部分了呀先生。 0 對是的啊,但是你不能說這個錢花出去的綠化做處理啊。現在只要你想換個一千三百九十塊錢呀,先生。 0 三中信信用卡的業務該怎么處理呢?我該怎么處理?可能是銀行信貸資金呀先生。 0 那你不是第一天知道你對信用卡逾期呀! 0
2、訓練部分
import pandas as pd import os from config.root_path import root from sklearn.feature_extraction.text import TfidfTransformer,TfidfVectorizer import xgboost import pickle from sklearn import metrics class XgbModel(): def __init__(self, strategy, train_tf=False, train_x = False): self.train_path = os.path.join(root, "chinese_classification", "datas", strategy, "data", "train.txt") self.dev_path = os.path.join(root, "chinese_classification", "datas", strategy, "data", "dev.txt") self.test_path = os.path.join(root, "chinese_classification", "datas", strategy, "data", "test.txt") self.class_path = os.path.join(root, "chinese_classification", "datas", strategy, "data", "class.txt") self.param = {'silent': 0, 'eta': 0.3, 'max_depth': 6, 'objective': 'multi:softmax', 'num_class': 2, 'eval_metric': 'mlogloss'} self.tfidf_model = os.path.join(root, "xgboost_model", "chkpt", "tfidf.pkl") self._model = os.path.join(root, "xgboost_model", "chkpt",strategy, "xgboost.pkl") self.train_tf= train_tf self.train_x = train_x def clean_data(self): seg = lambda s: " ".join(s) cw = lambda s:int(s) train_df = pd.read_csv(self.train_path, sep="\t", header=None, names=["s", "l"]) train_x = train_df["s"].apply(seg) train_y = train_df["l"].apply(cw) test_df = pd.read_csv(self.test_path, sep="\t", header=None, names=["s", "l"]) test_x = test_df["s"].apply(seg) test_y = test_df["l"].apply(cw) dev_df = pd.read_csv(self.dev_path, sep="\t", header=None, names=["s", "l"]) dev_x = dev_df["s"].apply(seg) dev_y = dev_df["l"].apply(cw) return train_x,train_y,dev_x,dev_y,test_x,test_y def train_tfidf(self, corpus): tfidf_vectorizer = TfidfVectorizer(max_features=5000, analyzer ="char",lowercase=False) feature = tfidf_vectorizer.fit_transform(corpus) with open(self.tfidf_model, 'wb') as f: pickle.dump(tfidf_vectorizer, f) def get_tfidf(self, corpus): with open(self.tfidf_model, 'rb') as f: tfidf_vectorizer = pickle.load(f) res = tfidf_vectorizer.transform(corpus).toarray() return res def train_xgb(self,train_weight,train_y,dev_weight,dev_y): dtrain = xgboost.DMatrix(train_weight, label=train_y) ddev = xgboost.DMatrix(dev_weight, label=dev_y) evallist = [(dtrain, 'train'),(ddev, 'dev')] num_round = 100 # 循環次數 my_eval_result = {} xgb_model = xgboost.train(self.param, dtrain, num_round, evallist, early_stopping_rounds=10, evals_result=my_eval_result) xgb_model.save_model(self._model) print(my_eval_result) def xgb_predict(self, test_weight, test_y): xgb_model = xgboost.Booster({'nthread': 4}) # init model xgb_model.load_model(self._model) # load data dtest = xgboost.DMatrix(test_weight, label=test_y) y_predict = xgb_model.predict(dtest, iteration_range=(0, xgb_model.best_iteration + 1)) # 模型預測 label_all = [] with open(self.class_path, "r", encoding="utf8") as f: for line in f.readlines(): line = line.strip() label_all.append(line) confusion_mat = metrics.confusion_matrix(test_y, y_predict) df = pd.DataFrame(confusion_mat, columns=label_all) df.index = label_all print('准確率:', metrics.accuracy_score(test_y, y_predict)) print('分類報告:', metrics.classification_report(test_y, y_predict)) def train_tfidf_xgb(self): train_x,train_y,dev_x,dev_y,test_x,test_y = self.clean_data() if self.train_tf: self.train_tfidf(train_x) if self.train_x: train_weight = self.get_tfidf(train_x) dev_weight = self.get_tfidf(dev_x) self.train_xgb(train_weight,train_y,dev_weight,dev_y) test_weight = self.get_tfidf(test_x) self.xgb_predict(test_weight, test_y) def train_model(): strategy_list = [ "gaodiqiu", "gongqing", "huhui", "kuangjia", "mianzhi", "sanmingzhi", "zanmei", "zijiaren" ] for s_list in strategy_list: m = XgbModel(s_list, False, True) m.train_tfidf_xgb() break if __name__ == '__main__': train_model()
3、預測部分
import xgboost import pickle import os from config.root_path import root class XgbPrediction(object): def __init__(self, strategy): self.tfidf_model = os.path.join(root, "xgboost_model", "chkpt", "tfidf.pkl") self._model = os.path.join(root, "xgboost_model", "chkpt",strategy, "xgboost.pkl") with open(self.tfidf_model, 'rb') as f: self.tfidf_vectorizer = pickle.load(f) self.xgb_model = xgboost.Booster({'nthread': 4}) # init model self.xgb_model.load_model(self._model) # load data self.cat_list = [] with open(os.path.join(root, "chinese_classification", "datas",strategy,"data", "class.txt"), "r", encoding="utf8") as f: for line in f.readlines(): self.cat_list.append(line.strip()) self.id_label = dict(zip(range(len(self.cat_list)), self.cat_list)) self.id_to_label = lambda x:self.id_label[x] def predict(self, sentence): content = [" ".join(sentence)] res = self.tfidf_vectorizer.transform(content).toarray() dtest = xgboost.DMatrix(res) y_predict = self.xgb_model.predict(dtest).astype("int")[0] # 模型預測 result = self.id_to_label(y_predict) return result def multi_predict(self, sentence_list): contents = [" ".join(sentence) for sentence in sentence_list] res = self.tfidf_vectorizer.transform(contents).toarray() dtest = xgboost.DMatrix(res) y_predict = self.xgb_model.predict(dtest).astype("int") result = list(map(self.id_to_label, y_predict)) return result if __name__ == '__main__': XgbPrediction("gongqing").predict("我愛中國")