50-R語言機器學習:集成模型與多類分類

《精通機器學習:基于R 第二版》學習筆記

1、集成模型簡介

集成學習的定義是:“有策略地建立多個模型(如分類器或?qū)<蚁到y(tǒng))并將其組合在一起,解決特定計算智能問題的過程?!痹陔S機森林和梯度提升模型中,我們將幾百或幾千棵樹的“投票”結(jié)果組合起來進行預測。于是,根據(jù)集成學習的定義,這些模型就是集成學習模型。
在機器學習中,這種方法的優(yōu)點是可以將幾種性能平平甚至很差的學習器的預測結(jié)果結(jié)合起來,從而提高整體準確率。

2、數(shù)據(jù)準備

> # 使用皮瑪印第安糖尿病數(shù)據(jù)集
> 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。在很多數(shù)據(jù)集中,我們感興趣的結(jié)果是一種罕見的事件。于是你就會得到這樣一種分類器,它的正確率非常高,但預測我們感興趣的結(jié)果時效果非常差。也就是說,根本預測不出真陽性的結(jié)果。為了平衡響應變量,可以對少數(shù)類進行上采樣,對多數(shù)類進行下采樣,建立“人工合成”的數(shù)據(jù)。
在上采樣過程中,對于交叉驗證使用的每折數(shù)據(jù),少數(shù)類都使用有放回的隨機抽樣,以使其數(shù)量與多數(shù)類的觀測數(shù)量相匹配。

> control <- trainControl(method = "cv",
+                         # 5折交叉驗證
+                         number = 5,
+                         # 保存最后預測概率
+                         savePredictions = "final",
+                         classProbs = T,
+                         # 按照索引再抽樣,使基礎模型使用同樣的數(shù)據(jù)折
+                         index = createResample(train$type,5),
+                         # 上采樣
+                         sampling = "up",
+                         summaryFunction = twoClassSummary)

訓練模型,可以使用任意caret包支持的模型。我們訓練3種:分類樹模型:"rpart"、多元自適應回歸樣條模型:"earth"和K最近鄰模型:"knn"。

> set.seed(123)
> # caretList() 函數(shù)不但能夠建立模型,還可以按照caret包的規(guī)則調(diào)整每種模型的超參數(shù)
> # 使用caretModelSpec()函數(shù)可以為每種模型創(chuàng)建各自的調(diào)優(yōu)參數(shù)網(wǎng)格
> 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"

基礎模型能夠有效組合的要求是,它們不高度相關。模型預測結(jié)果是否相關沒有嚴格的規(guī)則,我們應該用結(jié)果進行實驗,查看結(jié)果:

> 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高度相關。我們先忽略它,繼續(xù)建立第四個分類器——融合模型——并檢查結(jié)果:

> model.preds <- lapply(models, predict, newdata = test, type = "prob") %>% 
+     lapply(function(x) x[,  "Yes"]) %>% as.data.frame()

使用 caretStack() 函數(shù)將這些模型融合在一起,進行最終預測。

> 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模型高度相關,它們的系數(shù)也依然是顯著的,所以應該可以在分析中保留這兩個模型。下面比較單獨模型的結(jié)果和集成模型的結(jié)果:

> 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() 函數(shù)可以看到單獨模型的AUC和集成模型的AUC,通過模型融合建立的集成模型確實可以提高預測能力。

4、多類分類

4.1 數(shù)據(jù)理解與數(shù)據(jù)準備

> p_load(mlr, ggplot2, HDclassif, DMwR, reshape2, corrplot)
> data(wine)
> table(wine$class)
## 
##  1  2  3 
## 59 71 48

響應變量是數(shù)值型的標簽(1、2和3)。
使用合成少數(shù)類過采樣技術,使數(shù)據(jù)量倍增:

> # 將類別轉(zhuǎn)換為因子變量,否則函數(shù)不起作用
> wine$class <- as.factor(wine$class)
> 
> set.seed(123)
> 
> # SMOTE() 函數(shù)中,默認的最近鄰數(shù)量為5
> # 如果想創(chuàng)建數(shù)量是現(xiàn)有數(shù)量2倍的少數(shù)類,就設定 'percent.over = 100'
> df <- DMwR::SMOTE(class ~ ., wine, perc.over = 300, perc.under = 300)
> table(df$class)
## 
##   1   2   3 
## 187 245 192

現(xiàn)在數(shù)據(jù)集的觀測變?yōu)榱?24個。然后對各特征進行可視化:

> # 先標準化
> 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()
數(shù)據(jù)分布箱線圖

離群點處理:對于其中的高異常值(高于第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)
+ }

創(chuàng)建新數(shù)據(jù)框:

> 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包要求將訓練集數(shù)據(jù)保存在task數(shù)據(jù)結(jié)構中,該數(shù)據(jù)結(jié)構專門為分類任務而設計
> wine.task <- makeClassifTask(id = "wine", data = train.rf, target = "class")

4.2.1 隨機森林

> # 創(chuàng)建一個重抽樣對象來為隨機森林模型調(diào)整樹的數(shù)量,它包括3個子樣本
> rdesc <- makeResampleDesc("Subsample", iters = 3)
> 
> # 建立一個樹的網(wǎng)格來調(diào)整樹的數(shù)量,最小值為750,最大值為2000
> # 也可以使用caret包建立多個參數(shù)列表
> param <- makeParamSet(makeDiscreteParam("ntree", 
+     values = c(750, 1000, 1250, 1500, 1750, 2000)))
> 
> # 創(chuàng)建控制對象,建立數(shù)值網(wǎng)格 這樣即可調(diào)整超參數(shù),從而找出最優(yōu)樹數(shù)量
> ctrl <- makeTuneControlGrid()
> 
> # 調(diào)出最優(yōu)樹數(shù)量和相應的樣本外誤差
> 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

最優(yōu)的樹數(shù)量是1250,相應的平均誤分類率是0,一個完美的分類。
使用超參數(shù)作為makelearner函數(shù)的訓練包裝器

> 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

看看在測試集上的表現(xiàn):

> 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")
> 
> # 創(chuàng)建一個包裝器用裝袋法進行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個,但正確率并不重要,應該更注重創(chuàng)建分類器、調(diào)整超參數(shù)和實現(xiàn)重抽樣的策略和方法。

5、MLR集成模型

> # 使用pima數(shù)據(jù)集創(chuàng)建task對象
> pima.task <- makeClassifTask(id = "pima", data = train, target = "type")
> 
> # 增加數(shù)據(jù)觀測,先前只有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 ...

現(xiàn)在訓練集中有533個觀測。建立3個基礎模型:隨機森林、二次判別分析和帶有L1懲罰項的GLM:

> base <- c("classif.randomForest", "classif.qda", "classif.glmnet")
# 融合模型是一個簡單的GLM模型,它的系數(shù)是通過交叉驗證調(diào)整得來的
> learns <- lapply(base, makeLearner) %>% 
+     lapply(setPredictType, "prob")
> 
> s1 <- makeStackedLearner(base.learners = learns, super.learner = "classif.logreg", 
+     predict.type = "prob", method = "stack.cv")

看看在測試集上的表現(xiàn):

> 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包建立的集成模型相比差不多。

總結(jié):
1.融合模型與單獨的基礎模型相比,確實在性能上有所提高;
2.兩種平衡類別的抽樣技術:上采樣與合成少數(shù)類過采樣技術。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容