GBDT迴歸的原理及Python實現
提到GBDT迴歸相信大家應該都不會覺得陌生(不陌生你點進來幹嘛[捂臉]),本文就GBDT迴歸的基本原理進行講解,並手把手、肩並肩地帶您實現這一演算法。
完整實現程式碼請參考本人的p。。。哦不是。。。github:
1。 原理篇
我們用人話而不是大段的數學公式來講講GBDT迴歸是怎麼一回事。
1。1 溫故知新
迴歸樹是GBDT的基礎,之前的一篇文章曾經講過迴歸樹的原理和實現。連結如下:
1。2 預測年齡
仍然以預測同事年齡來舉例,從《迴歸樹》那篇文章中我們可以知道,如果需要透過一個常量來預測同事的年齡,平均值是最佳選擇之一。
1。3 年齡的殘差
我們不妨假設同事的年齡分別為5歲、6歲、7歲,那麼同事的平均年齡就是6歲。所以我們用6歲這個常量來預測同事的年齡,即[6, 6, 6]。每個同事年齡的殘差 = 年齡 - 預測值 = [5, 6, 7] - [6, 6, 6],所以殘差為[-1, 0, 1]
1。4 預測年齡的殘差
為了讓模型更加準確,其中一個思路是讓殘差變小。如何減少殘差呢?我們不妨對殘差建立一顆迴歸樹,然後預測出準確的殘差。假設這棵樹預測的殘差是[-0。9, 0, 0。9],將上一輪的預測值和這一輪的預測值求和,每個同事的年齡 = [6, 6, 6] + [-0。9, 0, 0。9] = [5。1, 6, 6。9],顯然與真實值[5, 6, 7]更加接近了, 年齡的殘差此時變為[-0。1, 0, 0。1],預測的準確性得到了提升。
1。5 GBDT
重新整理一下思路,假設我們的預測一共迭代3輪 年齡:[5, 6, 7]
第1輪預測:[6, 6, 6] (平均值)
第1輪殘差:[-1, 0, 1]
第2輪預測:[6, 6, 6] (平均值) + [-0。9, 0, 0。9] (第1顆迴歸樹) = [5。1, 6, 6。9]
第2輪殘差:[-0。1, 0, 0。1]
第3輪預測:[6, 6, 6] (平均值) + [-0。9, 0, 0。9] (第1顆迴歸樹) + [-0。08, 0, 0。07] (第2顆迴歸樹) = [5。02, 6, 6。97]
第3輪殘差:[-0。08, 0, 0。03]
看上去殘差越來越小,而這種預測方式就是GBDT演算法。
1。6 公式推導
看到這裡,相信您對GBDT已經有了直觀的認識。這麼做有什麼科學依據麼,為什麼殘差可以越來越小呢?前方小段數學公式低能預警。
假設要做m輪預測,預測函式為Fm,初始常量或每一輪的迴歸樹為fm,輸入變數為X,有:
設要預測的變數為y,採用MSE作為損失函式:
我們知道泰勒公式的一階展開式是長成這個樣子滴:
如果:
那麼,根據式3和式4可以得出:
根據式2可以知道,損失函式的一階偏導數為:
根據式6可以知道,損失函式的二階偏導數為:
蓄力結束,開始放大招。根據式1,損失函式的一階導數為:
根據式5,將式8進一步展開為:
令式9,即損失函式的一階導數為0,那麼:
將式6,式7代入式9得到:
因此,我們需要透過用第m-1輪殘差的均值來得到函式fm,進而最佳化函式Fm。而回歸樹的原理就是透過最佳劃分區域的均值來進行預測。所以fm可以選用迴歸樹作為基礎模型,將初始值,m-1顆迴歸樹的預測值相加便可以預測y。
2。 實現篇
本人用全宇宙最簡單的程式語言——Python實現了GBDT迴歸演算法,沒有依賴任何第三方庫,便於學習和使用。簡單說明一下實現過程,更詳細的註釋請參考本人github上的程式碼。
2。1 匯入迴歸樹類
迴歸樹是我之前已經寫好的一個類,在之前的文章詳細介紹過,程式碼請參考:
from
。。tree。regression_tree
import
RegressionTree
2。2 建立GradientBoostingBase類
初始化,儲存迴歸樹、學習率、初始預測值和變換函式。(注:迴歸不需要做變換,因此函式的返回值等於引數)
class
GradientBoostingBase
(
object
):
def
__init__
(
self
):
self
。
trees
=
None
self
。
lr
=
None
self
。
init_val
=
None
self
。
fn
=
lambda
x
:
x
2。3 計算初始預測值
初始預測值即y的平均值。
def
_get_init_val
(
self
,
y
):
return
sum
(
y
)
/
len
(
y
)
2。4 計算殘差
def
_get_residuals
(
self
,
y
,
y_hat
):
return
[
yi
-
self
。
fn
(
y_hat_i
)
for
yi
,
y_hat_i
in
zip
(
y
,
y_hat
)]
2。5 訓練模型
訓練模型的時候需要注意以下幾點:
1。 控制樹的最大深度max_depth;
2。 控制分裂時最少的樣本量min_samples_split;
3。 訓練每一棵迴歸樹的時候要乘以一個學習率lr,防止模型過擬合;
4。 對樣本進行抽樣的時候要採用有放回的抽樣方式。
def
fit
(
self
,
X
,
y
,
n_estimators
,
lr
,
max_depth
,
min_samples_split
,
subsample
=
None
):
self
。
init_val
=
self
。
_get_init_val
(
y
)
n
=
len
(
y
)
y_hat
=
[
self
。
init_val
]
*
n
residuals
=
self
。
_get_residuals
(
y
,
y_hat
)
self
。
trees
=
[]
self
。
lr
=
lr
for
_
in
range
(
n_estimators
):
idx
=
range
(
n
)
if
subsample
is
not
None
:
k
=
int
(
subsample
*
n
)
idx
=
choices
(
population
=
idx
,
k
=
k
)
X_sub
=
[
X
[
i
]
for
i
in
idx
]
residuals_sub
=
[
residuals
[
i
]
for
i
in
idx
]
y_hat_sub
=
[
y_hat
[
i
]
for
i
in
idx
]
tree
=
RegressionTree
()
tree
。
fit
(
X_sub
,
residuals_sub
,
max_depth
,
min_samples_split
)
self
。
_update_score
(
tree
,
X_sub
,
y_hat_sub
,
residuals_sub
)
y_hat
=
[
y_hat_i
+
lr
*
res_hat_i
for
y_hat_i
,
res_hat_i
in
zip
(
y_hat
,
tree
。
predict
(
X
))]
residuals
=
self
。
_get_residuals
(
y
,
y_hat
)
self
。
trees
。
append
(
tree
)
2。6 預測一個樣本
def
_predict
(
self
,
Xi
):
return
self
。
fn
(
self
。
init_val
+
sum
(
self
。
lr
*
tree
。
_predict
(
Xi
)
for
tree
in
self
。
trees
))
2。7 預測多個樣本
def
predict
(
self
,
X
):
return
[
self
。
_predict
(
Xi
)
for
Xi
in
X
]
3 效果評估
3。1 main函式
使用著名的波士頓房價資料集,按照7:3的比例拆分為訓練集和測試集,訓練模型,並統計準確度。
@run_time
def
main
():
(
“Tesing the accuracy of GBDT regressor。。。”
)
X
,
y
=
load_boston_house_prices
()
X_train
,
X_test
,
y_train
,
y_test
=
train_test_split
(
X
,
y
,
random_state
=
10
)
reg
=
GradientBoostingRegressor
()
reg
。
fit
(
X
=
X_train
,
y
=
y_train
,
n_estimators
=
4
,
lr
=
0。5
,
max_depth
=
2
,
min_samples_split
=
2
)
get_r2
(
reg
,
X_test
,
y_test
)
3。2 效果展示
最終擬合優度0。851,執行時間5。0秒,效果還算不錯~
3。3 工具函式
本人自定義了一些工具函式,可以在github上檢視
1。 run_time - 測試函式執行時間
2。 load_boston_house_prices - 載入波士頓房價資料
3。 train_test_split - 拆分訓練集、測試集
4。 get_r2 - 計算擬合優度
總結
GBDT迴歸的原理:平均值加回歸樹
GBDT迴歸的實現:加加減減for迴圈