图解LightGBM直方图算法、单边梯度采样算法(GOSS)

文章详细介绍了LightGBM中的直方图算法和GOSS单边梯度采样过程,通过案例展示如何通过图表理解这两个概念,强调了在直方图中存储样本点ID的重要性。同时,解释了GOSS在构建决策树前的样本筛选过程及其膨胀系数的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我近期研究了各种树模型,在理解LightGBM的直方图、GOSS单边梯度采样环节时都有些困难。我在网上找了很多资料,但大部分以文字为主,且非常抽象,理解起来十分困难。

因此我想通过画图、画表、举例子的方式,具体解释一下LightGBM的直方图算法和GOSS算法。

本文旨在以图表化的方式展现直方图算法和GOSS算法的具体过程,因此不涉及任何数学推导和代码内容。如果有小伙伴对梯度提升树、xgboost、LightGBM的概念还比较陌生,建议先补充相关知识。

假设现在有这么一个案例:在第n次生成弱学习器时,样本如下,

192a5ffb27004e3a942e2124a7e9ccea.png

本文将围绕这个案例展开

直方图算法

在什么时间点生成直方图?

首先明确,这个过程发生在遍历每个特征时。也就是说,在构建每一轮的决策树时,每个特征列都会生成1张直方图。

对本文的案例而言,此时模型开始第n次生成弱学习器。

第一步需要做什么?是找到第一个分割点。

如何找到第一个分裂点?循环x1和x2,看看它们内部哪一个分割点的增益最大。

万一x1和x2数据量很大怎么办?如果有100万个样本点,那么x1和x2将会产生(100万+100万-2)个分割点,这显然是极其消耗算力的。因此采用直方图算法来加速这个过程。但是由于直方图带来的精度丢失,可能无法求解出数学意义上的最优解,所以本质上是牺牲精度换取效率。

 

直方图都存储了什么信息?

有一些回答说:样本点数量 + 样本点的梯度和

但我经过反复实验,认为还必须储存样本点的id,或者索引、指针之类能够回溯到该样本点本身的信息,才能够实现直方图的加减。

对本文的案例而言,可以把原数据转化成这样存储:

ca8cb431b56e4490964ebc9415c8175d.png

注意,这个过程并不是排序,而是:

1. 创建若干个“框”,例如我为x2创建了三个“框”,分别是[3,4] , [5,7], [8-10]。

2. 循环x2的8个样本点,判断每个样本点属于哪一个“框”。

3. 对每个特征都进行如此的操作。

整个过程并不涉及排序,因此比xgboost预排序的时间复杂度小很多。

 

如何实现直方图作差加速?

1d93713f1f6e4d60ad302a8406bc4471.png

 图片来源:(十)LightGBM的原理、具体实例、代码实现 - 知乎

这里的关键问题就在于x2的左子节点直方图如何得到。这也是我为什么认为直方图还需要额外存储样本点的id的原因。

还是之前的例子:

b96edf3993c344c7aef0f9bdc11ce42b.png

假设我以这条红线做分割,于是可以得到x1的左右两个节点:

8ea25c21a07541a3b6fb3f9ddade2008.png

但是要得到x2的左节点就没有那么直接了:我首先需要回溯x1特征属于左节点的样本点ABC,再到原始表格中回溯它们的x2特征,再查看这个特征属于x2直方图中的哪一个区间。

以上过程成立的基本条件是:我能够通过x1直方图回溯到样本点ABC,因此我认为直方图需要存储特征所对应的样本点信息。

当我找到ABC的x2特征后,我就可以填充x2直方图:

e39cb6abac4e41ceaa9d17b0bebad33f.png

然后就可以应用直方图作差,直接得出右子节点的x2直方图:

953d5173782d499eb9714a0fca12488f.png

但是,我并没有在其它的论坛、博客、论文中找到与我上述结论类似的结论,我只是模拟真实过程进行了一步步的推导得出的合理推测。因此,或许会存在不严谨的地方,希望各位小伙伴指正。当然,如果有时间我会啃一下github的源码。

 

单边梯度采样算法(GOSS)

在什么时间点进行单边梯度采样?

首先明确,这个过程发生在构建决策树前。也就是说,我这个模型生成了n颗决策树,就意味着我进行了n次GOSS过程。即每生成1个弱学习器,就进行一次GOSS过程。

还是上述的例子,此时假设我进入第n轮决策树的构建。

根据梯度从大到小,可以理解为样本点的重要性也在依次递减。我选择前50%作为大梯度样本,然后在剩余的样本中,抽取整体数量的25%作为小梯度样本:

00e8313e86424f9caf4d49d8fdb34462.png

如此,我选中了ABCD+FG作为我本轮的训练集。

膨胀系数?

为了平衡采样之后的不均匀分布的样本,我们需要为小梯度样本乘上一个膨胀系数

膨胀系数 = (1-top_rate)/other_rate。这里即为(1-50%)/25% = 2

f38faf15aed54cf9a58c44e3c17fad21.png

至此,我本轮的训练集构建完成。在本轮的增益计算中,我需要使用膨胀后的梯度gg。

此轮过后,样本点E和H将不会再出现在训练集中,因为它们既然不参与本轮训练,那么也就不产生y_hat,因此也就无法再计算梯度,所以也就无法再去拟合负梯度。

这个goss过程,随着每轮决策树的构建,会使得用于训练的样本会越来越少,这表示模型越来越注重梯度更大的样本(梯度越大,对目标函数的影响就越大),这个操作其实跟adaboost中为样本点加权的操作的道理是相通的。

 

 

 

### LightGBMGOSS (单边梯度采样) 的实现与原理 #### 原理介绍 GOSS(Gradient-based One-Side Sampling)是一种用于减少训练样本数量的技术,旨在提高模型训练效率而不显著降低性能。通过这种方法,可以有效去除那些对损失函数贡献较小的数据实例,从而加快收敛速度。 具体来说,在每次迭代过程中,GOSS会保留具有较大绝对值梯度的所有数据点,并随机抽取一部分具有较小绝对值梯度的数据点来构建新的训练集[^1]。这种做法能够确保重要的更新不会被忽略的同时减少了不必要的计算开销。 #### 数学表达 假设当前节点有 \( n \) 个样本,则对于这些样本按照其对应的负梯度绝对值大小排序得到序列 \( g_1, g_2,...,g_n \),其中 \( |g_i| >= |g_{i+1}| \)。接着定义两个参数: - `a` 表示保留大梯度样本的比例; - `b` 是小梯度样本抽样的比例; 那么最终选取的样本数为: \[ N = a*n + b*(n-a*n)\] 这里需要注意的是,虽然只选择了部分样本参与下一轮的学习过程,但是权重会被相应调整以保持统计特性的一致性。 #### Python代码展示 下面是一个简单的Python伪代码片段展示了如何在LightGBM框架内应用GOSS机制: ```python import numpy as np from lightgbm import LGBMClassifier def apply_goss(gradients, top_rate=0.2, other_rate=0.1): """Apply Gradient-Based One Side Sampling.""" # Sort indices by absolute value of gradients in descending order. sorted_indices = np.argsort(-np.abs(gradients)) # Select the largest gradient samples and sample from others. large_grad_idx = sorted_indices[:int(top_rate * len(sorted_indices))] small_grad_idx = np.random.choice( sorted_indices[int(top_rate * len(sorted_indices)):], size=int(other_rate * len(sorted_indices)), replace=False) selected_samples = np.concatenate([large_grad_idx, small_grad_idx]) return selected_samples # Example usage with LightGBM classifier model = LGBMClassifier(boosting_type='goss') model.fit(X_train, y_train) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值