LightGBM是由微软推出的可扩展机器学习系统,是微软旗下DMKT的一个开源项目,由首届阿里巴巴大数据竞赛获胜者之一柯国霖老师带领开发。它是一款基于GBDT(梯度提升决策树)算法的分布式梯度提升框架,为了满足缩短模型计算时间的需求,LightGBM的设计思路主要集中在减小数据对内存与计算性能的使用,以及减少多机器并行计算时的通讯代价。原论文:LightGBM | Proceedings of the 31st International Conference on Neural Information Processing Systems (),以及优化改进版本[1611.01276] A Communication-Efficient Parallel Algorithm for Decision Tree ()
LightGBM可以看作是XGBoost的升级豪华版,在获得与XGBoost近似精度的同时,又提供了更快的训练速度与更少的内存消耗。正如其名字中的Light所蕴含的那样,LightGBM在大规模数据集上跑起来更加优雅轻盈,一经推出便成为各种数据竞赛中刷榜夺冠的神兵利器。
LightGBM在Higgs数据集上LightGBM比XGBoost快将近10倍,内存占用率大约为XGBoost的1/6,并且准确率也有提升。GBDT在每一次迭代的时候,都需要遍历整个训练数据多次。如果把整个训练数据装进内存则会限制训练数据的大小;如果不装进内存,反复地读写训练数据又会消耗非常大的时间。尤其面对工业级海量的数据,普通的GBDT算法是不能满足其需求的。LightGBM提出的主要原因就是为了解决GBDT在海量数据遇到的问题,让GBDT可以更好更快地用于工业实践。
LightGBM模型可以表示为以下形式,我们约定 f t ( x ) f_t(x) ft(x)表示前 t 颗树的和, h t ( x ) h_t(x) ht(x)表示第t颗决策树,模型定义如下: f t ( x ) = ∑ t = 1 T h t ( x ) f_t(x)=\sum^T_{t=1}h_t(x) ft(x)=∑t=1Tht(x)。由于模型递归生成,第t步的模型由第t−1步的模型形成,可以写成: f t ( x ) = f t − 1 ( x ) + h t ( x ) f_t(x)=f_{t-1}(x)+h_t(x) ft(x)=ft−1(x)+ht(x)。每次需要加上的树 h t ( x ) h_t(x) ht(x)是之前树求和的误差: r t i = y i − f m − 1 ( x i ) r_{ti}=y_i-f_{m-1}(x_i) rti=yi−fm−1(xi).我们每一步只要拟合一颗输出为 r t i r_{ti} rti的CART树加到 f t − 1 ( x ) f_{t−1}(x) ft−1(x)就可以了。
LightGBM在哪些地方进行了优化 (区别XGBoost)?深入理解LightGBM - 知乎 ()
基于Histogram的决策树算法带深度限制的Leaf-wise的叶子生长策略直方图做差加速直接支持类别特征(Categorical Feature)Cache命中率优化基于直方图的稀疏特征优化多线程优化。
常用的机器学习算法,例如神经网络等算法,都可以以mini-batch的方式训练,训练数据的大小不会受到内存限制。而GBDT在每一次迭代的时候,都需要遍历整个训练数据多次。如果把整个训练数据装进内存则会限制训练数据的大小;如果不装进内存,反复地读写训练数据又会消耗非常大的时间。尤其面对工业级海量的数据,普通的GBDT算法是不能满足其需求的。LightGBM提出的主要原因就是为了解决GBDT在海量数据遇到的问题,让GBDT可以更好更快地用于工业实践。
基于Histogram的决策树算法
Histogram algorithm应该翻译为直方图算法,直方图算法的基本思想是:先把连续的浮点特征值离散化成 k 个整数,同时构造一个宽度为 k 的直方图。在遍历数据的时候,根据离散化后的值作为索引在直方图中累积统计量,当遍历一次数据后,直方图累积了需要的统计量,然后根据直方图的离散值,遍历寻找最优的分割点。
import cv2 as cvimport matplotlib.pyplot as pltimg = cv.imread('./dogs.jpg') #BGRimg1=img.copy()img1[:,:,2],img1[:,:,0] =img[:,:,0],img[:,:,2] #BGR->RGBplt.subplot(1,2,1);plt.imshow(img1)color=('b','g','r')plt.subplot(1,2,2)for i,col in enumerate(color):hist=cv.calcHist([img],[i],None,[256],[0,256]) #找到第i个通道的直方图数据plt.plot(hist,color=col)plt.xlim([0,256])plt.show()
直方图算法简单理解为:首先确定对于每一个特征需要多少个箱子(bin)并为每一个箱子分配一个整数;然后将浮点数的范围均分成若干区间,区间个数与箱子个数相等,将属于该箱子的样本数据更新为箱子的值;最后用直方图(#bins)表示。看起来很高大上,其实就是直方图统计,将大规模的数据放在了直方图中。
特征离散化具有很多优点,如存储方便、运算更快、鲁棒性强、模型更加稳定等。对于直方图算法来说最直接的有以下两个优点:
内存占用更小:直方图算法不仅不需要额外存储预排序的结果,而且可以只保存特征离散化后的值,而这个值一般用 8 位整型存储就足够了,内存消耗可以降低为原来的 1 8 \frac18 81 。也就是说XGBoost需要用 32 位的浮点数去存储特征值,并用 32 位的整形去存储索引,而 LightGBM只需要用 8 位去存储直方图,内存相当于减少为 1 8 \frac18 81 ;
计算代价更小:预排序算法XGBoost每遍历一个特征值就需要计算一次分裂的增益,而直方图算法LightGBM只需要计算 k k k 次( k 可以认为是常数),直接将时间复杂度从 O ( # d a t a ∗ # f e a t u r e ) O(\#data*\#feature) O(#data∗#feature) 降低到 O ( k ∗ # f e a t u r e ) O(k*\#feature) O(k∗#feature),而我们知道 # d a t a ≫ k \#data\gg k #data≫k。
当然,Histogram算法并不是完美的。由于特征被离散化后,找到的并不是很精确的分割点,所以会对结果产生影响。但在不同的数据集上的结果表明,离散化的分割点对最终的精度影响并不是很大,甚至有时候会更好一点。原因是决策树本来就是弱模型,分割点是不是精确并不是太重要;较粗的分割点也有正则化的效果,可以有效地防止过拟合;即使单棵树的训练误差比精确分割的算法稍大,但在梯度提升(Gradient Boosting)的框架下没有太大的影响。
直方图做差加速
LightGBM另一个优化是Histogram(直方图)做差加速。一个叶子的直方图可以由它的父亲节点的直方图与它兄弟的直方图做差得到,在速度上可以提升一倍。通常构造直方图时,需要遍历该叶子上的所有数据,但直方图做差仅需遍历直方图的k个桶。在实际构建树的过程中,LightGBM还可以先计算直方图小的叶子节点,然后利用直方图做差来获得直方图大的叶子节点,这样就可以用非常微小的代价得到它兄弟叶子的直方图。XGBoost 在进行预排序时只考虑非零值进行加速,而 LightGBM 也采用类似策略:只用非零特征构建直方图。
带深度限制的 Leaf-wise 算法
在Histogram算法之上,LightGBM进行进一步的优化。首先它抛弃了大多数GBDT工具使用的按层生长 (level-wise) 的决策树生长策略,而使用了带有深度限制的按叶子生长 (leaf-wise) 算法。XGBoost 采用 Level-wise 的增长策略,该策略遍历一次数据可以同时分裂同一层的叶子,容易进行多线程优化,也好控制模型复杂度,不容易过拟合。但实际上Level-wise是一种低效的算法,因为它不加区分的对待同一层的叶子,实际上很多叶子的分裂增益较低,没必要进行搜索和分裂,因此带来了很多没必要的计算开销。
LightGBM采用Leaf-wise的增长策略,该策略每次从当前所有叶子中,找到分裂增益最大的一个叶子,然后分裂,如此循环。因此同Level-wise相比,Leaf-wise的优点是:在分裂次数相同的情况下,Leaf-wise可以降低更多的误差,得到更好的精度;Leaf-wise的缺点是:可能会长出比较深的决策树,产生过拟合。因此LightGBM会在Leaf-wise之上增加了一个最大深度的限制,在保证高效率的同时防止过拟合。
单边梯度采样算法
Gradient-based One-Side Sampling 应该被翻译为单边梯度采样(GOSS)。GOSS算法从减少样本的角度出发,排除大部分小梯度的样本,仅用剩下的样本计算信息增益,它是一种在减少数据量和保证精度上平衡的算法。AdaBoost中,样本权重是数据重要性的指标。然而在GBDT中没有原始样本权重,不能应用权重采样。幸运的是,我们观察到GBDT中每个数据都有不同的梯度值,对采样十分有用。即梯度小的样本,训练误差也比较小,说明数据已经被模型学习得很好了,直接想法就是丢掉这部分梯度小的数据。然而这样做会改变数据的分布,将会影响训练模型的精确度,为了避免此问题,提出了GOSS算法。
GOSS是一个样本的采样算法,目的是丢弃一些对计算信息增益没有帮助的样本留下有帮助的。根据计算信息增益的定义,梯度大的样本对信息增益有更大的影响。因此,GOSS在进行数据采样的时候只保留了梯度较大的数据,但是如果直接将所有梯度较小的数据都丢弃掉势必会影响数据的总体分布。所以,GOSS首先将要进行分裂的特征的所有取值按照绝对值大小降序排序(XGBoost一样也进行了排序,但是LightGBM不用保存排序后的结果),选取绝对值最大的 a ∗ 100 % a*100\% a∗100%个数据。然后在剩下的较小梯度数据中随机选择 b ∗ 100 % b*100\% b∗100% 个数据。接着将这 b ∗ 100 % b*100\% b∗100% 个数据乘以一个常数 1 − a b \frac{1-a}{b} b1−a ,这样算法就会更关注训练不足的样本,而不会过多改变原数据集的分布。最后使用这 ( a + b ) ∗ 100 % (a+b)*100\% (a+b)∗100% 个数据来计算信息增益。
互斥特征捆绑算法
高维度的数据往往是稀疏的,这种稀疏性启发我们设计一种无损的方法来减少特征的维度。通常被捆绑的特征都是互斥的(即特征不会同时为非零值,像one-hot),这样两个特征捆绑起来才不会丢失信息。如果两个特征并不是完全互斥(部分情况下两个特征都是非零值),可以用一个指标对特征不互斥程度进行衡量,称之为冲突比率,当这个值较小时,我们可以选择把不完全互斥的两个特征捆绑,而不影响最后的精度。互斥特征捆绑算法(Exclusive Feature Bundling, EFB)指出如果将一些特征进行融合绑定,则可以降低特征数量。这样在构建直方图时的时间复杂度从 O ( # d a t a ∗ # f e a t u r e ) O(\#data*\#feature) O(#data∗#feature) 变为 O ( # d a t a ∗ # b u n d l e ) O(\#data*\#bundle) O(#data∗#bundle) ,这里 # b u n d l e \#bundle #bundle 指特征融合绑定后特征包的个数,且 # b u n d l e \#bundle #bundle 远小于 # f e a t u r e \#feature #feature 。针对这种想法,我们会遇到两个问题:怎么判定哪些特征应该绑在一起(build bundled)?怎么把特征绑为一个(merge feature)?
将相互独立的特征进行绑定是一个 NP-Hard 问题,LightGBM的EFB算法将这个问题转化为图着色的问题来求解,将所有的特征视为图的各个顶点,将不是相互独立的特征用一条边连接起来,边的权重就是两个相连接的特征的总冲突值,这样需要绑定的特征就是在图着色问题中要涂上同一种颜色的那些点(特征)。此外,我们注意到通常有很多特征,尽管不是100%相互排斥,但也很少同时取非零值。 如果我们的算法可以允许一小部分的冲突,我们可以得到更少的特征包,进一步提高计算效率。经过简单的计算,随机污染小部分特征值将影响精度最多 O ( [ ( 1 − γ ) n ] − 2 3 ) O([(1-\gamma)n]^{\frac{-2}{3}}) O([(1−γ)n]3−2) , 是每个绑定中的最大冲突比率 γ \gamma γ,当其相对较小时,能够完成精度和效率之间的平衡。具体步骤可以总结如下:
构造一个加权无向图,顶点是特征,边有权重,其权重与两个特征间冲突相关;根据节点的度进行降序排序,度越大,与其它特征的冲突越大;遍历每个特征,将它分配给现有特征包,或者新建一个特征包,使得总体冲突最小。
算法允许两两特征并不完全互斥来增加特征捆绑的数量,通过设置最大冲突比率 γ \gamma γ 来平衡算法的精度和效率。
算法3的时间复杂度是 O ( # f e a t u r e 2 ) O(\#feature^2) O(#feature2) ,训练之前只处理一次,其时间复杂度在特征不是特别多的情况下是可以接受的,但难以应对百万维度的特征。为了继续提高效率,LightGBM提出了一种更加高效的无图的排序策略:将特征按照非零值个数排序,这和使用图节点的度排序相似,因为更多的非零值通常会导致冲突,新算法在算法3基础上改变了排序策略。
解决怎么把特征绑为一捆:特征合并算法,其关键在于原始特征能从合并的特征中分离出来。绑定几个特征在同一个bundle里需要保证绑定前的原始特征的值可以在bundle中识别,考虑到histogram-based算法将连续的值保存为离散的bins,我们可以使得不同特征的值分到bundle中的不同bin(箱子)中,这可以通过在特征值中加一个偏置常量来解决。比如,我们在bundle中绑定了两个特征A和B,A特征的原始取值为区间[0,10),B特征的原始取值为区间[0,20),我们可以在B特征的取值上加一个偏置常量10,将其取值范围变为[10,30),绑定后的特征取值范围为 [0, 30),这样就可以放心的融合特征A和B了。
LightGBM的主要优缺点:
简单易用。提供了主流的Python\C++\R语言接口,用户可以轻松使用LightGBM建模并获得相当不错的效果。高效可扩展。在处理大规模数据集时高效迅速、高准确度,对内存等硬件资源要求不高。鲁棒性强。相较于深度学习模型不需要精细调参便能取得近似的效果。LightGBM直接支持缺失值与类别特征,无需对数据额外进行特殊处理。
LightGBM 采用了直方图算法将遍历样本转变为遍历直方图,极大的降低了时间复杂度;LightGBM 在训练过程中采用单边梯度算法过滤掉梯度小的样本,减少了大量的计算;LightGBM 采用了基于 Leaf-wise 算法的增长策略构建树,减少了很多不必要的计算量;LightGBM 采用优化后的特征并行、数据并行方法加速计算,当数据量非常大的时候还可以采用投票并行的策略;LightGBM 对缓存也进行了优化,增加了缓存命中率;
相对于深度学习模型无法对时空位置建模,不能很好地捕获图像、语音、文本等高维数据。在拥有海量训练数据,并能找到合适的深度学习模型时,深度学习的精度可以遥遥领先LightGBM。Boosting族是迭代算法,每一次迭代都根据上一次迭代的预测结果对样本进行权重调整,所以随着迭代不断进行,误差会越来越小,模型的偏差(bias)会不断降低。由于LightGBM是基于偏差的算法,所以会对噪点较为敏感;在寻找最优解时,依据的是最优切分变量,没有将最优解是全部特征的综合这一理念考虑进去。
记录用lightgbm解决一个数据挖掘案例
本次我们选择英雄联盟数据集进行LightGBM的场景体验。英雄联盟是美国拳头游戏开发的MOBA竞技网游,在每局比赛中蓝队与红队在同一个地图进行作战,游戏的目标是破坏敌方队伍的防御塔,进而摧毁敌方的水晶枢纽,拿下比赛的胜利。现在共有9881场英雄联盟韩服钻石段位以上的排位比赛数据,数据提供了在十分钟时的游戏状态,包括击杀数、死亡数、金币数量、经验值、等级……等信息。列blueWins是数据的标签,代表了本场比赛是否为蓝队获胜。(数据集及代码思路来自阿里云天池比赛)
Step1: 库函数导入Step2: 数据读取/载入Step3: 数据信息简单查看Step4: 可视化描述Step5: 利用 LightGBM 进行训练与预测Step6: 利用 LightGBM 进行特征选择Step7: 通过调整参数获得更好的效果
## 基础函数库import numpy as np import pandas as pd## 绘图函数库import matplotlib.pyplot as pltimport seaborn as sns## 我们利用Pandas自带的read_csv函数读取并转化为DataFrame格式df = pd.read_csv('./data/high_diamond_ranked_10min.csv')y = df.blueWins## 利用.info()查看数据的整体信息df.info()
进行简单的数据查看,我们可以利用 .head() 头部.tail()尾部
df.head()
标注标签并利用value_counts函数查看训练集标签的数量
y = df.blueWinsy.value_counts()
0 49491 4930Name: blueWins, dtype: int64
数据集正负标签数量基本相同,不存在数据不平衡的问题。
## 标注特征列drop_cols = ['gameId','blueWins']x = df.drop(drop_cols, axis=1)## 对于特征进行一些统计描述x.describe()
我们发现不同对局中插眼数和拆眼数的取值范围存在明显差距,甚至有前十分钟插了250个眼的异常值。 我们发现EliteMonsters的取值相当于Deagons + Heralds。发现TotalGold 等变量在大部分对局中差距不大。发现两支队伍的经济差和经验差是相反数。发现红队和蓝队拿到首次击杀的概率大概都是50%
## 根据上面的描述,我们可以去除一些重复变量,比如只要知道蓝队是否拿到一血,# 我们就知道红队有没有拿到,可以去除红队的相关冗余数据。drop_cols = ['redFirstBlood','redKills','redDeaths','redGoldDiff','redExperienceDiff', 'blueCSPerMin','blueGoldPerMin','redCSPerMin','redGoldPerMin']x.drop(drop_cols, axis=1, inplace=True) # 加上inplace# Step4:可视化描述data = xdata_std = (data - data.mean()) / data.std() # 简洁的语句实现了标准化变换data = pd.concat([y, data_std.iloc[:, 0:9]], axis=1)data = pd.melt(data, id_vars='blueWins', var_name='Features', value_name='Values')fig, ax = plt.subplots(1,2,figsize=(15,5))# 绘制小提琴图sns.violinplot(x='Features', y='Values', hue='blueWins', data=data, split=True, inner='quart', ax=ax[0], palette='Blues')fig.autofmt_xdate(rotation=45)data = xdata_std = (data - data.mean()) / data.std()data = pd.concat([y, data_std.iloc[:, 9:18]], axis=1)data = pd.melt(data, id_vars='blueWins', var_name='Features', value_name='Values')# 绘制小提琴图sns.violinplot(x='Features', y='Values', hue='blueWins', data=data, split=True, inner='quart', ax=ax[1], palette='Blues')fig.autofmt_xdate(rotation=45)plt.show()
一般来说,小提琴图是一种绘制连续型数据的方法,可以认为是箱形图与核密度图的结合体。当然了,在小提琴图中,我们可以获取与箱形图中相同的信息。
中位数(小提琴图上的一个白点)四分位数范围(小提琴中心的黑色条)。较低/较高的相邻值(黑色条形图)–分别定义为第一四分位数-1.5 IQR和第三四分位数+1.5 IQR。这些值可用于简单的离群值检测技术,即位于这些 "栅栏"之外的值可被视为离群值。
小提琴图 (Violin Plot)是用来展示多组数据的分布状态以及概率密度。这种图表结合了箱形图和密度图的特征,主要用来显示数据的分布形状。与箱形图相比,小提琴图毫无疑问的优势在于:除了显示上述的统计数据外,它还显示了数据的整体分布。这个差异点很有意义,特别是在处理多模态数据时,即有多峰值的分布。
从小提琴图中我们可以看出:击杀英雄数量越多更容易赢,死亡数量越多越容易输(bluekills与bluedeaths左右的区别)。 助攻数量与击杀英雄数量形成的图形状类似,说明他们对游戏结果的影响差不多。 一血的取得情况与获胜有正相关,但是相关性不如击杀英雄数量明显。 经济差与经验差对于游戏胜负的影响较小。 击杀野怪数量对游戏胜负的影响并不大。
plt.figure(figsize=(18,14))sns.heatmap(round(x.corr(),2), cmap='Blues', annot=True)plt.show()
同时我们画出各个特征之间的相关性热力图,颜色越深代表特征之间相关性越强,我们剔除那些相关性较强的冗余特征。
# 去除冗余特征drop_cols = ['redAvgLevel','blueAvgLevel']x.drop(drop_cols, axis=1, inplace=True)sns.set(style='whitegrid', palette='muted')# 构造两个新特征x['wardsPlacedDiff'] = x['blueWardsPlaced'] - x['redWardsPlaced']x['wardsDestroyedDiff'] = x['blueWardsDestroyed'] - x['redWardsDestroyed']data = x[['blueWardsPlaced','blueWardsDestroyed','wardsPlacedDiff','wardsDestroyedDiff']].sample(1000)data_std = (data - data.mean()) / data.std()data = pd.concat([y, data_std], axis=1)data = pd.melt(data, id_vars='blueWins', var_name='Features', value_name='Values')plt.figure(figsize=(10,6))sns.swarmplot(x='Features', y='Values', hue='blueWins', data=data)plt.xticks(rotation=45)plt.show()
我们画出了插眼数量的散点图,发现不存在插眼数量与游戏胜负间的显著规律。猜测由于钻石分段以上在哪插眼在哪好排眼都是套路,所以数据中前十分钟插眼数拔眼数对游戏的影响不大。所以我们暂时先把这些特征去掉。
## 去除和眼位相关的特征drop_cols = ['blueWardsPlaced','blueWardsDestroyed','wardsPlacedDiff','wardsDestroyedDiff','redWardsPlaced','redWardsDestroyed']x.drop(drop_cols, axis=1, inplace=True)x['killsDiff'] = x['blueKills'] - x['blueDeaths']x['assistsDiff'] = x['blueAssists'] - x['redAssists']x[['blueKills','blueDeaths','blueAssists','killsDiff','assistsDiff','redAssists']].hist(figsize=(12,10), bins=20)plt.show()
我们发现击杀、死亡与助攻数的数据分布差别不大。但是击杀减去死亡、助攻减去死亡的分布与原分布差别很大,因此我们新构造这么两个特征。
data = x[['blueKills','blueDeaths','blueAssists','killsDiff','assistsDiff','redAssists']].sample(1000)data_std = (data - data.mean()) / data.std()data = pd.concat([y, data_std], axis=1)data = pd.melt(data, id_vars='blueWins', var_name='Features', value_name='Values')plt.figure(figsize=(10,6))sns.swarmplot(x='Features', y='Values', hue='blueWins', data=data)plt.xticks(rotation=45)plt.show()
从上图我们可以发现击杀数与死亡数与助攻数,以及我们构造的特征对数据都有较好的分类能力。
data = pd.concat([y, x], axis=1).sample(500)sns.pairplot(data, vars=['blueKills','blueDeaths','blueAssists','killsDiff','assistsDiff','redAssists'], hue='blueWins')plt.show()
一些特征两两组合后对于数据的划分能力也有提升。
x['dragonsDiff'] = x['blueDragons'] - x['redDragons']x['heraldsDiff'] = x['blueHeralds'] - x['redHeralds']x['eliteDiff'] = x['blueEliteMonsters'] - x['redEliteMonsters']data = pd.concat([y, x], axis=1)eliteGroup = data.groupby(['eliteDiff'])['blueWins'].mean()dragonGroup = data.groupby(['dragonsDiff'])['blueWins'].mean()heraldGroup = data.groupby(['heraldsDiff'])['blueWins'].mean()fig, ax = plt.subplots(1,3, figsize=(15,4))eliteGroup.plot(kind='bar', ax=ax[0])dragonGroup.plot(kind='bar', ax=ax[1])heraldGroup.plot(kind='bar', ax=ax[2])print(eliteGroup)print(dragonGroup)print(heraldGroup)plt.show()
我们构造了两队之间是否拿到龙、是否拿到峡谷先锋、击杀大型野怪的数量差值,发现在游戏的前期拿到龙比拿到峡谷先锋更容易获得胜利。拿到大型野怪的数量和胜率也存在着强相关。推塔是英雄联盟这个游戏的核心,因此推塔数量可能与游戏的胜负有很大关系。我们绘图发现,尽管前十分钟推掉第一座防御塔的概率很低,但是一旦某只队伍推掉第一座防御塔,获得游戏的胜率将大大增加。
x['towerDiff'] = x['blueTowersDestroyed'] - x['redTowersDestroyed']data = pd.concat([y, x], axis=1)towerGroup = data.groupby(['towerDiff'])['blueWins']print(towerGroup.count())print(towerGroup.mean())fig, ax = plt.subplots(1,2,figsize=(15,5))towerGroup.mean().plot(kind='line', ax=ax[0])ax[0].set_title('Proportion of Blue Wins')ax[0].set_ylabel('Proportion')towerGroup.count().plot(kind='line', ax=ax[1])ax[1].set_title('Count of Towers Destroyed')ax[1].set_ylabel('Count')# Step5:利用 LightGBM 进行训练与预测## 为了正确评估模型性能,将数据划分为训练集和测试集,并在训练集上训练模型,在测试集上验证模型性能。from sklearn.model_selection import train_test_split## 选择其类别为0和1的样本 (不包括类别为2的样本)data_target_part = ydata_features_part = x## 测试集大小为20%, 80%/20%分x_train, x_test, y_train, y_test = train_test_split(data_features_part, data_target_part, test_size = 0.2, random_state = )print(len(x_train),len(x_test),len(y_train),len(y_test))
导入LightGBM模型
from lightgbm.sklearn import LGBMClassifier## 定义 LightGBM 模型 clf = LGBMClassifier()# 在训练集上训练LightGBM模型clf.fit(x_train, y_train)
在训练集和测试集上分布利用训练好的模型进行预测
train_predict = clf.predict(x_train)test_predict = clf.predict(x_test)from sklearn import metrics## 利用accuracy(准确度)【预测正确的样本数目占总预测样本数目的比例】评估模型效果print('The accuracy of the Logistic Regression is:',metrics.accuracy_score(y_train,train_predict))print('The accuracy of the Logistic Regression is:',metrics.accuracy_score(y_test,test_predict))## 查看混淆矩阵 (预测值和真实值的各类情况统计矩阵)confusion_matrix_result = metrics.confusion_matrix(test_predict,y_test)print('The confusion matrix result:\n',confusion_matrix_result)# 利用热力图对于结果进行可视化plt.figure(figsize=(8, 6))sns.heatmap(confusion_matrix_result, annot=True, cmap='Blues')plt.xlabel('Predicted labels')plt.ylabel('True labels')plt.show()
利用 LightGBM 进行特征选择,LightGBM的特征选择属于特征选择中的嵌入式方法,在LightGBM中可以用属性feature_importances_去查看特征的重要度。
sns.barplot(y=data_features_part.columns, x=clf.feature_importances_)
总经济差距等特征,助攻数量、击杀死亡数量等特征都具有很大的作用。插眼数、推塔数对模型的影响并不大。除此之外,我们还可以使用LightGBM中的下列重要属性来评估特征的重要性。gain:当利用特征做划分的时候的评价基尼指数 ;split:是以特征用到的次数来评价; 这些图同样可以帮助我们更好的了解其他重要特征。
from sklearn.metrics import accuracy_scorefrom lightgbm import plot_importancedef estimate(model,data):#sns.barplot(data.columns,model.feature_importances_)ax1=plot_importance(model,importance_type="gain")ax1.set_title('gain')ax2=plot_importance(model, importance_type="split")ax2.set_title('split')plt.show()def classes(data,label,test):model=LGBMClassifier()model.fit(data,label)ans=model.predict(test)estimate(model, data)return ansans=classes(x_train,y_train,x_test)pre=accuracy_score(y_test, ans)print('acc=',accuracy_score(y_test,ans))
通过调整参数获得更好的效果, LightGBM中包括但不限于下列对模型影响较大的参数:
learning_rate: 有时也叫作eta,系统默认值为0.3。每一步迭代的步长,很重要。太大了运行准确率不高,太小了运行速度慢。
num_leaves:系统默认为32。这个参数控制每棵树中最大叶子节点数量。
feature_fraction:系统默认值为1。我们一般设置成0.8左右。用来控制每棵随机采样的列数的占比(每一列是一个特征)。
max_depth: 系统默认值为6,我们常用3-10之间的数字。这个值为树的最大深度。这个值是用来控制过拟合的。max_depth越大,模型学习的更加具体。
调节模型参数的方法有贪心算法、网格调参、贝叶斯调参等。这里我们采用网格调参,它的基本思想是穷举搜索:在所有候选的参数选择中,通过循环遍历,尝试每一种可能性,表现最好的参数就是最终的结果
from sklearn.model_selection import GridSearchCV## 定义参数取值范围learning_rate = [0.1, 0.3, 0.6]feature_fraction = [0.5, 0.8, 1]num_leaves = [16, 32, 64]max_depth = [-1,3,5,8]parameters = {'learning_rate': learning_rate,'feature_fraction':feature_fraction,'num_leaves': num_leaves,'max_depth': max_depth}model = LGBMClassifier(n_estimators = 50)## 进行网格搜索clf = GridSearchCV(model, parameters, cv=3, scoring='accuracy',verbose=3, n_jobs=-1)clf = clf.fit(x_train, y_train)## 网格搜索后的最好参数为clf.best_params_
Fitting 3 folds for each of 108 candidates, totalling 324 fits[LightGBM] [Warning] feature_fraction is set=1, colsample_bytree=1.0 will be ignored. Current value: feature_fraction=1{'feature_fraction': 1, 'learning_rate': 0.1, 'max_depth': 3, 'num_leaves': 16}
在训练集和测试集上分布利用最好的模型参数进行预测
## 定义带参数的 LightGBM模型 clf = LGBMClassifier(feature_fraction = 0.8,learning_rate = 0.1,max_depth= 3,num_leaves = 16)# 在训练集上训练LightGBM模型clf.fit(x_train, y_train)train_predict = clf.predict(x_train)test_predict = clf.predict(x_test)## 利用accuracy(准确度)【预测正确的样本数目占总预测样本数目的比例】评估模型效果print('The accuracy of the Logistic Regression is:',metrics.accuracy_score(y_train,train_predict))print('The accuracy of the Logistic Regression is:',metrics.accuracy_score(y_test,test_predict))## 查看混淆矩阵 (预测值和真实值的各类情况统计矩阵)confusion_matrix_result = metrics.confusion_matrix(test_predict,y_test)print('The confusion matrix result:\n',confusion_matrix_result)# 利用热力图对于结果进行可视化plt.figure(figsize=(8, 6))sns.heatmap(confusion_matrix_result, annot=True, cmap='Blues')plt.xlabel('Predicted labels')plt.ylabel('True labels')plt.show()
LightGBM的重要参数
num_leaves参数这是控制树模型复杂度的主要参数,一般的我们会使num_leaves小于(2的max_depth次方),以防止过拟合。由于LightGBM是leaf-wise建树与XGBoost的depth-wise建树方法不同,num_leaves比depth有更大的作用。、
min_data_in_leaf这是处理过拟合问题中一个非常重要的参数. 它的值取决于训练数据的样本个树和 num_leaves参数. 将其设置的较大可以避免生成一个过深的树, 但有可能导致欠拟合. 实际应用中, 对于大数据集, 设置其为几百或几千就足够了.
max_depth树的深度,depth 的概念在 leaf-wise 树中并没有多大作用, 因为并不存在一个从 leaves 到 depth 的合理映射。
针对训练速度的参数调整
通过设置 bagging_fraction 和 bagging_freq 参数来使用 bagging 方法。
通过设置 feature_fraction 参数来使用特征的子抽样。
选择较小的 max_bin 参数。
使用 save_binary 在未来的学习过程对数据加载进行加速。
针对准确率的参数调整
使用较大的 max_bin (学习速度可能变慢)
使用较小的 learning_rate 和较大的 num_iterations
使用较大的 num_leaves (可能导致过拟合)
使用更大的训练数据
尝试 dart 模式
针对过拟合的参数调整
使用较小的 max_bin
使用较小的 num_leaves
使用 min_data_in_leaf 和 min_sum_hessian_in_leaf
通过设置 bagging_fraction 和 bagging_freq 来使用 bagging
通过设置 feature_fraction 来使用特征子抽样
使用更大的训练数据
使用 lambda_l1, lambda_l2 和 min_gain_to_split 来使用正则
尝试 max_depth 来避免生成过深的树
n (学习速度可能变慢)
使用较小的 learning_rate 和较大的 num_iterations
使用较大的 num_leaves (可能导致过拟合)
使用更大的训练数据
尝试 dart 模式
【机器学习算法实践】lightGBM将可解释的机器学习实现工业落地 小数据量机器学习就够了 大数据量还得深度学习。推荐看论文进行理解 boosting方法有残差的感觉了