XGBoost的安装与介绍
XGBoost其實是GBDT的一個工程實現,在Kaggle等比賽中被廣泛應用。同時XGBoost也對GBDT做了一些近似和優化,具體內容也發了Paper(華盛頓大學陳天奇博士),本篇博客將會對論文進行解讀。
xgboost的優點,幾個關鍵詞:scalable,end-to-end,used widely,sparsity-aware。先不用管這些花里胡哨的優點,我們就關注XGBoost是怎么發展起來的,是為了解決什么問題,怎樣有效地解決了這個問題。
XGBoost的安裝
有的人使用whl很快就安裝好了,如果你的vs版本和python不對應的話,只能自行編譯。有的人即便是安裝好了,但是導入報錯,明明有xgboost.dll,依然導入不成功,提示缺少vc2015的dll,即便是下載了扔在windows32中,依然無解。同時編譯過程也有很多坑,首先是cmake版本太低,重新安裝;configuration的時候繼續報錯,提示工程有問題。其實確實是工程有問題,因為我是直接下載zip的,這樣有一些依賴就不會自動下載,所以還是推薦使用git clone --recursive https://github.com/dmlc/xgboost,如果是在公司,還會碰到需要使用代理的問題。但其實clone也會出問題,所以其實可以手動下載XGBoost下缺失的子文件夾。然后使用cmake configuration之后再generate,得到sln。然而我編譯時報錯3000多個,最后又試了穩定的09版本才編譯成功,得到了dll。
git config –global http.proxy http://user:password@http://10.10.10.10:8080git config --global http.sslVerify false
Boosting
了解過集成學習的都知道集成學習分為三大類:bagging,boosting,stacking。boosting是串行的。訓練過程:每一個基分類器都要依靠前一個的結果,對于i出錯的樣本加大權重,再送入i+1分類器中繼續學習。預測過程:對n個基分類器的結果加權求和。這里有兩個權重:訓練樣本的權重(訓練時用);基學習器的權重(預測時用)。這兩個權重怎么得到呢,繼續往下看。
boosting的思想其實就很類似于深度學習了,都是有一個優化目標,不斷迭代地去逼近。不同的是深度學習模型結構是固定的,通過多次重復地輸入樣本來調整權重;而boosting中模型是不斷生長的,變化的是樣本的權重。同樣是調整權重,一個是調整模型中參數,一個是調整樣本的權重,其實沒有什么不同。
像深度學習一樣,boosting算法也有三要素:1.訓練數據。對于我們來說訓練數據是一個個樣本,但其實對于boosting來說是由訓練樣本初步得到的弱學習器(一般是決策樹);2.模型。模型決定了如何由弱學習器得到最終的輸出,在boosting中,模型一般是加法模型additive model:對多棵樹的結果進行加權相加。3.損失函數。很顯然,有了損失函數,才能對模型進行優化。
加法模型
(基分類器相當于加法模型中的基函數)
損失函數-Adaboost
加法模型其實沒什么好說的,更重要的是損失函數,而損失函數的選取,就決定了四種不同的方法。
https://zhuanlan.zhihu.com/p/42740654
表-深入淺出http://www.52caml.com/head_first_ml/ml-chapter6-boosting-family/
AdaBoost采用的是e指數損失。。AdaBoost其實是加法模型下,用前向分步算法的一個特例:損失函數為指數函數。下面簡要說明:
前向分步算法在優化求解時是逐個基函數求解,即是逐個求解.當損失函數是指數函數:,那么
上面這個公式的意思是將個基分類器的最終輸出分解第個的輸出和第m個基分類器的輸出之和(這也是加法模型的本意),這樣exp指數位置由兩個加法項構成:第一項是標簽和第m-1的輸出,它和要求解的是無關的(我們關心的是m)。令,那么可以由下式求得:
可以得到:
,表示誤差率:錯分樣本的權重之和。
,因為對任意,當分類器預測與標簽異號時才是真正的誤差。
注意到之前定義的雖然與最小化第m個基函數無關,但它包含了m-1,所以在每輪迭代中是變化的:。事實上,這個對應了AdaBoost中樣本權重的變化:原來的權重乘以該樣本對應的損失作為新權重,損失越大,新的權重越大。
手把手Adaboosthttps://zhuanlan.zhihu.com/p/27126737
推導https://blog.csdn.net/watermelon12138/article/details/84785736
劉建平https://www.cnblogs.com/pinard/p/6133937.html
AdaBoost采用的是加權表決法,所以得到基分類器和分類器系數就可以得到最終的分類函數:
損失函數-GBDT
從名字上來看,GBDT分為兩部分,一個是GB(Grdient Boosting),它引入了梯度的概念。GBDT與AdaBoost的不同主要是思想不同,同樣是加法模型,后者是通過每次迭代中調整樣本權重,而前者是調整每次迭代時的擬合目標:Freidman提出了梯度提升(gradient boosting)算法:使用損失函數的負梯度來擬合本輪的損失近似值。
GBDT中使用的損失函數可以是多樣的,可以是均方差,絕對損失,指數損失函數。當損失函數是均方差時(CART樹一般使用平方誤差函數),對其求梯度(導數)就變成殘差了。這就是一般意義上大家把GBDT下一棵樹將要學習的目標是殘差(和殘差網絡類似)的原因,但我們需要知道其實擬合殘差是擬合負梯度的一個特例。當使用的是絕對損失,前向分布算法不好求解,使用損失函數的負梯度來擬合本輪的損失近似值更易求解。當使用指數損失函數時,GBDT退化為AdaBoost算法。
使用負梯度的好處還有就是可以應對新增的正則項,因為只是經驗損失最小的話容易過擬合,為了防止過擬合需要增加正則項,而如果有正則項,那么損失函數就不僅僅是殘差了。https://www.zhihu.com/question/63560633
GBDT的第二部分DT(Dcision Tree),字面意思是決策樹,但實際上在GBDT中的決策樹必須是回歸樹,它限定了弱學習器必須是回歸樹。因為預測殘差的過程是一個回歸的過程,是對數值的預測。這一點看GBDT的另外一個名字就更清楚了:?MART(Multiple?Additive?Regression?Tree)
GBDT除了GB和DT,后來還演進了一個重要的部分:Shrinkage。在最開始提到的GBDT使用的加法模型對各個樹是沒有添加權重的,因為每一棵樹都是最大程度去去擬合殘差。之前提到在擬合殘差的過程近似于梯度下降的過程,有時候為了擬合殘差有可能矯枉過正,所以我們其實可以對回歸樹增加權重,等價于梯度下降中的步長,意義是漸進地去擬合。
原來前i棵樹的預測結果:y(1~i)=sum(y1,...,yi)
使用Shrinkage之后:y(1~i)=y(1~i-1)+step*yi
白開水加糖https://www.cnblogs.com/peizhe123/p/6105696.html
從Grdient Descend 到GradientBoostinghttps://blog.csdn.net/qq_19446965/article/details/82079624
XGBoost
首先定義了樹的復雜度,而為了定義復雜度,需要將樹的結構分為樹結構部分q和葉子權重部分w。這里的葉子節點權重怎么理解呢?就算使用了shrinkage,也只是每棵樹有一個權重啊。其實這里的權重是葉子節點輸出,當做回歸時,這個輸出是值,當做分類時,輸出是標簽。而因為GBDT一般使用CART回歸樹,其在分裂節點時使用的是MSE均方差準則,MSE越小,意味著該部分數據越“純”,這樣在最終預測的時候就可以以所有落在該節點的數據值的均值作為預測值。
從上式中可以看到,復雜度由葉子節點構成:葉子節點的個數T,葉子節點的權重。為什么葉子節點這么重要呢,不考慮樹的深度嗎?定義復雜度是為了控制過擬合,葉子節點個數太多分得太細容易過擬合;葉子節點權重???
具體到第t棵樹,以前t-1棵樹為基礎,那么優化目標是
?不能直接期望第t棵樹就達到目標值,所以會有一個constant的存在。注意我們把看作,利用泰勒二階展開,達到優化目標的近似值,只需要關心損失函數的一階導和二階導(當然要求損失函數是可導的)。GBDT的負梯度其實是一階泰勒展開,XGBoost二階泰勒展開更加準確。
去掉只與t-1有關的常數項,得到:
的輸出其實就是葉子節點的取值(葉子節點權重)。到這里可以看到第t棵樹的損失函數以n個樣本點的和構成,我們可以換個思路,以葉子節點進行分組:對每個葉子統計落在該葉子節點的所有樣本,最后按照葉子節點進行相加。那么第t棵樹的優化目標可以寫為:
各個葉子目標子式相獨立,每一個可以看作是一元二次方程,則可以得到每個葉子節點的權重值:
第t棵樹對應的最小損失:
注意,當樹結構確定時才能使用上式求出葉子節點權重,而我們首先要確定樹結構。因為每種樹結構的損失是可以計算出來的,所以一個方法是窮舉樹結構,對每一個樹結構計算其對應的損失函數,然后選取其最小的那個。更常見的是貪心法:通過分裂節點得到樹結構,在每次分裂的時候只考慮當前的節點,使得當前分裂前后的增益最大,而增益就是分裂前后兩種樹結構的損失函數之差:
既然是一個個分裂節點試,總還是要有規律一點:看作兩層for循環,外循環是遍歷各個特征,內循環是遍歷該特征下的每個值。兩層循環的分裂過程是最耗時的,XGBoost分別對特征的取和分裂點的選取做了優化。
針對特征選取的外循環,其實是可以使用多線程并行化的。Boosting不同弱學習器之間無法并行,但是對于單個弱學習器來說,可以并行地尋找分裂的最大增益。
內循環在訪問特征值的時候也需要按大小順序遍歷,所以可以提前將每個特征下的順序排好,存放在block結構中。block的大小需要合理設置,一方面充分利用CPU緩存進行讀取加速(cache-aware access),一方面將block compression儲存到硬盤得到更大的IO。為了進一步增加速度,在內循環的時候遍歷粒度可以增大,只選擇分位點(如四分位,四分之三分位點)進行分裂。這就是XGBoost中的分位點近似法。對每一個特征確定一個近似點集合,根據這個集合是重復使用還是每次分裂時重新確定,近似分位法又分為全局策略和局部策略。因為是Boosting算法,樹之間無法并行,但是計算增益點的時候可以并行化:不再是兩層循環,而是不同的特征同時計算各自的增益。
陳天奇提出了一種帶權重的分位草圖Weighted Quantile Sketch。這里我們假定所使用的特征是第k個特征,目的是在這個維度中找到分離點,相當于一個閾值,把樹分成左右兩部分。對數據在這個維度上排序,我們可以精確得到將數據分成某個比例的分割點??梢远x一個函數,表示第k個特征的值k小于z的樣本占全部樣本的比值,那么近似分位數方法就不是尋找滿足特定比例的一個分割點,而是比例在一個區間內的幾個分割點。在這個區間中,eps是分辨率,所以理論上會得到1/eps個分割點。重寫目標函數,將其配方,可以看作是一個平方誤差函數:標簽是g/h,二階導數h是權重。當損失函數是square loss時,二階導數恒為2,此時每個樣本的權重相等;當損失函數是log loss時,二階導數是一元二次函數,開口朝下,pred=0.5時權重最大,這意味著預測越是不準確,權重越大,分桶時被切分得更細。
對XGBoost的總結
XGBoost的一大特點是scalability,這個尺度可變指的是數據量,意味著XGBoost可以在單機上實現十幾倍的加速,在分布式中可以應對billion是級別的數據。這得益于對于稀疏數據sparse data的處理,并且使用了帶權重的分位數草圖近似算法。當然,為了加速還可以使用分布式和并行計算。XGBoost還使用了核外計算out-of-core computation,這部分結構稱為cache-aware block。
集成學習其實是利用多個弱學習器(樹)的輸出,對其加權求和得到預測。這里的樹使用的是回歸樹,如CART。樹包含兩個屬性,一個是樹的結構,將n維的輸入轉換為T維的輸出,T即是葉子結點數;第二個是葉子結點的權重,因為回歸樹的輸出不是簡單地投票給某個葉子結點,而是對每一個葉子結點輸出一個連續的得分,所以我們可以對每一個葉子結點賦予一個權重。那么最終對X的輸出就是每棵樹。樹的葉子節點個數的權重平方和一起用來控制模型的復雜度,防止過擬合。當正則項為0時,就退化為傳統的GBDT。為了防止過擬合,除了之前添加的正則項,還有兩種方法。方法一是Shrinkage,它的思想和learning rate是類似的,對于較新的樹,以一定比例增加其權重;方法二是對特征(列)進行下采樣,這個方法其實在隨機森林算法中也用到了。
在優化目標函數中,因為變量是函數,不能使用傳統的優化方法在歐式空間中優化,而是使用了叫做追加訓練法additive manner的訓練方法。在這種方法中,樹依舊是一棵棵訓練的,和boosting的思想一樣,需要注意的是這里使用的貪婪算法的思想,每加一棵樹保持之前的樹不變,寄希望于新加的樹使得優化目標函數最小。參考文獻12,可以使用泰勒二階展開對此時的目的函數求近似:將新加的樹看作是自變量的增長量detla。
二階近似時,目標函數可以看作是權重w的二次函數,所以可以解得目標函數最小時w的取值,這時目標函數的表達式也可以得到,并且可以用于衡量樹結構的質量水平,類似于不純度impurity score。注意在第t棵樹的結構是固定時才可以這樣計算,換言之我們只能通過枚舉樹的結構選擇目標函數最小的那個樹的結構作為下一棵樹,但是這樣明顯是不現實的。
XGBoost使用了貪心算法,從單一的葉子結點開始,不斷地添加分支,計算分裂前后的loss變化。貪心算法是在確定結點之后怎么分的問題,那么如何確定最優的結點呢?這里繼續使用貪心算法,叫做exact possible algorithm。沒有枚舉所有樹的結構,這里枚舉的是所有的特征,在連續的特征上枚舉所有可能的splits,為了減少計算量,需要對特征進行排序,按序遍歷,計算梯度統計。
算法的框架是首先根據特征分布的百分比找到候選的splitting points,然后將連續的特征根據分離點映射到桶buckets中,由aggregated statistics找到最優解。根據何時提供候選點,有兩種近似方法,一種是全局的global variant,在初始構建樹的過程中就提供候選點,local variant則是在每次slit之后re-proposal,全局的方法提供的次數更少,但是需要更多的候選點,而local因為在每次splits之后對候選點進行了refine提純,所以適合于更深的樹。
對稀疏數據的處理。造成稀疏性的原因大致有三種:缺失值的存在;某項統計值高頻率地取0;人為的編碼,如獨熱編碼。論文在每個樹結點處增加了一個默認的方向(左子樹或者右子樹),用于處理系數值,這也是為什么XGBoost可以處理缺失值的原因。XGBoost不是簡單地指定左或者右,而是根據數據自動設定的。缺失值影響的其實是分割點的確定。分割點的確定方法和之前一樣,無非是排序,遍歷,求導數,增益。對于缺失值,排序方法不同,位置也不同:增序排序,缺失值排在第一個,被分進左子樹;降序則被分在右子樹,最后同樣是選擇增益最大的那種結構。
最耗時的步驟是排序,所以這篇文章在系統上做了一些設計。設計了一個名為block的結構用于存放數據,在這里數據不是以樣本為單位,而是以特征為單位,每一列是一個特征,并對該特征的數值進行排序。在近似算法中,可以使用多個block,這樣尋找分位數的操作變成線性的掃描,這對于local proposal是更有意義的,因為候選會更頻繁地被產生。block這種結構還可以減少尋找分割點過程中的計算量。
使用block帶來的問題是統計梯度的過程中對于內存的訪問是不連續的。對于excat greedy算法,為每一個線程分配一個外接buffer。對于近似算法,做法是選擇一個合適大小的block size,size太小會導致并行效率低;太大會造成無法載入CPU,丟失緩存。這里選擇的是存儲在一個block中最多的樣本樹,這反映了統計梯度的內存成本。
模型的保存與加載,推薦使用序列化的pickle.dump與pickle.load,否則predict的時候會報奇怪的錯誤。
Reference:
1.字節跳動架構師https://zhuanlan.zhihu.com/p/30339807
2.淺談https://www.jianshu.com/p/d55f7aaac4a7
3.https://www.jianshu.com/p/6fba1bcb303c
4類別特征https://www.biaodianfu.com/categorical-features.html
5.講義https://www.cnblogs.com/yumoye/p/10421153.html
6.近似分位算法https://blog.csdn.net/anshuai_aw1/article/details/83025168?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase
7.終于明白了https://cloud.tencent.com/developer/article/1513111
8.https://www.cnblogs.com/jiangxinyang/p/9248154.html
9.標點符https://www.biaodianfu.com/xgboost.html
總結
以上是生活随笔為你收集整理的XGBoost的安装与介绍的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于PCA和SVM的人脸识别
- 下一篇: Android之Intent深入