(十)LightGBM的原理、具体实例、代码实现

(十)LightGBM

本系列重点在浅显易懂,快速上手。不进行过多的理论讲解:也就是不去深究what,而是关注how。全文围绕以下三个问题展开:

1)长什么样?

2)解决什么问题?

3)怎么实现?

​ 3.1)从数学讲,原理

​ 3.2)具体实例

​ 3.3)从代码上讲,如何掉包实现

定义

可以用如下一个简单公式来说明LightGBM和XGBoost的关系:

LightGBM = XGBoost + Histogram + GOSS + EFB。

关于XGBoost的详细论述请看本系列的第九篇文章——(九)XGBoost的原理、具体实例、代码实现

XGBoost与GBDT比较大的不同就是目标函数的定义,基本思想是一致的,同样是利用加法模型与前向分步算法实现学习的优化过程。预测过程如下:
y ^ i = ∑ k = 1 K f k ( x i ) \hat{y}_{i}=\sum_{k=1}^{K}f_{k}({x}_{i}) y^i=k=1Kfk(xi)

其中, f k f_k fk表示回归X树,K为回归树的数量。

在这里插入图片描述

XGBoost是由GBDT发展而来,同样是利用加法模型与前向分步算法实现学习的优化过程。

回归

在这里插入图片描述

二分类

在这里插入图片描述

LightGBM在XGBoost上主要有3方面的优化。

1,Histogram算法:直方图算法。

2,GOSS算法:基于梯度的单边采样算法。

3,EFB算法:互斥特征捆绑算法。

XGBoost生成一片叶子的复杂度可以粗略估计为 = 特征数量*候选分裂点数量*样本的数量。

Histogram算法的主要作用是减少候选分裂点数量,

GOSS算法的作用是减少样本的数量,

EFB算法的作用是减少特征的数量。

通过这3个算法的引入,LightGBM生成一片叶子需要的复杂度大大降低了,从而极大节约了计算时间。
同时Histogram算法还将特征由浮点数转换成0~255位的整数进行存储,从而极大节约了内存存储。

Histogram直方图算法

直方图算法是替代XGBoost的预排序(pre-sorted)算法的。预排序算法首先将样本按照特征取值排序,然后从全部特征取值中找到最优的分裂点位,该算法的候选分裂点数量与样本数量成正比。而直方图算法通过将连续特征值离散化到固定数量(如255个)的bins上,使得候选分为点位为常数个(num_bins -1)。

此外,直方图算法还能够作直方图差加速。当节点分裂成两个时,右边叶子节点的直方图等于其父节点的直方图减去左边叶子节点的直方图。从而大大减少构建直方图的计算量。

利用直方图对于每个特征的所有候选分割点按照其范围分成N个箱子,累加箱子内的梯度提升值,对于箱子里的每个候选分割点都计算 带来的梯度增益,对于每个箱子分别保存其累计梯度、箱子内的样本数量。之后再分裂节点时直接对直方图遍历进行分割点的候选即可,通过直方图的方式,虽然分割的精度变差了,但是对最后的结果影响不大,一方面能够提升计算效率,另一方面这种较粗的分割点可以起到一种正则化的效果。之后进行结点分裂时候,只需要根据梯度之和计算loss即可。对Gain增益的计算在梯度计算中。

增益计算公式:
Δ l o s s = s L 2 n L + s R 2 n R − s P 2 n P \Delta loss=\frac{s_L^2}{n_L}+\frac{s_R^2}{n_R}-\frac{s_P^2}{n_P} Δloss=nLsL2+nRsR2nPsP2
s L s_L sL左边bin的梯度之和, n L n_L nL左边的样本数量, s R s_R sR右边bin的梯度之和, n R n_R nR右边的样本数量, s P s_P sP父节点的梯度之和, n P n_P nP父节点的样本数量。

在这里插入图片描述

SL指的是左边的bin至当前bin的梯度之和,nL指样本数量,以6.5为分裂点。
Δ l o s s = ( − 0.43 ) 2 4 + ( 0.18 + 0.12 − 0.33 ) 2 16 − ( − 0.43 + 0.18 + 0.12 − 0.33 ) 2 20 = 0.04127 \begin{aligned}&\Delta\mathrm{loss}=\frac{(-0.43)^{2}}{4}+\frac{(0.18+0.12-0.33)^{2}}{16}\\&-\frac{(-0.43+0.18+0.12-0.33)^{2}}{20}=0.04127\end{aligned} Δloss=4(0.43)2+16(0.18+0.120.33)220(0.43+0.18+0.120.33)2=0.04127
直方图做差, 一个叶子的直方图可以由它的父亲节点的直方图与它兄弟的直方图做差得到。通常构造直方图, 需要遍历该叶子上的所有数据, 但直方图做差仅需遍历直方图的k个桶。利用这个方法, LightGBM可以在构造一个叶子的直方图后(父节点在上一轮就已经计算出来了), 可以用非常微小的代价得到它兄弟叶子的直方图, 在速度上可以提升一倍。直方图差加速:

在这里插入图片描述

GOSS基于梯度的单边采样算法

GOSS算法全称为Gradient-based One-Side Sampling,即基于梯度的单边采样算法。
样本的梯度越小,则样本的训练误差越小,表示样本已经训练的很好了。最直接的做法就是丢掉这部分样本,然而直接扔掉会影响数据的分布,因此lightGBM采用了one-side 采样的方式来适配:GOSS。

GOSS首先根据数据的梯度绝对值排序,选取top a个实例。然后在剩余的数据中随机采样b个实例。这样算法就会更关注训练不足的实例,而不会过多改变原数据集的分布。GOSS保留了所有的大梯度样本,对小梯度样本进行随机采样,同时为了保证分布的一致性,在计算信息增益的时候,将采样的小梯度样本乘以一个常量:(1−a)/b,a表示Top a×100%的大梯度样本比例值,b表示小梯度样本的采样比值,乘以1−a是因为大梯度样本采样的整体是整个样本集N,小梯度样本采样的候选样本集为(1−a)*N,除以b是因为采样导致小梯度样本的整体分布减少,为此需要将权重放大1/b倍)。

  • 取样步骤:

输入:训练数据,迭代步数d,大梯度数据的采样率a,小梯度数据的采样率b,损失函数和若学习器的类型(一般为决策树);
输出:训练好的强学习器;
(1)根据样本点的梯度的绝对值对它们进行降序排序;
(2)对排序后的结果选取前a*100%的样本生成一个大梯度样本点的子集;
(3)对剩下的样本集合(1-a)*100%的样本,随机的选取b/(1-a)*100%个样本点,生成一个小梯度样本点的集合;
(4)将大梯度样本和采样的小梯度样本合并;
(5)将小梯度样本乘上一个权重系数;
(6)使用上述的采样的样本,学习一个新的弱学习器;
(7)不断地重复(1)~(6)步骤直到达到规定的迭代次数或者收敛为止。
通过上面的算法可以在不改变数据分布的前提下不损失学习器精度的同时大大的减少模型学习的速率。
从上面的描述可知,当a=0时,GOSS算法退化为随机采样算法;当a=1时,GOSS算法变为采取整个样本的算法。在许多情况下,GOSS算法训练出的模型精确度要高于随机采样算法。另一方面,采样也将会增加若学习器的多样性,从而潜在的提升了训练出的模型泛化能力。

  • 实例:

这是不采用GOSS采样的情况:

在这里插入图片描述

这是采用GOSS采样的情况:

在这里插入图片描述

梯度绝对值的阈值是0.1,大于0.1的数据有2,小于0.1的有6个。大梯度的数据全部保留,大梯度的采样率a=2/8。小梯度的采样个数为2个,采样率为b=2/8。则小梯度的数据都乘 (1-a)/b=(1-2/8)/(2/8)=3。

EFB互斥特征捆绑算法

EFB算法全称是Exclusive Feature Bundling,即互斥特征绑定算法。(将多个特征捆绑到一起)

EFB是通过特征捆绑的方式减少特征维度(其实是降维技术)的方式,来提升计算效率。通常被捆绑的特征都是互斥的(一个特征值为零,一个特征值不为零),这样两个特征捆绑起来才不会丢失信息。如果两个特征并不是完全互斥(部分情况下两个特征都是非零值),可以用一个指标对特征不互斥程度进行衡量,称之为冲突比率,当这个值较小时,我们可以选择把不完全互斥的两个特征捆绑,而不影响最后的精度。

  • EFB的算法步骤如下:
    • 将特征按照非零值的个数进行排序
    • 计算不同特征之间的冲突比率
    • 遍历每个特征并尝试合并特征,使冲突比率最小化

在这里插入图片描述

将特征1和特征2进行捆绑,形成新的特征。只把值都为0的特征合并。

实例

请看本系列的第九篇文章——
(九)XGBoost的原理、具体实例、代码实现

参考xgboost原理分析以及实践_SCUT_Sam-CSDN博客_xgboost流程图

以二分类情况为例。15条数据,两个特征。这里为了简单起见,树的深度设置为3(max_depth=3),树的颗数设置为(num_boost_round=2),学习率为0.1(eta=0.1)。另外再设置两个正则的参数, λ = 1 , γ = 0 λ=1,γ=0 λ=1,γ=0
损失函数选择logloss

IDx1x2y
11-50
2250
33-21
4121
5201
66-51
7751
86-20
9720
10601
118-51
12951
1310-20
14820
15901

logloss交叉熵损失函数的一阶导数以及二阶导数:
g i = y ^ i − y i h i = y ^ i ∗ ( 1 − y ^ i ) g_i=\hat y_i-y_i\\ h_i=\hat y_i*(1-\hat y_i) gi=y^iyihi=y^i(1y^i)

  1. 建立第一课树(K=1)

从根节点开始分裂,共有15个数据。取增益Gain最大的点划分。
G a i n = 1 2 [ G L 2 H L 2 + λ + G R 2 H R 2 + λ − ( G L + G R ) 2 ( H L + H R ) 2 + λ ] − γ Gain=\frac{1}{2}\left[\frac{G_L^2}{H_L^2+\lambda}+\frac{G_R^2}{H_R^2+\lambda}-\frac{(GL+G_R)^2}{(H_L+H_R)^2+\lambda}\right]-\gamma Gain=21[HL2+λGL2+HR2+λGR2(HL+HR)2+λ(GL+GR)2]γ
要计算 g i g_i gi h i h_i hi那么初始的 y ^ i \hat y_i y^i应该定位多少?这里和GBDT一样,应该说和所有的Boosting算法一样,都需要一个初始值。而在xgboost里面,对于分类任务只需要初始化为(0,1)中的任意一个数都可以。具体来说就是参数base_score。(其默认值是0.5)

  • 关于初始值

XGBoost - wwwpy - 博客园 (cnblogs.com)这样回答:

样本不均衡时可以使用 分类问题中它就是分类的先验概率 如有1000个样本,300个正样本和700个负样本,则base_score就是0.3 对于回归来说这个分数默认为0.5,但是理论上而言这个分数应该设置为标签的均值更好

xgboost : base_score 参数的含义 - IT屋-程序员软件开发技术分享社区 (it1352.com)这样回答:

对于高度不平衡的数据,您可以将其初始化为更有意义的基础分数,以改进学习过程.理论上,只要选择合适的学习率并给它足够的训练步骤,起始基础分数应该不会影响结果.看看这个issue中作者的回答.参考:https://github.com/dmlc/xgboost/issues/799

这里也设base_score=0.5。然后我们就可以计算每个样本的一阶导数值和二阶导数值了。具体如下表:

ID123456789101112131415
gi0.50.5-0.5-0.5-0.5-0.5-0.50.50.5-0.5-0.5-0.50.50.5-0.5
hi0.250.250.250.250.250.250.250.250.250.250.250.250.250.250.25
  1. 计算每个划分点的Gain,这里以x1<2为例划分,其他雷同:

左子结点包含的样本 I L = [ 1 , 4 ] I_L=[1,4] IL=[1,4],右子节点包含的样本 I R = [ 2 , 3 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 ] I_R=[2,3,5,6,7,8,9,10,11,12,13,14,15] IR=[2,3,5,6,7,8,9,10,11,12,13,14,15]

左子结点的一阶导数和: G L = ∑ ( i ∈ I L ) g i = ( 0.5 − 0.5 ) = 0 G_L=\sum_{(i \in I_L)}g_i=(0.5-0.5)=0 GL=(iIL)gi=(0.50.5)=0

左子结点的二阶导数和: H L = ∑ ( i ∈ I L ) h i = ( 0.25 + 0.25 ) = 0.5 H_L=\sum_{(i \in I_L)}h_i=(0.25+0.25)=0.5 HL=(iIL)hi=(0.25+0.25)=0.5

右子结点的一阶导数和: G R = ∑ ( i ∈ I R ) g i = − 1.5 G_R=\sum_{(i \in I_R)}g_i=-1.5 GR=(iIR)gi=1.5

右子结点的二阶导数和: H R = ∑ ( i ∈ I R ) h i = 3.25 H_R=\sum_{(i \in I_R)}h_i=3.25 HR=(iIR)hi=3.25

增 益:

G a i n = [ G L 2 H L 2 + λ + G L 2 H L 2 + λ − ( G L + G R ) 2 ( H L + H R ) 2 + λ ] = 0.0557275541796 Gain=\left[\frac{G_L^2}{H_L^2+\lambda}+\frac{G_L^2}{H_L^2+\lambda}-\frac{(GL+G_R)^2}{(H_L+H_R)^2+\lambda}\right]=0.0557275541796 Gain=[HL2+λGL2+HL2+λGL2(HL+HR)2+λ(GL+GR)2]=0.0557275541796

其他的X1的特征值类似,计算归总到下面表 ( ( x 1 < 1 ) 是 I L 为空,数据全在 I R ,跟没分一样,所以 G a i n 为 0 ) ((x1<1)是I_L为空,数据全在I_R,跟没分一样,所以Gain为0) ((x1<1)IL为空,数据全在IR,跟没分一样,所以Gain0)

切分点23678910
GL00-0.5-1-1-1-2
HL0.511.2522.533.5
GR-1.5-1.5-1-0.5-0.5-0.50.5
HR3.252.752.51.751.250.750.25
Gain0.055720.12631-0.07686-0.04944-0.07685-0.080830.61520

同样的方法遍历X2的全部特征值:

切分点-2025
GL-0.50-1.5-1
HL0.751.52.253
GR-1-1.50-0.5
HR32.251.50.75
Gain-0.080830.218620.21862-0.08083

可见最大增益为x<10处的0.61520,以x1<10来进行分裂。

左子节点的样本集合 I L = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 14 , 15 ] I_L=[1,2,3,4,5,6,7,8,9,10,11,12,14,15] IL=[1,2,3,4,5,6,7,8,9,10,11,12,14,15]右子节点的样本集合 I R = [ 13 ] I_R=[13] IR=[13]

  1. 设置的最大深度是3,此时只有1层,所以还需要继续往下分裂。

右子节点此时只剩一个样本,不需要分裂了,也就是已经是叶子结点。可以计算其对应的叶子结点值了,按照公式:
w 1 = − G R H R + λ = − g 13 h 13 + 1 = − 0.5 1 + 0.25 = − 0.4 w_1=-\frac{G_R}{H_R+\lambda}=-\frac{g_{13}}{h_{13}+1}=-\frac{0.5}{1+0.25}=-0.4 w1=HR+λGR=h13+1g13=1+0.250.5=0.4
下面就是对左子结点IL进行分裂。分裂的时候把此时的结点看成根节点,其实就是循环上面的过程,同样也是需要遍历所有特征(x1,x2)的所有取值作为分裂点,选取增益最大的点。

所有X1,X2的值都遍历完后可以得到下表:

切分点236789
GL00-0.5-1-1-1
HL0.511.2522.53
GR-2-2-1.5-1-1-1
HR32.52.251.510.5
Gain0.111110.25397-0.08547-0.15555-0.103170.02777
切分点-2025
GL-0.5-0.5-2.5-1.5
HL0.751.252.252.75
GR-1.5-1.50.5-0.5
HR2.752.251.250.75
Gain-0.14603-0.085470.44444-0.14603

x 2 < 2 x2<2 x2<2时分裂可以获得最大的增益0.44444,以x2<2为分裂点。分裂后左右叶子结点的集合如下:

I L = [ 1 , 3 , 5 , 6 , 8 , 10 , 11 , 15 ] , I R = [ 2 , 4 , 7 , 9 , 12 , 14 ] I_L=[1,3,5,6,8,10,11,15],I_R=[2,4,7,9,12,14] IL=[1,3,5,6,8,10,11,15],IR=[2,4,7,9,12,14]

  1. 然后同样的方法继续分裂,这里直接给出最终结果:
    在这里插入图片描述

w1计算结果为-0.4,图上却是-0.04. 不要忘记学习率:这里其实和在GBDT中的处理一样,我们会以一个学习率来乘这个值,当完全取-0.4时说明学习率取1,这个时候很容易过拟合。所以每次得到叶子结点的值后需要乘上学习率eta,在前面我们已经设置了学习率是0.1。这里也是GBDT和xgboost一个共同点,大家都是通过学习率来进行Shrinkage,以减少过拟合的风险。

  1. 建立第2颗树(k=2)

其实过程和第一颗树完全一样。只不过对于 y ^ i \hat y_i y^i需要进行更新,也就是拟合第二颗树是在第一颗树预测的结果基础上。这和GBDT一样,因为大家都是Boosting思想的算法。

在第一颗树里面由于前面没有树,所以初始 y ^ i = 0.5 \hat y_i=0.5 y^i=0.5(自己设置的)。假设此时,模型只有这一颗树(K=1),那么模型对样例xi进行预测时,预测的结果表达是什么呢?

由加法模型可知:
y i K = ∑ k = 0 K f k ( x i )         y i 1 = f 0 ( x i ) + f 1 ( x i ) y_i^K=\sum_{k=0}^Kf_k(x_i) \ \ \ \ \ \ \ y_i^1=f_0(x_i)+f_1(x_i) yiK=k=0Kfk(xi)       yi1=f0(xi)+f1(xi)
f 1 ( x i ) f_1(x_i) f1(xi)是xi落在第一棵树上对应的叶子节点的值, f 0 ( x i ) f_0(x_i) f0(xi)呢?它并不是我们的初始值base_score。base_score是 y ^ i \hat y_i y^i的值,是 f 0 ( x i ) f_0(x_i) f0(xi)取sigmoid函数之后的结果。所以
f 0 ( x i ) = l n y 1 − y = 0 f_0(x_i)=ln\frac{y}{1-y}=0 f0(xi)=ln1yy=0
所以第一颗树预测的结果就是
y i 1 = f 0 ( x i ) + f 1 ( x i ) = 0 + w q ( x i ) y_i^1=f_0(x_i)+f_1(x_i)=0+w_{q(x_i)} yi1=f0(xi)+f1(xi)=0+wq(xi)
对应的预测结果就是
y ^ i = 1 1 − e − y i 1 \hat y_i=\frac{1}{1-e^{-y_i^1}} y^i=1eyi11
比如对于ID=1的样本,其落在-0.04这个节点。那么经过sigmod映射后的值为 y ^ 1 = 1 1 − e − ( 0 − 0.04 ) ) = 0.49001 \hat y_1=\frac{1}{1-e^{-(0-0.04))}}=0.49001 y^1=1e(00.04))1=0.49001

(其实当训练次数K足够多的时候,初始化这个值几乎不起作用的,这个在官网文档上有说明)

所以,我们可以得到第一棵树预测的结果为下表(预测后将其映射成概率)

ID y ^ i \hat y_i y^i
10.490001
20.494445
30.522712
40.494445
50.522712
60.522712
70.494445
80.522712
90.494445
100.522712
110.522712
120.509999
130.490001
140.494445
150.522712

有了这个之后,就可以计算所有样本新的一阶导数和二阶导数的值了。具体如下表:

ID g i g_i gi h i h_i hi
10.4900013208390.249900026415
20.4944446682930.24996913829
3-0.4772883653640.249484181652
4-0.5055553317070.24996913829
5-0.4772883653640.249484181652
6-0.4772883653640.249484181652
7-0.5055553317070.24996913829
80.5227116346360.249484181652
90.4944446682930.24996913829
10-0.4772883653640.249484181652
11-0.4772883653640.249484181652
12-0.4900013208390.24990002641513
140.4944446682930.24996913829
15-0.4772883653640.249484181652

之后,我们和第一颗树建立的时候一样以公式(10)去建树。拟合完后第二颗树如下图:

在这里插入图片描述

  1. 最终的预测结果为

第一课树:

叶子12345
样本13 5 6 8 10 11 152 4 7 9 141213
f 1 ( x i ) f_1(x_i) f1(xi)-0.040.09090910.0222220.04-0.04

第二棵树:

叶子12345
样本13 5 6 8 10 11 1542 7 9 12 1413
f 2 ( x i ) f_2(x_i) f2(xi)-0.03920320.08523990.0404454-0.0216811-0.0392032

预测结果:

ID y i 2 = f 0 ( x i ) + f 1 ( x i ) + f 2 ( x i ) y_i^2=f_0(x_i)+f_1(x_i)+f_2(x_i) yi2=f0(xi)+f1(xi)+f2(xi) y ^ i = 1 1 + e − y i 2 \hat y_i=\frac{1}{1+e^{-y_i^2}} y^i=1+eyi21
1-0.0790.48
2-0.2440.439
30.1760.544
40.0630.516
50.1760.544
60.1760.544
70.0010.5
80.1760.544
90.0010.5
100.1760.544
110.1760.544
120.0180.504
13-0.0790.48
140.0010.5
150.1760.544

代码实现

lightgbm并没有包含在sklearn包中,需要单独的安装另外一个包:

pip install ligthgbm
"""对训练集数据进行划分,分成训练集和验证集,并进行相应的操作"""
from sklearn.model_selection import train_test_split
import lightgbm as lgb
# 数据集划分
X_train_split, X_val, y_train_split, y_val = train_test_split(X_train, y_train, test_size=0.2)
train_matrix = lgb.Dataset(X_train_split, label=y_train_split)
valid_matrix = lgb.Dataset(X_val, label=y_val)

params = {
            'boosting_type': 'gbdt',
            'objective': 'binary',
            'learning_rate': 0.1,
            'metric': 'auc',
            'min_child_weight': 1e-3,
            'num_leaves': 31,
            'max_depth': -1,
            'reg_lambda': 0,
            'reg_alpha': 0,
            'feature_fraction': 1,
            'bagging_fraction': 1,
            'bagging_freq': 0,
            'seed': 2020,
            'nthread': 8,
            'silent': True,
            'verbose': -1,
}

"""使用训练集数据进行模型训练"""
model = lgb.train(params, train_set=train_matrix, valid_sets=valid_matrix, num_boost_round=20000, verbose_eval=1000, early_stopping_rounds=200)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值