编者按:在上一篇中,我们介绍了决策树的基础知识,并用一些实例和计算演示了决策树的构建、分裂和剪枝。现在,有了“一棵树”,我们就能种植一片“森林”,并用分类组合的方法,从中挑选出表现最佳的树。
“从零学习”系列第4篇“详解基于树形结构的ML建模(R & Python)——随机森林篇”,来自知名印度数据科学网站Analytics Vidhya的内容团队。
树形模型的集成方法
什么是Bagging
什么是随机森林
什么是Boosting
GBM VS XGboost
用R和Python使用GBM
习题(回归、分类)
注:XGboost的实现由于篇幅过长,将单独放在番外篇中介绍。
和其他模型一样,树形结构模型也会受bias和方差影响。bias指的是理论预测值与实际值的平均偏差,而方差指的是当模型的输入是来自同一群体的不同样本,它预测的结果会有多大的不同。
如果你构建了一棵决策树,你就会得到一个低方差、高bias的模型,那么,该怎么平衡这两者之间的关系呢?通常情况下,随着模型变得越来越复杂,模型bias会越小,预测值误差也越小。这时如果你进一步提升它的复杂度,那么你最终会获得一个高方差的过拟合模型。
因此,最好的做法是在bias和方差间保持平衡,也就是bias-variance trade-off(偏差—方差权衡)。集成学习正式执行这种折衷分析的一个常用的方法。
之前提到了,集成学习的主要思路是将若干个个体学习器组成起来。在实际操作中,它们可以是一类的,也可以是不同的,但前者应用更广泛。CART决策树是集成学习最长使用的模型之一,由于本文介绍的是基于树形结构的模型,因此后文涉及的都是以树形模型为代表的同质个体学习器的集成学习方法,它们包括Bagging、Boosting。
step 1:在原训练集D的基础上创建多个子数据集D1、D2……Dt-1、Dt
这里的采样是有放回的,子数据集从训练集中采取样本后,这些样本会被放回训练集中参与下一轮采样,因此我们有可能会得到多个相同的子数据集;
这些新的子数据集可能有一些关于行和列的分数,它们通常是Bagging模型中的超参数;
如果行列分数小于1,它们将有助于建立一个不太会过拟合的强学习器。
step 2:构建多个学习器(分类器)
一个子数据集对应一个学习器;
通常,这些学习器都使用同一模型。
step 3:组合分类器
根据具体问题,用平均值、中位数或众数组合各个学习器的预测;
组合得出的值通常比任何学习器的预测都靠谱。
请注意,这里的学习器数量并不是一个超参数。虽然很多情况下模型数量越多越好,但是这样做也会带来更多相似的、性能较低的预测。从Bagging的理论推理中我们可以发现,在一些问题中,Bagging得出的新预测的方差往往是原方差的1/n(n为分类器数量)。
Bagging模型有多种实现,其中随机森林就是它的一个进阶版。
随机森林的原理
在随机森林中,我们不再只构建一个CART模型,而是要“种植”多棵树。对于分类问题,每棵决策树都会给出一个预测分类,这相当于给那个类别“投票”,最后森林会选择“票数”最多的那个分类;而对于回归问题,我们一般取所有树预测值的平均值。
它的构建步骤如下:
设输入样本为N个,从训练集中有放回地随机抽取N个子数据集;
如果存在M个特征变量,则选取m≤M,以m中的最佳分裂作为分裂节点,对采样之后的数据构建决策树;
每棵决策树都必须是完全分裂的,不能剪枝(随机采样,不会过拟合);
组合决策树预测,得出新数据。
随机森林的优点
随机森林主要有6大优势:
能同时解决分类、回归两类问题,并给出高质量的预测;
能处理高维的大数据。随机森林能从上千个特征中筛选出最重要的特征,并以此降维(随机挑选,无需特征选择)。在某些随机数据集上,这种依赖变量的模型十分适用;
提供了一种有效的方法来计算丢失数据,并能保证其中大部分计算的准确性;
能处理不平衡数据集,平衡其中的误差;
上述的这些功能能扩展到无标签数据上,可以实现无监督聚类、制作数据视图及异常值检测;
随机森林的采样方法被称为自助采样法(Bootstap sampling),它无需测试集。现在,如果我们将1/N的数据作为测试集,那它们就是袋外样本(out of bag samples),它们包含的误差被称为袋外误差(out of bag error)。通过研究袋外样本的误差我们可以发现,如果测试集的大小和训练集一样,那它们得出的预测值也是一样的。所以,使用这种采样方法不用另行安排测试集。
随机森林的缺点
比起分类问题,随机森林在回归问题上的表现更为突出。但是由于回归问题是连续预测,算法不会超出训练数据的范围,因此模型会在噪声数据上出现过拟合的现象;
对于开发者,随机森林更像是一种黑盒方法,因为它的作用是不可控的。
Python&R的实现
随机森林在Python和R语言中都有现成的工具。
Python
#导入Library
from sklearn.ensemble import RandomForestClassifier #用随机森林解决回归问题
#假设你有一个关于训练集的预测模型X和目标值y,以及一个测试集的预测模型x_test
# 创建随机森林对象
model= RandomForestClassifier(n_estimators=1000)
# 用训练集训练模型并验证得分
model.fit(X, y)
#预测输出
predicted= model.predict(x_test)
R语言
> library(randomForest)
> x <- cbind(x_train,y_train)
# Fitting model
> fit <- randomForest(Species ~ ., x,ntree=500)
> summary(fit)
#Predict Output
> predicted= predict(fit,x_test)
为了更简单形象地解释它,这里我们举一个垃圾邮件识别的例子。
相信许多开发者在学习时都遇到过这个问题:如何将电子邮件分类为垃圾邮件?和其他人一样,很多人的首选做法可能是设置一些判断标签:
如果邮件中只有一个图像文件(宣传海报),这是一个垃圾邮件;
如果邮件中只有一个链接,这是一个垃圾邮件;
如果邮件正文包含“您赢得了……的奖金”这个句子,这是一个垃圾邮件;
如果邮件来源后缀是“jqr.com”,这是论智君给你送年货了,不是垃圾邮件;
如果邮件来源是“联系人XXX”,不是垃圾邮件。
这里我们列出了5个分类邮件的规则,但它们离成功分类还遥遥无期。
像这样能分类部分邮件但不能分类所有邮件的规则,我们称为弱学习器。为了把它们转化为更强大的学习器,它们需要“团结一心”:
平均/加权平均;
预测类别“得票数”更高。
例如,在该情景下,我们定义了5个弱学习器,其中3个将同一封电子邮件判断为“垃圾邮件”,2个判断为“不是垃圾邮件”,那么默认情况下我们会把电子邮件归类为垃圾邮件,因为它的“票数”更多。
Boosting原理
现在我们知道了,Boosting依靠弱学习器构建强大的规则,那么它是怎么识别弱学习器的呢?
为了找到弱学习器(也称“基学习器”(base learner),对应的学习算法为“基学习算法”(base learning algorithm)),我们需要将“基学习算法”应用于不同分布。每次使用“基学习算法”,就会产生一个新的弱预测规则。这是一个循环往复的过程,经过多次迭代,最后我们得到了一堆性能不一的弱预测规则,再用Boosting算法将它们组合成一个强预测规则。
那么,我们又该如何选择每一次迭代的正确分布?以下是具体步骤:
弱学习器采用所有分布,并为每个观察样本分配相同的权值;
如果第一个基学习算法算得学习误差,提高相应观察样本的权值,以便下一个弱分类器能更“重视”它们;
重复步骤2进行迭代,直到算法达到极限或得到更高的精度。
最后,这些弱学习器会在增强算法的作用下组合成为一个强学习器,由于每一轮迭代都“重点关注”分类错误数据,在训练中改进了弱规则,所以强学习器的预测准确率较之前有明显提升。组合学习器的Boosting算法有许多种,如AdaBoost算法和提升树(boosting tree)系列算法,后文重点介绍梯度提升(GBM)和XGboost就属于提升树系列算法。
而XGboost同样起源于boosting算法,但它在计算损失函数的基础上又引入了bagging的一些集成学习思想。它通过Gradient Boosting框架自定义损失函数提高了算法解决通用问题的能力,同时又增加了更多可控参数。
在实际应用中,我们通常会发现XGboost比GBM性能更好、精度更高。
正则化:GBM不会进行正则化,而XGboost会把树模型复杂度作为正则项加到优化目标中,能更好地防止过拟合;
并行计算:和GBM相比,XGboost使用并行计算,速度更快;
高灵活性:XGboost增加了许多可控参数,它允许开发者定义自定义的优化目标和评估标准,相当于为模型增加了一个全新的维度;
处理缺失值:XGBoost有一个内置的例程来处理缺失数据,开发者需要提供与其他观察值不同的值,并将其作为参数传递。并且,随着节点缺失值的发现,XGBoost能把当前的寻找路径方法推广到未来;
剪枝:当把GBM算法用于剪枝时,它会在节点信息增益为负时直接停止分裂,所以是一个贪婪的算法;而XGBoost则会先允许CART树长到max_depth(最大深),之后再由下而上对负增益的分裂进行修剪,这时它会计算子节点和父节点的信息增益之和,如果父节点为-2,子节点为+10,那么XGBoost会将它保留下来,目光更加长远。
内置交叉验证:XGBoost允许开发者在提升过程的每次迭代中进行交叉验证,这就方便统计每一次的迭代次数;而GBM依靠网格搜索,只能测试有限的值;
继续使用当前模型:XGBoost允许开发者迭代完模型后直接进行训练,这在某些场景下优势明显。GBM的sklearn实现现在也支持这个功能。
1.初始化结果
2.从1到1的树总数
2.1根据以前的运行更新目标的权重(错误分类的权重更高)
2.2在选定的数据子样本上拟合模型
2.3对全套观察数据进行预测
2.4依据学习率,用当前结果更新输出
3.返回最终输出。
Python中用于提高模型性能的GBM参数
learning_rate
学习率,这决定了每棵树对最终结果的影响。一般来说,我们会定义一个较低的学习率,因为它们可以使模型对于树的特定特征具有鲁棒性,从而提高泛化能力。但更低的学习率也将需要更多的树,而且会耗费大量计算。
n_estimators
需要被建模的树的数量(基学习器的个数)。尽管GBM在树数量较多的情况下表现良好,但它还是会出现过拟合,这时我们可以用交叉验证来调整学习率。
subsample
子采样数,通过随机抽样从每棵树上选出的部分观察样本。一般是小于1的,可以减小方差提高模型鲁棒性,最佳值可选0.8,但是适当调整会获得更好的模型。
loss
每个分裂最小化的损失函数,根据分类、回归问题有不同选择,默认可选loss。只有当损失函数对模型有影响时,才选择其他。
init
模型变量,即GBM的启动模型。
random_state
随机数的种子。不同的种子会造成不同的随机采样结果,相同的种子采样结果相同。它对调参很重要。
verbose
是否打印模型训练过程:0,不打印;1,根据时间间隔打印;>1,打印所有树的训练输出。
warm_start
热启动,是否调用上次fit的模型结果并增加新结果。当你重开中止的训练时,这个参数可以让你在上次的基础上接着训练。
presort
是否预先分割数据。
以上只是部分参数,更多介绍请上Github:https://github.com/aarshayj/Analytics_Vidhya/tree/master/Articles/Parameter_Tuning_GBM_with_Example。
R语言中用于提高模型性能的GBM参数
使用caret包,R语言开发者只需要调整这几个主要参数:
n.trees:迭代次数;
interaction.depth:决策树的深度;
shrinkage:学习速率;
n.minobsinnode:节点分裂所需的最小训练样本数量。
R中的GBM(带有交叉验证)
> library(caret)
> fitControl <- trainControl(method = "cv",
number = 10, #5folds)
> tune_Grid <- expand.grid(interaction.depth = 2,
n.trees = 500,
shrinkage = 0.1,
n.minobsinnode = 10)
> set.seed(825)
> fit <- train(y_train ~ ., data = train,
method = "gbm",
trControl = fitControl,
verbose = FALSE,
tuneGrid = gbmGrid)
> predicted= predict(fit,test,type= "prob")[,2]
Python中的GBM
#导入libraries
from sklearn.ensemble import GradientBoostingClassifier #分类问题
from sklearn.ensemble import GradientBoostingRegressor #回归问题
#使用GBM算法
clf = GradientBoostingClassifier(n_estimators=100, learning_rate=1.0, max_depth=1)
clf.fit(X_train, y_train)
考虑到本文篇幅问题,论智将单独为XGBoost的R & Python实现写一篇教程,敬请期待!
论智在这里提供分类、回归各一个问题,感兴趣的读者可以前去一试。
回归:大型超市销售预测(https://datahack.analyticsvidhya.com/contest/practice-problem-bigmart-sales-prediction/)
分类:贷款预测(https://datahack.analyticsvidhya.com/contest/practice-problem-loan-prediction/)
原文地址:www.analyticsvidhya.com/blog/2016/04/complete-tutorial-tree-based-modeling-scratch-in-python/#eleven