GBDT回歸的原理及Python實現
程序員大咖
點擊右側關注,免費進階高級!
作者:
李小文
Github: https://gith
ub.com/tushu shu
提到GBDT回歸相信大家應該都不會覺得陌生,本文就GBDT回歸的基本原理進行講解,並手把手、肩並肩地帶您實現這一演算法。完整實現代碼請參考本人的github。
一、原理篇
我們用人話而不是大段的數學公式來講講GBDT回歸是怎麼一回事。
1.1 溫故知新
回歸樹是GBDT的基礎,之前的一篇文章曾經講過回歸樹的原理和實現。鏈接如下:
回歸樹的原理及Python實現
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 = [5.1, 6, 6.9]
第2輪殘差:[-0.1, 0, 0.1]
第3輪預測:6, 6, 6 + -0.9, 0, 0.9 + -0.08, 0, 0.07 = [5.02, 6, 6.97]
第3輪殘差:[-0.08, 0, 0.03]
看上去殘差越來越小,而這種預測方式就是GBDT演算法。
1.6 公式推導
看到這裡,相信您對GBDT已經有了直觀的認識。這麼做有什麼科學依據么,為什麼殘差可以越來越小呢?前方小段數學公式低能預警。
因此,我們需要通過用第m-1輪殘差的均值來得到函數fm,進而優化函數Fm。而回歸樹的原理就是通過最佳劃分區域的均值來進行預測。所以fm可以選用回歸樹作為基礎模型,將初始值,m-1顆回歸樹的預測值相加便可以預測y。
二、實現篇
本人用全宇宙最簡單的編程語言——Python實現了GBDT回歸演算法,沒有依賴任何第三方庫,便於學習和使用。簡單說明一下實現過程,更詳細的注釋請參考本人github上的代碼。
2.1 導入回歸樹類
回歸樹是我之前已經寫好的一個類,在之前的文章詳細介紹過,代碼請參考:
https:
/
/github.com/tushushu/imylu/blob/master/imylu/tree/regression
_tree.pyfrom import
初始化,存儲回歸樹、學習率、初始預測值和變換函數。(註:回歸不需要做變換,因此函數的返回值等於參數)
class GradientBoostingBase(object)
def
__init__(self)
:self.trees =
None
self.lr =
None
self.init_val =
None
self.fn =
lambda
x: x2.3 計算初始預測值
初始預測值即y的平均值。
def _get_init_val( self
return
sum(y) / len(y)2.4 計算殘差
def _get_residuals ( self
return
[yi -self
.fn(y_hat_i)for
yi, y_hat_iin
zip(y, y_hat)]2.5 訓練模型
訓練模型的時候需要注意以下幾點: 1. 控制樹的最大深度max_depth; 2. 控制分裂時最少的樣本量min_samples_split; 3. 訓練每一棵回歸樹的時候要乘以一個學習率lr,防止模型過擬合; 4. 對樣本進行抽樣的時候要採用有放回的抽樣方式。
def fit( self
self
.init_val =self
._get_init_val(y) n = len(y)
y_hat = [
self
.init_val] * nresiduals =
self
._get_residuals(y, y_hat)
self
.trees = []self
.lr = lrfor
_
in
range(n_estimators):idx = range(n)
if
subsample isnot
None:
k = int(subsample * n)
idx = choices(population=idx, k=k)
X_sub = [X[i]
for
iin
idx]residuals_sub = [residuals[i]
for
iin
idx]y_hat_sub = [y_hat[i]
for
iin
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
return
self
.fn(self
.init_val + sum(self
.lr * tree._predict(Xi)for
treein
self
.trees))2.7 預測多個樣本
def predict( self
return
[self
._predict(Xi)for
Xiin
X]三、效果評估
3.1 main函數
使用著名的波士頓房價數據集,按照7:3的比例拆分為訓練集和測試集,訓練模型,並統計準確度。
@run_time def main()
print(
"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上查看
//github.com/tushushu/imylu/blob/master/imylu/utils.pyhttps:
run_time - 測試函數運行時間
load_boston_house_prices - 載入波士頓房價數據
train_test_split - 拆分訓練集、測試集
get_r2 - 計算擬合優度
四、總結
GBDT回歸的原理:平均值加回歸樹
GBDT回歸的實現:加加減減for循環
【點擊成為源碼大神】
※呀!抓個正著!!
※Python爬蟲 --- 1.3 BS4庫的解析器
TAG:Python開發 |