Vicente Rodríguez

Oct. 17, 2018

Decision trees / Árboles de decisión para clasificar en python

Un árbol de decisión es un modelo de predicción que se usa en problemas de machine learning, se llama de esta manera por su parecido con las ramas de un árbol. Hay dos tipos de árboles de decisión, uno se usa para problemas de clasificación DecisionTreeClassifier y otro para problemas de regresión DecisionTreeRegressor (Un ejemplo de un problema de regresión es cuando queremos buscar el valor de una casa y dependemos de las caracteristicas de esta casa, el valor que queremos encontrar es numerico), en este tutorial nos enfocaremos en los árboles de decisión para clasificar.

En este tutorial usaremos scikit-learn, una librería para python que contiene muchos modelos que podemos usar para problemas de machine learning, puedes instalarla con pip pero recomiendo usar Google colab, ya que te da acceso a una computadora con todos los programas instalados.

Aquí hay una libreta con el código que veremos en este tutorial, puedes agregar la libreta a Google colab y ejecutar el código, también puedes descargarla desde mi Github

Partes de un árbol de decisión

Partes de un árbol

Overfitting

Un problema común con los árboles de decisión es lo que llamamos Overfitting, este concepto lo podemos traducir como sobre aprendizaje, cuando se genera un árbol de decisión con los parametros por defecto este cubre todos los posibles casos sobre los datos, por ejemplo si nuestros datos son sobre personas con diabetes, un arbol con overfitting generara nodos que cubran a cada persona que aparezca en los datos, puede que en nuestros datos exista una persona con sintomas muy especificos y el árbol aprendera a tomar en cuenta estos sintomas pero en la vida real solo el 1% de la poblacion cuenta con estos sintomas por lo cual el árbol no funcionara bien para el resto de la población, en pocas palabras el árbol aprendio mucho sobre las personas que aparecen en los datos, pensando que solo existen ese tipo de personas en todo el mundo pero la realidad es que hay muchos tipos de personas con diferentes sintomas, lo mejor es buscar un balance para que aprenda de las personas pero que no piense que solo existen este tipo de personas.

Mas adelante veremos esto explicado en el código.

Parametros del árbol de decisión

Estos son los parametros que modificamos más comunes cuando creamos un modelo:

Existen más parametros para el modelo, la lista la puedes encontrar en la documentación de scikit-learn.

Information Gain, Gini Index

Existen diversos algoritmos para dividir los nodos de un árbol, scikit-learn usa dos Information Gain y Gini Index, ambos funcionan de manera diferente pero el proposito es el mismo, encontrar la mejor manera de dividir los datos.

Information Gain

Este algoritmo funciona con la siguiente formula


-(p log2(p)) -(q log2(q))

Si tenemos datos de 100 personas con diabetes,

57 tienen diabetes y 43 no:

p es la probabilidad de exito o de casos positivos: 57/100 (0.57)

q es la probabilidad de fracaso o de casos negativos: 43/100 (0.43)

y la formula quedaria de la siguiente manera:


-(0.57 log2(0.57)) -(0.43 log2(0.43))

Dando un resultado de 0.98

Si el resultado de la formula da 1 esto quiere decir que las clases estan divididas (50%, 50%) en este caso dio 0.98 ya que las se acercan a estar divididas equitativamente, si el resultado de la formula diese 0 eso significa que solo existen datos de una sola clase, lo que el árbol hará es dividir los datos donde el resultado de la formula siempre sea lo más cercana a 0 posible.

Si aparte tenemos los siguientes datos:

Sexo: Masculino, femenino

Masculino: tiene diabetes 35, no tiene diabetes 22

Femenino: tiene diabetes 22, no tiene diabetes 21

Poseen otro tipo de enfermedades: si, no

Si: tiene diabetes 29, no tiene diabetes 28

No: tiene diabetes 28, no tiene diabetes 15

Lo primero que el árbol hara es calcular la entropia si divide los datos por sexo o si divide los datos por poseer otro tipo de enfermedades

Entropia (sexo)

Masculino:

en total tenemos 57 hombres, 35 tienen diabetes (p) y 22 no tienen diabetes (q)

35/57 = 0.61

22/57 = 0.39


-(0.61 log2(0.61)) -(0.39 log2(0.39))

la respuesta es 0.96

Femenino:

tenemos 43 mujeres, 22 tienen diabetes y 21 no:

22/43 = 0.51

21/43 = 0.49


-(0.51 log2(0.51)) -(0.49 log2(0.49))

la respuesta es 0.99

ahora tenemos que sumar estas dos entropias y calcularas por el total de hombres y mujeres:

Hombres:


(57/100) * 0.96 = 0.5472

Mujeres:


(43/100) * 0.99 = 0.4257

Resultado: 0.97

Entropia (Poseen otro tipo de enfermedades)

Sí:

Total: 57

29/57 = 0.51

28/57 = 0.49


-(0.51 log2(0.51)) -(0.49 log2(0.49))

Resultado: 0.99

No:

Total: 43

28/43 = 0.65

15/43 = 0.35


-(0.65 log2(0.65)) -(0.35 log2(0.35))

Resultado: 0.93

Ahora sumamos ambras entropias:

Sí:


(57/100) * 0.99 = 0.5643

No:


(43/100) * 0.93 = 0.3999

Resultado: 0.9642

Como podemos notar dividir primero los datos por (Poseen otro tipo de enfermedades) es mejor ya que los datos son menos homogenios y tenemos una división más pura.

Information Gain

La formula de information Gain es la siguiente:


 p^2 + q^2

p y q valen lo mismo que en la formula de entropia e igual que en esa formula queremos un numero lo más cercano a cero posible ya que esto indica que los datos se dividen de manera menos homogenia.

Usando los datos de Sexo y Poseen otro tipo de enfermedades calcularemos los resultados:

Gini (sexo)

Masculino:

35/57 = 0.61

22/57 = 0.39


0.61^2 + 0.39^2

la respuesta es 0.5242

Femenino:

22/43 = 0.51

21/43 = 0.49


0.51^2 + 0.49^2

la respuesta es 0.5002

Sumamos los resultados:

Hombres:


(57/100) * 0.5242 = 0.298794

Mujeres:


(43/100) * 0.5002 = 0.215086

Resultado: 0.51388

Gini (Poseen otro tipo de enfermedades)

Sí:

29/57 = 0.51

28/57 = 0.49


0.51^2 + 0.49^2

Resultado: 0.5002

No:

28/43 = 0.65

15/43 = 0.35


0.65^2 + 0.35^2

Resultado: 0.545

Ahora sumamos ambras entropias:

Sí:


(57/100) * 0.5002 = 0.285114

No:


(43/100) * 0.545 = 0.23435

Resultado: 0.519464

Usando Information Gain la diferencia entre que datos usar para realizar la división es muy pequeña, pero podemos notar que en este caso es mejor dividir los datos por sexo.

El problema

Usaremos un set de datos sobre personas con diabetes se puede descargar aquí, Aquí no limpiaremos los datos ni tampoco los exploraremos, aunque hay que aclarar que la exploración de datos es el punto más importante a la hora de resolver un problema de machine learning, son más importantes los datos que el modelo.

El set de datos contiene información sobre cada persona, en total son 768 filas cada una representando a una persona y 9 columnas, tenemos columnas como numero de embarazos, presión sanguínea, insulina, edad y la más importante outcome, que es la que indica si la persona tiene diabetes o no (0 significa que no tiene diabetes y 1 que si tiene diabetes), un árbol para clasificar necesita dos entradas, se representan con X y con y, X son las propiedades las cuales el árbol tendra que aprender y relacionar para saber si una persona tiene diabetes o no, y es el valor al cual pertenece la clase, en este ejemplo solo tenemos dos clases, negativo y positivo (0, 1), a X le tenemos que asignar las columnas: numero de embarazos, presión sanguínea, insulina, etc y a y solo la columna outcome.

Código

Recomiendo descargar la libreta para poder ver el código paso a paso, aquí solo dejare los puntos importantes.

Usare pandas para acceder a las variables y para mostrar un poco de los contenidos que tienen los datos, pandas es una librería para manipular datos que es muy usada y útil en el mundo de la ciencia de datos.

Importar librerías


from sklearn.model_selection import train_test_split

from sklearn.tree import DecisionTreeClassifier

import numpy as np

import pandas as pd

Estas son las librerías que necesitaremos para crear el modelo y para manipular los datos.

Acediendo a los datos

Lo primero que haremos es acceder a los datos y guardarlos en una variable llamada dataset:


dataset = pd.read_csv("diabetes.csv")

Podemos visualizar los datos como si estuvieramos usando excel:


dataset.head()

dataset.shape

Ahora vamos a dividir los datos en X, y


features = dataset.drop(["Outcome"], axis=1)

X = np.array(features)

y = np.array(dataset["Outcome"])

La variable features guarda todos los datos excepto Outcome ya que esta columna es la que usamos para crear la variable y, convertimos los datos a array con np.array

Training y validation set

Cuando se construye un modelo es importante tener tres set de datos:

Los datos de entrenamiento son los datos que el modelo utilizara para aprender, los datos de validación son los datos con los cuales comprobaras que tan bueno es el modelo con datos que no conoce y sirve para encontrar los mejores parametros del modelo, la idea es encontrar un equilibrio entre estos dos y por ultimo los datos de prueba son parecidos a los de validación pero estos solo se usan una sola vez ya cuando sientes que tu modelo es suficientemente bueno, es importante que estos sets no tengan datos repetidos porque la idea es ver como se comporta el modelo con datos que nunca ha visto.

Cada set de datos tiene su propio conjunto X, y.

Ahora gracias a scikit-learn dividimos los datos en training y validation set, en este ejemplo no usaremos el test set porque no hay suficientes datos.


X_train, X_val, y_train, y_val = train_test_split(X, y, random_state=0, test_size=0.20)

train test split divide los datos X, y, test_size indica el porcentaje que se le asignara a los datos de validacion, en este caso el 20%.

Creando el modelo

Finalmente veremos como crear el modelo, la librería scikit-learn, es muy fácil de utilizar ya que ya tiene todo el modelo escrito y solo tenemos que indicarle los datos y los parametros, empezaremos con un modelo que tenga los parametros por defecto:


tree = DecisionTreeClassifier()

tree.fit(X_train, y_train)

La primera línea de código crea el objeto tree, la segunda le indica al objeto que aprenda con los datos otorgados X_train, y_train

Podemos preguntar cual es la profundidad del árbol:


tree.tree_.max_depth

y la respuesta es 15, quiere decir que hay una alta probabilidad de que el modelo tenga un sobre aprendizaje.

Exactitud del modelo

Vamos a ver que tan bueno es el modelo con los datos de entrenamiento y los datos de validación:


validation_prediction = tree.predict(X_val)

training_prediction = tree.predict(X_train)

Le pedimos al objeto tree que nos prediga apartir de los valores X_val y X_train si la persona tiene diabetes o no, estas variables contienen un array de valores (0, 1), el primer valor del array training_prediction[0] indica la prediccion para la primera persona que aparece en los datos de X_train.


print('Exactitud training data: ', accuracy_score(y_true=y_train, y_pred=training_prediction))

print('Exactitud validation data: ', accuracy_score(y_true=y_val, y_pred=validation_prediction))

Con estas lineas de código podemos imprimir la exactitud de cada set de datos:


Exactitud training data:  1.0

Exactitud validation data:  0.7922077922077922

La exactitud en el set de entrenamiento es del 100% pero en el set de validación es del 80%, esto indica que el modelo aprendio mucho de los datos del set de entrenamiento y no puede identificar bien a las personas en el set de validación en pocas palabras tenemos overfitting.

Visualizar el árbol de decisión

Una de las ventajas de los árboles de decisión es que podemos visualizarlo para saber como esta trabajando:

tendremos que instalar una librería llamada graphviz, si estas en linux o Google colab puedes instalarla con el siguiente comando


apt-get install graphviz

y también se necesita instalar en python:


!pip install graphviz

Ahora podemos importarla


import graphviz 

from sklearn.tree import export_graphviz

Y podemos crear una imagen del árbol:


feature_names = features.columns



dot_data = export_graphviz(tree, out_file=None, 

                         feature_names=feature_names,  

                         class_names=True,  

                         filled=True, rounded=True,  

                         special_characters=True)  

graph = graphviz.Source(dot_data)



graph

En la primera línea de código obtenemos los nombres de cada columna, estos nos serviran para imprimirlos en el arbol de decisión, la segunda línea crea un objeto para poder visualizar el árbol,

esta funcion necesita el árbol y el nombre de las columnas (feature_names)

El primer modelo es muy grande como para poner una imagen, pero la dejare en este link, la imagen se puede leer en el siguiente orden, cada nodo tiene una descripción:

  1. La decisión que se toma para dividir el nodo

  2. El tipo criterio que se uso para dividir cada nodo

  3. Cuantos valores tiene ese nodo

  4. Cuantos valores pertenecen a la primera categoria y cuantos pertenecen a la segunda categoría

  5. Por ultimo la clase a la cual pertenece ese nodo

Creando el segundo modelo

Vamos a crear un modelo que tenga un mejor equilibrio entre el set de entrenamiendo y el set de validación:


tree = DecisionTreeClassifier(min_samples_leaf=10, max_depth=8, min_samples_split=50)

Esta vez creamos un árbol indicandole parametros, queremos que la maxima profundidad sea de 8, que cada nodo hoja tenga al menos 10 datos y que el minimo numero de datos para que un nodo pueda ser de decisión sea de 50.

Repetimos el mismo proceso para entrenar al modelo y obtener las predicciones:


tree.fit(X_train, y_train)

validation_prediction = tree.predict(X_val)

training_prediction = tree.predict(X_train)

Ahora queremos mostrar la exactitud de cada set:


print('Exactitud training data: ', accuracy_score(y_true=y_train, y_pred=training_prediction))

print('Exactitud validation data: ', accuracy_score(y_true=y_val, y_pred=validation_prediction))


Exactitud training data:  0.7964169381107492

Exactitud validation data:  0.8116883116883117

Como podemos notar hemos mejorado la exactitud del set de validación y ahora ambos sets ya estan más equilibrados, es importante recalcar que muchas veces no es posible alcanzar una exactitud del 100% en ambos sets, en este ejemplo podriamos mejorar la calidad de los datos y explorarlos más y también buscar mejores parametros, realmente la tarea de un cientifico de datos es explorar los datos y encontrar los mejores parametros para los modelos y son los pasos más importantes cuando se resuelven problemas.