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
-
Nodo principal: Es el nodo que representa toda la población o todos los datos que tenemos y que posteriormente se dividirá. También cuenta como un nodo de decisión.
-
Nodo de decisión: Este tipo de nodo se encarga de dividir los datos en dos grupos dependiendo de una decisión, por ejemplo si tenemos datos sobre personas podemos dividir por peso, si pesa igual o más de 50kg toma el camino de la izquierda, si pesa menos de 50kg toma el camino de la derecha.
-
Nodo hoja: El nodo hoja no se divide más y es donde recae la decisión final, en el caso de árbol para clasificar cada nodo hoja representa una clase, por ejemplo si una persona tiene diabetes o no.
-
Profundidad: La profundidad indica que tantas ramas tiene un arbol de decisión, las ramas podriamos definirlas como las filas de un árbol, si el árbol de ejemplo lo representaramos como filas este tendria 4, la del nodo principal, la de los dos nodos de decisión, la de los tres nodos hoja y por último la de los dos nodos hoja.
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:
-
max_depth: Indica cual es la maxima profundidad a la cual puede llegar el árbol, esto ayuda a combatir el overfitting pero tambien puede provocar underfitting.
-
min_samples_leaf: Indica cual es la cantidad mínima de datos que tiene que tener un nodo hoja
-
min_samples _split: Indica cual es la cantidad mínima de datos para que un nodo de decisión se pueda dividir, si la cantidad no es suficiente este nodo se convierte en un nodo hoja.
-
criterion: Indica cual es la función que se utilizara para dividir los datos puede ser gini o entropy. Cuando el árbol es de regresión este valor es diferente y se usan funciones diferentes como el error medio cuadrado mse.
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:
-
Datos de entrenamiento(Training)
-
Datos de validación(Validation)
-
Datos de prueba(Test)
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:
-
La decisión que se toma para dividir el nodo
-
El tipo criterio que se uso para dividir cada nodo
-
Cuantos valores tiene ese nodo
-
Cuantos valores pertenecen a la primera categoria y cuantos pertenecen a la segunda categoría
-
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.