机器学习算法原理实现——lightgbm,核心leaf-wise生长结合数据和特征并行+直方图算法+单边梯度抽样+互斥特征捆绑
算法亮点:
1、leaf-wise生长策略+特征并行和数据并行
让我们通过一个简单的例子来详细解释 LightGBM 的 Leaf-wise 生长策略。假设我们有以下的数据集:
| 年龄 | 收入 | 购买 |
| ---- | ---- | ---- |
| 20 | 3000 | 0 |
| 25 | 3500 | 0 |
| 30 | 4000 | 0 |
| 35 | 4500 | 1 |
| 40 | 5000 | 1 |
| 45 | 5500 | 1 |
我们的目标是预测一个人是否会购买产品。我们有两个特征:年龄和收入。
1. 初始化:首先,我们初始化一个根节点,包含所有的样本。
2. 第一次分裂:我们计算所有可能的分裂点(对于年龄,可能的分裂点是 22.5, 27.5, 32.5, 37.5, 42.5;对于收入,可能的分裂点是 3250, 3750, 4250, 4750, 5250),并找到能够最大程度降低损失函数的分裂点。假设我们找到的最优分裂点是年龄 32.5,我们按照这个分裂点将数据分为两部分,形成两个新的叶子节点。
3. 第二次分裂:我们在所有的叶子节点中,找到一个能够最大程度降低损失函数的节点进行分裂。假设我们找到的最优节点是年龄大于 32.5 的那部分,我们再在这部分数据中找到最优的分裂点。假设我们找到的最优分裂点是收入 4750,我们按照这个分裂点将数据分为两部分,形成两个新的叶子节点。
4. 后续的分裂:我们重复上述过程,每次都在所有的叶子节点中找到一个能够最大程度降低损失函数的节点进行分裂,直到达到预设的最大叶子节点数量或者无法进一步降低损失函数为止。
这就是 LightGBM 的 Leaf-wise 生长策略的基本过程。需要注意的是,这个过程中的每一步分裂都是基于严格的数学计算的,包括计算损失函数的降低量,选择最优的分裂点等。
lightgbm算法中,Leaf-wise是如何做到多线程优化的?
LightGBM 使用了一种名为特征并行(Feature Parallel)和数据并行(Data Parallel)的技术来实现多线程优化。
1. 特征并行(Feature Parallel):特征并行是指在寻找最佳分裂点时,将特征分配给不同的线程进行处理。每个线程都会计算其分配的特征的最佳分裂点,并返回结果。然后,所有的线程会比较各自的结果,选择最佳的分裂点进行分裂。这种方式可以大大提高在高维数据上的训练速度。
2. 数据并行(Data Parallel):数据并行是指将数据分割成多个子数据集,每个线程处理一个子数据集并找到最佳的分裂点。然后,所有的线程会比较各自的结果,选择最佳的分裂点进行分裂。这种方式可以处理大规模的数据集,并且可以有效地利用多核处理器。
在 Leaf-wise 的生长策略中,LightGBM 会在每次分裂时,都使用特征并行或数据并行来寻找最佳的分裂点,从而实现多线程优化。这种方式不仅可以提高训练速度,而且还可以处理大规模的数据集。
2、直方图算法
LightGBM 中的直方图算法是一种用于加速决策树训练的技术。它的主要思想是将连续的特征值离散化为离散的箱子(bins),然后将数据点映射到对应的箱子中,从而将特征值的数量大大减少,提高了训练速度。
具体来说,直方图算法的步骤如下:
1. 特征离散化:首先,对于每个特征,算法会将其连续的特征值离散化为若干个箱子。例如,如果我们有一个年龄特征,其值范围从 0 到 100,我们可以将其离散化为 10 个箱子,每个箱子的范围是 10 岁。
2. 数据映射:然后,算法会将每个数据点的特征值映射到对应的箱子中。例如,如果一个人的年龄是 25 岁,那么他的年龄特征就会被映射到 "20-30" 这个箱子中。
3. 直方图累加:在决策树的训练过程中,算法会在每个节点处计算每个特征的每个箱子的梯度统计信息(如梯度和、梯度平方和等),并将这些信息累加到对应的箱子的直方图中。
4. 最佳分裂点查找:当需要找到最佳的分裂点时,算法会在直方图中查找,而不是在原始的特征值中查找。这大大减少了需要考虑的特征值的数量,从而提高了训练速度。
举个例子,假设我们有以下的数据集:
| 年龄 | 购买 |
| ---- | ---- |
| 20 | 0 |
| 25 | 0 |
| 30 | 0 |
| 35 | 1 |
| 40 | 1 |
| 45 | 1 |
我们可以将年龄特征离散化为以下的箱子:20-30),[30-40),[40-50)。然后,我们将每个数据点的年龄映射到对应的箱子中,得到以下的直方图:
| 箱子 | 数据点数量 | 购买=0 | 购买=1 |
| ---- | ---------- | ------ | ------ |
| 20-30| 2 | 2 | 0 |
| 30-40| 2 | 1 | 1 |
| 40-50| 2 | 0 | 2 |
在训练决策树时,我们可以直接在这个直方图上查找最佳的分裂点,而不是在原始的年龄特征值上查找。
3、单边梯度抽样
LightGBM 的单边梯度抽样(GOSS,Gradient-based One-Side Sampling)是一种用于处理大规模数据集的抽样方法。它的主要思想是在每次迭代时,保留具有大梯度的数据点,而对具有小梯度的数据点进行抽样。
具体来说,GOSS 的步骤如下:
1. 排序:首先,将所有的数据点按照梯度的绝对值进行排序。
2. 保留:然后,保留具有最大梯度的 a% 的数据点。
3. 抽样:对于剩下的数据点,从中随机抽取 b% 的数据点。
4. 调整:对于被抽样的数据点,将其梯度和损失函数的权重进行调整,使其能够代表未被抽样的数据点。
这种方法的优点是可以保留具有大梯度的数据点,这些数据点通常对模型的训练更为重要。同时,通过抽样和调整,可以大大减少需要处理的数据量,从而提高训练速度。
举个例子,假设我们有以下的数据点和对应的梯度:
| 数据点 | 梯度 |
| ---- | ---- |
| 1 | 0.1 |
| 2 | 0.2 |
| 3 | 0.3 |
| 4 | 0.4 |
| 5 | 0.5 |
如果我们设置 a=0.6 和 b=0.2,那么在第一步,我们会保留具有最大梯度的 60% 的数据点,即数据点 4 和 5。然后,在剩下的数据点(1,2,3)中,我们随机抽取 20% 的数据点,假设我们抽取到了数据点 2。最后,我们将数据点 2 的梯度和损失函数的权重进行调整,使其能够代表未被抽样的数据点(1,3)。这样,我们就得到了一个新的数据集(4,5,2'),用于进行下一次的迭代。
4、互斥特征捆绑
LightGBM 的互斥特征捆绑(Exclusive Feature Bundling,EFB)是一种用于处理高维稀疏数据的技术。它的主要思想是将多个互斥的特征(即在同一行中不会同时出现的特征)捆绑在一起,形成一个新的特征,从而减少特征的维度,提高训练速度。
具体来说,EFB 的步骤如下:
1. 找到互斥特征:首先,算法会找到所有的互斥特征。互斥特征是指在同一行中不会同时出现的特征。
2. 特征捆绑:然后,算法会将找到的互斥特征捆绑在一起,形成一个新的特征。新的特征的值是原始特征值的和。
这种方法的优点是可以大大减少特征的维度,从而提高训练速度。同时,由于捆绑的是互斥特征,因此不会丢失太多的信息。
举个例子,假设我们有以下的数据集:
| 特征1 | 特征2 | 特征3 |
| ---- | ---- | ---- |
| 1 | 0 | 0 |
| 0 | 1 | 0 |
| 0 | 0 | 1 |
在这个数据集中,特征1、特征2和特征3是互斥的,因为它们在同一行中不会同时出现。因此,我们可以将它们捆绑在一起,形成一个新的特征,如下:
| 新特征 |
| ---- |
| 1 |
| 1 |
| 1 |
这样,我们就将特征的维度从3降低到了1,大大提高了训练速度。
好了我们使用下lightgbm这个库:
# 导入相关模块 import lightgbm as lgb from sklearn.metrics import accuracy_score from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split import matplotlib.pyplot as plt # 导入iris数据集 iris = load_iris() data, target = iris.data, iris.target # 数据集划分 X_train, X_test, y_train, y_test = train_test_split(data, target, test_size=0.2, random_state=43) # 创建lightgbm分类模型 gbm = lgb.LGBMClassifier(objective='multiclass', num_class=3, num_leaves=31, learning_rate=0.05, n_estimators=20) # 模型训练 gbm.fit(X_train, y_train, eval_set=[(X_test, y_test)]) # 预测测试集 y_pred = gbm.predict(X_test, num_iteration=gbm.best_iteration_) # 模型评估 print('Accuracy of lightgbm:', accuracy_score(y_test, y_pred)) # 绘制模型特征重要性 lgb.plot_importance(gbm) plt.show()
输出精度 1
画图: