机器学习之决策树算法:XGBoost、LightGBM和CatBoost 参考原⽂:
1、XGBoost
XGBoost是陈天奇于2014年提出的⼀种算法,被称为GBM Killer。它⽤预排序算法+直⽅图算法为每⼀层的叶⼦出最佳分裂,简⽽⾔之,就是它是不加区分地分裂同⼀层所有叶⼦。
XGBoost算法的思想:不断添加树,不断进⾏特征分裂来⽣成⼀棵树,每添加⼀棵树就是学习⼀个新的函数来拟合上次的残差,当训练完成后得到K棵树,要预测⼀个样本的分数,其实就是根据样本的特征,在每棵树中会落到对应的⼀个叶⼦节点,每个叶⼦节点对应⼀个分数,最后需要将每棵树对应的分数加起来就是该样本的预测值;
XGBoost能⾃动利⽤cpu的多线程,⽽且适当改进了gradient boosting,加了剪枝,控制了模型的复杂程度, 传统GBDT以CART作为基分类器,特指梯度提升决策树算法,⽽XGBoost还⽀持线性分类器(gblinear),这个时候XGBoost相当于带L1和L2正则化项的逻辑斯蒂回归(分类问题)或者线性回归(回归问题);
XGBoost使⽤的是pre-sorted算法,基本⼯作流程如下:
对每个叶⼦(分割点)遍历所有特征;
对每个特征,按特征值对数据点进⾏排序;
确定当前特征的基本分裂增益,⽤线性扫描决定最佳分裂⽅法;
基于所有特征采取最佳分裂⽅法
直⽅图算法的⼯作⽅式则是根据当前特征把所有数据点分割称离散区域,然后利⽤这些区域查直⽅图的分割值。虽然⽐起预排序算法那种在排序好的特征值上枚举所有可能的分割点的做法,直⽅图算法的效率更⾼,但它在速度上还是落后于GOSS。
2、LightGBM
LightGBM是个快速、分布式的、⾼性能的基于决策树的梯度提升算法,可以⽤于分类、回归、排序等机器学习任务中。
因为它是基于决策树算法的,它采⽤最优的leaf-wise策略分裂叶⼦节点,其它提升算法⼀般采⽤的是depth-wise或者level-wise⽽不是leaf-wise。因此,在LightGBM算法中,当增长到相同的叶⼦节点,leaf-wise算法⽐level-wise算法减少更多的loss。因此导致更⾼的精度,⽽其他的任何已存在的提升算法都不能够达。与此同时,它的速度也让⼈感到震惊,这就是该算法名字Light 的原因。Leaf-Wise分裂导致复杂性的增加并且可能导致过拟合。但是这是可以通过设置另⼀个参数 max-depth来克服,它
分裂产⽣的树的最⼤深度。总结起来LightGBM采⽤Histogram算法进⾏特征选择以及采⽤Leaf-wise的决策树⽣长策略,使其在⼀批以树模型为基模型的boosting算法中脱颖⽽出。
LightGBM使⽤的是histogram算法,基本思想是先把连续的浮点特征值离散化成k个整数,同时构造⼀个宽度为k的直⽅图。在遍历数据的时候,根据离散化后的值作为索引在直⽅图中累积统计量,当遍历⼀次数据后,直⽅图累积了需要的统计量,然后根据直⽅图的离散值,遍历寻最优的分割点。
Level-wise过⼀次数据可以同时分裂同⼀层的叶⼦,容易进⾏多线程优化,也好控制模型复杂度,不容易过拟合。但实际上Level-wise是⼀种低效的算法,因为它不加区分的对待同⼀层的叶⼦,带来了很多没必要的开销,因为实际上很多叶⼦的分裂增益较低,没必要进⾏搜索和分裂。
Leaf-wise则是⼀种更为⾼效的策略,每次从当前所有叶⼦中,到分裂增益最⼤的⼀个叶⼦,然后分裂,如此循环。因此同Level-wise相⽐,在分裂次数相同的情况下,Leaf-wise可以降低更多的误差,得到更好的精度。Leaf-wise的缺点是可能会长出⽐较深的决策树,产⽣过拟合。因此LightGBM在Leaf-wise之上增加了⼀个最⼤深度的限制,在保证⾼效率的同时防⽌过拟合。
2.1、为什么 GOSS ⽅法如此⾼效?
在过滤数据样例寻分割值时,LightGBM 使⽤的是全新的技术:基于梯度的单边采样(GOSS);⽽ XGBoost 则通过预分类算法和直⽅图算法来确定最优分割。
在 Adaboost 中,样本权重是展⽰样本重要性的很好的指标。但在梯度提升决策树(GBDT)中,
并没有天然的样本权重,因此 Adaboost 所使⽤的采样⽅法在这⾥就不能直接使⽤了,这时我们就需要
基于梯度的采样⽅法。
梯度表征损失函数切线的倾斜程度,所以⾃然推理到,如果在某些意义上数据点的梯度⾮常⼤,那么这些
样本对于求解最优分割点⽽⾔就⾮常重要,因为算其损失更⾼。
GOSS 保留所有的⼤梯度样例,并在⼩梯度样例上采取随机抽样。⽐如,假如有 50 万⾏数据,其中
1 万⾏数据的梯度较⼤,那么我的算法就会选择(这 1 万⾏梯度很⼤的数据+x% 从剩余 49 万⾏中
随机抽取的结果)。如果 x 取 10%,那么最后选取的结果就是通过确定分割值得到的,从 50 万⾏中
抽取的 5.9 万⾏。
在这⾥有⼀个基本假设:如果训练集中的训练样例梯度很⼩,那么算法在这个训练集上的训练误差就
很⼩,因为训练已经完成了。
为了使⽤相同的数据分布,在计算信息增益时,GOSS 在⼩梯度数据样例上引⼊⼀个常数因⼦。因此,
GOSS 在减少数据样例数量与保持已学习决策树的准确度之间取得了很好的平衡。
2.2、LightGBM的优势
更快的训练速度和更⾼的效率:LightGBM使⽤基于直⽅图的算法。例如,它将连续的特征值分桶(buckets)装进离散的箱⼦(bins),这是的训练过程中变得更快。
更低的内存占⽤:使⽤离散的箱⼦(bins)保存并替换连续值导致更少的内存占⽤,只保存特征离散化后的值,⽽这个值⼀般⽤8位整型存储就⾜够了,内存消耗可以降低为原来的1/8。
更⾼的准确率(相⽐于其他任何提升算法):它通过leaf-wise分裂⽅法产⽣⽐level-wise分裂⽅法更复杂的树,这就是实现更⾼准确率的主要因素。然⽽,它有时候或导致过拟合,但是我们可以通过设置max-depth参数来防⽌过拟合的发⽣。
⼤数据处理能⼒:相⽐于XGBoost,由于它在训练时间上的缩减,它同样能够具有处理⼤数据的能⼒。
⽀持并⾏学习:特征并⾏和数据并⾏。
3、CatBoost
CatBoost是⼀种能够很好处理类别特征的梯度提升算法,所谓的类别特征是这类特征不是数值型特征,⽽是离散型特征,⽐如性别(男⼥),天⽓(⾬、阴、晴等)。在其他的梯度提升算法中对于类别特征⼀般采⽤one-hot encoder或者label encoder⽅法将类别特征转化为数值特征。
CatBoost 与其它增强学习算法的⼀个主要区别是,CatBoost 实现了对称树。CatBoost 训练速度快,它的性能可能会优于其它增强学习算法,如果数据集中的⼤多数特征都是分类特征,那么CatBoost 是⼀个很好的选择;
CatBoost 可赋予分类变量指标,进⽽通过独热最⼤量得到独热编码形式的结果(独热最⼤量:在所有特征上,对⼩于等于某个给定参数值的不同的数使⽤独热编码)。如果在 CatBoost 语句中没有设置「跳过」,CatBoost 就会将所有列当作数值变量处理;
注意,如果某⼀列数据中包含字符串值,CatBoost 算法就会抛出错误。另外,带有默认值的 int 型变
量也会默认被当成数值数据处理。在CatBoost 中,必须对变量进⾏声明,才可以让算法将其作为分类变量处理。只需告诉CatBoost算法哪些特征是类别特征,他会⾃动把类别特征转化为数值特征;
CatBoost的两⼤优势:第⼀、在训练过程中处理类别型特征,⽽不是在特征预处理阶段处理类别特征;第⼆、选择树结构时,CatBoost选择对称树作为基学习器,有助于减少预测时间,可以避免过拟合。
另外,CatBoost对数据集进⾏随机排序,CatBoost算法还提供了⾮常炫酷的训练可视化功能;
4、应⽤⽰例
使⽤了 2015 年航班延误的 Kaggle 数据集,其中同时包含分类变量和数值变量。这个数据集中⼀共有约 500 万条记录,因此很适合⽤来同时评估⽐较三种 boosting 算法的训练速度和准确度。我使⽤了 10% 的数据:50 万⾏记录。
以下是建模使⽤的特征:
⽉、⽇、星期:整型数据
航线或航班号:整型数据
出发、到达机场:数值数据
出发时间:浮点数据
到达延误情况:这个特征作为预测⽬标,并转为⼆值变量:航班是否延误超过 10 分钟
距离和飞⾏时间:浮点数据
4.1、数据处理
import pandas as pd, numpy as np, time
del_selection import train_test_split
data = pd.read_csv("flights.csv")
data = data.sample(frac = 0.1, random_state=10)
data = data[["MONTH","DAY","DAY_OF_WEEK","AIRLINE","FLIGHT_NUMBER","DESTINATION_AIRPORT",
"ORIGIN_AIRPORT","AIR_TIME", "DEPARTURE_TIME","DISTANCE","ARRIVAL_DELAY"]]
data.dropna(inplace=True)
data["ARRIVAL_DELAY"] = (data["ARRIVAL_DELAY"]>10)*1
cols = ["AIRLINE","FLIGHT_NUMBER","DESTINATION_AIRPORT","ORIGIN_AIRPORT"]
for item in cols:
data[item] = data[item].astype("category").des +1
train, test, y_train, y_test = train_test_split(data.drop(["ARRIVAL_DELAY"], axis=1), data["ARRIVAL_DELAY"],random_state=10, test_size=0.25) 4.2、XGBoost部分代码
import xgboost as xgb from sklearn import metrics
def auc(m, train, test):
return (_auc_score(y_train,m.predict_proba(train)[:,1]),
<_auc_score(y_test,m.predict_proba(test)[:,1]))
# Parameter Tuning
model = xgb.XGBClassifier()
param_dist = {"max_depth": [10,30,50],
"min_child_weight" : [1,3,6],
"n_estimators": [200],
"learning_rate": [0.05, 0.1,0.16],}
grid_search = GridSearchCV(model, param_grid=param_dist, cv = 3,
verbose=10, n_jobs=-1)
grid_search.fit(train, y_train)
grid_search.best_estimator_
model = xgb.XGBClassifier(max_depth=50, min_child_weight=1,  n_estimators=200,\
n_jobs=-1 , verbose=1,learning_rate=0.16)
model.fit(train,y_train)
auc(model, train, test)
4.3、LighGBM部分代码
import lightgbm as lgb
from sklearn import metrics
def auc2(m, train, test):
return (_auc_score(y_train,m.predict(train)),
<_auc_score(y_test,m.predict(test)))
lg = lgb.LGBMClassifier(silent=False)
param_dist = {"max_depth": [25,50, 75],
"learning_rate" : [0.01,0.05,0.1],
"num_leaves": [300,900,1200],
"n_estimators": [200]
}
grid_search = GridSearchCV(lg, n_jobs=-1, param_grid=param_dist, cv = 3, scoring="roc_auc", verbose=5)
grid_search.fit(train,y_train)
grid_search.best_estimator_
d_train = lgb.Dataset(train, label=y_train)
params = {"max_depth": 50, "learning_rate" : 0.1, "num_leaves": 900,  "n_estimators": 300}
# Without Categorical Features
model2 = ain(params, d_train)
auc2(model2, train, test)
#With Catgeorical Features
cate_features_name = ["MONTH","DAY","DAY_OF_WEEK","AIRLINE","DESTINATION_AIRPORT",
"ORIGIN_AIRPORT"]
model2 = ain(params, d_train, categorical_feature = cate_features_name)
auc2(model2, train, test)
4.4、CatBoost部分代码
在对 CatBoost 调参时,很难对分类特征赋予指标。因此,我同时给出了不传递分类特征时的调参结果,并评估了两个模型:⼀个包含分类特征,另⼀个不包含。我单独调整了独热最⼤量,因为它并不会影响其他参数。
import catboost as cb
cat_features_index = [0,1,2,3,4,5,6]
def auc(m, train, test):
return (_auc_score(y_train,m.predict_proba(train)[:,1]),
<_auc_score(y_test,m.predict_proba(test)[:,1]))
params = {'depth': [4, 7, 10],
'learning_rate' : [0.03, 0.1, 0.15],
'l2_leaf_reg': [1,4,9],
'iterations': [300]}
cb = cb.CatBoostClassifier()
cb_model = GridSearchCV(cb, params, scoring="roc_auc", cv = 3)
cb_model.fit(train, y_train)
With Categorical features
clf = cb.CatBoostClassifier(eval_metric="AUC", depth=10, iterations= 500, l2_leaf_reg= 9, learning_rate= 0.15)
clf.fit(train,y_train)
splitwiseauc(clf, train, test)
With Categorical features
clf = cb.CatBoostClassifier(eval_metric="AUC",one_hot_max_size=31, \
depth=10, iterations= 500, l2_leaf_reg= 9, learning_rate= 0.15)
clf.fit(train,y_train, cat_features= cat_features_index)
auc(clf, train, test)
4.5、模型评估
为了评估模型,我们应该同时考虑模型的速度和准确度表现。
请记住,CatBoost 在测试集上表现得最好,测试集的准确度最⾼(0.816)、过拟合程度最⼩(在训练集和测试集上的准确度很接近)以及最⼩的预测和调试时间。但这个表现仅仅在有分类特征,⽽且调节了独热最⼤量时才会出现。如果不利⽤ CatBoost 算法在这些特征上的优势,它的表现效果就会变成最差的:仅有 0.752 的准确度。因此我们认为,只有在数据中包含分类变量,同时我们适当地调节
了这些变量时,CatBoost 才会表现很好。
第⼆个使⽤的是 XGBoost,它的表现也相当不错。即使不考虑数据集包含有转换成数值变量之后能使⽤的分类变量,它的准确率也和CatBoost ⾮常接近了。但是,XGBoost 唯⼀的问题是:它太慢了。尤其是对它进⾏调参,⾮常令⼈崩溃(我⽤了 6 个⼩时来运⾏GridSearchCV——太糟糕了)。更好的选择是分别调参,⽽不是使⽤ GridSearchCV。
最后⼀个模型是 LightGBM,这⾥需要注意的⼀点是,在使⽤ CatBoost 特征时,LightGBM 在训练速度和准确度上的表现都⾮常差。我认为这是因为它在分类数据中使⽤了⼀些修正的均值编码⽅法,进⽽导致了过拟合(训练集准确率⾮常⾼:0.999,尤其是和测试集准确率相⽐之下)。但如果我们像使⽤XGBoost ⼀样正常使⽤ LightGBM,它会⽐ XGBoost 更快地获得相似的准确度,如果不是更⾼的话(LGBM—0.785, XGBoost—0.789)。
最后必须指出,这些结论在这个特定的数据集下成⽴,在其他数据集中,它们可能正确,也可能并不正确。但在⼤多数情况下,XGBoost 都⽐另外两个算法慢。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。