GBDT迴歸的原理及Python實現
提到GBDT迴歸相信大家應該都不會覺得陌生,本文就GBDT迴歸的基本原理進行講解,並手把手、肩並肩地帶您實現這一演算法。完整實現程式碼請參考本人的github。
一、原理篇
我們用人話而不是大段的數學公式來講講GBDT迴歸是怎麼一回事。
1.1 溫故知新
迴歸樹是GBDT的基礎,之前的一篇文章曾經講過迴歸樹的原理和實現。連結如下:
OA==&mid=2653557207&idx=1&sn=65d635e4b2b2d514c5cf472317001f2d&chksm=806e3f6ab719b67c01d1891eaf4524e8318983481cfbc6cefe555156d6858e02f8ffeb12b859&scene=21#wechat_redirect" target="_blank" rel="nofollow,noindex">迴歸樹的原理及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.py
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.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上檢視
https://github.com/tushushu/imylu/blob/master/imylu/utils.py● run_time - 測試函式執行時間 ● load_boston_house_prices - 載入波士頓房價資料 ● train_test_split - 拆分訓練集、測試集
● get_r2 - 計算擬合優度
四、總結
GBDT迴歸的原理:平均值加回歸樹
GBDT迴歸的實現:加加減減for迴圈
原文釋出時間為:2018-09-7
本文作者:xxx
本文來自雲棲社群合作伙伴“ Python中文社群 ”,瞭解相關資訊可以關注“ Python中文社群 ”。