前向分步演算法與提升樹模型
本篇部落格主要來說明前向分步演算法以及通過前向分步演算法構造的提升樹模型。
首先,我們假設某一模型公式具有如下形式:
其中我們稱為基函式,為此基函式的係數,而為基函式的相關引數,我們稱這樣的模型為加法模型;我們通過使用這樣一個模型來進行迴歸於分類任務。
然而,這樣一個模型改如何來構造呢?我們採用一種叫做前向分步演算法的方法來構造這麼一個模型。
首先我們給定損失函式如下所示:
那我們的任務就是優化的損失函式如下所示:
然而這是一個複雜的優化問題,對其直接求解往往過於複雜,因此這裡採用前向分步演算法從前向後逐個求出基函式及其係數,也就是說我們每次只需優化如下損失函式即可求出一組
李航的統計學習方法書中寫到這裡用,但是個人認為這裡的並非是樣本真實標記,其只代表一個數值,表明了每次只需優化有這這種形式的損失函式,到後面可以看出,這個實際上代表殘差,即樣本真實值和當前模型預測值之差。
接下來給出前向分步演算法的具體操作步驟:
假設存在訓練資料集,,;損失函式為;並且我們還知道基函式集,我們不知道每一個基函式的引數,我們需要通過前向分步演算法將引數求解出來
第一步:初始化模型
第二步:對於:
- 2.1 極小化如下損失:,其中為當前模型對於樣本點的預測值,我們記做,也就是對進行優化;實際上我們是要讓逼近真實值,即,而為已知量,因此將這個逼近問題轉化為
- 2.2 更新當前模型:
- 迴圈執行2.1和2.2知道構造完所有的個基函式
第三步:構造最終分類器:
以上便是前向分步演算法的具體步驟及公式
前向分步演算法中的損失函式如果是針對分類問題我們往往採取指數損失;而如果是迴歸問題,我們通常採用平方誤差損失;其實Adaboost演算法得到的模型實質上與前向分步演算法採用指數損失得到的加法模型等價。
下面我們介紹提升樹模型:
以決策樹為基函式,進行前向分步演算法得到的模型就是提升樹模型,這種以決策樹為基函式的提升方法成為提升樹(boosting tree)。如果我們最終要構造的時分類模型,我們採用二叉分類樹作為基函式;如果要構造迴歸模型,我們採用二叉迴歸樹作為基函式。
提升樹模型如果用來解決分類問題,我們只需將前向分佈演算法中的損失函式用指數損失,基函式用二叉分類樹即可得到提升樹模型;實質上我們在Adaboost演算法中將基分類器限制為二叉分類樹也可以得到提升樹模型,與前者是等價的,因此提升樹演算法是Adaboost演算法的一種特殊情況。
而提升樹模型如果用來解決迴歸問題,我們需要將前向分佈演算法中的損失函式設定為平方誤差損失,基函式使用二叉迴歸樹即可得到解決迴歸問題的提升樹模型。
因為解決分類問題就是Adaboost演算法,只不過基分類器採用了二叉分類樹,Adaboost前面已經介紹過了,所以這裡就不介紹了,這裡我們只介紹用於解決迴歸問題的提升樹模型,這裡還是採用前向分步演算法:
第一步:確定初始提升樹:
第二步:確定第顆二叉迴歸樹,其中表示這棵樹的引數:
首先可以確定的是,通過前向分步演算法我們可以通過解決這樣一個優化問題來求出第顆樹。但我們進行進一步分析,由於我們這裡採用的是平方誤差損失,故,而實際上就是當前模型對於第個樣本預測值與真實值的殘差,我們記作,因此損失函式便成為了,可以看出實際上是用來擬合當前模型的殘差的。
所以第顆樹的求取我們可以通過:
- 2.1 計算殘差
- 2.2 優化:
- 得到第顆樹,更新
第三步:得到迴歸問題的提升樹模型:
到這裡提升樹以及前向分步演算法頁介紹完畢了,下面附上提升樹相關程式,資料為波士頓房價資料:
from sklearn.preprocessing import StandardScaler
import pandas as pd
import numpy as np
from numpy import random as rd
from sklearn.datasets import load_boston
from copy import copy
np.set_printoptions(linewidth=1000, suppress=True)
class Foward(object):
def __init__(self, n):
# self.base_funcs用於存放基函式
self.base_funcs = []
x = load_boston()["data"]
y = load_boston()["target"]
self.x_train, self.x_test, self.y_train, self.y_test = train_test_split(x, y, test_size=0.3, random_state=3)
stand_transfer = StandardScaler()
self.x_train = stand_transfer.fit_transform(self.x_train)
self.x_test = stand_transfer.transform(self.x_test)
self.n = n
# 假設初始殘差為self.residual與訓練標記相同
self.residual = copy(self.y_train)
def train(self):
# acum_residual用於累加預測的殘差
acum_residual = np.zeros(self.y_train.shape)
for i in range(self.n):
tree = DecisionTreeRegressor()
param = {"max_depth": np.arange(1, 5), "min_samples_leaf": np.arange(1, 5), "min_samples_split": np.arange(2, 5)}
clsf = GridSearchCV(estimator=tree, param_grid=param, cv=10)
clsf.fit(self.x_train, self.residual)
residual_pred = clsf.predict(self.x_train)
self.base_funcs.append(clsf.best_estimator_)
acum_residual += residual_pred
# self.residual為真實殘差,計算方式為y的真實值減去預測殘差的累加就是真實殘差,下一次再用base_func擬合這個殘差
self.residual = copy(self.y_train - acum_residual)
def test(self):
y_test_predict = np.zeros(self.y_test.shape)
for model in self.base_funcs:
y_test_predict += model.predict(self.x_test)
print("===================前向分步演算法================")
print("測試集真實值:", self.y_test)
print("測試集預測值:", y_test_predict)
print("測試集均方誤差為:", mean_squared_error(y_pred=y_test_predict, y_true=self.y_test))
def run(self):
self.train()
self.test()
def main():
f = Foward(30)
f.run()
if __name__ == "__main__":
main()