1. LightGBM简介
GBDT (Gradient Boosting Decision Tree) 是机器学习中一个长盛不衰的模型,其主要思想是利用弱分类器(决策树)迭代训练以得到最优模型,该模型具有训练效果好、不易过拟合等优点。GBDT不仅在工业界应用广泛,通常被用于多分类、点击率预测、搜索排序等任务;在各种数据挖掘竞赛中也是致命武器,据统计Kaggle上的比赛有一半以上的冠军方案都是基于GBDT。而LightGBM(Light Gradient Boosting Machine)是一个实现GBDT算法的框架,支持高效率的并行训练,并且具有更快的训练速度、更低的内存消耗、更好的准确率、支持分布式可以快速处理海量数据等优点。
1.1 LightGBM提出的动机
常用的机器学习算法,例如神经网络等算法,都可以以mini-batch的方式训练,训练数据的大小不会受到内存限制。而GBDT在每一次迭代的时候,都需要遍历整个训练数据多次。如果把整个训练数据装进内存则会限制训练数据的大小;如果不装进内存,反复地读写训练数据又会消耗非常大的时间。尤其面对工业级海量的数据,普通的GBDT算法是不能满足其需求的。
LightGBM提出的主要原因就是为了解决GBDT在海量数据遇到的问题,让GBDT可以更好更快地用于工业实践。
1.2 XGBoost的缺点及LightGBM的优化
(1)XGBoost的缺点
在LightGBM提出之前,最有名的GBDT工具就是XGBoost了,它是基于预排序方法的决策树算法。这种构建决策树的算法基本思想是:首先,对所有特征都按照特征的数值进行预排序。其次,在遍历分割点的时候用的代价找到一个特征上的最好分割点。最后,在找到一个特征的最好分割点后,将数据分裂成左右子节点。
这样的预排序算法的优点是能精确地找到分割点。但是缺点也很明显:首先,空间消耗大。这样的算法需要保存数据的特征值,还保存了特征排序的结果(例如,为了后续快速的计算分割点,保存了排序后的索引),这就需要消耗训练数据两倍的内存。其次,时间上也有较大的开销,在遍历每一个分割点的时候,都需要进行分裂增益的计算,消耗的代价大。最后,对cache优化不友好。在预排序后,特征对梯度的访问是一种随机访问,并且不同的特征访问的顺序不一样,无法对cache进行优化。同时,在每一层长树的时候,需要随机访问一个行索引到叶子索引的数组,并且不同特征访问的顺序也不一样,也会造成较大的cache miss。
(2)LightGBM的优化
为了避免上述XGBoost的缺陷,并且能够在不损害准确率的条件下加快GBDT模型的训练速度,lightGBM在传统的GBDT算法上进行了如下优化:
-
1. 基于Histogram的决策树算法。
-
2. 单边梯度采样 Gradient-based One-Side Sampling(GOSS):使用GOSS可以减少大量只具有小梯度的数据实例,这样在计算信息增益的时候只利用剩下的具有高梯度的数据就可以了,相比XGBoost遍历所有特征值节省了不少时间和空间上的开销。
-
3. 互斥特征捆绑 Exclusive Feature Bundling(EFB):使用EFB可以将许多互斥的特征绑定为一个特征,这样达到了降维的目的。
-
4. 带深度限制的Leaf-wise的叶子生长策略:大多数GBDT工具使用低效的按层生长 (level-wise) 的决策树生长策略,因为它不加区分的对待同一层的叶子,带来了很多没必要的开销。实际上很多叶子的分裂增益较低,没必要进行搜索和分裂。LightGBM使用了带有深度限制的按叶子生长 (leaf-wise) 算法。
-
5. 直接支持类别特征(Categorical Feature)
-
6. 支持高效并行
-
7. Cache命中率优化。Cache命中率(Cache Hit Rate)是指从缓存中成功读取数据的次数占总读取次数的比例。高的缓存命中率意味着更多的数据请求可以直接从缓存中获取,而无需访问较慢的主存储器或外部数据源,从而提高系统性能。
Cache命中率优化的目的是通过不同的策略和技术来提高缓存的命中率,从而减少数据访问的延迟,提高系统整体的响应速度和效率。
下面我们就详细介绍以上提到的lightGBM优化算法。
2. LightGBM的基本原理
2.1 基于Histogram的决策树算法
(1)直方图算法
Histogram algorithm应该翻译为直方图算法,直方图算法的基本思想是:先把连续的浮点特征值离散化成 K个整数,同时构造一个宽度为K 的直方图。在遍历数据的时候,根据离散化后的值作为索引在直方图中累积统计量,当遍历一次数据后,直方图累积了需要的统计量,然后根据直方图的离散值,遍历寻找最优的分割点。
图:直方图算法
直方图算法简单理解为:首先确定对于每一个特征需要多少个箱子(bin)并为每一个箱子分配一个整数;然后将浮点数的范围均分成若干区间,区间个数与箱子个数相等,将属于该箱子的样本数据更新为箱子的值;最后用直方图(#bins)表示。看起来很高大上,其实就是直方图统计,将大规模的数据放在了直方图中。
直方图示例
直方图算法(Histogram Algorithm)用于统计数据集中各个值或区间出现的频率。它常用于数据分析和图像处理等领域。以下是关于直方图算法的一些详细说明和示例:
直方图基本概念
直方图是一种柱状图,用于表示数据分布情况。它将数据范围分成若干个区间(称为“桶”或“bin”),然后统计每个区间内数据的数量。
直方图的步骤
-
确定区间(bin)的数量和范围:
- 区间的数量可以是固定的,也可以根据数据动态调整。
- 每个区间的宽度通常是相等的。
-
分配数据到区间:
- 根据数据值,确定它属于哪个区间。
-
统计每个区间的数据数量:
- 对每个区间,统计数据的数量。
示例:生成直方图
以下是一个用 Python 实现直方图算法的简单示例:
import matplotlib.pyplot as plt
def generate_histogram(data, bin_count):
min_value = min(data)
max_value = max(data)
bin_width = (max_value - min_value) / bin_count
bins = [0] * bin_count
bin_edges = [min_value + i * bin_width for i in range(bin_count + 1)]
for value in data:
for i in range(bin_count):
if bin_edges[i] <= value < bin_edges[i + 1]:
bins[i] += 1
break
return bins, bin_edges
# 示例数据
data = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5]
# 生成直方图
bin_count = 5
bins, bin_edges = generate_histogram(data, bin_count)
# 输出直方图
print("Bins:", bins)
print("Bin Edges:", bin_edges)
# 使用 matplotlib 可视化直方图
plt.hist(data, bins=bin_edges, edgecolor='black')
plt.xlabel('Value')
plt.ylabel('Frequency')
plt.title('Histogram')
plt.show()
优化直方图算法
为了提高直方图算法的性能,可以考虑以下优化方法:
- 使用更高效的数据结构:例如,使用数组而不是列表来存储区间计数。
- 并行化计算:在数据量很大的情况下,可以将数据分片,并行计算每个片段的直方图,然后合并结果。
- 选择合适的区间数量:区间数量过少可能导致信息丢失,过多则可能导致计算复杂度增加。可以根据数据分布动态调整区间数量。
直方图算法在数据分析和图像处理中非常有用,掌握它可以帮助你更好地理解数据分布并进行相应的处理。
我们知道特征离散化具有很多优点,如存储方便、运算更快、鲁棒性强、模型更加稳定等。对于直方图算法来说最直接的有以下两个优点:
- 内存占用更小: 直方图算法不仅不需要额外存储预排序的结果,而且可以只保存特征离散化后的值,而这个值一般用 8位整型存储就足够了,内存消耗可以降低为原来的1/8 。也就是说XGBoost需要用 32位的浮点数去存储特征值,并用32 位的整形去存储索引,而 LightGBM只需要用8位去存储直方图,内存相当于减少为1/8 ;
图:内存占用优化为预排序算法的1/8
- 计算代价更小: 预排序算法XGBoost每遍历一个特征值就需要计算一次分裂的增益,而直方图算法LightGBM只需要计算K 次&#x