机器不学习:一文看懂机器学习时代神器—LightGBM

先show 一下:

30万样本,40维特征,lightGBM在22秒内跑完,速度惊人,比xgboost快不少,精度与xgboost不相上下。但是易用性和特性相比xgboost还有待提高,cv,early stopping这两个我觉得非常重要的特性并没有找到

大多数机器学习工具都无法直接支持类别特征作为输入,一般需要转换成多维0/1特征,带来计算和内存上的额外消耗。LightGBM增加了针对于类别特征的决策规则,这在决策树上也很好实现。主要的思想是,在对类别特征计算分割增益的时候,不是按照数值特征那样由一个阈值进行切分,而是直接把其中一个类别当成一类,其他的类别当成另一类。这实际上与0/1展开的效果是一样的。

GBDT 虽然是个强力的模型,但却有着一个致命的缺陷,不能用类似 mini batch 的方式来训练,需要对数据进行无数次的遍历。如果想要速度,就需要把数据都预加载在内存中,但这样数据就会受限于内存的大小;如果想要训练更多的数据,就要使用外存版本的决策树算法。虽然外存算法也有较多优化,SSD 也在普及,但在频繁的 IO 下,速度还是比较慢的。

为了能让 GBDT 高效地用上更多的数据,我们把思路转向了分布式 GBDT, 然后就有了 LightGBM。设计的思路主要是两点,1. 单个机器在不牺牲速度的情况下,尽可能多地用上更多的数据;2.

多机并行的时候,通信的代价尽可能地低,并且在计算上可以做到线性加速。

基于这两个需求,LightGBM 选择了基于 histogram 的决策树算法。相比于另一个主流的算法 pre-sorted(如 xgboost 中的 exact 算法),histogram 在内存消耗和计算代价上都有不少优势。

  • Pre-sorted 算法需要的内存约是训练数据的两倍(2 * #data * #features
  • * 4Bytes),它需要用32位浮点来保存 feature value,并且对每一列特征,都需要一个额外的排好序的索引,这也需要32位的存储空间。对于 histogram 算法,则只需要(#data
  • * #features * 1Bytes)的内存消耗,仅为 pre-sorted算法的1/8。因为 histogram 算法仅需要存储 feature
  • bin value (离散化后的数值),不需要原始的 feature value,也不用排序,而 bin
  • value 用 uint8_t (256
  • bins) 的类型一般也就足够了。
  • 在计算上的优势则主要体现在“数据分割”。决策树算法有两个主要操作组成,一个是“寻找分割点”,另一个是“数据分割”。从算法时间复杂度来看,Histogram 算法和 pre-sorted 算法在“寻找分割点”的代价是一样的,都是O(#feature*#data)。而在“数据分割”时,pre-sorted 算法需要O(#feature*#data),而 histogram 算法是O(#data)。因为 pre-sorted 算法的每一列特征的顺序都不一样,分割的时候需要对每个特征单独进行一次分割。Histogram算法不需要排序,所有特征共享同一个索引表,分割的时候仅需对这个索引表操作一次就可以。(更新: 这一点不完全正确,pre-sorted 与 level-wise 结合的时候,其实可以共用一个索引表(row_idx_to_tree_node_idx)。然后在寻找分割点的时候,同时操作同一层的节点,省去分割的步骤。但这样做的问题是会有非常多随机访问,有很大的chche miss,速度依然很慢。)。

机器不学习:一文看懂机器学习时代神器—LightGBM


  • 最后,在数据并行的时候,用 histgoram 可以大幅降低通信代价。用 pre-sorted 算法的话,通信代价是非常大的(几乎是没办法用的)。所以 xgoobst 在并行的时候也使用 histogram 进行通信。

当然, histogram 算法也有缺点,它不能找到很精确的分割点,训练误差没有 pre-sorted 好。但从实验结果来看, histogram 算法在测试集的误差和 pre-sorted 算法差异并不是很大,甚至有时候效果更好。实际上可能决策树对于分割点的精确程度并不太敏感,而且较“粗”的分割点也自带正则化的效果。

机器不学习:一文看懂机器学习时代神器—LightGBM

在 histogram 算法之上, LightGBM 进行进一步的优化。首先它抛弃了大多数 GBDT 工具使用的按层生长

(level-wise) 的决策树生长策略,而使用了带有深度限制的按叶子生长 (leaf-wise) 算法。 level-wise 过一次数据可以同时分裂同一层的叶子,容易进行多线程优化,不容易过拟合。但实际上level-wise是一种低效的算法,因为它不加区分的对待同一层的叶子,带来了很多没必要的开销。因为实际上很多叶子的分裂增益较低,没必要进行搜索和分裂。leaf-wise则是一种更为高效的策略,每次从当前所有叶子中,找到分裂增益最大(一般也是数据量最大)的一个叶子,然后分裂,如此循环。因此同 level-wise 相比,在分裂次数相同的情况下,leaf-wise 可以降低更多的误差,得到更好的精度。leaf-wise 的缺点是可能会长出比较深的决策树,产生过拟合。因此 LightGBM 在leaf-wise 之上增加了一个最大深度的限制,在保证高效率的同时防止过拟合。

另一个比较巧妙的优化是 histogram 做差加速。一个容易观察到的现象:一个叶子的直方图可以由它的父亲节点的直方图与它兄弟的直方图做差得到。通常构造直方图,需要遍历该叶子上的所有数据,但直方图做差仅需遍历直方图的 k 个桶。利用这个方法,LightGBM 可以在构造一个叶子的直方图后,可以用非常微小的代价得到它兄弟叶子的直方图,在速度上可以提升一倍。

https://github.com/Microsoft/LightGBM

相关推荐