《精通机器学习:基于R 第二版》学习笔记
1、集成模型简介
集成学习的定义是:“有策略地建立多个模型(如分类器或专家系统)并将其组合在一起,解决特定计算智能问题的过程。”在随机森林和梯度提升模型中,我们将几百或几千棵树的“投票”结果组合起来进行预测。于是,根据集成学习的定义,这些模型就是集成学习模型。
在机器学习中,这种方法的优点是可以将几种性能平平甚至很差的学习器的预测结果结合起来,从而提高整体准确率。
2、数据准备
> # 使用皮玛印第安糖尿病数据集
> library(pacman)
> p_load(MASS, caret, caretEnsemble, caTools)
>
> pima <- rbind(Pima.tr, Pima.te)
> set.seed(123)
>
> # 拆分为训练集和测试集
> split <- createDataPartition(y = pima$type, p = 0.75, list = F)
> train <- pima[split, ]
> test <- pima[-split, ]
> table(train$type)
##
## No Yes
## 267 133
3、模型评价与模型选择
训练集中Yes和No的比例大概为2:1。在很多数据集中,我们感兴趣的结果是一种罕见的事件。于是你就会得到这样一种分类器,它的正确率非常高,但预测我们感兴趣的结果时效果非常差。也就是说,根本预测不出真阳性的结果。为了平衡响应变量,可以对少数类进行上采样,对多数类进行下采样,建立“人工合成”的数据。
在上采样过程中,对于交叉验证使用的每折数据,少数类都使用有放回的随机抽样,以使其数量与多数类的观测数量相匹配。
> control <- trainControl(method = "cv",
+ # 5折交叉验证
+ number = 5,
+ # 保存最后预测概率
+ savePredictions = "final",
+ classProbs = T,
+ # 按照索引再抽样,使基础模型使用同样的数据折
+ index = createResample(train$type,5),
+ # 上采样
+ sampling = "up",
+ summaryFunction = twoClassSummary)
训练模型,可以使用任意caret包支持的模型。我们训练3种:分类树模型:"rpart"、多元自适应回归样条模型:"earth"和K最近邻模型:"knn"。
> set.seed(123)
> # caretList() 函数不但能够建立模型,还可以按照caret包的规则调整每种模型的超参数
> # 使用caretModelSpec()函数可以为每种模型创建各自的调优参数网格
> models <- caretList(type ~ ., data = train, trControl = control, metric = "ROC",
+ methodList = c("rpart", "earth", "knn"))
> models
## $rpart
## CART
##
## 400 samples
## 7 predictor
## 2 classes: 'No', 'Yes'
##
## No pre-processing
## Resampling: Cross-Validated (5 fold)
## Summary of sample sizes: 400, 400, 400, 400, 400
## Addtional sampling using up-sampling
##
## Resampling results across tuning parameters:
##
## cp ROC Sens Spec
## 0.02631579 0.7317882 0.7083862 0.7436684
## 0.03383459 0.7549152 0.7268892 0.7400487
## 0.28571429 0.6766871 0.7609114 0.5924628
##
## ROC was used to select the optimal model using the largest value.
## The final value used for the model was cp = 0.03383459.
##
## $earth
## Multivariate Adaptive Regression Spline
##
## 400 samples
## 7 predictor
## 2 classes: 'No', 'Yes'
##
## No pre-processing
## Resampling: Cross-Validated (5 fold)
## Summary of sample sizes: 400, 400, 400, 400, 400
## Addtional sampling using up-sampling
##
## Resampling results across tuning parameters:
##
## nprune ROC Sens Spec
## 2 0.7729508 0.6911083 0.7151514
## 8 0.7685541 0.7188456 0.6668305
## 14 0.7486742 0.7106624 0.6515910
##
## Tuning parameter 'degree' was held constant at a value of 1
## ROC was used to select the optimal model using the largest value.
## The final values used for the model were nprune = 2 and degree = 1.
##
## $knn
## k-Nearest Neighbors
##
## 400 samples
## 7 predictor
## 2 classes: 'No', 'Yes'
##
## No pre-processing
## Resampling: Cross-Validated (5 fold)
## Summary of sample sizes: 400, 400, 400, 400, 400
## Addtional sampling using up-sampling
##
## Resampling results across tuning parameters:
##
## k ROC Sens Spec
## 5 0.7120835 0.6554364 0.6975610
## 7 0.7409795 0.6941265 0.6695143
## 9 0.7590350 0.7110115 0.6845189
##
## ROC was used to select the optimal model using the largest value.
## The final value used for the model was k = 9.
##
## attr(,"class")
## [1] "caretList"
基础模型能够有效组合的要求是,它们不高度相关。模型预测结果是否相关没有严格的规则,我们应该用结果进行实验,查看结果:
> modelCor(resamples(models))
## rpart earth knn
## rpart 1.0000000 0.6360436 0.8140344
## earth 0.6360436 1.0000000 0.3596560
## knn 0.8140344 0.3596560 1.0000000
rpart与knn高度相关。我们先忽略它,继续建立第四个分类器——融合模型——并检查结果:
> model.preds <- lapply(models, predict, newdata = test, type = "prob") %>%
+ lapply(function(x) x[, "Yes"]) %>% as.data.frame()
使用 caretStack() 函数将这些模型融合在一起,进行最终预测。
> stack <- caretStack(models, method = "glm", metric = "ROC",
+ trControl = trainControl(method = "boot",
+ number = 5, savePredictions = "final", classProbs = T,
+ summaryFunction = twoClassSummary))
> summary(stack)
##
## Call:
## NULL
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -1.7594 -0.7203 -0.4605 0.8363 2.3597
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) 1.5601 0.1998 7.809 5.76e-15 ***
## rpart -1.5056 0.3690 -4.081 4.49e-05 ***
## earth -1.5263 0.5211 -2.929 0.0034 **
## knn -1.3850 0.3438 -4.029 5.61e-05 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 954.74 on 744 degrees of freedom
## Residual deviance: 756.85 on 741 degrees of freedom
## AIC: 764.85
##
## Number of Fisher Scoring iterations: 4
即使rpart模型和knn模型高度相关,它们的系数也依然是显著的,所以应该可以在分析中保留这两个模型。下面比较单独模型的结果和集成模型的结果:
> ensemble.prob <- 1 - predict(stack, newdata = test, type = "prob")
> model.preds <- model.preds %>% mutate(ensemble = ensemble.prob)
> colAUC(model.preds, test$type)
## rpart earth knn ensemble
## No vs. Yes 0.8512397 0.8358729 0.8667355 0.8743543
通过 colAUC() 函数可以看到单独模型的AUC和集成模型的AUC,通过模型融合建立的集成模型确实可以提高预测能力。
4、多类分类
4.1 数据理解与数据准备
> p_load(mlr, ggplot2, HDclassif, DMwR, reshape2, corrplot)
> data(wine)
> table(wine$class)
##
## 1 2 3
## 59 71 48
响应变量是数值型的标签(1、2和3)。
使用合成少数类过采样技术,使数据量倍增:
> # 将类别转换为因子变量,否则函数不起作用
> wine$class <- as.factor(wine$class)
>
> set.seed(123)
>
> # SMOTE() 函数中,默认的最近邻数量为5
> # 如果想创建数量是现有数量2倍的少数类,就设定 'percent.over = 100'
> df <- DMwR::SMOTE(class ~ ., wine, perc.over = 300, perc.under = 300)
> table(df$class)
##
## 1 2 3
## 187 245 192
现在数据集的观测变为了624个。然后对各特征进行可视化:
> # 先标准化
> wine.scale <- scale(wine[, 2:5]) %>% as.data.frame() %>% mutate(class = wine$class)
>
> wine.scale %>% melt(id.var = "class") %>% ggplot(aes(class, value)) +
+ geom_boxplot(outlier.colour = "red") + facet_wrap(~variable, ncol = 2) +
+ theme_bw()
数据分布箱线图
离群点处理:对于其中的高异常值(高于第99个百分位),将其设为第75个百分位的值;对于其中的低异常值(低于第1个百分位),将其设为第25个百分位的值。
> out <- function(x) {
+ x[x > quantile(x, 0.99)] <- quantile(x, 0.75)
+ x[x < quantile(x, 0.01)] <- quantile(x, 0.25)
+ return(x)
+ }
创建新数据框:
> wine.trunc <- lapply(wine[, -1], out) %>% as.data.frame() %>% cbind(class = wine$class)
>
> # 选1个特征看看
> wine.trunc %>% melt(id.var = "class") %>% filter(variable == "V3") %>%
+ ggplot(aes(class, value)) +
+ geom_boxplot(outlier.colour = "red") + theme_bw()
处理离群点后的箱线图
可以看到,效果非常好。再看看变量之间的相关性:
> wine.trunc[, -14] %>% cor(.) %>% corrplot.mixed(upper = "ellipse")
变量间的相关性
V6和V7高度相关,一般来说,在基于非线性的学习方法中,这不是一个问题。但我们还是加入一个L2惩罚项(岭回归)来解决它。
4.2 模型评价与模型选择
> set.seed(123)
> split <- createDataPartition(y = df$class, p = 0.7, list = F)
> train.rf <- df[split, ]
> test.rf <- df[-split, ]
>
> # mlr包要求将训练集数据保存在task数据结构中,该数据结构专门为分类任务而设计
> wine.task <- makeClassifTask(id = "wine", data = train.rf, target = "class")
4.2.1 随机森林
> # 创建一个重抽样对象来为随机森林模型调整树的数量,它包括3个子样本
> rdesc <- makeResampleDesc("Subsample", iters = 3)
>
> # 建立一个树的网格来调整树的数量,最小值为750,最大值为2000
> # 也可以使用caret包建立多个参数列表
> param <- makeParamSet(makeDiscreteParam("ntree",
+ values = c(750, 1000, 1250, 1500, 1750, 2000)))
>
> # 创建控制对象,建立数值网格 这样即可调整超参数,从而找出最优树数量
> ctrl <- makeTuneControlGrid()
>
> # 调出最优树数量和相应的样本外误差
> tuning <- tuneParams("classif.randomForest", task = wine.task, resampling = rdesc,
+ par.set = param, control = ctrl)
> tuning$x
## $ntree
## [1] 1250
> tuning$y
## mmce.test.mean
## 0
最优的树数量是1250,相应的平均误分类率是0,一个完美的分类。
使用超参数作为makelearner函数的训练包装器
> rf <- setHyperPars(makeLearner("classif.randomForest", predict.type = "prob", par.vals = tuning$x))
>
> # 训练模型
> fit.rf <- train(rf, wine.task)
>
> # 查看训练集上的混淆矩阵
> fit.rf$learner.model
##
## Call:
## randomForest(formula = f, data = data, classwt = classwt, cutoff = cutoff, ntree = 1250)
## Type of random forest: classification
## Number of trees: 1250
## No. of variables tried at each split: 3
##
## OOB estimate of error rate: 0.23%
## Confusion matrix:
## 1 2 3 class.error
## 1 130 1 0 0.007633588
## 2 0 172 0 0.000000000
## 3 0 0 135 0.000000000
看看在测试集上的表现:
> pred.rf <- predict(fit.rf, newdata = test.rf)
> getConfMatrix(pred.rf)
## predicted
## true 1 2 3 -err.-
## 1 56 0 0 0
## 2 0 73 0 0
## 3 0 0 57 0
## -err.- 0 0 0 0
> performance(pred.rf, measures = list(mmce, acc))
## mmce acc
## 0 1
测试集上没有一个错误。
4.2.2 岭回归
> p_load(penalized, RWeka, mda)
> ovr <- makeMulticlassWrapper("classif.penalized", mcw.method = "onevsrest")
>
> # 创建一个包装器用装袋法进行10次(默认值)有放回重抽样,抽取70%的观测和所有输入特征
> bag.ovr <- makeBaggingWrapper(ovr, bw.iters = 10,
+ bw.replace = T, bw.size = 0.7, bw.feats = 1)
>
> # 训练算法
> set.seed(123)
> fit.ovr <- mlr::train(bag.ovr, wine.task)
> pred.ovr <- predict(fit.ovr, newdata = test.rf)
> getConfMatrix(pred.ovr)
## predicted
## true 1 2 3 -err.-
## 1 56 0 0 0
## 2 1 72 0 1
## 3 0 0 57 0
## -err.- 1 0 0 1
错了1个,但正确率并不重要,应该更注重创建分类器、调整超参数和实现重抽样的策略和方法。
5、MLR集成模型
> # 使用pima数据集创建task对象
> pima.task <- makeClassifTask(id = "pima", data = train, target = "type")
>
> # 增加数据观测,先前只有400行
> pima.smote <- smote(pima.task, rate = 2, nn = 3)
> str(getTaskData(pima.smote))
## 'data.frame': 533 obs. of 8 variables:
## $ npreg: num 5 5 0 3 3 2 0 1 12 1 ...
## $ glu : num 86 77 107 83 142 128 137 189 92 86 ...
## $ bp : num 68 82 60 58 80 78 40 60 62 66 ...
## $ skin : num 28 41 25 31 15 37 35 23 7 52 ...
## $ bmi : num 30.2 35.8 26.4 34.3 32.4 43.3 43.1 30.1 27.6 41.3 ...
## $ ped : num 0.364 0.156 0.133 0.336 0.2 ...
## $ age : num 24 35 23 25 63 31 33 59 44 29 ...
## $ type : Factor w/ 2 levels "No","Yes": 1 1 1 1 1 2 2 2 2 1 ...
现在训练集中有533个观测。建立3个基础模型:随机森林、二次判别分析和带有L1惩罚项的GLM:
> base <- c("classif.randomForest", "classif.qda", "classif.glmnet")
# 融合模型是一个简单的GLM模型,它的系数是通过交叉验证调整得来的
> learns <- lapply(base, makeLearner) %>%
+ lapply(setPredictType, "prob")
>
> s1 <- makeStackedLearner(base.learners = learns, super.learner = "classif.logreg",
+ predict.type = "prob", method = "stack.cv")
看看在测试集上的表现:
> fit.s1 <- mlr::train(s1, pima.smote)
> pred.fit <- predict(fit.s1, newdata = test)
> getConfMatrix(pred.fit)
## predicted
## true No Yes -err.-
## No 77 11 11
## Yes 14 30 14
## -err.- 14 11 25
> performance(pred.fit, measures = list(mmce, acc, auc))
## mmce acc auc
## 0.1893939 0.8106061 0.8719008
正确率为81%,AUC与用caretEnsemble包建立的集成模型相比差不多。
总结:
1.融合模型与单独的基础模型相比,确实在性能上有所提高;
2.两种平衡类别的抽样技术:上采样与合成少数类过采样技术。
网友评论