Juan David Velásquez Henao
jdvelasq@unal.edu.co
Universidad Nacional de Colombia, Sede Medellín
Facultad de Minas
Medellín, Colombia
[Licencia]
[Readme]
Software utilizado.
Este es un documento interactivo escrito como un notebook de Jupyter, en el cual se presenta un tutorial sobre regresión logistica usando R en el contexto de aprendizaje de maquinas. Los notebooks de Jupyter permiten incoporar simultáneamente código, texto, gráficos y ecuaciones. El código presentado en este notebook puede ejecutarse en los sistemas operativos Linux y OS X.
Haga click aquí para obtener instrucciones detalladas sobre como instalar Jupyter en Windows y Mac OS X.
Haga clic [aquí] para ver la última versión de este documento en nbviewer.
Descargue la última versión de este documento a su disco duro; luego, carguelo y ejecutelo en línea en Try Jupyter!
Haga clic aquí para ver el tutorial de visualización y gráficas.
Los métodos basados en árboles son modelos que utilizan diagramas de que representan reglas lógicas, más conocidos como árboles, que se utilizan para representar y categorizar una serie de condiciones que ocurren de forma sucesiva, para la resolución de un problema a partir de los datos de entrada.
Estos modelos se enfrentan a los problemas de regresión y clasificación de forma exitosa los cuales se veran más adelante, así como sus propiedades interesantes en cada uno de los métodos (manejo de valores faltantes o su alta interpretabilidad).
Los árboles de decisión consisten en una serie de puntos de partición o nodos donde cada uno representa una regla de decisión para dividir los datos. El modelo empieza con el nodo principal o raíz el cual reparte, a la derecha o izquierda del árbol, la información de entrada a partir del valor de unos de las variables de entrada. Este proceso se repite en los nodos interiores hasta que se encuentra un nodo hoja en la base del árbol, el cual da el valor de salida para la predicción.
En el siguiente árbol se tiene 5 nodos: 1 nodo raíz, 1 nodo intermedio y 3 nodos hojas. Observe que el nodo raíz se parte en dos (Sub-arbol derecho e izquierdo).
Para ilustrar, suponga que se utiliza este árbol para predecir la salida de una observación con los valores de $ X_1 = 96.0 ; X_2 = 79.9 $. Se evalua la regla del nodo raíz $ X_2 < 23 $ la cual no cumple, por lo tanto la observación se dirige al lado derecho del árbol para evaluar el siguiente nodo (El lado izquierdo siempre se toma cuando la condición se cumple). Nuevamente, se evalua la siguiente regla donde $ X_1 < 46 $ la cual tampoco se cumple, por lo tanto, la observación se dirige a la derecha donde llega a un nodo hoja y se le asigna la salida -3.7.
Una forma de interpretar un árbol de decision es que son de hecho una serie encadenada de reglas si - entonces que conllevan a distintos valores de entrada, donde para cada nodo hoja se puede escribir una regla usando operadores lógicos para juntar varias reglas que deben ser verdad para que el árbol prediga dicho valor.
Otra forma análoga de pensar el modelo es que realiza particiones en el espacio de la variable como se observa en la imagen para el ejemplo de dos variables de entrada.Cuando es una dimensión (variable de entrada) la partición es lineal. Cuando son dos dimensiones las particiones son rectangulares. En tres dimensiones las particiones son cubos y en más dimensiones las particiones son hiperplanos. Esta forma de ver el modelo es interesante ya que ilustra que no pueden existir observaciones sin valor de salida ya que todo el espacio es particionado sin dejar espacios vacíos, lo cual es una ventaja de este tipo de modelos.
Existen varios algoritmos que realizan árboles de decisión de los cuales se verán los más utilizados:
Todos los algoritmos de árboles responden 4 preguntas elementales:
Dentro de este enfoque, se escoje tanto la variable y su valor de partición encontrando la combinación de éstas que maximice la reducción de la suma de errores cuadrados (SSE). En principio (nodo raíz), se tiene todo el conjunto de observaciones, por lo tanto el valor inicial del SSE está dado por:
$$ SSE = \sum_{i=1}^{n} ( \hat{y} - y_i)^{2} $$Si se divide las observaciones en dos $ n_1 $ (izquierda) y $ n_2 $ (derecha) y se calcula nuevamente el SSE, se tiene que:
$$ SSE = \sum_{i=1}^{n_1} ( \bar{y} - y_i)^{2} + \sum_{j=1}^{n_2} ( \bar{y} - y_j)^{2} $$Por lo tanto, la idea de CART es formar estos dos grupos considerando cada variable de entrada y su valor posible de tal forma que el SSE se minimice.
Este proceso se realiza desde el nodo raíz hasta los nodos intermedios siguientes, lo que se conoce como particionamiento recursivo. No obstante si este proceso sigue indeterminadamente, se tendrá un nodo hoja para cada observación lo que generaría un ajuste perfecto del modelo pero no se desempeñaría muy bien con datos no observados o no entrenados. Por lo tanto, los árboles están expuestos a sobre-ajuste.
Con el fin de combatir esto se poda el árbol. Esto consiste en remover nodos del árbol para limitar su crecimiento y complejidad.
Una forma de podar el árbol consiste en establecer un límite mínimo de datos dentro de cada nodo hoja limitando la partición recursiva, por lo tanto si al particionar el nodo resultando presenta menos datos que el establecido, este no se particiona y el nodo se convierte en un nodo final. Por último, en este caso, para cada nodo hoja el valor de salida es simplemente el valor promedio del valor de las salidas individuales de cada observación dentro de dicho nodo.
Otra aproximación para podar el árbol, consiste en un proceso de regularización conocido como ajuste de costo por complejidad. Para esto, se deja crecer el árbol sin restricción alguna, donde luego se empiezan a remover o fusionar ciertos nodos dependiendo cierta condición. Dicha condición más usada es añadir un parámetro de castigo a la función de SSE:
$$ SSE_{penalized} = SSE + \alpha · T_p $$Donde:
A niveles bajos de $ \alpha $ la poda es corta, donde un nivel de $ \alpha = 0 $ signfica que no existe poda alguna. Por el contrario, valores altos de $ \alpha $ significa que el arbol será más corto, donde a un límite determinado resultaría en que no exista partición de los datos. Por lo tanto, para cada $ \alpha $ existe una única forma de podar el árbol con el fin de minimizar el SSE. No obstante, el método no dice qué valor de $ \alpha $ se debe tomar, pero existen técnicas como la validación cruzada u optimización combinatoria que pueden determinar qué valor es mejor para dicho parámetro.
Nota: Cuando existan valores faltantes en los datos, para calcular el SSE se pueden ignorar dichos valores y de esta forma evitar los problemas de cálculo. No obstante puede incrementar el sesgo del modelo, especialmente cuando existen muchos valores faltantes.
Análogo a los árboles de regresión, se dividen las observaciones recursivamente a partir de la minimización de una función de error. No obstante, a diferencia del SSE que mide el error en un nodo numérico, lo que se busca medir dentro de un nodo de variables categóricas es la pureza, que mide si el nodo contiene primordialmente observaciones pertenecientes a una variable de salida categórica. Lo anterior asegura que se puede predecir con confianza la salida del nodo final si una observación llega a este.
Una de las posibles medidas de pureza es el el Índice Gini. Para una variable con $ K $ clases diferentes se define como:
$$ G = \sum_{k=1}^{K} \hat{p_k} · (1-\hat{p_k}) $$Por lo tanto, el índice representa la suma del producto de la probabilidad de cada clase por la probabilidad de no pertencer a dicha clase. Luego, en un nodo particular se puede usar la división simple del número de datos clasificados en la categoria $ k $ sobre el total de observaciones en dicho nodo para estimar dicha probabilidad. Note que si el nodo contiene todas las observaciones de la misma categoría entonces el índice sería igual a 0. ($ \hat{p_k} = 1 $, luego $ (1-\hat{p_k}) = 0 $)
Similar al SSE en regresión, se toma en cuenta la disminución ponderada por tamaño relativo en el índice Gini para determinar los nodos intermedios.
$$ Gini_{reduced} = Gini_{initial} - \sum_{i=1}^{p} \frac {n_i}{n} · Gini_i $$Otro criterio utilizado es la "devianza", la cual es similar al SSE ya que mide el error, no obstante se utiliza cuando el estimador se obtuvo hallando la máxima verosimilitud. En este caso, se calcula la devianza de la siguiente forma:
$$ D = -2 \sum_{k=1}^{K} n_k · log(\hat{p_k}) $$A diferencia del índice Gini, el número total de observaciones $ n_k $ en un nodo afecta el valor de la devianza. Por lo tanto, se puede observar que si todos los nodos tienen la misma proporcion de clases sin importar el número de datos tendrán un mismo índice Gini. No obstante, no tendrán el mismo valor de devianza. Note que de igual forma, si dentro de un nodo todas las observaciones pertenecen a una misma clase, el valor será 0. ($ \hat{p_k} = 1 $ , luego $ log(1) = 0$)
Predicción de Ligas en el Juego SkillCraft SkillCraft es un juego en tiempo real donde los jugadores compiten entre otros en mapas, donde cada uno debe escoger una raza ficticia de tres disponibles e iniciar con 6 trabajadores que son usados para recolectar uno de dos recursos en el juego. Estos recursos son necesarios para construir edificios de militares y de producción, investigar tecnología y generar más constructores, por lo que combina avance económico, crecimiento y estrategia militar. La idea bajo este proyecto es estudiar el desempeño de los jugadores, cómo pueden los humanos aprender habilidades complejas, desarrollar velocidad y competencia en estrategía de tiempo real que incolucre administración de recursos en ambientes dinámicos.
Los jugadores son enfretados unos a otros en línea usando un algoritmo de emparejamiento que agrupa los jugadroes en ligas de acuerdo a su nivel de habilidades percibido. Esta percepectión del algoritmo cambia con el tiempo en base al desempeño del jugador a través de los juegos en los cuales participa. Existen 8 ligas en total que son disparejas en población donde la ligas menores tienden a tener más jugadores y las ligas mayores tienen menor cantidad, en orden de 1 al 8, respectivamente.
El objetivo de el árbol de decisión es determinar, a partir de ciertas variables de jugador, predecir a qué liga pertenece. Para esto las filas de la base de datos son juegos individuales que ha participado un jugador con sus respectivas características de velocidad, competencia y toma de decisiones.
En la imagen se observa el nombre de las variables y su descripción.
In [1]:
## Instale y cargue las siguientes librerías
library(caret)
library(rpart)
library(e1071)
In [2]:
## Lectura de los datos
## Link de descarga del archivo plano (csv)
link <- "https://archive.ics.uci.edu/ml/machine-learning-databases/00272/SkillCraft1_Dataset.csv"
## lectura
skillcraft <- read.csv(url(link)) # Función url abre la conexión con el link de descarga
## Primeros 10 registros de los datos
head(skillcraft, 10)
No obstante la gran cantidad de datos, existen valores no validos "?", donde para esto se convierten a l formato de N/A en R y se rellenan estos N/A con la función "complete.cases()"
In [3]:
## Manipulación y transformación de los datos.
## Elimina primera columna.
skillcraft <- skillcraft[-1]
## Se observan valores no numéricos en la Base de Datos
skillcraft$TotalHours[skillcraft$TotalHours=="?"]
In [4]:
## Se reemplazan los valores "?" por "NA" en las variables "TotalHours", "HoursPerWeek" y "Age"
## La función levels trae los valores únicos de la variable y se forza el NA con la función as.numeric
skillcraft$TotalHours <- as.numeric(levels(skillcraft$TotalHours))[skillcraft$TotalHours]
skillcraft$HoursPerWeek <- as.numeric(levels(skillcraft$HoursPerWeek))[skillcraft$HoursPerWeek]
skillcraft$Age <- as.numeric(levels(skillcraft$Age))[skillcraft$Age]
In [5]:
## Completar datos faltantes
skillcraft <- skillcraft[complete.cases(skillcraft),]
## Se verifica que no exista "?"
skillcraft$TotalHours[skillcraft$TotalHours=="?"]
In [6]:
## Creación de particion para entrenamiento y validación
## Semilla para resultados reproducibles
set.seed(133)
skillcraft_sampling_vector <- createDataPartition(skillcraft$LeagueIndex, # Datos a particionar
p = 0.80, # Porcentaje de partición
list = FALSE)
skillcraft_train <- skillcraft[skillcraft_sampling_vector,] # Datos para entrenamiento
skillcraft_test <- skillcraft[-skillcraft_sampling_vector,] # Datos para test
In [7]:
## Arbol CART
regtree <- rpart(LeagueIndex ~ ., # Variable dependiente "LeagueIndex"
data = skillcraft_train) # Datos de entrenamiento del modelo
## Resumen del modelo
summary(regtree)
Esta función muestra el arbol nodo por nodo, donde muestra las reglas aplicadas, los nodos surrogados, las mejoras, el ajuste entre otra información que puede ser util para entender cómo funciona el arbol. No obstante, la versión gráfica es mejor de entender.
In [8]:
## Arbol gráfico
options(repr.plot.width=10, repr.plot.height=5)
plot(regtree, # Modelo CART
uniform = TRUE) # Grafica uniforme en la division de los nodos
text(regtree, # Modelo CART
use.n = FALSE, # No muestra cantidad de datos en el nodo
all = TRUE, # Todos los nodos
cex = .8) # Parametro de proporción en el grafico
In [9]:
## Predicción con el modelo
regtree_predictions <- predict(regtree, # Modelo CART
skillcraft_test) # Datos para validacion
head(regtree_predictions)
In [10]:
## Funcion para calcular el error cuadrado medio
compute_SSE <- function(correct, predictions) {
return(sum((correct - predictions) ^ 2))
}
(regtree_SSE <- compute_SSE(regtree_predictions, # Predicciones
skillcraft_test$LeagueIndex)) # Reales
Se puede observar un $SSE = 740.08$ el cual por si solo no nos da mucha información. Para comparar, se realizará un mejoramiento de los parámetros del árbol.
In [11]:
## Modelo con parametros modificados al azar
regtree.random <- rpart(LeagueIndex ~ .,
data = skillcraft_train, # Datos de entrenamiento
control = rpart.control(minsplit = 20, # Número mínimo de datos para poder dividir (crear rama)
cp = 0.001, # Parametro de complejidad
maxdepth = 10)) # Numero maximo de nodos entre las hojas y la raiz
## Predicciones
regtree.random_predictions <- predict(regtree.random,skillcraft_test)
## Suma del error cuadrado
(regtree.random_SSE <- compute_SSE(regtree.random_predictions,skillcraft_test$LeagueIndex))
In [12]:
## Estructura del arbol grafico
options(repr.plot.width=10, repr.plot.height=6)
plot(regtree.random, uniform=TRUE)
Note que un árbol con parámetros al azar pueden tener un SSE más alto que el original dado a su alto nivel de poblamiento en las hojas y complejidad, por lo tanto procederemos a la mejora con la librería e1070 y sus funciones de tuning.
In [13]:
## Se define una lista con el espacio de busqueda de los parametros.
library(e1071)
rpart.ranges <- list(minsplit = seq(5, 50, by = 5),
cp = c(0,0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5),
maxdepth = 1:10)
## Se encuentran los mejores paramtros para el modelo.
(regtree.tune <- tune(rpart,LeagueIndex ~ .,
data = skillcraft_train, # Datos de entrenamiento
ranges = rpart.ranges)) # Rangos
In [14]:
## Modelo CART con los parametros calibrados
regtree.tuned <- rpart(LeagueIndex ~ .,
data = skillcraft_train, # Datos de entrenamiento
control = rpart.control(minsplit = 35, # Número mínimo de datos para poder dividir (crear rama)
cp = 0.002, # Parametro de complejidad
maxdepth = 6)) # Numero maximo de nodos entre las hojas y la raiz
## Predicciones del modelo calibrado
regtree.tuned_predictions <- predict(regtree.tuned, # Modelo calibrado
skillcraft_test) # Datos de test
## Suma del error cuadratico del modelo calibrado.
(regtree.tuned_SSE <- compute_SSE(regtree.tuned_predictions, # Predicciones
skillcraft_test$LeagueIndex)) # Valores reales
In [15]:
## Arbol grafico del modelo calibrado
options(repr.plot.width=10, repr.plot.height=6)
plot(regtree.tuned, uniform=TRUE)
text(regtree.tuned, cex=0.5)
In [16]:
## Grafico de barras
options(repr.plot.width=10, repr.plot.height=4)
barplot(regtree.tuned$variable.importance, # Datos a graficar
las=2) # Nombres eje x forma vertical
Se puede observar que la mejora del arbol CART arrojo un $SSE = 701.33$, adicional que se puede evidenciar un arbol más compacto que los dos arboles anteriores. Por último, a partir de la importancia de las variables podemos ver que el APM (acciones por minuto) es la variable que más influye a la hora de decidir a qué liga pertence el jugador.
In [ ]:
Este algorimto fue desarrollado por Ross Quinlan el cual es una mejora de los modelos ID3 y C4.5. A diferencia del algoritmo CART, la eleccion del criterio de partición y el procedimiento de poda son los cambios principales.
En primer lugar, el criterio de partición usado se conoce como entropía o el estadístico de información que tiene sus raices en la física y la teoría de la información. La primera la define como el grado de caos e incertidumbre en un sístema físico, la segunda la define como la función de distribución de probabilidades el número promedio de digitos binarios necesarios (basado en diferentes simbolos usados) para comunicar información a través de un mensaje.
$$ Entropy = - \sum_{k=1}^{K} p_k · log_2 (p_k) $$En esta función cuando los componentes de un sistema tienen la misma probabilidad existe un grado más alto de incertidumbre, por otro lado, cuando un componente es más probable que los demas la entropía es más baja. Por lo tanto, aplicado en árboles de decisión, a medida que una clase sea más probable menos entropía (o más puro) será el nodo.
En siguiente imagen se puede comparar el coeficiente Gini y la entropía para una variable binaria, donde se puede observar que ambas tienen la misma forma curva, no obstante para valores bajos de entropía menor la incertidumbre que tenemos de la clase y mayor pureza.
Por lo tanto, el objetivo es minimizar la entropía del arbol. Para esto se define la ganancia de información de la siguiente forma:
$$ Information Gain = Entropy_{initial} - \sum_{i=1}^{p} \frac {n_i}{n} · Entropy_i $$No obstante, esta ganancia se encuentra sesgada ya que favorece más a las variables categoricas que las numéricas, dado que los posibles grupos categoricos de agrupación son mayores comparados con el rango lineal de partición. Para superar esta limitación, se utilizala proporcion de ganancia de información, la cual es una versión normalizada respecto al valor de información de la partición.
$$ Information Gain Ratio = \frac {Information Gain}{Split Information Value} $$$$ Split Information Value = - \sum_{i=1}^{p} \frac {n_i}{n} · log_2 (\frac {n_i}{n}) $$Lo anterior representa el incremento potencial en información que se puede obtener solo por el tamaño de las particiones por si solas (Split Information Value). Luego, un valor alto de Split Information Value se da cuando mucha de las observaciones se concentran en un número pequeño de particiones.
Por último, el algoritmo C5.0 incorpora métodos para podar arboles diferentes a los mencionados en CART. Por ejemplo, un nodo intermedio se puede remover antes de un nodo final de tal forma que los nodos debajo de este (sub-arbol) se mueva para arriba y reemplace dicho nodo. De igual forma, incorpora mejoras en velocidad, memoría y mejoramiento, así como el manejo de una matriz de costo para evitar que el algoritmo cometa cierto tipo de clasificaciones erróneas.
Falsificación Notas Bancarias En este ejemplo se estudiará el problema de predicir si una nota bancaria es genuina o si ha sido falsificada. La imagen de la nota en blanco y negro ha sido escalda usando la transformación ondícula y se han sacado 4 propiedades de estas: La varianza, la asimetría, la curtosis y la entropía, además de la clasificación binaria 1 (falsa) o 0 (geniuna).
In [17]:
## Link de los datos
link<-"https://archive.ics.uci.edu/ml/machine-learning-databases/00267/data_banknote_authentication.txt"
## Lectura de datos
bnote<-read.csv(url(link), # URL abre la conexion con el link
header=F) # No tiene encabezado
## Nombres de las variables
colnames(bnote)<-c("waveletVar", # Varianza
"waveletSkew", # Asimetría
"waveletCurt", # Curtosis
"entropy", # Entropía
"class") # Clasificacion falsa o no
## Forzamos la variable class a categórica (factor)
bnote$class<-as.factor(bnote$class)
head(bnote) # Primeros datos
In [18]:
## Cargar librería caret
## library(caret)
## Semilla para resultados reproducibles
set.seed(266)
## Crear vector índices de poblaciones de entrenamiento
bnote_sampling_vector <- createDataPartition(bnote$class, # Crear partición para variable "class"
p =0.80, # Porcentaje de particion de la data para entrenamiento
list = FALSE) # Output de la función no sea una lista
## Primeros registros
head(bnote_sampling_vector) # Note que no está el índice 4
Se puede observar que en la columna Resample no se encuentran ciertos números. Los numeros que se encuentran en esta variable indica las posiciones de los registros en la base de datos que se utilizarán para entrenar el árbol de decisión. Por el contrario, aquellos números que no se encuentren dentro de esta variable indican las posiciones de los registros en la base de datos que se usarán como validación.
In [19]:
## Subset par datos de entrenamiento
bnote_train <- bnote[bnote_sampling_vector,] # Datos entrenamiento
## Subset par datos de validacion
bnote_test <- bnote[-bnote_sampling_vector,] ## Signo menos trae los registros que no fueron seleccionados en el entrenamiento
In [20]:
## Librería tree
library(C50)
## Creamos arbol C5.0
bnote_tree <- C5.0(class ~ ., # Variable independiente "class", independientes todas (.)
data = bnote_train) # Data input
## Resumen del modelo
summary(bnote_tree)
Se observa el árbol de forma lógica con cada uno de sus nodos, sus respectivas reglas y valores de corte. Para los nodos hoja, se observan el número de observaciones que hay en cada uno con su respectivo valor 0 o 1. No obstante, esta vista puede ser confusa y poco entendible, por lo tanto se grafica el arbol.
In [21]:
## Arbol gráfico
options(repr.plot.width=10, repr.plot.height=6)
plot(bnote_tree)
In [22]:
## Predicciones
bnote_predictions <- predict(bnote_tree, # Modelo de arbol de decision
bnote_test) # Data de validacion
head(bnote_predictions) # Primero registros
In [23]:
## Evaluar las predicciones que coinciden con el valor real (Precision)
mean(bnote_test$class == bnote_predictions)
Se observa que para los datos de validación, se acierta en la clasificación de la nota bancaria en el 98.90% de las veces.
Ejercicio.--. Utilice la base de datos de Iris la cual contiene las siguientes características de 3 especies de flores:
Para este ejericio, se busca identificar a partir de las longitudes y largos del sépalo y pétalo, el tipo de especie al que la flor pertenece. Para esto utilice un 90% de los datos para entrenamiento y el restante para evaluación. Adicionalmente se requiere que realice dos modelos con los algoritmos C5.0 y CART, compare sus desempeños y diga cuál es el mejor. En caso de ser posible, utilice mejoramiento de parámetros para ambos modelos.
In [ ]:
Ejercicio.--.Se tiene la información de personas aseguradas en un plan médico donde indica las características de los pacientes así como los gastos médicos asociados a cada asegurado.
Las variables contenidas en este conjunto de datos son:
Dado que para una aseguradora no le sale rentable tener unos gastos muy elevados, un modelo de predicción de gastos agregaría valor a la administración de riesgos dado que permite estimar posibles perdidas incurridas para establecer provisiones o realizar campañas de prevención a las características de mayor importancia. Se busca desarrollar una solución a las siguientes preguntas de negocio que se enfrentan:
Tenga en cuenta que algunas veces la relación entre las variables presenta relaciones no lineales, por ejemplo, la edad entre más alta sea más implicaciones en el costo médico tendrá (no necesariamente es una relación lineal). El IMC por ejemplo comenzará a ser crítico en los costos médicos si este sobrepasa el promedio. Por tanto, definir estas relaciones no lineales será de gran importancia para que el modelo analizado pueda ajustarse de manera más precisa. Por lo tanto utilice las siguientes transformaciones a las variables:
In [ ]: