上手機器學習系列-第5篇(下)-XGBoost原理

本篇中我們結合XGBoost的論文《XGBoost: A Scalable Tree Boosting System》來理解一下該算法的原理。

這是一篇寫自2016年的論文,當時已經有大量的競賽、工作實踐開始採用XGBoost。作者在論文中將XGBoost稱爲Scalable,正是基於它的高性能特點,據論文講,根據當時的評估,在單機上的運行速度對比中,XGBoost較業內其它算法快了至少10倍以上。

論文核心思想導讀

看論文的過程中可以結合以下兩個資料:
https://xgboost.readthedocs.io/en/latest/tutorials/model.html,
https://homes.cs.washington.edu/~tqchen/pdf/BoostedTree.pdf
因爲論文是高度精簡的,有些推導過程是直接省略的,所以需要看一些補充材料。尤其是上面第二個PPT材料,講得很有條理,也有推理的過程。

在正式閱讀原文前,我們需要先在腦海裏回顧一下一個普通的決策樹構建過程中關鍵點有哪些,這樣在讀論文的過程中就可以有意識地進行對比它是怎麼做的,在以後去看新的tree boosting方法時,也可以更有條理地去對比關鍵點。

  1. 定義損失函數,否則沒辦法判斷決策樹分裂後是變好還是變壞了
  2. 有了損失函數的定義後,怎樣來找每個節點上使用哪個變量,以及該變量值的哪個數值點來進行分裂
  3. 如何判斷是否可能存在過擬合,怎樣剪枝
  4. 葉子節點上的值怎麼使用

下面開始閱讀論文《XGBoost: A Scalable Tree Boosting System》。我們這裏無法面面俱到,更多是把一些作者話裏放外省略掉的一些推導、想法進行展開討論。方便大家在邏輯上推敲得更充分。所以可以結合其它講XGBoost上的文章一起來看。

論文共分了幾個核心部分:

  1. 如何用boosting的方法訓練集成樹
  2. 如果尋找分裂點
  3. 系統上的設計(並行、分佈式)
  4. 數據實驗

第一部分。
先來說用boosting的方法訓練樹。這裏的核心是設計目標函數,並求解該函數。在XGBoost中,作者採用瞭如下的目標函數:
(img)
在這裏插入圖片描述

注意這裏與普通的GBDT相比,多了一項對於葉子節點上得分值的平方和。其實正則化的設計是比較靈活的,不同的人可以有不同的方法,更多是基於經驗原則來看是否有益於模型整體表現。作者在文中說加了這樣一個設計可以使得葉子節點的得分值更加平滑,以避免過擬合。那麼爲什麼呢?

下面是筆者的理解:
在這裏插入圖片描述
有沒有嚴格的數學推理呢?根據柯西不等式定理,可以做如下推導:
在這裏插入圖片描述
由此可見,如果有多棵樹滿足了目標函數中真值與預測值的損失項,那麼就看誰的正則化項更小了,其中T是樹的葉子節點數量,葉子越少越好;得分平方和項則是不同葉子節點之間得分值越接近越好;至於爲什麼要這樣設計,用作者的原話來說:

Of course, there is more than one way to define the complexity, but this one works well in practice.

暫時沒有看到相關資料談到如果不加後面這個對於得分平方和的正則化項,XGBoost會損失多少精度,所以只能猜測是作者在經驗基礎上的神來之筆。

第二部分。
接下來作者講了如何做boosting的過程,即逐一地添加新的樹,並在每一步如何通過最優化方法生成當前步驟所需的新樹。這裏的核心是逐一添加新樹的過程中,怎樣在形式上簡化目標函數(從中我們也能感受到一點:把計算通過數學符號的形式進行提煉,確實可以幫助快速整理思路)。這裏的推導過程大家一定要結合上面給出的兩個參考資料來看,否則光看論文顯得太精簡,無法get到推導過程。這個過程多看幾遍,相信大家就可以清楚了,所以這裏不展開討論。對泰勒展開不熟悉的同學可以參考下文“相關知識點討論”部分給出的資料推薦。總之吧,最後論文作者得出來了兩個高度精煉的公式:
第一個是用於表示一棵樹的得分公式:
在這裏插入圖片描述
它有點類似於普通決策樹中的信息熵或Gini值,值的大小可能不代表什麼,但當樹的結構發生變化時,它的變化量是有信息的,即通過下面這個公式來刻畫:
在這裏插入圖片描述
這個值越大,代表分裂後的樹結構相比於分裂前,損失函數的數值降低的越多,那就是好事。
有了量化的標準之後,在一棵樹的節點上,該怎麼分裂呢?傳統的決策樹做法:
在這裏插入圖片描述
而XGBoost的作者創新性地提出了更高效的做法,詳見以下第三部分的討論。

對於類別型變量,是需要做one-hot-encoding的(以後當我們讀CatBoost論文時會看到不同的思路)。
在這裏插入圖片描述

至於如何防止過擬合,除了引入正則項之外,還要考慮剪枝:
在這裏插入圖片描述

第三部分。
按照傳統的做法去遍歷所有屬性與所有可能的分裂點的話,在海量數據的場景下肯定會有計算性能的影響。所以作者提出了他的近似計算方案(其實在很多現實問題的求解中,一個足夠好的近似算法往往會是工程上最佳的選擇,畢竟有時候理論值是無法達到的,或者需要以極高的成本來實現)。

尋找最佳分裂點的近似方法:

  1. 根據特徵變量的數值分佈,提出一些分裂點候選集;
  2. 將連續型變量的值映射到這些候選點分成的區間中(原文稱爲bucket);
  3. 根據前文給出的目標函數找到最佳分裂點。

原文講到這裏,筆者有一種感覺:每個字都認識,但就是連起來之後不知道作者講了啥。所以讓我們展開討論一下。

本質上,現在的目標就是把非常非常多個可以分裂的點,減少到一定的的量。作者提出的是一種基於屬性值分佈的方法,並且專門爲此設計了一種算法(推導過程在論文附錄,這一段就比較麻煩了,感興趣的同學可以自行去研究一下),這裏我們先定性地理解一下:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
這裏用h構造了一個特徵值x的一個分佈函數,然後將值域進行等分,對應的x點即是所需分裂點了。

再接下來,作者談了如何應對數據稀疏問題。提出的方法是在每個分裂的節點上增加一個默認的分支。
在這裏插入圖片描述
那麼問題是:這個默認的分支怎麼選出來?方法如下:
在這裏插入圖片描述
其實通俗點理解,就是分別把缺失值的實例計入左側分支與右側分支,看哪個帶來的得分更大。

第四部分。
作者在該部分主要談了工程上如何優化,以實現並行計算或分佈式。這一部分不涉及學習算法,更多是數據結構與系統設計,感興趣的同學可以瀏覽一下。日後有時間我們再來更新本篇文章的內容。

相關知識點討論

XGBboost的弱學習器爲什麼用了迴歸樹(也稱CART),而不是分類決策樹?

我的理解是XGBoost不僅可以用於分類,也可以用於迴歸。迴歸樹恰恰可以實現這兩種目標(對於分類場景,可以通過給定的閾值來將連續的迴歸預測值轉化爲離散的分類值;這與邏輯迴歸底層產生的是連續概率值,然後再轉化爲分類目標值是一樣的道理),而分類決策樹則不能。

泰勒展開

知乎上有一些文章講泰勒展開的思想,很有趣,推薦大家看一看:[https://www.zhihu.com/question/25627482]

數據存儲格式

CSC (官方說明[http://scipy-lectures.org/advanced/scipy_sparse/csc_matrix.html]),這種數據結構據說可以支持高效的列索引、列計算。在python的scipy包中,CSC有兩種構造方式
方式一:

row = np.array([0, 0, 1, 2, 2, 2])
col = np.array([0, 2, 2, 0, 1, 2])
data = np.array([1, 2, 3, 4, 5, 6])
mtx = sparse.csc_matrix((data, (row, col)), shape=(3, 3))

這種比較好理解,row、col分別是行索引、列索引,配對起來就代表着data中對應元素在矩陣中的位置。

方式二:

data = np.array([1, 4, 5, 2, 3, 6])
indices = np.array([0, 2, 2, 0, 1, 2])
indptr = np.array([0, 2, 3, 6])
mtx = sparse.csc_matrix((data, indices, indptr), shape=(3, 3))

這個稍微費解一些。這裏indices是行索引,indptr倒不是列索引,而是對應data中的數據位置,我們用示意圖解釋一下:
在這裏插入圖片描述
我們看一下打印出來的mtx內容,證明確實是一致的:
在這裏插入圖片描述

結語

本文我們一起讀了一下XGBoost的論文,過程中對於一些作者未提到細節的內容做了一些展開討論。未來我們繼續看LightGBM、CatBoost的論文時就可以有做一些有針對性的對比討論。敬請繼續關注本公衆號文章推送。
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章