sklearn的GBDT源码笔记

参考:http://www.jianshu.com/p/1fa837221360

代码主要在gradient_boosting.py里面,定义了各种loss类以及estimator类,前者定义了loss的计算方式,后者是一些简单基本的estimator类,主要用于LossFunction中init_estimator的计算,即初始预测值的计算。

loss类

loss的类层级关系为:

  • LossFunction
    • RegressionLossFunction
      • LeastSquaresError(负梯度是目标值y和预测值pred的差)
      • LeastAbsoluteError(负梯度是目标值y和预测值pred的差的符号,适用于 稳健回归 ,PS:数据出现异常点时最小二乘法的替代方法)
      • HuberLossFunction( 一种适用于稳健回归Robust Regression的损失函数,init_estimator是QuantileEstimator(alpha=0.5))
      • QuantileLossFunction(分位数回归的损失函数,分位数回归允许估计目标值条件分布的百分位值)
    • ClassificationLossFunction
      • BinomialDeviance(logistic regression,二分类问题损失函数,init_estimator是LogOddsEstimator)
      • MultinomialDeviance(softmax, 多分类问题损失函数,init_estimator是PriorProbabilityEstimator)
      • ExponentialLoss(二分类问题指数损失,AdaBoost用的loss。init_estimator是ScaledLogOddsEstimator)

注意:对于ClassificationLossFunction, 代码里计算loss和负梯度的时候,并不是用概率计算而是用决策树的直接累计输出score(pred)计算。

以BinomialDeviance为例,score转成概率要经过sigmoid转化。设p为概率,P为预测值,则BinomialDeviance的loss公式为

ylog(p)+(1y)log(1p)=log(1p)+ylog(p1p)

由于
p=eP1+eP

所以
log(p1p)=log(eP)=P

log(1p)=log(11+eP)=log(1+eP)

原来的公式变成

yPlog(1+eP)

就是代码里写的那样,负号是loss定义里有的,至于2就是系数(?)。

estimator类

estimator类的层级关系:

  • BaseEstimator
    • LogOddsEstimator,预测训练集target的对数几率(适合二分类问题,因为公式只分正样本和负样本)
      • ScaledLogOddsEstimator,缩放后的对数几率(适用于指数损失函数)
    • PriorProbabilityEstimator , 预测训练集中每个类别的概率
    • ZeroEstimator, 预测结果都是0的estimator
    • MeanEstimator, 预测训练集target的平均值的estimator。
    • QuantileEstimator,预测训练集target的alpha-百分位的estimator

estimator类都有自己的fit函数和predict函数,前者确定数值(均值、中位值等),后者用来输出预测值。除了一些estimator类和loss类,还有3个类:
1. BaseGradientBoosting

带有gb操作的父类,也是个抽象类,为所有的estimator定义了通用的fit函数,后面的分类树和回归树差别只在于loss function不同。

2. GradientBoostingClassifier

用于分类的GBDT,是BaseGradientBoosting的子类

3. GradientBoostingRegressor

用于回归的GBDT,是BaseGradientBoosting的子类

BaseGradientBoosting

主要有以下几个方法:
- __init__: 传入了超参数,即包括boosting框架和决策树本身。比如loss是损失函数LossFunction的选择,learning_rate为学习率,n_estimators是boosting的次数(迭代次数或者Stage的个数)。通常learning_rate和n_estimators中需要做一个trade_off上的选择。 init参数指的是我们的初始化时候的弱学习器,即默认为每个LossFunction里面的init_estimator,用来计算初始预测值。 warm_start决定是否重用之前的结果并加入更多estimators,或者直接抹除之前的结果。
具体的参数含义可以看 http://www.cnblogs.com/pinard/p/6143927.html

  • _check_params: 检查超参数是否合法,以及初始化模型参数(包括所用的loss等),初始化了self.max_features_变量和所用的loss类别,变量为self.loss_。

  • _init_state: 初始化init_estimator以及model中的状态(中间有个six库用于处理跨Py2和Py3的兼容问题),根据self.loss_初始化self.init_,还有初始化self.estimators_, self.train_score_,self.oob_improvement_( 事先根据预设迭代次数申请好空间 ),后三个都是数组,分别存储每一个单模型对应的estimator、训练集得分和outofbag评估的进步。

  • _clear_state: 清除模型状态,把上面四个变量del掉

  • _resize_state:调整n_estimators,并调整上面四个变量的size

  • fit: 训练模型的方法,主要的操作有:

    • 如果不是warm_start,那就清除模型状态;即从头开始训练。
    • 调用check_X_y检查x和y的格式,要求X 2d 和y 1d,支持X和y是稀疏矩阵( 具体待看 )
    • 初始化sample_weight
    • _check_params检查参数
    • 进行初始prediction(使用estimator的predict或者已有模型的_decision_function)
    • (可选)对稠密输入数据矩阵作presort, fortran风格排序
    • 训练模型(调用_fit_stages函数)
    • 根据最终迭代次数调整self.estimators_,self.train_score_,self.oob_improvement_的尺寸
  • _fit_stages: 迭代训练模型。每次迭代中,

    • 首先做下采样,分出训练验证集(可选,oob),
    • 然后调用_fit_stage,用包内数据及其原y_pred训练新模型,并得到所有样本的新y_pred,
    • 计算记录oob_improvement_(即当次迭代oob的loss减少量)和train_score(即loss)
  • _fit_stage:单次训练模型,其中 loss.K针对的是多分类问题,回归和二分类时K为1 。对于每一类,操作主要有:

    • 先计算残差resudual(负梯度向量,长度即为样本数),
    • 然后初始化一个DecisionTreeRegressor,并 以残差为目标值训练 ( 也就是说每一类都会训练一棵树,如果是二分类就只有一棵 )
    • 调用loss.update_terminal_region函数,根据学习率计算新的累计y_pred(这个一定有),更新当前树的叶子节点的值(这个不一定有,如LSE只更新累计y_pred数组,没有更新叶子节点值,这样每个叶子节点值应当是当次迭代对残差的预估值)。loss父类的update_terminal_regions代码如下:
    
    # 计算每个样本对应到树的哪一个叶子节点
    
    terminal_regions = tree.apply(X)
    
    # 将outofbag的样本的结果都置为-1(不参与训练过程)
    
    masked_terminal_regions = terminal_regions.copy()
    masked_terminal_regions[~sample_mask] = -1
    
    # 更新每个叶子节点上的value,tree.children_left == TREE_LEAF是判断叶子节点的方法。一个很关键的点是这里只更新了叶子节点,而只有LossFunction是LeastSquaresError时训练时生成的决策树上的value和我们实际上想要的某个节点的预测值是一致的。
    
    for leaf in np.where(tree.children_left == TREE_LEAF)[0]:
        # _update_terminal_region由每个具体的损失函数具体实现,在LossFunction基类中只提供模板
        self._update_terminal_region(tree, masked_terminal_regions, leaf, X, y, residual, y_pred[:, k], sample_weight)
    
    # 更新预测值,tree预测的是负梯度值,预测值通过加上学习率 * 负梯度来更新,这里更新所有inbag和outofbag的预测值
    
    y_pred[:, k] += (learning_rate * tree.value[:, 0, 0].take(terminal_regions, axis=0))
    • 保存新的tree到self.estimators_数组中
  • _decision_function: 内部用来输出预测值的函数,fit利用已有模型计算初始预测值的时候用到, 在两个子类中也用到。首先调用_init_decision_function得到初始预测值,然后再调用predict_stages获得累计预测值。

  • _staged_decision_function: 和上面类似,但是是获取每一个stage的预测值

  • feature_importances_: 计算公式是每个特征在每个stage所有树的importance的均值之和的均值

  • _apply:返回样本在每个estimator落入的叶子节点编号。输入尺寸为[n_samples, n_features],输出尺寸为[n_samples, n_estimators, n_classes],Regressor只有前两个维度。

  • _validate_y

GradientBoostingClassifier

可用loss为’deviance’, ‘exponential’,即ExponentialLoss, MultinomialDeviance, BinomialDeviance。
要注意的是, Classifier用的基分类器也是DecisionTreeRegressor ,Regressors输出的累计预测值要先经过转换才会是类别概率。

新增:

  • predict_proba:输出每个类别的概率,先调用self.decision_function()获取每个x的预测值,然后调用self.loss_._score_to_proba(score)将预测值转化为概率,每种loss的转化公式不同,如 BinomialDeviance就是直接用sigmoid转化 。(其他的还没看懂)
  • predict: 输出预测类别,计算出概率的操作和上面相同,之后取概率最高的类为输出。
  • predict_log_proba

GradientBoostingRegressor

可用loss为’ls’, ‘lad’, ‘huber’, ‘quantile’,即LeastSquaresError, LeastAbsoluteError, HuberLossFunction, QuantileLossFunction。

  • __init__:直接调用父类的,
  • apply: 就是reshape一下父类apply的输出。
  • predict和staged_predict函数: 只是分别调用_decision_function和_staged_decision_function获取输出。

你可能感兴趣的:(机器学习,源码笔记)