XGBoost Plotting API以及GBDT組合特征實踐
寫在前面:
最近在深入學習一些樹模型相關知識點,打算整理一下。剛好昨晚看到餘音大神在Github上分享了一波 MachineLearningTrick,趕緊上車學習一波!大神這波節奏分享了xgboost相關的幹貨,還有一些内容未分享….總之值得關注!我主要看了:Xgboost的葉子節點位置生成新特征封裝的函數。之前就看過相關博文,比如Byran大神的這篇:http://blog.csdn.net/bryan__/article/details/51769118,但是自己從未實踐過。本文是基于bryan大神部落格以及餘音大神的代碼對GBDT組合特征實踐的了解和拓展,此外探索了一下XGBoost的Plotting API,學習為主!
官方API介紹:
http://xgboost.readthedocs.io/en/latest/python/python_api.html#module-xgboost.sklearn
1.利用GBDT構造組合特征原理介紹
從byran大神的部落格以及這篇利用GBDT模型構造新特征中,可以比較好的了解GBDT組合特征:
論文的思想很簡單,就是先用已有特征訓練GBDT模型,然後利用GBDT模型學習到的樹來構造新特征,最後把這些新特征加入原有特征一起訓練模型。構造的新特征向量是取值0/1的,向量的每個元素對應于GBDT模型中樹的葉子結點。當一個樣本點通過某棵樹最終落在這棵樹的一個葉子結點上,那麼在新特征向量中這個葉子結點對應的元素值為1,而這棵樹的其他葉子結點對應的元素值為0。新特征向量的長度等于GBDT模型裡所有樹包含的葉子結點數之和。
舉例說明。下面的圖中的兩棵樹是GBDT學習到的,第一棵樹有3個葉子結點,而第二棵樹有2個葉子節點。對于一個輸入樣本點x,如果它在第一棵樹最後落在其中的第二個葉子結點,而在第二棵樹裡最後落在其中的第一個葉子結點。那麼通過GBDT獲得的新特征向量為[0, 1, 0, 1, 0],其中向量中的前三位對應第一棵樹的3個葉子結點,後兩位對應第二棵樹的2個葉子結點。
在實踐中的關鍵點是如何獲得每個樣本在訓練後樹模型每棵樹的哪個葉子結點上。之前知乎上看到過可以設定pre_leaf=True獲得每個樣本在每顆樹上的leaf_Index,打開XGBoost官方文檔查閱一下API:
原來這個參數是在predict裡面,在對原始特征進行簡單調參訓練後,對原始資料以及測試資料進行
new_feature= bst.predict(d_test, pred_leaf=True)
即可得到一個(nsample, ntrees) 的結果矩陣,即每個樣本在每個樹上的index。了解這個方法之後,我仔細學習了餘音大神的代碼,發現他并沒有用到這個,如下:
可以看到他用的是apply()方法,這裡就有點疑惑了,在XGBoost官方API并沒有看到這個方法,于是我去SKlearn GBDT API看了下,果然有apply()方法可以獲得leaf indices:
因為XGBoost有自帶接口和Scikit-Learn接口,是以代碼上有所差異。至此,基本了解了利用GBDT(XGBoost)構造組合特征的實作方法,接下去按兩種接口實踐一波。
2.利用GBDT構造組合特征實踐
發車發車~
(1).包導入以及資料準備
from sklearn.model_selection import train_test_split
from pandas import DataFrame
from sklearn import metrics
from sklearn.datasets import make_hastie_10_2
from xgboost.sklearn import XGBClassifier
import xgboost as xgb
#準備資料,y本來是[-1:1],xgboost自帶接口邀請标簽是[0:1],把-1的轉成1了。
X, y = make_hastie_10_2(random_state=)
X = DataFrame(X)
y = DataFrame(y)
y.columns={"label"}
label={-:,:}
y.label=y.label.map(label)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=, random_state=)#劃分資料集
y_train.head()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
label | |
---|---|
843 | 1 |
9450 | |
7766 | 1 |
9802 | 1 |
8555 | 1 |
(2).XGBoost兩種接口定義
#XGBoost自帶接口
params={
'eta': ,
'max_depth':,
'min_child_weight':,
'gamma':,
'subsample':,
'colsample_bytree':,
'booster':'gbtree',
'objective': 'binary:logistic',
'nthread':,
'scale_pos_weight': ,
'lambda':,
'seed':,
'silent': ,
'eval_metric': 'auc'
}
d_train = xgb.DMatrix(X_train, label=y_train)
d_valid = xgb.DMatrix(X_test, label=y_test)
d_test = xgb.DMatrix(X_test)
watchlist = [(d_train, 'train'), (d_valid, 'valid')]
#sklearn接口
clf = XGBClassifier(
n_estimators=,#三十棵樹
learning_rate =,
max_depth=,
min_child_weight=,
gamma=,
subsample=,
colsample_bytree=,
objective= 'binary:logistic',
nthread=,
scale_pos_weight=,
reg_lambda=,
seed=)
model_bst = xgb.train(params, d_train, , watchlist, early_stopping_rounds=, verbose_eval=)
model_sklearn=clf.fit(X_train, y_train)
y_bst= model_bst.predict(d_test)
y_sklearn= clf.predict_proba(X_test)[:,]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
print("XGBoost_自帶接口 AUC Score : %f" % metrics.roc_auc_score(y_test, y_bst))
print("XGBoost_sklearn接口 AUC Score : %f" % metrics.roc_auc_score(y_test, y_sklearn))
- 1
- 2
- 1
- 2
(3).生成兩組新特征
print("原始train大小:",X_train.shape)
print("原始test大小:",X_test.shape)
##XGBoost自帶接口生成的新特征
train_new_feature= model_bst.predict(d_train, pred_leaf=True)
test_new_feature= model_bst.predict(d_test, pred_leaf=True)
train_new_feature1 = DataFrame(train_new_feature)
test_new_feature1 = DataFrame(test_new_feature)
print("新的特征集(自帶接口):",train_new_feature1.shape)
print("新的測試集(自帶接口):",test_new_feature1.shape)
#sklearn接口生成的新特征
train_new_feature= clf.apply(X_train)#每個樣本在每顆樹葉子節點的索引值
test_new_feature= clf.apply(X_test)
train_new_feature2 = DataFrame(train_new_feature)
test_new_feature2 = DataFrame(test_new_feature)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
print("新的特征集(sklearn接口):",train_new_feature2.shape)
print("新的測試集(sklearn接口):",test_new_feature2.shape)
- 1
- 2
- 1
- 2
train_new_feature1.head()
- 1
- 1
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | … | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
8 | 11 | 9 | 9 | 10 | 8 | 11 | 12 | 9 | 9 | … | 10 | 8 | 10 | 11 | 9 | 10 | 10 | 8 | 11 | 12 | |
1 | 10 | 11 | 9 | 11 | 11 | 9 | 11 | 12 | 9 | 9 | … | 10 | 9 | 11 | 11 | 9 | 10 | 10 | 8 | 11 | 13 |
2 | 10 | 11 | 9 | 12 | 10 | 9 | 11 | 12 | 9 | 9 | … | 10 | 9 | 14 | 11 | 10 | 13 | 10 | 8 | 11 | 13 |
3 | 10 | 11 | 9 | 10 | 10 | 9 | 11 | 14 | 9 | 9 | … | 8 | 9 | 11 | 12 | 9 | 10 | 10 | 8 | 13 | 12 |
4 | 12 | 11 | 9 | 9 | 10 | 7 | 11 | 12 | 9 | 10 | … | 10 | 8 | 13 | 11 | 9 | 10 | 10 | 8 | 12 | 12 |
5 rows × 30 columns
(4).基于新特征訓練、預測
#用兩組新的特征分别訓練,預測
#用XGBoost自帶接口生成的新特征訓練
new_feature1=clf.fit(train_new_feature1, y_train)
y_new_feature1= clf.predict_proba(test_new_feature1)[:,]
#用XGBoost自帶接口生成的新特征訓練
new_feature2=clf.fit(train_new_feature2, y_train)
y_new_feature2= clf.predict_proba(test_new_feature2)[:,]
print("XGBoost自帶接口生成的新特征預測結果 AUC Score : %f" % metrics.roc_auc_score(y_test, y_new_feature1))
print("XGBoost自帶接口生成的新特征預測結果 AUC Score : %f" % metrics.roc_auc_score(y_test, y_new_feature2))
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
3.Plotting API畫圖
因為獲得的新特征是每棵樹的葉子結點的Index,可以看下每棵樹的結構。XGBoost Plotting API可以實作:
(1).安裝導入相關包: XGBoost Plotting API需要用到graphviz 和pydot,我是Win10 環境+Anaconda3,pydot直接
pip install pydot
或者
conda install pydot
即可。graphviz 稍微麻煩點,直接pip(conda)安裝了以後導入沒有問題,但是畫圖的時候就會報錯,類似路徑環境變量的問題。
網上找了一些解決方法,各種試不行,最後在stackoverflow上找到了解決方案:
http://stackoverflow.com/questions/35064304/runtimeerror-make-sure-the-graphviz-executables-are-on-your-systems-path-aft
http://stackoverflow.com/questions/18334805/graphviz-windows-path-not-set-with-new-installer-issue-when-calling-from-r
需要先下載下傳一個windows下的graphviz 安裝包,安裝完成後将安裝路徑和bin檔案夾路徑添加到系統環境變量,然後重新開機系統。重新pip(conda) install graphviz ,打開jupyter notebook(本次代碼都在notebook中測試完成)或者Python環境運作以下代碼:
from xgboost import plot_tree
from xgboost import plot_importance
import matplotlib.pyplot as plt
from graphviz import Digraph
import pydot
#安裝說明:
#pip install pydot
#http://www.graphviz.org/Download_windows.php
#先安裝上面下載下傳的graphviz.msi,安裝,然後把路徑添加到環境變量,重新開機下
#然後pip3 install graphviz...竟然就可以了...
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
(2).兩種接口的model畫圖: 上面兩種接口的模型分别儲存下來,自帶接口的參數設定更友善一些。沒有深入研究功能,畫出來的圖效果還不是很好。
#model_bst = xgb.train(params, d_train, 30, watchlist, early_stopping_rounds=500, verbose_eval=10)
#model_sklearn=clf.fit(X_train, y_train)
#model_bst
plot_tree(model_bst, num_trees=)
plot_importance(model_bst)
plt.show()
#model_sklearn:
plot_tree(model_sklearn)
plot_importance(model_sklearn)
plt.show()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
4.完
餘音大神已經把它的代碼封裝好了,可以直接下載下傳調用,點贊。
在實踐中可以根據自己的需求實作特征構造,也不是很麻煩,主要就是儲存每個樣本在每棵樹的葉子索引。然後可以根據情況适當調整參數,得到的新特征再融合到原始特征中,最終是否有提升還是要看場景吧,下次比賽打算嘗試一下!
此外,XGBboost Plotting API 之前沒用過,感覺很nice,把每個樹的樣子畫出來可以非常直覺的觀察模型的學習過程,不過本文中的代碼畫出的圖并不是很清晰,還需進一步實踐!
參考資料:文中已列出,這裡再次感謝!
http://blog.csdn.net/bryan__/article/details/51769118
https://breezedeus.github.io/2014/11/19/breezedeus-feature-mining-gbdt.html#fn:fbgbdt
https://github.com/lytforgood/MachineLearningTrick
http://xgboost.readthedocs.io/en/latest/python/python_api.html#module-xgboost.core