专栏名称: 解说科研项目
解说国自然,主要公益解说国家自然科学基金相关政策,提供医学科学部项目撰写策略、注意事项等;同时,还进行医学相关的课题设计、SCI论文撰写投稿、医学统计与meta分析、生物信息学等培训交流。为广大医生朋友们的科研和基金申请提供帮助!
目录
相关文章推荐
APPSO  ·  这个中国 AI ... ·  6 小时前  
APPSO  ·  苹果发布两款 AI 电脑!一款自砍 ... ·  13 小时前  
小众软件  ·  另外两件事[250305] ·  18 小时前  
APPSO  ·  ChatGPT ... ·  昨天  
小众软件  ·  不爽 PowerToys 功能,手搓!将 ... ·  2 天前  
51好读  ›  专栏  ›  解说科研项目

机器学习笔记 05 -- 分类回归树

解说科研项目  · 公众号  ·  · 2018-11-22 17:57

正文

题记:这篇读书笔记我们将继续深入研究分类回归技术:回归树、分类树、随机森林与极限梯度提升。本文参考Cory Lesmeister博士主编《Mastering Machine Learning with R》(第2版)一书。数据来自R语言MASS包自带biopsy,Pima.tr和Pima.te数据集,ElemStatLearn包自带prostate数据集。代码来自Cory Lesmeister博士主编《Mastering Machine Learning with R》这本书配套的代码,笔者对部分代码进行修改。本文是个人的读书笔记,仅用于学习交流使用,我只是一个知识的搬运工。

1. 背景知识

“最好的分类器可能是随机森林(RF),而最好的版本(用R开发,通过caret包调用)可以在84.3%的数据集上达到94.1%的最大正确率,超过了90%的其他分类器。” -- 费尔南德斯.德尔加多 等(2014)

上文引自费尔南德斯德尔加多等人在《机器学习研究期刊》上发表的一篇论文,从这段话可以看出,我们在这一章要讨论的技术有多么强大,特别是用于解决分类问题时。当然,它们不是永恒的最优解,但确实提供了一个非常好的起点。

我们之前研究的技术或用于预测定量结果,或用于预测分类问题。本章要讨论的技术对这两类问题都适用,但处理问题的方式和前几章不同。本章我们并不引入新的问题,而是把这些技术用于前面已经解决的问题,着眼于这些技术能否提高预测能力。总而言之,本章案例的目的就是要看看是否能继续改善以前的模型。

第一个要讨论的项目是基础的决策树,模型建立和解释都非常简单。但决策树模型的表现并不如我们研究过的模型那样好,比如支持向量机模型;也比不上我们将要学习的模型,比如神经网络模型。所以,我们要讨论的是建立多个(有时是上百个)不同的树,然后把每个树的结果组合在一起,最后形成一个综合的预测。

正像本章开头引用的论文所说,这些方法产生的效果不次于甚至超过书中涉及的所有其他技术。这些技术被称为随机森林和梯度提升树。此外,在讨论案例之余,我们还会介绍如何在数据集上应用随机森林方法,以帮助实现特征消除或较特征选择。

2. 回归树

要想理解基于树的方法,比较容易的方式是从预测定量结果开始,然后学习它是如何解决分类问题的。树方法的精髓就是划分特征,从第一次分裂开始就要考虑如何最大程度改善RSS,然后持续进行“树杈”分裂,直到树结束。后面的划分并不作用于全体数据集,而仅作用于上次划分时落到这个分支之下的那部分数据。这个自顶向下的过程被称为“递归划分”。这个过程是贪婪的,这个名词在研究机器学习方法时会经常遇到。贪婪的含义是指算法在每次分裂中都追求最大程度减少RSS,而不管以后的划分中表现如何。这样做可能会生成一个带有无效分支的树,尽管偏差很小,但是方差很大。为了避免这个问题,生成完整的树之后,你要对树进行剪枝,得到最优的解。

这种方法的优点是可以处理高度非线性关系,但它还存在一些潜在的问题:一个观测被赋予所属终端节点的平均值,这会损害整体预测效果(高偏差)。相反,如果你一直对数据进行划分,树的层次越来越深,这样可以达到低偏差的效果,但是高方差又成了问题。和其他方法一样,你也可以用交叉验证来选择合适的深度。

3. 分类树

分类树与回归树的运行原理是一样的,区别在于决定分裂过程的不是RSS,而是误差率。这里的误差率不是你想的那样,简单地由误分类的观测数除以总观测数算出。实际上,进行树分裂时,误分类率本身可能会导致这样一种情况:你可以从下次分裂中获得一些有用信息,但误分类率却没有改善。我们看一个例子。

假设有一个节点N0,节点中有7个标号为N0的观测和3个标号为Yes的观测,我们就可以说误分类率为30%。记住这个数,然后通过另一种误差测量方式进行计算,这种方式称为基尼指数。单个节点的基尼指数计算公式如下:

基尼指数 = 1 - (类别1的概率)^2 - (类别2的概率)^2

所以,对于N0,基尼指数为1 - (0.7)^2 - (0.3)^2,等于0.42,与之相对的误分类率为30%。

将这个例子讨论得更深入一些,假设将节点N0分裂成两个节点N1和N2,N1中有3个观测属于类别1,没有属于类别2的观测;N2中有4个观测属于类别1,3个属于类别2。现在,树的这个分支的整体的误分类率还是30%,那么看看整体的基尼指数是如何改善的:

(1)基尼指数(N1) = 1 - (3/3)^2 - (0/3)^2 = 0

(2)基尼指数(N2) = 1 - (4/7)^2 - (3/7)^2 = 0.49

(3)新基尼指数 = (N1比例×基尼指数(N1))+(N2比例×基尼指数(N2)) = (0.3×0) + (0.7×0.49) = 0.343

通过一次基于替代误差率的分裂,我们确实改善了模型的不纯度,将其从原来的0.42减小到0.343,误分类率却没有变化。rpart()包就是使用Gini指数测量误差的,在本章中我们会使用这个程序包。

4. 随机森林

为了显著提高模型的预测能力,我们可以生成多个树,然后将这些树的结果组合起来。随机森林技术在模型构建过程中使用两种奇妙的方法,以实现这个构想。第一个方法称为自助聚集,或称装袋。

在装袋法中,使用数据集的一次随机抽样建立一个独立树,抽样的数量大概为全部观测的2/3(请记住,剩下的1/3被称为袋外数据,out-of-bag)。这个过程重复几十次或上百次,最后取平均结果。其中每个树都任其生长,不进行任何基于误差测量的剪枝,这意味着每个独立树的方差都很大。但是,通过对结果的平均化处理可以降低方差,同时又不增加偏差。

另一个在随机森林中使用的方法是,对数据进行随机抽样(装袋)的同时,独立树每次分裂时对输入特征也进行随机抽样。在randomForest包中,我们使用随机抽样数的默认值来对预测特征进行抽样。对于分类问题,默认值为所有预测特征数量的平方根;对于回归问题,默认值为所有预测特征数量除以3。在模型调优过程中,每次树分裂时,算法随机选择的预测特征数量是可变的。

通过每次分裂时对特征的随机抽样以及由此形成的一套方法,你可以减轻高度相关的预测特征的影响,这种预测特征在由装袋法生成的独立树中往往起主要作用。这种方法还使你不必思索如何减少装袋导致的高方差。独立树彼此之间的相关性减少之后,对结果的平均化可以使泛化效果更好,对于异常值的影响也更加不敏感,比仅进行装袋的效果要好。

5. 梯度提升

梯度提升法的主要思想是,先建立一个某种形式的初始模型(线性、样条、树或其他),称为基学习器;然后检查残差,在残差的基础上围绕损失函数拟合模型。损失函数测量模型和现实之间的差别,例如,在回归问题中可以用误差的平方。一直继续这个过程,直到满足某个特定的结束条件。这与下面的情形有点相似:一个学生进行模拟考试,100道题中错了30道,然后只研究那30道错题;在下次模考中,30道题中又错了10道,然后只研究那10道题,以此类推。如果你想更加深入地研究背后的理论,可以参考Frontiers in Neurorobotics, Gradient boosting machines,a tutorial, Natekin A., Knoll A. (2013)(http://www.ncbi.nlm.nih.gov/pmc/atricles/PMC3885826/).

刚才提到过,梯度提升方法可以用于很多不同类型的基分类器,本章只集中讨论基于树的学习这个主题。每个树迭代的次数都很少,我们要通过一个调优参数决定树的分裂次数,这个参数称为交互深度。事实上,有些树很小,只可以分裂一次,这样的树就被称为“树桩”。这些树依次按照损失函数拟合残差,直至达到我们指定的树的数量(我们的结束条件)。使用Xgboost包进行建模的过程中,有一些参数需要调整。Xgboost表示eXtreme Gradient Boosting。这个程序包表现优异,广泛应用于网上的数据科学竞赛。在下面这个网站中,你可以找到关于提升树和Xgboost的非常详尽的背景资料:http://xgboost.readthedocs.io/en/latest/model.html.

在下面的案例中,我们要看看如何找到最优的超参数,并使模型生成有意义的结果和预测。这些参数之间会互相作用,如果你只攻一点而不顾其他,模型的效果可能会越来越差。在调参过程中,caret包会助你一臂之力。

6. 案例分析

在前几章中,我们通过努力建立了一些模型,本章的总体目标是看看我们能否提高这些模型的预测能力。对于回归问题,重新回到第4章中的前列腺癌数据集,以0.444的均方误差作为基准,看看能否改善。对于分类问题,我们使用第3章中的乳腺癌数据集和第5章中的皮玛印第安人糖尿病数据集。在乳腺癌数据集中,我们取得了97.6%的预测正确率。在糖尿病数据集中,正确率是79.6%,我们希望再提高一些。随机森林和提升方法都会用于这3个数据集,简单树模型只用于乳腺癌数据集和前列腺癌数据集。

7. 模型构建与模型评价

要进行模型构建,需要加载7个不同的R包。我们会实验每种技术,并与前面章节中介绍的方法进行对比,看看它们在数据分析中会有什么突出表现。

(1)回归树 我们直接跳到前列腺癌数据集,但首先要加载所需的R包。当然,要确定在加载之前你已经安装了它们:

library(rpart) #classification and regression trees
library(partykit) #treeplots
library(MASS) #breast and pima indian data
library(ElemStatLearn) #prostate data
library (randomForest) #random forests
library(xgboost) #gradient boosting 
library(caret) #tune hyper-parameters

先用prostate数据集做回归。使用ifelse()函数将gleason评分编码为指标变量,划分训练数据集和测试数据集,训练数据集为pros.train,测试数据集为pros.test,如下所示:

data(prostate)
prostate$gleason 6, 0, 1)
pros.train TRUE)[, 1:9]
pros.test = subset(prostate, train == FALSE)[, 1:9]

要在训练数据集上建立回归树,需要使用party包中的rpart()函数,语法与我们在其他建模技术中使用过的非常类似:

set.seed(123)
tree.pros 

可以调出这个对象,然后检查每次分裂的误差,以决定最优的树分裂次数:

tree.pros$cptable
##           CP nsplit rel error    xerror      xstd
## 1 0.35852251      0 1.0000000 1.0364016 0.1822698
## 2 0.12295687      1 0.6414775 0.8395071 0.1214181
## 3 0.11639953      2 0.5185206 0.7255295 0.1015424
## 4 0.05350873      3 0.4021211 0.7608289 0.1109777
## 5 0.01032838      4 0.3486124 0.6911426 0.1061507
## 6 0.01000000      5 0.3382840 0.7102030 0.1093327

这个表对于我们的分析非常重要。标号为CP的第一列是成本复杂性参数,第二列nsplit是树分裂的次数,rel error列表示相对误差,即某次分裂的RSS除以不分裂的RSS(RSS(k)/RSS(0)),xerror和xstd都是基于10折交叉验证的,xerror是平均误差,xstd是交叉验证过程的标准差。可以看出,5次分裂在整个数据集上产生的误差最小,但使用交叉验证时,4次分裂产生的误差略微更小。可以使用plotcp()函数查看统计图:

plotcp(tree.pros)

这幅图使用误差条表示树的规模和相对误差之间的关系,误差条和树规模是对应的。选择树的规模为5,也就是经过4次分裂可以建立一个新的树对象,这个树的xerror可以通过剪枝达到最小化。剪枝的方法是先建立一个cp对象,将这个对象和表中第5行相关联,然后使用prune()函数完成剩下的工作:

cp 5, ])
prune.tree.pros 

完成上面的代码后,可以用统计图比较完整树和剪枝树。由partykit包生成的树图明显优于party包生成的,在plot()函数中,你可以简单地使用as.party()函数作为包装器函数:

plot(as.party(tree.pros))

同样地,使用as.party()函数处理剪枝树:

plot(as.party(prune.tree.pros))

请注意,除了最后一次分裂(完整树包含变量age),两个树是完全一样的。有趣的是,树的第一次分裂和第二次分裂都与肿瘤大小的对数(lcavol)有关。这两张图的信息量都很大,因为它们显示了树的分裂、节点、每节点观测数,以及预测结果的箱线图。

下面我们看看剪枝树在测试集上表现如何。在测试数据上使用predict()函数进行预测,并建立一个对象保存这些预测值。然后用预测值减去实际值,得到误差,最后算出误差平方的平均值:

party.pros.test #calculate residualmean(rpart.resid^2)
## [1] 0.5267748

第4章中的基准MSE为0.44,我们的努力并没有改善预测结果。但是,这种方法并非一无是处。从生成的树图中可以看出对响应变量影响最大的特征,并且很容易解释。就像引言中说过的,树模型很容易理解和解释,这往往比正确率重要得多。

(2)分类树 对于分类问题,我们先像第3章一样准备好乳腺癌数据。加载数据之后,你要删除患者ID,对特征进行重新命名,删除一些缺失值,然后建立训练数据集和测试数据集。如下所示:

data(biopsy)
biopsy 1]
names(biopsy) "thick", "u.size"




    
, "u.shape", "adhsn", "s.size", "nucl", "chrom", "n.nuc", "mit", "class")
biopsy.v2 123) #random number generator
ind 2, nrow(biopsy.v2), replace = TRUE, prob = c(0.7, 0.3)) biop.train 1, ] #the training data set
biop.test 2, ] #the test data set

准备好合适的数据之后,使用和前面回归问题一样的代码风格来解决分类问题。建立分类树之前,要确保结果变量是一个因子,可以用str()函数进行检查:

str(biop.test)
## 'data.frame':    209 obs. of  10 variables:
##  $ thick  : int  5 6 4 2 1 7 6 7 1 3 ...
##  $ u.size : int  4 8 1 1 1 4 1 3 1 2 ...
##  $ u.shape: int  4 8 1 2 1 6 1 2 1 1 ...
##  $ adhsn  : int  5 1 3 1 1 4 1 10 1 1 ...
##  $ s.size : int  7 3 2 2 1 6 2 5 2 1 ...
##  $ nucl   : int  10 4 1 1 1 1 1 10 1 1 ...
##  $ chrom  : int  3 3 3 3 3 4 3 5 3 2 ...
##  $ n.nuc  : int  2 7 1 1 1 3 1 4 1 1 ...
##  $ mit    : int  1 1 1 1 1 1 1 4 1 1 ...
##  $ class  : Factor w/ 2 levels "benign","malignant": 1 1 1 1 1 2 1 2 1 1 ...
##  - attr(*, "na.action")= 'omit' Named int  24 41 140 146 159 165 236 250 276 293 ...
##   ..- attr(*, "names")= chr  "24" "41" "140" "146" ...

先生成树,然后检查输出中的表格,找到最优分裂次数:

set.seed(123)
tree.biop 
##           CP nsplit rel error    xerror       xstd
## 1 0.79651163      0 1.0000000 1.0000000 0.06086254
## 2 0.07558140      1 0.2034884 0.2674419 0.03746996
## 3 0.01162791      2 0.1279070 0.1453488 0.02829278
## 4 0.01000000      3 0.1162791 0.1744186 0.03082013

交叉验证误差仅在两次分裂后就达到了最小值(见第3行)。现在可以对树进行剪枝,再在图中绘制剪枝树,看看它在测试集上的表现:

cp 3, ])
prune.tree.biop = prune(tree.biop, cp 

plot(as.party(prune.tree.biop))

从对树图的检查中可以发现,细胞大小均匀度是第一个分裂点,第二个是nuclei。完整树还有一个分支是细胞浓度。使用predict()函数并指定type=“class”,在测试集上进行预测。如下所示:

rparty.test "class")
table(rparty.test, biop.test$class)
## rparty.test benign malignant
##   benign       136         3
##   malignant      6        64
(136+64)/209
## [1] 0.9569378

只有两个分支的基本树模型给出了差不多96%的正确率。这虽然比不上Logistic回归的正确率97.6%,但足以激励我们相信,在随后的随机森林方法中会得到更好的改善效果。

(3)随机森林回归 在这一节中,我们还是首先处理前列腺癌数据集,然后处理乳腺癌数据集和皮玛印第安人糖尿病数据集,使用的程序包是randomForest。建立一个随机森林对象的通用语法是使用randomForest()函数,指定模型公式和数据集这两个基本参数。回想一下每次树迭代默认的变量抽样数,对于回归问题,是p/3;对于分类问题,是p的平方根,p为数据集中预测变量的个数。对于大规模数据集,就p而言,你可以调整mtry参数,它可以确定每次迭代的变量抽样数值。如果p小于10,可以省略上面的调整过程。想在多特征数据集中优化mtry参数时,可以使用caret包,或使用randomForest包中的tuneRF()函数。了解这些情况之后,即可建立随机森林模型并检查模型结果。如下所示:

set.seed(123)
rf.pros 
## Call:
##  randomForest(formula = lpsa ~ ., data = pros.train) 
##                Type of random forest: regression
##                      Number of trees: 500
## No. of variables tried at each split: 2
##           Mean of squared residuals: 0.6936697
##                     % Var explained: 51.73

调用rf.pros对象,可以知道随机森林生成了500个不同的树(默认设置),并且在每次树分裂时随机抽出两个变量。结果的MSE为0.68,差不多53%的方差得到了解释。下面看看能否对默认数量的树做一些改善。过多的树会导致过拟合,当然,“多大的数量是‘过多’”依赖于数据规模。两种方式可以帮助我们达到目的,第一是做出rf.pros的统计图,另一件是求出最小的MSE:

plot(rf.pros)

这个图表示MSE与模型中树的数量之间的关系。可以看出,树的数量增加时,一开始MSE会有显著改善,当森林中大约建立了100棵树之后,改善几乎停滞。可以通过which.min()函数找出具体的最优树数量,如下所示:

which.min(rf.pros$mse)
## [1] 80

可以试试有75棵树的随机森林,在模型语法中指定ntree = 75即可:

set.seed(123)
rf.pros.2 75)
rf.pros.2
## Call:
## randomForest(formula = lpsa ~ ., data = pros.train, ntree = 75) 
##                Type of random forest: regression
##                      Number of trees: 75
## No. of variables tried at each split: 2
##           Mean of squared residuals: 0.6672276
##                     % Var explained: 53.57

可以看到,MSE和解释方差都有一点微小的改善。对模型进行检验之前,先看看另一张统计图。如果使用自助重抽样和两个随机预测变量建立了75棵不同的树,要想将树的结果组合起来,需要一种方法确定哪些变量驱动着结果。独木不成林,你需要做出变量重要性统计图及相应的列表。Y轴是按重要性降序排列的变量列表,X轴是MSE改善百分比。请注意,在分类问题中,X轴应该是基尼指数的改善。使用varImpPlot()函数生成统计图:

varImpPlot(rf.pros.2, scale = TRUE,
           main = "Variable Importance Plot - PSA Score")

和单个树模型一致,lcavol是最重要的变量,lweight次之。如果想查看具体数据,可以使用importance()函数。如下所示:

importance(rf.pros.2)
##         IncNodePurity
## lcavol      24.657359
## lweight     15.817242
## age          7.240911
## lbph         5.550491
## svi          8.175192
## lcp          8.387174
## gleason      4.879600
## pgg45        6.876621

现在可以看看模型在测试数据上的表现:

rf.pros.test #plot(rf.pros.test, pros.test$lpsa)
rf.resid #calculate residual
mean(rf.resid^2)
## [1] 0.5568805

MSE依然高于我们在第4章中使用LASSO得到的0.44,但也不比单个树模型更好。

(4)随机森林分类 你可能会对随机森林回归模型的表现感到失望,但这种技术的真正威力在于解决分类问题。我们从乳腺癌诊断数据开始,这个过程和在回归问题中的做法几乎一样:

set.seed(123)
rf.biop 
## Call:
## randomForest(formula = class ~ ., data = biop.train) 
##                Type of random forest: classification
##                      Number of trees: 500
## No. of variables tried at each split: 3
##         OOB estimate of  error rate: 3.38%
## Confusion matrix:
##           benign malignant class.error
## benign       294         8  0.02649007
## malignant      8       164  0.04651163

OOB(袋外数据)误差率为3.38%。随机森林还是由默认的全部500棵树组成。画出表示误差和树的数量关系的统计图:

plot(rf.biop)

图中显示,误差和标准误差的最小值是在树的数量很少的时候取得的,可以使用which.min()函数找出具体值。和前面不同的一点是,需要指定第一列来得到误差率,这是整体误差率。算法会为每个类标号的误差率生成一个附加列,本例中不需要它们。模型结果中也没有mse,而是代之以err.rate。如下页代所示:

which.min(rf.biop$err.rate[, 1])
## [1] 125

只需19棵树就可以使模型正确率达到最优。实验一下,看看模型的表现:

set.seed(123




    
)
rf.biop.2 19)#getTree(rf.biop,1)
rf.biop.2
## Call:
## randomForest(formula = class ~ ., data = biop.train, ntree = 19) 
##                Type of random forest: classification
##                      Number of trees: 19
## No. of variables tried at each split: 3
##         OOB estimate of  error rate: 4.22%
## Confusion matrix:
##           benign malignant class.error
## benign       293         9  0.02980132
## malignant     11       161  0.06395349
rf.biop.test "response")
table(rf.biop.test, biop.test$class)
## rf.biop.test benign malignant
##    benign       138         0
##    malignant      4        67
(139 + 67) / 209
## [1] 0.9856459

训练集上的误差率4.22%,在测试集上甚至表现得更好,误差率不到1.5%。测试集的209个观测中,只有3个观测被误分类,而且没有一个是误诊为“恶性”的。回忆一下,我们使用Logistic回归得到的正确率最好结果是97.6%,所以随机森林模型就是至今为止在乳腺癌数据上的最佳模型。进行下面的内容之前,先看看变量重要性统计图。

varImpPlot(rf.biop.2)

上图中,变量重要性是指每个变量对基尼指数平均减少量的贡献,此处的变量重要性与单个树分裂时有很大区别。回忆一下,单个树是在细胞大小均匀度开始分裂的(与随机森林一致),然后是nuclei,接着是细胞密度。这揭示了随机森林技术具有非常大的潜力,不但可以提高模型预测能力,还可以改善特征选择的结果。

现在,开始面对皮玛印第安人糖尿病模型的艰巨挑战。我们先做好数据准备,如下所示:

data(Pima.tr)
data(Pima.te)
pima 502)
ind 2, nrow(pima), replace = TRUE, prob = c(0.7, 0.3))
pima.train 1, ]
pima.test 2, ]

做完数据准备工作,下一步建立模型。如下所示:

set.seed(321)
rf.pima 
## Call:
## randomForest(formula = type ~ ., data = pima.train) 
##                Type of random forest: classification
##                      Number of trees: 500
## No. of variables tried at each split: 2
## 
##         OOB estimate of  error rate: 20.26%
## Confusion matrix:
##      No Yes class.error
## No  235  27   0.1030534
## Yes  51  72   0.4146341
# plot(rf.pima)

我们在训练数据集上得到了20.26%的误分类误差率,并不比以前的结果更好。下面对树的数目进行优化,看看能否显著改善模型结果:

which.min(rf.pima$err.rate[,1])
## [1] 88
set.seed(321)
rf.pima.2 80)
rf.pima.2
## Call:
## randomForest(formula = type ~ ., data = pima.train, ntree = 80) 
##                Type of random forest: classification
##                      Number of trees: 80
## No. of variables tried at each split: 2
## 
##         OOB estimate of  error rate: 21.04%
## Confusion matrix:
##      No Yes class.error
## No  233  29   0.1106870
## Yes  52  71   0.4227642

当随机森林中有80棵树时,OOB误差并没有改善。随机森林能够在测试集上证明自己吗?我们来看一下。如下所示

rf.pima.test "response")
table(rf.pima.test, pima.test$type)
## rf.pima.test No Yes
##          No  74  18
##          Yes 19  36
(75




    
+33)/147
## [1] 0.7346939
#varImpPlot(rf.pima.2)

我们在测试集上只得到了大约73%的正确率,低于使用SVM的结果。虽然随机森林在糖尿病数据集上令人失望,但它已经证明了,自己在乳腺癌诊断方面是迄今为止最好的分类器。最后,我们实验一下梯度提升方法。

(5)极限梯度提升分类 我们提到过,在这一节要使用已经加载的xgboost包。因为这种方法的效果如雷贯耳,所以我们直接用它解决最有挑战性的皮玛印第安人糖尿病问题。正如在提升算法简介中所说,我们要调整一些参数。

(1)nrounds:最大迭代次数(最终模型中树的数量)。

(2)colsample_bytree:建立树时随机抽取的特征数量,用一个比率表示,默认值为1(使用100%的特征)。

(3)min_child_weight:对树进行提升时使用的最小权重,默认为1。

(4)eta:学习率,每棵树在最终解中的贡献,默认为0.3。

(5)gamma:在树中新增一个叶子分区时所需的最小减损。

(6)subsample:子样本数据占整个观测的比例,默认值为1(100%)。

(7)max_depth:单个树的最大深度。

使用expand.grid()函数可以建立实验网格,以运行caret包的训练过程。对于前面列出的参数,如果没有设定具体值,那么即使有默认值,运行函数时也会收到出错信息。下面的参数取值是基于我以前的一些训练迭代而设定的。建议各位亲自实验参数调整过程。使用以下命令建立网格:

grid = expand.grid(
  nrounds = c(75, 100),
  colsample_bytree = 1,
  min_child_weight = 1,
  eta = c(0.01, 0.1, 0.3), #0.3 is default,
  gamma = c(0.5, 0.25),
  subsample = 0.5,
  max_depth = c(2, 3)
)
grid
##    nrounds colsample_bytree min_child_weight  eta gamma subsample
## 1       75                1                1 0.01  0.50       0.5
## 2      100                1                1 0.01  0.50       0.5
## 3       75                1                1 0.10  0.50       0.5
## 4      100                1                1 0.10  0.50       0.5
## 5       75                1                1 0.30  0.50       0.5
## 6      100                1                1 0.30  0.50       0.5
## 7       75                1                1 0.01  0.25       0.5
## 8      100                1                1 0.01  0.25       0.5
## 9       75                1                1 0.10  0.25       0.5
## 10     100                1                1 0.10  0.25       0.5
## 11      75                1                1 0.30  0.25       0.5
## 12     100                1                1 0.30  0.25       0.5
## 13      75                1                1 0.01  0.50       0.5
## 14     100                1                1 0.01  0.50       0.5
## 15      75                1                1 0.10  0.50       0.5
## 16     100                1                1 0.10  0.50       0.5
## 17      75                1                1 0.30  0.50       0.5
## 18     100                1                1 0.30  0.50       0.5
## 19      75                1                1 0.01  0.25       0.5
## 20     100                1                1 0.01  0.25       0.5
## 21      75                1                1 0.10  0.25       0.5
## 22     100                1                1 0.10  0.25       0.5
## 23      75                1                1 0.30  0.25       0.5
## 24     100                1                1 0.30  0.25       0.5
##    max_depth
## 1          2
## 2          2
## 3          2
## 4          2
## 5          2
## 6          2
## 7          2
## 8          2
## 9          2
## 10         2
## 11         2
## 12         2
## 13         3
## 14         3
## 15         3
## 16         3
## 17         3
## 18         3
## 19         3
## 20         3
## 21         3
## 22         3
## 23         3
## 24         3

以上命令会建立一个具有24个模型的网格,caret包会运行这些模型,以确定最好的调优参数。此处必须提示各位,对于我们现在所用的这种规模的数据集,代码运行过程只需几秒钟,但对于一些大数据集,这个运行过程可能需要几小时。所以,在时间非常宝贵的情况下,你必须应用自己的判断力,并使用小数据样本来找出合适的调优参数,否则硬盘空间可能不足。

使用car包的train()函数之前,我要创建一个名为cntrl的对象,来设定trainControl的参数。这个对象会保存我们要使用的方法,以训练调优参数。我们使用5折交叉验证,如下所示:

cntrl = trainControl(
  method = "cv",
  number = 5,
  verboseIter = TRUE,
  returnData = FALSE,
  returnResamp = "final"                                                        )

要使用train.xgb()函数,只需和训练其他模型一样,设定好所需参数即可:训练数据集、标号、训练控制对象和实验网格。记得要设定随机数种子:

set.seed(1)
train.xgb = train(
  x = pima.train[, 1:7],
  y = ,pima.train[, 8],
  trControl = cntrl,
  tuneGrid = grid,
  method = "xgbTree")
## + Fold1: eta=0.01, max_depth=2, gamma=0.25, colsample_bytree=1, min_child_weight=1, subsample=0.5, nrounds=100 
......
## - Fold5: eta=0.30, max_depth=3, gamma=0.50, colsample_bytree=1, min_child_weight=1, subsample=0.5, nrounds=100 
## Aggregating results
## Selecting tuning parameters
## Fitting nrounds = 100, max_depth = 2, eta = 0.01, gamma = 0.5, colsample_bytree = 1, min_child_weight = 1, subsample = 0.5 on full training set

因为在trControl中设定了verboseIter为TURE,所以可以看到每折交叉验证中的每次训练迭代。

调用train.xgb这个对象可以得到最优的参数,以及每种参数设置的结果,如下所示(有所简省):

train.xgb
## eXtreme Gradient Boosting 
## No pre-processing
## Resampling: Cross-Validated (5 fold) 
## Summary of sample sizes: 308, 308, 308, 308, 308 
## Resampling results across tuning parameters:
##   eta   max_depth  gamma  nrounds  Accuracy   Kappa    
##   0.01  2          0.25    75      0.7974026  0.5016374
##   0.01  2          0.25   100      0.8000000  0.5086971
##   0.01  2          0.50    75      0.7974026  0.5011633
##   0.01  2          0.50   100      0.8025974  0.5175787
##   0.01  3          0.25    75      0.7922078  0.4922910
##   0.01  3          0.25   100      0.7974026  0.5066888
##   0.01  3          0.50    75      0.7948052  0.4997955
##   0.01  3          0.50   100      0.7948052  0.4997955
##   0.10  2          0.25    75      0.7870130  0.4863234
##   0.10  2          0.25   100      0.7896104  0.4946068
##   0.10  2          0.50    75      0.7974026  0.5208894
##   0.10  2          0.50   100      0.7922078  0.5078930
##   0.10  3          0.25    75      0.7870130  0.4871247
##   0.10  3          0.25   100      0.7870130  0.4946006
##   0.10  3          0.50    75      0.7896104  0.5000596
##   0.10  3          0.50   100      0.7948052  0.5131372
##   0.30  2          0.25    75      0.7688312  0.4604660
##   0.30  2          0.25   100      0.7688312  0.4599298
##   0.30  2          0.50    75      0.7818182  0.4853625
##   0.30  2          0.50   100      0.7636364  0.4481784
##   0.30  3          0.25    75      0.7974026  0.5112360
##   0.30  3          0.25   100      0.7740260  0.4589619
##   0.30  3          0.50    75      0.7818182  0.4946846
##   0.30  3          0.50   100      0.7792208  0.4877327
### Tuning parameter 'colsample_bytree' was held constant at a value of
##  1
## Tuning parameter 'min_child_weight' was held constant at a value of
##  1
## Tuning parameter 'subsample' was held constant at a value of 0.5
## Accuracy was used to select the optimal model using the largest value.
## The final values used for the model were nrounds = 100, max_depth = 2,
##  eta = 0.01, gamma = 0.5, colsample_bytree = 1, min_child_weight = 1
##  and subsample = 0.5.

由此可以得到最优的参数组合来建立模型。模型在训练数据上的正确率是81%,Kappa值是0.55。下面要做的事情有点复杂,但我认为这才是最佳实践。首先创建一个参数列表,供Xgboost包的训练函数xgb.train()使用。然后将数据框转换为一个输入特征矩阵,以及一个带标号的数值型结果列表(其中的值是0和1)。接着,将特征矩阵和标号列表组合成符合要求的输入,即一个xgb.Dmatrix对象。代码如下:

param "binary:logistic", 
                booster             = "gbtree",
                eval_metric         = "error",
                eta                 = 0.1, 
                max_depth           = 2, 
                subsample           = 0.5,
                colsample_bytree    = 1,
                gamma               = 0.5)

x 1:7])
y "Yes", 1, 0)
train.mat 

这些准备工作完成之后,即可创建模型:

set.seed(1)
xgb.fit 75)
xgb.fit
## ##### xgb.Booster
## raw: 29 Kb 
## call:
##   xgb.train(params = param, data = train.mat, nrounds = 75)
## params (as set within xgb.train):
##   objective = "binary:logistic", booster = "gbtree", eval_metric = "error", eta = "0.1", max_depth = "2", subsample = "0.5", colsample_bytree = "1", gamma = "0.5", silent = "1"
## xgb.attributes:
##   niter
## callbacks:
##   cb.print.evaluation(period = print_every_n)
## # of features: 7 
## niter: 75
## nfeatures : 7
pred # summary(pred)# head(pred)# head(y)

在测试集上查看模型效果之前,先检查变量重要性,并绘制统计图。你可以检查3个项目:gain、cover和frequecy。gain是这个特征对其所在分支的正确率做出的改善,cover是与这个特征相关的全体观测的相对数量,frequency是这个特征在所有树中出现的次数百分比。以下代码会生成我们需要的输出:

impMatrix 2]], model = xgb.fit)
impMatrix 
##    Feature       Gain      Cover  Frequency
## 1:     glu 0.40000545 0.31701685 0.24509804
## 2:     age 0.16177613 0.15685051 0.17156863
## 3:     bmi 0.12074047 0.14691329 0.14705882
## 4:     ped 0.11717240 0.15400327 0.16666667
## 5:   npreg 0.07642331 0.05920866 0.06862745
## 6:    skin 0.06389970 0.08682107 0.10294118
## 7:      bp 0.05998254 0.07918635 0.09803922
xgb.plot.importance(impMatrix, main = "Gain by Feature")

与其他方法相比,这个特征重要性图有什么不同吗?







请到「今天看啥」查看全文