¿Que es CARET?

El paquete caret (classification and regression training, Kuhn (2016)) incluye una serie de funciones que facilitan el uso de decenas de métodos complejos de clasificación y regresión. Utilizar este paquete en lugar de las funciones originales de los métodos presenta dos ventajas:

  • Permite utilizar un código unificado para aplicar reglas de clasificación muy distintas, implementadas en diferentes paquetes.
  • Es más fácil poner en práctica algunos procedimientos usuales en problemas de clasificación. Por ejemplo, hay funciones específicas para dividir la muestra en datos de entrenamiento y datos de test o para ajustar parámetros mediante validación cruzada.

Mucha información y ayuda sobre uso del paquete se puede encontrar en la página web http://topepo.github.io/caret/index.html, que ha sido la fuente de información básica para elaborar esta introducción. Aquí nos centraremos en el uso de caret en problemas de clasificación. Las aplicaciones en problemas de regresión son similares.

Los datos

# carga caret
library(caret)
# carga datos
load(url('http://verso.mat.uam.es/~joser.berrendero/datos/Wisc.RData')) 

División en entrenamiento y test

Para dividir los datos en una muestra de entrenamiento y otra de test se usa el comando createDataPartition. En el código siguiente:

  • p representa la proporción de datos en la muestra de entrenamiento.
  • La partición se lleva a cabo para cada nivel de la variable y que aparece como primer argumento. El resultado es un vector con los índices de las filas seleccionadas para formar parte de la muestra de entrenamiento.
  • El argumento list=FALSE se usa para evitar que el resultado sea una lista.
set.seed(100)  # Para reproducir los mismos resultados
IndicesEntrenamiento <- createDataPartition(y = Wisconsin$tipo,
                                            p = 0.6,
                                            list = FALSE)
Entrenamiento <- Wisconsin[IndicesEntrenamiento,]
Test <- Wisconsin[-IndicesEntrenamiento,]

Especificar el clasificador

El comando más importante de caret es train. Se puede usar este comando único para aplicar un gran número de métodos de clasificación determinando (en caso necesario) los valores óptimos de sus parámetros mediante validación cruzada u otros métodos de remuestreo. Para usar train en general es necesario:

  • Elegir el método de clasificación que queremos usar. El catálogo de todos los métodos disponibles se puede consultar en este enlace. Una información técnica detallada de cada método se puede obtener con el comando getModelInfo.

  • Si el método de clasificación requiere determinar parámetros, es necesario fijar cuáles y en qué rango de valores.

  • También hay que definir el método de remuestreo que se va a utilizar para determinar estos parámetros.

Ejemplo: la regla de Fisher con caret

Veamos cómo aplicar la regla lineal de Fisher, que no requiere el ajuste de ningún parámetro. Posteriormente, aplicaremos la regla de \(k\) vecinos más próximos, que requiere determinar apropiadamente el número \(k\) de vecinos.

Argumentos de train:

  1. La variable que queremos predecir (en clasificación, la variable que contiene la clase) y qué variables vamos a utilizar para hacerlo. Se usa la sintaxis habitual en R para definir un modelo.
  2. El data frame en el que se encuentran los datos.
  3. El método de clasificación que queremos usar. La regla de Fisher corresponde a lda.
  4. Argumentos adicionales de cada método concreto (en el ejemplo, el argumento prior para fijar las mismas probabilidades a priori para las dos clases).
lda.resultado <- train(tipo ~ .,
      data = Wisconsin,
      method = "lda",
      prior = c(0.5, 0.5))

La lista lda.resultado contiene muchos elementos. Entre ellos, finalModel contiene la información básica del modelo ajustado.

lda.resultado$finalModel

A continuación, clasificamos los datos con la regla obtenida y evaluamos los resultados:

# Primero calculamos las predicciones
predicciones <- predict(lda.resultado, Wisconsin)

# Evaluación
confusionMatrix(predicciones, Wisconsin$tipo)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction benigno maligno
##    benigno     345      22
##    maligno      12     190
##                                           
##                Accuracy : 0.9402          
##                  95% CI : (0.9175, 0.9583)
##     No Information Rate : 0.6274          
##     P-Value [Acc > NIR] : <2e-16          
##                                           
##                   Kappa : 0.871           
##  Mcnemar's Test P-Value : 0.1227          
##                                           
##             Sensitivity : 0.9664          
##             Specificity : 0.8962          
##          Pos Pred Value : 0.9401          
##          Neg Pred Value : 0.9406          
##              Prevalence : 0.6274          
##          Detection Rate : 0.6063          
##    Detection Prevalence : 0.6450          
##       Balanced Accuracy : 0.9313          
##                                           
##        'Positive' Class : benigno         
## 

El vector overall contiene diversas medidas de precisión de las cuales la primera es el porcentaje de aciertos.

1-confusionMatrix(predicciones, Wisconsin$tipo)$overall[1]
##   Accuracy 
## 0.05975395

Ejercicios

  1. Calcula la función discriminante de Fisher, pero utilizando únicamente la muestra de entrenamiento. ¿Se parece a la obtenida usando la muestra completa?

  2. Aplica la función discriminante obtenida en el ejercicio anterior a la muestra de test y calcula la matriz de confusión correspondiente y la tasa de error estimada. Compara el resultado con la tasa de error aparente.

Preprocesado

El comando preProcess permite diversas transformaciones de los datos previas a la aplicación de una regla de clasificación. Algunos métodos disponibles son:

  • center, scale: para estandarizar las variables
  • BoxCox: transformaciones para conseguir normalidad.
  • pca: cálculo de componentes principales

Como ejemplo, veamos como reducir la dimensión a dos componentes principales antes de clasificar:

# calcula las dos primeras componentes principales
parametros <- preProcess(Wisconsin, method=c('pca'), pcaComp = 2)
Wisconsin.pca <- predict(parametros, Wisconsin)
head(Wisconsin.pca)
##      tipo        PC1         PC2
## 1 benigno -0.6511564  0.07133842
## 2 benigno -0.3638370 -1.41866440
## 3 benigno -2.3104508 -1.74903196
## 4 benigno -1.8318295  1.05858481
## 5 benigno -3.1093882 -0.97408238
## 6 benigno -1.2671203 -0.40161774

El nuevo data.frame contiene las etiquetas de las clases y las proyecciones de los datos originales en el espacio generado por las dos primeras componentes principales. A continuación representamos gráficamente estas proyecciones:

# representacion grafica
plot(Wisconsin.pca$PC1, Wisconsin.pca$PC2, col=Wisconsin.pca$tipo)

Aplicamos la regla de Fisher a las dos primeras componentes:

lda.pca <- train(tipo ~ .,
      data = Wisconsin.pca,
      method = "lda",
      prior = c(0.5, 0.5))

lda.pca$finalModel$scaling
##           LD1
## PC1 0.6970331
## PC2 0.1834260
predicciones <- predict(lda.pca, Wisconsin.pca)
confusionMatrix(predicciones, Wisconsin.pca$tipo)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction benigno maligno
##    benigno     350      36
##    maligno       7     176
##                                           
##                Accuracy : 0.9244          
##                  95% CI : (0.8996, 0.9448)
##     No Information Rate : 0.6274          
##     P-Value [Acc > NIR] : < 2.2e-16       
##                                           
##                   Kappa : 0.8337          
##  Mcnemar's Test P-Value : 1.955e-05       
##                                           
##             Sensitivity : 0.9804          
##             Specificity : 0.8302          
##          Pos Pred Value : 0.9067          
##          Neg Pred Value : 0.9617          
##              Prevalence : 0.6274          
##          Detection Rate : 0.6151          
##    Detection Prevalence : 0.6784          
##       Balanced Accuracy : 0.9053          
##                                           
##        'Positive' Class : benigno         
## 

A continuación se representa gráficamente la recta obtenida:

plot(Wisconsin.pca$PC1, Wisconsin.pca$PC2, col=Wisconsin.pca$tipo)
abline(recta.fisher(lda.pca$finalModel))

Ajuste de las constantes de los modelos

Para tener un buen comportamiento en distintas situaciones muchas reglas de clasificación incorporan una serie de parámetros que les confieren una mayor flexibilidad. Normalmente se usan métodos de validación cruzada para determinar estos parámetros. Veamos cómo hacerlo en caret usando un ejemplo sencillo: la regla de \(k\) vecinos más próximos, en la que hay que determinar el número \(k\) de vecinos que intervienen para clasificar cada punto.

Primero hay que determinar el conjunto de valores entre los que vamos a seleccionar \(k\) mediante el comando expand.grid. En el ejemplo se han fijado los valores \(1, 3, 5, 7,\ldots, 15\).

En segundo lugar hay que determinar el método que se va a usar para elegir el valor óptimo de \(k\). Para ello se usa el comando trainControl. En el ejemplo, se ha fijado validación cruzada en 10 partes. Se dividen los datos en 10 submuestras del mismo tamaño. Por turno, una de las submuestras se usa como test y las nueve restantes como entrenamiento. Se promedian los errores de los diez turnos y esto se hace para cada valor de \(k\). Se selecciona el valor que da el mejor resultado.

Posteriormente, se usa el comando train de manera similar a como se hizo para calcular la regla de Fisher, pero añadiendo los ajustes anteriores mediante los nuevos parámetros tuneGrid y trControl respectivamente:

# Define el grid de parámetros a probar
valores <- expand.grid(k = seq(1, 15, 2)) 

# Define los detalles del método de validación cruzada o remuestreo
ajustes <- trainControl(method='cv',  # validación cruzada
             number = 10)  # diez submuestras

# Aplica el método seleccionando el valor óptimo de k
set.seed(100)
resultado.knn <- train(tipo ~ .,
      data = Wisconsin,
      method = 'knn',
      tuneGrid = valores,
      trControl = ajustes)

resultado.knn$results$Accuracy
## [1] 0.8648075 0.8806596 0.8859239 0.8894326 0.8858623 0.8858310 0.8911254
## [8] 0.8928798
plot(resultado.knn)

Una vez determinado el número de vecinos óptimo, se obtienen los siguientes resultados de clasificación:

predicciones <- predict(resultado.knn, Wisconsin)
confusionMatrix(predicciones, Wisconsin$tipo)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction benigno maligno
##    benigno     347      50
##    maligno      10     162
##                                           
##                Accuracy : 0.8946          
##                  95% CI : (0.8664, 0.9186)
##     No Information Rate : 0.6274          
##     P-Value [Acc > NIR] : < 2.2e-16       
##                                           
##                   Kappa : 0.7655          
##  Mcnemar's Test P-Value : 4.782e-07       
##                                           
##             Sensitivity : 0.9720          
##             Specificity : 0.7642          
##          Pos Pred Value : 0.8741          
##          Neg Pred Value : 0.9419          
##              Prevalence : 0.6274          
##          Detection Rate : 0.6098          
##    Detection Prevalence : 0.6977          
##       Balanced Accuracy : 0.8681          
##                                           
##        'Positive' Class : benigno         
## 

Cuando se desea prefijar el valor de \(k\), una posibilidad es definir un grid de un solo punto. En el siguiente ejemplo, fijamos \(k=3\):

valores <- expand.grid(k = 3)             # fijamos k=3 
ajustes <- trainControl(method='none')   # no es necesario seleccionar k óptimo
resultados.knn3 <- train(tipo ~ .,
                   data = Wisconsin,
                   method = 'knn',
                   tuneGrid = valores,
                   trControl = ajustes)

predicciones <- predict(resultados.knn3, Wisconsin)
confusionMatrix(predicciones, Wisconsin$tipo)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction benigno maligno
##    benigno     341      24
##    maligno      16     188
##                                           
##                Accuracy : 0.9297          
##                  95% CI : (0.9055, 0.9493)
##     No Information Rate : 0.6274          
##     P-Value [Acc > NIR] : <2e-16          
##                                           
##                   Kappa : 0.8485          
##  Mcnemar's Test P-Value : 0.2684          
##                                           
##             Sensitivity : 0.9552          
##             Specificity : 0.8868          
##          Pos Pred Value : 0.9342          
##          Neg Pred Value : 0.9216          
##              Prevalence : 0.6274          
##          Detection Rate : 0.5993          
##    Detection Prevalence : 0.6415          
##       Balanced Accuracy : 0.9210          
##                                           
##        'Positive' Class : benigno         
## 

Observaciones finales

Para otros métodos se recomienda usar el comando modelLookup para saber qué parámetros pueden optimizarse y cómo se llaman. Algunos ejemplos:

modelLookup('lda')
##   model parameter     label forReg forClass probModel
## 1   lda parameter parameter  FALSE     TRUE      TRUE
modelLookup('knn')
##   model parameter      label forReg forClass probModel
## 1   knn         k #Neighbors   TRUE     TRUE      TRUE
modelLookup('qda')
##   model parameter     label forReg forClass probModel
## 1   qda parameter parameter  FALSE     TRUE      TRUE
modelLookup('rpart')
##   model parameter                label forReg forClass probModel
## 1 rpart        cp Complexity Parameter   TRUE     TRUE      TRUE

En algunos métodos la implementación no permite optimizar algunos parámetros mediante validación cruzada. Sus valores se pueden fijar como parámetros adicionales de train.

Ejercicios

  • Divide la muestra completa en dos submuestras, cada una de ellas con el 50 % de los datos, de forma que una se use para entrenamiento y otra para test en los ejercicios que siguen.

  • Aplica la regla cuadrática (regla Bayes bajo normalidad) a la muestra de entrenamiento y clasifica los datos de la muestra de test. ¿Qué porcentaje de aciertos se obtiene?

  • Construye un árbol de clasificación con la muestra de entrenamiento (método rpart). ¿Hay que determinar algún parámetro usando validacion cruzada? Si la respuesta es afirmativa,
    • fija un conjunto de valores adecuado,
    • define un procedimiento de validación cruzada y
    • determina el valor óptimo del parámetro. Utiliza el árbol construido para clasificar los datos de la muestra de test y determina el porcentaje de ellos que han sido bien clasificados.
  • Considera los datos de los lirios de Fisher contenidos en el fichero iris
    • Calcula la primera componente principal y representa un diagrama de cajas de los valores de esta componente para cada una de las tres especies.
    • clasifica los datos utilizando únicamente esta primera componente principal mediante el método de \(k\) vecinos más próximos con \(k=3\). ¿Cuál es el porcentaje de acierto de clasificación?
    • Repite el ejercicio anterior usando a) dos componentes principales y b) los datos originales. Compara los porcentajes de acierto de clasificación.