對xgboost和lightgbm的理解及其調參應該關註的點
集成的方法是 Gradient Boosting
比如我要擬合一個數據如下:
第一次建了一個模型如上圖中的折線,效果不是很理想,然後要新建一個模型來綜合一下結果,那麽第二個模型如何建,我們將實際目標值和我們第一個模型的預測的差值 作為第二次模型的目標值如下圖再建一個模型:
然後不斷地新建新的模型,過程如下:
最後就能集成這些模型不斷提升預測的精度。
步驟如下:
損失函數:
最小化目標:
對每一個基學習器求偏導:
這兩個算法的基學習器都是分類回歸樹,也就是先分類然後回歸的樹,這裏采用決策樹來做特征分類。
建立決策樹要考慮主要的問題是,我們如何找到合適的特征和合適的切分點來做數據集劃分。判斷標準是什麽。
劃分依據是分類之後的數據集的目標值的方差和要下降的最多(對於目標值是連續值)。
假設下面代碼中最後一列為目標值:
def err_cnt(dataSet): ‘‘‘回歸樹的劃分指標 input: dataSet(list):訓練數據 output: m*s^2(float):總方差 ‘‘‘ data = np.mat(dataSet) return np.var(data[:, -1]) * np.shape(data)[0]
這樣就可以建立每一顆樹。
但是考慮到遍歷每一個特征,還要遍歷每一個特征的每一個值會效率很低,所以lightgbm相對xgboost有個改進,就是對於某一個特征的值得分布建立一個直方圖,然後根據直方圖的切分點來對數據集進行分割。
對於這個地方有個重要的參數:
max_bin,也就是我這裏每一個特征畫直方圖最大的柱子的個數有多少個,如果畫的越多說明分得越細,所以在調整這個參數的時候要自己先對特征的分布有個把握,我的特征是否需要這麽多個bin來繪制直方圖。直方圖有個好處,子節點的兄弟節點的直方圖只要用父節點減去子節點就可以獲得該兄弟節點的直方圖。
建立了每一個樹之後要考慮過擬合的問題,例如假如每一個葉子節點上面只有一個數據,當然就能完美擬合訓練數據集,但是可能泛化能力就很差,也就是我的樹建的很深,這裏有兩個參數:
表示一個葉子節點上面最少有多少個數據,要根據訓練數據集的目標值的分布來看,有可能有的目標值就真的有一個偏離非常遠(例如平安數據大賽中有的目標賠率非常高,可能是發生了重大事故),你實在要擬合這個數據,只能把這個參數調成1
min_sum_hessian_in_leaf
這個參數表示一個葉子上的數據的權重的和,如果調的很小,也就是這個葉子上面的數據越少,也有過擬合的風險,調的越大說明葉子上面數據越多,要根據數據集的目標值的分布來確定
n_estimators 表示有多少顆樹
num_leaves表示有多少葉子,請註意,lightgbm和xgboost建立一棵樹的時候有不同(他們並不是完整的二叉樹,也就是num_leaves可以小於2^max_depth)
xgboost建樹的過程如下(level-wise tree):
lightgbm建樹的過程如下(leaf-wise tree):
優先生長殘差比較大的節點。上圖中在第二步後會選擇生長殘差較大的下一層而不是補齊右子樹的子節點。
lightgbm建立新的模型的時候對於每一個數據並不是一視同仁的,他會做一個采樣,選擇梯度(殘差)較大的數據作為新的模型的輸入。
對於調參有個博客介紹的很好:https://www.analyticsvidhya.com/blog/2016/03/complete-guide-parameter-tuning-xgboost-with-codes-python/
最後本文放一段代碼 演示如何建立一顆分類回歸樹:
import numpy as np
import pickle
class node:
‘‘‘樹的節點的類
‘‘‘
def __init__(self, fea=-1, value=None, results=None, right=None, left=None):
self.fea = fea # 用於切分數據集的屬性的列索引值
self.value = value # 設置劃分的值
self.results = results # 存儲葉節點的值
self.right = right # 右子樹
self.left = left # 左子樹
def load_data(data_file):
‘‘‘導入訓練數據
input: data_file(string):保存訓練數據的文件
output: data(list):訓練數據
‘‘‘
data = []
f = open(data_file)
for line in f.readlines():
sample = []
lines = line.strip().split("\t")
for x in lines:
sample.append(float(x)) # 轉換成float格式
data.append(sample)
f.close()
return data
def split_tree(data, fea, value):
‘‘‘根據特征fea中的值value將數據集data劃分成左右子樹
input: data(list):訓練樣本
fea(float):需要劃分的特征index
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):
‘‘‘計算葉節點的值
input: dataSet(list):訓練樣本
output: np.mean(data[:, -1])(float):均值
‘‘‘
data = np.mat(dataSet)
return np.mean(data[:, -1])
def err_cnt(dataSet):
‘‘‘回歸樹的劃分指標
input: dataSet(list):訓練數據
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):
‘‘‘構建樹
input: data(list):訓練樣本
min_sample(int):葉子節點中最少的樣本數
min_err(float):最小的error
output: node:樹的根結點
‘‘‘
# 構建決策樹,函數返回該決策樹的根節點
if len(data) <= min_sample:
return node(results=leaf(data))
# 1、初始化
best_err = err_cnt(data)
bestCriteria = None # 存儲最佳切分屬性以及最佳切分點
bestSets = None # 存儲切分後的兩個數據集
# 2、開始構建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、計算劃分後的error值
now_err = err_cnt(set_1) + err_cnt(set_2)
# 2.3、更新最優劃分
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、判斷劃分是否結束
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)) # 返回當前的類別標簽作為最終的類別標簽
def predict(sample, tree):
‘‘‘對每一個樣本sample進行預測
input: sample(list):樣本
tree:訓練好的CART回歸樹模型
output: results(float):預測值
‘‘‘
# 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:訓練好的CART回歸樹模型
output: err/m(float):均方誤差
‘‘‘
m = len(data) # 樣本的個數
n = len(data[0]) - 1 # 樣本中特征的個數
err = 0.0
for i in range(m):
tmp = []
for j in range(n):
tmp.append(data[i][j])
pre = predict(tmp, tree) # 對樣本計算其預測值
# 計算殘差
err += (data[i][-1] - pre) * (data[i][-1] - pre)
return err / m
def save_model(regression_tree, result_file):
‘‘‘將訓練好的CART回歸樹模型保存到本地
input: regression_tree:回歸樹模型
result_file(string):文件名
‘‘‘
with open(result_file, ‘wb‘) as f:
pickle.dump(regression_tree, f)
if __name__ == "__main__":
# 1、導入訓練數據
print ("----------- 1、load data -------------")
data = load_data("sine.txt")
# 2、構建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")
數據格式如下:
數據中間用tab鍵隔開 前面的列為特征,最後一列為目標值。
對xgboost和lightgbm的理解及其調參應該關註的點