GBDT+LR的特征组合方案是工业界经常使用的组合,尤其是计算广告CTR中应用比较广泛,方案的提出者是Facebook 2014的一篇论文。
相关的开发工具包,sklearn和xgboost(ps:xgboost是一个大杀器,并且支持hadoop分布式,你可以部署实现分布式操作,博主部署过,布置过程较为负责,尤其是环境变量的各种设置)
特征决定模型性能上界,例如深度学习方法也是将数据如何更好的表达为特征。如果能够将数据表达成为线性可分的数据,那么使用简单的线性模型就可以取得很好的效果。GBDT构建新的特征也是使特征更好地表达数据。
主要参考Facebook[1],原文提升效果:
在预测Facebook广告点击中,使用一种将决策树与逻辑回归结合在一起的模型,其优于其他方法,超过3%。
主要思想:GBDT每棵树的路径直接作为LR输入特征使用。
用已有特征训练GBDT模型,然后利用GBDT模型学习到的树来构造新特征,最后把这些新特征加入原有特征一起训练模型。构造的新特征向量是取值0/1的,向量的每个元素对应于GBDT模型中树的叶子结点。当一个样本点通过某棵树最终落在这棵树的一个叶子结点上,那么在新特征向量中这个叶子结点对应的元素值为1,而这棵树的其他叶子结点对应的元素值为0。新特征向量的长度等于GBDT模型里所有树包含的叶子结点数之和。
上图为混合模型结构。输入特征通过增强的决策树进行转换。 每个单独树的输出被视为稀疏线性分类器的分类输入特征。 增强的决策树被证明是非常强大的特征转换。
例子1:上图有两棵树,左树有三个叶子节点,右树有两个叶子节点,最终的特征即为五维的向量。对于输入x,假设他落在左树第一个节点,编码[1,0,0],落在右树第二个节点则编码[0,1],所以整体的编码为[1,0,0,0,1],这类编码作为特征,输入到线性分类模型(LR or FM)中进行分类。
需要注意的是在sklearn或者xgboost输出的结果都是叶子节点的index,所以需要自己动手去做onehot编码,然后交给lr训练,onehot你可以在sklearn的预处理包中调用即可
论文中GBDT的参数,树的数量最多500颗(500以上就没有提升了),每棵树的节点不多于12。
下面给出二者相结合的代码演示
# -*- coding: utf-8 -*- # @Time : 2018/2/27 上午10:39 # @Author : Tomcj # @File : gbdt_lr.py # @Software: PyCharm import xgboost as xgb from sklearn.datasets import load_svmlight_file from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from sklearn.metrics import roc_curve, auc, roc_auc_score from sklearn.externals import joblib from sklearn.preprocessing import OneHotEncoder import numpy as np from scipy.sparse import hstack def xgb_feature_encode(libsvmFileNameInitial): # load样本数据 X_all, y_all = load_svmlight_file(libsvmFileNameInitial) # 训练/测试数据分割 X_train, X_test, y_train, y_test = train_test_split(X_all, y_all, test_size = 0.3, random_state = 42) # 定义模型 xgboost = xgb.XGBClassifier(nthread=4, learning_rate=0.08, n_estimators=50, max_depth=5, gamma=0, subsample=0.9, colsample_bytree=0.5) # 训练学习 xgboost.fit(X_train, y_train) # 预测及AUC评测 y_pred_test = xgboost.predict_proba(X_test)[:, 1] xgb_test_auc = roc_auc_score(y_test, y_pred_test) print('xgboost test auc: %.5f' % xgb_test_auc) # xgboost编码原有特征 X_train_leaves = xgboost.apply(X_train) X_test_leaves = xgboost.apply(X_test) # 训练样本个数 train_rows = X_train_leaves.shape[0] # 合并编码后的训练数据和测试数据 X_leaves = np.concatenate((X_train_leaves, X_test_leaves), axis=0) X_leaves = X_leaves.astype(np.int32) (rows, cols) = X_leaves.shape # 记录每棵树的编码区间 cum_count = np.zeros((1, cols), dtype=np.int32) for j in range(cols): if j == 0: cum_count[0][j] = len(np.unique(X_leaves[:, j])) else: cum_count[0][j] = len(np.unique(X_leaves[:, j])) + cum_count[0][j-1] print('Transform features genenrated by xgboost...') # 对所有特征进行ont-hot编码,注释部分是直接使用onehot函数,结果输出保证是libsvm格式也可以使用 #sklearn中的dump_svmlight_file操作,这个文件代码是参考别人的代码,这些点都是可以优化的。 # onehot=OneHotEncoder() # onehot.fit(X_leaves) # x_leaves_encode=onehot.transform(X_leaves) for j in range(cols): keyMapDict = {} if j == 0: initial_index = 1 else: initial_index = cum_count[0][j-1]+1 for i in range(rows): if X_leaves[i, j] not in keyMapDict: keyMapDict[X_leaves[i, j]] = initial_index X_leaves[i, j] = initial_index initial_index = initial_index + 1 else: X_leaves[i, j] = keyMapDict[X_leaves[i, j]] # 基于编码后的特征,将特征处理为libsvm格式且写入文件 print('Write xgboost learned features to file ...') xgbFeatureLibsvm = open('xgb_feature_libsvm', 'w') for i in range(rows): if i < train_rows: xgbFeatureLibsvm.write(str(y_train[i])) else: xgbFeatureLibsvm.write(str(y_test[i-train_rows])) for j in range(cols): xgbFeatureLibsvm.write(' '+str(X_leaves[i, j])+':1.0') xgbFeatureLibsvm.write('\n') xgbFeatureLibsvm.close() def xgboost_lr_train(xgbfeaturefile, origin_libsvm_file): # load xgboost特征编码后的样本数据 X_xg_all, y_xg_all = load_svmlight_file(xgbfeaturefile) X_train, X_test, y_train, y_test = train_test_split(X_xg_all, y_xg_all, test_size = 0.3, random_state = 42) # load 原始样本数据 X_all, y_all = load_svmlight_file(origin_libsvm_file) X_train_origin, X_test_origin, y_train_origin, y_test_origin = train_test_split(X_all, y_all, test_size = 0.3, random_state = 42) # lr对原始特征样本模型训练 lr = LogisticRegression(n_jobs=-1, C=0.1, penalty='l1') lr.fit(X_train_origin, y_train_origin) joblib.dump(lr, 'lr_orgin.m') # 预测及AUC评测 y_pred_test = lr.predict_proba(X_test_origin)[:, 1] lr_test_auc = roc_auc_score(y_test_origin, y_pred_test) print('基于原有特征的LR AUC: %.5f' % lr_test_auc) # lr对load xgboost特征编码后的样本模型训练 lr = LogisticRegression(n_jobs=-1, C=0.1, penalty='l1') lr.fit(X_train, y_train) joblib.dump(lr, 'lr_xgb.m') # 预测及AUC评测 y_pred_test = lr.predict_proba(X_test)[:, 1] lr_test_auc = roc_auc_score(y_test, y_pred_test) print('基于Xgboost特征编码后的LR AUC: %.5f' % lr_test_auc) # 基于原始特征组合xgboost编码后的特征 X_train_ext = hstack([X_train_origin, X_train]) del(X_train) del(X_train_origin) X_test_ext = hstack([X_test_origin, X_test]) del(X_test) del(X_test_origin) # lr对组合后的新特征的样本进行模型训练 lr = LogisticRegression(n_jobs=-1, C=0.1, penalty='l1') lr.fit(X_train_ext, y_train) joblib.dump(lr, 'lr_ext.m') # 预测及AUC评测 y_pred_test = lr.predict_proba(X_test_ext)[:, 1] lr_test_auc = roc_auc_score(y_test, y_pred_test) print('基于组合特征的LR AUC: %.5f' % lr_test_auc) if __name__ == '__main__': xgb_feature_encode("/Users/leiyang/xgboost/demo/data/agaricus.txt.train") xgboost_lr_train("xgb_feature_libsvm","/Users/leiyang/xgboost/demo/data/agaricus.txt.train")
下面给出一个ipynb文件,也是从官方的文件改过来的,主要是对GBDT输出到lr部分数据观察