xgboost的出现,让数据民工们告别了传统的机器学习算法们:RF、GBM、SVM、LASSO……..。微软推出了一个新的boosting框架,想要挑战xgboost的江湖地位。
顾名思义,lightGBM包含两个关键点:light即轻量级,GBM 梯度提升机。
LightGBM 是一个梯度 boosting 框架,使用基于学习算法的决策树。它可以说是分布式的,高效的,有以下优势:
- 更快的训练效率
- 低内存使用
- 更高的准确率
- 支持并行化学习
- 可处理大规模数据
xgboost缺点
其缺点,或者说不足之处:
每轮迭代时,都需要遍历整个训练数据多次。如果把整个训练数据装进内存则会限制训练数据的大小;如果不装进内存,反复地读写训练数据又会消耗非常大的时间。
预排序方法(pre-sorted):首先,空间消耗大。这样的算法需要保存数据的特征值,还保存了特征排序的结果(例如排序后的索引,为了后续快速的计算分割点),这里需要消耗训练数据两倍的内存。其次时间上也有较大的开销,在遍历每一个分割点的时候,都需要进行分裂增益的计算,消耗的代价大。
对cache优化不友好。在预排序后,特征对梯度的访问是一种随机访问,并且不同的特征访问的顺序不一样,无法对cache进行优化。同时,在每一层长树的时候,需要随机访问一个行索引到叶子索引的数组,并且不同特征访问的顺序也不一样,也会造成较大的cache miss。
lightGBM特点
以上与其说是xgboost的不足,倒不如说是lightGBM作者们构建新算法时着重瞄准的点。解决了什么问题,那么原来模型没解决就成了原模型的缺点。
概括来说,lightGBM主要有以下特点:
- 基于Histogram的决策树算法
- 带深度限制的Leaf-wise的叶子生长策略
- 直方图做差加速
- 直接支持类别特征(Categorical Feature)
- Cache命中率优化
- 基于直方图的稀疏特征优化
- 多线程优化
前2个特点使我们尤为关注的。
Histogram算法
直方图算法的基本思想:先把连续的浮点特征值离散化成k个整数,同时构造一个宽度为k的直方图。遍历数据时,根据离散化后的值作为索引在直方图中累积统计量,当遍历一次数据后,直方图累积了需要的统计量,然后根据直方图的离散值,遍历寻找最优的分割点。
带深度限制的Leaf-wise的叶子生长策略
Level-wise过一次数据可以同时分裂同一层的叶子,容易进行多线程优化,也好控制模型复杂度,不容易过拟合。但实际上Level-wise是一种低效算法,因为它不加区分的对待同一层的叶子,带来了很多没必要的开销,因为实际上很多叶子的分裂增益较低,没必要进行搜索和分裂。
Leaf-wise则是一种更为高效的策略:每次从当前所有叶子中,找到分裂增益最大的一个叶子,然后分裂,如此循环。因此同Level-wise相比,在分裂次数相同的情况下,Leaf-wise可以降低更多的误差,得到更好的精度。
Leaf-wise的缺点:可能会长出比较深的决策树,产生过拟合。因此LightGBM在Leaf-wise之上增加了一个最大深度限制,在保证高效率的同时防止过拟合。
xgboost和lightgbm
决策树算法
XGBoost使用的是pre-sorted算法,能够更精确的找到数据分隔点;
- 首先,对所有特征按数值进行预排序。
- 其次,在每次的样本分割时,用O(# data)的代价找到每个特征的最优分割点。
- 最后,找到最后的特征以及分割点,将数据分裂成左右两个子节点。
优缺点:
这种pre-sorting算法能够准确找到分裂点,但是在空间和时间上有很大的开销。
- i. 由于需要对特征进行预排序并且需要保存排序后的索引值(为了后续快速的计算分裂点),因此内存需要训练数据的两倍。
- ii. 在遍历每一个分割点的时候,都需要进行分裂增益的计算,消耗的代价大。
LightGBM使用的是histogram算法,占用的内存更低,数据分隔的复杂度更低。
其思想是将连续的浮点特征离散成k个离散值,并构造宽度为k的Histogram。然后遍历训练数据,统计每个离散值在直方图中的累计统计量。在进行特征选择时,只需要根据直方图的离散值,遍历寻找最优的分割点。
Histogram 算法的优缺点:
- Histogram算法并不是完美的。由于特征被离散化后,找到的并不是很精确的分割点,所以会对结果产生影响。但在实际的数据集上表明,离散化的分裂点对最终的精度影响并不大,甚至会好一些。原因在于decision tree本身就是一个弱学习器,采用Histogram算法会起到正则化的效果,有效地防止模型的过拟合。
- 时间上的开销由原来的
O(#data * #features)
降到O(k * #features)
。由于离散化,#bin
远小于#data
,因此时间上有很大的提升。 - Histogram算法还可以进一步加速。一个叶子节点的Histogram可以直接由父节点的Histogram和兄弟节点的Histogram做差得到。一般情况下,构造Histogram需要遍历该叶子上的所有数据,通过该方法,只需要遍历Histogram的k个捅。速度提升了一倍。
决策树生长策略
XGBoost采用的是按层生长level(depth)-wise生长策略,如Figure 1所示,能够同时分裂同一层的叶子,从而进行多线程优化,不容易过拟合;但不加区分的对待同一层的叶子,带来了很多没必要的开销。因为实际上很多叶子的分裂增益较低,没必要进行搜索和分裂。
LightGBM采用leaf-wise生长策略,如Figure 2所示,每次从当前所有叶子中找到分裂增益最大(一般也是数据量最大)的一个叶子,然后分裂,如此循环。因此同Level-wise相比,在分裂次数相同的情况下,Leaf-wise可以降低更多的误差,得到更好的精度。Leaf-wise的缺点是可能会长出比较深的决策树,产生过拟合。因此LightGBM在Leaf-wise之上增加了一个最大深度的限制,在保证高效率的同时防止过拟合。
网络通信优化
XGBoost由于采用pre-sorted算法,通信代价非常大,所以在并行的时候也是采用histogram算法;LightGBM采用的histogram算法通信代价小,通过使用集合通信算法,能够实现并行计算的线性加速。
LightGBM支持类别特征
实际上大多数机器学习工具都无法直接支持类别特征,一般需要把类别特征,转化one-hotting特征,降低了空间和时间的效率。而类别特征的使用是在实践中很常用的。基于这个考虑,LightGBM优化了对类别特征的支持,可以直接输入类别特征,不需要额外的0/1展开。并在决策树算法上增加了类别特征的决策规则。
lightGBM调参
所有的参数含义,参考:http://lightgbm.apachecn.org/cn/latest/Parameters.html
调参过程:
num_leaves
LightGBM使用的是leaf-wise的算法,因此在调节树的复杂程度时,使用的是num_leaves而不是max_depth。大致换算关系:$num_leaves = 2^{(max_depth)}$;
样本分布非平衡数据集:可以
param[‘is_unbalance’]=’true’
;Bagging参数:bagging_fraction+bagging_freq(必须同时设置)、feature_fraction。bagging_fraction可以使bagging的更快的运行出结果,feature_fraction设置在每次迭代中使用特征的比例;
min_data_in_leaf、min_sum_hessian_in_leaf:调大它的值可以防止过拟合,它的值通常设置的比较大。
sklearn接口形式的LightGBM示例
这里主要以sklearn的使用形式来使用lightgbm算法,包含建模,训练,预测,网格参数优化。
1 | import lightgbm as lgb |
原生形式使用lightgbm
1 | # coding: utf-8 |
参数速查:
xgb | lgb | xgb.sklearn | lgb.sklearn |
---|---|---|---|
booster=’gbtree’ | boosting=’gbdt’ | booster=’gbtree’ | boosting_type=’gbdt’ |
objective=’binary:logistic’ | application=’binary’ | objective=’binary:logistic’ | objective=’binary’ |
max_depth=7 | num_leaves=2**7 | max_depth=7 | num_leaves=2**7 |
eta=0.1 | learning_rate=0.1 | learning_rate=0.1 | learning_rate=0.1 |
num_boost_round=10 | num_boost_round=10 | n_estimators=10 | n_estimators=10 |
gamma=0 | min_split_gain=0.0 | gamma=0 | min_split_gain=0.0 |
min_child_weight=5 | min_child_weight=5 | min_child_weight=5 | min_child_weight=5 |
subsample=1 | bagging_fraction=1 | subsample=1.0 | subsample=1.0 |
colsample_bytree=1.0 | feature_fraction=1 | colsample_bytree=1.0 | colsample_bytree=1.0 |
alpha=0 | lambda_l1=0 | reg_alpha=0.0 | reg_alpha=0.0 |
lambda=1 | lambda_l2=0 | reg_lambda=1 | reg_lambda=0.0 |
scale_pos_weight=1 | scale_pos_weight=1 | scale_pos_weight=1 | scale_pos_weight=1 |
seed | bagging_seed feature_fraction_seed | random_state=888 | random_state=888 |
nthread | num_threads | n_jobs=4 | n_jobs=4 |
evals | valid_sets | eval_set | eval_set |
eval_metric | metric | eval_metric | eval_metric |
early_stopping_rounds | early_stopping_rounds | early_stopping_rounds | early_stopping_rounds |
verbose_eval | verbose_eval | verbose | verbose |
评论