个人博客:https://iyinst.github.io/2021/05/19/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-%E6%89%8B%E6%92%95GBDT/
梯度提升决策树(GBDT)的原理不再赘述,不熟悉的同学可以参看李航老师的《统计学习方法》,这里给出一个GBDT的实现。
在编码之前,我们首先要考虑三个问题:
1、每棵树拟合的目标是什么?
2、树的每个节点怎么分裂?
3、叶节点的预测值怎么计算?
想清楚这三个问题,就可以讲问题转成构建多个CART的问题,将复杂的问题简单化。从分类和回归两个任务来考虑,回归任务相对简单,当损失函数是均方误差(MSE)的时候,每棵树拟合的目标就是当前模型的残差,树的每个节点的分裂方法就是CART节点的分裂方法,叶节点的预测值是所有分到此节点的样本的均值。分类任务相对复杂,考虑损失函数是对数似然损失函数的二分类任务,模型预测的结果是对数几率,这里和逻辑回归是十分相似的,每棵树拟合的目标为真实值与预测概率的差值,树的每个节点的分裂方法就是CART节点的分裂方法,叶节点的预测值是 y − P P ∗ ( 1 − P ) \frac{y - P}{P * ( 1 - P ) } P∗(1−P)y−P,其中 y y y为真实标签, P P P为预测概率。对上述三个问题进行总结,针对问题一,每棵树拟合的目标都是损失函数的负梯度,对于均方误差为损失函数的回归问题和对数似然损失函数的二分类问题,其负梯度都是真实值与预测值的差值;针对问题二,GBDT的每棵树都是CART回归树,与分类还是回归任务无关,因此节点分裂的方式即为CART回归树的分裂方式;针对问题三,叶节点的预测值应为使得损失函数最小的值,对于均方误差为损失函数的回归问题预测值为真实值的均值,对于对数似然损失函数的二分类问题预测值为。另外 y − P P ∗ ( 1 − P ) \frac{y - P}{P * ( 1 - P ) } P∗(1−P)y−P还要需要注意的点在于,GBDT里树的生成是按照(X,r)来建立的,但叶节点的预测值是根据真实标签和模型预测值计算的,也就是说决策树的分裂是按照响应值来分裂的。
解决了这三个问题,我们开始编写GBDT的代码。复用上篇写的CART代码,需要修改CART叶节点预测值的计算方法。
修改后的CART树如下。
class CART:
def __init__(self, objective='regression', max_depth=10, min_samples_leaf=1, min_impurity_decrease=0., real_label = None):
self.objective = objective
if self.objective == 'regression':
self.loss = se_loss
self.leaf_weight = lambda res,label: -np.mean(res)
elif self.objective == 'classification':
self.loss = se_loss
self.leaf_weight = lambda res, label : np.sum(res) / np.sum((label - res) * (1 - label + res))
self.min_impur