這兩個(gè)算法都是集成學(xué)習(xí)了分類回歸樹模型,先討論是怎么集成的。
集成的方法是 Gradient Boosting
比如我要擬合一個(gè)數(shù)據(jù)如下:
創(chuàng)新互聯(lián)專業(yè)為企業(yè)提供那坡網(wǎng)站建設(shè)、那坡做網(wǎng)站、那坡網(wǎng)站設(shè)計(jì)、那坡網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計(jì)與制作、那坡企業(yè)網(wǎng)站模板建站服務(wù),10年那坡做網(wǎng)站經(jīng)驗(yàn),不只是建網(wǎng)站,更提供有價(jià)值的思路和整體網(wǎng)絡(luò)服務(wù)。
第一次建了一個(gè)模型如上圖中的折線,效果不是很理想,然后要新建一個(gè)模型來綜合一下結(jié)果,那么第二個(gè)模型如何建,我們將實(shí)際目標(biāo)值和我們第一個(gè)模型的預(yù)測的差值 作為第二次模型的目標(biāo)值如下圖再建一個(gè)模型:
然后不斷地新建新的模型,過程如下:
最后就能集成這些模型不斷提升預(yù)測的精度。
步驟如下:
損失函數(shù):
最小化目標(biāo):
對每一個(gè)基學(xué)習(xí)器求偏導(dǎo):
這兩個(gè)算法的基學(xué)習(xí)器都是分類回歸樹,也就是先分類然后回歸的樹,這里采用決策樹來做特征分類。
建立決策樹要考慮主要的問題是,我們?nèi)绾握业胶线m的特征和合適的切分點(diǎn)來做數(shù)據(jù)集劃分。判斷標(biāo)準(zhǔn)是什么。
可以采用遍歷的方式,遍歷每一個(gè)特征的每一個(gè)數(shù)據(jù),注意為了能夠快速劃分?jǐn)?shù)據(jù)集,在分某一個(gè)特征的時(shí)候,就根據(jù)這個(gè)特征進(jìn)行排序,這樣切割數(shù)據(jù)集的時(shí)候就不要每一個(gè)數(shù)據(jù)重新進(jìn)行和切分點(diǎn)的閾值進(jìn)行比較。
劃分依據(jù)是分類之后的數(shù)據(jù)集的目標(biāo)值的方差和要下降的最多(對于目標(biāo)值是連續(xù)值)。
假設(shè)下面代碼中最后一列為目標(biāo)值:
def err_cnt(dataSet):
'''回歸樹的劃分指標(biāo)
input: dataSet(list):訓(xùn)練數(shù)據(jù)
output: m*s^2(float):總方差
'''
data = np.mat(dataSet)
return np.var(data[:, -1]) * np.shape(data)[0]
這樣就可以建立每一顆樹。
但是考慮到遍歷每一個(gè)特征,還要遍歷每一個(gè)特征的每一個(gè)值會(huì)效率很低,所以lightgbm相對xgboost有個(gè)改進(jìn),就是對于某一個(gè)特征的值得分布建立一個(gè)直方圖,然后根據(jù)直方圖的切分點(diǎn)來對數(shù)據(jù)集進(jìn)行分割。
對于這個(gè)地方有個(gè)重要的參數(shù):
max_bin,也就是我這里每一個(gè)特征畫直方圖最大的柱子的個(gè)數(shù)有多少個(gè),如果畫的越多說明分得越細(xì),所以在調(diào)整這個(gè)參數(shù)的時(shí)候要自己先對特征的分布有個(gè)把握,我的特征是否需要這么多個(gè)bin來繪制直方圖。直方圖有個(gè)好處,子節(jié)點(diǎn)的兄弟節(jié)點(diǎn)的直方圖只要用父節(jié)點(diǎn)減去子節(jié)點(diǎn)就可以獲得該兄弟節(jié)點(diǎn)的直方圖。
建立了每一個(gè)樹之后要考慮過擬合的問題,例如假如每一個(gè)葉子節(jié)點(diǎn)上面只有一個(gè)數(shù)據(jù),當(dāng)然就能完美擬合訓(xùn)練數(shù)據(jù)集,但是可能泛化能力就很差,也就是我的樹建的很深,這里有兩個(gè)參數(shù):
min_data_in_leaf
表示一個(gè)葉子節(jié)點(diǎn)上面最少有多少個(gè)數(shù)據(jù),要根據(jù)訓(xùn)練數(shù)據(jù)集的目標(biāo)值的分布來看,有可能有的目標(biāo)值就真的有一個(gè)偏離非常遠(yuǎn)(例如平安數(shù)據(jù)大賽中有的目標(biāo)賠率非常高,可能是發(fā)生了重大事故),你實(shí)在要擬合這個(gè)數(shù)據(jù),只能把這個(gè)參數(shù)調(diào)成1
min_sum_hessian_in_leaf
這個(gè)參數(shù)表示一個(gè)葉子上的數(shù)據(jù)的權(quán)重的和,如果調(diào)的很小,也就是這個(gè)葉子上面的數(shù)據(jù)越少,也有過擬合的風(fēng)險(xiǎn),調(diào)的越大說明葉子上面數(shù)據(jù)越多,要根據(jù)數(shù)據(jù)集的目標(biāo)值的分布來確定
n_estimators 表示有多少顆樹
num_leaves表示有多少葉子,請注意,lightgbm和xgboost建立一棵樹的時(shí)候有不同(他們并不是完整的二叉樹,也就是num_leaves可以小于2^max_depth)
xgboost建樹的過程如下(level-wise tree):
lightgbm建樹的過程如下(leaf-wise tree):
優(yōu)先生長殘差比較大的節(jié)點(diǎn)。上圖中在第二步后會(huì)選擇生長殘差較大的下一層而不是補(bǔ)齊右子樹的子節(jié)點(diǎn)。
lightgbm建立新的模型的時(shí)候?qū)τ诿恳粋€(gè)數(shù)據(jù)并不是一視同仁的,他會(huì)做一個(gè)采樣,選擇梯度(殘差)較大的數(shù)據(jù)作為新的模型的輸入。
對于調(diào)參有個(gè)博客介紹的很好:https://www.analyticsvidhya.com/blog/2016/03/complete-guide-parameter-tuning-xgboost-with-codes-python/
最后本文放一段代碼 演示如何建立一顆分類回歸樹:
import numpy as np
import pickle
class node:
'''樹的節(jié)點(diǎn)的類
'''
def __init__(self, fea=-1, value=None, results=None, right=None, left=None):
self.fea = fea # 用于切分?jǐn)?shù)據(jù)集的屬性的列索引值
self.value = value # 設(shè)置劃分的值
self.results = results # 存儲(chǔ)葉節(jié)點(diǎn)的值
self.right = right # 右子樹
self.left = left # 左子樹
def load_data(data_file):
'''導(dǎo)入訓(xùn)練數(shù)據(jù)
input: data_file(string):保存訓(xùn)練數(shù)據(jù)的文件
output: data(list):訓(xùn)練數(shù)據(jù)
'''
data = []
f = open(data_file)
for line in f.readlines():
sample = []
lines = line.strip().split("\t")
for x in lines:
sample.append(float(x)) # 轉(zhuǎn)換成float格式
data.append(sample)
f.close()
return data
def split_tree(data, fea, value):
'''根據(jù)特征fea中的值value將數(shù)據(jù)集data劃分成左右子樹
input: data(list):訓(xùn)練樣本
fea(float):需要?jiǎng)澐值奶卣鱥ndex
value(float):指定的劃分的值
output: (set_1, set_2)(tuple):左右子樹的聚合
'''
set_1 = [] # 右子樹的集合
set_2 = [] # 左子樹的集合
for x in data:
if x[fea] >= value:
set_1.append(x)
else:
set_2.append(x)
return (set_1, set_2)
def leaf(dataSet):
'''計(jì)算葉節(jié)點(diǎn)的值
input: dataSet(list):訓(xùn)練樣本
output: np.mean(data[:, -1])(float):均值
'''
data = np.mat(dataSet)
return np.mean(data[:, -1])
def err_cnt(dataSet):
'''回歸樹的劃分指標(biāo)
input: dataSet(list):訓(xùn)練數(shù)據(jù)
output: m*s^2(float):總方差
'''
data = np.mat(dataSet)
return np.var(data[:, -1]) * np.shape(data)[0]
def build_tree(data, min_sample, min_err):
'''構(gòu)建樹
input: data(list):訓(xùn)練樣本
min_sample(int):葉子節(jié)點(diǎn)中最少的樣本數(shù)
min_err(float):最小的error
output: node:樹的根結(jié)點(diǎn)
'''
# 構(gòu)建決策樹,函數(shù)返回該決策樹的根節(jié)點(diǎn)
if len(data) <= min_sample:
return node(results=leaf(data))
# 1、初始化
best_err = err_cnt(data)
bestCriteria = None # 存儲(chǔ)最佳切分屬性以及最佳切分點(diǎn)
bestSets = None # 存儲(chǔ)切分后的兩個(gè)數(shù)據(jù)集
# 2、開始構(gòu)建CART回歸樹
feature_num = len(data[0]) - 1
for fea in range(0, feature_num):
feature_values = {}
for sample in data:
feature_values[sample[fea]] = 1
for value in feature_values.keys():
# 2.1、嘗試劃分
(set_1, set_2) = split_tree(data, fea, value)
if len(set_1) < 2 or len(set_2) < 2:
continue
# 2.2、計(jì)算劃分后的error值
now_err = err_cnt(set_1) + err_cnt(set_2)
# 2.3、更新最優(yōu)劃分
if now_err < best_err and len(set_1) > 0 and len(set_2) > 0:
best_err = now_err
bestCriteria = (fea, value)
bestSets = (set_1, set_2)
# 3、判斷劃分是否結(jié)束
if best_err > min_err:
right = build_tree(bestSets[0], min_sample, min_err)
left = build_tree(bestSets[1], min_sample, min_err)
return node(fea=bestCriteria[0], value=bestCriteria[1], \
right=right, left=left)
else:
return node(results=leaf(data)) # 返回當(dāng)前的類別標(biāo)簽作為最終的類別標(biāo)簽
def predict(sample, tree):
'''對每一個(gè)樣本sample進(jìn)行預(yù)測
input: sample(list):樣本
tree:訓(xùn)練好的CART回歸樹模型
output: results(float):預(yù)測值
'''
# 1、只是樹根
if tree.results != None:
return tree.results
else:
# 2、有左右子樹
val_sample = sample[tree.fea] # fea處的值
branch = None
# 2.1、選擇右子樹
if val_sample >= tree.value:
branch = tree.right
# 2.2、選擇左子樹
else:
branch = tree.left
return predict(sample, branch)
def cal_error(data, tree):
''' 評估CART回歸樹模型
input: data(list):
tree:訓(xùn)練好的CART回歸樹模型
output: err/m(float):均方誤差
'''
m = len(data) # 樣本的個(gè)數(shù)
n = len(data[0]) - 1 # 樣本中特征的個(gè)數(shù)
err = 0.0
for i in range(m):
tmp = []
for j in range(n):
tmp.append(data[i][j])
pre = predict(tmp, tree) # 對樣本計(jì)算其預(yù)測值
# 計(jì)算殘差
err += (data[i][-1] - pre) * (data[i][-1] - pre)
return err / m
def save_model(regression_tree, result_file):
'''將訓(xùn)練好的CART回歸樹模型保存到本地
input: regression_tree:回歸樹模型
result_file(string):文件名
'''
with open(result_file, 'wb') as f:
pickle.dump(regression_tree, f)
if __name__ == "__main__":
# 1、導(dǎo)入訓(xùn)練數(shù)據(jù)
print ("----------- 1、load data -------------")
data = load_data("sine.txt")
# 2、構(gòu)建CART樹
print ("----------- 2、build CART ------------")
regression_tree = build_tree(data, 30, 0.3)
# 3、評估CART樹
print ("----------- 3、cal err -------------")
err = cal_error(data, regression_tree)
print ("\t--------- err : ", err)
# 4、保存最終的CART模型
print ("----------- 4、save result -----------")
save_model(regression_tree, "regression_tree")
數(shù)據(jù)格式如下:
數(shù)據(jù)中間用tab鍵隔開 前面的列為特征,最后一列為目標(biāo)值。