GBDT+LR算法背景
以前在推荐系统中的CTR预估问题有以下几点:
- 样本数量大,点击率预估模型中的训练样本可达上亿级别
- 学习能力有限,以往的CTR预估采用LR模型,LR是线性模型,虽然速度较快,但是学习能力有限
- 人工成本高,为了更好的进行特征提取,提升LR的学习能力,需要采用人工特征工程,即通过人工方式找到有区分度的特征、特征组合。对人的要求高,时间成本高
针对以上问题,为了自动发现有效的特征及特征组合,弥补人工经验不足,缩短LR实验周期,提出了GBDT+LR算法。
这个算法的大体思路如下:
- 先用GBDT对样本进行特征构造,通过GBDT训练得到每棵树激活的叶子节点并进行热编码处理(原本的数据维度会得到升维)
- 然后在放到LR中进行训练和预测
Gradient Boosting Decision Tree
- GBDT是是由多颗CART树组成,将累加所有树的结果作为最终结果。
- GBDT拟合的目标是一个梯度值(连续值,实数),所以在GBDT里,都是回归树。
- GBDT用来做回归预测,调整后也可以用于分类
下面讲讲Gradient Boosting的思想
- Boosting,即即通过迭代多棵树来共同决策
- Gradient Boosting,每一次建立模型是在之前模型损失函数的梯度下降方向
- Gradient Boosting Decision Tree,每一棵树的残差作为下一颗树拟合的目标。最终的结果是将所有树的结果累加起来。
- 流程图:
简 化 流 程 : 初 始 化 f 0 ( x ) = 0 每 生 成 一 棵 树 , 都 会 产 生 残 差 : r = y i − f i − 1 ( x ) , i = 1 , 2 , 3 , . . . n 将 上 一 颗 树 生 成 的 残 差 作 为 下 一 颗 树 拟 合 的 目 标 , 即 有 一 颗 新 的 树 : h i ( x ) 每 生 成 一 棵 树 , 更 新 G B D T 集 成 模 型 : f i ( x ) = f i − 1 ( x ) + h i ( x ) 获 得 最 终 的 结 果 f i ( x ) = ∑ i = 1 n h i ( x ) \begin{aligned} &简化流程:\\ &初始化f_0(x) = 0\\ &每生成一棵树,都会产生残差:r = y_{i} - f_{i-1}(x),i = 1,2,3,...n\\ &将上一颗树生成的残差作为下一颗树拟合的目标,即有一颗新的树:h_i(x)\\ &每生成一棵树,更新GBDT集成模型:f_i(x) = f_{i-1}(x)+h_i(x)\\ &获得最终的结果f_i(x) = \sum_{i=1}^n{h_i(x)}\\ \end{aligned} 简化流程:初始化f0(x)=0每生成一棵树,都会产生残差:r=yi−fi−1(x),i=1,2,3,...n将上一颗树生成的残差作为下一颗树拟合的目标,即有一颗新的树:hi(x)每生成一棵树,更新GBDT集成模型:fi(x)=fi−1(x)+hi(x)获得最终的结果fi(x)=i=1∑nhi(x)
详 细 流 程 : f 0 = 0 初 始 化 模 型 f 1 = f 0 + h 1 = h 1 计 算 首 颗 树 h 1 的 结 果 , 将 其 加 入 的 模 型 中 , 并 获 得 残 差 r 1 f 2 = f 1 + h 2 = h 2 + h 1 根 据 r 1 作 为 新 的 l a b e l 进 行 拟 合 , 计 算 得 出 第 二 棵 树 h 2 , 将 其 加 入 到 模 型 中 , 并 获 得 残 差 r 2 f 3 = f 2 + h 3 = h 3 + h 2 + h 1 将 r 2 作 为 新 的 l a b e l 进 行 拟 合 , 计 算 出 第 三 棵 树 h 3 , 将 其 加 入 到 模 型 中 , 并 获 得 残 差 r 3 . . . f n = f n − 1 + h n = h 1 + h 2 + h 3 + . . . + h n 将 r n − 1 的 结 果 作 为 新 的 l a b e l 进 行 拟 合 , 计 算 出 第 n 棵 树 h n , 并 将 其 加 入 到 模 型 中 , 获 得 残 差 r n \begin{aligned} &详细流程:\\ &f_0 = 0\ 初始化模型\\ &f_1 = f_0+h_1=h_1\ 计算首颗树h_1的结果,将其加入的模型中,并获得残差r_1\\ &f_2 = f_1+h_2=h_2+h_1\ 根据r_1作为新的label进行拟合,计算得出第二棵树h_2,将其加入到模型中,并获得残差r_2\\ &f_3 = f_2+h_3 = h_3+h_2+h_1\ 将r_2作为新的label进行拟合,计算出第三棵树h_3,将其加入到模型中,并获得残差r_3\\ &...\\ &f_n = f_{n-1}+h_n = h_1+h_2+h_3+...+h_n\ 将r_{n-1}的结果作为新的label进行拟合,计算出第n棵树h_n,并将其加入到模型中,获得残差r_n \end{aligned} 详细流程:f0=0 初始化模型f1=f0+h1=h1 计算首颗树h1的结果,将其加入的模型中,并获得残差r1f2=f1+h2=h2+h1 根据r1作为新的label进行拟合,计算得出第二棵树h2,将其加入到模型中,并获得残差r2f3=f2+h3=h3+h2+h1 将r2作为新的label进行拟合,计算出第三棵树h3,将其加入到模型中,并获得残差r3...fn=fn−1+hn=h1+h2+h3+...+hn 将rn−1的结果作为新的label进行拟合,计算出第n棵树hn,并将其加入到模型中,获得残差rn
经典+究极版例子讲解:
了解GBDT的算法简单原理,那么就需要知道如何使用GBDT进行新特征的构造:
- 当GBDT训练好做预测的时候,输出的并不是最终的二分类概率值,而是要把模型中的每棵树计算得到的预测概率值所属的叶子结点位置记为1 => 构造新的训练数据
- 如下图,有2棵决策树,一共有5个叶子节点
- 如果一个实例,选择了第一棵决策树的第2个叶子节点。同时,选择第2棵子树的第1个叶子节点。那么前3个叶子节点中,第2位设置为1,后2个叶子节点中,第1位设置为1。concatenate所有特征向量,得到[0,1,0,1,0]
- 因为GBDT是一堆树的组合,假设有k棵树(T1,T2…Tk),每棵树的节点数分别为 N_t, GBDT会输出一个k*N_t维的向量
GBDT+LR结论
根据论文 Greedy function Approximation – A Gradient Boosting Machine的结果,有以下结论:
- 使用GBDT+LR,相比单纯的LR和GBDT,在Loss上减少了3%,提升作用明显
- 前500棵决策树,会让NE下降,而后1000棵决策树对NE下降不明显,不超过0.1%(GBDT决策树规模超过500时,效果不明显)
- 实验中,boosting tree数量从1到2000,叶节点个数被限制为最大12个(即每棵树不超过12个叶子节点)
- 所有特征的重要度总和是1
- TOP10个特征,贡献了50%的重要程度,而后面300个特征,贡献了1%的重要度
- 大量弱特征的累积也很重要,不能都去掉。如果去掉部分不重要的特征,对模型的影响比较小
Example
下面通过一个例子,对比
- RF,RF+LR
- GBDT,GBDT+LR
- LGB,LGB+LR
以上这几种算法进行比较二分类结果,评测指标为AUC
算法步骤简单阐述:
- Step1,将数据分成训练集和测试集,其中训练集分成GBDT训练集和LR训练集
- Step2,使用决策树进行特征转换
- Step3,使用LR对转换好的特征进行训练
- Step4,对比不同算法,绘制ROC曲线
代码展示
#先导入用到的包
import numpy as np
import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.datasets import make_classification
import lightgbm as lgb
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import OneHotEncoder
from sklearn.metrics import roc_auc_score,roc_curve
#调包生成数据集
X, y = make_classification(n_samples=80000,n_features=20)
#整体划分数据集,按8-2划分原则
X_train,X_test,y_train,y_test = train_test_split(X,y,test_size = 0.2)
#在根据训练集划分集成学习训练的部分+LR训练的部分,按5-5划分原则
X_train_ensemble,X_train_lr,y_train_ensemble,y_train_lr = train_test_split(X_train,y_train,test_size = 0.5)
#策略一:RF+LR
#先调用Random Forest进行训练,激活叶子节点
rf = RandomForestClassifier(n_estimators = 10, max_depth = 3)
rf.fit(X_train_ensemble, y_train_ensemble)
#此处为了获得每棵树所激活的节点
#rf.apply(X_train_lr)的大小为x行10列,10表示树的数量
#rf.apply(X_train_lr)的元素代表了哪一个样本落在了哪一个树的第几个叶子节点上,如32,就代表了落在了一棵树的第32个叶子节点上。
node_ensemble = rf.apply(X_train_ensemble)
node_lr = rf.apply(X_train_lr)
#将获取的节点进行热编码onehot处理
oe = OneHotEncoder(categories='auto')
oe.fit(node_ensemble)
oe_node = oe.transform(node_lr)
#在调用Logistic Regression进行训练+预测+评测
lr = LogisticRegression(solver = 'lbfgs', max_iter = 1000)
lr.fit(oe_node,y_train_lr)
y_pred_rf_lr = lr.predict_proba(oe.transform(rf.apply(X_test)))[:,1]
auc_rf_lr = roc_auc_score(y_test,y_pred_rf_lr)
fpr_rf_lr,tpr_rf_lr,_ = roc_curve(y_test,y_pred_rf_lr)
auc_rf_lr #AUC结果为0.9516905330971566
#策略二:GBDT+LR
#先调用GBDT进行训练,激活叶子节点
gbdt = GradientBoostingClassifier(n_estimators = 10, max_depth = 3)
gbdt.fit(X_train_ensemble, y_train_ensemble)
#此处为了获得每棵树所激活的节点
#gbdt.apply(X_train_lr)的大小为x行10列,10表示树的数量
#gbdt.apply(X_train_lr)的元素代表了哪一个样本落在了哪一个树的第几个叶子节点上,如32,就代表了落在了一棵树的第32个叶子节点上。
node_ensemble = gbdt.apply(X_train_ensemble)[:,:,0]
node_lr = gbdt.apply(X_train_lr)[:,:,0]
#将获取的节点进行热编码onehot处理
oe = OneHotEncoder(categories='auto')
oe.fit(node_ensemble)
oe_node = oe.transform(node_lr)
#在调用Logistic Regression进行训练+预测+评测
lr = LogisticRegression(solver = 'lbfgs', max_iter = 1000)
lr.fit(oe_node,y_train_lr)
y_pred_gbdt_lr = lr.predict_proba(oe.transform(gbdt.apply(X_test)[:,:,0]))[:,1]
auc_gbdt_lr = roc_auc_score(y_test,y_pred_gbdt_lr)
fpr_gbdt_lr,tpr_gbdt_lr,_ = roc_curve(y_test,y_pred_gbdt_lr)
auc_gbdt_lr #AUC结果为0.957183465262456
#策略三:LGB+LR
#先调用LGB进行训练,激活叶子节点
lgbm = lgb.LGBMClassifier(n_estimators = 10, max_depth = 3)
lgbm.fit(X_train_ensemble, y_train_ensemble)
#此处为了获得每棵树所激活的节点
#lgbm.predict(X_train_lr,pred_leaf=True)的大小为x行10列,10表示树的数量
#lgbm.predict(X_train_lr,pred_leaf=True)的元素代表了哪一个样本落在了哪一个树的第几个叶子节点上,如32,就代表了落在了一棵树的第32个叶子节点上。
node_ensemble = lgbm.predict(X_train_ensemble,pred_leaf=True)
node_lr = lgbm.predict(X_train_lr,pred_leaf=True)
#将获取的节点进行热编码onehot处理
oe = OneHotEncoder(categories='auto')
oe.fit(node_ensemble)
oe_node = oe.transform(node_lr)
#在调用Logistic Regression进行训练+预测+评测
lr = LogisticRegression(solver = 'lbfgs', max_iter = 1000)
lr.fit(oe_node,y_train_lr)
y_pred_lgbm_lr = lr.predict_proba(oe.transform(lgbm.predict(X_test,pred_leaf=True)))[:,1]
auc_lgbm_lr = roc_auc_score(y_test,y_pred_lgbm_lr)
fpr_lgbm_lr,tpr_lgbm_lr,_ = roc_curve(y_test,y_pred_lgbm_lr)
auc_lgbm_lr #AUC结果为0.955906697794402
#策略四:RF
#Random Forest进行训练+测试+评测
rf = RandomForestClassifier(n_estimators = 10, max_depth = 3)
rf.fit(X_train, y_train)
y_pred_rf = rf.predict_proba(X_test)[:,1]
auc_rf = roc_auc_score(y_test,y_pred_rf)
fpr_rf,tpr_rf,_ = roc_curve(y_test,y_pred_rf)
auc_rf #AUC结果为0.9432453364178567
#策略五:GBDT
#GBDT进行训练+测试+评测
gbdt = GradientBoostingClassifier(n_estimators = 10, max_depth = 3)
gbdt.fit(X_train, y_train)
y_pred_gbdt = gbdt.predict_proba(X_test)[:,1]
auc_gbdt = roc_auc_score(y_test,y_pred_gbdt)
fpr_gbdt,tpr_gbdt,_ = roc_curve(y_test,y_pred_gbdt)
auc_gbdt #AUC结果为0.9546816494674741
#策略六:LGB
#LGB进行训练+测试+评测
lgbm = lgb.LGBMClassifier(n_estimators = 10, max_depth = 3)
lgbm.fit(X_train, y_train)
y_pred_lgbm = lgbm.predict_proba(X_test)[:,1]
auc_lgbm = roc_auc_score(y_test,y_pred_lgbm)
fpr_lgbm,tpr_lgbm,_ = roc_curve(y_test,y_pred_lgbm)
auc_lgbm #AUC结果为0.9541327390663392
#将所有结果进行ROC曲线绘图
import matplotlib.pyplot as plt
plt.figure(figsize = (10,8))
plt.plot([0,1],[0,1,],'k--')
plt.plot(fpr_rf_lr,tpr_rf_lr,label = 'RF+LR')
plt.plot(fpr_gbdt_lr,tpr_gbdt_lr,label = 'GBDT+LR')
plt.plot(fpr_lgbm_lr,tpr_lgbm_lr,label = 'LGB+LR')
plt.plot(fpr_rf,tpr_rf,label = 'RF')
plt.plot(fpr_gbdt,tpr_gbdt,label = 'GBDT')
plt.plot(fpr_lgbm,tpr_lgbm,label = 'LGB')
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_lr,tpr_rf_lr,label = 'RF+LR')
plt.plot(fpr_gbdt_lr,tpr_gbdt_lr,label = 'GBDT+LR')
plt.plot(fpr_lgbm_lr,tpr_lgbm_lr,label = 'LGB+LR')
plt.plot(fpr_rf,tpr_rf,label = 'RF')
plt.plot(fpr_gbdt,tpr_gbdt,label = 'GBDT')
plt.plot(fpr_lgbm,tpr_lgbm,label = 'LGB')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC curve (zoomed)')
plt.legend(loc='best')
plt.show()
结论
综上可以得出,不同模型对应的auc大小排序为:
GBDT+LR > LGB+LR > RF+LR > GBDT > LGB > RF