Machine Learning Ensembles III
En este artículo vamos a crear un modelo apilado (stack): igual que en el anterior, crearemos algunos modelos de primer nivel; pero luego seleccionaremos uno de segundo nivel, que tomará las predicciones de los de primer nivel como entradas para realizar su propia predicción.
Como primer paso, cargamos y preparamos los datos:
library(C50)
library(modeldata)
data(mlc_churn)
churn <- mlc_churn
# Variables target y predictoras (features)
# Variables target y predictoras (features)
target <- "churn"
predictoras <- names(churn)[names(churn) != target]
# Convertimos factors a integer para que no nos de problemas con svm ni xgbm
for (v in predictoras) {
if (is.factor(churn[, v])) {
newName <- paste0("F_", v)
names(churn)[which(names(churn) == v)] <- newName
churn[, v] <- unclass(churn[, newName])
}
}
set.seed(127)
train_idx <- sample(nrow(churn), 0.9*nrow(churn))
churnTrain <- churn[train_idx,]
churnTest <- churn[-train_idx,]
rm(churn)
library(caret)
set.seed(123)
Vamos a seleccionar ahora un modelo de segundo nivel un poco más complejo entre varios posibles: una red neuronal, un random forest, un extreme gradient boosting y un support vector machine.
Ahora puedo utilizar caret
para construir tanto los modelos de primer nivel como los de segundo. Por tanto, puedo validarlos mediante la técnica cross validation que puedo utilizar a partir del train set, sin necesidad de utiliizar un validation set aparte para ello.
Construyamos los modelos de primer nivel:
churn_train <- churnTrain
churn_test <- churnTest
trControl <- trainControl(
# 5-fold Cross Validation
method = "cv",
number = 5,
# Save the predictions for the optimal tuning
# parameters
savePredictions = 'final',
# Class probabilities will be computed along with
# predicted values in each resample
classProbs = TRUE
)
f <- as.formula(paste0(target, "~ ."))
model_rf <- train(f, churn_train[ , c(predictoras, target)],
method = "rf",
trControl = trControl,
tuneLength = 3)
model_svm <- train(f, churn_train[ , c(predictoras, target)],
method = "svmRadial",
trControl = trControl,
tuneLength = 3)
model_xgbm <- train(f, churn_train[ , c(predictoras, target)],
method = "xgbTree",
trControl = trControl,
tuneLength = 3)
Ahora compararíamos la performance y la correlación de los modelos de primer nivel como hemos hecho antes, por eso no vamos a repetirlo.
Pasamos, pues, directamente, a construir los modelos de segundo nivel, en este caso a partir del train_set:
# Utilizamos los modelos de 1er nivel para predecir las probabilidades
# Out-Of-Fold del training set
churn_train$OOF_pred_rf <-
model_rf$pred$yes[order(model_rf$pred$rowIndex)]
churn_train$OOF_pred_svm <-
model_svm$pred$yes[order(model_svm$pred$rowIndex)]
churn_train$OOF_pred_xgbm <-
model_xgbm$pred$yes[order(model_xgbm$pred$rowIndex)]
Hay que hacer notar que siempre debemos emplear en esta fase las predicciones out of bag o out of fold; de otra manera la importancia de los modelos de primer nivel sería tan solo función de lo bien que cada modelo de primer nivel es capaz de “recordar” los datos de entrenamiento.
Ahora ya podemos entrenar los modelos de segundo nivel:
# Predictoras de los modelos de primer nivel para el segundo nivel
predictoras2N <- c('OOF_pred_rf','OOF_pred_svm','OOF_pred_xgbm')
trControl <- trainControl(
# 5-fold Cross Validation
method = "cv",
number = 5,
# Class probabilities will be computed along with
# predicted values in each resample
classProbs = TRUE
)
model_2nn <- train(f, churn_train[ , c(predictoras2N, target)],
method = "nnet",
# Neural nets like scaled and normalized inputs
preProcess = c("center", "scale"),
trace = FALSE,
trControl = trControl,
tuneLength = 3)
model_2rf <- train(f, churn_train[ , c(predictoras2N, target)],
method = "rf",
trControl = trControl,
tuneLength = 3)
## note: only 2 unique complexity parameters in default grid. Truncating the grid to 2 .
model_2svm <- train(f, churn_train[ , c(predictoras2N, target)],
method = "svmRadial",
trControl = trControl,
tuneLength = 3)
model_2xgbm <- train(f, churn_train[ , c(predictoras2N, target)],
method = "xgbTree",
trControl = trControl,
tuneLength = 3)
Y compararlos:
resamps <- resamples(list(nnet = model_2nn, rf = model_2rf,
svm = model_2svm, xgbm = model_2xgbm))
summary(resamps)
##
## Call:
## summary.resamples(object = resamps)
##
## Models: nnet, rf, svm, xgbm
## Number of resamples: 5
##
## Accuracy
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## nnet 0.9566667 0.9588889 0.9611111 0.9600000 0.9611111 0.9622222 0
## rf 0.9533333 0.9588889 0.9588889 0.9584444 0.9588889 0.9622222 0
## svm 0.9300000 0.9444444 0.9455556 0.9426667 0.9466667 0.9466667 0
## xgbm 0.9522222 0.9588889 0.9588889 0.9613333 0.9666667 0.9700000 0
##
## Kappa
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## nnet 0.8051083 0.8227781 0.8263506 0.8241705 0.8331061 0.8335095 0
## rf 0.7923647 0.8137584 0.8190217 0.8152334 0.8215434 0.8294790 0
## svm 0.7177700 0.7767303 0.7799847 0.7686728 0.7804878 0.7883909 0
## xgbm 0.7851194 0.8123944 0.8137584 0.8267822 0.8537774 0.8688613 0
bwplot(resamps)
diffs <- diff(resamps)
summary(diffs)
##
## Call:
## summary.diff.resamples(object = diffs)
##
## p-value adjustment: bonferroni
## Upper diagonal: estimates of the difference
## Lower diagonal: p-value for H0: difference = 0
##
## Accuracy
## nnet rf svm xgbm
## nnet 0.001556 0.017333 -0.001333
## rf 1.00000 0.015778 -0.002889
## svm 0.02588 0.07628 -0.018667
## xgbm 1.00000 1.00000 0.04815
##
## Kappa
## nnet rf svm xgbm
## nnet 0.008937 0.055498 -0.002612
## rf 1.00000 0.046561 -0.011549
## svm 0.08275 0.20369 -0.058109
## xgbm 1.00000 1.00000 0.15473
xgbm
y random forest son virtualmente indistinguibles.
Como xgbm
es ligeramente superior en accuracy y en Kappa a nnet
, nos quedaremos con xgbm
como modelo final.
Solo nos queda ya comprobar el resultado con el test set:
churn_test$OOF_pred_rf <- predict(model_rf, churn_test[, predictoras],
type = "prob")$yes
churn_test$OOF_pred_svm <- predict(model_svm, churn_test[, predictoras],
type = "prob")$yes
churn_test$OOF_pred_xgbm <- predict(model_xgbm, churn_test[, predictoras],
type = "prob")$yes
churn_test$pred_nn <- predict(model_2xgbm, churn_test[, predictoras2N])
confusionMatrix(churn_test$churn, churn_test$pred_nn)
## Confusion Matrix and Statistics
##
## Reference
## Prediction yes no
## yes 64 18
## no 1 417
##
## Accuracy : 0.962
## 95% CI : (0.9413, 0.977)
## No Information Rate : 0.87
## P-Value [Acc > NIR] : 1.773e-12
##
## Kappa : 0.8488
##
## Mcnemar's Test P-Value : 0.0002419
##
## Sensitivity : 0.9846
## Specificity : 0.9586
## Pos Pred Value : 0.7805
## Neg Pred Value : 0.9976
## Prevalence : 0.1300
## Detection Rate : 0.1280
## Detection Prevalence : 0.1640
## Balanced Accuracy : 0.9716
##
## 'Positive' Class : yes
##
Como veis, unos resultados excelentes, mejores incluso que los de los modelos por separado con el train set.
Por último, deciros que tenéis el código en github.