Arbol de Decisión en Python: Clasificación y predicción.

En este artículo describiremos rápidamente en qué consisten y cómo funcionan los árboles de decisión utilizados en Aprendizaje Automático y nos centraremos en un divertido ejemplo en Python en el que analizaremos a los cantantes y bandas que lograron un puesto número uno en las listas de Billboard Hot 100 e intentaremos predecir quién será el próximo Ed Sheeran a fuerza de Inteligencia Artificial. Realizaremos Gráficas que nos ayudarán a visualizar los datos de entrada y un grafo para interpretar el árbol que crearemos con el paquete Scikit-Learn. Comencemos!

¿Qué es un árbol de decisión?

Los arboles de decisión son representaciones gráficas de posibles soluciones a una decisión basadas en ciertas condiciones, es uno de los algoritmos de aprendizaje supervisado más utilizados en machine learning y pueden realizar tareas de clasificación o regresión (acrónimo del inglés CART). La comprensión de su funcionamiento suele ser simple y a la vez muy potente.

Utilizamos mentalmente estructuras de árbol de decisión constantemente en nuestra vida diaria sin darnos cuenta:

¿Llueve? => lleva paraguas. ¿Soleado? => lleva gafas de sol. ¿estoy cansado? => toma café. (decisiones del tipo IF THIS THEN THAT)

Los árboles de decisión tienen un primer nodo llamado raíz (root) y luego se descomponen el resto de atributos de entrada en dos ramas (podrían ser más, pero no nos meteremos en eso ahora) planteando una condición que puede ser cierta o falsa. Se bifurca cada nodo en 2 y vuelven a subdividirse hasta llegar a las hojas que son los nodos finales y que equivalen a respuestas a la solución: Si/No, Comprar/Vender, o lo que sea que estemos clasificando.

Otro ejemplo son los populares juegos de adivinanza:

  1. ¿Animal ó vegetal? -Animal
  2. ¿Tiene cuatro patas? -Si
  3. ¿Hace guau? -Si
  4. -> Es un perro!

¿Qué necesidad hay de usar el Algoritmo de Arbol?

Supongamos que tenemos atributos como Género con valores “hombre ó mujer” y edad en rangos: “menor de 18 ó mayor de 18” para tomar una decisión. Podríamos crear un árbol en el que dividamos primero por género y luego subdividir por edad. Ó podría ser al revés: primero por edad y luego por género. El algoritmo es quien analizando los datos y las salidas -por eso es supervisado!– decidirá la mejor forma de hacer las divisiones (split) entre nodos. Tendrá en cuenta de qué manera lograr una predicción (clasificación ó regresión) con mayor probabilidad de acierto. Parece sencillo, no? Pensemos que si tenemos 10 atributos de entrada cada uno con 2 o más valores posibles, las combinaciones para decidir el mejor árbol serían cientos ó miles… Esto ya no es un trabajo para hacer artesanalmente. Y ahí es donde este algoritmo cobra importancia, pues él nos devolverá el árbol óptimo para la toma de decisión más acertada desde un punto de vista probabilístico.

¿Cómo funciona un árbol de decisión?

Para obtener el árbol óptimo y valorar cada subdivisión entre todos los árboles posibles y conseguir el nodo raiz y los subsiguientes, el algoritmo deberá medir de alguna manera las predicciones logradas y valorarlas para comparar de entre todas y obtener la mejor. Para medir y valorar, utiliza diversas funciones, siendo las más conocidas y usadas los “Indice gini” y “Ganancia de información” que utiliza la denominada “entropía“. La división de nodos continuará hasta que lleguemos a la profundidad máxima posible del árbol ó se limiten los nodos a una cantidad mínima de muestras en cada hoja. A continuación describiremos muy brevemente cada una de las estrategias nombradas:

Indice Gini:

Se utiliza para atributos con valores continuos (precio de una casa). Esta función de coste mide el “grado de impureza” de los nodos, es decir, cuán desordenados o mezclados quedan los nodos una vez divididos. Deberemos minimizar ese GINI index.

Ganancia de información:

Se utiliza para atributos categóricos (cómo en hombre/mujer). Este criterio intenta estimar la información que aporta cada atributo basado en la “teoría de la información“. Para medir la aleatoriedad de incertidumbre de un valor aleatorio de una variable “X” se define la Entropia.
Al obtener la medida de entropía de cada atributo, podemos calcular la ganancia de información del árbol. Deberemos maximizar esa ganancia.

Ejemplo de Arbol de Decisión con Python SKLearn paso a paso

Para este ejercicio me propuse crear un set de datos original e intentar que sea divertido a la vez que explique de forma clara el funcionamiento del árbol. Comencemos:

Requerimientos para hacer el Ejercicio

Para realizar este ejercicio, utilizaremos una Jupyter notebook con código python y la librería Scikit learn muy utilizada en Data Science. Recomendamos utilizar la suite de Anaconda. Si aún no la tienes, puedes leer este artículo donde muestra paso a paso como instalar el ambiente de desarrollo. Además podrás descargar los archivos de entrada csv o visualizar la notebook online (al final del artículo los enlaces).

Predicción del “Billboard 100”: ¿Qué artista llegará al número uno del ranking?

A partir de atributos de cantantes y de un histórico de canciones que alcanzaron entrar al Billboard 100 (U.S.) en 2013 y 2014 crearemos un árbol que nos permita intentar predecir si un nuevo cantante podrá llegar a número uno.

Obtención de los datos de entrada

Utilicé un código python para hacer scraping de una web pública “Ultimate Music Database” con información histórica del Billboard que obtuve de este artículo: “Analyzing billboard 100″. Luego completé atributos utilizando la API de Deezer (duración de las canciones), la API de Gracenote (género y ritmo de las canciones). Finalmente agregué a mano varias fechas de nacimiento de artistas utilizando la Wikipedia que no había conseguido con la Ultimate Music Database. Algunos artistas quedaron sin completar su fecha de nacimiento y con valor 0. Veremos como superar este obstáculo tratando los datos.

Para empezar importemos las librerías que utilizaremos y revisemos sus atributos de entrada:

Si te falta alguna de ellas, recuerda que puedes instalarla con el entorno Anaconda o con la herramienta Pip.

Análisis Exploratorio Inicial

Ahora veamos cuantas columnas y registros tenemos:

Esto nos devuelve (635,11) es decir que tenemos 11 columnas (features) y 635 filas de datos. Vamos a echar un ojo a los primeros registros para tener una mejor idea del contenido:

Vemos que tenemos: Titulo de la canción, artista, “mood” ó estado de ánimo de esa canción, tempo, género, Tipo de artista, fecha en que apareció en el billboard (por ejemplo 20140628 equivale al 28 de junio de 2014), la columna TOP será nuestra etiqueta, en la que aparece 1 si llegó al número uno de Billboard ó 0 si no lo alcanzó y el anio de Nacimiento del artista. Vemos que muchas de las columnas contienen información categórica. La columna durationSeg contiene la duración en segundos de la canción, siendo un valor continuo pero que nos convendrá pasar a categórico más adelante.

Vamos a realizar algunas visualizaciones para comprender mejor nuestros datos.

Primero, agrupemos registros para ver cuántos alcanzaron el número uno y cuantos no:

nos devuelve:
top
0 494
1 141

Es decir que tenemos 494 canciones que no alcanzaron la cima y a 141 que alcanzaron el número uno. Esto quiere decir que tenemos una cantidad DESBALANCEADA de etiquetas con 1 y 0. Lo tendremos en cuenta al momento de crear el árbol.

Visualizamos esta diferencia:

Nuestras etiquetas que indican 0-No llego al Top y 1-Llego al número uno Billboard están desbalanceadas. Deberemos resolver este inconveniente

Veamos cuántos registros hay de tipo de artista, “mood”, tempo y género de las canciones:

Aqui vemos que tenemos más del doble de artistas masculinos que femeninos y unos 100 registros de canciones mixtas
Vemos que de 23 tipos de Mood, destacan 7 con picos altos. Además notamos que algunos estados de ánimo son similares
En esta gráfica vemos que hay 3 tipos de Tempo: Medium, Slow y Fast. Evidentemente predominan los tiempos Medium y también es donde encontramos más canciones que hayan alcanzado el Top 1 (en azul)
Entre los géneros musicales destacan Urban y Pop, seguidos de Tradicional.

Veamos ahora que pasa al visualizar los años de nacimiento de los artistas:

Aqui notamos algo raro: en el año “cero” tenemos cerca de 140 registros…

Como se ve en la gráfica tenemos cerca de 140 canciones de las cuales desconocemos el año de nacimiento del artista. El resto de años parecen concentrarse entre 1979 y 1994 (a ojo). Más adelante trataremos estos registros.

Balanceo de Datos: Pocos artistas llegan al número uno

Como dijimos antes, no tenemos “equilibrio” en la cantidad de etiquetas top y “no-top” de las canciones. Esto se debe a que en el transcurso de un año, apenas unas 5 o 6 canciones logran el primer puesto y se mantienen durante varias semanas en ese puesto. Cuando inicialmente extraje las canciones, utilicé 2014 y 2015 y tenía apenas a 11 canciones en el top de Billboard y 494 que no llegaron.
Para intentar equilibrar los casos positivos agregué solamente los TOP de los años 2004 al 2013. Con eso conseguí los valores que tenemos en el archivo csv: son 494 “no-top” y 141 top. A pesar de esto sigue estando desbalanceado, y podríamos seguir agregando sólo canciones TOP de años previos, pero utilizaremos un parámetro del algoritmo de árbol de decisión para compensar esta diferencia.

Visualicemos los top y no top de acuerdo a sus fechas en los Charts:

En nuestro conjunto de Datos, se agregaron canciones que llegaron al top (en azul) de años 2004 al 2013 para sumar a los apenas 11 que lo habían logrado en 2014-2015.

Preparamos los datos

Vamos a arreglar el problema de los años de nacimiento que están en cero. Realmente el “feature” o característica que queremos obtener es : “sabiendo el año de nacimiento del cantante, calcular qué edad tenía al momento de aparecer en el Billboard”. Por ejemplo un artista que nació en 1982 y apareció en los charts en 2012, tenía 30 años.

Primero vamos a sustituir los ceros de la columna “anioNacimiento”por el valor None -que es es nulo en Python-.

Luego vamos a calcular las edades en una nueva columna “edad_en_billboard” restando el año de aparición (los 4 primeros caracteres de chart_date) al año de nacimiento. En las filas que estaba el año en None, tendremos como resultado edad None.

Y finalmente asignaremos edades aleatorias a los registros faltantes: para ello, obtenemos el promedio de edad de nuestro conjunto (avg) y su desvío estándar (std) -por eso necesitábamos las edades en None- y pedimos valores random a la función que van desde avg – std hasta avg + std. En nuestro caso son edades de entre 21 a 37 años.

Si bien lo ideal es contar con la información real, y de hecho la podemos obtener buscando en Wikipedia (o en otras webs de música), quise mostrar otra vía para poder completar datos faltantes manteniendo los promedios de edades que teníamos en nuestro conjunto de datos.

Podemos visualizar los valores que agregamos (en color verde) en el siguiente gráfico:

Mapeo de Datos

Vamos a transformar varios de los datos de entrada en valores categóricos. Las edades, las separamos en: menor de 21 años, entre 21 y 26, etc. las duraciones de canciones también, por ej. entre 150 y 180 segundos, etc. Para los estados de ánimo (mood) agrupé los que eran similares.

El Tempo que puede ser lento, medio o rápido queda mapeado: 0-Rapido, 1-Lento, 2-Medio (por cantidad de canciones en cada tempo: el Medio es el que más tiene)

Finalmente obtenemos un nuevo conjunto de datos llamado artists_encoded con el que tenemos los atributos definitivos para crear nuestro árbol. Para ello, quitamos todas las columnas que no necesitamos con “drop”:

Como quedan los top en relación a los datos mapeados

Revisemos en tablas cómo se reparten los top=1 en los diversos atributos mapeados. Sobre la columna sum, estarán los top, pues al ser valor 0 o 1, sólo se sumarán los que sí llegaron al número 1.

La mayoría de top 1 los vemos en los estados de ánimo 5 y 6 con 46 y 43 canciones
Aqui están bastante repartidos, pero hay mayoría en tipo 3: artistas masculinos
Los géneros con mayoría son evidentemente los géneros 3 y 4 que corresponden con Urbano y Pop
El tempo con más canciones exitosas en el número 1 es el 2, tempo medio
Están bastante repartidos en relación a la duración de las canciones
Edad con mayoría es la tipo 1 que comprende de 21 a 25 años.

Buscamos la profundidad para nuestro árbol de decisión

Ya casi tenemos nuestro árbol. Antes de crearlo, vamos a buscar cuántos niveles de profundidad le asignaremos. Para ello, aprovecharemos la función de KFold que nos ayudará a crear varios subgrupos con nuestros datos de entrada para validar y valorar los árboles con diversos niveles de profundidad. De entre ellos, escogeremos el de mejor resultado.

Creamos el árbol y lo tuneamos

Para crear el árbol utilizamos de la librería de sklearn tree.DecisionTreeClasifier pues buscamos un árbol de clasificación (no de Regresión). Lo configuramos con los parámetros:

  • criterion=entropy ó podría ser gini, pero utilizamos entradas categóricas
  • min_samples_split=20 se refiere a la cantidad mínima de muestras que debe tener un nodo para poder subdividir.
  • min_samples_leaf=5 cantidad mínima que puede tener una hoja final. Si tuviera menos, no se formaría esa hoja y “subiría” un nivel, su antecesor.
  • class_weight={1:3.5} IMPORTANTíSIMO: con esto compensamos los desbalances que hubiera. En nuestro caso, como venía diciendo anteriormente, tenemos menos etiquetas de tipo top=1 (los artistas que llegaron al número 1 del ranking). Por lo tanto, le asignamos 3.5 de peso a la etiqueta 1 para compensar. El valor sale de dividir la cantidad de top=0 (son 494) con los top=1 (son 141).

NOTA: estos valores asignados a los parámetros fueron puestos luego de prueba y error (muchas veces visualizando el árbol, en el siguiente paso y retrocediendo a este).

Podmeos ver que en 4 niveles de splits tenemos el score más alto, con casi 65%.

Ahora ya sólo nos queda crear y visualizar nuestro árbol de 4 niveles de profundidad.

Visualización del árbol de decisión

Asignamos los datos de entrada y los parámetros que configuramos anteriormente con 4 niveles de profundidad. Utilizaremos la función de export_graphviz para crear un archivo de extensión .dot que luego convertiremos en un gráfico png para visualizar el árbol.

Al fin nuestro preciado árbol aparece en pantalla!. Ahora tendremos que mirar y ver si lo podemos mejorar (por ejemplo tuneando los parámetros de entrada).

Conclusiones y análisis del árbol

En la gráfica vemos, un nodo raíz que hace una primer subdivisión por género y las salidas van a izquierda por True que sea menor a 2.5, es decir los géneros 0, 1 y 2 (eran los que menos top=1 tenían) y a derecha en False van los géneros 3 y 4 que eran Pop y Urban con gran cantidad de usuarios top Billboard.

En el segundo nivel vemos que la cantidad de muestras (samples) queda repartida en 232 y 403 respectivamente.

A medida que bajamos de nivel veremos que los valores de entropía se aproximan más a 1 cuando el nodo tiene más muestras top=1 (azul) y se acercan a 0 cuando hay mayoría de muestras Top=0 (naranja).

En los diversos niveles veremos divisiones por tipo de artista , edad, duración y mood. También vemos algunas hojas naranjas que finalizan antes de llegar al último nivel: esto es porque alcanzan un nivel de entropía cero, o porque quedan con una cantidad de muestras menor a nuestro mínimo permitido para hacer split (20).

Veamos cuál fue la precisión alcanzada por nuestro árbol:

Nos da un valor de 64.88%. Notamos en que casi todas las hojas finales del árbol tienen samples mezclados sobre todo en los de salida para clasificar los top=1. Esto hace que se reduzca el score.

Pongamos a prueba nuestro algoritmo

Predicción de Canciones al Billboard 100

Vamos a testear nuestro árbol con 2 artistas que entraron al billboard 100 en 2017: Camila Cabello que llegó al numero 1 con la Canción Havana y Imagine Dragons con su canción Believer que alcanzó un puesto 42 pero no llegó a la cima.

Nos da que Havana llegará al top 1 con una probabilidad del 83%. Nada mal…

Nos da que la canción de Imagine Dragons NO llegará con una certeza del 88%. Otro acierto.

Veamos los caminos tomados por cada una de las canciones:

Aqui vemos los caminos tomados por Havana en Rojo, que alcanzó el número 1 y el camino por Believer (en rosa) que no llegó.

Te atreves con un ejercicio de Aprendizaje No supervisado? Utiliza K-means con este ejemplo práctico

Conclusiones Finales

Pues hemos tenido un largo camino, para poder crear y generar nuestro árbol. Hemos revisado los datos de entrada, los hemos procesado, los pasamos a valores categóricos y generamos el árbol. Lo hemos puesto a prueba para validarlo.

Obtener un score de menos de 65% en el árbol no es un valor muy alto, pero tengamos en cuenta que nos pusimos una tarea bastante difícil de lograr: poder predecir al número 1 del Billboard y con un tamaño de muestras tan pequeño (635 registros). Ya quisieran las discográficas poder hacerlo 🙂

Espero que hayan disfrutado de este artículo y si encuentran errores, comentarios, o sugerencias para mejorarlo, siempre son bienvenidas. Además pueden escribirme si tienen problemas en intentaré responder a la brevedad.

Como siempre los invito a suscribirse al blog para seguir creciendo como comunidad de desarrolladores que estamos aprendiendo mediante ejemplos a crear algoritmos inteligentes.

Suscribirme al Blog

Recibir el próximo artículo quincenal sobre Machine Learning y prácticas en Python

Y si quieres aprender otros ejercicios en Python puedes hacer nuestros Ejercicios paso a paso de Regresión LinealRegresión Logística o de Aprendizaje no supervisado clustering K-means.

Recursos y enlaces del ejercicio

Otros enlaces con Artículos sobre Decisión Tree (en Inglés)

GuardarGuardarGuardarGuardar

24 Replies to “Arbol de Decisión en Python: Clasificación y predicción.”

  1. Muy bueno, te sigo constantemente con tus articulos, te felicito por tomarte el tiempo para compartir tu conocimiento

    1. Gracias Sebastian! Me alegra que me escribas y que sirvan mis artículos! Este en particular me llevó mucho más de lo que pensaba… Pues iba a ser un árbol “normal” y fueron apareciendo muchos obstáculos para alcanzar el objetivo. Pero creo que sirve ver como encarar los problemas que surgen al enfrentarse con los datos y como irlos resolviendo. Saludos y espero que sigamos en contacto!

  2. Tengo una pregunta, y disculpa mi ignorancia respecto al tema: para ambas predicciones el código es prácticamente el mismo…lo que cambia es x_test.loc[0]…ahora mi pregunta es como x_test.loc[0] = (1,5,2,4,1,0,3) se traduce en “#predecir artista CAMILA CABELLO featuring YOUNG THUG

    con su canción Havana llego a numero 1 Billboard US en 2017”

    1. Hola Robert, gracias por escribir. Te cuento que los valores salen de los valores reales de la canción de Camila Cabello obtenidos con las APIs de música (Gracenote) y luego mapeados a valores discretos. Por ejemplo la edad era menor a 21 años al momento de publicar la canción y se mapea con un 0

        1. Me alegro mucho! Espero que te sirva y cualquier cosa me escribes. Puede que tarde en responder a veces (pues tengo 4 hijos pequeños…) Pero en cuanto pueda, lo haré

          1. Creo que ya lo entendí…la canción havana y la believer no están en el dataset original que se uso para entrenar el modelo…lo que hiciste fue consultar la api de gracenote para la canción habana que te retorno los valores 1,5,2,4,1,0,3, para ‘top’,’moodEncoded’, ‘tempoEncoded’, ‘genreEncoded’,’artist_typeEncoded’,’edadEncoded’,’durationEncoded’….armas tu dataframe de test(x_test) y luego con esa data le preguntas al modelo sobre la probabilidad de que llegue al número 1…tengo que cambiar el mindset, espero mi background como desarrollador me ayude, esto se ve muy entretenido!. Gracias de nuevo por tu tiempo.

    1. Pues lo que estamos haciendo es crear una tabla de Pandas. Al declararla enumeramos las columnas (top, mood, tempo,etc) y con test.loc[0] lo que decimos es que inserte esos valores a la primer fila (su índice es cero)

      1. Ya lo entendí, la respuesta de gracenote api + el mapeo que usas me debiese dar los valores, por ejemplo edadEncoded <= 21 se mapea a cero. Muchas gracias!!

  3. Hola mi nombre es Wilma y estaba viendo si el codigo funcionaba y me tope con que el archivo tree1.dot no existe, hay que generarlo o este archivo se debe crear?

    1. Hola Wilma, disculpa la tardanza en responder. El archivo tree.dot se genera al ejecutar el código. Y luego se utiliza para generar el gráfico PNG (o jpg).
      Gracias por comentar y espero sigas visitando el blog!

  4. Hola. Mi nombre es Jaime. Intento hacer el ejercicio completo pero no encuentro el archivo donde estan los 6 mil y tantos registros de los artistas. Me puedes apoyar en publicarlo para que lo pueda bajar y seguir tu ejercicio please?

  5. Hola. Soy Jaime. Apenas empiezo en este mundo de Python y ML. Me pueden explicar para que sirve la linea %matplotlib inline. Ya que cuando la corro en el prompt de Python me marca error (invalid sintax). Pero si la corro en Jupiter no me marca error.

  6. Hola Juan Ignacio.
    Estoy intentando que me aparezca la imagen del árbol y no hay manera.
    El archivo tree1.dot lo tengo ubicado en la misma carpeta donde se encuentra el artists_billboard.csv y el archivo .ipynb de Jupyter.

    FileNotFoundError Traceback (most recent call last)
    in ()
    23
    24 # Convertir el archivo .dot a png para poder visualizarlo
    —> 25 check_call([‘dot’,’-Tpng’,r’tree1.dot’,’-o’,r’tree1.png’])
    26 PImage(“tree1.png”)

    FileNotFoundError: [WinError 2] El sistema no puede encontrar el archivo especificado

    ¿Me podrías decir donde ubicar el tree1.dot?

    Gracias por tu gran blog!

    1. Hola Pedro, para poder ayudarte, dime si estás usando Anaconda u otra suite ó Python 3.6 desde línea de comandos.
      A su vez, comprueba si tienes instalado el modulo PIL que es el que convierte de tree.dot a imagen.
      Si no lo tienes puedes instalarlo via:

      • pip install pil (si no tienes anaconda)
      • conda install -c anaconda pil (Así teniendo Anaconda desde linea de comandos ó tb puedes hacerlo desde la interfaz gráfica

      Por último, imagino que tienes permisos de escritura en el directorio donde estás ejecutando el código, no? pues tanto el archivo dot como el png se generan “on the fly” y el programa necesita poder escribir en esos directorios.
      Saludos, dime si se corrige o me comentas si da otro error

      1. Gracias Nacho por tu rápida respuesta.
        Tengo permisos de administrador.
        Tengo anaconda y la versión de Python es la 3.5.5

        Al intentar instalar el pàquete PIL da este error:

        (py35) C:\Users\Administrador>conda install -c anaconda pil
        Solving environment: failed

        UnsatisfiableError: The following specifications were found to be in conflict:
        – pil
        – tensorboard
        Use “conda info ” to see the dependencies for each package.

        1. Prueba instalar con la interfaz gráfica:

              Abre Anaconda Navigator
              Environments
              Marca “ALL” en el combobox
              En Search Package escribes “pip”
              Del listado marca donde la columna Name ponga “pil”
              Le das a Apply

          Con eso te lo debería descargar e instalar.

Leave a Reply