Capítulo 10 Aprendizaje Supervisado

Para entender el aprendizaje supervisado de forma intuitiva usaremos un ejemplo cotidiano. Todos hemos ido al doctor y alguna vez le hemos dicho que nos duele la garganta, que hemos tenido dolor de cabeza y fiebre. Éste nos hará unas cuantas preguntas más y luego nos dirá qué enfermedad podríamos tener y qué tratamiento seguir.

Intuitivamente sabemos que el doctor tuvo que entrenar inicialmente a partir de clases y libros donde muestran casos pasados, y estudiar qué síntomas son señal de cada enfermedad. Luego, empezó a testear lo aprendido en un grupo de pacientes durante su internado. Finalmente, cuando ya estaba entrenado tuvo licencia para poder aplicar este aprendizaje a pacientes en su consultorio u hospital.

Este es un ejemplo de aprendizaje supervisado porque el entrenamiento se realizó a partir de datos conocidos o inputs los cuales están etiquetados (duele la garganta, dolor de cabeza, fiebre) con la finalidad de obtener un resultado o output que también era conocido y etiquetado (¿tiene gripe o no?). Cuando un doctor testea lo aprendido se sabe los inputs de pacientes y también el output que es dado por un doctor con más experiencia que puede decir qué tan efectivo es su entrenamiento. Cuando el doctor sale a atender pacientes solo tendrá inputs etiquetados con la finalidad de predecir un output etiquetado.

Ésta es la lógica que se ha llevado a algoritmos computacionales. Lo podemos ver en facebook, quienes recopilan una serie de inputs, como nuestros likes, compartidos, etc, para predecir qué podríamos querer consumir y nos lo muestra a modo de recomendación. Y también lo veremos en nuestro ambiente de trabajo cuando tengamos inputs de nuestros clientes, como consumo, nivel adquisitivo, lugar donde vive, etc, para predecir cuál de nuestros productos predecimos que es más propenso a comprar y así llamarlo para ofrecerle ese producto.

10.1 Clasificacion y Regresión

Existen múltiples algoritmos de aprendizaje supervisado, pero los diferenciaremos en dos de acuerdo al tipo de variable que manejemos.

Cuando la variable sea discreta los llamaremos clasificación. Los ejemplos líneas arriba son muestra de ello. Hemos clasificado en dos clases (gripe o no) o varias clases (producto “x” a recomendar)

Cuando la variable sea continua los llamaremos regresión. La predicción de los precios de una casa dadas las características de la casa como tamaño, precio, etc. es uno de los ejemplos comunes de regresión.

En las siguientes secciones aprenderemos algunos algoritmos señalando si son de clasificación de regresión.

10.2 kNN: k vecinos más próximos

Comencemos por un simple, pero muy útil algoritmo de clasificación, el algoritmo de los k vecinos más próximos (kNN por sus siglas en inglés).

10.2.1 Dos variables como input

Comencemos por entenderlo visualmente. Imaginemos que tenemos dos variable como input y como output nos da si es Clase Roja o Clase Azul. Esta data es nuestra data de entrenamiento.

Ahora que ya tenemos nuestra data de entrenamiento empezaremos a usar la data de test. Como queremos predecir la clase, el output, veremos cómo uno de estos datos se vería visualmente y lo pintaremos de color amarillo. A continuación calculamos la distancia entre este punto y los demás datos.

Hemos trazado solo algunas distancias, pero podríamos hacerlo con todos. Para este ejemplo tomaremos los k = 3 vecinos más cercanos.

Notamos que si nos enfocamos solo en los 3 vecinos más cercanos hay más rojos que azules, entonces nuestra predicción será que este punto ha de ser clase R (rojo).

Calcular la distancia en un plano cartesiano es relativamente sencillo, solo tenemos variables como input: en el eje x y eje y. Sin embargo, la misma lógica se puede llevar a más variables.

10.2.2 Múltiples variables como input

Veamos cómo sería con 4 variables como input. Vamos a trabajar nuevamente con el data frame iris, el cual, como recordaremos, tiene 4 atributos de una planta y la última columna es la especie a la que pertenece.

La idea es la siguiente, tomaremos una data de entrenamiento, 50 datos. De esta data tenemos los 4 atributos de input y la última columna es el output, la especie. Utilizaremos el algoritmo kNN tomando como input esta data de entrenamiento para crear nuestro modelo. Luego, con una data de testing, otros 50 datos, probaremos nuestro modelo.

Comencemos tomemos una muestra aleatoria de 100 registros y separaremos la mitad para entrenamiento y la mitad para testear. Dado que tenemos 150 datos en nuestro data frame, tomemos una muestra de los índices. En este caso vamos a utilizar la función set.seed(n) para forzar a que los valores de la muestra aleatoria sean los mismos siempre. Así, poder todos obtener los mismos resultados y la explicación del libro en estos capítulos sea coherente con los resultados que cada lector obtenga. Para un ejercicio real no deberíamos de incluir esa línea. Se recomienda leer la documentación ?set.seed().

Ahora que tenemos los índices podemos construir nuestra data de entrenamiento y nuestro test.

Si bien podríamos construir los algoritmos para calcular las distancias mínimas para cada punto, R nos provee de librerías que nos facilitan la creación de estos modelos. Para ello, cargaremos la librería class, la cual nos permitirá ejecutar kNN rápidamente.

Esta libreriá nos provee la función knn(), la cual tomará los datos de entrenamiento para crear el modelo y una vez creado el modelo tomará los datos de test para predecir el output para nuestra data de test.

Asi, la función knn nos arroja la predicción solo con ingresarle como atributos la data de entrenamiento, los inputs del test y cuántos vecinos cercanos buscará (k). Y no solo eso, podemos comparar nuestra predicción con los output del test para ver qué tan exacto (accuracy en inglés) es nuestro modelo. Para ello calculamos el porcentaje de aciertos de la predicción respecto al output del test.

Además, podemos colocar un resumen en una tabla, también conocida como matriz de confusión, para ver cuántos valores predichos fueron iguales a los reales utilizando la función table().

Intepretemos celda a celda este resultado:

  1. Nuestro modelo kNN predijo 14 valores como especie “setosa” y resulta que en nuestro test el valor real, output era también setosa.
  2. Nuestro modelo predijo 20 como especie versicolor. Sin embargo, en la data real-test, de esos 20, solo 18 son versicolor y 2 son virginica.
  3. Nuestro modelo predijo 16 como especie virginica. Sin embargo, en la data real-test, de esos 16, solo 15 son virginica.

10.2.3 Diversos valores de k

Hasta el momento solo hemos utilizado un solo valor para k, 3 vecinos más cercanos. Sin embargo, podríamos ver la exactitud o accuracy para diferentes valores de k. Como tenemos 50 valores en nuestra data de entrenamiento veremos los aciertos tomando máximo 50 vecinos más cercanos.

Como vemos, para este caso, a partir de cierto número de vecinos más cercanos el nivel de aciertos de nuestro algoritmo empieza a reducirse. Dependerá de cada caso el escoger el mejor “k” para nuestro modelo.

Ya hemos construido así nuestro primer modelo de machine learning.

10.3 Paquete caret

Ahora que ya hemos creado nuestro primer modelo de machine learning nos hemos visto con muchas líneas de código. Por ejemplo, para partir la muestra en entrenamiento y test, para calcular el “k” óptimo, etc. Para hacer el trabajo más sencillo usaremos la librería caret. Caret14 es un potente paquete que proporciona soporte para más de 230 algoritmos de machine learning. También proporciona excelentes funciones para muestrear los datos (para entrenamiento y test), preprocesamiento, creación del modelo, evaluación del modelo, etc.

Vamos a hacer otro ejemplo con k vecinos más cercanos, pero ésta vez utilizando las funciones de la librería Caret. La data de este ejemplo la obtendremos de la librería ISLR, la cual contiene los porcentajes diarios de rendimiento para el índice bursátil S&P 500 entre el 2001 y 2005. Este data frame tiene 8 columnas que usaremos de input y la última columna que tiene dos clases (si sube o baja el índice) que usaremos como output (Ver ?Smarket).

10.3.1 Creación de data de entrenamiento y test

Del total de nuestro data frame partiremos una parte de la data para entrenamiento y la otra para hacer los test. Para ello, caret nos ofrece la función createDataPartition(y = outcomes, p = proporcion, list = FALSE) la cual nos permitirá partir una proporción para entrenamiento y dejar el resto para testing. Vamos a destinar el 75% de los datos para entrenamiento y el atributo list lo dejamos en falso porque no queremos un objeto lista. El resultado de esta función es una serie de índices aleatorios, los cuales usaremos para construir nuestra data de entrenamiento y test.

Esta función nos hace mucho más sencillo el muestro de datos y ya podríamos empezar a crear nuestro modelo.

10.3.2 Entrenando nuestro algoritmo de predicción

Para crear nuestro modelo de predicción y entrenarlo usaremos la función train() y especificaremos el atributo tuneLength = 20 para indicar que queremos que nos evalúe 20 diferentes valores para “k”. A diferencia de la función utilizada cuando construimos nuestro anterior modelo kNN, con caret no tenemos que separar los inputs de los outputs, sino lo ingresamos como un parámetro más de la función train().

Vemos la exactitud (accuracy) para cada valor de “k” y el indicador Kappa. En el caso de Kappa no lo usaremos por el momento ya que es usado para problemas que tienen un desequilibrio en las clases (por ejemplo, división de 70-30 para los outcomes 0 y 1 y puede lograr una exactitud del 70% al predecir que todas las instancias son para el outcome 0).

Así mismo, en la última línea tenemos el valor de “k” óptimo para este modelo. Además, nos encontramos con un nuevo tipo de objeto, el objeto train, el cual está listo para ingresarlo directamente a un gráfico usando la función plot().

10.3.3 Ajuste de parámetros

Si bien ya podríamos probar nuestro modelo en la data de test nos vamos a detener a ajustar parámetros del modelo. Una de las partes más importantes del entrenamiento de modelos de Machine learning es ajustar los parámetros. Para ello usaremos la función trainControl para especificar una serie de parámetros en el modelo. El objeto que sale de trainControl se proporcionará como argumento para la función train que usaremos para entrenar.

Usaremos el método cv (Validación Cruzada) para indicar que vamos a partir nuestra data de entrenamiento en 5 partes iguales de forma aleatoria (5 fold en inglés). Luego, cada una de estas partes las vamos a utilizar como test para el modelo que creemos por las otras 4 partes. Es decir, usaremos parte de nuestra data de entrenamiento para testear nuestro modelo. Esto nos permitirá tomar el error promedio de los 5 test y obtener un dato más certero. Anteriormente no habíamos configurado este parámetro y dejado al valor por default. Por default el método es bootstrap el cual utiliza un método de muestreo con reemplazo a diferencia del cv que el muestro lo hace sin reemplazo.

Ahora, entrenemos nuestro modelo con los parámetros de control:

10.3.4 Pre-procesando datos

Así mismo, un paso importante es pre-procesar la data, dado que, por ejemplo, los datos a diferentes escalas pueden alterar sustancialmente un algoritmo de aprendizaje. El método de escala (división por la desviación estándar) y el centrado (sustracción de la media) sons dos métodos muy frecuentes para poder pre-procesar los datos y obtener una mejor exactitud (ver ?preProcess).

Vemos la mejora sustancial ahora que hemos ajustado algunos parámetros y hemos hecho que primero se reprocese. Tener en cuenta que cada vez que ajustamos parámetros el valor de “k” puede cambiar hasta encontrar al más óptimo. En este caso cambió a k = 29. Esto no quiere decir que a menor “k” el algoritmo es mejor, solo que es el más óptimo para este caso en particular con estos ajustes realizados.

10.3.5 Testeo del modelo de predicción

Ya tenemos nuestro modelo entrenado y listo para testearlo. Caret nos facilita realizar la predicción utilizando la función predict(modelo, newdata = data_de_test)

Si queremos ir aun más allá, para comprender mejor cómo operan estos algoritmos de machine learning, podemos obtener el resultado de la predicción antes de indicar qué clase le asigna el modelo. Para ello utilizamos la misma función predict() pero esta vez modificamos el atributo type. Por default type = "raw" mostrándonos la clase predicha, pero el resultado real es un conjunto de probabilidades. El modelo calcula internamente qué tan probable es que sea de una u otra clase (Baja o Sube).

Mostremos las probabilidades en vez de la clase.

Como vemos, para cada valor del test el modelo nos calcula la probabilidad que estima de que Baje y la probabilidad de Suba el índice S&P. El algoritmo por default nos da la clase que tiene más de 50% de probabilidad.

Ya tenemos el objeto SP_knnPrediccion con los valores predichos. Si queremos calcular la exactitud del modelo (accuracy) utilizaremos la función confusionMatrix(data_prediccion, output_real) la cual no solo nos reporta la matriz de confusión, sino también una serie de mediciones de la presición de nuestro modelo.

Obtenemos casi 91.99% de exactitud (accuracy), así como un intervalo de confianza del 95% de esta exactitud.

10.4 Matriz de confusión

Ya hemos utilizado matrices de confusión en nuestros dos ejemplos anteriores. Ahora nos toca entender propiamente su definición así como algunas de las métricas de evaluación de esta matriz.

Una matriz de confusión, también conocida como matriz de error, nos permite la visualización del rendimiento de un algoritmo, generalmente uno de aprendizaje supervisado (en el aprendizaje no supervisado generalmente se denomina matriz coincidente). Cada fila de la matriz representa las instancias en una clase predicha, mientras que cada columna representa las instancias en una clase real (o viceversa). El nombre se deriva del hecho de que hace que sea fácil ver si el sistema confunde dos clases (es decir, comúnmente etiquetar incorrectamente una como otra).

Las clasificaciones binarias, cuando el outcome puede tomar solo dos clases, nos arrojan esta siguiente matriz de confusión.

10.4.1 exactitud

Ya hemos venido utilizando este término en nuestros ejemplos. La exactitud del modelo (accuracy en inglés), la podemos calcular a partir de la matriz de confusión:

\(Accuracy=\frac{VP+VN}{VP+VN+FP+FN}\)

El accuracy del modelo es la proporción de veces que el algoritmo predijo correctamente, respecto al total de datos evaluados.

10.4.2 Sensitividad

La sensibilidad (también llamada tasa positiva verdadera, el recuerdo o la probabilidad de detección en algunos campos) mide la proporción de positivos reales que se identifican correctamente como tales (por ejemplo, el porcentaje de personas enfermas que se identifican correctamente como que tienen condición).

\(Sensitivity=\frac{VP}{VP+FN}\)

10.4.3 Especificidad

La especificidad (también llamada tasa negativa verdadera) mide la proporción de negativos reales que se identifican correctamente como tales (por ejemplo, el porcentaje de personas sanas que se identifican correctamente como que no tienen la afección).

\(Specificity=\frac{VN}{VN+FP}\)

10.5 Ejercicios

  1. Utilizando la librería caret, particiona el data frame iris de tal forma de tener 70% de datos de entrenamiento y 30% de datos de prueba.

Solución

  1. Utilizando la librería caret y los datos de entrenamiento obtenidos en el ejercicio anterior, crea un modelo de k-vecino más cercano. Plotea el resultado del objeto de entrenamiento.

Solución

  1. Utiliza el modelo creado en el ejercicio anterior para predecir los outputs del objeto test. Reporta la matriz de confusión.

Solución

10.6 Regresión lineal simple

Ahora nos toca predecir sobre variables continuas, los algoritmos de supervisión para estos casos son llamados regresión.

Para entender regresión lineal vamos a iniciar con un ejemplo con una sola variable como input, a ello se le conoce como Regresión lineal simple. Para ello vamos a utilizar data de la librería HistData donde encontraremos un conjunto de datos que enumeran las observaciones individuales de 934 niños en 205 familias almacenadas en el objeto GaltonFamilies.

Visualmente podríamos ver si hay una relación entre las alturas de papá e hijo:

Como vemos, hay una correlación positiva, de tal forma que mientras más alto el padre el hijo llega a tener una mayor altura de adulto. Esta línea, sin embargo, no es más que una línea por default. El reto está en encontrar cuál es le línea que minimiza la distancia de los puntos a esta línea, conocido como minimización del error.

Podríamos intentar predecir la estatura que tendrá el hijo a partir de la estatura del padre usando la ecuación de esta recta:

\(Y = \beta_0+\beta_1X\)

Donde \(X\) es una variable independiente, explicativa, en este caso la estatura del papá. \(\beta_1\) es un parámetro que mide la influencia que la variable explicativa tiene sobre la variable dependiente \(Y\) y \(\beta_0\) es la intersección o término constante. En nuestro caso, la estatura del hijo.

En estadística, la regresión lineal o ajuste lineal es un modelo matemático usado para aproximar la relación de dependencia entre una variable dependiente \(Y\) y las variables independientes \(X_i\).

Así, nuestro problema se reduce a entrenar a nuestro modelo para que encuentre los valores de a intersección, \(\beta_0\), y el valor del parámetro que acompaña a \(X_1\), \(\beta_1\), para luego utilizar estos datos como predicción en nuestra data de test.

Ahora que ya tenemos nuestra data podemos entrenar nuestro modelo.

Vemos como resultados principales el RMSE, que son las siglas en inglés de error cuadrático medio, y es el valor que la regresión lineal buscar minimizar. Además, tenemos el Rsquared, R cuadrado o \(R^2\), que es el coeficiente de determinación el cual determina la calidad del modelo para replicar los resultados. Mientras más alto y cercano a 1 es mejor la calidad del modelo.

Finalmente, dado que ya tenemos el modelo entrenado podemos predecir los valores, que no sería más que evaluar la ecuación con los valores de la data de test. Además, como mencionamos, en regresión buscamos reducir el error cuadrático medio, RMSE. Entonces, calcularemos el RMSE para la data predicha vs la data de test.

Si deseamos también podemos reportar los coeficientes de la ecuación y visualizarlos:

\(Y = \beta_0+\beta_1X\)

10.7 Regresión lineal múltiple

Ahora que ya conocemos la regresión lineal podemos ejecutar un modelo de regresión lineal múltiple, el cual involucra a más de 1 variable como input. Para ello, utilizaremos el dataset diamonds que contiene los precios y otros atributos de casi 54,000 diamantes.

Partimos la data en dos tomando 70% de data para entrenamiento:

Creamos ahora nuestro modelo de regresión lineal múltiple y reportamos tanto los resultados del error como los coeficientes de la ecuación lineal.

Vemos que nos da el RMSE y un R cuadrado bastante cercano a 1, el cual nos denota una alta calidad del modelo para replicar los resultados.

Utilicemos nuestro modelo para predecir los precios de la data de test.

Así, hemos aprendido a realizar un modelo más de machine learning: el de regresión lineal, tanto simple como múltiple.

10.8 Método estándar para evaluar exactitud

Ahora que ya conocemos cómo construir modelos aplicaremos métricas que nos permiten una mejor exactitud en los modelos de clasificación para dos clases.

Para esto recordemos los resultados del modelo que creamos usando el algoritmo de k-vecinos más próximos para predecir si sube o baja el índice S&P.

En la penúltima línea se puede leer que la exactitud (accuracy) fue usada para seleccionar el modelo más óptimo usando el valor más grande. Sin embargo, esta no es la única forma de determinar cuál es el modelo más óptimo.

Recordemos cómo se calcula por default la exactitud (accuracy), hemos utilizado la regla simple de que si la probabilidad de que sea de una determinada clase es más de 50% entonces se le asigne esa clase y luego calculamos la proporción de aciertos entre el total de casos.

Sin embargo, no tiene que ser 50%, podríamos ser más exigentes e indicar que si la probabilidad es mayor del 60% u 80% entonces se le asigna una determinada clase. Vemos que hay diferentes probabilidades y ello nos daría diferentes accuracy.

Es así como surge el indicador del área bajo la curva de Característica Operativa del Receptor, ROC por sus siglas en inglés (Fawcett 2005). Este indicador nos mide qué tan bien puede distinguir un modelo entre dos clases y es considerado el método estándar para evaluar la exactitud de los modelos de distribución predictiva (Jorge M. Lobo 2007) y calcula las exactitudes no solo para cuando discriminamos a partir del 50%, sino para más valores de probabilidad.

Para utilizar esta métrica modificaremos nuestros parámetros de control agregando tres atributos que permitirán calcular el ROC.

Con estos parámetros modificados procederemos a volver a entrenar a nuestro modelo.

Vemos que ahora ROC fue utilizado para seleccionar el modelo más óptimo. Mientras más cercano el valor de ROC sea a 1 mejor será nuestro modelo. Con este modelo podemos predecir los valores a partir de la data de test.

Vemos cómo nuestra exactitud (accuracy) ha incrementado de 91.99% a 93.27%. Ésta métrica es muy recomendada para mejorar la exactitud de nuestro modelo, además que nos permite más fácilmente utilizarlo como comparador entre diferentes modelos que podamos crear.

10.9 Selección de modelo más óptimo

Hemos aprendido cómo crear algunos modelos de machine learning. Como nos debemos de haber percatado, con caret seguimos el mismo patrón para la partición, entrenamiento y predicción. La variación está en el cómo pre-procesar los datos y el ajuste de parámetros. Podríamos así crear múltiples modelos, pero finalmente tenemos que elegir uno el cual nos servirá para hacer nuestras predicciones.

En esta sección, vamos a comparar diferentes modelos predictivos aceptando sus valores por default y elegiremos el mejor usando las herramientas presentadas en las secciones anteriores.

Para ello, vamos a utilizar un nuevo caso. Ésta vez estamos evaluando el comportamiento de nuestros 5,000 clientes, algunos de los cuales se han dado de baja de nuestros servicios. Tenemos 19 predictores, la mayoría de ellos numéricos, en el dataset mlc_churn. Para acceder a la data tenemos que cargar la librería modeldata.

data(mlc_churn)

str(mlc_churn)
#> tibble [5,000 × 20] (S3: tbl_df/tbl/data.frame)
#>  $ state                        : Factor w/ 51 levels "AK","AL","AR",..: 17 36 32 36 37 2 20 25 19 50 ...
#>  $ account_length               : int [1:5000] 128 107 137 84 75 118 121 147 117 141 ...
#>  $ area_code                    : Factor w/ 3 levels "area_code_408",..: 2 2 2 1 2 3 3 2 1 2 ...
#>  $ international_plan           : Factor w/ 2 levels "no","yes": 1 1 1 2 2 2 1 2 1 2 ...
#>  $ voice_mail_plan              : Factor w/ 2 levels "no","yes": 2 2 1 1 1 1 2 1 1 2 ...
#>  $ number_vmail_messages        : int [1:5000] 25 26 0 0 0 0 24 0 0 37 ...
#>  $ total_day_minutes            : num [1:5000] 265 162 243 299 167 ...
#>  $ total_day_calls              : int [1:5000] 110 123 114 71 113 98 88 79 97 84 ...
#>  $ total_day_charge             : num [1:5000] 45.1 27.5 41.4 50.9 28.3 ...
#>  $ total_eve_minutes            : num [1:5000] 197.4 195.5 121.2 61.9 148.3 ...
#>  $ total_eve_calls              : int [1:5000] 99 103 110 88 122 101 108 94 80 111 ...
#>  $ total_eve_charge             : num [1:5000] 16.78 16.62 10.3 5.26 12.61 ...
#>  $ total_night_minutes          : num [1:5000] 245 254 163 197 187 ...
#>  $ total_night_calls            : int [1:5000] 91 103 104 89 121 118 118 96 90 97 ...
#>  $ total_night_charge           : num [1:5000] 11.01 11.45 7.32 8.86 8.41 ...
#>  $ total_intl_minutes           : num [1:5000] 10 13.7 12.2 6.6 10.1 6.3 7.5 7.1 8.7 11.2 ...
#>  $ total_intl_calls             : int [1:5000] 3 3 5 7 3 6 7 6 4 5 ...
#>  $ total_intl_charge            : num [1:5000] 2.7 3.7 3.29 1.78 2.73 1.7 2.03 1.92 2.35 3.02 ...
#>  $ number_customer_service_calls: int [1:5000] 1 1 0 2 3 0 3 0 1 0 ...
#>  $ churn                        : Factor w/ 2 levels "yes","no": 2 2 2 2 2 2 2 2 2 2 ...

# Traducimos outputs
mlc_churn <- mlc_churn %>% 
  rename(de_baja = churn) %>% 
  mutate(de_baja = ifelse(de_baja == "yes", "Sí", "No")) %>% 
  mutate(de_baja = as.factor(de_baja))
  
# Proporción de "Sí" y "No"s:
prop.table(table(mlc_churn$de_baja))
#> 
#>     No     Sí 
#> 0.8586 0.1414

Creamos ahora nuestra de entrenamiento y de test, 70% entrenamiento.

Hasta aquí hemos hecho exactamente el mismo paso que en los modelos anteriores. Sin embargo, anteriormente hemos especificado el método cross-validation dentro de nuestros parámetros de control y como recordaremos este método parte nuestra data en n partes seleccionadas aleatoriamente (folds). Ahora no necesitamos que sean aleatoriamente para cada modelo, sino los mismos folds.

Así, crearemos manualmente una lista de 5 folds utilizando la función createFolds(outputs, numero_folds).

Utilizaremos la métrica ROC para todos los modelos. Además, agregraremos el atributo index donde ingresaremos como atributo los folds creados.

El siguiente sería elegir los algoritmos de machine learning que queremos utilizar para crear nuestros modelos. Caret nos provee más de 230 algoritmos que se pueden listar utilizando la función getModelInfo(), la cual nos hace un llamado a la lista total de algoritmos. Usaremos names() para obtener solo los nombres.

names(getModelInfo())
#>   [1] "ada"                 "AdaBag"              "AdaBoost.M1"        
#>   [4] "adaboost"            "amdai"               "ANFIS"              
#>   [7] "avNNet"              "awnb"                "awtan"              
#>  [10] "bag"                 "bagEarth"            "bagEarthGCV"        
#>  [13] "bagFDA"              "bagFDAGCV"           "bam"                
#>  [16] "bartMachine"         "bayesglm"            "binda"              
#>  [19] "blackboost"          "blasso"              "blassoAveraged"     
#>  [22] "bridge"              "brnn"                "BstLm"              
#>  [25] "bstSm"               "bstTree"             "C5.0"               
#>  [28] "C5.0Cost"            "C5.0Rules"           "C5.0Tree"           
#>  [31] "cforest"             "chaid"               "CSimca"             
#>  [34] "ctree"               "ctree2"              "cubist"             
#>  [37] "dda"                 "deepboost"           "DENFIS"             
#>  [40] "dnn"                 "dwdLinear"           "dwdPoly"            
#>  [43] "dwdRadial"           "earth"               "elm"                
#>  [46] "enet"                "evtree"              "extraTrees"         
#>  [49] "fda"                 "FH.GBML"             "FIR.DM"             
#>  [52] "foba"                "FRBCS.CHI"           "FRBCS.W"            
#>  [55] "FS.HGD"              "gam"                 "gamboost"           
#>  [58] "gamLoess"            "gamSpline"           "gaussprLinear"      
#>  [61] "gaussprPoly"         "gaussprRadial"       "gbm_h2o"            
#>  [64] "gbm"                 "gcvEarth"            "GFS.FR.MOGUL"       
#>  [67] "GFS.LT.RS"           "GFS.THRIFT"          "glm.nb"             
#>  [70] "glm"                 "glmboost"            "glmnet_h2o"         
#>  [73] "glmnet"              "glmStepAIC"          "gpls"               
#>  [76] "hda"                 "hdda"                "hdrda"              
#>  [79] "HYFIS"               "icr"                 "J48"                
#>  [82] "JRip"                "kernelpls"           "kknn"               
#>  [85] "knn"                 "krlsPoly"            "krlsRadial"         
#>  [88] "lars"                "lars2"               "lasso"              
#>  [91] "lda"                 "lda2"                "leapBackward"       
#>  [94] "leapForward"         "leapSeq"             "Linda"              
#>  [97] "lm"                  "lmStepAIC"           "LMT"                
#> [100] "loclda"              "logicBag"            "LogitBoost"         
#> [103] "logreg"              "lssvmLinear"         "lssvmPoly"          
#> [106] "lssvmRadial"         "lvq"                 "M5"                 
#> [109] "M5Rules"             "manb"                "mda"                
#> [112] "Mlda"                "mlp"                 "mlpKerasDecay"      
#> [115] "mlpKerasDecayCost"   "mlpKerasDropout"     "mlpKerasDropoutCost"
#> [118] "mlpML"               "mlpSGD"              "mlpWeightDecay"     
#> [121] "mlpWeightDecayML"    "monmlp"              "msaenet"            
#> [124] "multinom"            "mxnet"               "mxnetAdam"          
#> [127] "naive_bayes"         "nb"                  "nbDiscrete"         
#> [130] "nbSearch"            "neuralnet"           "nnet"               
#> [133] "nnls"                "nodeHarvest"         "null"               
#> [136] "OneR"                "ordinalNet"          "ordinalRF"          
#> [139] "ORFlog"              "ORFpls"              "ORFridge"           
#> [142] "ORFsvm"              "ownn"                "pam"                
#> [145] "parRF"               "PART"                "partDSA"            
#> [148] "pcaNNet"             "pcr"                 "pda"                
#> [151] "pda2"                "penalized"           "PenalizedLDA"       
#> [154] "plr"                 "pls"                 "plsRglm"            
#> [157] "polr"                "ppr"                 "PRIM"               
#> [160] "protoclass"          "qda"                 "QdaCov"             
#> [163] "qrf"                 "qrnn"                "randomGLM"          
#> [166] "ranger"              "rbf"                 "rbfDDA"             
#> [169] "Rborist"             "rda"                 "regLogistic"        
#> [172] "relaxo"              "rf"                  "rFerns"             
#> [175] "RFlda"               "rfRules"             "ridge"              
#> [178] "rlda"                "rlm"                 "rmda"               
#> [181] "rocc"                "rotationForest"      "rotationForestCp"   
#> [184] "rpart"               "rpart1SE"            "rpart2"             
#> [187] "rpartCost"           "rpartScore"          "rqlasso"            
#> [190] "rqnc"                "RRF"                 "RRFglobal"          
#> [193] "rrlda"               "RSimca"              "rvmLinear"          
#> [196] "rvmPoly"             "rvmRadial"           "SBC"                
#> [199] "sda"                 "sdwd"                "simpls"             
#> [202] "SLAVE"               "slda"                "smda"               
#> [205] "snn"                 "sparseLDA"           "spikeslab"          
#> [208] "spls"                "stepLDA"             "stepQDA"            
#> [211] "superpc"             "svmBoundrangeString" "svmExpoString"      
#> [214] "svmLinear"           "svmLinear2"          "svmLinear3"         
#> [217] "svmLinearWeights"    "svmLinearWeights2"   "svmPoly"            
#> [220] "svmRadial"           "svmRadialCost"       "svmRadialSigma"     
#> [223] "svmRadialWeights"    "svmSpectrumString"   "tan"                
#> [226] "tanSearch"           "treebag"             "vbmpRadial"         
#> [229] "vglmAdjCat"          "vglmContRatio"       "vglmCumulative"     
#> [232] "widekernelpls"       "WM"                  "wsrf"               
#> [235] "xgbDART"             "xgbLinear"           "xgbTree"            
#> [238] "xyf"

A continuación, crearemos una serie de modelos con los datos de entrenamiento y nuestros parámetros de control utilizando los algoritmos más utilizados en machine learning. Así mismo, utilizaremos la métrica de rendimiento ROC en vez de accuracy para comparar los modelos.

10.9.2 Modelo lineal generalizado - GLM

El modelo lineal generalizado (GLM) es una generalización flexible de la regresión lineal ordinaria que permite variables de respuesta que tienen modelos de distribución de errores distintos de una distribución normal. El GLM generaliza la regresión lineal al permitir que el modelo lineal esté relacionado con la variable de respuesta a través de una función de enlace y al permitir que la magnitud de la varianza de cada medición sea una función de su valor predicho.

Para ello necesitamos instalar la librería glmnet antes de crear nuestro modelo.

10.9.3 Modelo de bosque aleatorio o Random Forest

Random Forest es un técnica de aprendizaje automático supervisada basada en árboles de decisión. Usaremos el modelo de bosque aleatorio (RF) y no un árbol de decisión porque un RF es un conjunto de árboles de decisión combinados. Al combinarlos, lo que en realidad está pasando, es que distintos árboles ven distintas porciones de los datos. Ningún árbol ve todos los datos de entrenamiento. Esto hace que cada árbol se entrene con distintas muestras de datos para un mismo problema. De esta forma, al combinar sus resultados, unos errores se compensan con otros y tenemos una predicción que generaliza mejor.

Para ello primero instalaremos la librería ranger y luego crearemos el modelo.

10.9.4 Modelo de máquina de soporte vectorial - SVM

Las máquinas de soporte vectorial o máquinas de vectores de soporte son un conjunto de algoritmos de aprendizaje supervisado desarrollados por Vladimir Vapnik y su equipo en los laboratorios AT&T. Dado un conjunto de puntos, subconjunto de un conjunto mayor (espacio), en el que cada uno de ellos pertenece a una de dos posibles categorías, un algoritmo basado en SVM construye un modelo capaz de predecir si un punto nuevo (cuya categoría desconocemos) pertenece a una categoría o a la otra.

Para crear este modelo usaremos:

10.9.5 Modelo Bayesiano ingenuo - Naive Bayes

Naïve Bayes (NB), Bayesiano ingenuo o el Ingenuo Bayes es uno de los algoritmos más simples, pero potentes, para la clasificación basado en el Teorema de Bayes con una suposición de independencia entre los predictores. Naive Bayes es fácil de construir y particularmente útil para conjuntos de datos muy grandes. El clasificador Naive Bayes asume que el efecto de una característica particular en una clase es independiente de otras características.

Para utilizar este modelo utilizaremos la librería naivebayes.

10.9.6 Comparación de modelos

Para comparar los modelos, primero crearemos una lista con todos los modelos que hemos creado.

Luego, utilizaremos la función resamples la cual nos preparará la data para analizar y visualizar el conjunto de resultados de remuestreo del conjunto de datos común.

Finalmente, usaremos la función bwplot() para trazar una serie de diagramas de caja donde los diagramas de caja individuales representan los datos subdivididos por cada modelo de acuerdo a la métrica que señalemos.

Para este caso el modelo de bosques aleatorios (RF) parece ser el mejor. Esto no es de extrañar dado que este algoritmo estar relacionado con su capacidad para hacer frente a diferentes tipos de entrada y requerir poco preprocesamiento. Podemos hacer nuestros modelos mejor pre procesando datos y cambiando los parámetros ad-hoc de cada modelo.

10.10 Ejercicios

  1. El data frame attrition de la librería modeldata muestra los datos de una relación de casi 1,500 trabajadores de una empresa. Crear una copia de este data frame y almacenalo en el objeto trabajadores. Luego, constuir un modelo RF con estos datos para predecir el campo Attrition (deserción laboral). Donde la clase “Yes” quiere decir que renunció y “No” quiere decir que aun trabaja.

Solución

  1. Utilizando la data de entrenamiento del ejercicio anterior, construye el modelo GLM.

Solución

  1. Utilizando la data de entrenamiento, construye el modelo SVM.

Solución

  1. A partir de los modelos creados, ¿cuál es el más óptimo?

Solución

Vemos cómo los resultados se superponen, con lo que podríamos optar por los dos que tengan la media más alta de ROC y entre ellos elegir el que nos un menor rango de valores.

  1. Crea las matrices de confusión para los tres modelos creados.

Solución

Tener en cuenta que el modelo con el más alto valor de ROC no necesariamente va a tener el más alto accuracy. Por ello la elección del modelo se realizó en un paso previo. El ROC balancea mejor sensitividad con el ratio de falsos positivos.

Referencias

Fawcett, Tom. 2005. An Introduction to Roc Analysis. Elsevier. https://people.inf.elte.hu/kiss/11dwhdm/roc.pdf.

Jorge M. Lobo, Raimundo Real, Alberto Jiménez-Valverde. 2007. AUC: A Misleading Measure of the Performance of Predictive Distribution Models. Ecological Sounding. https://www2.unil.ch/biomapper/Download/Lobo-GloEcoBioGeo-2007.pdf.