决策树的一个缺点是容易出现过拟合,可以把利用融合的方式把各个弱模型集成起来,解决过拟合,提高模型的泛化能力。决策树和bagging 、boosting的思想结合在一起,诸如随机森林、GBDT,在数据挖掘中的预测分类、推荐广告以及搜索中的排序算法模型、搜索关键词的扩展推荐等等应用的非常广泛。
先讲一下bagging和boosting方式的区别。
Bagging的方式算是比较简单的,训练多个模型,利用每个模型进行投票,每个模型的权重都一样,对于分类问题,取总票数最多作为分类,对于回归,取平均值。利用多个弱分类器,集成一个性能高的分类器。典型代表是随机森林。随机森林在训练每个模型的时,增加随机的因素,对特征和样本进行随机抽样,然后把各颗树训练的结果集成融合起来。随机森林可以进行并行训练多颗树。
Boosting的方式也是训练多个决策树模型,是一种迭代的算法模型,在训练过程中更加关注错分的样本,对于越是容易错分的样本,后续的模型训练约要花更多精力去关注,提高上一次分错的数据权重,越在意那些分错的数据。在集成融合时,每次训练的模型权重也会不一样,最终通过加权的方式融合成最终的模型。Adaboost、GBDT采用的都是boosting的思想。
用一张图来看一下Adaboost方法。
训练过程如下:
1、 初始时,样本的训练权重都是一样,通过一个弱分类器,得到这些样本的分类预测标签。与给出的样本真实标签对比,就可能出现误差(即错误)。如果某个样本预测错误,则它对应的错误值为该样本的权重,如果分类正确,则错误值为0. 最后累加5个样本的错误率之和,记为ε。
2、 通过ε来计算该弱分类器的权重α,公式如下:
3、 通过α来计算训练下一个弱分类器样本的权重D,如果对应样本分类正确,则减小该样本的权重,公式为:
如果样本分类错误,则增加该样本的权重,公式为:
4、 循环步骤1,2,3来继续训练多个分类器,只是其D值不同而已。
预测过程:
输入一个样本到训练好的每个弱分类中,则每个弱分类都对应一个输出标签,然后该标签乘以对应的α,最后求和得到值的符号即为预测标签值。 还有一种常用的boosting是Gradient Boosting,GBDT,它主要的思想是,每一次建立模型是在之前建立模型损失函数的梯度下降方向。损失函数(loss function)描述的是模型的不靠谱程度,损失函数越大,则说明模型越容易出错(其实这里有一个方差、偏差均衡的问题,但是这里就假设损失函数越大,模型越容易出错)。如果我们的模型能够让损失函数持续的下降,则说明我们的模型在不停的改进,而最好的方式就是让损失函数在其梯度(Gradient)的方向上下降。算法的每一步沿着损失函数下降最快的方向建立新的模型,这样使得算法在每一步均沿着下降最快的方向收敛。直到满足要求,建立满足要求的若干组合加权子模型。GradientBoosting,定义loss function为
则对于训练样本集合{y, x},我们的任务是寻找最小化loss的函数F*(x):
而gradient boosting的思路是将映射模型函数表示为以下形式:
其中h(x;am)为简单函数/模型, am 为h的参数,此时, belta, a,就为我们要预估的最小化loss下的参数:
同时Fm与F_m-1的关系为
之后可以求belta和a序列参数,求解过程如下:
在第i个样本点,第m个模型里边的伪残差求解方法为:
要构建模型h(x,am),最快的方法,就是让所有的样本点处,损失函数都沿着最快的方向下降。
也就是:
利用最小二乘法求解am后,即可求解belta_m
依次求解所有am, belta_m后,即得到最终模型F*(x)
算法的流程如下:
MLlib中的随机森林RandomForest可以做到每棵树并行的训练,GBDT由于是迭代的算法,无法实现多棵树的并行训练。但是每颗树的训练都是基于DesionTree的实现,所以支持在单棵树级别实现并行的操作,这个在决策树的并行训练提到过。
MLlib中的RandomForest实现其实就是决策树的实现,只是在最后进行最后模型预测时,加权融合多个模型,比较简单。支持分类和回归支持,分类只支持二元,对于多标签的分类,MLlib中的GBDT目前还不支持。
对于GBDT,在MLlib中实现的算法在上面基本已经进行了介绍,相关的模型训练过程可以参考下面的代码注解。
GradientBoostedTrees
private def boost(
input: RDD[LabeledPoint],
boostingStrategy: BoostingStrategy): GradientBoostedTreesModel = {
val timer = new TimeTracker()
timer.start("total")
timer.start("init")
boostingStrategy.assertValid()
// Initialize gradient boosting parameters
//迭代次数
val numIterations = boostingStrategy.numIterations
//每次迭代的决策树模型
val baseLearners = new Array[DecisionTreeModel](numIterations)
//每个树模型的权重
val baseLearnerWeights = new Array[Double](numIterations)
//损失的计算方式,参考下表
val loss = boostingStrategy.loss
//学习率,一般不建议对这个参数进行调优,如果算法模型不稳定,则建议降低这个值
val learningRate = boostingStrategy.learningRate
// Prepare strategy for individual trees, which use regression with variance impurity.
val treeStrategy = boostingStrategy.treeStrategy.copy
//algo支持classication和Regresion
treeStrategy.algo = Regression
treeStrategy.impurity = Variance
treeStrategy.assertValid()
// Cache input,缓存样本集合
if (input.getStorageLevel == StorageLevel.NONE) {
input.persist(StorageLevel.MEMORY_AND_DISK)
}
timer.stop("init")
logDebug("##########")
logDebug("Building tree 0")
logDebug("##########")
var data = input
// Initialize tree
timer.start("building tree 0")
val firstTreeModel = new DecisionTree(treeStrategy).run(data)
baseLearners(0) = firstTreeModel
baseLearnerWeights(0) = 1.0
val startingModel = new GradientBoostedTreesModel(Regression, Array(firstTreeModel), Array(1.0))
logDebug("error of gbt = " + loss.computeError(startingModel, input))
// Note: A model of type regression is used since we require raw prediction
timer.stop("building tree 0")
// psuedo-residual for second iteration
data = input.map(point => LabeledPoint(loss.gradient(startingModel, point),
point.features))
var m = 1
//迭代训练各树模型
while (m < numIterations) {
timer.start(s"building tree $m")
logDebug("###################################################")
logDebug("Gradient boosting tree iteration " + m)
logDebug("###################################################")
val model = new DecisionTree(treeStrategy).run(data)
timer.stop(s"building tree $m")
// Create partial model
baseLearners(m) = model
// Note: The setting of baseLearnerWeights is incorrect for losses other than SquaredError.
// Technically, the weight should be optimized for the particular loss.
// However, the behavior should be reasonable, though not optimal.
//当前数模型的权重weight
baseLearnerWeights(m) = learningRate
// Note: A model of type regression is used since we require raw prediction
//每个树的模型
val partialModel = new GradientBoostedTreesModel(
Regression, baseLearners.slice(0, m + 1), baseLearnerWeights.slice(0, m + 1))
logDebug("error of gbt = " + loss.computeError(partialModel, input))
//利用残差(梯度方向)更新样本数据集,作为下颗树模型训练的样本
data = input.map(point => LabeledPoint(-loss.gradient(partialModel, point),
point.features))
m += 1
}
timer.stop("total")
logInfo("Internal timing for DecisionTree:")
logInfo(s"$timer")
//最后融合各颗树的模型
new GradientBoostedTreesModel(
boostingStrategy.treeStrategy.algo, baseLearners, baseLearnerWeights)
}
Loss | Task | Formula | Description |
---|---|---|---|
Log Loss | Classification | 2 \sum_{i=1}^{N} \log(1+\exp(-2 y_i F(x_i))) | Twice binomial negative log likelihood. |
Squared Error | Regression | \sum_{i=1}^{N} (y_i - F(x_i))^2 | Also called L2 loss. Default loss for regression tasks. |
Absolute Error | Regression | \sum_{i=1}^{N} |y_i - F(x_i)| | Also called L1 loss. Can be more robust to outliers than Squared Error. |