天天看點

LightGBM源碼學習

​​知乎 - lightgbm 源碼剖析​​

​​知乎 - lightgbm源碼閱讀(一)​​

​​知乎 - lightgbm源碼閱讀(二)​​

 lightgbm源碼閱讀+理論分析(處理特征類别,預設值的實作細節)​​

​​簡述fastdbt和lightgbm中gbdt的實作​​

​​調試準備​​

​​從main到gbdt::train​​

執行路徑

​​重點看一下trainoneiter​​

gbdt::boostfromaverage

objectivefunction::getgradients

回顧疊代過程

serialtreelearner::train

num_leaves次數的最佳葉子結點分裂過程

​​efb​​

調試準備

把-o3改為-o0,不然指令優化之後有些值調試不出來

代碼裡面有些帶​<code>​#pragma omp parallel for​</code>​ 的預處理語句可以注釋掉,并行改串行,不然調試不進去

LightGBM源碼學習

先以二分類為例,資料用的是官方給的例子

LightGBM源碼學習

從main到gbdt::train

main函數執行路徑

​<code>​application::train​</code>​

​<code>​src/application/application.cpp:200​</code>​

LightGBM源碼學習

​<code>​boosting_-&gt;train(config_.snapshot_freq, config_.output_model);​</code>​

去看boosting

​<code>​include/lightgbm/boosting.h​</code>​

boosting是一個抽象類,派生出​<code>​boosting_type​</code>​對應的4個子類

LightGBM源碼學習

有空畫個繼承關系圖

LightGBM源碼學習

找到gbdt::train函數

​<code>​src/boosting/gbdt.cpp:246​</code>​

​<code>​fun_timer​</code>​應該是一個raii對象

樸實無華的疊代過程,看​<code>​trainoneiter​</code>​

LightGBM源碼學習

注意上層調用方式是​<code>​is_finished = trainoneiter(nullptr, nullptr);​</code>​,​<code>​gradient​</code>​和​<code>​hessian​</code>​都是空指針

gbdt有兩個成員變量,維護每次疊代時的梯度和黑塞(用了自己寫的記憶體配置設定器)

vector裡面放智能指針比放指針靠譜(對比slsm源碼)

重點看一下trainoneiter

重點看一下初始值怎麼設的

binarylogloss

LightGBM源碼學習

馬上列印出:

去​<code>​obtainautomaticinitialscore​</code>​看看

跳到​<code>​src/objective/binary_objective.hpp:134​</code>​

如圖,總共7000全量樣本,正樣本3716,pavg=0.53

LightGBM源碼學習
LightGBM源碼學習

i

n

t

s

c

o

r

e

=

log

(

p

a

v

g

1

)

initscore=\log(\frac{pavg}{1-pavg})

initscore=log(1−pavgpavg​)

算出來的是logits

本小節主要闡述目标函數​<code>​1 2階導數​</code>​的求解,如果後期需要實作hinge loss或者focal loss可以用得上

init_scores看完了,我們看​<code>​gbdt::boosting​</code>​

\rightarrow

→ ​

<code>​objectivefunction::getgradients​</code>​

​<code>​src/objective/binary_objective.hpp:101​</code>​

LightGBM源碼學習

為了友善調試,注釋掉​<code>​#pragma omp parallel for schedule(static)​</code>​

注意到binarylogloss初始化的時候*weights_=1.2

發現是配置檔案指定了weight

LightGBM源碼學習

我們康康gradient和hessian是怎麼算的

初次計算時,score就是​<code>​init_scores​</code>​,用label avg算的

LightGBM源碼學習

忽略樣本權重,可以歸納為這兩個式子(注意,label已經變換為{-1, 1},即

y

{

,

}

y\in\{-1, 1\}

y∈{−1,1}):

+

x

×

^

g=-\frac{y}{1+exp(y\times \hat{y})}

g=−1+exp(y×y^​)y​

h

h=|g|\times (1-|g|)

h=∣g∣×(1−∣g∣)

好,我們來推一下這兩個公式怎麼來的

​​知乎 - 邏輯回歸損失函數的兩種形式​​

根據上述知乎文章,可知在

y∈{−1,1}條件下,負對數似然函數為

l

nll=\log(1+exp(-y \cdot \hat{y}))

nll=log(1+exp(−y⋅y^​))

nll對

\hat{y}

y^​求導,可得

σ

\large{ \begin{array}{ll} \frac{\partial{nll}}{\partial{\hat{y}}} &amp;=-\frac{y}{1+exp(y\cdot \hat{y})} \\ \\ &amp;=-\sigma(-y\cdot \hat{y}) \cdot y \end{array} }

∂y^​∂nll​​=−1+exp(y⋅y^​)y​=−σ(−y⋅y^​)⋅y​

sigmoid

LightGBM源碼學習

sigmoid 導數

LightGBM源碼學習
LightGBM源碼學習

treelearner派生了很多learner,這裡以serialtreelearner為例進行學習

啟動過程:

gbdt::init

train_data_是const dataset*類型,dataset包含efb之後的結果

在serialtreelearner::init中:

這些變量全部被初始化為長度​<code>​num_leaves​</code>​的vector

LightGBM源碼學習

在histogrampool::dynamicchangesize方法中,會将​<code>​pool_​</code>​和​<code>​data_​</code>​設為num_leaves,将​<code>​feature_metas_​</code>​設為num_features

LightGBM源碼學習

列印了一下

LightGBM源碼學習
LightGBM源碼學習

​<code>​data_​</code>​的長度開到​<code>​num_total_bin * 2​</code>​

原本在gbdt::init中調用serialtreelearner::init的時候,不應該是7000個資料嗎,怎麼變成5626了呢?我回去看了下,是在gbdt::train中調用了gbdt::bagging導緻的

周遊所有可分裂的葉子<code>​for (int split = init_splits; split &lt; config_-&gt;num_leaves - 1; ++split) {​</code>

serialtreelearner::beforefindbestsplit

​<code>​histogram_pool_​</code>​ 相關處理

serialtreelearner::findbestsplits

constructhistograms(is_feature_used, use_subtract);

findbestsplitsfromhistograms(is_feature_used, use_subtract);

<code>​for (int feature_index = 0; feature_index &lt; num_features_; ++feature_index) {​</code>

serialtreelearner::computebestsplitforfeature

featurehistogram::findbestthreshold

find_best_threshold_fun_(仿函數對象)

featurehistogram::findbestthresholdsequentially

根據成員變量​<code>​best_split_per_leaf_​</code>​找到最佳葉子索引(int) ​<code>​best_leaf​</code>​

如果​<code>​best_leaf_splitinfo.gain &lt;= 0.0​</code>​,break

調用​<code>​split(tree_prt, best_leaf, &amp;left_leaf, &amp;right_leaf);​</code>​進行葉子分裂

real_feature_idx_

serialtreelearner

efb

​<code>​src/io/dataset.cpp:338​</code>​

LightGBM源碼學習
LightGBM源碼學習

hist_t是double的别名

meta_維護了num_bin,config 等

LightGBM源碼學習

繼續閱讀