LightGBM模型 0基础小白也能懂(附代码)

LightGBM模型 0基础小白也能懂(附代码)

原文链接

啥是LightGBM模型

之前对强大的 boosting 模型工具 XGBoost 做了介绍,本篇我们来学习一下 GBDT 模型的另一个进化版本:LightGBM 。LightGBM 是微软开发的 boosting 集成模型,和 XGBoost 一样是对 GBDT 的优化和高效实现,原理有一些相似之处,但它很多方面比 XGBoost 有着更为优秀的表现。官方给出的这个工具库模型的优势如下:

更快的训练效率
低内存使用
更高的准确率
支持并行化学习
可处理大规模数据
支持直接使用 category 特征

互联网领域的算法应用,通常背后都有海量的大数据。深度学习中一系列神经网络算法,都是以 mini-batch 的方式喂数据迭代训练的,总训练数据量不受内存限制。

但我们用到的机器学习算法,比如 GBDT 在每一次迭代的时候,都需要遍历整个训练数据多次。

如果把整个训练数据一次性装进内存,会明显限制训练数据的大小。
如果不装进内存,反复地读写训练数据又会消耗非常大的时间。
面对工业级海量的数据,普通的 GBDT 算法无法满足需求。 LightGBM 提出的主要原因之一,就是为了解决上述大数据量级下的 GBDT 训练问题,以便工业实践中能支撑大数据量并保证效率。

LightGBM 优化点

首先看看XGBoost的优缺点,LightGBM 针对其中的一部分进行了调整优化。
1.精确贪心算法

2.Level-wise生长方式

3.对cache优化不友好
在预排序后,特征对梯度的访问是一种随机访问,并且不同的特征访问的顺序不一样,无法对 cache 进行优化。同时,在每一层长树的时候,需要随机访问一个行索引到叶子索引的数组,并且不同特征访问的顺序也不一样,也会造成较大的 cache miss。

LightGBM优化主要有以下几点:
基于 Histogram 的决策树算法(直方图算法)
带深度限制的 Leaf-wise 的叶子生长策略
直方图做差加速
直接支持类别特征(Categorical Feature)
Cache命中率优化
基于直方图的稀疏特征优化
多线程优化

加下来一一介绍

决策树的算法

1)XGBoost :Pre-sorted算法

XGBoost 使用的是 Pre-sorted 算法,能够更精确的找到数据分隔点。

首先,对所有特征按数值进行预排序。
其次,在每次的样本分割时,用\(O(\#data)\)的代价找到每个特征的最优分割点,这里的\(\#data\)可以理解为数据的总量,即特征和样本的乘积。
最后,找到最后的特征以及分割点,将数据分裂成左右两个子节点。

这种 pre-sorting 算法能够准确找到分裂点,但是在空间和时间上有很大的开销。

由于需要对特征进行预排序并且需要保存排序后的索引值(为了后续快速的计算分裂点),因此内存需要训练数据的两倍。
在遍历每一个分割点的时候,都需要进行分裂增益的计算,消耗的代价大。

2)LightGBM :直方图算法

LightGBM 使用的是直方图算法(histogram algorithm),占用的内存更低,数据分割的复杂度更低。直方图算法思想是:

将连续特征值进行离散化:将连续型特征值按照某个分位点划分成有限数量的“桶”(buckets)。这些“桶”形成了直方图,每个“桶”中包含一定范围内的特征值。

使用直方图代替所有原始数据进行计算:对于每个特征,直方图表示的是特征值在各个范围(或区间)内的分布情况,而不是原始的连续值。这种离散化极大地减少了需要评估的分裂点数量。

寻找分裂点:在直方图中寻找最优分裂点,而不需要逐一遍历所有特征的每一个值。通过在“桶”之间计算分裂增益,直接找到分裂点。

优点如下:

应用直方图算法,计算代价也大幅降低,预排序算法每遍历一个特征值就需要计算一次分裂的增益,而直方图算法只需要计算\(k\)次(常数),时间复杂度从\(O(\#data*\#features)\)直接优化到\(O(k*\#features)\)

使用分桶 bin 替代原始数据相当于增加了正则化。
使用分桶 bin 意味着很多数据的细节特征丢失,相似的数据如果划分到相同的桶中,数据之间的差异就无法捕获了。
分桶 bin 数量决定了正则化的程度,bin 越少惩罚越严重,欠拟合风险越高。
因为预先设定了 bin 的范围,构建直方图时不需要对数据进行排序。
直方图保存「划分阈值」、「当前 bin 内样本数」、「当前 bin 内所有样本的一阶梯度和」。

优缺点:
Histogram 算法并不是完美的。由于特征被离散化后,找到的并不是很精确的分割点,所以会对结果产生影响。但在实际的数据集上表明,离散化的分裂点对最终的精度影响并不大,甚至会好一些。原因在于 decision tree 本身就是一个弱学习器,采用 Histogram 算法会起到正则化的效果,有效地防止模型的过拟合。

决策树生长策略

1)树生长策略调整

直方图算法之上,LightGBM 进行进一步的优化。它没有使用大多数 GBDT工具使用的按层生长(Level-wise)的决策树生长策略,而使用了带有深度限制的按叶子生长(Leaf-wise)算法。

写的很复杂,但是其实就是广度优先和深度优先

2)XGBoost :Level-wise

XGBoost 采用的是Level-wise(按层生长)策略生长的,能够同时分裂同一层的叶子,从而进行多线程优化,不容易过拟合。

但不加区分的对待同一层的叶子,带来了很多没必要的开销。因为实际上很多叶子的分裂增益较低,没必要进行搜索和分裂。

3)LightGBM :Leaf-wise

LightGBM 采用 Leaf-wise(按叶子生长)生长策略,每次从当前所有叶子中找到分裂增益最大(一般也是数据量最大)的一个叶子,然后分裂,如此循环。同 Level-wise 相比,在分裂次数相同的情况下,Leaf-wise 可以降低更多的误差,得到更好的精度。Leaf-wise 的缺点是可能会长出比较深的决策树,产生过拟合。因此 LightGBM 在 Leaf-wise 之上增加了一个最大深度的限制,在保证高效率的同时防止过拟合。

直方图差加速

LightGBM 另一个优化是 Histogram (直方图)做差加速。整个构建过程中可以观察到:一个叶子的直方图可以由它的父亲节点的直方图与它兄弟的直方图做差得到。

一般来说构造直方图,需要遍历该叶子上的所有数据,但直方图做差仅需遍历直方图的\(k\)个桶。利用上述特征,LightGBM 可以在构造一个叶子的直方图后,可以用非常微小的代价得到它兄弟叶子的直方图,在速度上可以提升一倍。

就相当于集合作差而已。

类别型特征支持

大多数机器学习工具都无法直接支持类别型特征,我们会先将其编码再做后续建模,比如使用 one-hot 这种编码方式,但会会降低空间和时间效率。

LightGBM 优化了对类别型特征的支持,可以直接输入类别特征,不需要额外的编码或 one-hot 公式 展开。并在决策树算法上增加了类别型特征的决策规则。

1)树模型与 one-hot 编码

one-hot 编码是处理类别特征的一个通用方法,然而在树模型中,这可能并不一定是一个好的方法,尤其当类别特征中类别个数很多的情况下,主要的问题是:

问题1:可能无法在这个类别特征上进行切分。

使用 one-hot 编码的话,意味着在每一个决策节点上只能使用one vs rest(例如是不是男性,是不是一线城市等)的切分方式。当类别值很多时,每个类别上的数据可能会比较少,这时候切分会产生不平衡,这意味着切分增益也会很小。

问题2:影响决策树的学习。

就算可以在这个类别特征进行切分,也会把数据切分到很多零碎的小空间上,如下左图所示。而决策树学习时利用的是统计信息,在这些数据量小的空间上,统计信息不准确,学习会变差。但如果使用下右图的分裂方式,数据会被切分到两个比较大的空间,进一步的学习也会更好。

2)LightGBM 类别型特征处理方式

LightGBM 采用了 Many vs Many 的切分方式解决 one-hot 编码带来的问题,实现了类别特征的最优切分。用 LightGBM 可以直接输入类别特征,并产生上右图的效果。

① 在枚举分割点之前,先把直方图按每个类别的均值进行排序。
② 接着按照均值的结果依次枚举最优分割点。

Sum(y) 代表该类别或区间内所有目标变量 y 的总和,Count(y) 代表该类别或区间内样本的数量,二者相除得到目标变量的均值。

求解类别型特征的最优切分的具体流程如下:

① 离散特征建立直方图的过程
统计该特征下每一种离散值出现的次数,并从高到低排序,并过滤掉出现次数较少的特征值。然后为每一个特征值,建立一个 bin 容器,对于在 bin 容器内出现次数较少的特征值直接过滤掉,不建立 bin 容器。

② 计算分裂阈值的过程

  • 先看该特征下划分出的 bin 容器的个数,如果 bin 容器的数量小于 4,直接使用 one vs other 方式即逐个扫描每一个bin容器,将某个bin作为分裂点,计算该分裂后的增益值。这样可以找到最佳分裂点。

  • 对于 bin 容器较多的情况,先进行过滤,只让子集合较大的 bin 容器参加划分阈值计算,对每一个符合条件的 bin 容器进行公式计算,得到一个值,根据该值对 bin 容器从小到大进行排序,然后分从左到右、从右到左进行搜索,得到最优分裂阈值。公式如下:

③ 对于连续特征,划分阈值只有一个。对于离散值可能会有多个划分阈值,每一个划分阈值对应着一个 bin 容器编号。

当使用离散特征进行分裂时,只要数据样本对应的 bin 容器编号在这些阈值对应的 bin 集合之中,这条数据就加入分裂后的左子树,否则加入分裂后的右子树。

并行支持与优化

LightGBM 原生支持并行学习,目前支持「特征并行」和「数据并行」的两种,LightGBM 针对这两种并行方法都做了优化。

  • 特征并行:在不同机器在不同的特征集合上分别寻找最优的分割点,然后在机器间同步最优的分割点。
    LightGBM 在特征并行算法中,通过在本地保存全部数据避免对数据切分结果的通信。

  • 数据并行:让不同的机器先在本地构造直方图,然后进行全局的合并,最后在合并的直方图上面寻找最优分割点。
    Lightgbm在数据并行中使用分散规约(Reduce scatter)把直方图合并的任务分摊到不同的机器,降低通信和计算,并利用直方图做差,进一步减少了一半的通信量

代码实现

import lightgbm as lgb
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.datasets import load_breast_cancer
import numpy as np

# 1. 加载数据
data = load_breast_cancer()
X = data.data
y = data.target

# 2. 将数据集分成训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 3. 构建 LightGBM 数据集
train_data = lgb.Dataset(X_train, label=y_train)
test_data = lgb.Dataset(X_test, label=y_test)

# 4. 设置模型的参数
params = {
    'boosting_type': 'gbdt',  # 使用 GBDT 算法
    'objective': 'binary',  # 二分类任务
    'metric': 'binary_logloss',  # 使用二进制逻辑损失作为评估指标
    'num_leaves': 64,  # 叶子节点数量,增大以允许更多分裂
    'max_depth': 10,  # 增加树的深度,允许更多分裂
    'min_data_in_leaf': 10,  # 每个叶子节点最少包含的样本数,较小的数值允许更多的分裂
    'learning_rate': 0.05,  # 学习率
    'feature_fraction': 0.9,  # 特征采样比例
    'bagging_fraction': 0.8,  # 数据采样比例
    'bagging_freq': 5,  # 每5轮进行一次bagging
    'verbose': 0  # 关闭训练日志
}

# 5. 定义早停回调函数
callbacks = [lgb.early_stopping(stopping_rounds=10)]

# 6. 训练模型
gbm = lgb.train(
    params,
    train_data,
    num_boost_round=100,  # 提升次数
    valid_sets=[train_data, test_data],  # 验证集
    valid_names=['train', 'test'],  # 设置验证集名称
    callbacks=callbacks  # 使用回调函数代替 early_stopping_rounds
)

# 7. 进行预测
y_pred_prob = gbm.predict(X_test, num_iteration=gbm.best_iteration)  # 使用最佳迭代轮次进行预测
y_pred = np.round(y_pred_prob)  # 将概率值转换为0或1

# 8. 评估模型
accuracy = accuracy_score(y_test, y_pred)
print(f'Accuracy: {accuracy * 100:.2f}%')

结果如下

D:\Anaconda\envs\ML\python.exe D:\PycharmProject\ML\lightgbm\tree.py 
[LightGBM] [Warning] No further splits with positive gain, best gain: -inf
Training until validation scores don't improve for 10 rounds
[LightGBM] [Warning] No further splits with positive gain, best gain: -inf
...
[LightGBM] [Warning] No further splits with positive gain, best gain: -inf
[LightGBM] [Warning] No further splits with positive gain, best gain: -inf
[LightGBM] [Warning] No further splits with positive gain, best gain: -inf
Early stopping, best iteration is:
[66]	train's binary_logloss: 0.0395605	test's binary_logloss: 0.10542
Accuracy: 96.49%

Process finished with exit code 0

可以看到出现了很多[LightGBM] [Warning] No further splits with positive gain, best gain: -inf,,这说明尽管模型在某些节点上无法找到更多的分裂点以提高增益,但训练过程仍然顺利,并且达到了早停条件。模型的损失和准确率表明其性能已经相对较好,不算报错

posted @ 2024-09-08 16:07  Mephostopheles  阅读(0)  评论(0编辑  收藏  举报