当前位置: 首页 > news >正文

【机器学习实战笔记 16】集成学习:LightGBM算法

目录

  • 1 LightGBM基本介绍
    • 1.1 高阶集成学习算法
    • 1.2 LightGBM算法简介
  • 2 LightGBM基本原理与EFB降维方法
    • 2.1 LightGBM原理简介
    • 2.2 LightGBM的数据压缩策略
    • 2.3 LightGBM决策树建模优化方法
      • 1.连续变量分箱
      • 2.互斥特征捆绑(Exclusive Feature Bundling,EFB)
        • (1) EFB算法简介与基本流程
        • (2) EFB算法基本原理
  • 3 LightGBM核心技术:GOSS采样与直方图优化算法
    • 3.1 基于梯度的单边采样(Gradient-based One-Side Sampling,GOSS)
      • (1) GOSS基本原理
      • (2) GOSS计算过程实例
    • 3.2 LGBM决策树生长过程与直方图优化算法(Histogram-based Algorithm)
      • (1) 基本原理介绍
      • (2) 手动实现流程
  • 4 LightGBM完整建模流程的数学推导
    • 4.1 LGBM理论推导中的关键数学环节
      • (1) 关键数学环节一:伪残差计算公式
      • (2) 关键数学环节二:决策树的预测结果
      • (3) 关键数学环节三:决策树分裂增益
    • 4.2 LightGBM完整建模流程的数学推导
      • (1) 初始化过程
      • (2) 数据压缩过程
      • (3) Boosting迭代过程
      • (4) Boosting迭代停止
  • 5 LightGBM sklearn API实现与进阶
    • 5.1 LightGBM算法的快速使用
      • (1) LGBM算法库安装
      • (2)LGBM的sklearn API快速使用
    • 5.2 LightGBM sklearn API超参数解释与使用方法
      • (1)LGBMClassifier超参数概览
      • (2)LGBMClassifier的决策树剪枝超参数
      • (3)LGBMClassifier的Boosting过程控制超参数解释
      • (4)LGBMClassifier的特征和数据处理类超参数
      • (5)LGBMClassifier的其他超参数
      • (6)LGBMRegressor损失函数
  • 6 LightGBM原生API使用与进阶
    • 6.1 LGBM的原生API调用
    • 6.2 LGBM原生API进阶使用方法
      • (1)Dataset API
        • 1.1 Dataset类的解释与使用方法
        • 1.2 更便捷的读取和存储
        • 1.3 借助.construct()方法进行数据集加载
        • 1.4 Dataset类读取本地文件类型
        • 1.5 LGBM高效数据保存和读取流程
        • 1.6 Dataset类参数讲解

1 LightGBM基本介绍

1.1 高阶集成学习算法

  在学习了一系列梯度提升树的改进算法后,接下来,我们将进入到更加前沿的集成学习算法中,即LightGBM算法(全称为Light Gradient Boosting Machine,以下简称LGBM算法)和CatBoost算法(全称为Categorical Boosting)的学习中。和XGBoost算法(以下简称XGB算法)类似,这两个算法也是GBDT的改进算法,并且由于这两个算法诞生时间更晚,因此相比之下,LGBM和CatBoost拥有更多功能上的优化,以便应对更加复杂的当前机器学习应用情况。例如,相比XGB,LGBM有更高效的计算效率和更低的内存占用,并且面对高维数据,LGBM算法拥有更好的过拟合特性,这使得在建模数据量日趋增加的今天,LGBM会更适合作为前期探索性建模的模型(Baseline模型),并且在具体建模效果上,对比XGB也是不遑多让。而CatBoost算法同样在训练效率上比XGB更快,并且更加自动化——在特征衍生和超参数优化已经成为机器学习模型训练标配的今天,CatBoost能够(一定程度上)实现对输入的特征进行自动特征衍生和对超参数进行自动超参数优化。不难看出,LGBM和CatBoost是诞生于新应用环境中的新型集成学习算法,而对LGBM和CatBoost算法的学习,也成为了当今算法工程师的必修课。

  不过同样需要说明的是,尽管LGBM和CatBoost算法对比XGB有诸多方面的优化,但这并不代表这两种算法相比XGB具有全方位的效果优势。在真实的实战应用,XGB(甚至是随机森林)仍然具有非常高的实践价值,很多时候我们需要尝试多种不同类型的算法,才能获得一个更好的结果。并且,由RF、XGB、LGBM、CatBoost属于“强而不同”的算法,这会导致这些模型结果会非常适合进行更进一步的模型融合,以达到更好的效果,因此在大多数追求极致建模效果的场景下,这些模型都需要训练,并得到一个尽可能好的结果,然后再进行融合。

而相比其他集成学习算法,例如Bagging、AdaBoost等,RF、XGB、LGBM和CatB可以说是有全方位的效果优势,因此,除非是某些特殊场景,否则一般不会优先考虑使用这些算法。

1.2 LightGBM算法简介

  LightGBM 是一种高效的 Gradient Boosting 算法,由 Microsoft Research Asia 团队开发,早期为Microsoft内部处理海量高维数据的专用算法,并于2017年由Guolin Ke, Qi Meng, Thomas Finley等人通过论文形式正式发布。如果说XGB为GBDT类算法在提升计算精度上做出了里程碑式的突破,那么LGBM则是在计算效率和内存优化上提出了开创性的解决方案,一举将GBDT类算法计算效率提高了近20倍、并且计算内存占用减少了80%,这也最终使得GBDT类算法、这一机器学习领域目前最高精度的预测类算法,能够真正应用于海量数据的建模预测。以下是官网给出的XGB、基于直方图优化的XGB和LGBM算法的在相同计算任务下计算时间的对比:

0a747b1752ff4c7c9b368d7415b398c
  • LightGBM算法相关论文
      对于新兴机器学习算法,最权威的介绍材料毫无疑问就是提出者发布的相关论文,这里我们重点推荐开发团队在2017年提出LGBM原理论文以及2019年由Essam Al Daoud提出的算法性能对比论文,两篇论文介绍及地址如下:

  LightGBM: A Highly Efficient Gradient Boosting Decision Tree (2017)
  作者:Guolin Ke, Qi Meng, Thomas Finley, Taifeng Wang, Wei Chen, Weidong Ma, Qiwei Ye, Tie-Yan Liu
  该论文是 LightGBM 的最初论文,详细阐述了 LightGBM 算法的设计思想、技术特点和实验结果。

  Comparison between XGBoost, LightGBM and CatBoost using a home credit dataset (2019)
  作者:Essam Al Daoud
  该论文详细对比了LGBM、XGB和CatB三个模型在信用卡数据上的性能差异,并提出了不同模型的超参数优化基本思路。

2 LightGBM基本原理与EFB降维方法

2.1 LightGBM原理简介

  LightGBM(Light Gradient Boosting Machine,以下简称LGBM)是一个基于梯度提升决策树(Gradient Boosted Decision Trees,GBDT)的高效、可扩展的机器学习算法,作为GBDT框架的算法的一员,并且作为XGB算法的后来者,LGBM非常好综合了包括XGB在内的此前GBDT算法框架内各算法的一系列优势,并在此基础上做了一系列更进一步的优化。LGBM算法提出的核心目的是为了解决GBDT算法框架在处理海量数据时计算效率低下的问题,而从实践效果来看,LGBM也确实做到了这点——LGBM以牺牲极小的计算精度为代价,将GBDT的计算效率提升了近20倍!这也最终使得LGBM算法是第一个真正意义上能处理海量数据的GBDT框架算法。并且,尽管计算精度存在“选择性的牺牲”,但LGBM的实际建模效果也能达到几乎和XGB同等水平,而且由于LGBM“选择性的牺牲精度”从另一个角度来看其实就是抑制模型过拟合,因此在很多场景下,LGBM的算法效果甚至会好于XGB。种种实践证明,LGBM是一个拥有超高计算效率的同时、又能够保持超高精度的算法,是目前机器学习领域当之无愧的顶级算法之一。
  而LGBM是如何做效率和精度“两手抓”的呢?简而言之就是LGBM充分借鉴了XGB提出的一系列提升精度的优化策略,同时在此基础之上进一步提出了一系列的数据压缩和决策树建模流程的优化策略。尽管在算法的数学原理层面LGBM并没有翻越XGB创建的理论高峰,但其提出的一系列优化策略也同样是极具开创性的,其中数据压缩方法能够让实际训练的数据量在大幅压缩的同时仍然保持较为完整的信息,而决策树建模流程方面的优化,则是在XGB提出的直方图优化算法基础上进行了大幅优化,不仅能够加速决策树建模速度,同时也能非常好的处理经过压缩后的数据,从而最终大幅提升每棵树的训练效率(甚至在LGBM提出的一段时间后,新版XGB也采用了LGBM类似的直方图算法来加速建模效率)。并且最重要的是,有理论能够证明,哪怕LGBM实际建模是基于压缩后的数据进行训练,但其预测精度受到的影响也是微乎其微。
  当然,除了算法原理层面的优化方法外,LGBM还提出了非常多针对于实际计算过程的优化,例如Voting Parallel(投票特征并行)方法、特征多线程并行处理方法、GPU加速方法和分布式计算等,这些方法进一步提升了LGBM实际建模效率,并且一定程度拓宽了算法的使用场景。并且需要注意的是,所谓的计算效率优化,不仅体现在计算时间的大幅缩短,同时得益于LGBM所提出的一系列数据压缩技术,使得实际建模时数据内存占用也大幅减少。
  总的来说,LGBM算法可以看成是迭代过程几乎全盘借鉴XGB、而在数据压缩方法和决策树训练方法上有大量创新的算法,因此在原理相关内容我们将分为两部分进行讲解,第一部分我们重点介绍LGBM创新性提出的一系列方法,第二部分再来探讨LGBM损失函数求解流程。考虑到LGBM的推导流程和XGB几乎完全一样,原理部分的讲解的重点将会是LGBM创新性提出的一系列数据压缩和优化策略。

XGB几乎可以说是GBDT类算法的原理层面的里程碑,开创性的提出了拟合二阶泰勒展开的思路,并据此设计了全套关键数学表达式,包括包含Hessian值得伪残差、分裂增益计算公式化和叶节点权重计算公式。而后继的LGBM和CatBoost,在损失函数求解过程几乎没有再提出超出XGB理论框架的内容,而是在数据预处理和决策树训练方法上提出了进一步优化方法。这点甚至也可以从LGBM原论文中看出,在LGBM原始论文中几乎没有任何关于损失函数求解的说明,通篇几乎都在强调数据压缩方法和决策树优化流程的有效性,我们也是通过查阅官方文档和源码才得知LGBM的具体迭代流程。因此,从这个角度来说,XGB是迄今为止GBDT类算法框架的理论最高峰。

2.2 LightGBM的数据压缩策略

  LightGBM建模过程总共会进行三方面的数据压缩,根据实际建模顺序,会现在全样本上连续变量分箱(连续变量离散化),然后同时带入离散特征和离散后的连续变量进行离散特征捆绑(合并)降维,最终在每次构建一颗树之前进行样本下采样。其中连续变量的分箱就是非常简单的等宽分箱,并且具体箱体的数量可以通过超参数进行人工调节;而离散特征的降维,则是采用了一种所谓的互斥特征捆绑(Exclusive Feature Bundling, EFB)算法,该算法也是由LGBM首次提出,该方法的灵感来源于独热编码的逆向过程,通过把互斥的特征捆绑到一起来实现降维,这种方法能够很好的克服传统降维方法带来的信息大量损耗的问题,并且需要注意的是,输入EFB进行降维的特征,即包括原始离散特征,也包括第一阶段连续变量离散化之后的特征;在这一系列数据压缩之后,LGBM在每次迭代(也就是每次训练一颗决策树模型)的时候,还会围绕训练数据集进行下采样,此时的下采样不是简单的随机抽样,而是一种名为基于梯度的单边采样(Gradient-based One-Side Sampling, GOSS)的方法,和EFB类似,这种方法能够大幅压缩数据,但同时又不会导致信息的大量损失。不难发现,最终输入到每颗决策树进行训练的数据,实际上是经过大幅压缩后的数据,这也是LGBM计算高效的根本原因之一。

2.3 LightGBM决策树建模优化方法

  而进入到具体的决策树训练环节,总的来说LGBM采用的决策树建模优化方法有两个,其一是直方图优化算法,这种方法本质上是通过直方图的形式更加高效简洁的表示每次分裂前后数据节点的核心信息,并且父节点和子节点也可以通过直方图减法计算直接算得,从而加速数据集分裂的计算过程:

981861843f708f9efb5f74829c336b3

其二则是leaf wise tree growth的叶子节点优先的决策树生长策略,这其实是一种树生长的模式,对于其他大多数决策树算法和集成算法来说,树都是一次生长一层,也就是所谓的Level-wise tree growth(深度优先的生长策略),生长过程如下。

b4030247c4f8acaee3a8337bb6e7bb2

而LGBM则允许决策树生长过程优先按照节点进行分裂,即允许决策树“有偏”的生长,也就是所谓的leaf wise tree growth的叶子节点优先的决策树生长策略,具体生长过程如下:

33f0d4c90d591b2577698a730d24dfc

根据LGBM论文的论述,但从Level-wise tree growth远离层面,这种方法其实是有利有弊,其优势在于能够大幅提升每颗树的收敛速度,从总体来看相当于是提升了每次迭代效率;而问题则在于会每棵树的计算过程会变得更加复杂,并且存在一定的过拟合风险。不过对于LGBM来说,这些问题都能够被很好的克服,比如计算过程复杂的问题可以通过数据压缩来对冲,而过拟合风险则可以通过限制最大树深度来解决,因此总的来看Level-wise tree growth就是最适合LGBM的决策树生长策略。

1.连续变量分箱

  • 等宽分箱基本概念回顾

  首先是连续变量分箱。LGBM采用的连续变量分箱方法就是简单的等宽分箱:首先计算连续变量的取值范围,然后人工输入的max_bin超参数,进行数量为max_bin等宽度的区间划分,并把连续变量的值划归到一个个箱体内部。例如某连续变量取值范围为[0, 10],max_bin=2,则两个等宽的区间划分为bin0=[0, 5)和bin1=[5, 10],并且如果某连续变量取值为1,则经过分箱后会被标记为bin0(或者0),如果某各连续变量取值为10,则分箱后会被标记为bin1(或者1)。至此,就将连续变量转化为了离散变量。

这里需要注意,XGB也会对连续变量进行分箱,但XGB的分箱是分位数分箱,而不是等宽分箱。

2.互斥特征捆绑(Exclusive Feature Bundling,EFB)

  接下来则是围绕这些离散特征进行降维。LGBM采用了一种名为互斥特征捆绑(Exclusive Feature Bundling,EFB)的降维方法,这种方法在LGBM论文LightGBM: A Highly Efficient Gradient Boosting Decision Tree (2017)中首次提出,不同于第一阶段的简单的等宽分箱,EFB实际计算过程非常复杂,我们这里从EFB方法提出背景、计算原理和手动示例三个方面对其进行介绍。

(1) EFB算法简介与基本流程
  • EFB算法提出背景

  根据LightGBM: A Highly Efficient Gradient Boosting Decision Tree (2017)论文描述,原始的GBDT在进行每颗树的训练时,需要带入全部数据来进行信息增益的计算,从而寻找到决策树生长过程中的最佳切分点,这个过程也就是所谓的扫描全部数据来决定切分点的过程。这个过程尽管非常精准,但计算复杂度非常高(直接和特征数量及样本数量成正比),在进行海量数据建模训练的时候会耗费大量的算力和时间。因此,为了能够更好的应对海量数据的模型训练,样本采样和特征降维是非常必要的。但传统的方法在这方面往往效果不佳,例如简单的欠采样(样本随机抽样)可能会造成模型训练过程非常不稳定,而PCA降维则只适用于处理冗余特征,当每个特征都具有相当信息体量时强行进行降维则会导致信息大量丢失。为了解决这个问题,LGBM开创性的提出了基于梯度的单边采样方法(GOSS)进行样本数量的压缩,提出了互斥特征捆绑方法(EFB)来进行特征压缩。不同于以往的方法,GOSS和EFB能够非常好的兼顾预测精度与计算效率。此外,对连续变量进行离散化也是非常有效的数据压缩的手段,LGBM在XGB提出的直方图优化的基础上,进一步提出了一种改进策略,和GOSS和EFB类似,这种LGBM直方图优化方法同样能够在大幅提高计算效率的同时保证预测精度。

  • 简化后的EFB计算流程

  而具体到EFB降维算法,其实是受到独热编码启发,设计的类似于独热编码逆向过程的一种算法。例如一组数据情况如下,独热编码是从左往右的计算过程,把一列展开为多列,而EFB则是从右往左进行计算,将多列压缩为一列:

0be767e9953d7db2a66752f524e6ba4

  那既然是独热编码的逆向计算,我们就需要首先讨论为什么LGBM不需要独热编码。我们知道,独热编码本质上是对离散特征更加细粒度的信息呈现,在某些场景下能够提升模型效果。当然更重要的是独热编码能够非常好的用于表示离散变量,对于大多数无法区分连续变量和离散变量的机器学习算法来说,通过独热编码重编码的数据将能够非常方便进行例如离散变量之间的距离计算等操作。但是这些独热编码的优势对于LGBM来说并不存在。首先LGBM带入模型计算的全部变量都是离散变量(连续变量也会被离散化),其次独热编码带来的更细粒度的信息呈现也不会进一步提升模型效果(对于大多数集成学习算法来说都是如此),当然更重要的是,LGBM的算法设计就是为了处理海量高维数据,独热编码只会进一步造成维度灾难。因此,LGBM不仅不需要进行独热编码,还需要进行独热编码的逆操作来进行特征降维。当然,我们这里只是借用独热编码的计算过程帮大家理解EFB的降维过程,在实际计算过程中,EFB的降维的目标并不是把独热编码之后的特征再转换回来,而是找到那些原始状态下就存在类似上图中x1和x2这种关系的特征,来将其合成为一个特征。这里我们注意观察,上图中x1和x2两个特征存在一种这样的关系——任何一条样本都不会同时在x1和x2上取值非0,在EFB原理的定义中,这种关系的特征又被称作互斥特征,而互斥特征其实是可以合成一个特征的,比如上图中的x,这个合成的过程并不会有任何的信息损失,而合成的过程又被称作特征捆绑。这也就是所谓的互斥特征捆绑算法。
  我们这里再看一个互斥特征捆绑的例子,比如如下x3和x4,也是互斥的,此时我们可以将x3和x4捆绑为一个新的x_b1特征,新特征中可以用0、1、2来表示x3和x4的不同组合,从而在不损失信息的情况下,进行了降维。

6a6d2ee962754f94b7232cb069835ce

  当然,这只是一个简化后的示例,真实的EFB特征降维情况会非常复杂,并不是简单的将多个离散变量的不同取值组合进行重新赋值,这个例子只是用于帮大家建立对EFB的感性的认识,接下来我们就围绕原论文中提出的EFB算法来进行更加严谨的算法流程介绍。

(2) EFB算法基本原理
  • 放宽互斥的条件:冲突比例(conflict_rate)概念介绍

  真实数据的EFB计算过程会非常复杂,首先是关于“互斥”关系的定义,EFB并不是只压缩完全互斥的特征,而是非常灵活的定义了一个冲突比例(又称非互斥比例),这个比例用于表示两个特征中冲突(即非互斥、同时取非零值)的取值占比,来衡量两个特征互斥程度。当然,冲突比例越大说明互斥程度越低。例如对于如下数据集,总共包含四条数据,其中只有第四条数据是同时取得了非零值,因此只有一条数据是冲突的,其他数据都是互斥的,因此冲突比例为1/4=0.25:

特征1特征2
01
10
00
11

同时,LGBM提供了一个名为max_conflict_rate的超参数,用于表示最大冲突比例,当两个特征的冲突比例小于我们设置的最大冲突比例(max_conflict_rate)时,我们就认为这两个特征并不冲突,而是互斥的,是可以进行捆绑的。例如假设我们设置max_conflict_rate=0.3,则上述两个特征可以进行捆绑,而如果我们设置max_conflict_rate=0.2,则上面两个特征超过了我们认为冲突的阈值,因此这两个特征是冲突的,而不是互斥的,是不能进行进一步捆绑的。很明显,max_conflict_rate设置的越小,对互斥的要求就越高,特征就越不容易被捆绑,模型计算量就越大、精度也越高,而如果max_conflict_rate设置的很大,则更多的特征会被捆绑,模型计算速度会更快,但精度也会降低。

3 LightGBM核心技术:GOSS采样与直方图优化算法

  在LGBM算法的计算流程中,当执行完特征压缩后,接下来就将进入到每棵树的建模过程中。不过同样是出于提高计算效率考虑,LGBM并不是带入全部数据进行每棵树的训练,而是采用了一种名为基于梯度的单边采样(Gradient-based One-Side Sampling,GOSS)的下采样方法,缩减实际带入模型训练的样本数量。这是一种非常特殊的采样方法,而其实践效果和EFB类似,都是能够在大幅提高计算效率的同时确保计算精度。并且,当已经完成了GOSS采样之后,在实际决策树生长过程中,LGBM也采用了XGB类似的直方图优化算法,来加速决策树的计算过程。从原理层面来看,LGBM和XGB的直方图优化算法并没有本质上的区别,只是二者在进行直方图计算时采用的指标略有不同,具体区别我们将在介绍直方图算法时具体讲解。此外,LGBM还在直方图的实际计算机计算层面进行了优化,例如Voting Parallel(投票特征并行)方法等。

3.1 基于梯度的单边采样(Gradient-based One-Side Sampling,GOSS)

(1) GOSS基本原理

  首先来看基于梯度的单边采样(Gradient-based One-Side Sampling)方法,也就是所谓的GOSS抽样方法。不同于简单随机抽样,GOSS是一种非常特殊的基于梯度分布的抽样方法。我们知道在执行优化算法的过程中,每个样本都有一个对应的梯度计算结果,如果某条样本梯度绝对值较小,则说明这条样本已经被正确的分类或者预测结果和真实结果非常接近,在后续的参数更新过程中,这些梯度绝对值较小的样本对参数的改进贡献较小,因此每次迭代计算时再把这些小梯度的样本再算一遍梯度,会一定程度造成资源浪费。而反观那些梯度绝对值较大的样本,这些样本具有更高的误差,因此对模型的训练有更大的贡献。因此GOSS的思路是将全部样本按照梯度绝对值大小进行降序排序,然后抽取梯度绝对值最大的前a%a\%a%的样本,然后把其他样本都视作小梯度样本,并从这些小梯度样本中随机抽取b%b\%b%个样本,而这些大梯度样本和随机抽取的小梯度样本,就构成了接下来模型训练的数据集。而只针对小梯度样本(一边)进行抽样、保留(另一边)全部大梯度样本,也就是单边采样一词的由来。

  而在具体执行GOSS的时候有以下几点需要注意:

  • 1.GOSS计算过程是根据梯度的绝对值大小进行样本划分和抽样,并不是样本梯度的真实值;

  • 2.GOSS中样本选取比例,也就是梯度绝对值最大的前a%a\%a%和小样本中随机抽样的b%b\%b%,实际上都是超参数,可以在建模过程中灵活调节。这里的a%a\%a%可以换成更专业的超参数名称:top_rate,而小样本抽取的b%b\%b%更专业的名称则是other_rate;

  • 3.我们知道样本梯度是基于预测结果计算而来的(具体来说是损失函数的一阶导数),而在第一棵树构建之前我们就需要进行GOSS采样,此时还没有模型预测结果,梯度计算依据的是LGBM的初始预测值,和其他集成学习类似,LGBM的初始预测值也是根据损失函数的不同类型计算得到的结果;

  • 4.由于每次迭代都会更新模型参数,因此每次建树之前都会重新进行抽样,而除非人为控制迭代过程(例如使用一种非常特殊的Booster API,后面会详细介绍),否则一般来说top_rate和other_rate设置好了就不会再发生变化;

  • 5.关于top_rate和other_rate的数值设置,一般来说top_rate越大other_rate越小,则模型过拟合风险就越大,反之则模型学习能力会被限制,而如果这两个参数同时较大,则会增加模型训练复杂度,增加模型训练时间。关于这组参数,并没有一个普遍适用的取值,还是需要根据实际情况进行超参数优化。

  • 6.尽管带入训练的数据是GOSS抽样后的数据,但在后续决策树生长的过程中,小梯度样本的梯度(和损失函数二阶导数)会再乘以一个大于1的膨胀系数,再和大梯度样本的梯度(和损失函数二阶导数)进行相加,构成一个数据集的梯度(和损失函数二阶导数),来指导后续的迭代进行。而之所以要让小梯度样本进一步膨胀再加入到样本数据梯度中,其实也是为了尽可能还原原始真实的数据集梯度。也即是说,GOSS抽样并不是想要改变数据集梯度,而是希望通过更小的计算量,来尽可能还原原始数据集完整梯度,以此来提升建模的精确度,其基本过程可以由下图进行表示:5a9d29da9104eae4106ca796a7a493c具体膨胀系数如何计算,其实也并不复杂,就是1−ab\frac{1-a}{b}b1a,或者说是1−top_rateother_rate\frac{1-top\_rate}{other\_rate}other_rate1top_rate。例如当top_rate=0.1,other_rate=0.2时,小样本梯度膨胀系数为:1−top_rateother_rate=1−0.10.2=4.5\frac{1-top\_rate}{other\_rate}=\frac{1-0.1}{0.2}=4.5other_rate1top_rate=0.210.1=4.5。最终样本梯度=大样本梯度+小样本梯度*4.5算得。

  • 7.GOSS过程带来的误差:最后需要注意的是,尽管GOSS抽样后我们通过膨胀系数来尽可能还原数据集整体的梯度,但这种还原肯定是存在一定误差的。根据原论文描述,这种误差可以通过如下公式进行表示:

    1681034491832

    原论文中的公式并不好理解,这里我们可以将其等价为另一个公式进行解释。首先,假设我们有一个样本集合III,其中有∣I∣|I|I个样本。对于一个梯度值阈值aaa,我们将样本集合III划分为两个子集IlargeI_{large}IlargeIsmallI_{small}IsmallIlargeI_{large}Ilarge包含所有梯度值大于等于aaa的样本,而IsmallI_{small}Ismall包含所有梯度值小于aaa的样本。用∣Ilarge∣|I_{large}|Ilarge表示IlargeI_{large}Ilarge中的样本数量,用∣Ismall∣|I_{small}|Ismall表示IsmallI_{small}Ismall中的样本数量。接下来,我们从IsmallI_{small}Ismall中随机采样一定比例(α\alphaα)的样本,则梯度计算的最大误差估计为:Error=1∣I∣∑i∈Ilargegi−1−αα∣Ismall∣∑i∈Ismallgi\mathrm{Error} = \frac{1}{|I|}\sum_{i \in I_{large}} g_i - \frac{1 - \alpha}{\alpha |I_{small}|}\sum_{i \in I_{small}} g_iError=I1iIlargegiαIsmall1αiIsmallgi这里,gig_igi表示第iii个样本的梯度值。

(2) GOSS计算过程实例

  • 损失函数、梯度与Hessian计算公式
      接下来我们借助简单示例数据集data来执行一次完整的GOSS采样计算过程。由于GOSS采样计算过程会涉及样本梯度计算,因此这里我们首先需要回顾样本梯度的计算方法。我们知道样本梯度是损失函数在各参数方向上求导得到的结果,因此梯度实际上和损失函数相关。这里data数据集是二分类数据集,因此假设建模过程中的损失函数是二分类交叉熵损失函数,在Lesson 4介绍梯度下降算法时,我们就介绍了二分类交叉熵损失函数的计算公式,以及样本梯度的计算方法,并借助手写代码来进行实现,这里我们快速回顾下交叉熵损失计算公式,对于第i条样本来说,yiy_iyi是真实标签,而y^i\hat y_iy^i或者pip_ipi是概率预测结果,则样本整体二分类交叉熵损失计算公式如下:
    L=−1n∑i=1n[yilog⁡pi+(1−yi)log⁡(1−pi)]\mathcal{L}=-\frac{1}{n} \sum_{i=1}^{n}\left[y_{i} \log p_{i}+\left(1-y_{i}\right) \log \left(1-p_{i}\right)\right] L=n1i=1n[yilogpi+(1yi)log(1pi)]
    此时第iii条样本的梯度就是损失函数对预测样本的一阶偏导数:
    ∂L∂pi=1−yi1−pi−yipi\frac{\partial \mathcal{L}}{\partial p_{i}}=\frac{1-y_{i}}{1-p_{i}}-\frac{y_{i}}{p_{i}}piL=1pi1yipiyi
    更进一步的,由于后续直方图计算过程还需要用到损失函数的二阶偏导数,也就是所谓的Hessian矩阵(值),也被称作黑塞矩阵、海森矩阵等。对于第iii条样本,损失函数的二阶偏导数计算公式如下:
    ∂2L∂pi2=yipi2+1−yi(1−pi)2\frac{\partial^{2} \mathcal{L}}{\partial p_{i}^{2}}=\frac{y_{i}}{p_{i}^{2}}+\frac{1-y_{i}}{\left(1-p_{i}\right)^{2}} pi22L=pi2yi+(1pi)21yi

  • LGBM初始预测值
      正如此前所说,首次进行GOSS抽样时梯度计算依据是LGBM算法的初始预测值,如果是交叉熵损失函数,则初始预测值为1类样本占比(或者1类样本总数)的对数几率(log odds)计算结果,并且每条样本初始预测值都相同,具体计算过程可以由如下计算公式表示:
    y^=lnp1−p\hat y = ln\frac{p}{1-p}y^=ln1pp

其中ppp为1类的占比(或者1类总数,计算结果都相同)。例如对于data数据集来说,初始预测值可以按照如下方式进行计算:

在这里插入图片描述

  • 样本梯度与hes值计算

在这里插入图片描述

  • GOSS抽样

  据此我们即可进一步执行GOSS抽样。这里我们假设top_rate=0.2,other_rate=0.5,由于我们样本量较少,因此这里设置的比例偏大。此时GOSS抽样过程如下,首先计算样本梯度绝对值:
在这里插入图片描述

此时top_rate=0.2,other_rate=0.5,即从10条数据中挑选出梯度最大的2条数据,然后从剩下的8条数据中抽取50%,即抽取4条数据,共同构成本次GOSS抽样得到的训练数据集。这里我们先借助布尔索引先挑选出梯度最大的2条数据:
在这里插入图片描述
至此,我们就完成了一次GOSS抽样全过程,接下来我们就将带入我们抽样得到的topn_data和raten_data带入进行模型训练。

3.2 LGBM决策树生长过程与直方图优化算法(Histogram-based Algorithm)

  在拿到GOSS抽样的数据集之后,接下来就正式进入到单独一颗决策树的生长过程中。该过程将涉及三个核心概念,LGBM决策树生长的增益计算公式、Leaf wise tree growth(叶节点优先)生长策略以及直方图优化算法。接下来我们逐一对其进行介绍。

(1) 基本原理介绍

  • 直方图优化算法(Histogram-based Algorithm)

  直方图优化算法与其说是一种算法,不如是一种决策树生长分裂过程中数据集(及其关键信息)的表示方法——即通过直方图来表示数据集(及其关键信息)在决策树生长过程中的分裂即计算过程。这种表示方法能够大幅减少数据集内存占用、提升计算速度,并且方便进行直方图差分计算——子节点的直方图可以通过从父节点的直方图中减去兄弟节点的直方图来得到。

981861843f708f9efb5f74829c336b3

  需要注意的是,直方图算法最早是XGB算法提出的一种加速计算的方法,LGBM和XGB的直方图算法在直方图的表示形式上并没有任何区别,只是在直方图统计的值方面有区别:XGB是用直方图统计样本值的累加(并对特征进行排序),而LGBM是用直方图统计数据集的一阶导数和Hessian值的累加(并对特征进行排序)。也正因如此,LGBM可以利用直方图进行差分加速,而XGB不行。从这点而言,LGBM的直方图优化算法可以视作XGB直方图优化算法的优化版本。当然除此之外,LGBM还有很多并行计算的策略,进一步加速其计算过程。

  • LGBM决策树生长的增益计算公式

  而在具体的决策树生长过程中,最重要的是进行切分点的选取,这个过程中最重要的分裂增益的计算方法。不同于CART树的基于基尼系数差的增益计算方法和C4.5的基于信息熵的信息增益,LGBM采用了一种非常特殊的、同时包含梯度和Hessian值得分裂增益计算方法,具体计算公式如下:

Gain=GL2HL+λ+GR2HR+λ−(GL+GR)2HL+HR+λ−α⋅(∣wL∣+∣wR∣)Gain = \frac{G_L^2}{H_L + \lambda} + \frac{G_R^2}{H_R + \lambda} - \frac{(G_L + G_R)^2}{H_L + H_R + \lambda} - \alpha \cdot (|w_L|+|w_R|)Gain=HL+λGL2+HR+λGR2HL+HR+λ(GL+GR)2α(wL+wR)

其中,GLG_LGLGRG_RGR 分别左节点和右节点的梯度和,HLH_LHLHRH_RHR 分别是左节点和右节点的Hessian和,λ\lambdaλ 是 reg_lambda 参数(L2正则化项),α\alphaα 是 reg_alpha 参数(L1正则化项),wLw_LwLwRw_RwR 分别是左节点和右节点的权重。并且,对于一个节点,权重计算公式如下:
w=−GH+λw=-\frac{G}{H+\lambda}w=H+λG
而对于reg_alpha和reg_lambda,其实是模型的两个超参数,也被称作L1正则化参数和L2正则化参数,用于控制模型结构风险。根据分裂增益计算公式不难看出,在数据集梯度和Hessian固定不变的情况下,L1正则化参数和L2正则化参数取值越大,增益计算结果就越小,决策树就越倾向于不分裂。

  • LGBM的Leaf wise tree growth生长策略

  在具体生长的过程中,LGBM中的决策树也是同样会比较不同切分点带来的增益,然后选择增益最大的切分点进行分裂,这个过程和其他所有决策树都一样。而所谓的Leaf wise tree growth(叶节点优先)生长策略,则是对比另一种生长策略:Level-wise tree growth(层次优先的生长策略),前者是一次生长一个节点,可以长成不同子树深浅不一的决策树:

33f0d4c90d591b2577698a730d24dfc

而Level-wise tree growth则是一次生长一层,最后决策树将是左右子树深度:

b4030247c4f8acaee3a8337bb6e7bb2

  当然,叶节点优先的生长策略也会增加模型过拟合风险,因此对于LGBM来说,必须要通过限制树的最大深度来解决叶节点优先带来的过拟合问题,因此,对于LGBM来说,在超参数优化时树的最大深度max_depth将会是一个非常重要的超参数。

(2) 手动实现流程

  接下来,我们结合data数据集及之前抽样得到的GOSS数据集,来手动执行决策树生长流程。这里我们首先介绍如何手动计算一颗树的生长流程,然后再介绍如何采用直方图算法来优化这个计算流程。这里需要注意的是,实际的LGBM在进行迭代时是围绕伪残差进行拟合,这里为了便于介绍决策树的分裂增益和Leaf wise tree growth生长策略,决策树拟合目标改为数据集标签。在下一小节,我们将详细讨论LGBM伪残差计算公式以及如何围绕伪残差进行决策树建模拟合。

  • 数据准备

  首先准备手动计算所需数据集,还是围绕此前介绍的data数据集,我们将GOSS抽样得到的数据集进行合并和标记处理。
在这里插入图片描述
这里我们将抽样得到的数据集进行拼接,只保留经过分箱和EFB剩下的特征,并保留标签、梯度和hess值,然后新增一个topn二分类变量,用于标记是否是大梯度样本,大梯度样本用1表示,小梯度样本用0标记:
在这里插入图片描述

  • 决策树的第一层分裂

  接下来,我们尝试进行决策树的生长过程计算。由于现在数据集中只有x1_binned、x2_binned&x4和x3三个离散变量,因此不难发现,备选的切分点只有x1_binned=0.5、x2_binned&x4=0.5、x2_binned&x4=1.5、x2_binned&x4=2.5和x3=0.5五个,因此我们需要分别计算这5个不同切分点取值下决策树分裂的增益,然后选取增益最大的切分点进行切分,从而完成这次的决策树生长。
  我们先以x1_binned=0.5为切分点计算分裂增益。根据此前介绍,LGBM决策树分裂过程中的增益计算公式如下:
Gain=GL2HL+λ+GR2HR+λ−(GL+GR)2HL+HR+λ−α⋅(∣wL∣+∣wR∣)Gain = \frac{G_L^2}{H_L + \lambda} + \frac{G_R^2}{H_R + \lambda} - \frac{(G_L + G_R)^2}{H_L + H_R + \lambda} - \alpha \cdot (|w_L|+|w_R|)Gain=HL+λGL2+HR+λGR2HL+HR+λ(GL+GR)2α(wL+wR)

其中,GLG_LGLGRG_RGR 分别左节点和右节点的梯度和,HLH_LHLHRH_RHR 分别是左节点和右节点的Hessian和,λ\lambdaλ 是 reg_lambda 参数(L2正则化项),α\alphaα 是 reg_alpha 参数(L1正则化项),wLw_LwLwRw_RwR 分别是左节点和右节点的权重。并且,对于一个节点,权重计算公式如下:
w=−GH+λw=-\frac{G}{H+\lambda}w=H+λG
同时,GL2HL+λ\frac{G_L^2}{H_L + \lambda}HL+λGL2GR2HR+λ\frac{G_R^2}{H_R + \lambda}HR+λGR2分别也可以看成是左、右节点的标签不纯度计算指标,(GL+GR)2HL+HR+λ\frac{(G_L + G_R)^2}{H_L + H_R + \lambda}HL+HR+λ(GL+GR)2也可以看成是父节点的标签不纯度计算指标,该计算公式和XGB算法的结构分数计算公式完全相同。而不同的是在LGBM的决策树生长过程中,还有一个用于控制整体复杂度的L1正则化项,用于乘以左右节点的权重绝对值之和,作为增益的惩罚项。这里需要注意,XGB中也有叶节点权重的概念,且XGB的叶节点权重和LGBM叶节点权重计算公式完全一致。
这里我们假设α=0.02\alpha=0.02α=0.02λ=0.1\lambda=0.1λ=0.1,则在x1_binned=0.5为切分点时数据集分裂情况如下:

8f2bbe02d4dbc19c4354d434e514301

其中GL、HL代表左节点的梯度和及hess和,这里乘以的1.6实际上就是膨胀系数,在之前的GOSS采样过程中,top_rate=0.2,other_rate=0.5,因此膨胀系数θ=1−0.20.5=1.6\theta = \frac{1-0.2}{0.5}=1.6θ=0.510.2=1.6,所以我们在计算梯度和及hess和的时候需要把小样本先乘以膨胀系数再和大样本的结果进行相加。对应的GR、HR则代表右边节点的计算结果,在进行子数据集的梯度和及hess和的计算时,我们可以按照原始计算公式把每个子节点的grad和hess带入进行加权计算(小梯度样本需要乘以权重,再和大梯度样本进行求和),此时GL=−6.413,HL=15.819,GR=−1.031,HR=24.87GL=-6.413,HL=15.819,GR=-1.031,HR=24.87GL=6.413,HL=15.819,GR=1.031,HR=24.87,带入计算公式得出此时决策树分裂增益为:
Gain(x1_binned=0.5)=(−6.4)215.8+0.1+(−1.031)224.87+0.1−(−6.4−1.03)215.81+24.84+0.1−0.02(6.4815.81+0.1+1.03124.87+0.1)=1.2533Gain_{(x1\_binned=0.5)} = \frac{(-6.4)^2}{15.8 + 0.1} + \frac{(-1.031)^2}{24.87 + 0.1} - \frac{(-6.4 - 1.03)^2}{15.81 + 24.84 + 0.1}-0.02(\frac{6.48}{15.81+0.1}+\frac{1.031}{24.87+0.1})=1.2533Gain(x1_binned=0.5)=15.8+0.1(6.4)2+24.87+0.1(1.031)215.81+24.84+0.1(6.41.03)20.02(15.81+0.16.48+24.87+0.11.031)=1.2533

类似的我们可以继续计算其他备选切分点的增益。

  • Leaf wise tree growth生长策略

  由于LGBM决策树算法是采用的Leaf wise tree growth生长策略,因此我们需要对比左、右子树各自的分裂增益,然后选择某一边进行进一步生长。这里左子树对应数据集data_goss_1,右子树对应data_goss_2数据集,很明显对于data_goss_1数据集来说,标签纯度已经达到100%,没有再分裂的必要,因此接下来重点考察右子树data_goss_2的分裂情况。通过观察我们不难发现,data_goss_2在x2_binned&x4=1.5切分点上,能够使得切分子数据集的标签纯度达到100%,因此我们手动尝试围绕这个点进行切分生长,并计算分裂增益。分裂过程如下:

1679731756120

类似的我们先尝试进行手动计算,根据每个数据集的grad和hess,我们可以计算目前两个左右节点各自的梯度和及hess和,其中GL1=5.376GL1=5.376GL1=5.376HL1=9.0496HL1=9.0496HL1=9.0496GR1=−6.396GR1=-6.396GR1=6.396HR1=15.8184HR1=15.8184HR1=15.8184,然后带入增益公式:
Gain=GL2HL+λ+GR2HR+λ−(GL+GR)2HL+HR+λ−α⋅(∣wL∣+∣wR∣)Gain = \frac{G_L^2}{H_L + \lambda} + \frac{G_R^2}{H_R + \lambda} - \frac{(G_L + G_R)^2}{H_L + H_R + \lambda} - \alpha \cdot (|w_L|+|w_R|)Gain=HL+λGL2+HR+λGR2HL+HR+λ(GL+GR)2α(wL+wR)

算得此时增益为:Gain(x2_binned&x4=1.5)=5.6862Gain_{(x2\_binned \& x4=1.5)}=5.6862Gain(x2_binned&x4=1.5)=5.6862
能够发现,确实𝑥2_𝑏𝑖𝑛𝑛𝑒𝑑&𝑥4=1.5增益是最高的,因此,这层决策树将按照该切分点进行分裂生长。而生长后的决策树全部叶节点的标签纯度都达到了100%,因此不用再进行分裂,这颗决策树的生长到此结束。同时,我们也确实发现该决策树最终左右两边的子树深度并不相同,这也从侧面说明当前决策树是按照Leaf wise tree growth进行生长。

  • 直方图差加速

  不难看出,尽管LGBM的决策树生长流程并不复杂,但针对每个备选节点的分裂增益的计算过程却略显繁琐,其中最繁琐的地方在于子数据集的grad和及hess和的计算过程,更具体的来说,繁琐之处在于需要区分大梯度样本和小梯度样本,然后赋予不同的权重进行求和,这个大小梯度样本的检索过程在实际计算机执行过程中是极耗费计算量的。

  而为了能够加速子样本的梯度和、hess和的计算过程,LGBM提出了基于梯度和及hess和的直方图优化算法,该算法首先要求用直方图来表示各数据集的梯度和、hess和的累计值,然后从数学层面可以非常简单的证明,在决策树分裂过程中梯度和、hess和整体累计值不变,因此子节点的梯度和、hess和的累计值将和父节点相同,进而在决策树分裂过程中,我们只需要计算父节点和左子树的梯度和、hess和,就能通过相减的方式得到右子树的梯度和、hess和。由此我们就可以摆脱每个节点独立计算梯度和、hess和的计算过程,进而大幅提升决策树建模过程中分裂增益的计算速度、从而加快决策树的建模效率。这个过程也就是直方图优化算法中的差加速优化。

  不难发现,直方图优化的过程涉及两个核心的技术要点,其一是要对每个数据集进行直方图转化,其二则是通过减法求得右子树的直方图。接下来我们尝试通过直方图优化算法来再执行一遍此前决策树模型的生长过程,通过对比来查看直方图具体加速过程的体现。

  首先需要计算和绘制根节点的直方图,这里的直方图指的是数据集中各变量取不同值时对应的样本的grad和及hess和,例如根节点来说,对于data_goss数据集中x1_binned这一列,0值样本如下:
在这里插入图片描述
其中0号样本是大梯度样本,而8号样本是小梯度样本。考虑到此时因此top_rate=0.2,other_rate=0.5,因此膨胀系数θ=1−0.20.5=1.6\theta = \frac{1-0.2}{0.5}=1.6θ=0.510.2=1.6,因此x1_binned中0值样本的梯度累加计算过程为0号样本梯度值+θ\thetaθ * 8号样本梯度值,其他的也类似计算。
据此,我们就可以将x1_binned用下图的直方图进行表示,其中直方图的不同柱子用于表示x1_binned特征不同取值下累计的梯度(也就是梯度和)和累计的hess(Hessian和),注意,这个累加的过程就是数值累加,而非绝对值累加:

1679718468700

据此,我们绘制整个根节点的直方图如下:

1679719468765

然后我们来进行第一次分裂,之前我们首先尝试了x1_binned=0.5为切分点进行分裂,分裂结果如下:

8f2bbe02d4dbc19c4354d434e514301

此时我们不难发现,GLGLGLHLHLHLGRGRGRHRHRHR实际上就是根节点中result_x1的计算结果:

1679737244972

因此,如果此前计算过根节点的直方图,就能够非常快速的直接得出现在的子节点的梯度和及hess和。当然,当我们得到两个子节点之后,我们需要围绕这两个数据集分别再进行直方图计算,即不同特征取值下对应的grad和及hess和,并且这里其实我们只需要计算左节点的直方图,右节点的直方图可以通过父节点减去左节点直方图直接计算得到,例如此时左节点的直方图如下:

1679736435248 而右节点直方图,则可以利用父节点直方图直接减去左节点直方图得到:
1679736744110

而这个详见的过程其实非常简单,就是对应位置梯度和及hess和相减即可。

而我们完成左右子节点的直方图计算后,即可继续计算不同切分点的分裂增益,同样,由于我们已经得到data_goss_1和data_goss_2两个数据集的直方图,因此这两个数据集的分裂增益计算过程也将变得非常简单,例如我们这里仍然以右节点的x2_binned&x4=1.5为切分点,计算分裂增益,此时的分裂增益计算过类似于根节点的第一次分裂过程,我们只需要根据data_goss_2的直方图就能快速得到分裂子节点的GL1、HL1、GR1和HR1:

1679737530884

此时GL1=5.38,HL1=9.05,GR1=(-2.46-3.946)=-6.396,HR1=6.084+9.73=15.81。然后就可依据这个结果快速计算分裂增益。这里可以对比此前我们手动计算的结果来验证直方图计算结果是否正确:

1679731756120

能够发现,直方图优化算法的计算结果和手动计算结果一致。从而也验证了直方图优化算法本身的正确性。

  • 直方图优化算法的有效性讨论

  从本质上来说,直方图优化其实是提供了一种特殊的能够保存数据集梯度累加和hess累加的对象(一下简称为直方图对象),然后在决策树的分裂增益计算过程中通过减法计算代替一个节点的梯度和及hess和的计算过程。这个过程看似减少的计算不多,但实际上LGBM算法在实际创建直方图对象的过程中会有非常多的数据类型优化方法,能够进一步加快这个计算过程。同时,哪怕在单次计算中看似提升不大的方法,在海量数据处理时都能带来巨大的计算时间减少。实际上,根据官方说明文档的论述,直方图优化为决策树生长的计算过程了节约了40%的计算时间。

  至此,我们就完整介绍了LGBM算法中单独一颗决策树的建模过程。下一小节我们将在这两个小节介绍的基本原理基础之上,来进行完整的LGBM迭代过程的数学推导,帮大家从更加宏观的层面掌握LGBM迭代流程。

4 LightGBM完整建模流程的数学推导

  在我们了解了一系列LGBM的数据压缩方法和决策树优化方法之后,本节我们围绕LGBM的损失函数求解过程进行完整的数学推导,同时也将结合此前介绍的LGBM提出的一系列数据预处理方法,从原理层面为大家呈现完整的LGBM算法建模流程。不过正如此前所说,LGBM的损失函数求解过程几乎可以看成是GBDT和XGB的结合体,因此LGBM损失函数的数学推导层面本身并不会有太多难点。但需要注意的是,不同于XGB算法提出了一整套非常严谨的数学推导和逻辑证明,LGBM其实更像是通常意义下的机器学习算法——即一切以后验结果为准。在LGBM算法提出团队来看,很多数学过程并不是一定需要环环相扣,一切以“追求最高精度”为导向,很多数学过程或许可以以“启发式”的方法拼凑到一起,只要最终能够获得一个足够精确的结果和高效的计算过程即可。
  因此,从这个角度来说,学习LGBM算法的难点并不在于数学推导(LGBM算法原理的数学过程不会超过XGB的范畴),但却需要我们将环环相扣的数学过程拆解成一个个独立的关键环节,并理解每个关键环节之于最终结果的影响,同时能够进一步理解不同环节的拼凑组合会有哪些优势和潜在问题,最终建立对GBDT算法框架各算法数学过程更层次理解。

需要注意的是,不仅LGBM是这种启发式的拼接数学过程、一切以后验的结果为准,后续介绍的CatBoost算法也是类似。因此,掌握这种拆分数学过程并进行灵活组装的思维,对于理解新一代GBDT算法至关重要。当然,从另一个角度来说,也能看出XGB算法的数学推导,是目前GBDT类算法无法逾越的理论高峰。

4.1 LGBM理论推导中的关键数学环节

  这里我们首先回顾GBDT算法和XGB算法在进行原理推导时关键的数学环节,并通过对比两个算法在相同环节的不同数学策略,来分析这些数学策略对最终结果的影响,并探讨LGBM算法在这些关键环节上的选择及其背后的依据。需要注意的是,接下来的内容需要用到大量Lesson 12、Lesson 13中的基础知识,在学习本节内容之前,需要回顾此前课程内容。
  总的来说,GBDT框架算法最核心的数学环节有以下三个,分别是伪残差计算公式、决策树预测结果的计算方法和决策树分裂增益计算方法,此处我们对比GBDT和XGB两个算法在这些不同数学环节采取的不同数学策略,并分析不同数学策略对结果的直接影响:

(1) 关键数学环节一:伪残差计算公式

  • XGB和GBDT伪残差计算公式
      在XGBoost中我们曾讨论,伪残差并不一定是真正的残差(当评估指标是MSE时,GBDT的伪残差就是残差,不过这只是一个“巧合”),但使用伪残差代替残差,能够非常好的提升模型的收敛效率、提高模型的泛化能力,并且能够大幅提升模型的可用性——即可以灵活定义不同类型的损失函数进行建模,进而拓展模型本身的应用范围。
      并且我们曾证明,伪残差之所以能够加快模型收敛速度,是因为伪残差代表的拟合方向就是损失函数最快速减小(下降)的方向。换而言之,通过一颗颗决策树不断拟合伪残差,最终能够使得损失函数最快速的减小。同时,在伪残差的具体选取上,GBDT的伪残差是样本的负梯度:rit−GBDT=−∂l(yi,Ht−1(xi))∂Ht−1(xi)r_{it-GBDT} = -\frac{\partial{l(y_i,H_{t-1}(x_i))}}{\partial{H_{t-1}(x_i)}}ritGBDT=Ht1(xi)l(yi,Ht1(xi))而XGB的伪残差则是一个同时包含梯度和损失函数二阶导的计算结果:gik−XGB=∂l(yi,Hk−1(xi))∂Hk−1(xi)g_{ik-XGB} = \frac{\partial{l(y_i,H_{k-1}(x_i))}}{\partial{H_{k-1}(x_i)}}gikXGB=Hk1(xi)l(yi,Hk1(xi))

    hik−XGB=∂2l(yi,Hk−1(xi))∂Hk−12(xi)h_{ik-XGB} = \frac{\partial^2{l(y_i,H_{k-1}(x_i))}}{\partial{H^2_{k-1}(x_i)}}hikXGB=Hk12(xi)2l(yi,Hk1(xi))

    rik−XGB=−gikhikr_{ik-XGB} = -\frac{g_{ik}}{h_{ik}}rikXGB=hikgik而根据Lesson 13中的数学推导不难看出,从本质上来说,XGB的伪残差是在拟合损失函数的二阶泰勒展开,而GBDT的伪残差则是在拟合损失函数的一阶泰勒展开。在大多数情况下,通过拟合二阶泰勒展开,能够更好的捕捉损失函数的更加细微的变动,从而提升精度,但代价是这么做需要耗费更大的计算量。

  • LGBM伪残差计算公式及选择依据
      而对于LGBM来说,却并没有采用看似理论精度更高的XGB伪残差计算策略,而是采用了GBDT的伪残差计算策略。究其原因,其实还是为了加快速度、保证精度。在加快速度方面,正如此前所说,包含二阶导数的伪残差计算过程会耗费更大量的计算资源;而在保证精度方面,则是因为经过实际验证,伪残差的不同选取对最终模型的精度并没有本质上的影响,尽管XGB的伪残差拥有更高的理论精度,但这种精度优势是非常微小的,考虑到实际模型的建模精度还会受到非常多的其他不确定性因素影响,因此XGB的伪残差并不是唯一最好的选择。当然,这里我们可以更进一步的进行讨论,XGB伪残差的理论精度优势之所以很小,是因为对于GBDT类算法来说,伪残差只是迭代的方向,并不是最终迭代的结果,实际拟合效果不仅跟方向有关,更和实际的每颗树的预测结果有关,换而言之,真正能让损失函数数值下降的,其实是决策树输出的预测结果。因此,LGBM判断(同时也是经过实验验证),XGB的伪残差并不能带来非常大的实际建模精度收益,真正对预测结果有显著影响的,是决策树的预测结果以及决策树的生长方式。

(2) 关键数学环节二:决策树的预测结果

  • XGB的决策树权重计算公式

  对于GBDT来说,决策树的预测结果其实就是简单的叶节点样本均值的计算结果,几乎和普通的CART树的计算过程类似。而XGB则开创性的提出了一种基于样本导数和二阶梯度的预测结果,计算公式如下:wj=−∑i∈jgi∑i∈jhi+λw_j = -\frac{\sum_{i \in j}g_{i}}{\sum_{i \in j}h_{i} + \lambda}wj=ijhi+λijgi其中wjw_jwj表示第jjj个分支的权重,而iii则表示这个分支中的第iii个样本,hih_ihi表示第iii个样本的hessian值,gig_igi表示第iii个样本的梯度。这里省去了第kkk次迭代的标号。
  而根据XGBoost讲解中的讨论,这种预测结果能够最大程度令损失函数下降。并且在XGB中,叶节点的预测结果被定义成叶节点权重(这么做的原因是因为最终各模型的叠加表达式是一个线性方程,叶节点的输出结果在这个线性方程中就像是一个个变量的权重,用于表示该节点对最终预测结果做出的贡献大小)。

  • XGB的决策树权重计算公式只和损失函数有关,和伪残差无关
      这里需要重点关注的是,这种设计真正创新之处在于将决策树的叶节点预测结果和损失函数直接挂扣,而非和当前数据集的标签挂钩。尽管从公式层面看起来确实像和XGB的伪残差存在某种关系,但实际上,在Lesson 13的数学推导过程中我们不难发现,这个式子实际上是直接从损失函数的公式中求解得到的,跟伪残差如何计算并没有任何关系。相关证明我们可以从Lesson 13的损失函数公式求解的推导过程中得出,当然,我们也可以用一种更加简单的方式,直接证明上述叶节点的预测结果能够最大程度另损失函数数值下降。

  首先,这个问题可以转化为求解损失函数在当前叶节点权重下的最小值,即假设某棵树已经完成生长,叶节点应该如何进行预测,才能令损失函数取得最小值。首先我们简单回顾泰勒展开的表示形式:假设δ\deltaδ是一个非常小的量,则对函数f(x)f(x)f(x)来说,可以将其在 xxx 点附近的 f(x+δ)f(x+\delta)f(x+δ) 进行泰勒展开,公式如下:
f(x+δ)=f(x)+δf′(x)+12δ2f′′(x)+...f(x+\delta) = f(x) + \delta f'(x) + \frac{1}{2}\delta^2f''(x)+...f(x+δ)=f(x)+δf(x)+21δ2f′′(x)+...
其中f′(x)f'(x)f(x)f(x)f(x)f(x)的一阶导,f′′(x)f''(x)f′′(x)f(x)f(x)f(x)的二阶导数。

注意,泰勒展开有多种表示,以上只是其中一种表示形式。

当然,具体来看f(x+δ)f(x+\delta)f(x+δ)的二阶展开就是 f(x)+δf′(x)+12δ2f′′(x)f(x) + \delta f'(x) + \frac{1}{2}\delta^2f''(x)f(x)+δf(x)+21δ2f′′(x),因此上述公式也可以写成:
f(x+δ)≈f(x)+δf′(x)+12δ2f′′(x)f(x+\delta) \approx f(x) + \delta f'(x) + \frac{1}{2}\delta^2f''(x)f(x+δ)f(x)+δf(x)+21δ2f′′(x)
  类似的,我们假设L(y,f(x))L(y, f(x))L(y,f(x))是损失函数,其中f(x)f(x)f(x)是某次迭代预测结果,并假设下一次预测的结果为δ\deltaδ,那么则有下次迭代之后的损失函数表达式为L(y,f(x)+δ)L(y, f(x)+\delta)L(y,f(x)+δ),此时我们就L(y,f(x)+δ)L(y, f(x)+\delta)L(y,f(x)+δ)进行二阶泰勒展开,得到公式如下:
L(y,f(x)+δ)≈L(y,f(x))+δL′(y,f(x))+12δ2L′′(y,f(x))L(y, f(x)+\delta) \approx L(y, f(x)) + \delta L'(y, f(x)) + \frac{1}{2}\delta^2 L''(y, f(x))L(y,f(x)+δ)L(y,f(x))+δL(y,f(x))+21δ2L′′(y,f(x))
并且此时L′(y,f(x))L'(y, f(x))L(y,f(x))就是样本梯度,可以表示为ggg,而L′′(y,f(x))L''(y, f(x))L′′(y,f(x))则是样本的hessian值,可以表示为hhh,同时我们知道,两次迭代之间的δ\deltaδ,其实就是这颗决策树的预测结果乘以学习率的结果,也就是XGB中定义的叶节点权重乘以权重,因此δ\deltaδ也可以用w⋅ηw\cdot \etawη进行表示。因此,上述公式等价于:
L(y,f(x)+w⋅η)≈L(y,f(x))+w⋅ηg+12w2⋅η2hL(y, f(x)+w\cdot \eta) \approx L(y, f(x)) + w\cdot \eta g + \frac{1}{2}w^2\cdot \eta^2 hL(y,f(x)+wη)L(y,f(x))+wηg+21w2η2h

而我们希望通过这次迭代,能够让损失函数尽可能的减少,即我们希望L(y,f(x)+δ)L(y, f(x)+\delta)L(y,f(x)+δ)尽可能小,也就等价于我们希望表达式L(y,f(x))+wg+12w2hL(y, f(x)) + w g + \frac{1}{2}w^2 hL(y,f(x))+wg+21w2h计算得到的结果尽可能小。而其中L(y,f(x))L(y, f(x))L(y,f(x))是上一次迭代结果,是固定的值,因此最终等价于求解如下表达式:min(w⋅ηg+12w2⋅η2h)min{(w\cdot \eta g + \frac{1}{2}w^2\cdot \eta^2 h)}min(wηg+21w2η2h)
而在这个表达式中,ggghhh都是已知的结果,因此这个表达式是一个关于www的函数。而要令其取值最小,我们同样可以对其进行求导,并令导数为0,得到表达式如下:
g⋅η+w⋅η2h=0g\cdot \eta + w\cdot \eta^2 h=0gη+wη2h=0
由此可以得到www的最佳取值为w=−gη⋅hw=-\frac{g}{\eta \cdot h}w=ηhg

η\etaη是人工设置的某个常数,因此www的最佳取值也可以等价为w=−ghw=-\frac{g}{h}w=hg
对应到第jjj个分支,则有如下计算公式:wj=−∑i∈jgik∑i∈jhik+λw_j = -\frac{\sum_{i \in j}g_{ik}}{\sum_{i \in j}h_{ik} + \lambda}wj=ijhik+λijgik

其中λ\lambdaλ是L2正则化项,用于控制模型复杂度。而每个节点的梯度(和hessian)就是对应节点中全部样本求和的结果。

  而通过上面这个过程我们不难发现,XGB的决策树叶节点权重计算公式只和损失函数有关,和伪残差无关。而这样的叶节点预测结果,相比CART树的预测结果(样本标签均值或者多数类类别),毫无疑问能够更好的降低损失函数取值,从而加快迭代效率。

  • LGBM的决策树预测结果计算公式

  也正因如此,LGBM采用了XGB一样的叶节点预测结果计算公式,即wj=−∑i∈jgik∑i∈jhik+λw_j = -\frac{\sum_{i \in j}g_{ik}}{\sum_{i \in j}h_{ik} + \lambda}wj=ijhik+λijgik并且我们知道了这个计算公式的推导实际上只和损失函数有关,而和伪残差无关,因此也解释了为何LGBM和XGB的伪残差不同,但决策树的预测结果计算公式相同的原因。不过需要注意的是,LGBM中并没有叶节点权重这一概念,只是将其称作叶节点预测结果。

(3) 关键数学环节三:决策树分裂增益

  • 决策树分裂增益由叶节点预测结果计算公式直接决定

  需要注意的是,上述决策树预测结果计算公式是基于某棵树已经建立完成后,推导得到的结论。和直观感受相悖的是,对于具体的每棵树如何生长、如何计算分裂增益,其实也是依据叶节点预测结果来进行的推导,即只要给定了叶节点的预测结果计算公式,就能在“最快速降低损失函数取值”这一目标下,推导出分裂增益的计算公式。例如,在XGB中,具体的由叶节点权重推导得到分裂增益的计算公式的过程如下:
  首先,我们将w=−gη⋅hw=-\frac{g}{\eta \cdot h}w=ηhg带入到损失函数L(y,f(x)+w⋅η)L(y, f(x)+w\cdot \eta)L(y,f(x)+wη)表达式中,得到结果如下:
L(y,f(x)+w⋅η)≈L(y,f(x))−gη⋅h⋅ηg+12(−gη⋅h)2⋅η2hL(y, f(x)+w\cdot \eta) \approx L(y, f(x)) -\frac{g}{\eta \cdot h}\cdot \eta g + \frac{1}{2}{(-\frac{g}{\eta \cdot h})}^2\cdot \eta^2 hL(y,f(x)+wη)L(y,f(x))ηhgηg+21(ηhg)2η2h
计算得到L(y,f(x)+w⋅η)≈L(y,f(x))−g2h+12g2h=L(y,f(x))−12g2hL(y, f(x)+w\cdot \eta) \approx L(y, f(x)) -\frac{g^2}{h} + \frac{1}{2}\frac{g^2}{h}=L(y, f(x)) -\frac{1}{2}\frac{g^2}{h}L(y,f(x)+wη)L(y,f(x))hg2+21hg2=L(y,f(x))21hg2

而此时,我们仍然希望损失函数取值足够小,即希望L(y,f(x))L(y, f(x))L(y,f(x))12g2h\frac{1}{2}\frac{g^2}{h}21hg2足够接近,换而言之,我们希望12g2h\frac{1}{2}\frac{g^2}{h}21hg2越大越好。而在XGB中,g2h+λ\frac{g^2}{h+\lambda}h+λg2也被称作结构分数,其中λ\lambdaλ是L2范数。

注意,这里的12g2h\frac{1}{2}\frac{g^2}{h}21hg2是无法超过L(y,f(x))L(y, f(x))L(y,f(x))的,这个是由函数本身性质决定。

  据此,我们就能得到分裂增益的计算公式了,即希望每次分裂都能够让子节点尽可能的获得一个更大的g2h\frac{g^2}{h}hg2值,即获得一个尽可能大的结构分数。因此XGB分裂增益的计算公式子节点的结构分数减去父节点的结构分数,即为:GainXGB=GL2HL+λ+GR2HR+λ−(GL+GR)2HL+HR+λ−γGain_{XGB} = \frac{G_L^2}{H_L + \lambda} + \frac{G_R^2}{H_R + \lambda} - \frac{(G_L + G_R)^2}{H_L + H_R + \lambda} - \gamma GainXGB=HL+λGL2+HR+λGR2HL+HR+λ(GL+GR)2γ其中 GLG_LGLGRG_RGR 分别表示左子树和右子树的梯度之和,HLH_LHLHRH_RHR 分别表示左子树和右子树的二阶导数之和,λ\lambdaλ 是 L2 正则化参数,γ\gammaγ 是用于调整树的复杂度的参数。

  同时,这里不难看出,分裂增益其实是一种局部最优算法,即希望每次分裂的时候都能够最大程度降低损失函数取值,但局部最优不一定会导致全域最优,因此从更严谨的角度来说,我们其实不能直接说是为了追求损失函数下降最快而设计的分裂增益计算公式。这其实也是XGB算法留给后人有待进一步提高的一个点。

  • LGBM的分裂增益计算公式

  不过由此,我们也不难发现,分裂增益的计算公式其实是和叶节点的预测结果直接挂钩的,同样也和伪残差的计算公式没有关系。而对于LGBM来说,由于采用了和XGB相同的叶节点预测结果计算公式,因此LGBM的决策树分裂增益计算公式和XGB的类似,基本计算公式如下:
GainLGBM=GL2HL+λ+GR2HR+λ−(GL+GR)2HL+HR+λGain_{LGBM} = \frac{G_L^2}{H_L + \lambda} + \frac{G_R^2}{H_R + \lambda} - \frac{(G_L + G_R)^2}{H_L + H_R + \lambda} GainLGBM=HL+λGL2+HR+λGR2HL+HR+λ(GL+GR)2同样的,GLG_LGLGRG_RGR 分别表示左子树和右子树的梯度之和,HLH_LHLHRH_RHR 分别表示左子树和右子树的二阶导数之和,λ\lambdaλ表示L2正则化项。而有所不同的是LGBM的分裂增益计算公式中并没有γ\gammaγ项,并且在LGBM中也并没有结构分数这一概念,只是简单的给出了叶节点预测计算公式和分裂增益计算公式。
  而为何LGBM中没有γ\gammaγ,其实也是因为LGBM的决策树本身生长方式和XGB有很大的不同,正如此前介绍的,LGBM中的决策树是叶节点优先的生长策略(Leaf-wise growth),而XGB中的决策树则是深度优先的生长策略(Level-wise growth),因此LGBM的决策树分裂过程会比XGB的决策树更加敏感,此时如果加入γ\gammaγ,可能会造成模型本身欠拟合,因此LGBM中并没有引入γ\gammaγ这一概念。而其他方面,则和XGB的分裂增益计算公式没有任何区别。

  • 带有L1正则化项的分裂增益计算公式
      上述分裂增益计算公式是官方说明文档和原始论文中推导得到的计算方法,除此之外,我们在Ch.2中还提出了另一种分裂增益计算公式,即GainL1=GL2HL+λ+GR2HR+λ−(GL+GR)2HL+HR+λ−α⋅(∣wL∣+∣wR∣)Gain_{L1} = \frac{G_L^2}{H_L + \lambda} + \frac{G_R^2}{H_R + \lambda} - \frac{(G_L + G_R)^2}{H_L + H_R + \lambda} - \alpha \cdot (|w_L|+|w_R|)GainL1=HL+λGL2+HR+λGR2HL+HR+λ(GL+GR)2α(wL+wR)这是源码实现过程中分裂增益的计算过程,不难发现,二者区别就在于是否添加了叶节点权重的L1正则化项。而需要注意的是,GainL1Gain_{L1}GainL1GainLGBMGain_{LGBM}GainLGBM本质上并没有区别,这是当LGBM损失函数中包含L1正则项的时候,将L1正则化在损失函数中发挥作用的方式移植到分裂增益计算过程中的具体体现,是一种近似的计算过程。换而言之,就是将损失函数中的L1正则化的计算过程通过(一定程度的)等价转化关系,放到分裂增益中来进行计算。
  • 加入GOSS过程后的分裂增益计算公式

  此外,需要注意的是,如果是进行了GOSS抽样,则需要在进行梯度和Hessian值计算时分别计算大梯度样本和小梯度样本的梯度和及Hessian和,然后再令小样本的梯度和及Hessian和乘以膨胀系数、并于大样本对应结果进行相加,得到最终数据集的梯度和及Hessian和。

  • LGBM原论文中的分裂增益计算公式

  而关于LGBM中决策树的分裂的增益计算公式还有一点需要说明的是,在LightGBM: A Highly Efficient Gradient Boosting Decision Tree (2017)原论文中,简单介绍过LGBM的分裂计算公式,即:

1679823029118

而当GOSS抽样时,分裂增益计算过程如下:

1681028026640

其中nnn为父节点的样本总数,AAABBB则表示大梯度样本和小梯度样本,nln_lnlnrn_rnr则表示左子节点、右子节点的样本数,gig_igi表示第iii条样本的梯度,而1−ab\frac{1-a}{b}b1a则表示膨胀系数,不难看出,这里的a就是大梯度样本占比,也就是top_rate,而b则是剩余小梯度样本的抽样比例,也就是other_rate。
  在这套公式中,并没有明确的“子节点某指标减去父节点某指标”的过程,而只是计算分裂后整体的子节点梯度平方的加权和。并且根据原论文的描述,3.1公式其实就是GBDT的分裂增益计算过程,而下面的公式(1)则是LGBM在进行GOSS抽样时,在GBDT分裂增益计算公式基础上提出的改进策略。但根据官网说明文档的补充说明,这其实只是在某些特定损失函数选取情况下,LGBM的分裂增益才会等价于上述计算公式,更为一般的分裂增益计算公式为此前介绍的GainLGBMGain_{LGBM}GainLGBM计算过程:$$Gain_{LGBM} = \frac{G_L^2}{H_L + \lambda} + \frac{G_R^2}{H_R + \lambda} - \frac{(G_L + G_R)^2}{H_L + H_R + \lambda}

不得不说,对于LGBM算法学习者来说,很多碎片化的信息和“前后矛盾”的信息,因此想要系统的进行自学的话难度会非常大。而LGBM原论文更是通篇只有三行数学公式(除了上面两个,还有一个是用于计算GOSS过程带来的信息损失),这让我们有理由怀疑,或许这些碎片的信息和“前后矛盾”的信息设计,是出于某种“保密性”需求。相比之下,XGB和CatBoost的论文则“完整”很多。

4.2 LightGBM完整建模流程的数学推导

  在梳理了LGBM算法原理的核心数学环节之后,让我们结合此前两个小节介绍的LGBM数据处理方法,来进行LGBM的完整建模流程的数学推导。类似的,这里我们同样借助Lesson 13相同的数学符号进行表示。假设现有数据集NNN,含有形如(xi,yi)(x_i,y_i)(xi,yi)的样本MMM个,iii为任意样本的编号,单一样本的损失函数为l(yi,H(xi))l(y_i,H(x_i))l(yi,H(xi)),其中H(xi)H(x_i)H(xi)iii号样本在集成算法上的预测结果,整个算法的损失函数为L(y,H(x))L(y,H(x))L(y,H(x)),且总损失等于全部样本的损失之和:L(y,H(x))=∑il(yi,H(xi))L(y,H(x)) = \sum_i l(y_i,H(x_i))L(y,H(x))=il(yi,H(xi))。目标函数中使用L2正则化(λ\lambdaλ为0,α\alphaα为0)。同时,弱评估器为fff,总共学习KKK轮。

(1) 初始化过程

  首先是正是开始训练之前的模型初始化过程,本阶段需要确定损失函数和正是开始迭代之前的初始预测值。和其他GBDT框架算法类似,LGBM同样也支持多种类型的损失函数,甚至可以自定义损失函数,并且,在不同损失函数选择情况下模型初始预测值是不同的。当然,无论是哪种初始值设置,其目的都是为了让损失函数计算结果尽可能的小。即满足如下计算公式:
H0(x)=argminC∑i=1Ml(yi,C)=argminCL(y,C)\begin{aligned} H_0(x) &= \mathop{argmin}_{C} \sum_{i=1}^M l(y_i,C)\\ \\ &= \mathop{argmin}_{C} L(y,C) \end{aligned} H0(x)=argminCi=1Ml(yi,C)=argminCL(y,C)
其中yiy_iyi为真实标签,CCC为任意常数。以上式子表示,找出令∑i=1Ml(yi,C)\sum_{i=1}^Ml(y_i,C)i=1Ml(yi,C)最小的常数CCC值,并输出最小的∑i=1Ml(yi,C)\sum_{i=1}^Ml(y_i,C)i=1Ml(yi,C)作为H0(x)H_0(x)H0(x)的值。这里列举几种比较典型的LGBM支持的损失函数,及其初始值计算公式:

  • 均方误差(MSE):适用于回归问题。初始值是训练数据集目标值的均值,即y_init = np.mean(y_train);

  • 二分类对数损失(Binary Log Loss):适用于二分类问题。初始值是训练数据集中正负样本比例的对数几率,即y_init = log(∑(y_train1) / ∑(y_train0));

  • 多分类对数损失(Multiclass Log Loss):适用于多分类问题。初始值是训练数据集中每个类别的对数几率,即y_init[k] = log(∑(y_train==k) / ∑(y_train!=k)),其中k表示类别;

  • 二分类交叉熵损失(Binary Cross Entropy):适用于二分类问题。初始值与二分类对数损失相同;

  • Poisson损失:适用于计数回归问题(预测值为非负整且服从泊松分布)。初始值是训练数据集目标值的对数均值,即y_init = log(np.mean(y_train));

  • Gamma损失:适用于正值回归问题(例如持续的时间或距离计算问题)。初始值是训练数据集目标值的均值除以其方差的对数,即y_init = log(np.mean(y_train) / np.var(y_train));

  • Tweedie损失:适用非负值回归问题。初始值取决于Tweedie分布的指数参数p。当p接近0时,初始值类似于Poisson损失,当p接近2时,初始值类似于Gamma损失。

(2) 数据压缩过程

  在根据所选择的损失函数得到初始全部数据的预测结果后,接下来就需要来进行数据压缩,也就是Ch.1中介绍的连续变量分箱和EFB降维。分箱的个数和降维的程度,其中,分箱个数可以通过max_bin超参数来进行控制,而降维的程度则可以通过max_conflict_rate超参数来进行控制,并且,max_bin取值越小、max_conflict_rate取值越大,数据压缩就越严重,模型训练速度就更快、模型精度就更低,反之如果max_bin取值越大、max_conflict_rate取值越小,则模型训练速度将有所下降,但模型精度会提高。
  总之这个阶段是围绕全部的样本进行数据压缩,并且会保留压缩过程中的核心信息,如分箱的边界、特征捆绑时的offset,用于处理后续新数据集的特征。
  当完成数据集压缩后,接下来建模的过程只会带入压缩后的数据,原始数据将被舍弃。

(3) Boosting迭代过程

  接下来进入到Boosting的迭代过程,即单独每颗决策树的训练过程。这里我们假设总共迭代K次,本次迭代过程为第k次,其中k取值范围为[1, k],本次迭代过程中LGBM将按照如下步骤进行迭代计算:

  • Step 1.GOSS抽样
      在构建每颗树之前,LGBM将按照大小梯度样本划分情况进行GOSS抽样,具体抽样比例受top_rate和other_rate影响,两个超参数取值越大、抽样得到的样本数量越多,反之抽样得到的样本数量就越少。这里我们同样假设第k次迭代时,抽取的数据集为NkN^kNk,同时计算得到膨胀系数为1−top_rateother_rate\frac{1-top\_rate}{other\_rate}other_rate1top_rate

  • Step 2.计算伪残差
      在得到了上一轮预测结果Ht−1(x)H_{t-1}(x)Ht1(x)和GOSS抽样数据的基础上,我们就可以进行本轮迭代的伪残差计算,伪残差是实际每轮建树时的拟合对象,LGBM的伪残差和GBDT的伪残差完全一样,就是当前样本的负梯度,其中,样本xix_ixi在这一轮迭代时的伪残差并按照如下公式进行计算:
    rki=−∂L(yi,Hk−1(xi))∂Hk−1(xi)r_{ki} = -\frac{\partial L(y_i, H_{k-1}(x_i))}{\partial H_{k-1}(x_i)}rki=Hk1(xi)L(yi,Hk1(xi))
    其中L(yi,Hk−1(xi))L(y_i, H_{k-1}(x_i))L(yi,Hk1(xi))表示xix_ixi在本轮计算时的损失函数,而Hk−1(xi)H_{k-1}(x_i)Hk1(xi)则表示样本xix_ixi上一轮的预测结果;

  • Step 3.拟合伪残差
      接下来尝试训练一颗决策树来拟合当前样本的伪残差。本阶段LGBM将采用叶节点优先的决策树生长策略,并采用直方图优化加速计算过程。决策树具体生长过程的分裂增益为:Gain=(∑i∈Lgi)2∑i∈Lhi+λ+(∑i∈Rgi)2∑i∈Rhi+λ−(∑i∈Pgi)2∑i∈Phi+λGain = \frac{(\sum_{i \in L}g_i)^2}{\sum_{i \in L}h_i + \lambda} + \frac{(\sum_{i \in R}g_i)^2}{\sum_{i \in R}h_i + \lambda} - \frac{(\sum_{i \in P}g_i)^2}{\sum_{i \in P}h_i + \lambda} Gain=iLhi+λ(iLgi)2+iRhi+λ(iRgi)2iPhi+λ(iPgi)2需要注意的是,尽管LGBM拟合的伪残差只有损失函数一阶导,但分裂增益却同时包含损失函数的一、二阶导数。而根本原因在于分裂增益的计算公式由叶节点预测结果决定,而叶节点预测结果则可以由损失函数直接推导得到,跟伪残差没有直接关系。

  • Step 4.输出预测结果
      最后则是输出本轮决策树的预测结果,对任意叶子节点jjj来说,输出值为wj=−∑i∈jgik∑i∈jhik+λw_j = -\frac{\sum_{i \in j}g_{ik}}{\sum_{i \in j}h_{ik} + \lambda}wj=ijhik+λijgik假设样本iii被分割到叶子jjj上,则有:fk(xi)=wjf_k(x_i) = w_jfk(xi)=wj对于LGBM来说,叶节点预测结果和XGB完全一致。

  • Step 5.更新损失函数计算结果
      最后,则是根据本轮计算结果,更新损失函数计算结果,即根据预测结果fk(xi)f_k(x_i)fk(xi)迭代模型,具体来说:


    Hk(xi)=Hk−1(xi)+fk(xi)H_k(x_i) = H_{k-1}(x_i) + f_k(x_i)Hk(xi)=Hk1(xi)+fk(xi)

    假设输入的步长为η\etaη,则Hk(x)H_k(x)Hk(x)应该为:


    Hk(xi)=Hk−1(xi)+ηfk(xi)H_k(x_i) = H_{k-1}(x_i) + \eta f_k(x_i)Hk(xi)=Hk1(xi)+ηfk(xi)

    对整个算法则有:


    Hk(x)=Hk−1(x)+ηfk(x)H_k(x) = H_{k-1}(x) + \eta f_k(x)Hk(x)=Hk1(x)+ηfk(x)

(4) Boosting迭代停止

  当执行完K轮迭代后,最终输出HK(x)H_K(x)HK(x)的值作为集成模型的最终预测结果。至此,便完成了模型整体训练过程。
  至此,我们就完成了LGBM完整建模流程的数学推导。当然,就像开篇所言,对于LGBM的数学原理方面的学习,重点不在于复杂公式的推导,而在于一些关键数学过程的更深层次理解,LGBM开创性的对不同算法的各关键环节进行“启发式”的组合,并且在一系列数据压缩和抽样方法配合下,达到了高效同时精准的建模水准。当然,至此我们也完成了全部的LGBM基础理论方面的学习,从下一小节开始,我们将进入到LGBM的具体实践环节的学习中,并在实战过程中感受LGBM算法的强大实例。

5 LightGBM sklearn API实现与进阶

5.1 LightGBM算法的快速使用

  在了解了LGBM基本原理和性能特性后,接下来让我们尝试快速使用先尝试着快速使用LGBM算法,并在实际使用过程中,逐步总结LGBM的计算效率和算法性能上的特性。

(1) LGBM算法库安装

  首次使用LGBM算法前,需要进行LGBM算法库的安装,这里推荐直接使用pip进行安装,在命令行中输入如下命令进行LGBM算法库的安装:

pip install lightgbm -i https://pypi.tuna.tsinghua.edu.cn/simple

(2)LGBM的sklearn API快速使用

  接下来我们尝试训练LGBM模型。对于LGBM来说,支持多种不同类型的数据输入以及多种不同类型的训练方式,这里我们先从最简单的应用情况开始介绍,即围绕CSV格式数据进行DATaFrame数据格式读取,并采用类sklearn的建模风格进行模型的训练。这里我们先采用鸢尾花数据集进行简单模型测试,数据导入和数据集划分如下:

import lightgbm as lgb# 读取数据
iris = load_iris()
data = pd.DataFrame(data= np.c_[iris['data'], iris['target']], columns= iris['feature_names'] + ['target'])# 划分数据集
X_train, X_test, y_train, y_test = train_test_split(data.drop("target", axis=1), data["target"], test_size=0.2)gbm = lgb.LGBMClassifier()
# 训练模型
gbm.fit(X_train, y_train)
gbm.predict(X_test)
gbm.predict_proba(X_test)
np.argmax(gbm.predict_proba(X_test), 1)accuracy_score(y_train, gbm.predict(X_train)), accuracy_score(y_test, gbm.predict(X_test))

至此,我们就完成了一次简单的LGBM算法的sklearn API调用。

5.2 LightGBM sklearn API超参数解释与使用方法

  在LGBM的sklearn API中,总共包含四个模型类(也就是四个评估器),分别是lightgbm.LGBMModel、LGBMClassifier 和 LGBMRegressor 以及LGBMRanker:

1679126801449

其中LGBMModel是 LightGBM 的基本模型类,它是一个泛型模型类,可以用于各种类型的问题(如分类、回归等)。通常,我们不直接使用 LGBMModel,而是使用针对特定任务的子类使用不同的类,即分类问题使用 LGBMClassifier 、回归问题使用 LGBMRegressor,而排序问题则使用LGBMRanker。接下来我们重点解释分类和回归模型类的超参数。

(1)LGBMClassifier超参数概览

  和sklearn中其他评估器命名规则一致,Classifier是用于分类问题的评估器,Regressor是用于回归问题的评估器,两个评估器的超参数构成类似,这里我们先看LGBMClassifier的超参数构成:

总的来看,我们可以将LGBMClassifier的超参数分为决策树剪枝超参数、Boosting过程控制超参数、特征和数据处理类超参数和其他超参数四类。其中决策树剪枝超参数包括max_depth(树的最大深度)、num_leaves(叶子节点数)、min_child_samples(子节点的最小样本数)等,都是大家非常熟悉的超参数;Boosting过程控制超参数和XGB的booster类似,包含boosting_type(提升类型,如gbdt、dart、goss、rf),n_estimators(迭代次数)、learning_rate(学习率)和reg_alpha(L1正则化系数)、reg_lambda(L2正则化系数);而特征和数据处理类超参数则主要与数据采样有关,包含subsample(样本子集的比例)、subsample_freq(进行子采样的频率)、colsample_bytree(列采样的比例)等;而其他参数则是一些对建模效果并没有重要影响的超参数,例如random_state(随机数种子)、n_jobs(并行计算所用的CPU核心数)、class_weight(类别权重)等。

(2)LGBMClassifier的决策树剪枝超参数

NameDescription
num_leaves一棵树上的叶子节点数,默认为 31
max_depth树的最大深度,默认值为 -1,表示无限制
min_split_gain相当于min_impurity_decrease,再分裂所需最小增益。默认值为 0,表示无限制
min_child_weight子节点的最小权重和。默认值为 1e-3。较大的 min_child_weight 可以防止过拟合
min_child_samples相当于min_samples_leaf,单个叶子节点上的最小样本数量。默认值为 20。较大的 min_child_samples 可以防止过拟合

能够发现,相比决策树或者随机森林,LGBM的决策树剪枝类参数有所精简,保留了最能影响决策树生长的核心超参数。

(3)LGBMClassifier的Boosting过程控制超参数解释

  然后来看更加重要的Boosting过程控制超参数,首先较为复杂的是boosting_type,该超参数解释如下:

  • boosting_type:
    使用的梯度提升算法类型,默认为 ‘gbdt’,可选项包括 ‘gbdt’(梯度提升决策树)、‘dart’(Dropouts meet Multiple Additive Regression Trees)、‘goss’(Gradient-based One-Side Sampling)和 ‘rf’(Random Forest,随机森林)。其中GBDT是最常用、且性能最稳定的 boosting 类型,也是boosting_type默认取值;而dart (Dropouts meet Multiple Additive Regression Trees)则是一种结合了 Dropout 和多重加性回归树的方法。它在每次迭代过程中随机选择一部分树进行更新,会较大程度增加模型随机性,可以用于存在较多噪声的数据集或者数据集相对简单(需要减少过拟合风险)的场景中;GOSS 是一种基于梯度的单侧采样方法。它在每次迭代中只使用具有较大梯度的样本进行训练,从而降低计算复杂度。goss(Gradient-based One-Side Sampling)则是前文介绍的梯度的单边采样算法,可以在保持较高精度的同时加速训练过程,适用于大规模数据集,可以在保持较高精度的同时加速训练过程,有些时候精度不如GBDT;而rf则是采用随机森林来进行“Boosting过程”,或者说此时就不再是Boosting,而是替换成了Bagging过程,类似于XGBRFClassifier的计算过程,此时LGBM本质上将按照RF的计算范式进行计算。

然后与之相关的是subsample_for_bin参数:

  • subsample_for_bin:
    该参数表示对连续变量进行分箱时(直方图优化过程)抽取样本的个数,默认取值为200000,当实际抽样个数大于输入训练数据样本数量时,会带入全部样本进行计算。而如果boosting_type选择的是goss,则在直方图优化时会自动完成抽样,具体抽样策略是:会保留所有较小梯度的样本(即那些已经被模型很好拟合的样本),并对较大梯度的样本进行采样。这种策略能够在加速训练(大梯度样本的贡献)的同时有效防止过拟合(小梯度样本的贡献)。因此,如果boosting_type选择的是 “goss”,。则subsample_for_bin参数会失去作用,此时无论subsample_for_bin取值多少都不影响最终结果。

如果需要控制goss过程,则需要借助top_rate 和 other_rate 这两个参数,但是这两个参数只存在于LGBM原生API中,在sklearn中并没有,因此在使用 LightGBM 的 sklearn API 时,GOSS 采样方法会自动进行调整。关于这两个参数的解释和使用,我们将在介绍LGBM原生API时讲解。

其他参数和GBDT及XGB类似,具体解释如下:

NameDescription
learning_rate学习率,即每次迭代中梯度提升的步长,默认值为 0.1
n_estimators迭代次数,即生成的基学习器的数量,默认值为 100
reg_alphaL1 正则化系数,默认值为 0
reg_lambdaL2 正则化系数。默认值为 0

(4)LGBMClassifier的特征和数据处理类超参数

  LGBM中特征和数据处理类超参数主要用于每次迭代时数据和特征的分配,核心作用是控制模型训练过程的随机性,其超参数的基本解释如下:

NameDescription
subsample模型训练时抽取的样本数量,取值范围为 (0, 1],表示抽样比例,默认为1.0
subsample_freq抽样频率,表示每隔几轮进行一次抽样,默认取值为0,表示不进行随机抽样
colsample_bytree在每次迭代(树的构建)时,随机选择特征的比例,取值范围为 (0, 1],默认为1.0

这里有以下几点需要注意:

  • subsample和subsample_for_bin之间的关系

  这两个参数尽管从字面理解都是抽样比例,但实际上这两个参数是完全独立的,彼此之间并不影响。其中subsample_for_bin抽样结果用于直方图构建,而subsample抽样结果则是用于模型训练,这两个抽样过程彼此独立,互不影响;

  • subsample和subsample_freq之间的关系

  更加关键的是subsample_freq参数,如果subsample_freq=0,则无论subsample取值为多少,模型训练时都不会进行随机抽样;换而言之,只有subsample_freq不等于0,且subsample不等于1.0时,才会进行抽样;

  • subsample_freq和colsample_bytree之间的关系

  不同于subsample是样本抽样,colsample_bytree是每次迭代(每次构建一颗树时)进行的特征抽样,并且colsample_bytree不受subsample_freq影响,即无论subsample_freq取值为多少,每次建树时都会根据colsample_bytree设置的比例进行列抽样。

  • LGBM和RF的不同特征抽样(分配)规则

  同时需要注意的是,LGBM和随机森林不同,随机森林是每棵树的每次分裂时都随机分配特征,而LGBM是每次构建一颗树时随机分配一个特征子集,这颗树在成长过程中每次分裂都是依据这个特征子集进行生长。

(5)LGBMClassifier的其他超参数

  最后则是一些用于辅助建模的超参数,具体超参数及解释如下:

NameDescription
objective指定目标函数,默认为None,会自动判断是二分类还是多分类问题,这里我们也可以手动设置 ‘binary’(用于二分类问题)或’multiclass’(用于多分类问题)
class_weight样本权重设置参数
importance_type特征重要性计算方式,默认为 ‘split’,表示使用特征在模型中被选中作为分裂特征的次数,可选 ‘gain’,表示使用特征在模型中的分裂收益之和作为重要性评估指标
random_state随机数种子
n_jobs并行的线程数,默认为-1,调用全部可用线程
silent是否沉默(不输出日志),默认为’warn’,仅显示警告和报错,可选’info’,用于打印全部信息

  首先是关于目标函数的设置,对于LGBM的sklearn API来说,objective参数较为简单,只有’binary’和’multiclass’两种,其中’binary’表示逻辑回归损失,也就是二分类交叉熵损失,而’multiclass’则代表多分类交叉熵损失。

当然,LGBM也支持softmax损失,只不过无法通过objective传入。softmax损失默认是只有原生API才支持调用,若需要传入sklearn API中,则需要借助**kwargs参数,以原生API形式进行传输。原生API的调用方法我们将在下一小节进行探讨。

  此外,需要注意用于计算特征重要性的importance_type参数,这也是LGBM算法的特点之一——即提供了两种不同的用于评估特征重要性的方法。很多时候我们在特征工程阶段都会创建海量特征来增强数据表现,并且通过一些数值指标或者模型来进行特征筛选,进而兼顾建模效率和预测效果。考虑到LGBM同时拥有非常高的计算效率,这使得LGBM算法成为筛选特征的最常用的模型之一。

具体特征衍生方法,我们将在特征工程阶段进行详细介绍。

  至此,我们就完整解释了LGBMClassifier的全部超参数。接下来我们继续讨论LGBMRegressor超参数。LGBMRegressor和LGBMClassifier只有两点不同,其一是LGBMRegressor没有class_weight参数,其二则是LGBMRegressor的损失函数和LGBMClassifier完全不同。接下来我们就LGBMRegressor的损失函数选取进行解释。

(6)LGBMRegressor损失函数

LGBMRegressor的损失函数包含了GBDT和XGB的各类回归类损失函数,相关计算过程及应用场景此前都有详细讨论,这里只进行简要说明:

  • 均方误差(MSE, Mean Squared Error):最常用的的损失函数,此时objective=‘regression’ 或 objective=‘regression_l2’;
  • 平均绝对误差(MAE, Mean Absolute Error):通常用于标签存在异常值情况,此时objective=‘regression_l1’;
  • Huber损失(Huber Loss):适用于目标值存在大量异常值或者噪声时。Huber损失在预测误差较小时表现为均方误差,在预测误差较大时表现为平均绝对误差,这使得它对异常值具有更好的鲁棒性。此时objective=‘quantile’;
  • Quantile损失(Quantile Loss):用于分位数回归,最小化预测值与真实值之间的分位数损失,适用于需要对预测分布进行精细控制的场景(例如围绕某种分布进行预测)。此时objective=‘quantile’;
  • Poisson损失(Poisson Loss): 适用于计数问题,即目标值是非负整数且服从泊松分布。此时objective=‘poisson’
  • Gamma损失(Gamma Loss):适用于预测非负实数且服从伽马分布的目标值。此时objective=‘gamma’
  • Tweedie损失(Tweedie Loss):适用于广义线性模型(Generalized Linear Models, GLMs)中的 Tweedie 分布(非对称分布)的数据集。此时objective=‘tweedie’。

  能够发现,大多数损失函数都是针对标签存在某个具体分布时进行的预测,一般情况下我们都是在均方误差(MSE)、平均绝对误差(MAE)和Huber损失中进行选择,通常情况首选MSE,而当标签存在噪声或者异常点时,MAE会表现出更好的泛化能力。并且由于MAE较为特殊的计算过程,导致其在正常情况数据集下精度不如MSE。而Huber则是二者的综合,适用于标签存在少量异常值的数据集,Huber对异常值较为鲁棒,同时又可以保留较好的精度。不过需要注意的是,如果

6 LightGBM原生API使用与进阶

6.1 LGBM的原生API调用

  当然,除了sklearn API调用外,我们还可以使用LGBM原生API进行模型训练。LGBM的原生API调用和XGB的原生API调用过程非常类似,一个最简单的流程如下::

  • Step1.Data Interface:借助.Dataset方式进行数据集封装;
  • Step2.Setting Parameters:创建超参数字典,用于向模型传输超参数。若完全使用默认参数,则可设置空的字典作作为超参数列表对象;
  • Step3.Training:通过.train的方式进行模型训练。
首先是数据集的创建:
# 数据集创建
train_data = lgb.Dataset(X_train, label=y_train)然后创建超参数字典。
# 设置超参数字典
param = {}
不过这里因为鸢尾花数据是多分类问题,LGBM模型在默认情况下是回归类模型,因此需要通过超参数字典传输建模类型,即objective超参数取值为multiclass(目标为解决多分类问题),同时设置num_class取值为3,即3个类别的多分类问题。
param['objective'] = ['multiclass']
param['num_class'] = [3]然后进行训练:
bst = lgb.train(param, train_data)然后在测试集上完成预测:
bst.predict(X_test)
然后即可将概率预测结果进一步转化为类别预测结果,这里同样可以使用argmax进行计算:
bst.predict(X_test).argmax(1)

6.2 LGBM原生API进阶使用方法

  相比sklearn API,更加重要、更需要重点掌握的是原生API的使用方法。同样,由于目前我们还未介绍模型原理,因此本部分对LGBM API的讲解更多的是各功能的实现方法,如本地文件读取方法、内存管理方法和GPU加速方法,而关于建模效果层面的超参数优化,我们将介绍完算法原理后再进行介绍。

(1)Dataset API

  首先来看Data Structure API,即数据和模型构建类API,这类API的取名具备一定的迷惑性,Data Structure并不是指数据结构或者构造数据的API,而是一系列底层API,也就是关于创建数据和模型的基础类。在LGBM的API设计中,首字母大写的都是类(类似于sklearn的评估器),首字母小写的则都是函数。和sklearn类似,LGBM也存在一些可以执行相同功能的函数和类,以满足不同场景的使用需求。

1678536205508

而Data Structure API则都是基础类。各类的基本解释如下:

  • Dataset:基础数据集创建类,用于创建LGBM模型训练的数据集;
  • Booster:基础模型类,用于(实例化)生成一系列可以用于训练的模型(这个过程类似于sklearn的评估器实例化过程);
  • CVBooster:基础模型类,和Booster类似,只不过CVBooster实例化的模型支持多次(带入不同数据集)进行训练,并保存多组模型训练结果,方便手动进行交叉验证;
  • Sequence:序列数据集创建类,用于创建序列型(如时间序列或者排序序列)数据集;

这里重点介绍Dataset基础类,Booster和CVBooster将配合Training API共同来进行讲解,Sequence相关使用将在后续时间序列模块中进行讲解。

当然,我们也可以 Data Structure看成是对象类型,那么无论是什么类,其最终目的都是为了创建某种对象类型,因此无论是创建模型对象还是创建数据对象,都是创建某种对象的API,从这个角度出发,就不难理解为何创建数据集的类和创建模型的类都属于这类API。这也是官网对Data Structure名称的解释。

1.1 Dataset类的解释与使用方法

  Dataset类主要用于创建Dataset类型的数据对象,方便带入模型进行训练,这点和XGB的DMatrix类似。对于LGBM来说,借助Dataset进行数据封装,能够非常便捷的进行更多功能拓展,例如直接读取不同类型的本地文件等;并且这种特定的数据格式还能进行非常高效率的数据存储——占用较少内容就能存储大量数据,并且还可以通过标注离散字段来进行高效率的数据重编码以及辅助模型提升预测效果。不难看出,灵活使用Dataset类来进行LGBM数据集封装,能够有效提升建模效率、降低存储空间、提升模型预测效力。从功能上来总结,我们可以借助Dataset类实现以下四类效果:

  • 便捷读取和存储:不仅可以读取当前编程环境中的Numpy和Pandas对象,同时能够直接读取本地文件,并支持多种文件格式类型的读取(包括LibSVM (zero-based) / TSV / CSV format text file等),同时提供了一种LGBM原生定义的二进制文件,便于更高效的进行读取和存储;
  • 更多功能拓展:能够在读取数据文件时标注离散变量,以及对离散变量自动编码、自动设置数据集各样本权重等;
  • 有效优化内存、计算效率和模型预测准确性:相比Numpy和Pandas提供的数据格式,Dataset数据格式能够显著降低内存,同时提高LGBM算法的计算效率,根据官方文档的说明,借助Dataset进行自动离散变量编码,其计算速度是sklearn中encoder的8倍,并且LGBM能够区分离散变量和连续变量(这点sklearn无法做到),因此在Dataset中对离散变量进行标注,能够有效提升算法的预测效力(在没有手动标注离散变量的情况下,系统会自动根据某种标准把取值较少的特征标注为离散变量);
  • 支持分布式计算和GPU加速计算:除了多线程计算外,LGBM还支持分布式计算和GPU加速计算,而要实现这两种计算方法,则必须提供Dataset类型数据;
      接下来我们就Dataset类的使用方法进行进一步探讨。
1.2 更便捷的读取和存储

  首先,Dataset和sklearn类似,都支持NumPy Arrays、Pandans DataFrame和SciPy sparse matrix(稀疏矩阵)对象类型的读取。例如对于之前的房价数据集,我们可以先读取为Dataframe类型,然后再转化为Dataset类型:

data = pd.read_csv("train_encode.csv",index_col=0)
train_data = lgb.Dataset(data=data.iloc[:, 1:80], label=data.iloc[:, 80:81])param = {}
bst = lgb.train(param, train_data)
1.3 借助.construct()方法进行数据集加载

  当然,有些时候训练一次模型耗费较大,此时也可以考虑使用.construct()方法进行数据加载,提前验证数据集正确性:

train_data = lgb.Dataset(data=data.iloc[:, 1:80], label=data.iloc[:, 80:81], free_raw_data=False)train_data.construct()
train_data.get_data()
1.4 Dataset类读取本地文件类型

  而在工业实践中,更为通用的做法是特征工程阶段和模型训练阶段相对独立,在执行完特征工程后,将这些已经处理好的特征进行本地文件保存,然后使用Dataset对本地文件直接进行读取。Dataset类可以直接读取本地LibSVM (zero-based) / TSV / CSV等文本格式文件。其中LibSVM (zero-based)是LibSVM(是一种用于支持向量机SVM训练的软件包)的最常用数据格式,这种文本文件格式使用空格或制表符分隔特征和标签,并使用稀疏表示法来存储特征值,可以有效地压缩数据,并且可以减少内存使用。在LibSVM格式中,每行代表一个样本,第一个数字是该样本的标签,接下来是一系列特征值,每个特征值都由一个特征索引和特征值组成,中间使用空格或制表符分隔,在"zero-based"版本中,特征索引从0开始。在稀疏表示法中,只有非零特征值才被列出,其余特征值假定为0。
  而TSV和我们熟悉的CSV文件区别就在于分隔符的不同,CSV格式文件是用逗号进行分割,而TSV则是用制表符(\t)进行分割。例如,对于House Price数据集,我们可以直接在本地进行读取:

train_data = lgb.Dataset('train_encode.csv')
type(train_data)
1.5 LGBM高效数据保存和读取流程

  在熟悉了一系列LGBM在保存和读取数据相关过程后,我们来总结一套通用的、面向LGBM的高效数据保存和读取流程,需要注意的是,更少的内存占用、更高效的数据读取,也是LGBM的最核心特点之一,也是各算法工作人员的必修功课。

  • Step 1.将本地文件读取为Panda数据类型,并进行数据清洗、特征工程等操作;
  • Step 2.分别提取训练集和测试集的特征和标签,并分别进行本地存储,避免出现数据丢失等情况;
  • Step 3.创建Dataset类型对象,并正确输入特征、标签、离散列特征名称等,非必要情况下一般建议free_raw_data=True;
  • Step 4.本地保存训练数据和测试数据的LGBM binary文件,方便下次模型训练时调用;
  • Step 5.若继续进行模型训练,则可以考虑在当前操作空间中删除原始数据文件对象,以减少内存占用率。
    具体代码实现流程如下:
# Step 1.读取数据,划分数据集,进行数据清洗等
data = pd.read_csv("train_encode.csv",index_col=0)
data.head()#Stap 2.划分训练集和测试集,并进行本地保存,这里划分方式和此前XGB课程中划分方式保一致
X_train, X_test, y_train, y_test = train_test_split(data.iloc[:, 1:80], data.iloc[:, 80], test_size=0.3, random_state=1412)
X_train.to_csv('X_train.csv')
X_test.to_csv('X_test.csv')
y_train.to_csv('y_train.csv')
y_test.to_csv('y_test.csv')# Step 3.创建Dataset类型对象
train_data = lgb.Dataset(X_train, label=y_train, categorical_feature=cate_features)
test_data = lgb.Dataset(X_test, label=y_test, categorical_feature=cate_features)# 同时进行加载,观察数据集是否正确创建
train_data.construct()
test_data.construct()# Stap 4.本地保存二进制文件,方便后续调用
# 需要提前删除此前创建的train.bin,save_binary不会覆盖之前的同名文件
train_data.save_binary('train.bin')
test_data.save_binary('test.bin')# Step 5.删除原始数据对象,清理内存
del(data)
del(X_train, y_train, X_test, y_test)
gc.collect()然后即可进行模型训练:
param = {}
bst = lgb.train(param, train_data, categorical_feature=cate_features)
1.6 Dataset类参数讲解
NameDescription
data数据集特征或者LGBM二进制文件,特征可以是当前变成环境中的Array、DataFrame、Spare对象,也可以是本地文件
label数据集标签,默认为None,None时需要以来LGBM二进制文件输入标签信息
reference参考数据集,若设置当前数据集为验证集,则需要通过此参数指明验证集背后的训练集
weight类似于sklearn中的class_weight,用于灵活的设置每个样本的权重
group设置样本所属组,在排序问题中需要使用
init_score初始化每个样本的得分,同样是在排序问题中使用
feature_name特征的列名称,默认是aotu,会使用DataFrame传入的colums作为列名称
categorical_feature离散列的列名称,默认是aotu,会将DataFrame传入的Object列视作离散变量
params训练过程的相关参数,可以在Dataset过程中提前指定
free_raw_data是否释放原始数据
http://www.lryc.cn/news/586546.html

相关文章:

  • Waiting for server response 和 Content Download
  • 【离线数仓项目】——电商域DWS层开发实战
  • BugBug.io 使用全流程(202507)
  • 计算机毕业设计Java停车场管理系统 基于Java的智能停车场管理系统开发 Java语言实现的停车场综合管理平台
  • STM32中的RTC(实时时钟)详解
  • 《Spring 中上下文传递的那些事儿》Part 8:构建统一上下文框架设计与实现(实战篇)
  • 利用docker部署前后端分离项目
  • 【攻防实战】记一次DC2攻防实战
  • 电网失真下单相锁相环存在的问题
  • CANoe实操学习车载测试课程、独立完成CAN信号测试
  • Spring Boot整合MyBatis+MySQL+Redis单表CRUD教程
  • 前端面试宝典---项目难点2-智能问答对话框采用虚拟列表动态渲染可视区域元素(10万+条数据)
  • 快速排序递归和非递归方法的简单介绍
  • Armstrong 公理系统深度解析
  • 人机协作系列(三)个体创业者的“新物种革命”
  • Agent任务规划
  • 分布式系统高可用性设计 - 缓存策略与数据同步机制
  • PostgreSQL安装及简单应用
  • 后端定时过期方案选型
  • python-for循环
  • linux 系统找出磁盘IO占用元凶 —— 筑梦之路
  • 工业软件出海的ERP-PLM-MES一体化解决方案
  • PostgreSQL HOT (Heap Only Tuple) 更新机制详解
  • Socket到底是什么(简单来说)
  • batchnorm类
  • 【Docker基础】Dockerfile指令速览:基础常用指令详解
  • 【PTA数据结构 | C语言版】车厢重排
  • 火山引擎:字节跳动的技术赋能初解
  • 【学习新知识】用 Clang 提取函数体 + 构建代码知识库 + AI 问答系统
  • 商业智能(BI)系统深度解析