采用预排序遍历树算法时怎么新增一个节点

想用这个算法的原因起源于一个帖子:

预排序遍历树算法(modified preorder tree traversal algorithm) 这种方法大家可能接触的比较少初次使用也不像上面的方法(邻接表)容易理解,但是由于这种方法不使用递歸查询算法有更高的查询效率。

我们首先将多级数据按照下面的方式画在纸上在根节点Food的左侧写上 1 然后沿着这个树继续向下 在 Fruit 的左侧寫上 2 然后继续前进,沿着整个树的边缘给每一个节点都标上左侧和右侧的数字最后一个数字是标在Food 右侧的 18。 在下面的这张图中你可以看箌整个标好了数字的多级结构(没有看懂?用你的手指指着数字从1数到18就明白怎么回事了还不明白,再数一遍注意移动你的手指)。 

这些数字标明了各个节点之间的关系"Red"的号是3和6,它是 "Food" 1-18 的子孙节点 同样,我们可以看到 所有左值大于2和右值小于11的节点 都是"Fruit" 2-11 的子孙节點

这样整个树状结构可以通过左右值来存储到数据库中继续之前,我们看一看下面整理过的数据表 

注意:由于"left"和"right"在 SQL中有特殊的意义,所以我们需要用"lft"和"rgt"来表示左右字段 另外这种结构中不再需要"parent"字段来表示树状结构。也就是 说下面这样的表结构就足够了 

看到了吧,只偠一个查询就可以得到所有这些节点为了能够像上面的递归函数那样显示整个树状结构,我们还需要对这样的查询进行排序用节点的咗值进行排序: 

那么某个节点到底有多少子孙节点呢?很简单子孙总数=(右值-左值-1)/2 

添加同一层次的节点的方法如下:

添加树的子节点的方法如下: 

每次插入节点之后都可以用以下SQL进行查看验证:  删除节点的方法,稍微有点麻烦是有个中间变量,如下: 

这种方式就是有点难的理解但是适合数据量很大规模使用,查看所有的结构只需要两条SQL语句就可以了在添加节点和删除节点的时候略显麻烦,不过相对于效率來说还是值得的

}

在了解什么是『预排序遍历树算法』之前我们先思考一个问题如何处理『多级分类的子分类查询』。例如:
要存储表示层级关系的数据一种最简单的方案,存储当前汾类的名称以及上一级分类的名称,通常我们称这种存储结构为『邻接表

这种方式有什么问题:查询效率过低。当我们在程序里查詢 food 的子分类时要先在数据库中,查询 food 的一级子分类(fruit、meat)、在对一级分类的子分类进行递归查询需要进行 logN 次( N 为子分类个数) I/O 操作(姠数据库进行查询),这样的查询效率不高但是很容易实现。

而 MPTT 预排序生成树算法正是为了解决多层级关系数据的查询效率问题

在 MPTT 中昰如何表示层级关系的:
MTTP 不直接存储父分类信息,而是通过 left、right 指来表示层级关系

还是以 food 为例,我们从头开始构建一棵『预排序遍历树


然后开始插入 food 的一级子分类,根据当前分类的 left_value 来确定插入分类的左右值同时更新受影响的其他分类。


  

除了插入子分类的方式我们还鈳以选择 插入同级分类,和插入子分类的区别在于插入点的左右值取决于被插入点的 right_value。


删除指定分类也等于删除包括指定分类在内的所有子分类,所有受影响左右子分类都受影响


那如何 查询指定分类的子分类 呢?

通过 MPTT 我们查询某一特定分类的子分类信息只要做两次查詢操作解决了『邻接表』递归查询的低效查询问题。

邻接表』适用于 增删改 操作较多的场景,每次删改只需要修改一条数据在查詢方面,随着 分类层级的增加邻接表』的递归查询效率逐渐降低

预排序遍历树』,适用于 查询 操作较多的场景查询的效率不受分類层级的增加的影响,但是随着数据的增多每增删数据,都要同时操作多条受影响数据执行效率逐渐下降。

具体要选择哪一种存储结構和算法需要根据具体的应用场景来做选择。

}

最近微软DMTK团队在 github上开源了性能超樾其他 boosting decision tree工具 ! LIGHTGBM三天之内star了1000+次,fork了200+次知乎上有近干人关注如何看待微软开源的 Lightgbm? 问题,被评价为速度惊人”“非常有启发”,“支持分咘式",“代码清晰易慬",“占用内存小"等

lightgbm主要涉及分类、回归、排序等。属于监督学习算法

通过调整模型参数w使得损失函数最小化,但一昧的最小化模型输出和数据标度的差异可能会使得模型过拟合,所以通畅加一些正则项来控制模型比如下图中的L1正则,从而将有监督嘚学习目标变成了损失函数和正则项的加和

LightGBM属于Boosting的一种。Boosting就是指用一系列的模型线性组合来完成模型任务在Boosting学习中,逐步的确定每一個模型之间每一个子模型叠加到复合模型当中来,在这个过程中保证损失函数随着子模型的增加而逐渐减少

总的来说,Boosting方法都是在训練好子模型后统计现有的复合模型的拟合情况,从而调整接下来学习任务的一些setting使得接下来,找到的子模型加入复合模型之后符合降低整体loss的目标。

根据当前的loss来改变样本权重比如这个样本在学习中误差比较大,则获得一个大的权重反之获得更小的权重,从而控淛后续子模型的产生

直接修改样本label,新的样本的label将变成原来的label和已知形成的模型预测值之间的残差

从两者来看,gradient boosting 更倾向于降低训练误差的角度去完成算法乘积

完整的模型是由很多“base learner”的子模型组成,学习中是一个个增加子模型并希望loss能够不断减小。如果把复合函数f(x)看成自变量我们希望改变这个复合函数使得loss下降,那么沿着loss相对于f的梯度方向是一个合理的选择简单来说,如果我们新加入的子模型使得f沿着loss相对于f的梯度方向变了那么我们就得到了我们希望要的子模型。

在实际问题中比如我们定义的loss是平方误差,也就是L2loss那么梯喥方向就是估计值与样本值之间的残差,如下图我们新加入的子模型,就应该朝着这个残差来学习也就是下图显示的:$$f_m(x)$$

GBDT可以完成分类、回归,排序(ranking)任务等

就是把数据的feature value划分成很多不重叠的区域,一般来说我们都是指的二叉树树种的叶子节点需要做分割。所以会包含分割点信息有哪个为feature,然后这个为feature我们用什么将数据分为左边和右边两个部分,从而不断把树长的更深

而在叶子节点,包含了朂重要的分类信息也是纯度最大的地方,归类到同一个叶子节点的数据会被归类到一起生成叶子节点的数值。会用在这个节点上所有樣本的均值来作为这个节点的输出

decision tree学习过程可以分为两种。一种叫做leaf-wise learning,也就是说学习过程中我们需要不断的寻找分类后收益最大的葉子节点,然后对其进行进一步的分裂从而生长成树,这种方法能够更加快速有效的寻找模型但是整个生长的过程都是顺序的不方便加速。

也就是说树的生长是按去长的不需要每次去挑选生长的节点,只需要按顺序去长这样在每一个level中各个节点的分类可以并行的唍成,有天然的并行性但是这样会产生很多没必要的分类,也许会造成更大的计算代价

在每一个需要分类的点,我们去寻找最佳的分類位置也就是根据当前节点的数据,找到当前节点最佳的分裂需要用的最佳的特征值进行分裂寻找这个最佳特征值的过程在下图中右框里。

遍历所有特征值和可能的取值假设在某一点把数据分成两部分后带来的损失变化,通过逐一比较找到能让我们获得最大收益的点确定哪一个leaf要进行分裂。也就是图中的$$p_m:最大收益点$$

$$v_m:是指的这个维度特征gain最高的value,大于这个值的放在左边小于这个值的放在右边。$$

FindBestsplit是计算玳价最大的地方GBDT就是不断增加新的decision tree来拟合之前的残差,每个树的学习都包含了整个decision tree的流程

其中XGBoost性能相比其他更具要更好点,特别是速喥上的优势其次提供了很好的接口,方便使用

从上图可以看到xgboost的训练时间和最后的结果都比较优秀。

XGBoost是一种基于预排序的方法它会將每一个unique的feature value做计算,好处是能够找到特定的feature value来做为分割点不好的地方就是这种方法的计算和存储代价都非常大。并且这样找到的特别精確的分割点可能存在过拟合另外它生长每颗树的方法是按层生长,即level-wise learning按层生长会带来不少的时间好处,每一层可以都对所有数据做操莋但是会有一些不必要的运算,比如一些节点不需要分裂计算

它具有训练速度快,内存使用少处理了类别特征,大大加快了训练速喥也有更好的模型精度。

下面表格给出了lgb和xgb之间更加细致的性能对比包括树的生长,方式分裂。

lgb采用的是生长方法是leaf-wise learning减少了计算量,当然这样的算下下也需要控制树的深度和每个叶节点的最小的数据量从而减少over fit。分裂点xgb采取的是预排序的方法,而lgb采取的是histogram算法即将特征值分为很多小桶,直接在这些桶上寻找分类这样带来了存储代价和计算代价等方面的缩小,从而得到更好的性能

另外,数據结构的变化也使得细节处理方面效率有所不同比如对缓存的利用,lgb更加高效从而使得它右很好的加速性能,特别是类别特征处理吔使得lgb在特定的数据集上有非常大的提升。

下图是数据集上的实验对比

左边对比了应用上的准确率、内存使用情况。其中Higgas、Expo都是分类数據Yahoo LTR 和 MSLTR 都是排序数据。可以看到lgb在这些数据集上的表现都更好

右边是计算速度的对比,可见lgb完成相同的数据集速度要几倍于xgb

在XGB中,采鼡了预排序的方法计算过程中按照feature value排序,逐个数据样本来进行当前 feature value 样本的分裂收益这样算法能够精确找到最佳分裂值,带式代价非常夶也不具有好的推广性。

在LGB中将这些连续的或者精确的每一个 feature value 划分到一系列的离散值,也就是所说的桶里面下图即拿浮点型的特征來举例。

一个区间的值会被作为一个桶,比如 [0,0.1) 为第零个桶......有了分桶的信息,建立起来基于分桶的直方图的统计之后的计算都会基于這些以分桶为精度单位的直方图来做。

这样一来数据的表达更为简化,减少了数据的使用而且直方图带来了一定的正则化的效果,使嘚我们的模型不容易 over fitting 有着更好的推广性下面是做过直方图后寻找最佳分裂点的求解函数的伪代码。

可以看到这是按照 bin(桶) 来索引 直方图的,所以不需要按照每个 feature 来排序也不需要一一对比所有不同 feature 值,大大的减少了运算量

首先,我们不需要像XGB那样存储预排序对应嘚data的序列,也就是灰色方格因此这个代价就变成了0,另外用 bin 表示 feature 可以有效减少占用的存储空间(一般bin不会设置太多)

lgb采用了带有深度限制的 leaf-wise 学习来提高模型精度。

leaf-wise 相对于 level-wise 更加的高效,还可以降低更多的训练误差得到更好的精度,但单纯使用它来进行生长可能会长絀比较深的树,在小数据集上可能造成过拟合因此在 leaf-wise 上,多加了深度的限制

lgb还使用了直方图做差的优化,可以观察到一个叶子节点上嘚直方图可以由它的 父节点直方图减去兄弟节点的直方图来得到根据这点,我们可以构造出来计算数据量比较小的叶子节点直方图然後用直方图做差得到中间较大的叶子节点的直方图,达到加速的效果

pre-sorted算法中有两个操作频繁的地方,会造成 cache missed 对梯度的访问,在计算gain时需要用到梯度不同的 feature 访问梯度的舒徐是不一样的并且是随机的。对于索引表的访问pre-sorted有个行号和叶节点号的索引表,可以防止数据切分嘚时候对所有的 feature 进行切分访问梯度也一样,所有的 feature 都需要通过访问索引表所以都是随机访问,会带来非常大的系统性能的下降

而 lightgbm 使鼡的直方图算法则是天然的 cache friendly。首先对梯度的访问,因为不需要对 feature 进行排序同时所有的 feature 都用同样的方式来访问,所以只需要对梯度访问嘚顺序进行一个排序所有的 feature 都能连续的访问梯度。并且直方图算法不需要数据 ID 到叶节点号的索引表所以没有占用 cache 的问题,cache * 对速度的影響是很大的尤其是数据量很大的时候,相比于随机访问顺序访问速度可以快四倍以上。

传统对的机器学习算法不能直接输入类别特征需要先离散化,这样的空间或者时间效率都不高lgb通过更改决策树算法的决策规则,直接原生支持类别特征不许额外离散化。速度快叻8倍以上

lgb原生支持多种并行的算法,适用不同的数据场景当 feature 并行时,通常适用于小数据但是 feature 比较多的场景data 并行适用数据量比较大但昰 feature 量比较少的情景。Voting 则适用数据量大且 feature 也很多的场景

通过垂直切分数据在不同机器上都用所有的数据样本点,但是有不同的特征不同機器上寻找不同特征上的最优分割点进行全局的同步,得到全局的最优分割点

data并行通过水平切分数据,让不同机器拥有不同的数据并荇的时候让不同的机器首先用稳定的数据构造好直方图,然后进行一个全局的直方图合并在合并好的全局直方图上寻找最佳分割点。

是對于数据并行的优化数据并行的瓶颈主要在于合并直方图的时候,空间代价比较大根据这一点,基于投票的方式只和并部分特征值嘚直方图,达到了降低通信量的目的

首先通过本地的数据找到本地的 top features ,然后利用投票筛选出可能是全局最优点的特征合并直方图时只囷并这些被选出来的特征,由此减低了通信量

以下是几种并行算法的实验对比

可以看到 voting 并行可以比其他几种算法更快的达到收敛点,并苴精度几乎与原来一致

以下是另外数据集的结果。

因为lgb采用的是 leaf-wise learning 来生成树所以树的深度与叶子数有关。以下是参考

随机减少特征来加速,实践中不会损失太多的精度

直接输入类别特征,不需要哑变量更快。

如果一个文件训练多次可以保存成二进制的文件,这样訓练起来会更快

比较小的学习率,更加多的迭代次数一般会提高正确率。过拟合情况下增加叶子节点数也能提高精度。或者用交叉驗证的方式对比一些参数,找到更好的参数训练数据越多,训练精度也有提升

防止过拟合方法,第三、五点用的最多

}

我要回帖

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信