zz:Xgboost 核心源码阅读

目录

2016/11/18简述FastDBT和LightGBM中GBDT的实现

2016/10/29XGboost核心源码阅读

[3]XGboost与Spark的完全集成 @Nan Zhu

2016/10/05XGboost: A Scalable Tree Boosting System论文及源码导读

2016/10/02Gradient Boosting Decision Tree[下篇]

2016/09/24Gradient Boosting Decision Tree[上篇]

gbRank & logsitcRank自顶向下

[笔记]Ranking Relevance in Yahoo Search


简述FastDBTLightGBMGBDT的实现

Nov 18, 2016 | 机器学习 |  Hits

FastDBT和LightGBM在XGBoost之后提出,FastBDT针对多类别问题进行了优化,从实现层面获得了更快的训练速度,LightGBM则在树的生成进行了改进,文章按以下思路介绍

  1. FastDBT简介及代码走读,LightGBM简介
  2. 从算法层面讨论下三者的异同点

FastDBT

树模型从模型层面和实现层面两种可行的改进方式:1.模型层面的改进:如SGBDT采用的样本采样方式[1],或者对弱作用特征的剪枝[2]。2.实现层面的改进,见引文[3].FastDBT延续自SGBDT,SGBDT在每层树的构建中并非所有样本都使用到,按参数αα对数据进行随机采样。
FastDBT有如下三个实现层面上的加速:

  1. 数据在内存的存储方式,分为两种a.结构体数据,b.数组结构体,如下图1。区别在于当对其中一维特征进行遍历的时候,按数组结构体的方式存储使得内存连续,提升内存访问速度。
  2. 将正例负例进行区分存储,减少一半左右的计算累计直方图(calculation of the cumulative probability histograms,CPH)缓存占用,同时节约了if判断条件跳转的浪费。
  3. 同层节点并行计算。由于每层的节点的数据来自于被父节点分割的子集,FastBDT在对不同的节点同时计算CPH(临时存储多个节点点的CPH),可以连续进行内存访问避免跳跃。

 

图1:数据内存布局

下面进行FastDBT建树部分代码走读:

 

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
main()
  ->train()
    ->data=readDataFile() //data结构为:vector<vector<float>>
    //上一步数据作为2D向量读入
    //FastDBT需要分桶后的数据,使用FeatureBinning对每个特征进行数据分桶。
    //先初始化FeatureBinning,对于每个特征,遍历所有样本对应的特征值,进行分桶边界的初始化
        for(unsigned int iFeature = 0; iFeature < numberOfFeatures; ++iFeature)
            //枚举特征,得到每个特征特征值列表
        std::vector<double> feature;
        feature.reserve(numberOfEvents);
        for(auto &event : data) 
          feature.push_back( event[iFeature] )
        //根据特征值进行对应桶的设定,
        featureBinnings.push_back(FastBDT::FeatureBinning<double>(nCuts, feature)); 
        nBinningLevels.push_back(nCuts)
 
   //eventSample存储完成特征值->分桶index的数据
   //将数据分为signal和background两个部分(1为signal,0为background),提供更快地访问速度
   for(auto &event : data)
     for(unsigned int iFeature = 0; iFeature < numberOfFeatures; ++iFeature)
           //找到当前样本特征值对应特征所属的桶
       bins[iFeature] = featureBinnings[iFeature].ValueToBin( event[iFeature])
       // bins存有该样本的,权重为1
           //signal取决于数据的_class标签是否为1;1则是正例,否则为负例。
        eventSample.AddEvent(bins, 1.0, _class == 1)
        
    //ForestBuilder类负责树模型的训练
    //输入为数据eventSample,树的个数nTrees
        //树的正则化因子Shrinkage,采样率randRatio,层数nLevels
    FastBDT::ForestBui1lder dt(eventSample, nTrees, shrinkage, randRatio, nLevels)
    //将生成的树组合为forest
    FastBDT::Forest<double> forest( dt.GetShrinkage(), dt.GetF0(), true)
    for( auto t : dt.GetForest() )
    //将树的节点由index转为特征值
      auto tmp=removeFeatureBinningTransformationFromTree(t,            
               featureBinnings)
      forest.AddTree(tmp)

有几个实现细节比较trick需要提一下

  1. FeatureBinning的内部实现采用了二分查找树(调了几个小时代码没弄明白,幸亏Thomas解答并帮忙增加了注释)
    特征值分桶核心代码:
 

1

2
3
4
5
6
7
8
9
10
//枚举每一层树
for(uint64_t iLevel = 0; iLevel < nLevels; ++iLevel):
  //桶的数目double
  const uint64_t nBins = (1 << iLevel)
  //枚举每个桶
  for(uint64_t iBin = 0; iBin < nBins; ++iBin) {
      
  //记特征值下标为 size/2^(iLevel+1)+iBin*size/2^iLevel
  //第bin_index指向的有序特征的对应二分查找位置
  binning[++bin_index] = \
    first[(size >> (iLevel+1)) + ((iBin*size) >> iLevel)]
  1. ForestBuilder类的初始化时完成SGBDT每一棵树的级联
    boosting过程的核心代码:
 

1

2
3
4
5
6
7
8
9
10
11
12
13
14
//生成nTrees棵树
for(unsigned int iTree = 0; iTree < nTrees; ++iTree):   
// Update the event weights according to their F value
// 使用对应的F值进行更新每个样本的权重,F值为pseudo_response
updateEventWeights(sample)
// Prepare the flags of the events
// 根据randRatio进行采样,eventsamples内有flag可以标记样本是否可用
prepareEventSample(sample, randRatio, sPlot ) 
// TreeBuilder类进行单棵树的生成, nLayersPerTree为树的层
TreeBuilder builder(nLayersPerTree, sample)
//将生成的树加入到森林中
forest.push_back( Tree<unsigned int>( builder.GetCuts(), \
      builder.GetNEntries(), builder.GetPurities(), \
      builder.GetBoostWeights() ) )
  1. TreeBuilder类完成单棵树的生成
    TreeBuilder核心代码走读:
 

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//生成每一棵树需要探索的节点,存在nodes列表
for(unsigned int iLayer = 0; iLayer <= nLayers; ++iLayer) 
  for(unsigned int iNode = 0; iNode < (1<<iLayer); ++iNode)
        nodes.push_back( Node(iLayer, iNode) );
// 每棵树的节点是从顶向下,从左到右生成,
// 树是贪心法生成,我们迭代计算每一层的节点
// 对于不同的分裂节点和特征,生成正负例的累计直方图
for(unsigned int iLayer = 0; iLayer < nLayers; ++iLayer) 
  //计算累计直方图,signal,background分别计算
  //由于signal,background在存储上物理分离,可以进行连续访问
  CumulativeDistributions CDFs(iLayer, sample) 
  //选择合适的特征和值,更新到当前节点
  UpdateCuts(CDFs, iLayer) 
  //采样数据,SGBDT特有操作
  UpdateFlags(sample) 
  //根据分裂节点划分样本
  UpdateEvents(sample, iLayer)
  1. 计算累计直方图,得到正例和负例的累计直方图
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值