推荐算法与量化交易-A-5-5:XGBoost-lightGBM“集成提升树模型”算法-基于模型算法
Posted 推荐算法与量化交易
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了推荐算法与量化交易-A-5-5:XGBoost-lightGBM“集成提升树模型”算法-基于模型算法相关的知识,希望对你有一定的参考价值。
推荐系统历史文章回顾:
本节内容:
一,XGBoost算法:
1,XGBoost回归算法
2,XGBoost分类算法
3,XGboost优点与缺点
二,lightGBM算法
1,引出lightGBM
2,lightGBM实施原理
3,lightGBM细化方向
三,CatBoost算法
http://learningsys.org/nips17/assets/papers/paper_11.pdf
四,GBDT,XGBoost,lightGBM算法比较
一,XGBoost算法:
上一章介绍了“基于树集成的提升算法—GBDT算法”,虽然是GBDT算法基于CART树,然后又是boosting算法思路,但是boosting算法始终是一个“串行计算”。针对于实际工业界的海量数据,其实有个问题,就是处理,计算,运行速度会受到限制,达不到想要的一个快速计算的需求。那么呢,在前面一些列文章提出了,加快训练速度和减少人工的特征工程是目前处理的主流的机器学习思路,那么针对于“加快训练速度”这个方面,我们在前面主要介绍了三个方面:
①采用分布式系统并行计算,特别是以hadoop组件为主,可以运行在YARN上运行来解决。其实就是转化成多线程任务执行,并行计算处理。虽然boosting算法是串行计算(算法内部串行,属于多进程),但是如果我们可以把它优化放在YARN任务调度系统上,靠吃内存来并行计算。不仅计算迅速,造价成本也低。(关于分布式计算系统,涉及到机器学习这部分,会在C系列的分布式计算章节来介绍)
②虽然上面的介绍分布式可以加快训练速度,但是,有一个问题,就是,针对于个人,针对于一部分企业,不一定都有分布式系统,分布式系统的从0开始布置到学习施实应用,整个学习成本,学习时间会很高。所以,在算法层面还有没有办法提升成了关键。就需要在单纯的纯算法内部做优化了。我们知道,针对于这种前向传播计算,而且是梯度求解参数的,常用的是“梯度下降求解”,在第一章介绍了求解部分介绍,具体看《》,里面介绍了,梯度求解其实可以针对于不同需求有多种方式,目前主流用的有“随机梯度下降”,“牛顿法”,“拟牛顿法”三种。好的,这里其实就是沿用了这样的思路,我们知道,在上一节介绍的GBDT算法里,我们虽然对“损失函数”做了求解,但是,是基于“一次求导”,那么我们就在这个过程里为了加快训练,选择二次求导,这样就加快了训练了。
③特征工程是最浪费时间和最浪费工作量,如果可以减少特征工程,减少特征表达这部分的工作量,那么也是可以加快进度的
上一节的介绍GBDT的过程中,我们同时介绍了GBDT正则化过拟合的三个方法:学习率,子采样,剪枝。但是,我们首先要明确,GBDT算法是基于boosting的思维框架采用CART树的计算准则进行的计算,我们上一节介绍的GBDT正则化,其实更多的是在boosting的思维框架上面在做正则化(子采样和学习率),而没有对CART树的层面思考正则化的优化(剪枝),那么其实我们针对于CART树环节也可以应用参数限制过拟合的(后剪枝的参数化处理)。
那么,我们就能够知道,其实还是基于GBDT算法,想加快训练速度和更深层次的解决过拟合问题,可以有上面的思路可以实施,那下面,就介绍GBDT的升级算法——XGBoost算法。XGBoost算法既然是相对于GBDT做的优化更改,主要有以下几个方便进行了优化:
①,加快了训练速度,可以支持分布式计算框架(支持放在YARN上计算)
②,增加了求导速度,使用了二阶泰勒展开式,加快求导
③,增加了正则化项,根据叶子数量和节点数值防止过拟合。
④,其余方向优化
当然,无论是XGBoost回归算法或者XGBoost分类算法,首先,它是一个监督学习(带label),其次,基于CART树作为计算准则,再次,采用的是损失函数的梯度下降求解方法。
1-1,XGBoost回归算法
☞首先,明确输入训练集为:
☞其次,目标函数就是:
注解:XGBoost算法的目标函数有两个部分构成,因为前面讲了一是在梯度求解上优化加快速度,二是在CART树的基础上正则化提升精度,所以,目标函数的前部分是损失函数,后部分是正则化,
☞现在进行的的XGBoost的回规算法,我们知道XGBoost是沿用了GBDT的框架,GBDT针对回归算法的损失函数有两种,一个是“均方差损失”,另外一个是“绝对值损失”,这里我们就用均方差MSE来展开讲解,那么损失函数部分表达为:
☞当然,我们知道,针对于CART树的过拟合处理,最常用的是我们说的“后剪枝”,但是限制后剪枝的因素有很多,比如:
看了上面的介绍,变现在树这个显性特征,其实最主要的是,两个关键因素:“叶子结点个数”+“节点上的数值”,所以,正则化项也是在这两个参数上做的应用,那么正则化部分的公式就是:
注释:
☞这样,我们就可以知道整个XGBoost的回归的损失函数是(采用MSE损失函数情况下):
有了上面的根据上面的损失函数,我们要明确XGBoost是一个前向算法,是需要通过迭代一轮一轮进行的,那么又是以CART树作为计算准则的,那么回归树的参数有两个方面决定:
(1)选取哪个feature分裂节点;
(2)节点的预测值。
上述形而上的公式并没有“直接”解决这两个,为了决绝这个问题,我们需要引出一个概念就是:“贪心策略+最优化”(二次最优化)
开始应用贪心算法,就是,假如刚开始有一群样本,放在第一个节点这时候T=1,求解W,这时候所有样本的预测值都是w(这个地方自己好好理解,决策树的节点表示类别,回归树的节点表示预测值),带入样本的label数值,此时损失函数变为 :
所以,我们到了下一层采用还会继续分裂下去,当然这种分裂是一个无序的,是一个遍历的,是一个枚举的,会在这一层的节点上分别计算损失函数,直到找到了损失函数最小的那个,然后再选一个feature分裂,又得到一个loss function最小值…枚举完,找一个效果最好的,把树给分裂,就得到了cart树,这个时候,我们可以把损失函数写成:
注释:增加了constant是常数项,为了后面求解更方便
因为我们目前是在做XGBoost的回归算法,采用的损失函数是均方差损失,那么损失函数就是:
注释:这个表述证明XGBoost是基于前向计算传播的算法,
那么针对于这个展开损失函数求导就会陷入麻烦,因为不仅有一次项,而且也有二次项,所以,这个时候为了求导,而且是为了加快求导,就需要直接引入二项泰勒展开式:
这里阐述一下二次泰勒展开公式:
将损失函数与f(x)对应起来:
所以实际上 x 即为,而 Δx 即为。故对f求导数时即对求偏导,那么,损失函数转化为:
那么,我们可以知道,一阶导系数表达为:
二阶导系数表达为:
☞有了以上的推理,我们的XGBoost的回归损失函数其实可以转化为:
☞当然,这样写还是太麻烦,我们继续简化,可以使得:
☞那么损失函数就会变为:
☞这样,就可以对这个损失函数进行求导了,求导结果是:
那么这个时候,Obj就代表了了CART的分裂指标,表示了这棵树的结构有多好,值越小,代表这样结构越好!也就是说,它是衡量第t棵CART树的结构好坏的标准。这个值仅仅是用来衡量结构的好坏的,与叶子节点的值是无关的。Obj只和Gj和Hj和T有关,而它们又只和树的结构有关。
☞现在,需要对给出解释,以便能获得感性的认识。假设分到J这个叶子节点上的样本只有一个。那么,就变成如下这个样子:
通过上面式子得知,的最佳值就是负的梯度乘以一个权重系数,该系数类似于随机梯度下降中的学习率。观察这个权重系数,可以发现,越大,这个系数越小,也就是学习率越小。越大代表什么意思呢?代表在该点附近梯度变化非常剧烈,可能只要一点点的改变,梯度就从10000变到了1,所以,此时,在使用反向梯度更新时步子就要小而又小,也就是权重系数要更小。
☞经过上面的介绍,似乎还有一点没有介绍,就是,如何确认树的最优结构,经过上面公式,有了评判树的结构好坏的标准,就可以先求最佳的树结构,这个定出来后,最佳的叶子结点的值实际上在上面已经求出来了。
问题是:树的结构近乎无限多,一个一个去测算它们的好坏程度,然后再取最好的显然是不现实的。所以,仍然需要采取一点策略,这就是逐步学习出最佳的树结构。这与将K棵树的模型分解成一棵一棵树来学习是一个道理,只不过从一棵一棵树变成了一层一层节点而已。下面来看一下具体的学习过程。
以上文提到过的判断一个人是否喜欢计算机游戏为例子。最简单的树结构就是一个节点的树。可以算出这棵单节点的树的好坏程度obj*。假设现在想按照年龄将这棵单节点树进行分叉,需要知道:
1、按照年龄分是否有效,也就是是否减少了obj的值
2、如果可分,那么以哪个年龄值来分。
为了回答上面两个问题,可以将这一家五口人按照年龄做个排序。如下图所示:
按照这个图从左至右扫描,我们就可以找出所有的切分点。对每一个确定的切分点,衡量切分好坏的标准如下:
这个Gain实际上就是单节点的obj*减去切分后的两个节点的树obj*,Gain如果是正的,并且值越大,表示切分后obj*越小于单节点的obj*,就越值得切分。同时,我们还可以观察到,Gain的左半部分如果小于右侧的γ,则Gain就是负的,表明切分后obj反而变大了。γ在这里实际上是一个临界值,它的值越大,表示我们对切分后obj下降幅度要求越严。这个值也是可以在xgboost中设定的。
扫描结束后,我们就可以确定是否切分,如果切分,对切分出来的两个节点,递归地调用这个切分过程,我们就能获得一个相对较好的树结构。
注意:xgboost的切分操作和普通的决策树切分过程是不一样的。普通的决策树在切分的时候并不考虑树的复杂度,而依赖后续的剪枝操作来控制。xgboost在切分的时候就已经考虑了树的复杂度,就是那个γ参数。所以,它不需要进行单独的剪枝操作。
2-3,XGboost优点与缺点
优点 | 1)GBDT以传统CART作为基分类器,而xgBoosting支持线性分类器,相当于引入L1和L2正则化项的逻辑回归(分类问题)和线性回归(回归问题); 2)GBDT在优化时只用到一阶导数,xgBoosting对代价函数做了二阶Talor展开,引入了一阶导数和二阶导数; 3)当样本存在缺失值是,xgBoosting能自动学习分裂方向; 4)xgBoosting借鉴RF的做法,支持列抽样,这样不仅能防止过拟合,还能降低计算; 5)xgBoosting的代价函数引入正则化项,控制了模型的复杂度,正则化项包含全部叶子节点的个数,每个叶子节点输出的score的L2模的平方和。从贝叶斯方差角度考虑,正则项降低了模型的方差,防止模型过拟合; 6)xgBoosting在每次迭代之后,为叶子结点分配学习速率,降低每棵树的权重,减少每棵树的影响,为后面提供更好的学习空间; 7)xgBoosting工具支持并行,但并不是tree粒度上的,而是特征粒度,决策树最耗时的步骤是对特征的值排序,xgBoosting在迭代之前,先进行预排序,存为block结构,每次迭代,重复使用该结构,降低了模型的计算;block结构也为模型提供了并行可能,在进行结点的分裂时,计算每个特征的增益,选增益最大的特征进行下一步分裂,那么各个特征的增益可以开多线程进行; 8)可并行的近似直方图算法,树结点在进行分裂时,需要计算每个节点的增益,若数据量较大,对所有节点的特征进行排序,遍历的得到最优分割点,这种贪心法异常耗时,这时引进近似直方图算法,用于生成高效的分割点,即用分裂后的某种值减去分裂前的某种值,获得增益,为了限制树的增长,引入阈值,当增益大于阈值时,进行分裂; |
缺点 | 1)xgBoosting采用预排序,在迭代之前,对结点的特征做预排序,遍历选择最优分割点,数据量大时,贪心法耗时,LightGBM方法采用histogram算法,占用的内存低,数据分割的复杂度更低; 2)xgBoosting采用level-wise生成决策树,同时分裂同一层的叶子,从而进行多线程优化,不容易过拟合,但很多叶子节点的分裂增益较低,没必要进行跟进一步的分裂,这就带来了不必要的开销;LightGBM采用深度优化,leaf-wise生长策略,每次从当前叶子中选择增益最大的结点进行分裂,循环迭代,但会生长出更深的决策树,产生过拟合,因此引入了一个阈值进行限制,防止过拟合. |
二,lightGBM算法
2-1,引出lightGBM算法
lightGBM算法,其实是在XGBoost的基础上进行的优化,针对于XGBoost更快速的训练提出的,那么我们先要确认的是XGBoost算法的缺点:
①XGBoost算法是一个前向基于CART树算法,算是在分裂叶子结点时是一个无序的,遍历的,枚举的,这样就导致,在计算最优分裂点的时候,每一次计算需要枚举都有的叶子结点,然后用CART树指标计算,选择最优的分裂点,需要花费大量的实际计算,需要花费巨大的空间来存储结果,所以,这个方向还有改进空间。
②XGBoost算法是一个同层叶子结点同时分裂的,这就导致其实所有叶子结果数值都是同时出来的,但是每个叶子结点数值是不同的,无论是针对于回归还是分类问题,只有数值大的,才有关键意义,数值小的其实作用意义不大,但是基于同时计算出结果就会导致计算速度整体是缓慢的,需要全部计算完毕,才可以只知道结果,因为只有权重大的作用最大,权重小的作用低,我应该首先拿到权重大的,这样才是合理的,所以,这个方向还有改进空间
③前面讲了XGBoost支持并行计算是放在分布式的调度系统YARN上才可以,那么,能不能不放在YARN上也可以并行计计算呢,形成彻底并行处理呢,这也是有优化方向
④加快速度另一个方向,在开头讲解,就是特征工程方面,特征工程一般需要把连续特征分桶,然后再哑变量离散化,需要花费巨大的特征工程处理和时间,这个方面能不能优化呢,所以,默认这也是个方向。
所以,我们知道了,其实lightGBM算法就是在XGBoost算法基础上,为了降低内存和加快时间的又一次提升而提出的。所以,理论知识方面,依旧需要CART,GBDT,XGBoost作为依据,所以,请要把这几个算法理论基础做好才可以来进行以下的lightGBM进化。
2-2. lighgbm优化方向原理:
①基于Histogram(直方图)的决策树算法(特征预排序,减少枚举计算过程)
②直方图做差加速(类似于减法,分类出一类,就可以直接推算另一类)
③带深度限制的Leaf-wise的叶子生长策略(权重大的叶子更重要,加快权重大的节点训练完毕)
④直接支持类别特征(Categorical Feature)(减少特征工程,减少哑变量,增加训练速度)
⑤Cache命中率优化(支持特征分裂后的存储,下次计算,直接取结果,不用再直方图再次计算)
⑥基于直方图的稀疏特征优化
⑦多线程优化 (特征并行和数据并行)
2-3优化细节思路:
①基于Histogram(直方图)的决策树算法(特征预排序,减少枚举计算过程):
XGBoost中想要进一步加快速度,可以采用特征预排序的方法,计算过程当中是按照value的排序,逐个数据样本来计算划分收益,这样的算法能够精确的找到最佳划分值,但是代价比较大同时也没有较好的推广性。
在LightGBM中没有使用传统的特征预排序的思路,而是将这些精确的连续的每一个value划分到一系列离散的域中,也就是分桶处理。以浮点型数据来举例,一个区间的值会被作为一个筒,然后以这些筒为精度单位的直方图来做。这样一来,数据的表达变得更加简化,减少了内存的使用,而且直方图带来了一定的正则化的效果,能够做出来的模型避免过拟合且具有更好的推广性。
直方图算法的基本思想是先把连续的浮点特征值离散化成k个整数,同时构造一个宽度为k的直方图。在遍历数据的时候,根据离散化后的值作为索引在直方图中累积统计量,当遍历一次数据后,直方图累积了需要的统计量,然后根据直方图的离散值,遍历寻找最优的分割点。
使用直方图算法有很多优点,针对于以下介绍:
▶最明显就是内存消耗的降低,直方图算法不仅不需要额外存储预排序的结果,而且可以只保存特征离散化后的值,而这个值一般用8位整型存储就足够了,内存消耗可以降低为原来的1/8。
▶计算上的代价也大幅降低,预排序算法每遍历一个特征值就需要计算一次分裂的增益,而直方图算法只需要计算k次(k可以认为是常数),时间复杂度从O(#data*#feature)优化到O(k*#features)。
Histogram算法并不是完美的。由于特征被离散化后,找到的并不是很精确的分割点,所以会对结果产生影响。但在不同的数据集上的结果表明,离散化的分割点对最终的精度影响并不是很大,甚至有时候会更好一点。原因是决策树本来就是弱模型,分割点是不是精确并不是太重要;较粗的分割点也有正则化的效果,可以有效地防止过拟合;即使单棵树的训练误差比精确分割的算法稍大,但在梯度提升(Gradient Boosting)的框架下没有太大的影响。
②.Lightgbm 的Histogram(直方图)做差加速
一个叶子的直方图可以由它的父亲节点的直方图与它兄弟的直方图做差得到。通常构造直方图,需要遍历该叶子上的所有数据,但直方图做差仅需遍历直方图的k个桶。利用这个方法,LightGBM可以在构造一个叶子的直方图后,可以用非常微小的代价得到它兄弟叶子的直方图,在速度上可以提升一倍。
③带深度限制的Leaf-wise的叶子生长策略(权重大的叶子更重要,加快权重大的节点训练完毕)
Level-wise过一次数据可以同时分裂同一层的叶子,容易进行多线程优化,也好控制模型复杂度,不容易过拟合。但实际上Level-wise是一种低效的算法,因为它不加区分的对待同一层的叶子,带来了很多没必要的开销,因为实际上很多叶子的分裂增益较低,没必要进行搜索和分裂。
Leaf-wise则是一种更为高效的策略,每次从当前所有叶子中,找到分裂增益最大的一个叶子,然后分裂,如此循环。因此同Level-wise相比,在分裂次数相同的情况下,Leaf-wise可以降低更多的误差,得到更好的精度。Leaf-wise的缺点是可能会长出比较深的决策树,产生过拟合。因此LightGBM在Leaf-wise之上增加了一个最大深度的限制,在保证高效率的同时防止过拟合。
④直接支持类别特征(Categorical Feature)(减少特征工程,减少哑变量,增加训练速度)
大多数机器学习工具都无法直接支持类别特征,一般需要把类别特征,转化到多维的0/1特征,降低了空间和时间的效率。而类别特征的使用是在实践中很常用的。基于这个考虑,LightGBM优化了对类别特征的支持,可以直接输入类别特征,不需要额外的0/1展开。并在决策树算法上增加了类别特征的决策规则。在Expo数据集上的实验,相比0/1展开的方法,训练速度可以加速8倍,并且精度一致。据我们所知,LightGBM是第一个直接支持类别特征的GBDT工具。
LightGBM的单机版本还有很多其他细节上的优化,比如cache访问优化,多线程优化,稀疏特征优化等等,更多的细节可以查阅Github Wiki(https://github.com/Microsoft/LightGBM/wiki)上的文档说明
⑤Cache命中率优化(支持特征分裂后的存储,下次计算,直接取结果,不用再直方图再次计算)
当我们用数据的bin描述数据特征的时候带来的变化:首先是不需要像预排序算法那样去存储每一个排序后数据的序列,也就是下图灰色的表,在LightGBM中,这部分的计算代价是0;第二个,一般bin会控制在一个比较小的范围,所以我们可以用更小的内存来存储
⑥.直接支持高效并行
LightGBM原生支持并行学习,目前支持特征并行(Featrue Parallelization)和数据并行(Data Parallelization)两种,还有一种是基于投票的数据并行(Voting Parallelization)
▶特征并行的主要思想是在不同机器、在不同的特征集合上分别寻找最优的分割点,然后在机器间同步最优的分割点。
▶数据并行则是让不同的机器先在本地构造直方图,然后进行全局的合并,最后在合并的直方图上面寻找最优分割点。
LightGBM针对这两种并行方法都做了优化。
特征并行算法中,通过在本地保存全部数据避免对数据切分结果的通信。
数据并行中使用分散规约 (Reduce scatter) 把直方图合并的任务分摊到不同的机器,降低通信和计算,并利用直方图做差,进一步减少了一半的通信量。
基于投票的数据并行(Voting Parallelization)则进一步优化数据并行中的通信代价,使通信代价变成常数级别。在数据量很大的时候,使用投票并行可以得到非常好的加速效果。
⑦顺序访问梯度
预排序算法中有两个频繁的操作会导致cache-miss,也就是缓存消失(对速度的影响很大,特别是数据量很大的时候,顺序访问比随机访问的速度快4倍以上 )。
对梯度的访问:在计算增益的时候需要利用梯度,对于不同的特征,访问梯度的顺序是不一样的,并且是随机的
对于索引表的访问:预排序算法使用了行号和叶子节点号的索引表,防止数据切分的时候对所有的特征进行切分。同访问梯度一样,所有的特征都要通过访问这个索引表来索引。
这两个操作都是随机的访问,会给系统性能带来非常大的下降。
LightGBM使用的直方图算法能很好的解决这类问题。首先。对梯度的访问,因为不用对特征进行排序,同时,所有的特征都用同样的方式来访问,所以只需要对梯度访问的顺序进行重新排序,所有的特征都能连续的访问梯度。并且直方图算法不需要把数据id到叶子节点号上(不需要这个索引表,没有这个缓存消失问题)
四,GBDT,XGBoost,lightGBM算法比较
以上是关于推荐算法与量化交易-A-5-5:XGBoost-lightGBM“集成提升树模型”算法-基于模型算法的主要内容,如果未能解决你的问题,请参考以下文章
量化资料学习《Python与量化投资从基础到实战》+《量化交易之路用Python做股票量化分析》+《组织与管理研究的实证方法第2版》