RF, GBDT和Xgboost构造新特征+LR融合的原理及实践

关于Xgboost的知识点很多,本篇博客介绍如何利用Xgboost构造新特征,且在此基础上,介绍与LR模型融合的相关知识点。

一、原理

为什么要使用LR模型进行融合呢?这是因为LR (逻辑回归) 算法简单有效,成为工业界最常用的算法之一。但 LR 算法是线性模型,不能捕捉到非线性信息,需要大量特征工程找到特征组合。为了发现有效的特征组合,Facebook 在 2014年的论文ractical Lessons from Predicting Clicks on Ads at Facebook介绍了通过 GBDT (Gradient Boost Decision Tree)+ LR 的方案 (XGBoost 是 GBDT 的后续发展)。在这篇论文中他们提出了一种将Xgboost作为feature transform的方法。随后在多个Kaggle 竞赛实践中,均证明了此思路的有效性。

大概的思想可以描述为如下:先用已有特征训练Xgboost模型,然后利用Xgboost模型学习到的树来构造新特征,最后把这些新特征加入原有特征一起训练模型。构造的新特征向量是取值0/1的,向量的每个元素对应于Xgboost模型中树的叶子结点。当一个样本点通过某棵树最终落在这棵树的一个叶子结点上,那么在新特征向量中这个叶子结点对应的元素值为1,而这棵树的其他叶子结点对应的元素值为0。新特征向量的长度等于XGBoost模型里所有树包含的叶子结点数之和。最后将新的特征扔到LR模型进行训练。

举例说明。下面的图中的两棵树是GBDT(Xgboost一样)学习到的,第一棵树有3个叶子结点,而第二棵树有2个叶子节点。对于一个输入样本点x,如果它在第一棵树最后落在其中的第二个叶子结点,而在第二棵树里最后落在其中的第一个叶子结点。那么通过GBDT获得的新特征向量为[0, 1, 0, 1, 0],其中向量中的前三位对应第一棵树的3个叶子结点,后两位对应第二棵树的2个叶子结点。

二、实践


2.1 如何获得样本落在哪个叶子节点

在实践中的关键点是如何获得每个样本落在训练后的每棵树的哪个叶子结点上。

A、对于Xgboost来说,因为其有sklearn接口和自带接口,因此有两种方法可以获得:

①、sklearn接口。可以设置pre_leaf=True获得每个样本在每颗树上的leaf_Index。XGBoost官方文档

扫描二维码关注公众号,回复: 5666144 查看本文章

②、自带接口。利用apply()方法可以获得leaf indices。SKlearn GBDT API

此过程需注意: 无论是设置pre_leaf=True还是利用apply()方法,获得的都是叶子节点的 index,也就是说落在了具体哪颗树的哪个叶子节点上,并非是0/1变量,因此需要自己动手去做 onehot 编码。onehot 可以在 sklearn 的预处理包中调用即可。

B、对于其它的树模型,如随机森林和GBDT,我们只能使用apply()方法获得leaf indices。


2.2 举例

接下来,我们举例来说明如何利用树模型,尤其是Xgboost来构建新特征,并且是如何与LR模型进行融合。

本例子对Feature transformations with ensembles of trees进行了改进,使得更加健壮易读。

2.2.1 训练集准备

从代码中,可以看到,我们对训练集X_train又进行了一次切分,生成了训练集X_train, X_train_lr和测试集y_train, y_train_lr。特别要注意数据集的大小,后续我们会进行分析。

注意:我们设定了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
'''
--------------------- 
作者:anshuai_aw1 
来源:CSDN 
原文:https://blog.csdn.net/anshuai_aw1/article/details/82983997 
版权声明:本文为博主原创文章,转载请附上博文链接!

2.2.2 RF+LR

我们首先使用随机森林进行实验。这里我们需要对代码进行一个解读。

  

    # Supervised transformation based on random forests
    rf = RandomForestClassifier(max_depth=3, n_estimators=n_estimator)
    rf_enc = OneHotEncoder()
    rf_lm = LogisticRegression(solver='lbfgs', max_iter=1000)
     
    # 使用随机森林进行训练
    rf.fit(X_train, y_train)
    '''
    rf.apply(X_train)的大小为20000*10,10就是10颗树。
    rf.apply(X_train)的元素代表了哪一个样本落在了哪一个树的第几个叶子节点上,如32,就代表了落在了一棵树的第32个叶子节点上。
    rf_enc.fit(rf.apply(X_train))对rf.apply(X_train)进行了onehot编码
    '''
    rf_enc.fit(rf.apply(X_train))
    '''
    rf_enc.transform(rf.apply(X_train_lr))按照rf.apply(X_train)的编码方式对rf.apply(X_train_lr)进行了onehot编码。
    这里需要注意得是:我们并没有像常规方式那样对rf.apply(X_train)和rf.apply(X_train_lr)先进行合并再进行onehot,这是因为在训练完成之后随机森林的树模型已经固定,即叶子节点的架构已经确定。
    因此rf.apply(X_train_lr)的值的范围和rf.apply(X_train)的值的范围必然一样!
    rf_lm.fit(rf_enc.transform(rf.apply(X_train_lr)), y_train_lr)使用随机森林构造的特征来训练LR
    '''
    rf_lm.fit(rf_enc.transform(rf.apply(X_train_lr)), y_train_lr)
     
    y_pred_rf_lm = rf_lm.predict_proba(rf_enc.transform(rf.apply(X_test)))[:, 1]
    fpr_rf_lm, tpr_rf_lm, _ = roc_curve(y_test, y_pred_rf_lm)
    print("RF+LR的AUC为:", roc_auc_score(y_test, y_pred_rf_lm))

2.2.3 GBDT+LR

我们其次使用随机森林进行实验。其代码与随机森林几乎一样。不再进行代码解读。

# 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))
--------------------- 
作者:anshuai_aw1 
来源:CSDN 
原文:https://blog.csdn.net/anshuai_aw1/article/details/82983997 
版权声明:本文为博主原创文章,转载请附上博文链接!

2.2.4 Xgboost+LR

 最后,我们使用Xgboost进行实验。

  

# Supervised transformation based on xgboost
xgb = xgb.XGBClassifier(nthread=4,     #含义:nthread=-1时,使用全部CPU进行并行运算(默认), nthread=1时,使用1个CPU进行运算。
                          learning_rate=0.08,    #含义:学习率,控制每次迭代更新权重时的步长,默认0.3。调参:值越小,训练越慢。典型值为0.01-0.2。
                          n_estimators=50,       #含义:总共迭代的次数,即决策树的个数
                          max_depth=5,           #含义:树的深度,默认值为6,典型值3-10。调参:值越大,越容易过拟合;值越小,越容易欠拟合
                          gamma=0,               #含义:惩罚项系数,指定节点分裂所需的最小损失函数下降值。
                          subsample=0.9,       #含义:训练每棵树时,使用的数据占全部训练集的比例。默认值为1,典型值为0.5-1。调参:防止overfitting。
                          colsample_bytree=0.5) #训练每棵树时,使用的特征占全部特征的比例。默认值为1,典型值为0.5-1。调参:防止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))

在之前的代码中,我们只是用树模型构造的新特征来训练LR。

接下来,我们更近一步,将新特征与原始的20个特征进行拼接形成新的数据集来训练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

为了进行对比,我们也输出单独使用RF,GBDT和Xgboost的结果。

   

# 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 结果对比

我们运行整个代码,结果为:

    '''
    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进行融合后的结果要比单独使用RF和GBT要好。而对于Xgboost,单独使用Xgboost效果最好,其次是xgboost新特征与原始特征+LR,最后才是xgboost+LR。这与我们预期不符。为什么会出现这样的结果,值得我们讨论。

画图来进一步看下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()
 
  1. plt.show()

左边的图为ROC曲线,右边的图是对ROC曲线左上角进行了放大。


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

在2.2.6中,我们提到了仅使用Xgboost的结果反而最好。这是为什么呢?因为XGBoost + LR 只是一种特征工程的方法,并不是一种能自动替代特征工程的方法。

借助参考文献【2】,我们来验证 XGBoost + LR 是尝试自动替代特征工程的方法,还只是一种特征工程的方法。

我们在自己业务的数据上做了一些实验。下图便是实验结果,其中: “xgboost+lr1" 是 XGBoost 的叶子节点特征、原始属性特征和二阶交叉特征一起给 LR 进行训练;"xgboost+lr2" 则只有叶子节点特征给 LR;"lr1" 是原始属性特征和二阶交叉特征; "lr2" 只有原始属性特征。

从上面的实验来看:

1) "xgboost+lr2" 明显弱于 "lr1" 方法,说明只用叶子节点特征的 XGBoost + LR 弱于有特征工程的 LR 算法。即 XGBoost 叶子节点不能取代特征工程,XGBoost + LR 无法取代传统的特征工程。

2) "xgboost+lr1" 取得了所有方法中的最好效果,说明了保留原来的特征工程 XGBoost + LR 方法拥有比较好的效果。即 XGBoost 叶子节点特征是一种有效的特征,XGBoost + LR 是一种有效的特征工程手段。

因此,可以得到以下的结论:

尽管XGBoost+LR 在工业和竞赛实践中,都取得了不错的效果。但 XGBoost 的叶子节点不能完全替代人工特征, XGBoost + LR 并没有像深度学习那样试图带来自动特征工程的故事和逻辑。最终,XGBoost + LR 的格局没有超越特征工程。
参考文献

【1】XGBoost+LR融合的原理和简单实现

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

【3】XGBoost Plotting API以及GBDT组合特征实践
 

https://blog.csdn.net/anshuai_aw1/article/details/82983997

猜你喜欢

转载自blog.csdn.net/zwqjoy/article/details/86612416
RF
今日推荐