RF,GBDT和Xgboost構(gòu)造新特征+LR融合的原理及實踐

轉(zhuǎn)載:https://blog.csdn.net/anshuai_aw1/article/details/82983997
關(guān)于Xgboost的知識點很多,本篇博客介紹如何利用Xgboost構(gòu)造新特征,且在此基礎(chǔ)上,介紹與LR模型融合的相關(guān)知識點。

一 目錄
二 實踐
2.1 如何獲得樣本落在哪個葉子節(jié)點
2.2 舉例
2.2.1 訓(xùn)練集準(zhǔn)備
2.2.2 RF+LR
2.2.3 GBDT+LR
2.2.4 Xgboost+LR
2.2.5 單獨使用RF,GBDT和Xgboost
2.2.6 結(jié)果對比
三 為什么Xgboost+LR 的融合效果沒有想象的那么好
四 參考文獻(xiàn)

一 原理

為什么要使用LR模型進(jìn)行融合呢? 這是因為LR(邏輯回歸)算法簡單有效,成為工業(yè)界最常用的算法之一。但LR算法是線性模型,不能捕捉到非線性西悉尼,需要大量的特征工程找到特征組合。為了發(fā)現(xiàn)有效的特征組合,F(xiàn)acebook在2014年論文ractical Lessons from Predicting Clicks on Ads at Facebook 介紹通過GBDT (Gradient Boost Decision Tree)+ LR 的方案 (XGBoost 是 GBDT 的后續(xù)發(fā)展)。在這篇論文中他們提出一中將Xgboost作為feature transform 的方法。隨后在多個Kaggle比賽中,均證明此思路的有效性。

大概的思想可以描述為如下:先用已有特征訓(xùn)練Xgboost模型,然后利用Xgboost模型學(xué)習(xí)到的樹來構(gòu)造新特征,最后把這些新特征加入原有特征一起訓(xùn)練模型。構(gòu)造的新特征向量是取值0/1的,向量的每個元素對應(yīng)于Xgboost模型中樹的葉子結(jié)點。當(dāng)一個樣本點通過某棵樹最終落在這棵樹的一個葉子結(jié)點上,那么在新特征向量中這個葉子結(jié)點對應(yīng)的元素值為1,而這棵樹的其他葉子結(jié)點對應(yīng)的元素值為0。新特征向量的長度等于XGBoost模型里所有樹包含的葉子結(jié)點數(shù)之和。最后將新的特征扔到LR模型進(jìn)行訓(xùn)練。

舉例說明。下面的圖中的兩棵樹是GBDT(Xgboost一樣)學(xué)習(xí)到的,第一棵樹有3個葉子結(jié)點,而第二棵樹有2個葉子節(jié)點。對于一個輸入樣本點x,如果它在第一棵樹最后落在其中的第二個葉子結(jié)點,而在第二棵樹里最后落在其中的第一個葉子結(jié)點。那么通過GBDT獲得的新特征向量為[0, 1, 0, 1, 0],其中向量中的前三位對應(yīng)第一棵樹的3個葉子結(jié)點,后兩位對應(yīng)第二棵樹的2個葉子結(jié)點。

image.png

二實踐

2.1 如何獲得樣本落在哪個葉子節(jié)點

在實踐中的關(guān)鍵點是如何獲得每個樣本落在訓(xùn)練后的每棵樹的哪個葉子結(jié)點上。

A、對于Xgboost來說,因為其有sklearn接口和自帶接口,因此有兩種方法可以獲得:

①、sklearn接口??梢栽O(shè)置pre_leaf=True獲得每個樣本在每顆樹上的leaf_Index。XGBoost官方文檔

image.png

②、自帶接口。利用apply()方法可以獲得leaf indices。SKlearn GBDT API
?。?!此過程需注意: 無論是設(shè)置pre_leaf=True還是利用apply()方法,獲得的都是葉子節(jié)點的 index,也就是說落在了具體哪顆樹的哪個葉子節(jié)點上,并非是0/1變量,因此需要自己動手去做 onehot 編碼。onehot 可以在 sklearn 的預(yù)處理包中調(diào)用即可。
B、對于其它的樹模型,如隨機(jī)森林和GBDT,我們只能使用apply()方法獲得leaf indices。

2.2 舉例

接下來,我們舉例來說明如何利用樹模型,尤其是Xgboost來構(gòu)建新特征,并且是如何與LR模型進(jìn)行融合。

本例子對Feature transformations with ensembles of trees進(jìn)行了改進(jìn),使得更加健壯易讀。

2.2.1 訓(xùn)練集準(zhǔn)備

從代碼中,可以看到,我們對訓(xùn)練集X_train又進(jìn)行了一次切分,生成了訓(xùn)練集X_train, X_train_lr和測試集y_train, y_train_lr。特別要注意數(shù)據(jù)集的大小,后續(xù)我們會進(jìn)行分析。

注意:我們設(shè)定了n_estimator = 10,這意味著樹模型中只有10顆樹.


import numpy as np
np.random.seed(10)
 
import matplotlib.pyplot as plt
import xgboost as xgb
 
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import ( RandomForestClassifier,
                              GradientBoostingClassifier)
from sklearn.preprocessing import OneHotEncoder
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_curve,  roc_auc_score
from scipy.sparse import hstack
 
 
 
X, y = make_classification(n_samples=80000)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5)
 
# It is important to train the ensemble of trees on a different subset
# of the training data than the linear regression model to avoid
# overfitting, in particular if the total number of leaves is
# similar to the number of training samples
X_train, X_train_lr, y_train, y_train_lr = train_test_split(
    X_train, y_train, test_size=0.5)
 
n_estimator = 10
 
'''
X_train為20000*20
X_train_lr為20000*20
y_train為20000*1
y_train_lr為20000*1
y_test 為40000*1
'''

2.2.2 RF+LR

我們首先使用隨機(jī)森林進(jìn)行實驗。這里我們需要對代碼進(jìn)行一個解讀。


# Supervised transformation based on gradient boosted trees
grd = GradientBoostingClassifier(n_estimators=n_estimator)
grd_enc = OneHotEncoder()
grd_lm = LogisticRegression(solver='lbfgs', max_iter=1000)
 
grd.fit(X_train, y_train)
grd_enc.fit(grd.apply(X_train)[:, :, 0])
grd_lm.fit(grd_enc.transform(grd.apply(X_train_lr)[:, :, 0]), y_train_lr)
 
y_pred_grd_lm = grd_lm.predict_proba(
    grd_enc.transform(grd.apply(X_test)[:, :, 0]))[:, 1]
fpr_grd_lm, tpr_grd_lm, _ = roc_curve(y_test, y_pred_grd_lm)
print("GBT+LR的AUC為:", roc_auc_score(y_test, y_pred_grd_lm))

2.2.3 GBDT+LR

我們其次使用隨機(jī)森林進(jìn)行實驗。其代碼與隨機(jī)森林幾乎一樣。不再進(jìn)行代碼解讀。

# Supervised transformation based on gradient boosted trees
grd = GradientBoostingClassifier(n_estimators=n_estimator)
grd_enc = OneHotEncoder()
grd_lm = LogisticRegression(solver='lbfgs', max_iter=1000)
 
grd.fit(X_train, y_train)
grd_enc.fit(grd.apply(X_train)[:, :, 0])
grd_lm.fit(grd_enc.transform(grd.apply(X_train_lr)[:, :, 0]), y_train_lr)
 
y_pred_grd_lm = grd_lm.predict_proba(
    grd_enc.transform(grd.apply(X_test)[:, :, 0]))[:, 1]
fpr_grd_lm, tpr_grd_lm, _ = roc_curve(y_test, y_pred_grd_lm)
print("GBT+LR的AUC為:", roc_auc_score(y_test, y_pred_grd_lm))

2.2.4 Xgboost+LR

最后,我們使用Xgboost進(jìn)行實驗。

# Supervised transformation based on xgboost
xgb = xgb.XGBClassifier(nthread=4,     #含義:nthread=-1時,使用全部CPU進(jìn)行并行運算(默認(rèn)), nthread=1時,使用1個CPU進(jìn)行運算。
                          learning_rate=0.08,    #含義:學(xué)習(xí)率,控制每次迭代更新權(quán)重時的步長,默認(rèn)0.3。調(diào)參:值越小,訓(xùn)練越慢。典型值為0.01-0.2。
                          n_estimators=50,       #含義:總共迭代的次數(shù),即決策樹的個數(shù)
                          max_depth=5,           #含義:樹的深度,默認(rèn)值為6,典型值3-10。調(diào)參:值越大,越容易過擬合;值越小,越容易欠擬合
                          gamma=0,               #含義:懲罰項系數(shù),指定節(jié)點分裂所需的最小損失函數(shù)下降值。
                          subsample=0.9,       #含義:訓(xùn)練每棵樹時,使用的數(shù)據(jù)占全部訓(xùn)練集的比例。默認(rèn)值為1,典型值為0.5-1。調(diào)參:防止overfitting。
                          colsample_bytree=0.5) #訓(xùn)練每棵樹時,使用的特征占全部特征的比例。默認(rèn)值為1,典型值為0.5-1。調(diào)參:防止overfitting。
 
xgb_enc = OneHotEncoder()
xgb_lm = LogisticRegression(solver='lbfgs', max_iter=1000)
 
xgb.fit(X_train, y_train)
xgb_enc.fit(xgb.apply(X_train))
xgb_lm.fit(xgb_enc.transform(xgb.apply(X_train_lr)), y_train_lr)
 
y_pred_xgb_lm = xgb_lm.predict_proba(
    xgb_enc.transform(xgb.apply(X_test)))[:, 1]
fpr_xgb_lm, tpr_xgb_lm, _ = roc_curve(y_test, y_pred_xgb_lm)
print("xgboost+LR的AUC為:", roc_auc_score(y_test, y_pred_xgb_lm))

在之前的代碼中,我們只是用樹模型構(gòu)造的新特征來訓(xùn)練LR。

接下來,我們更近一步,將新特征與原始的20個特征進(jìn)行拼接形成新的數(shù)據(jù)集來訓(xùn)練LR。


X_train_ext = hstack([xgb_enc.transform(xgb.apply(X_train_lr)), X_train_lr])
X_test_ext = hstack([xgb_enc.transform(xgb.apply(X_test)), X_test])
xgb_lm.fit(X_train_ext, y_train_lr)
 
y_pred_xgb_originalfeature_lm = xgb_lm.predict_proba(X_test_ext)[:, 1]
fpr_xgb_originalfeature_lm, tpr_xgb_originalfeature_lm, _ = roc_curve(y_test, y_pred_xgb_originalfeature_lm)
print("xgboost新特征與原始特征+LR的AUC為:", roc_auc_score(y_test, y_pred_xgb_originalfeature_lm))

2.2.5 單獨使用RF, GBDT和Xgboost

為了進(jìn)行對比,我們也輸出單獨使用RF,GBDT和Xgboost的結(jié)果。

# The gradient boosted model by itself
y_pred_grd = grd.predict_proba(X_test)[:, 1]
fpr_grd, tpr_grd, _ = roc_curve(y_test, y_pred_grd)
print("GBT的AUC為:", roc_auc_score(y_test, y_pred_grd))
 
# The random forest model by itself
y_pred_rf = rf.predict_proba(X_test)[:, 1]
fpr_rf, tpr_rf, _ = roc_curve(y_test, y_pred_rf)
print("RF的AUC為:", roc_auc_score(y_test, y_pred_rf))
 
 
# The xgboost model by itself
xgb.fit(X_train, y_train)
y_pred_xgb = xgb.predict_proba(X_test)[:, 1]
fpr_xgb, tpr_xgb, _ = roc_curve(y_test, y_pred_xgb)
print('xgboost的AUC為:' , roc_auc_score(y_test, y_pred_xgb))

2.2.6 結(jié)果對比
我們運行整個代碼,結(jié)果為:

RF+LR的AUC為: 0.972532755993
GBT+LR的AUC為: 0.984711442675
xgboost+LR的AUC為: 0.992587688381
xgboost新特征與原始特征+LR的AUC為: 0.992632312284
GBT的AUC為: 0.98220013158
RF的AUC為: 0.965762807823
xgboost的AUC為: 0.99284427301

我們可以看到:對于RF和GBT,與LR進(jìn)行融合后的結(jié)果要比單獨使用RF和GBT要好。而對于Xgboost,單獨使用Xgboost效果最好,其次是xgboost新特征與原始特征+LR,最后才是xgboost+LR。這與我們預(yù)期不符。為什么會出現(xiàn)這樣的結(jié)果,值得我們討論。

畫圖來進(jìn)一步看下ROC曲線:

plt.figure(1)
plt.plot([0, 1], [0, 1], 'k--')
plt.plot(fpr_rf, tpr_rf, label='RF')
plt.plot(fpr_rf_lm, tpr_rf_lm, label='RF + LR')
plt.plot(fpr_grd, tpr_grd, label='GBT')
plt.plot(fpr_grd_lm, tpr_grd_lm, label='GBT + LR')
plt.plot(fpr_xgb, tpr_xgb, label='XGB')
plt.plot(fpr_xgb_lm, tpr_xgb_lm, label='XGB + LR')
plt.plot(fpr_xgb_originalfeature_lm, tpr_xgb_originalfeature_lm, label='XGB + ori_fea+ LR')
plt.xlabel('False positive rate')
plt.ylabel('True positive rate')
plt.title('ROC curve')
plt.legend(loc='best')
plt.show()

plt.figure(2)
plt.xlim(0, 0.2)
plt.ylim(0.8, 1)
plt.plot([0, 1], [0, 1], 'k--')
plt.plot(fpr_rf, tpr_rf, label='RF')
plt.plot(fpr_rf_lm, tpr_rf_lm, label='RF + LR')
plt.plot(fpr_grd, tpr_grd, label='GBT')
plt.plot(fpr_grd_lm, tpr_grd_lm, label='GBT + LR')
plt.plot(fpr_xgb, tpr_xgb, label='XGB')
plt.plot(fpr_xgb_lm, tpr_xgb_lm, label='XGB + LR')
plt.plot(fpr_xgb_originalfeature_lm, tpr_xgb_originalfeature_lm, label='XGB + ori_fea + LR')
plt.xlabel('False positive rate')
plt.ylabel('True positive rate')
plt.title('ROC curve (zoomed in at top left)')
plt.legend(loc='best')
plt.show()
image
image

上面的圖為ROC曲線,下面的圖是對ROC曲線左上角進(jìn)行了放大。

三、為什么Xgboost+LR的融合效果沒有想象中那么好

在2.2.6中,我們提到了僅使用Xgboost的結(jié)果反而最好。這是為什么呢?因為XGBoost + LR 只是一種特征工程的方法,并不是一種能自動替代特征工程的方法。

借助參考文獻(xiàn)【2】,我們來驗證 XGBoost + LR 是嘗試自動替代特征工程的方法,還只是一種特征工程的方法。

我們在自己業(yè)務(wù)的數(shù)據(jù)上做了一些實驗。下圖便是實驗結(jié)果,其中: “xgboost+lr1" 是 XGBoost 的葉子節(jié)點特征、原始屬性特征和二階交叉特征一起給 LR 進(jìn)行訓(xùn)練;"xgboost+lr2" 則只有葉子節(jié)點特征給 LR;"lr1" 是原始屬性特征和二階交叉特征; "lr2" 只有原始屬性特征。


image.png

從上面的實驗來看:

  1. "xgboost+lr2" 明顯弱于 "lr1" 方法,說明只用葉子節(jié)點特征的 XGBoost + LR 弱于有特征工程的 LR 算法。即 XGBoost 葉子節(jié)點不能取代特征工程,XGBoost + LR 無法取代傳統(tǒng)的特征工程。

  2. "xgboost+lr1" 取得了所有方法中的最好效果,說明了保留原來的特征工程 XGBoost + LR 方法擁有比較好的效果。即 XGBoost 葉子節(jié)點特征是一種有效的特征,XGBoost + LR 是一種有效的特征工程手段。

因此,可以得到以下的結(jié)論:

盡管XGBoost+LR 在工業(yè)和競賽實踐中,都取得了不錯的效果。但 XGBoost 的葉子節(jié)點不能完全替代人工特征, XGBoost + LR 并沒有像深度學(xué)習(xí)那樣試圖帶來自動特征工程的故事和邏輯。最終,XGBoost + LR 的格局沒有超越特征工程。

參考文獻(xiàn)
【1】XGBoost+LR融合的原理和簡單實現(xiàn)

【2】XGBoost + LR 就是加特征而已

【3】XGBoost Plotting API以及GBDT組合特征實踐

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

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

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