GBDT梯度提升迭代决策树
是一个集成模型,基分类器采用CART回归树,GBDT是通过采用加法模型,以及不断减少训练过程产生的残差来达到将数据分类或者回归的算法。
直观理解:每一轮预测和实际值有残差,下一轮根据残差再进行预测,最后将所有预测相加,得出结果。
GBDT通过多轮迭代,每轮迭代产生一个弱分类器,每个分类器在上一轮分类器的残差基础上进行训练。
通过例子直观理解:
设三个人的年龄分别为5岁,6岁,7岁,则三人的平均年龄为6岁,所以用6岁这个常量来预测三人的年龄即[6 , 6 , 6]。每个人的年龄的残差 = 年龄 - 预测值 = [5 , 6 , 7] - [6 , 6 , 6],所以残差为[-1 , 0 , 1]
接下来为了让模型更准确,需要让残差变小,因此对残差建立一颗回归树,然后预测出准确的残差。假设这棵树的残差是[-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],预测的准确性得到了提升。
具体思路
假设我们的预测一共迭代3轮 年龄:[5, 6, 7]
第1轮预测:[6, 6, 6] (平均值)
第1轮残差:[-1, 0, 1]
第2轮预测:[6, 6, 6] (平均值) + [-0.9, 0, 0.9] (第1颗回归树) = [5.1, 6, 6.9]
第2轮残差:[-0.1, 0, 0.1]
第3轮预测:[6, 6, 6] (平均值) + [-0.9, 0, 0.9] (第1颗回归树) + [-0.08, 0, 0.07] (第2颗回归树) = [5.02, 6, 6.97]
第3轮残差:[-0.08, 0, 0.03]
残差越来越小,这种预测方式就是GBDT算法。
代码实现
1.构建测试数据,做简单的线性回归
def create_data():
X = []
for i in range(100):
x = 2 * i
y = 3 * i
z = i
l = x + y + z + np.random.rand() * 10
X.append([x,y,z,l])
return np.array(X)
data = create_data()
2.构建CART回归树,计算均方误差,寻找最好的切分点
def calc_mse(data):
if len(data) == 0:
return 0
label = data[:,-1]
return np.var(label) * len(label) #np.var计算方差
def select_split(data):
min_gini = np.inf
best_feat = None
best_val = None
left = None
right = None
data_type = 'continuity'
for i in range(data.shape[1]-1):
c_set = set(data[:, i])
for val in c_set:
arr1,arr2 = split_data(data,i,val,data_type)
g1 = calc_mse(arr1) #计算误差
g2 = calc_mse(arr2)
# g = len(arr1) / len(data) * g1 + len(arr2) / len(data) * g2 #基尼用于分类
g = g1 + g2 # 获取剩下最小的均方误差
# print(i,val,g)
if min_gini > g:
min_gini = g
best_feat = i
best_val = val
left = arr1
right = arr2
return best_feat,best_val,left,right
3.构建GBDT回归树
def create_gbdt(dataset,n_estimators,lr):
'''
:param data: 输入数据
:param n_estimators: 弱分类器的个数
:param lr: 学习率,也叫残差学习率
:return:
'''
data = copy.copy(dataset)
tree_list = []
tree_list.append(np.mean(data[:, -1]))
data[:, -1] = data[:, -1] - np.mean(data[:, -1])
tree_list.append(create_tree(data))
for i in range(1,n_estimators):
data[:,-1] = (1 - lr) * data[:,-1] # 剩余残差
tree_list.append(create_tree(data))
return tree_list
4.构建预测函数,利用递归函数构造
# 预测单颗树
def predict_one(tree,X):
if type(tree) != dict:
return tree
for key in tree:
if X[key[0]] < key[1]:
r = tree[(key[0],key[1],'left')]
else:
r = tree[(key[0], key[1], 'right')]
return predict_one(r, X)
# 预测
def predict(tree_list,X,lr):
result = tree_list[0]
for tree in tree_list[1:]:
result += lr * predict_one(tree,X)
return result
5.进行测试,构建了5个树
n_estimators = 5
gbdt_tree = create_gbdt(data,n_estimators,0.1)
print("create gbdt:",predict(gbdt_tree,data[0,:-1]))
XGBoost基于预排序方法的决策树算法
XGBoost对应的模型就是一堆CART树。不断地添加树,不断地进行特征分裂来生长一棵树,每次添加一个树,其实是学习一个新韩淑f(x),去拟合上次预测的残差。当训练完成得到k棵树,要预测一个样本的分数,其实就是根据这个样本的特征,在每棵树中会落到对应的一个叶子节点,每个叶子节点就对应一个分数,最后只需要将每棵树对应的分数加起来就是该样本的预测值。
直观理解
要预测一家人对电子游戏的喜好程度,考虑到年轻和年老相比,年轻更可能喜欢电子游戏,以及男性和女性相比,男性更喜欢电子游戏,故先根据年龄大小区分小孩和大人,然后再通过性别区分开是男是女,逐一给各人在电子游戏喜好程度上打分,如下图所示:
训练出了2棵树tree1和tree2,类似之前GBDT的原理,两棵树的结论累加起来便是最终的结论,所以小孩的预测分数就是两棵树中小孩所落到的结点的分数相加: 2 + 0.9 = 2.9.爷爷的预测分数同理: -1 + (-0.9) = -1.9.
代码实现
import xgboost as xgb
from xgboost import plot_importance
from matplotlib import pyplot as plt
# 设置模型参数
params = {
'booster': 'gbtree',
'objective': 'multi:softmax',
'num_class': 3,
'gamma': 0.1,
'max_depth': 2,
'lambda': 2,
'subsample': 0.7,
'colsample_bytree': 0.7,
'min_child_weight': 3,
'silent': 1,
'eta': 0.001,
'seed': 1000,
'nthread': 4,
}
plst = params.items()
dtrain = xgb.DMatrix(X_train, y_train) #利用原生xgboost库读取libsvm数据
num_rounds = 200
model = xgb.train(plst, dtrain, num_rounds)
# 对测试集进行预测
dtest = xgb.DMatrix(X_test)
y_pred = model.predict(dtest)
# 计算准确率
accuracy = accuracy_score(y_test, y_pred)
print ("Accuracy:", accuracy)
# 绘制特征重要性
plot_importance(model)
plt.show();
结果
Accuracy: 0.9166666666666666