clasificación | Aprende Machine Learning https://www.aprendemachinelearning.com en Español Thu, 15 Dec 2022 09:00:15 +0000 en-US hourly 1 https://wordpress.org/?v=6.4.3 https://www.aprendemachinelearning.com/wp-content/uploads/2017/11/cropped-icon_red_neuronal-1-32x32.png clasificación | Aprende Machine Learning https://www.aprendemachinelearning.com 32 32 134671790 Modelos de Detección de Objetos https://www.aprendemachinelearning.com/modelos-de-deteccion-de-objetos/ https://www.aprendemachinelearning.com/modelos-de-deteccion-de-objetos/#respond Fri, 21 Aug 2020 08:00:00 +0000 https://www.aprendemachinelearning.com/?p=6747 Luego de haber hecho un ejercicio Práctico de Detección de objetos en imágenes por medio de redes neuronales, veremos la teoría que hay detrás de estos algoritmos. Para comprender el artículo doy por sentado que ya tienes conocimiento de cómo funcionan las redes neuronales y...

The post Modelos de Detección de Objetos first appeared on Aprende Machine Learning.

]]>
Luego de haber hecho un ejercicio Práctico de Detección de objetos en imágenes por medio de redes neuronales, veremos la teoría que hay detrás de estos algoritmos.

Para comprender el artículo doy por sentado que ya tienes conocimiento de cómo funcionan las redes neuronales y de la teoría de Clasificación de imágenes. Si no, te recomiendo que leas primero esos artículos.

Agenda

  • Introducción: ¿Qué es la detección de imágenes?
  • Primera intuición de detección a partir de la clasificación con CNN
  • R-CNN: búsqueda selectiva
    • ¿Cómo funciona R-Cnn?
  • Problemas y mejoras: fast y faster r-cnn
  • Detección Rápida: YOLO
    • ¿Cómo funciona YOLO?
    • Arquitectura de la red Darknet
  • Otras alternativas
    • 2016 – Single Shot Detection
    • 2018 – RetinaNet
    • 2019 – Google Spinet
    • 2020 – Facebook saca del horno DETR
  • Resumen

Introducción: ¿Qué es la detección de imágenes?

Podemos tener la errónea intuición de que la detección de imágenes sea una tarea sencilla, pero veremos que realmente no lo es y de hecho es un gran problema a resolver. Nosotros los humanos podemos ver una foto y reconocer inmediatamente cualquier objeto que contenga de un vistazo rápido, si hay objetos pequeños o grandes, si la foto es oscura ó hasta algo borrosa. Imaginemos un niño escondido detrás de un árbol donde apenas sobresale un poco su cabeza ó un pie.

Para la detección de imágenes mediante Algoritmos de Machine Learning esto implica una red neuronal convolucional que detecte una cantidad limitada (ó específica) de objetos, no pudiendo detectar objetos que antes no hubiera visto, ó si están en tamaños que logra discernir y todas las dificultades de posibles “focos”, rotación del objeto, sombras y poder determinar en qué posición -dentro de la imagen- se encuentra.

Si es difícil con 1 objeto… imagínate con muchos!.

¿En qué consiste la detección de objetos?

Un algoritmo de Machine Learning de detección, para considerarse como tal deberá:

  • Detectar multiples objetos.
  • dar la posición X e Y del objeto en la imagen (o su centro) y dibujar un rectángulo a su alrededor.
  • Otra alternativa es la segmentación de imágenes (no profundizaremos en este artículo).
  • Detectar “a tiempo”… o puede que no sirva el resultado. Esta es una característica que debemos tener en cuenta si por ejemplo queremos hacer detección en tiempo real sobre video.

Nueva Salida

Entonces para entrenar nuestra máquina de manera supervisada deberemos indicar la clase del objeto (por ejemplo perro ó gato) y además la posición dentro de la imagen, X, Y el ancho y alto del objeto.

Y por si esto fuera poco, podrían ser múltiples objetos en la misma imagen, con lo cual para detectar 2 perros en una foto, necesitamos como salida 10 neuronas.

Este es un gran cambio, pues en clasificación de imágenes veníamos acostumbrados a devolver un array con por ejemplo Perro = [1 0] y Gato = [0 1].

La nueva salida deberá contener adicionalmente la posición (por ej. 54,45) y dimensión (por ej. 100,100) de cada clase, resultando en algo mínimo como

  • [1 0 100 100 54 45] pudiendo detectar sólo 1 objeto ó
  • [1 0 100 100 54 45 0 1 200 200 30 25] para 2 objetos.

Primera intuición: detección a partir de la clasificación

Podemos partir de este punto: tenemos una red CNN entrenada para detectar perros y gatos y supongamos que tiene una muy buena taza de aciertos. A esta red le pasamos una imagen nueva y nos devuelve “perro” ó “gato”. Agregaremos una tercera salida “otros” por si le pasamos la foto de algo que no sepa reconocer .

Entre las redes CNN pre-entregadas más conocidas están Alexnet, Resnet, y VGG

Si a nuestra red pre-entrenada, le pasamos una imagen con 2 perros será incapaz de detectarlos, puede que no detecte ni siquiera a uno.

Si le pasamos una imagen con perros y gatos, tampoco los podrá identificar y mucho menos localizar.

Entonces lo que el “sentido común de ingenieros” nos dice es: “vamos a iterar”. Es decir, iteremos un “área reducida” dentro de la foto de izquierda a derecha y de arriba abajo y le aplicamos la CNN pre-entrenada para ver si detecta algo.

Al ir iterando, lograremos detectar los 2 animales de la foto.

La foto original
El resultado deseado
Comenzamos a iterar…
Perro detectado
Otro tamaño de bounding-box…
iteramos de izq-der, arriba abajo…
Gato detectado!

Sin embargo esta solución trae consigo múltiples inconvenientes:

  1. ¿De qué tamaño será la ventana deslizante? y de hecho, podría ser de diversos tamaños.
  2. ¿Cuántos píxeles nos moveremos hacia izquierda (y luego hacia abajo)?
  3. Dependiendo de esos factores, el tiempo de cómputo podría ser muy largo, pues para cada movimiento implica realizar una clasificación individual con la CNN.
  4. Si detectamos algún objeto dentro de la ventana, ¿quiere decir que tengo los valores x e y? No necesariamente.
  5. Si nos movemos apenas pixeles con la ventana, podemos estar detectando al “mismo perro” múltiples veces
  6. Surge una problemática de poder distinguir entre animales si estos se encuentran muy cercanos.
Podemos tener dos cajas que detectan al mismo perro.
Esta detección es correcta, 2 perros: pero podría ocurrir…
…detectar por error a 2 perros dentro de una misma caja

De los puntos 5 y 6 surge la necesidad de crear una nueva métrica específica para la detección de imágenes en donde podamos evaluar al mismo tiempo si la clase de objeto es correcta y si la posición del “bounding box” (X,Y, alto y ancho) es buena. Esa métrica será “mAP“.

A raíz de estos puntos, surgen estrategias para intentar solventarlos. Veamos algunas.

R-CNN: búsqueda selectiva

En 2014 surgen las “Region Based Convolutional Neural Networks” con la siguiente propuesta: primero determinar “regiones de interés” dentro de la imagen (esto es conocido como “selective search”) y luego realizar clasificación de imágenes sobre esas áreas usando una red pre-entrenada.

Esto implica un primer algoritmo sobre la imágen que pueda determinar las áreas de interés que pueden llegar a ser 2000 regiones de diversos tamaños (si había más, se descartan). Luego pasar esas regiones por la CNN y mediante un clasificador binario validar si eran de clases correctas y eliminar las de poca confianza. Finalmente un regresor se encargaría de ajustar correctamente la posición de la localización.

La selección de las regiones podría ser por ejemplo “áreas contiguas con un mismo tono de color” ó detección de líneas que delimiten áreas, ó cambios bruscos en contraste y brillo. Son pasadas “rápidas” sobre una imagen, similar a como lo hace un editor de imágenes.

Fuente:  https://arxiv.org/abs/1311.2524

Para evitar el solapamiento del mismo objeto en diversas áreas se utiliza el concepto de IoU ó “Intersection over Union”.

IoU: nos da un porcentaje de acierto del área de predicción frente a la bounding-box real que queríamos detectar.

El IoU en conjunto con “Non-Máximum-Supression” ayudan a seleccionar las áreas del objeto que queremos localizar.

NMS: nos permite quedarnos de entre muchas cajas que detectaron al mismo objeto y se superponen, con la que mejor se ajusta al resultado. Nos quedamos con la mejor y eliminamos al resto.

A pesar de todas estas mejoras, la detección de objetos sobre una sola imagen podía tomar unas 25 segundos. Y el entrenamiento de la propia red es muy lento.

Mejoras sobre R-CNN: fast y faster R-cnn

Surgen otros 2 algoritmos: fast R-CNN y luego faster R-CNN para intentar mejorar el tiempo de detección.

Fast R-CNN mejora el algoritmo inicial haciendo reutilización de algunos recursos como el de las features extraídas por la CNN agilizando el entreno y detección de las imágenes. Esta nueva red tiene mejoras también en el IOU y en la función de Loss para mejorar el posicionamiento de la “caja delimitante”. Sin embargo no ofrece un aumento dramático de velocidad en el entrenamiento y detección.

Faster R-CNN logra una mejora en velocidad al integrar el algoritmo de “región proposal” sobre la propia CNN. Además aparece el concepto de usar “anchor” fijos, es decir, ciertos tamaños pre calculados para la detección de objetos específicos de la red. Por ejemplo, podemos definir 3 tamaños de ventana en 3 escalas distintas de tamaños, es decir un total de 9 anclas.

Faster-R-CNN. Fuente https://arxiv.org/abs/1506.01497

Mask R-CNN

No entraré en detalle, esta red, intenta hacer uso de las R-CNN pero en vez de detectar el “bounding box” de cada objeto, intentará hacer segmentación de imagen, definiendo la superficie de cada objeto.

Fuente: https://arxiv.org/abs/1703.06870

Detección Rápida: YOLO

En 2016 crean YOLO, una red que quiere decir “You Only Look Once“. Esta red hace una única pasada a la red convolucional y detecta todos los objetos para los que ha sido entrenada para clasificar. Al ser un “sólo cálculo” y sin necesidad de iterar, logra velocidades nunca antes alcanzadas con ordenadores que no tienen que ser tan potentes. Esto permite detección sobre video en tiempo real de cientos de objetos en simultáneo y hasta su ejecución en dispositivos móviles.

¿Cómo funciona YOLO ?

Yolo es una solución que reutiliza varias técnicas que vimos anteriormente con un “twist-plot” final.

Yolo define una grilla de tamaño fijo sobre la imagen de 13×13. Sobre esas celdas intentará detectar objetos valiéndose de anchors fijos, por ejemplo de 3 anclas con 3 tamaños distintos (9 predicciones por cada celda). Hace uso de IoU y Non-Max-supression. También tiene asociada una red de regresión al final para las posiciones de los bounding-boxes.

Yolo utiliza una grilla fija, en este caso de 13×13
Aqui vemos ejemplo de 5 anclas de distintos tamaños

La “grandiosidad” de YOLO consiste en su red CNN. Antes vimos que R-CNN utilizaba algún algoritmo adicional para seleccionar las regiones de interés sobre las que realiza las predicciones. En cambio YOLO, utiliza la misma Red CNN de clasificación con un “truco” por el cual no necesita iterar la grilla de 13×13, si no que la propia red se comporta como si hiciera un especie de “offset” que le permite hacer la detección en simultáneo de las 169 casillas.

YOLO utiliza una red CNN llamada Darknet, aunque también puede ser entrenada con cualquier otra red Convolucional. Al mismo tiempo de entrenarse se crea la red con este <<offset>> que comentaba.

Este video te ayudará comprender el funcionamiento de YOLO, explicado nada más y nada menos que por Andrew Ng.

Además Yolo utiliza las neuronas de tipo convolucional al final de la cadena sin necesidad de hacer la transformación a una red “tradicional”.

Gracias a estos retoques, logra la sorprendente capacidad de casi 60 FPS (cuadros por segundo) en ordenadores normales. Se le critica que si bien es rápida, suele tener menor porcentaje de aciertos frente a las R-CNN.

Pero con el paso del tiempo fueron evolucionando las versiones YoloV2, V3 y recientemente V4 que están enfocadas a mejorar esa precisión de las bounding boxes, a la vez que mantienen su rapidez.

Resultados de YOLOv3 sobre el Dataset COCO.

Arquitectura de la Red

La arquitectura se basa en una red convolucional GoogleNet y consta de 24 capas convolucionales. El autor la bautizó como Darknet. Embebe en su salida tanto la parte que clasifica las imágenes como la de posicionamiento y tamaño de los objetos.

Por ejemplo par el CocoDataset que debe detectar 80 objetos diferentes, tendremos como salida:

Tamaño de grillaCantidad AnclasCantidad de clases Ccore, X, Y, Alto, Ancho
13 * 13* 3 * (80 +* 5)

Para este ejemplo nos dará un array de 43.095 datos siendo el máximo de objetos que puede detectar y localizar 13x13x3 = 507 objetos de 80 clases en la misma foto en una sola pasada. (Realmente hará 13x13x3 x3 tamaños = 1521 predicciones). Sorprendente!.

Crea tu propia red de detección de objetos YOLO siguiendo este ejercicio explicado paso a paso y con todo el código Python en una Jupyter Notebook usando Keras y Tensorflow

Otras Alternativas para Detección

Comentaremos brevemente otras técnicas que surgieron y que también se pueden utilizar.

SSD – Single Shot Detector

Tiene una estructura piramidal en su CNN en la que las capas van disminuyendo gradualmente. Esto le permite poder detectar objetos grandes y pequeños. No utiliza una grilla predefinida, pero cuenta con “anclas” de distintas proporciones que se van escalando a medida que descendemos por la pirámide (mapa de features más pequeños, con anclas proporcionalmente más grandes).

RetinaNet (2018)

RetinaNet también se basa en una estructura de CNN piramidal mejorada para reconocer objetos de diversos tamaños en una sola pasada. Innova con una nueva función de pérdida llamada <<Focal Loss>>.

Google: Spinet (dic 2019)

Google Spinet rompe con la estructura piramidal y propone una arquitectura novedosa llamada “scale-permuted” en la que se alternan diversos tamaños en las convoluciones.

Facebook: DETR (junio 2020)

Facebook propone una “End to End object detection with Transformers“. Es decir, utilizar la más novedosa y efectiva técnica de redes neuronales utilizada en NLP pero aplicada a la detección de imágenes! Muy ingenioso!

Resumen

La tarea de Detección de objetos en imágenes fue impulsora de mejora tanto en redes neuronales convolucionales como en la arquitectura general utilizada poniendo a prueba el valor real del deeplearning, entrelazando redes con funciones específicas.

Los logros obtenidos son enormes, de gran aplicación y como vemos sigue siendo un campo en desarrollo, en donde grandes como Google y Facebook siguen innovando con nuevas propuestas, aún con un mundo bajo Pandemia.

Las aplicaciones que tiene la detección de imágenes van desde seguridad, conducción de coches autónomos hasta salud y poder dar visión -al fin- a los robots 😉

Si te suscribes salvas un gatito (o no)

Recibe los próximos artículos sobre Machine Learning, estrategias, teoría y código Python en tu casilla de correo!

NOTA: algunos usuarios reportaron que el email de confirmación y/o posteriores a la suscripción entraron en su carpeta de SPAM. Te sugiero que revises y recomiendo que agregues nuestro remitente info @ aprendemachinelearning.com a tus contactos para evitar problemas. Gracias!

Aún no realizaste el ejercicio práctico de detección de objetos con Python, Keras y Tensorflow? Anímate!

El libro del Blog

Si te gustan los contenidos del blog puedes comprar el libro en papel ó en

formato digital (el precio lo pones tú!)…

The post Modelos de Detección de Objetos first appeared on Aprende Machine Learning.

]]>
https://www.aprendemachinelearning.com/modelos-de-deteccion-de-objetos/feed/ 0 6747
Sets de Entrenamiento, Test y Validación https://www.aprendemachinelearning.com/sets-de-entrenamiento-test-validacion-cruzada/ https://www.aprendemachinelearning.com/sets-de-entrenamiento-test-validacion-cruzada/#comments Tue, 03 Mar 2020 08:00:00 +0000 https://www.aprendemachinelearning.com/?p=7152 Vamos a comentar las diferencias entre los conjuntos de Entrenamiento, Validación y Test utilizados en Machine Learning ya que suele haber bastante confusión en para qué es cada uno y cómo utilizarlos adecuadamente. Intentaré hacerlo mediante un ejemplo práctico por eso de ser didácticos 🙂...

The post Sets de Entrenamiento, Test y Validación first appeared on Aprende Machine Learning.

]]>
Vamos a comentar las diferencias entre los conjuntos de Entrenamiento, Validación y Test utilizados en Machine Learning ya que suele haber bastante confusión en para qué es cada uno y cómo utilizarlos adecuadamente.

Intentaré hacerlo mediante un ejemplo práctico por eso de ser didácticos 🙂

Además veremos que tenemos distintas técnicas de hacer la validación del modelo y aplicarlas con Scikit Learn en Python.

Un nuevo Mundo

Al principio de los tiempos, sólo tenemos un conjunto Pangea que contiene todo nuestro dato disponible. Digamos que tenemos un archivo csv con 10.000 registros.

Para entrenar nuestro modelo de Machine Learning y poder saber si está funcionando bien, alguien dijo: Separemos el conjunto de datos inicial en 2: conjunto de entrenamiento (train) y conjunto de Pruebas (test). Por lo general se divide haciendo “80-20”. Y se toman muestras aleatorias -no en secuencia, si no, mezclado.

Para hacer el ejemplo sencillo, supongamos que queremos hacer clasificación usando un algoritmo supervisado, con lo cual tendremos:

  • X_train con 8.000 registros para entrenar
  • y_train con las “etiquetas” de los resultados esperados de X_train
  • X_test con 2.000 registros para test
  • y_test con las “etiquetas” de los resultados de X_test

Hágase el conjunto de Test

Lo interesante y a destacar de esto es que una vez los separamos en 8.000 registros para entrenar y 2.000 para probar, usaremos sólo esos 8.000 registros para alimentar al modelo al entrenarlo haciendo:

modelo.fit(X_train, y_train)

Luego de entrenar nuestro modelo y habiendo decidido como métrica de negocio el Accuracy (el % de aciertos) obtenemos un 75% sobre el set de entrenamiento (y asumimos que ese porcentaje nos sirve para nuestro objetivo de negocio).

Los 2.000 registros que separamos en X_test aún nunca han pasado por el modelo de ML. ¿Se entiende esto? porque eso es muy importante!!! Cuando usemos el set de test, haremos:

modelo.predict(X_test)

Como verás, no estamos usando fit()!!! sólo pasaremos los datos sin la columna de “y_test” que contiene las etiquetas. Además remarco que estamos haciendo predicción; me refiero a que el modelo NO se está entrenando ni <<incorporando conocimiento>>. El modelo se limita a “ver la entrada y escupir una salida”.

Cuando hacemos el predict() sobre el conjunto de test y obtenemos las predicciones, las podemos comprobar y contrastar con los valores reales almacenados en y_test y hallar así la métrica que usamos. Los resultados que nos puede dar serán:

  1. Si el accuracy en Test es <<cercano>> al de Entrenamiento (dijimos 75%) por ejemplo en este caso si estuviera entre 65 ú 85% quiere decir que nuestro modelo entrenado está generalizando bien y lo podemos dar por bueno (siempre y cuando estemos conformes con las métricas obtenidas).
  2. Si el Accuracy en Test es muy distinto al de Entrenamiento tanto por encima como por debajo, nos da un 99% ó un 25% (lejano al 75%) entonces es un indicador de que nuestro modelo no ha entrenado bien y no nos sirve. De hecho este podría ser un indicador de Overfitting.

Para evaluar mejor el segundo caso, es donde aparece el “conjunto de Validación”.

Al Séptimo día Dios creo el Cross-Validation

Si el conjunto de Train y Test nos está dando métricas muy distintas esto es que el modelo no nos sirve.

Para mejorar el modelo, podemos pensar en Tunear sus parámetros y volver a entrenar y probar, podemos intentar obtener más registros, cambiar el preprocesado de datos, limpieza, balanceo de clases, selección de features, generación de features… De hecho, podemos pensar que seleccionamos un mal modelo, y podemos intentar con distintos modelos: de árbol de decisión, redes neuronales, ensambles

La técnica de Validación Cruzada nos ayudará a medir el comportamiento el/los modelos que creamos y nos ayudará a encontrar un mejor modelo rápidamente.

Aclaremos antes de empezar: hasta ahora contamos con 2 conjuntos: el de Train y Test. El “set de validación” no es realmente un tercer set si no que “vive” dentro del conjunto de Train. Reitero: el set de validación no es un conjunto que apartemos de nuestro archivo csv original. El set de validación se utilizará durante iteraciones que haremos con el conjunto de entrenamiento.

Técnicas de Validación Cruzada

Entonces volvamos a tener las cosas claras: SOLO tenemos conjunto de Train y Test, ok?. El de Test seguirá tratándose como antes: lo apartamos y lo usaremos al final, una vez entrenemos el modelo.

Dentro del conjunto de Train, y siguiendo nuestro ejemplo inicial, tenemos 8.000 registros. La validación más común utilizada y que nos sirve para entender el concepto es “K-folds”, vamos a comentarla:

Cross-Validation: K-fold con 5 splits

Lo que hacemos normalmente al entrenar el modelo es pasarle los 8.000 registros y que haga el fit(). Con K-Folds -en este ejemplo de 5 splits- para entrenar, en vez de pasarle todos los registros directamente al modelo, haremos así:

  • Iterar 5 veces:
    1. Apartaremos 1/5 de muestras, es decir 1600.
    2. Entrenamos al modelo con el restante 4/5 de muestras = 6400.
    3. Mediremos el accuracy obtenido sobre las 1600 que habíamos apartado.
  • Esto quiere decir que hacemos 5 entrenamientos independientes.
  • El Accuracy final será el promedio de las 5 accuracies anteriores.
En amarillo las muestras para entrenar y en verde el conjunto de Validación.

Entonces fijémonos que estamos “ocultando” una quinta parte del conjunto de train durante cada iteración. Esto es similar a lo que explique antes, pero esta vez aplicado al momento de entrenamiento. Al cabo de esas 5 iteraciones, obtenemos 5 accuracies que deberían ser “similares” entre sí, esto sería un indicador de que el modelo está funcionando bien.

Ejemplo K-Folds en Python

Veamos en código python usando la librería de data science scikit-learn como podemos hacer el cross-validation con K-Folds:

from sklearn import datasets, metrics
from sklearn.model_selection import train_test_split
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import KFold
from sklearn.linear_model import LogisticRegression

iris = datasets.load_iris()

X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.2, random_state=0)

kf = KFold(n_splits=5)

clf = LogisticRegression()

clf.fit(X_train, y_train)

score = clf.score(X_train,y_train)

print("Metrica del modelo", score)

scores = cross_val_score(clf, X_train, y_train, cv=kf, scoring="accuracy")

print("Metricas cross_validation", scores)

print("Media de cross_validation", scores.mean())

preds = clf.predict(X_test)

score_pred = metrics.accuracy_score(y_test, preds)

print("Metrica en Test", score_pred)

En el ejemplo vemos los pasos descritos anteriormente:

  • Cargar el dataset
  • Dividir en Train y Test (en 80/20)
  • Creamos un modelo de Regresión Logística (podría ser otro) y lo entrenamos con los datos de Train
  • Hacemos Cross-Validation usando K-folds con 5 splits
  • Comparamos los resultados obtenidos en el modelo inicial, en el cross validation y vemos que son similares.
  • Finalmente hacemos predict sobre el Conjunto de Test y veremos que también obtenemos buen Accuracy

Más técnicas para Validación del modelo

Otras técnicas usadas y que nos provee sklearn para python son:

Stratified K-Fold

Statified K-fold es una variante mejorada de K-fold, que cuando hace los splits (las divisiones) del conjunto de train tiene en cuenta mantener equilibradas las clases. Esto es muy útil, porque imaginen que tenemos que clasificar en “SI/NO” y si una de las iteraciones del K-fold normal tuviera muestras con etiquetas sólo “SI” el modelo no podría aprender a generalizar y aprenderá para cualquier input a responder “SI”. Esto lo soluciona el Stratified K-fold.

Leave P Out

Leave P Out selecciona una cantidad P por ejemplo 100. Entonces se separarán de a 100 muestras contra las cuales validar y se iterará como se explico anteriormente. Si el valor P es pequeño, esto resultará en muchísimas iteraciones de entrenamiento con un alto coste computacional (y seguramente en tiempo). Si el valor P es muy grande, podría contener más muestras que las usadas para entrenamiento, lo cual sería absurdo. Usar esta técnica con algo de sentido común y manteniendo un equilibrio entre los scores y el tiempo de entreno.

ShuffleSplit

ShuffleSplit primero mezcla los datos y nos deja indicar la cantidad de splits (divisiones) es decir las iteraciones independientes que haremos y también indicar el tamaño del set de validación.

Instala tu ambiente de Desarrollo Python siguiendo esta guía paso a paso

Series Temporales: Atención al validar

Para problemas de Series temporales tenemos que prestar especial cuidado con los datos. Pues si pasamos al modelo “dato futuro” antes de tiempo estaríamos haciendo Data Leakage, esto es como si le hiciéramos spoiler al modelo y le contaremos el final de la película antes de que la vea. Esto causaría overfitting.

Para empezar al hacer el split inicial de datos estos deberán estar ordenados por fecha y no podemos mezclarlos.

Para ayudarnos con el cross-validation sklearn nos provee de TimeSeriesSplit.

TimeSeriesSplit

TimeSeriesSplit es una variante adaptada de K-folds que evita “la fuga” de datos. Para hacerlo va iterando los “folds” de a uno (usando una ventana de tiempo que se desplaza) y usando el “fold más reciente” cómo el set de validación. Se puede entender mejor viendo una animación:

En Amarillo las muestras para entrenar y en verde el conjunto de Validación.

Practicar con un ejercicio en Python de Series Temporales!

Pero entonces? Cuando uso Cross-Validation?

Es una buena práctica usar cross-validation en nuestros proyectos. De hecho usarlo nos ayudará a elegir el modelo correcto y nos da mayor seguridad y respaldo ante nuestra decisión.

PERO… (siempre hay un pero)

En casos en los que hacer 1 sólo entrenamiento “normal” tome muchísimo tiempo y recursos, podría ser nuestra perdición. Imaginen que hacer un k-folds de 10 implica hacer 10 entrenos -aunque un poco más pequeños-, pero que consumirían mucho tiempo y dinero.

Entonces en la medida de lo posible siempre usar validación cruzada. Y -vuelvo a reforzar el concepto- luego se probará el modelo contra el conjunto de Pruebas (test).

Para hacer tuneo de Hiper-parámetros como RandomSearch, GridSearch ó Tuneo Bayesiano es muy útil hacer Cross-Validation.

¿Si ya estoy “conforme” y quiero llevar el modelo a un entorno de Producción?

Supongamos que el entrenamiento haciendo Cross Validation y el predict() en Test nos están dando buenos accuracy (y similares) y estamos conformes con nuestro modelo. PUES si lo queremos usar en un entorno REAL y productivo, ANTES de publicarlo es recomendado que agreguemos el conjunto de test al modelo!!!, pues así estaremos aprovechando el 100% de nuestros datos. Espero que esto último también se entienda porque es super importante: lo que estoy diciendo es que si al final de todas nuestras iteraciones, pre procesado de dato, mejoras de modelo, ajuste de hiper-parámetros y comparando con el conjunto de test, estamos seguros que el modelo funciona correctamente, es entonces ahora, que usaremos las 10.000 muestras para entrenar al modelo, y ese modelo final, será el que publicamos en producción.

Es una última iteración que debería mejorar el modelo final aunque este no lo podemos contrastar contra nada… excepto con su comportamiento en el entorno real.

Si esta última iteración te causara dudas, no la hagas, excepto que tu problema sea de tipo Serie Temporal. En ese caso sí que es muy importante hacerlo o quedaremos con un modelo que no “es el más actual”.

Resumen, Conclusiones y por favor Que quede claro!

Lo más importante que quisiera que quede claro es que entonces tenemos 2 conjuntos: uno de Train y otro de Test. El “conjunto de validación” no existe como tal, si no, que “vive temporalmente” al momento de entrenar y nos ayuda a obtener al mejor modelo de entre los distintos que probaremos para conseguir nuestro objetivo. Esa técnica es lo que se llama Validación Cruzada ó en inglés cross-validation.

NOTA: en los ejemplos de la documentación de sklearn podremos ver que usan las palabras train y test. Pero conceptualmente se está refiriendo al conjunto de validación y no al de Test que usaremos al final. Esto es en parte el causante de tanta confusión con este tema.

Tener en cuenta el tamaño de split 80/20 es el usual pero puede ser distinto, y esta proporción puede cambiar sustancialmente las métricas obtenidas del modelo entrenado! Ojo con eso. El tamaño ideal dependerá del dominio de nuestro problema, deberemos pensar en una cantidad de muestras para test que nos aseguren que estamos el modelo creado está funcionando correctamente. Teniendo 10.000 registros puede que con testear 1000 filas ya estemos conformes ó que necesitemos 4000 para estar mega-seguros. Por supuesto debemos recordar que las filas que estemos “quitando” para testear, no las estamos usando al entrenar.

Otro factor: al hacer el experimento y tomar las muestras mezcladas, mantener la “semilla” ó no podremos reproducir el mismo experimento para comparar y ver si mejora o no. Este suele ser un parámetro llamado “random_state” y está bien que lo usemos para fijarlo.

Recomendaciones finales:

  • En principio separar Train y Test en una proporción de 80/20
  • Hacer Cross Validation siempre que podamos:
    • No usar K-folds. Usar Stratified-K-folds en su lugar.
    • La cantidad de “folds” dependerá del tamaño del dataset que tengamos, pero la cantidad usual es 5 (pues es similar al 80-20 que hacemos con train/test).
    • Para problemas de tipo time-series usar TimeSeriesSplit
  • Si el Accuracy (ó métrica que usamos) es similar en los conjuntos de Train (donde hicimos Cross Validation) y Test, podemos dar por bueno al modelo.

Recibe los artículos del Blog

Recibe los próximos artículos sobre Machine Learning, estrategias, teoría y código Python en tu casilla de correo!

NOTA: algunos usuarios reportaron que el email de confirmación y/o posteriores a la suscripción entraron en su carpeta de SPAM. Te sugiero que revises y recomiendo que agregues nuestro remitente a tus contactos para evitar problemas. Gracias!

Recursos Adicionales

Otros artículos interesantes en inglés:

El libro del Blog

Si te gustan los contenidos del blog y quieres darme una mano, puedes comprar el libro en papel, ó en digital.

The post Sets de Entrenamiento, Test y Validación first appeared on Aprende Machine Learning.

]]>
https://www.aprendemachinelearning.com/sets-de-entrenamiento-test-validacion-cruzada/feed/ 7 7152
Sistemas de Recomendación https://www.aprendemachinelearning.com/sistemas-de-recomendacion/ https://www.aprendemachinelearning.com/sistemas-de-recomendacion/#comments Tue, 27 Aug 2019 18:00:00 +0000 https://www.aprendemachinelearning.com/?p=5989 Crea en Python un motor de recomendación con Collaborative Filtering Una de las herramientas más conocidas y utilizadas que aportó el Machine Learning fueron los sistemas de Recomendación. Son tan efectivas que estamos invadidos todos los días por recomendaciones, sugerencias y “productos relacionados” aconsejados por...

The post Sistemas de Recomendación first appeared on Aprende Machine Learning.

]]>
Crea en Python un motor de recomendación con Collaborative Filtering

Una de las herramientas más conocidas y utilizadas que aportó el Machine Learning fueron los sistemas de Recomendación. Son tan efectivas que estamos invadidos todos los días por recomendaciones, sugerencias y “productos relacionados” aconsejados por distintas apps y webs.

Sin dudas, los casos más conocidos de uso de esta tecnología son Netflix acertando en recomendar series y películas, Spotify sugiriendo canciones y artistas ó Amazon ofreciendo productos de venta cruzada <<sospechosamente>> muy tentadores para cada usuario.

Pero también Google nos sugiere búsquedas relacionadas, Android aplicaciones en su tienda y Facebook amistades. O las típicas “lecturas relacionadas” en los blogs y periódicos.

Todo E-Comerce que se precie de serlo debe utilizar esta herramienta y si no lo hace… estará perdiendo una ventaja competitiva para potenciar sus ventas.

¿Qué son los Sistemas ó Motores de Recomendación?

Los sistemas de recomendación, a veces llamados en inglés “recommender systems” son algoritmos que intentan “predecir” los siguientes ítems (productos, canciones, etc.) que querrá adquirir un usuario en particular.

Antes del Machine Learning, lo más común era usar “rankings” ó listas con lo más votado, ó más popular de entre todos los productos. Entonces a todos los usuarios se les recomendaba lo mismo. Es una técnica que aún se usa y en muchos casos funciona bien, por ejemplo, en librerías ponen apartados con los libros más vendidos, best sellers. Pero… ¿y si pudiéramos mejorar eso?… ¿si hubiera usuarios que no se guían como un rebaño y no los estamos reteniendo?…

Los Sistemas de Recomendación intentan personalizar al máximo lo que ofrecerán a cada usuario. Esto es ahora posible por la cantidad de información individual que podemos recabar de las personas y nos da la posibilidad de tener una mejor tasa de aciertos, mejorando la experiencia del internauta sin ofrecer productos a ciegas.

Tipos de motores

Entre las estrategias más usadas para crear sistemas de recomendación encontramos:

  • Popularity: Aconseja por la “popularidad” de los productos. Por ejemplo, “los más vendidos” globalmente, se ofrecerán a todos los usuarios por igual sin aprovechar la personalización. Es fácil de implementar y en algunos casos es efectiva.
  • Content-based: A partir de productos visitados por el usuario, se intenta “adivinar” qué busca el usuario y ofrecer mercancías similares.
  • Colaborative: Es el más novedoso, pues utiliza la información de “masas” para identificar perfiles similares y aprender de los datos para recomendar productos de manera individual.

En este artículo comentaré mayormente el Collaborative Filtering y realizaremos un ejercicio en Python.

¿Cómo funciona Collaborative Filtering?

Para explicar cómo funciona Collaborative Filtering vamos a entender cómo será el dataset.

Ejemplo de Dataset

Necesitaremos, “ítems” y las valoraciones de los usuarios. Los ítems pueden ser, canciones, películas, productos, ó lo que sea que queremos recomendar.

Entonces nos quedará una matriz de este tipo, donde la intersección entre fila y columna es una valoración del usuario:

En esta “gráfica educativa” tenemos una matriz con productos (a la izquierda) y los ítems (arriba). En este ejemplo los ítems serán frutas y cada celda contiene la valoración hecha por cada usuario de ese ítem. Las casillas vacías significa que el usuario aún no ha probado esa fruta.

Entonces veremos que tenemos “huecos” en la tabla pues evidentemente no todos los usuarios tienen o “valoraron” todos los ítems. Por ejemplo si los ítems fueran “películas”, es evidente que un usuario no habrá visto <<todas las películas del mundo>>… entonces esos huecos son justamente los que con nuestro algoritmo “rellenaremos” para recomendar ítems al usuario.

Una matriz con muchas celdas vacías se dice -en inglés- que es sparce (y suele ser normal) en cambio si tuviéramos la mayoría de las celdas cubiertas con valoraciones, se llamará dense.

Tipos de Collaborative Filtering

  • User-based: (Este es el que veremos a continuación)
    • Se identifican usuarios similares
    • Se recomiendan nuevos ítems a otros usuarios basado en el rating dado por otros usuarios similares (que no haya valorado este usuario)
  • Item-based:
    • Calcular la similitud entre items
    • Encontrar los “mejores items similares” a los que un usuario no tenga evaluados y recomendárselos.

Predecir gustos (User-based)

Collaborative Filtering intentará encontrar usuarios similares, para ofrecerle ítems “bien valorados” para ese perfil en concreto (lo que antes llamé “rellenar los huecos” en la matriz). Hay diversas maneras de medir ó calcular la similitud entre usuarios y de ello dependerá que se den buenas recomendaciones. Pero tengamos en cuenta que estamos hablando de buscar similitud entre “gustos” del usuario sobre esos ítems, me refiero a que no buscaremos perfiles similares por ser del mismo sexo, edad ó nivel educativo. Sólo nos valdremos de los ítems que ha experimentado, valorado (y podría ser su secuencia temporal) para agrupar usuarios “parecidos”.

Una de las maneras de medir esa similitud se llama distancia por coseno de los vectores y por simplificar el concepto, digamos que crea un espacio vectorial con n dimensiones correspondientes a los n items y sitúa los vectores siendo su medida el “valor rating” de cada usuario -a ese item-. Luego calcula el ángulo entre los vectores partiendo de la “coordenada cero”. A “poca distancia” entre ángulos, se corresponde con usuarios con mayor similitud.

Este método no es siempre es perfecto… pero es bastante útil y rápido de calcular.

Calcular los Ratings

Una vez que tenemos la matriz de similitud, nos valdremos de otra operación matemática para calcular las recomendaciones.

FORMULA para calcular los ratings faltantes: sería algo así como “Matriz de similitud PROD.VECTORIAL ratings / (sumatoria de cada fila de ratings) Transpuesta

Lo haremos es: cada rating se multiplica por el factor de similitud de usuario que dio el rating. La predicción final por usuario será igual a la suma del peso de los ratings dividido por la “suma ponderada”.

Bueno, no te preocupes que este cálculo luego lo verás en código y no tiene tanto truco…

Ejercicio en Python: “Sistema de Recomendación de Repositorios Github”

Vamos a crear un motor de recomendación de repositorios Github. Es la propuesta que hago en el blog… porque los recomendadores de música, películas y libros ya están muy vistos!.

La idea es que si este recomendador le parece de interés a los lectores, en un futuro, publicarlo online para extender su uso. Inicialmente contaremos con un set de datos limitado (pequeño), pero que como decía, podremos llevar a producción e ir agregando usuarios y repositorios para mejorar las sugerencias.

Vamos al código!

Cargamos las librerías que utilizaremos

import pandas as pd
import numpy as np
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
from sklearn.neighbors import NearestNeighbors
import matplotlib.pyplot as plt
import sklearn

Cargamos y previsualizamoás los 3 archivos de datos csv que utilizaremos:

df_users = pd.read_csv("users.csv")
df_repos = pd.read_csv("repos.csv")
df_ratings = pd.read_csv("ratings.csv")
print(df_users.head())
print(df_repos.head())
print(df_ratings.head())

Vemos que tenemos un archivo con la información de los usuarios y sus identificadores, un archivo con la información de los repositorios y finalmente el archivo “ratings” que contiene la valoración por usuario de los repositorios. Como no tenemos REALMENTE una valoración del 1 al 5 -como podríamos tener por ejemplo al valorar películas-, la columna rating es el número de usuarios que tienen ese mismo repositorio dentro de nuestra base de datos. Sigamos explorando para comprende un poco mejor:

n_users = df_ratings.userId.unique().shape[0]
n_items = df_ratings.repoId.unique().shape[0]
print (str(n_users) + ' users')
print (str(n_items) + ' items')

30 users
167 items

Vemos que es un dataset reducido, pequeño. Tenemos 30 usuarios y 167 repositorios valorados.

plt.hist(df_ratings.rating,bins=8)

Tenemos más de 80 valoraciones con una puntuación de 1 y unas 40 con puntuación en 5. Veamos las cantidades exactas:

df_ratings.groupby(["rating"])["userId"].count()

rating
1 94
2 62
3 66
4 28
5 40
6 12
7 14
8 8
Name: userId, dtype: int64

plt.hist(df_ratings.groupby(["repoId"])["repoId"].count(),bins=8)

Aquí vemos la cantidad de repositorios y cuantos usuarios “los tienen”. La mayoría de repos los tiene 1 sólo usuario, y no los demás. Hay unos 30 que los tienen 2 usuarios y unos 20 que coinciden 3 usuarios. La suma total debe dar 167.

Creamos la matriz usuarios/ratings

Ahora crearemos la matriz en la que cruzamos todos los usuarios con todos los repositorios.

df_matrix = pd.pivot_table(df_ratings, values='rating', index='userId', columns='repoId').fillna(0)
df_matrix

Vemos que rellenamos los “huecos” de la matriz con ceros. Y esos ceros serán los que deberemos reemplazar con las recomendaciones.

Sparcity

Veamos el porcentaje de sparcity que tenemos:

ratings = df_matrix.values
sparsity = float(len(ratings.nonzero()[0]))
sparsity /= (ratings.shape[0] * ratings.shape[1])
sparsity *= 100
print('Sparsity: {:4.2f}%'.format(sparsity))

Sparsity: 6.43%

Esto serán muchos “ceros” que rellenar (predecir)…

Dividimos en Train y Test set

Separamos en train y test para -más adelante- poder medir la calidad de nuestras recomendaciones.

¿Porqué es tan importante dividir en Train, Test y Validación del Modelo?

ratings_train, ratings_test = train_test_split(ratings, test_size = 0.2, random_state=42)
print(ratings_train.shape)
print(ratings_test.shape)

(24, 167)
(6, 167)

Matriz de Similitud: Distancias por Coseno

Ahora calculamos en una nueva matriz la similitud entre usuarios.

sim_matrix = 1 - sklearn.metrics.pairwise.cosine_distances(ratings)
print(sim_matrix.shape)

(30, 30)

plt.imshow(sim_matrix);
plt.colorbar()
plt.show()

Cuanto más cercano a 1, mayor similitud entre esos usuarios.

Predicciones -ó llamémosle “Sugeridos para ti”-

#separar las filas y columnas de train y test
sim_matrix_train = sim_matrix[0:24,0:24]
sim_matrix_test = sim_matrix[24:30,24:30]

users_predictions = sim_matrix_train.dot(ratings_train) / np.array([np.abs(sim_matrix_train).sum(axis=1)]).T
plt.rcParams['figure.figsize'] = (20.0, 5.0)
plt.imshow(users_predictions);
plt.colorbar()
plt.show()

Vemos pocas recomendaciones que logren puntuar alto. La mayoría estará entre 1 y 2 puntos. Esto tiene que ver con nuestro dataset pequeño.

Vamos a tomar de ejemplo mi usuario de Github que es jbagnato.

USUARIO_EJEMPLO = 'jbagnato'
data = df_users[df_users['username'] == USUARIO_EJEMPLO]
usuario_ver = data.iloc[0]['userId'] - 1 # resta 1 para obtener el index de pandas.

user0=users_predictions.argsort()[usuario_ver]

# Veamos los tres recomendados con mayor puntaje en la predic para este usuario
for i, aRepo in enumerate(user0[-3:]):
    selRepo = df_repos[df_repos['repoId']==(aRepo+1)]
    print(selRepo['title'] , 'puntaje:', users_predictions[usuario_ver][aRepo])

4 ytdl-org / youtube-dl
Name: title, dtype: object puntaje: 2.06
84 dipanjanS / practical-machine-learning-with-py…
Name: title, dtype: object puntaje: 2.44
99 abhat222 / Data-Science–Cheat-Sheet
Name: title, dtype: object puntaje: 3.36

Vemos que los tres repositorios con mayor puntaje para sugerir a mi usuario son el de Data-Science–Cheat-Sheet con una puntuación de 3.36, practical-machine-learning-with-py con 2.44 y youtube-dl con 2.06. Lo cierto es que no son puntuaciones muy altas, pero tiene que ver con que la base de datos (nuestro csv) tiene muy pocos repositorios y usuarios cargados.

Validemos el error

Sobre el test set comparemos el mean squared error con el conjunto de entrenamiento:

def get_mse(preds, actuals):
    if preds.shape[1] != actuals.shape[1]:
        actuals = actuals.T
    preds = preds[actuals.nonzero()].flatten()
    actuals = actuals[actuals.nonzero()].flatten()
    return mean_squared_error(preds, actuals)

get_mse(users_predictions, ratings_train)

# Realizo las predicciones para el test set
users_predictions_test = sim_matrix.dot(ratings) / np.array([np.abs(sim_matrix).sum(axis=1)]).T
users_predictions_test = users_predictions_test[24:30,:]

get_mse(users_predictions_test, ratings_test)

3.39
4.72

Vemos que para el conjunto de train y test el MAE es bastante cercano. Un indicador de que no tiene buenas predicciones sería si el MAE en test fuera 2 veces más (ó la mitad) del valor del de train.

Hay más…

En la notebook completa -en Github-, encontrarás más opciones de crear el Recomendador, utilizando K-Nearest Neighbors como estimador, y también usando la similitud entre ítems (ítem-based). Sin embargo para los fines de este artículo espero haber mostrado el funcionamiento básico del Collaborative Filtering. Te invito a que luego lo explores por completo.

Conclusiones

Vimos que es relativamente sencillo crear un sistema de recomendación en Python y con Machine Learning. Como muchas veces en Data-Science una de las partes centrales para que el modelo funcione se centra en tener los datos correctos y un volumen alto. También es central el valor que utilizaremos como “rating” -siendo una valoración real de cada usuario ó un valor artificial que creemos adecuado-. Recuerda que me refiero a rating como ese puntaje que surge de la intersección entre usuario e ítems en nuestro dataset. Luego será cuestión de evaluar entre las opciones de motores user-based, ítem-based y seleccionar la que menor error tenga. Y no descartes probar en el “mundo real” y ver qué porcentaje de aciertos (o feedback) te dan los usuarios reales de tu aplicación!

Existen algunas librerías que se utilizan para crear motores de recomendación como “surprise”. También te sugiero que las explores.

Por último, decir que -como en casi todo el Machine Learning- tenemos la opción de crear Redes Neuronales con Embeddings como recomendados y hasta puede que sean las que mejor funcionan para resolver esta tarea!… pero queda fuera del alcance de este tutorial. Dejaré algún enlace por ahí abajo 😉

Forma parte del Blog!

Recibe los próximos artículos sobre Machine Learning, estrategias, teoría y código Python en tu casilla de correo!

NOTA: algunos usuarios reportaron que el email de confirmación y/o posteriores a la suscripción entraron en su carpeta de SPAM. Te sugiero que revises y recomiendo que agregues nuestro remitente a tus contactos para evitar problemas. Gracias!

Recursos del Artículo

Descarga los 3 archivos csv y el Notebook con el ejercicio Python completo (y adicionales!)

Otros artículos de interés (en inglés)

The post Sistemas de Recomendación first appeared on Aprende Machine Learning.

]]>
https://www.aprendemachinelearning.com/sistemas-de-recomendacion/feed/ 25 5989
Procesamiento del Lenguaje Natural (NLP) https://www.aprendemachinelearning.com/procesamiento-del-lenguaje-natural-nlp/ https://www.aprendemachinelearning.com/procesamiento-del-lenguaje-natural-nlp/#comments Thu, 27 Dec 2018 11:00:00 +0000 https://www.aprendemachinelearning.com/?p=5909 Definición de NLP y teoría. Funcionamiento básico y sus técnicas. Herramientas para procesamiento del lenguaje con Python

The post Procesamiento del Lenguaje Natural (NLP) first appeared on Aprende Machine Learning.

]]>

¿Qué es Natural Language Processing?

El Procesamiento del Lenguaje Natural (NLP por sus siglas en inglés) es el campo de estudio que se enfoca en la comprensión mediante ordenador del lenguaje humano. Abarca parte de la Ciencia de Datos, Inteligencia Artificial (Aprendizaje Automático) y la lingüística.

En NLP las computadoras analizan el leguaje humano, lo interpretan y dan significado para que pueda ser utilizado de manera práctica. Usando NLP podemos hacer tareas como resumen automático de textos, traducción de idiomas, extracción de relaciones, Análisis de sentimiento, reconocimiento del habla y clasificación de artículos por temáticas.

El gran desafío

NLP es considerado uno de los grandes retos de la inteligencia artificial ya que es una de las tareas más complicadas y desafiantes: ¿cómo comprender realmente el significado de un texto? ¿cómo intuir neologísmos, irónias, chistes ó poesía? Si la estrategia/algoritmo que utilizamos no sortea esas dificultades de nada nos servirán los resultados obtenidos.

Modelos, maquetas y el mundo

En NLP no es suficiente con comprender meras palabras, se deberá comprender al conjunto de palabras que conforman una oración, y al conjunto de lineas que comprenden un párrafo. Dando un sentido global al análisis del texto/discurso para poder sacar buenas conclusiones.

Nuestro lenguaje está lleno de ambigüedades, de palabras con distintas acepciones, giros y diversos significados según el contexto. Esto hace que el NLP sea una de las tareas más difíciles de dominar.

¿Para qué sirve NLP? Usos

Vamos a comentar algunos de los usos más frecuentes:

  • Resumen de textos: El algoritmo deberá encontrar la idea central de un artículo e ignorar lo que no sea relevante.
  • ChatBots: deberán ser capaces de mantener una charla fluida con el usuario y responder a sus preguntas de manera automática.
  • Generación automática de keywords y generación de textos siguiendo un estilo particular
  • Reconocimiento de Entidades: encontrar Personas, Entidades comerciales o gubernamentales ó Países, Ciudades, marcas…
  • Análisis de Sentimientos: deberá comprender si un tweet, una review o comentario es positivo ó negativo y en qué magnitud (ó neutro). Muy utilizado en Redes Sociales, en política, opiniones de productos y en motores de recomendación.
  • Traducción automática de Idiomas
  • Clasificación automática de textos en categorías pre-existentes ó a partir de textos completos, detectar los temas recurrentes y crear las categorías.

¿Cómo es capaz de entender el lenguaje el ordenador?

Pues deberemos armar diversos modelos con el lenguaje, crear estructuras y con ellas alimentar algoritmos de Machine Learning:

Podemos empezar por ejemplo tomando un texto extenso. Utilizaremos Expresiones Regulares para subdividir el texto en palabras. Podemos contar las palabras, su frecuencia. Si hay algún patrón, por ejemplo si siempre después de una palabra X, siempre viene una palabra Y. Podemos analizar como terminan las palabras, por ejemplo “verbos terminados en “ar, er, ir” y descubrir la raíz de la palabra. Podríamos agrupar palabras con significados similares en contraposición a su palabras antónimas.

Resumiendo, podemos procesar de diversas maneras al lenguaje, sus componentes: gramática, sintaxis e intentar crear estructuras de apoyo que nos servirán como entradas para aplicar Regresión Lineal, Regresión Logística, Naive Bayes, árbol de decisión o Redes Neuronales según el resultado que estemos buscando.

¿Quieres pasar a la práctica? Nuevo Artículo sobre NLP con Python: Analizamos 380 cuentos en Español de Hernán Casciari

Técnicas Comunes usadas en NLP

(Spoiler: existen herramientas para realizar estas técnicas y no tener que programar todo a mano)

  • Tokenizar: separar palabras del texto en entidades llamadas tokens, con las que trabajaremos luego. Deberemos pensar si utilizaremos los signos de puntuación como token, si daremos importancia o no a las mayúsculas y si unificamos palabras similares en un mismo token.
  • Tagging Part of Speech (PoS): Clasificar las oraciones en verbo, sustantivo, adjetivo preposición, etc.
  • Shallow parsing / Chunks: Sirve para entender la gramática en las oraciones. Se hace un parseo de los tokens y a partir de su PoS se arma un árbol de la estructura.
  • Significado de las palabras: lexical semantics y word sense disambiguation. Semántica…
  • Pragmatic Analysis: detectar cómo se dicen las cosas: ironía, sarcasmo, intencionalidad, etc
  • Bag of words: es una manera de representar el vocabulario que utilizaremos en nuestro modelo y consiste en crear una matriz en la que cada columna es un token y se contabilizará la cantidad de veces que aparece ese token en cada oración (representadas en cada fila).
  • word2vec: Es una técnica que aprende de leer enormes cantidades de textos y memorizar qué palabras parecen ser similares en diversos contextos. Luego de entrenar suficientes datos, se generan vectores de 300 dimensiones para cada palabra conformando un nuevo vocabulario en donde las palabras “similares” se ubican cercanas unas de otras. Utilizando vectores pre-entrenados, logramos tener muchísima riqueza de información para comprender el significado semántico de los textos.

Herramientas usadas en Python para NLP

En próximos artículos veremos con mayor detalle ejemplos de NLP con python pero aquí les dejo una breve reseña de herramientas usadas en Python:

  • NLTK: Esta es la lib con la que todos empiezan, sirve mucho para pre-procesamiento, crear los tokens, stemming, POS tagging, etc
  • TextBlob: fue creada encima de NLYK y es fácil de usar. Incluye algunas funcionalidades adicionales como análisis de sentimiento y spell check.
  • Gensim: contruida específicamente para modelado de temas e incluye multiples técnicas (LDA y LSI). También calcula similitud de documentos.
  • SpaCy: Puede hacer muchísimas cosas al estilo de NLTK pero es bastante más rápido.
  • WebScraping: Obtener textos desde diversas páginas webs
Somos los pioneros del Machine Learning, con sus pro y sus contras

Conclusiones

Vivimos en un mundo en el cual seguramente los humanos nos diferenciemos de otras especies por haber desarrollado herramientas de manera eficiente como el lenguaje. Nos comunicamos constantemente, hablando, con palabras, con gestos. Estamos rodeados de símbolos, de carteles, de indicaciones, de unos y ceros. El NLP es una herramienta fundamental que deberemos aprender y dominar para poder capacitar a nuestras máquinas y volverlas mucho más versátiles al momento de interactuar con el entorno, dando capacidad de comprender mejor, de explicarse: de comunicarse.

Deberemos ser capaces de entender las diversas herramientas y técnicas utilizadas en NLP y saber utilizarlas para resolver el problema adecuado. El NLP abarca mucho -muchísimo- espectro y es un recorrido que comienza pero nunca acaba… siguen apareciendo nuevos papers y nuevos instrumentos de acción. Al combinar estas técnicas de NLP “tradicional” con Deep Learning, la combinatoria de nuevas posibilidades es exponencial!

Suscripción al Blog

Recibe nuevos artículos sobre Machine Learning, redes neuronales, NLP y código Python 1 vez al mes. SI hay suerte 2 veces 😉

Futuro NLP y Recursos

En los próximos artículos iré agregando ejemplos prácticos Python con ejercicios de NLP (Ya está hecho!) para poder plasmar en código real los usos de este área del Machine Learning.

Mientras les dejo una lista de artículos interesantes también con ejercicios NLP en Python:

The post Procesamiento del Lenguaje Natural (NLP) first appeared on Aprende Machine Learning.

]]>
https://www.aprendemachinelearning.com/procesamiento-del-lenguaje-natural-nlp/feed/ 18 5909
¿Cómo funcionan las Convolutional Neural Networks? Visión por Ordenador https://www.aprendemachinelearning.com/como-funcionan-las-convolutional-neural-networks-vision-por-ordenador/ https://www.aprendemachinelearning.com/como-funcionan-las-convolutional-neural-networks-vision-por-ordenador/#comments Thu, 29 Nov 2018 09:00:00 +0000 https://www.aprendemachinelearning.com/?p=6209 En este artículo intentaré explicar la teoría relativa a las Redes Neuronales Convolucionales (en inglés CNN) que son el algoritmo utilizado en Aprendizaje Automático para dar la capacidad de “ver” al ordenador. Gracias a esto, desde apenas 1998, podemos clasificar imágenes, detectar diversos tipos de...

The post ¿Cómo funcionan las Convolutional Neural Networks? Visión por Ordenador first appeared on Aprende Machine Learning.

]]>
En este artículo intentaré explicar la teoría relativa a las Redes Neuronales Convolucionales (en inglés CNN) que son el algoritmo utilizado en Aprendizaje Automático para dar la capacidad de “ver” al ordenador. Gracias a esto, desde apenas 1998, podemos clasificar imágenes, detectar diversos tipos de tumores automáticamente, enseñar a conducir a los coches autónomos y un sinfín de otras aplicaciones.

El tema es bastante complejo/complicado e intentaré explicarlo lo más claro posible. En este artículo doy por sentado que tienes conocimientos básicos de cómo funciona una red neuronal artificial multicapa feedforward (fully connected). Si no es así te recomiendo que antes leas sobre ello:

¿Qúe es una CNN? ¿Cómo puede ver una red neuronal? ¿Cómo clasifica imagenes y distingue un perro de un gato?

La CNN es un tipo de Red Neuronal Artificial con aprendizaje supervisado que procesa sus capas imitando al cortex visual del ojo humano para identificar distintas características en las entradas que en definitiva hacen que pueda identificar objetos y “ver”. Para ello, la CNN contiene varias capas ocultas especializadas y con una jerarquía: esto quiere decir que las primeras capas pueden detectar lineas, curvas y se van especializando hasta llegar a capas más profundas que reconocen formas complejas como un rostro o la silueta de un animal.

Necesitaremos…

Recodemos que la red neuronal deberá aprender por sí sola a reconocer una diversidad de objetos dentro de imágenes y para ello necesitaremos una gran cantidad de imágenes -lease más de 10.000 imágenes de gatos, otras 10.000 de perros,…- para que la red pueda captar sus características únicas -de cada objeto- y a su vez, poder generalizarlo -esto es que pueda reconocer como gato tanto a un felino negro, uno blanco, un gato de frente, un gato de perfil, gato saltando, etc.-

Pixeles y neuronas

Para comenzar, la red toma como entrada los pixeles de una imagen. Si tenemos una imagen con apenas 28×28 pixeles de alto y ancho, eso equivale a  784 neuronas. Y eso es si sólo tenemos 1 color (escala de grises). Si tuviéramos una imagen a color, necesitaríamos 3 canales (red, green, blue) y entonces usaríamos 28x28x3 = 2352 neuronas de entrada. Esa es nuestra capa de entrada. Para continuar con el ejemplo, supondremos que utilizamos la imagen con 1 sólo color.

No Olvides: Pre-procesamiento

Antes de alimentar la red, recuerda que como entrada nos conviene normalizar los valores. Los colores de los pixeles tienen valores que van de 0 a 255, haremos una transformación de cada pixel: “valor/255” y nos quedará siempre un valor entre 0 y 1.

Convoluciones

Ahora comienza el “procesado distintivo” de las CNN. Es decir, haremos las llamadas “convoluciones”: Estas consisten en tomar “grupos de pixeles cercanos” de la imagen de entrada e ir operando matemáticamente (producto escalar) contra una pequeña matriz que se llama kernel. Ese kernel supongamos de tamaño 3×3 pixels “recorre” todas las neuronas de entrada (de izquierda-derecha, de arriba-abajo) y genera una nueva matriz de salida, que en definitiva será nuestra nueva capa de neuronas ocultas. NOTA: si la imagen fuera a color, el kernel realmente sería de 3x3x3 es decir: un filtro con 3 kernels de 3×3; luego  esos 3 filtros se suman (y se le suma una unidad bias) y conformarán 1 salida (cómo si fuera 1 solo canal).

El kernel tomará inicialmente valores aleatorios(1) y se irán ajustando mediante backpropagation. (1)Una mejora es hacer que siga una distribución normal siguiendo simetrías, pero sus valores son aleatorios.

Filtro: conjunto de kernels

UN DETALLE: en realidad, no aplicaremos 1 sólo kernel, si no que tendremos muchos kernel (su conjunto se llama filtros). Por ejemplo en esta primer convolución podríamos tener 32 filtros, con lo cual realmente obtendremos 32 matrices de salida (este conjunto se conoce como “feature mapping”), cada una de 28x28x1 dando un total del 25.088 neuronas para nuestra PRIMER CAPA OCULTA de neuronas. ¿No les parecen muchas para una imagen cuadrada de apenas 28 pixeles? Imaginen cuántas más serían si tomáramos una imagen de entrada de 224x224x3 (que aún es considerado un tamaño pequeño)…

Aquí vemos al kernel realizando el producto matricial con la imagen de entrada y desplazando de a 1 pixel de izquierda a derecha y de arriba-abajo y va generando una nueva matriz que compone al mapa de features

A medida que vamos desplazando el kernel y vamos obteniendo una “nueva imagen” filtrada por el kernel. En esta primer convolución y siguiendo con el ejemplo anterior, es como si obtuviéramos 32 “imágenes filtradas nuevas”. Estas imágenes nuevas lo que están “dibujando” son ciertas características de la imagen original. Esto ayudará en el futuro a poder distinguir un objeto de otro (por ej. gato ó perro).

La imagen realiza una convolución con un kernel y aplica la función de activación, en este caso ReLu

La función de Activación

La función de activación más utilizada para este tipo de redes neuronales es la llamada ReLu por Rectifier Linear Unit  y consiste en f(x)=max(0,x).

Subsampling

Ahora viene un paso en el que reduciremos la cantidad de neuronas antes de hacer una nueva convolución. ¿Por qué? Como vimos, a partir de nuestra imagen blanco y negro de 28x28px tenemos una primer capa de entrada de 784 neuronas y luego de la primer convolución obtenemos una capa oculta de 25.088 neuronas -que realmente son nuestros 32 mapas de características de 28×28-

Si hiciéramos una nueva convolución a partir de esta capa, el número de neuronas de la próxima capa se iría por las nubes (y ello implica mayor procesamiento)! Para reducir el tamaño de la próxima capa de neuronas haremos un proceso de subsampling en el que reduciremos el tamaño de nuestras imágenes filtradas pero en donde deberán prevalecer las características más importantes que detectó cada filtro. Hay diversos tipos de subsampling, yo comentaré el “más usado”: Max-Pooling

Subsampling con Max-Pooling

Vamos a intentar explicarlo con un ejemplo: supongamos que haremos Max-pooling de tamaño 2×2. Esto quiere decir que recorreremos cada una de nuestras 32 imágenes de características obtenidas anteriormente de 28x28px de izquierda-derecha, arriba-abajo PERO en vez de tomar de a 1 pixel, tomaremos de “2×2” (2 de alto por 2 de ancho = 4 pixeles) e iremos preservando el valor “más alto” de entre esos 4 pixeles (por eso lo de “Max”). En este caso, usando 2×2, la imagen resultante es reducida “a la mitad”y quedará de 14×14 pixeles. Luego de este proceso de subsamplig nos quedarán  32 imágenes de 14×14, pasando de haber tenido 25.088 neuronas a  6272, son bastantes menos y -en teoría- siguen almacenando la información más importante para detectar características deseadas.

¿Ya terminamos? NO: ahora más convoluciones!!

Muy bien, pues esa ha sido una primer convolución: consiste de una entrada, un conjunto de filtros, generamos un mapa de características y hacemos un subsampling. Con lo cual, en el ejemplo de imágenes de 1 sólo color tendremos:

1)Entrada: Imagen 2)Aplico Kernel 3)Obtengo Feature Mapping 4)Aplico Max-Pooling 5)Obtengo “Salida” de la Convolución
28x28x1 32 filtros de 3×3 28x28x32 de 2×2 14x14x32

La primer convolución es capaz de detectar características primitivas como lineas ó curvas. A medida que hagamos más capas con las convoluciones, los mapas de características serán capaces de reconocer formas más complejas, y el conjunto total de capas de convoluciones podrá “ver”.

Pues ahora deberemos hacer una Segunda convolución que será:

1)Entrada: Imagen 2)Aplico Kernel 3)Obtengo Feature Mapping 4)Aplico Max-Pooling 5)Obtengo “Salida” de la Convolución
14x14x32 64 filtros de 3×3 14x14x64 de 2×2 7x7x64

La 3er convolución comenzará en tamaño 7×7 pixels y luego del max-pooling quedará en 3×3 con lo cual podríamos hacer sólo 1 convolución más. En este ejemplo empezamos con una imagen de 28x28px e hicimos 3 convoluciones. Si la imagen inicial hubiese sido mayor (de 224x224px) aún hubiéramos podido seguir haciendo convoluciones.

1)Entrada: Imagen 2)Aplico Kernel 3)Obtengo Feature Mapping 4)Aplico Max-Pooling 5)Obtengo “Salida” de la Convolución
7x7x64 128 filtros de 3×3 7x7x128 de 2×2 3x3x128

Llegamos a la última convolución y nos queda el desenlace…

Conectar con una red neuronal “tradicional”.

Para terminar, tomaremos la última capa oculta a la que hicimos subsampling, que se dice que es “tridimensional” por tomar la forma -en nuestro ejemplo- 3x3x128 (alto,ancho,mapas) y la “aplanamos”, esto es que deja de ser tridimensional, y pasa a ser una capa de neuronas “tradicionales”, de las que ya conocíamos. Por ejemplo, podríamos aplanar (y conectar) a una nueva capa oculta de 100 neuronas feedforward.

Entonces, a esta nueva capa oculta “tradicional”, le aplicamos una función llamada Softmax que conecta contra la capa de salida final que tendrá la cantidad de neuronas correspondientes con las clases que estamos clasificando. Si clasificamos perros y gatos, serán 2 neuronas. Si es el dataset Mnist numérico serán 10 neuronas de salida. Si clasificamos coches, aviones ó barcos serán 3, etc.

Las salidas al momento del entrenamiento tendrán el formato conocido como “one-hot-encoding” en el que para perros y gatos sera: [1,0] y [0,1], para coches, aviones ó barcos será [1,0,0]; [0,1,0];[0,0,1].

Y la función de Softmax se encarga de pasar a probabilidad (entre 0 y 1) a las neuronas de salida. Por ejemplo una salida [0,2 0,8] nos indica 20% probabilidades de que sea perro y 80% de que sea gato.

¿Y cómo aprendió la CNN a “ver”?: Backpropagation

El proceso es similar al de las redes tradicionales en las que tenemos una entrada y una salida esperada (por eso aprendizaje supervisado) y mediante el backpropagation mejoramos el valor de los pesos de las interconexiones entre capas de neuronas y a medida que iteramos esos pesos se ajustan hasta ser óptimos. PERO…

En el caso de la CNN, deberemos ajustar el valor de los pesos de los distintos kernels. Esto es una gran ventaja al momento del aprendizaje pues como vimos cada kernel es de un tamaño reducido, en nuestro ejemplo en la primer convolución es de tamaño de 3×3, eso son sólo 9 parámetros que debemos ajustar en 32 filtros dan un total de 288 parámetros. En comparación con los pesos entre dos capas de neuronas “tradicionales”: una de 748 y otra de  6272 en donde están TODAS interconectarlas con TODAS y eso equivaldría a tener que entrenar y ajustar más de 4,5 millones de pesos (repito: sólo para 1 capa).

Comparativa entre una red neuronal “tradicional” y una CNN

Dejaré un cuadro resumen para intentar aclarar más las diferencias entre las redes Fully connected y las Convolutional Neural Networks.

  Red “tradicional” Feedforward multicapa Red Neuronal Convolucional CNN
Datos de entrada en la Capa Inicial Las características que analizamos. Por ejemplo: ancho, alto, grosor, etc. Pixeles de una imagen. Si es color, serán 3 capas para rojo,verde,azul
Capas ocultas elegimos una cantidad de neuronas para las capas ocultas. Tenemos de tipo:
* Convolución (con un tamaño de kernel y una cantidad de filtros)
* Subsampling
Capa de Salida La cantidad de neuronas que queremos clasificar. Para “comprar” ó “alquilar” serán 2 neuronas. Debemos “aplanar” la última convolución con una (ó más) capas de neuronas ocultas “tradicionales” y hacer una salida mediante SoftMax a la capa de salida que clasifica “perro” y “gato” serán 2 neuronas.
Aprendizaje Supervisado Supervisado
Interconexiones Entre capas, todas las neuronas de una capa con la siguiente. Son muchas menos conexiones necesarias, pues realmente los pesos que ajustamos serán los de los filtros/kernels que usamos.
Significado de la cantidad de capas ocultas Realmente es algo desconocido y no representa algo en sí mismo. Las capas ocultas son mapas de detección de características de la imagen y tienen jerarquía: primeras capas detectan lineas, luego curvas y formas cada vez más elaboradas.
Backpropagation Se utiliza para ajustar los pesos de todas las interconexiones de las capas Se utiliza para ajustar los pesos de los kernels.

Arquitectura básica

Resumiendo: podemos decir que los elementos que usamos para crear CNNs son:

  • Entrada: Serán los pixeles de la imagen. Serán alto, ancho y profundidad será 1 sólo color o 3 para Red,Green,Blue.
  • Capa De Convolución: procesará la salida de neuronas que están conectadas en “regiones locales” de entrada (es decir pixeles cercanos), calculando el producto escalar entre sus pesos (valor de pixel) y una pequeña región a la que están conectados en el volumen de entrada. Aquí usaremos por ejemplo 32 filtros o la cantidad que decidamos y ese será el volumen de salida.
  • “CAPA RELU” aplicará la función de activación en los elementos de la matriz.
  • POOL ó SUBSAMPLING: Hará una reducción en las dimensiones alto y ancho, pero se mantiene la profundidad.
  • CAPA “TRADICIONAL” red de neuronas feedforward que conectará con la última capa de subsampling y finalizará con la cantidad de neuronas que queremos clasificar.

Pon en práctica YA MISMO la teoría y aprende a clasificar imágenes en Python mediante este ejercicio!!

Finalizando…

Se me quedan en el tintero muchísimas cosas más que explicar… pero creo que lo iré completando con el tiempo o crearé un nuevo artículo con mayor detalle/más técnico. Temas y definiciones como padding, stride, evitar overfitting, image-aumentation, dropout… o por nombrar algunas redes famosas ResNet, AlexNet, GoogLeNet and DenseNet, al mismísimo Yann LeCun… todo eso.. se queda fuera de este texto.

Este artículo pretende ser un punto inicial para seguir investigando y aprendiendo sobre las CNN. Al final dejo enlace a varios artículos para ampliar información sobre CNN.

También puedes pasar a un nuevo nivel y hacer Detección de Objetos en Python!

Conclusiones

Hemos visto cómo este algoritmo utiliza variantes de una red neuronal tradicional y las combina con el comportamiento biológico del ojo humano, para lograr aprender a ver. Recuerda que puedes hacer un ejercicio propuesto para clasificar más de 70.000 imágenes deportivas con Python en tu ordenador!

Suscripción al Blog

Recibe nuevos artículos sobre Machine Learning, redes neuronales y código Python cada 15 días 

Más recursos sobre CNN (en Inglés)


El libro del Blog (en desarrollo)

Puedes colaborar comprando el libro ó lo puedes descargar gratuitamente. Aún está en borrador, pero apreciaré mucho tu ayuda! Contiene Extras descargares como el “Lego Dataset” utilizado en el artículo de Detección de Objetos.

The post ¿Cómo funcionan las Convolutional Neural Networks? Visión por Ordenador first appeared on Aprende Machine Learning.

]]>
https://www.aprendemachinelearning.com/como-funcionan-las-convolutional-neural-networks-vision-por-ordenador/feed/ 28 6209
Clasificación de Imágenes en Python https://www.aprendemachinelearning.com/clasificacion-de-imagenes-en-python/ https://www.aprendemachinelearning.com/clasificacion-de-imagenes-en-python/#comments Thu, 08 Nov 2018 07:30:00 +0000 https://www.aprendemachinelearning.com/?p=5910 Crearemos una Convolutional Neural Network con Keras y Tensorflow en Python para reconocimiento de Imágenes. En este artículo iremos directo al grano: veremos el código que crea la red neuronal para visión por computador. En un próximo artículo explicaré bien los conceptos utilizados, pero esta...

The post Clasificación de Imágenes en Python first appeared on Aprende Machine Learning.

]]>
Crearemos una Convolutional Neural Network con Keras y Tensorflow en Python para reconocimiento de Imágenes.

En este artículo iremos directo al grano: veremos el código que crea la red neuronal para visión por computador. En un próximo artículo explicaré bien los conceptos utilizados, pero esta vez haremos un aprendizaje Top-down 😉

Ejercicio Propuesto: Clasificar imágenes de deportes

Para el ejercicio se me ocurrió crear “mi propio set MNIST” con imágenes de deportes. Para ello, seleccioné los 10 deportes más populares del mundo -según la sabiduría de internet- : Fútbol, Basket, Golf, Futbol Americano, Tenis, Fórmula 1, Ciclismo, Boxeo, Beisball y Natación (enumerados sin orden particular entre ellos).

Obtuve entre 5000 y 9000 imágenes de cada deporte, a partir de videos de Youtube (usando a FFMpeg!). Las imágenes están en tamaño <<diminuto>> de 21×28 pixeles en color y son un total de 77.000. Si bien el tamaño en pixeles puede parecer pequeño ES SUFICIENTE para que nuestra red neuronal pueda distinguirlas!!! (¿increíble, no?).

Entonces el objetivo es que nuestra máquina: “red neuronal convolucional” aprenda a clasificar -por sí sóla-, dada una nueva imagen, de qué deporte se trata.

Ejemplo de imágenes de los deportes más populares del mundo

Dividiremos el set de datos en 80-20 para entrenamiento y para test. A su vez, el conjunto de entrenamiento también lo subdividiremos en otro 80-20 para Entrenamiento y Validación en cada iteración (EPOCH) de aprendizaje.

Una muestra de las imágenes del Dataset que he titulado sportsMNIST. Contiene más de 70.000 imágenes de los 10 deportes más populares del mundo.

Requerimientos para realizar el Ejercicio

Necesitaremos por supuesto tener Python 3.6 y como lo haremos en una Notebook Jupyter, recomiendo tener instalada una suite como Anaconda, que nos facilitará las tareas.

Además instalar Keras y Tensorflow como backend. Puedes seguir este artículo en donde se explica como instalar todo el ambiente de desarrollo rápidamente.

Necesitarás descargar el archivo zip con las imágenes (están comprimidas) y decomprimirlas en el mismo directorio en donde ejecutarás la Notebook con el código. Al descomprimir, se crearán 10 subdirectorios con las imágenes: uno por cada deporte

Al código Python sin más!

Por más que no entiendas del todo el código sigue adelante, intentaré explicar brevemente qué hacemos paso a paso y en un próximo artículo se explicará cada parte de las CNN (Convolutional Neural Networks). También dejaré al final varios enlaces con información adicional que te ayudarán.

Esto es lo que haremos hoy:

  1. Importar librerías
  2. Cargar las 70.000 imágenes (en memoria!)
  3. Crear dinámicamente las etiquetas de resultado.
  4. Dividir en sets de Entrenamiento, Validación y Test
    • algo de preprocesamiento de datos
  5. Crear el modelo de la CNN
  6. Ejecutar nuestra máquina de aprendizaje (Entrenar la red)
  7. Revisar los resultados obtenidos

Empecemos a programar!:

1- Importar librerías

Cargaremos las libs que utilizaremos para el ejercicio.

import numpy as np
import os
import re
import matplotlib.pyplot as plt
%matplotlib inline
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
import keras
from keras.utils import to_categorical
from keras.models import Sequential,Input,Model
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras.layers.normalization import BatchNormalization
from keras.layers.advanced_activations import LeakyReLU

2-Cargar las imágenes

Recuerda tener DESCOMPRIMIDAS las imágenes!!! Y ejecutar el código en el MISMO directorio donde descomprimiste el directorio llamado “sportimages” (contiene 10 subdirectorios: uno por cada deporte).

Este proceso plt.imread(filepath)  cargará a memoria en un array las 77mil imágenes, por lo que puede tomar varios minutos y consumirá algo de memoria RAM de tu ordenador.

dirname = os.path.join(os.getcwd(), 'sportimages')
imgpath = dirname + os.sep 

images = []
directories = []
dircount = []
prevRoot=''
cant=0

print("leyendo imagenes de ",imgpath)

for root, dirnames, filenames in os.walk(imgpath):
    for filename in filenames:
        if re.search("\.(jpg|jpeg|png|bmp|tiff)$", filename):
            cant=cant+1
            filepath = os.path.join(root, filename)
            image = plt.imread(filepath)
            images.append(image)
            b = "Leyendo..." + str(cant)
            print (b, end="\r")
            if prevRoot !=root:
                print(root, cant)
                prevRoot=root
                directories.append(root)
                dircount.append(cant)
                cant=0
dircount.append(cant)

dircount = dircount[1:]
dircount[0]=dircount[0]+1
print('Directorios leidos:',len(directories))
print("Imagenes en cada directorio", dircount)
print('suma Total de imagenes en subdirs:',sum(dircount))
leyendo imagenes de /Users/xxx/proyecto_python/sportimages/
Directorios leidos: 10
Imagenes en cada directorio [9769, 8823, 8937, 5172, 7533, 7752, 7617, 9348, 5053, 7124]
suma Total de imagenes en subdirs: 77128

3- Crear etiquetas y clases

Crearemos las etiquetas en labels , es decir, le daremos valores de 0 al 9 a cada deporte. Esto lo hacemos para poder usar el algoritmo supervisado e indicar que cuando cargamos una imagen de futbol en la red, ya sabemos que corresponde con la “etiqueta 6”. Y con esa información, entrada y salida esperada, la red al entrenar, ajustará los pesos de las neuronas.

Luego convertimos las etiquetas y las imágenes en numpy array con np.array()

labels=[]
indice=0
for cantidad in dircount:
    for i in range(cantidad):
        labels.append(indice)
    indice=indice+1
print("Cantidad etiquetas creadas: ",len(labels))

deportes=[]
indice=0
for directorio in directories:
    name = directorio.split(os.sep)
    print(indice , name[len(name)-1])
    deportes.append(name[len(name)-1])
    indice=indice+1

y = np.array(labels)
X = np.array(images, dtype=np.uint8) #convierto de lista a numpy

# Find the unique numbers from the train labels
classes = np.unique(y)
nClasses = len(classes)
print('Total number of outputs : ', nClasses)
print('Output classes : ', classes)
Cantidad etiquetas creadas: 77128
0 golf
1 basket
2 tenis
3 natacion
4 ciclismo
5 beisball
6 futbol
7 americano
8 f1
9 boxeo
Total number of outputs : 10
Output classes : [0 1 2 3 4 5 6 7 8 9]

4-Creamos sets de Entrenamiento y Test, Validación y Preprocesar

Nótese la “forma” (shape) de los arrays: veremos que son de 21×28 y por 3 pues el 3 se refiere a los 3 canales de colores que tiene cada imagen: RGB (red, green, blue) que tiene valores de 0 a 255.

Preprocesamos el valor de los pixeles y lo normalizamos para que tengan un valor entre 0 y 1, por eso dividimos en 255.

Ademas haremos el “One-Hot encoding” con to_categorical()  que se refiere a convertir las etiquetas (nuestras clases) por ejemplo de fútbol un 6 a una salida de tipo (0 0 0 0 0 0 1 0 0 0) Esto es porque así funcionan mejor las redes neuronales para clasificar y se corresponde con una capa de salida de la red neuronal de 10 neuronas.
NOTA: por si no lo entendiste, se pone un 1 en la “sexta posición” del array y el resto en ceros, PERO no te olvides que empieza a contar incluyendo el cero!!! por eso la “etiqueta 6” queda realmente en la séptima posición.

Por último en este bloque, subdividimos los datos en 80-20 para test y entrenamiento con train_test_split()  y nuevamente en 80-20 el de training para obtener un subconjunto de validación.

#Mezclar todo y crear los grupos de entrenamiento y testing
train_X,test_X,train_Y,test_Y = train_test_split(X,y,test_size=0.2)
print('Training data shape : ', train_X.shape, train_Y.shape)
print('Testing data shape : ', test_X.shape, test_Y.shape)

train_X = train_X.astype('float32')
test_X = test_X.astype('float32')
train_X = train_X / 255.
test_X = test_X / 255.

# Change the labels from categorical to one-hot encoding
train_Y_one_hot = to_categorical(train_Y)
test_Y_one_hot = to_categorical(test_Y)

# Display the change for category label using one-hot encoding
print('Original label:', train_Y[0])
print('After conversion to one-hot:', train_Y_one_hot[0])

train_X,valid_X,train_label,valid_label = train_test_split(train_X, train_Y_one_hot, test_size=0.2, random_state=13)

print(train_X.shape,valid_X.shape,train_label.shape,valid_label.shape)
Training data shape : (61702, 21, 28, 3) (61702,)
Testing data shape : (15426, 21, 28, 3) (15426,)
Original label: 0
After conversion to one-hot: [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
(49361, 21, 28, 3) (12341, 21, 28, 3) (49361, 10) (12341, 10)

5 – Creamos la red (Aquí la Magia)

Ahora sí que nos apoyamos en Keras para crear la Convolutional Neural Network. En un futuro artículo explicaré mejor lo que se está haciendo. Por ahora “confíen” en mi:

  • Declaramos 3 “constantes”:
    • El valor inicial del learning rate INIT_LR
    • cantidad de epochs  y
    • tamaño batch de imágenes a procesar batch_size  (cargan en memoria).
  • Crearemos una primer capa de neuronas  “Convolucional de 2 Dimensiones” Conv2D() , donde entrarán nuestras imágenes de 21x28x3.
  • Aplicaremos 32 filtros (kernel) de tamaño 3×3 (no te preocupes si aún no entiendes esto!) que detectan ciertas características de la imagen (ejemplo: lineas verticales).
  • Utilizaremos La función LeakyReLU como activación de las neuronas.
  • Haremos un MaxPooling (de 2×2) que reduce la imagen que entra de 21×28 a la mitad,(11×14) manteniendo las características “únicas” que detectó cada kernel.
  • Para evitar el overfitting, añadimos una técnica llamada Dropout
  • “Aplanamos” Flatten()  los 32 filtros y creamos una capa de 32 neuronas “tradicionales” Dense()
  • Y finalizamos la capa de salida con 10 neuronas con activación Softmax, para que se corresponda con el “hot encoding” que hicimos antes.
  • Luego compilamos nuestra red sport_model.compile()  y le asignamos un optimizador (en este caso de llama Adagrad).
INIT_LR = 1e-3
epochs = 6
batch_size = 64

sport_model = Sequential()
sport_model.add(Conv2D(32, kernel_size=(3, 3),activation='linear',padding='same',input_shape=(21,28,3)))
sport_model.add(LeakyReLU(alpha=0.1))
sport_model.add(MaxPooling2D((2, 2),padding='same'))
sport_model.add(Dropout(0.5))

sport_model.add(Flatten())
sport_model.add(Dense(32, activation='linear'))
sport_model.add(LeakyReLU(alpha=0.1))
sport_model.add(Dropout(0.5)) 
sport_model.add(Dense(nClasses, activation='softmax'))

sport_model.summary()

sport_model.compile(loss=keras.losses.categorical_crossentropy, optimizer=keras.optimizers.Adagrad(lr=INIT_LR, decay=INIT_LR / 100),metrics=['accuracy'])

6-Entrenamos la CNN

Llegó el momento!!! con esta linea sport_model.fit()  iniciaremos el entrenamiento y validación de nuestra máquina! Pensemos que introduciremos miles de imágenes, pixeles, arrays, colores… filtros y la red se irá regulando sola, “aprendiendo” los mejores pesos para las más de 150.000 interconexiones para distinguir los 10 deportes. Esto tomará tiempo en un ordenador como mi Macbook Pro (del 2016) unos 4 minutos… puede parecer mucho o muy poco… según se lo mire. NOTA: podemos ejecutar este mismo código pero utilizando GPU (en tu ordenador o en la nube) y los mismos cálculos tomaría apenas 40 segundos.

Por último guardamos la red YA ENTRENADA sport_model.save()  en un formato de archivo h5py ya que nos permitirá poder utilizarla en el futuro SIN necesidad de volver a entrenar (y ahorrarnos los 4 minutos de impaciencia! ó incluso si contamos con GPU, ahorrarnos esa espera).

sport_train_dropout = sport_model.fit(train_X, train_label, batch_size=batch_size,epochs=epochs,verbose=1,validation_data=(valid_X, valid_label))

# guardamos la red, para reutilizarla en el futuro, sin tener que volver a entrenar
sport_model.save("sports_mnist.h5py")
Train on 49361 samples, validate on 12341 samples
Epoch 1/6
49361/49361 [==============================] – 40s 814us/step – loss: 1.5198 – acc: 0.4897 – val_loss: 1.0611 – val_acc: 0.7136
Epoch 2/6
49361/49361 [==============================] – 38s 775us/step – loss: 1.2002 – acc: 0.6063 – val_loss: 0.8987 – val_acc: 0.7717
Epoch 3/6
49361/49361 [==============================] – 43s 864us/step – loss: 1.0886 – acc: 0.6469 – val_loss: 0.8078 – val_acc: 0.7977
Epoch 4/6
49361/49361 [==============================] – 41s 832us/step – loss: 1.0166 – acc: 0.6720 – val_loss: 0.7512 – val_acc: 0.8180
Epoch 5/6
49361/49361 [==============================] – 36s 725us/step – loss: 0.9647 – acc: 0.6894 – val_loss: 0.7033 – val_acc: 0.8323
Epoch 6/6
49361/49361 [==============================] – 40s 802us/step – loss: 0.9258 – acc: 0.7032 – val_loss: 0.6717 – val_acc: 0.8379

Vemos que tras 6 iteraciones completas al set de entrenamiento, logramos un valor de precisión del 70% y en el set de validación alcanza un 83%. ¿Será esto suficiente para distinguir las imágenes deportivas?

7-Resultados obtenidos

Ya con nuestra red entrenada, es la hora de la verdad: ponerla a prueba con el set de imágenes para Test que separamos al principio y que son muestras que nunca fueron “vistas” por la máquina.

test_eval = sport_model.evaluate(test_X, test_Y_one_hot, verbose=1)

print('Test loss:', test_eval[0])
print('Test accuracy:', test_eval[1])
15426/15426 [==============================] – 5s 310us/step
Test loss: 0.6687967825782881
Test accuracy: 0.8409179307662388

En el conjunto de Testing vemos que alcanza una precisión del 84% reconociendo las imágenes de deportes. Ahora podríamos hacer un análisis más profundo, para mejorar la red, revisando los fallos que tuvimos… pero lo dejaremos para otra ocasión (BONUS: en la Jupyter Notebook verás más información con esto!) Spoiler Alert: La clase que peor detecta, son las de Fórmula 1.

Puedes probar con esta imagen de Basketball y de Fútbol a clasificarlas. En mi caso, fueron clasificadas con éxito.

En mis pruebas, a veces confundía esta imagen de Fútbol con Golf… ¿Será por el verde del campo?

Conclusiones y promesa futura!

Creamos una red neuronal “novedosa”: una red convolucional, que aplica filtros a las imágenes y es capaz de distinguir distintos deportes con un tamaño 21×28 pixels a color en tan sólo 4 minutos de entrenamiento.

Esta vez fuimos a la inversa que en otras ocasiones y antes de conocer la teoría de las redes específicas para reconocimiento de imágenes (las CNN) les he propuesto que hagamos un ejercicio práctico. Aunque pueda parecer contra-intuitivo, muchas veces este método de aprendizaje (en humanos!) funciona mejor, pues vuelve algo más dinámica la teoría. Espero que les hayan quedado algunos de los conceptos y los terminaremos de asentar en un próximo artículo (ya puedes leerlo!)

Suscripción al Blog

Recibe el próximo artículo con más teoría, prácticas y material para seguir aprendiendo Machine Learning! 

Los recursos y… Más recursos

Y mientras escribo el próximo artículo para el blog en español…

Ya disponible: ¿Qué son las Convolutional Neural Networks y cómo funcionan? La Teoría que faltaba 🙂

…les dejo varios enlaces (que seguramente utilizaré como inspiración) con más información sobre las Convolutional Neural Networks:

Y por último MIS artículos sobre Redes Neuronales (en Español! ejem-ejem!)

Otros:


El libro del Blog (en desarrollo)

Puedes colaborar comprando el libro ó lo puedes descargar gratuitamente. Aún está en borrador, pero apreciaré mucho tu ayuda! Contiene Extras descargares como el “Lego Dataset” utilizado en el artículo de Detección de Objetos.

The post Clasificación de Imágenes en Python first appeared on Aprende Machine Learning.

]]>
https://www.aprendemachinelearning.com/clasificacion-de-imagenes-en-python/feed/ 92 5910
Comprende Principal Component Analysis https://www.aprendemachinelearning.com/comprende-principal-component-analysis/ https://www.aprendemachinelearning.com/comprende-principal-component-analysis/#comments Mon, 08 Oct 2018 13:00:00 +0000 https://www.aprendemachinelearning.com/?p=5904 En este artículo veremos una herramienta muy importante para nuestro kit de Machine Learning y Data Science: PCA para Reducción de dimensiones. Como bonus-track veremos un ejemplo rápido-sencillo en Python usando Scikit-learn. Introducción a PCA Imaginemos que queremos predecir los precios de alquiler de vivienda...

The post Comprende Principal Component Analysis first appeared on Aprende Machine Learning.

]]>
En este artículo veremos una herramienta muy importante para nuestro kit de Machine Learning y Data Science: PCA para Reducción de dimensiones. Como bonus-track veremos un ejemplo rápido-sencillo en Python usando Scikit-learn.

Introducción a PCA

Imaginemos que queremos predecir los precios de alquiler de vivienda del mercado. Al recopilar información de diversas fuentes tendremos en cuenta variables como tipo de vivienda, tamaño de vivienda, antigüedad, servicios, habitaciones, con/sin jardín, con/sin piscina, con/sin muebles  pero también podemos tener en cuenta la distancia al centro, si hay colegio en las cercanías, o supermercados, si es un entorno ruidoso, si tiene autopistas en las cercanías, la “seguridad del barrio”, si se aceptan mascotas, tiene wifi, tiene garaje, trastero… y seguir y seguir sumando variables.

Es posible que cuanta más (y mejor) información, obtengamos una predicción más acertada. Pero también empezaremos a notar que la ejecución de nuestro algoritmo seleccionado (regresión lineal, redes neuronales, etc.) empezará a tomar más y más tiempo y recursos. Es posible que algunas de las variables sean menos importantes y no aporten demasiado valor a la predicción. También podríamos acercarnos peligrosamente a causar overfitting al modelo.

¿No sería mejor tomar menos variables, pero más valiosas?

Al quitar variables estaríamos haciendo Reducción de Dimensiones. Al hacer Reducción de Dimensiones (las características) tendremos menos relaciones entre variables a considerar. Para reducir las dimensiones podemos hacer dos cosas:

  • Eliminar por completo dimensiones
  • Extracción de Características

Eliminar por completo algunas dimensiones no estaría mal, pero deberemos tener certeza en que estamos quitando dimensiones poco importantes. Por ejemplo para nuestro ejemplo, podemos suponer que el precio de alquiler no cambiará mucho si el dueño acepta mascotas en la vivienda. Podría ser un acierto o podríamos estar perdiendo información importante.

En la Extracción de Características si tenemos 10 características crearemos otras 10 características nuevas independientes en donde cada una de esas “nuevas” características es una combinación de las 10 características “viejas”. Al crear estas nuevas variables independientes lo haremos de una manera específica y las pondremos en un orden de “mejor a peor” sean para predecir a la variable dependiente.

¿Y la reducción de dimensiónes? te preguntarás. Bueno, intentaremos mantener todas las variables posibles, pero prescindiremos de las menos importantes. Como tenemos las variables ordenadas de “mejor a peores predictoras” ya sabemos cuales serán las más y menos valiosas. A diferencia de la eliminación directa de una característica “vieja”, nuestras nuevas variables son combinaciones de todas las variables originales, aunque eliminemos algunas, estaremos manteniendo la información útil de todas las variables iniciales.

¿Qué es Principal Component Analysis?

Entonces Principal Component Analysis es una técnica de Extracción de Características donde combinamos las entradas de una manera específica y podemos eliminar algunas de las variables “menos importantes” manteniendo la parte más importante todas las variables. Como valor añadido, luego de aplicar PCA conseguiremos que todas las nuevas variables sean independientes una de otra.

¿Cómo funciona PCA?

En resumen lo que hace el algoritmo es:

  • Estandarizar los datos de entrada (ó Normalización de las Variables)
  • Obtener los autovectores y autovalores de la matriz de covarianza
  • Ordenar los autovalores de mayor a menor y elegir los “k” autovectores que se correspondan con los autovectores “k” más grandes (donde “k” es el número de dimensiones del nuevo subespacio de características).
  • Construir la matriz de proyección W con los “k” autovectores seleccionados.
  • Transformamos el dataset original “X estandarizado” vía W para obtener las nuevas características k-dimensionales.

Tranquilos, que todo esto ya lo hace solito scikit-learn (u otros paquetes Python). Ahora que tenemos las nuevas dimensiones, deberemos seleccionar con cuales nos quedamos.

Selección de los Componentes Principales

Típicamente utilizamos PCA para reducir dimensiones del espacio de características original (aunque PCA tiene más aplicaciones). Hemos rankeado las nuevas dimensiones de “mejor a peor reteniendo información”. Pero ¿cuantas elegir para obtener buenas predicciones, sin perder información valiosa? Podemos seguir 3 métodos:

Método 1: Elegimos arbitrariamente “las primeras n dimensiones” (las más importantes). Por ejemplo si lo que queremos es poder graficar en 2 dimensiones, podríamos tomar las 2 características nuevas y usarlas como los ejes X e Y.

Método 2: calcular la “proporción de variación explicada de cada característica  e ir tomando dimensiones hasta alcanzar un mínimo que nos propongamos, por ejemplo hasta alcanzar a explicar el 85% de la variabilidad total.

Método 3: Crear una gráfica especial llamada scree plot -a partir del Método 2- y seleccionar cuántas dimensiones usaremos por el método “del codo” en donde identificamos visualmente el punto en donde se produce una caída significativa en la variación explicada relativa a la característica anterior.

¿Pero… porqué funciona PCA?

Suponiendo nuestras características de entrada estandarizadas como la matriz Z  y ZT su transpuesta, cuando creamos la matriz de covarianza ZTZ es una matriz que contiene estimados de cómo cada variable de Z se relaciona con cada otra variable de Z. Comprender como una variable es asociada con otra es importante!

Los autovectores representan dirección. Los autovalores representan magnitud. A mayores autovalores, se correlacionan direcciones más importantes.

Por último asumimos que a más variabilidad en una dirección particular se correlaciona con explicar mejor el comportamiento de una variable dependiente. Mucha variabilidad usualmente  indica “Información” mientras que poca variabilidad indica “Ruido”.

Ejemplo “mínimo” en Python

Utilizaré un archivo csv de entrada de un ejercicio anterior, en el cual decidíamos si convenía alquilar o comprar casa dadas 9 dimensiones. En este ejemplo:

  • normalizamos los datos de entrada,
  • aplicamos PCA
  • y veremos que con 5 de las nuevas dimensiones (y descartando 4) obtendremos
    • hasta un 85% de variación explicada y
    • buenas predicciones.
  • Realizaremos 2 gráficas:
    • una con el acumulado de variabilidad explicada y
    • una gráfica 2D, en donde el eje X e Y serán los 2 primero componentes principales obtenidos por PCA.

Y veremos cómo los resultados “comprar ó alquilar” tienen [icon name=”angle-double-left” class=”” unprefixed_class=””]bastante buena[icon name=”angle-double-right” class=”” unprefixed_class=””] separación en 2 dimensiones.

#importamos librerías
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
plt.rcParams['figure.figsize'] = (16, 9)
plt.style.use('ggplot')
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

#cargamos los datos de entrada
dataframe = pd.read_csv(r"comprar_alquilar.csv")
print(dataframe.tail(10))

#normalizamos los datos
scaler=StandardScaler()
df = dataframe.drop(['comprar'], axis=1) # quito la variable dependiente "Y"
scaler.fit(df) # calculo la media para poder hacer la transformacion
X_scaled=scaler.transform(df)# Ahora si, escalo los datos y los normalizo

#Instanciamos objeto PCA y aplicamos
pca=PCA(n_components=9) # Otra opción es instanciar pca sólo con dimensiones nuevas hasta obtener un mínimo "explicado" ej.: pca=PCA(.85)
pca.fit(X_scaled) # obtener los componentes principales
X_pca=pca.transform(X_scaled) # convertimos nuestros datos con las nuevas dimensiones de PCA

print("shape of X_pca", X_pca.shape)
expl = pca.explained_variance_ratio_
print(expl)
print('suma:',sum(expl[0:5]))
#Vemos que con 5 componentes tenemos algo mas del 85% de varianza explicada

#graficamos el acumulado de varianza explicada en las nuevas dimensiones
plt.plot(np.cumsum(pca.explained_variance_ratio_))
plt.xlabel('number of components')
plt.ylabel('cumulative explained variance')
plt.show()

#graficamos en 2 Dimensiones, tomando los 2 primeros componentes principales
Xax=X_pca[:,0]
Yax=X_pca[:,1]
labels=dataframe['comprar'].values
cdict={0:'red',1:'green'}
labl={0:'Alquilar',1:'Comprar'}
marker={0:'*',1:'o'}
alpha={0:.3, 1:.5}
fig,ax=plt.subplots(figsize=(7,5))
fig.patch.set_facecolor('white')
for l in np.unique(labels):
    ix=np.where(labels==l)
    ax.scatter(Xax[ix],Yax[ix],c=cdict[l],label=labl[l],s=40,marker=marker[l],alpha=alpha[l])

plt.xlabel("First Principal Component",fontsize=14)
plt.ylabel("Second Principal Component",fontsize=14)
plt.legend()
plt.show()

En esta gráfica de variabilidad explicada acumulada, vemos que tomando los primeros 5 componentes llegamos al 85%

Aquí vemos que al reducir las 9 dimensiones iniciales a tan sólo 2 logramos darnos una idea de dónde visualizar nuestras predicciones para comprar o alquilar casa.

Puedes revisar más ejemplos Python en nuestra sección de Práctica

Instala el Ambiente de Programación siguiendo estos pasos

Conclusiones Finales

Con PCA obtenemos:

  1. una medida de como cada variable se asocia con las otras (matriz de covarianza)
  2. La dirección en las que nuestros datos están dispersos (autovectores)
  3. La relativa importancia de esas distintas direcciones (autovalores)

PCA combina nuestros predictores y nos permite deshacernos de los autovectores de menor importancia relativa.

Contras de PCA y variantes

No todo es perfecto en la vida ni en PCA. Como contras, debemos decir que el algoritmo de PCA es muy influenciado por los outliers en los datos. Por esta razón, surgieron variantes de PCA para minimizar esta debilidad. Entre otros se encuentran: RandomizedPCA, SparcePCA y KernelPCA.

Por último decir que PCA fue creado en 1933 y ha surgido una buena alternativa en 2008 llamada t-SNE con un enfoque distinto y del que hablaremos en un futuro artículo…

Te recomiendo leer un nuevo artículo “Interpretación de Modelos de Machine Learning” en donde se comprende mejor la importancia de las diversas features de los modelos.

Resultados de PCA en el mundo real

Para concluir, les comentaré un ejemplo muy interesante que vi para demostrar la eficacia de aplicar PCA. Si conocen el ejercicio “clásico” MNIST (algunos le llaman el Hello Word del Machine Learning), donde tenemos un conjunto de 70.000 imágenes con números “a mano” del 0 al 9 y debemos reconocerlos utilizando alguno de los algoritmos de clasificación.

Pues en el caso de MNIST, nuestras características de entrada son las imágenes de 28×28 pixeles, lo que nos da un total de 748 dimensiones de entrada. Ejecutar Regresión Logística en con una Macbook tarda unos 48 segundos en entrenar el set de datos y lograr una precisión del 91%.

Aplicando PCA al MNIST con una varianza retenida del 90% logramos reducir las dimensiones de 748 a 236. Ejecutar Regresión Logística ahora toma 10 segundos y la precisión obtenida sigue siendo del 91% !!!

Suscripción al Blog

Recibe el próximo artículo quincenal sobre Aprendizaje automático, teoría y ejemplos 

Más recursos, seguir leyendo sobre PCA

Mas información en los siguientes enlaces (en inglés):

The post Comprende Principal Component Analysis first appeared on Aprende Machine Learning.

]]>
https://www.aprendemachinelearning.com/comprende-principal-component-analysis/feed/ 11 5904
¿Comprar casa o Alquilar? Naive Bayes usando Python https://www.aprendemachinelearning.com/comprar-casa-o-alquilar-naive-bayes-usando-python/ https://www.aprendemachinelearning.com/comprar-casa-o-alquilar-naive-bayes-usando-python/#comments Thu, 23 Aug 2018 09:00:00 +0000 https://www.aprendemachinelearning.com/?p=5903 Hoy veremos un nuevo ejercicio práctico, intentando llevar los algoritmos de Machine Learning a ejemplos claros y de la vida real, repasaremos la teoría del Teorema de Bayes (video) de estadística para poder tomar una decisión muy importante: ¿me conviene comprar casa ó alquilar? Veamos...

The post ¿Comprar casa o Alquilar? Naive Bayes usando Python first appeared on Aprende Machine Learning.

]]>
Hoy veremos un nuevo ejercicio práctico, intentando llevar los algoritmos de Machine Learning a ejemplos claros y de la vida real, repasaremos la teoría del Teorema de Bayes (video) de estadística para poder tomar una decisión muy importante: ¿me conviene comprar casa ó alquilar?

Veamos si la Ciencia de Datos nos puede ayudar a resolver el misterio… ¿Si alquilo estoy tirando el dinero a la basura? ó ¿Es realmente conveniente pagar una hipoteca durante el <<resto de mi vida>>?

Si bien tocaremos el tema livianamente -sin meternos en detalles como intereses de hipotecas variable/fija, porcentajes, comisiones de bancos,etc- haremos un planteo genérico para obtener resultados y tomar la mejor decisión dada nuestra condición actual.

En artículos pasados vimos diversos algoritmos Supervisados del Aprendizaje Automático que nos dejan clasificar datos y/o obtener predicciones o asistencia a la toma de decisiones (árbol de decisión, regresión logística y lineal, red neuronal). Por lo general esos algoritmos intentan minimizar algún tipo de coste iterando las entradas y las salidas y ajustando internamente las “pendientes” ó “pesos” para hallar una salida. Esta vez, el algoritmo que usaremos se basa completamente en teoría de probabilidades  y obteniendo resultados estadísticos. ¿Será suficiente el Teorema de Bayes para obtener buenas decisiones? Veamos!

¿Qué necesitaras para programar?

Para realizar este ejercicio, crearemos una Jupyter notebook con código Python y la librería SkLearn muy utilizada en Data Science. Recomendamos utilizar la suite para Python de Anaconda. Puedes leer este artículo donde muestro paso a paso como instalar el ambiente de desarrollo. Podrás descargar los archivos de entrada csv o visualizar la notebook online (al final de este artículo los enlaces).

Nuestros Datos de Entrada:

Importemos las librerías que usaremos y visualicemos la información que tenemos de entrada:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import colors
import seaborn as sb

%matplotlib inline
plt.rcParams['figure.figsize'] = (16, 9)
plt.style.use('ggplot')

from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix
from sklearn.naive_bayes import GaussianNB
from sklearn.feature_selection import SelectKBest

Y carguemos la info del archivo csv:

dataframe = pd.read_csv(r"comprar_alquilar.csv")
dataframe.head(10)

Las columnas que tenemos son:

  • ingresos: los ingresos de la familia mensual
  • gastos comunes: pagos de luz, agua, gas, etc mensual
  • pago coche: si se está pagando cuota por uno o más coches, y los gastos en combustible, etc al mes.
  • gastos_otros: compra en supermercado y lo necesario para vivir al mes
  • ahorros: suma de ahorros dispuestos a usar para la compra de la casa.
  • vivienda: precio de la vivienda que quiere comprar esa familia
  • estado civil:
    • 0-soltero
    • 1-casados
    • 2-divorciados
  • hijos: cantidad de hijos menores y que no trabajan.
  • trabajo:
    • 0-sin empleo 1-autónomo (freelance)
    • 2-empleado
    • 3-empresario
    • 4-pareja: autónomos
    • 5-pareja: empleados
    • 6-pareja: autónomo y asalariado
    • 7-pareja:empresario y autónomo
    • 8-pareja: empresarios los dos o empresario y empleado
  • comprar: 0-No comprar 1-Comprar (esta será nuestra columna de salida, para aprender)

Algunos supuestos para el problema formulado:

  • Está pensado en Euros pero podría ser cualquier otra moneda
  • No tiene en cuenta ubicación geográfica, cuando sabemos que dependerá mucho los precios de los inmuebles de distintas zonas
  • Se supone una hipoteca fija a 30 años con interés de mercado “bajo”.

Con esta información, queremos que el algoritmo aprenda y que como resultado podamos consultar nueva información y nos dé una decisión sobre comprar (1) o alquilar (0) casa.

El teorema de Bayes

  • El teorema de Bayes es una ecuación que describe la relación de probabilidades condicionales de cantidades estadísticas. En clasificación bayesiana estamos interesados en encontrar la probabilidad de que ocurra una “clase” dadas unas características observadas (datos). Lo podemos escribir como P( Clase | Datos). El teorema de Bayes nos dice cómo lo podemos expresar en términos de cantidades que podemos calcular directamente:
  • Clase es una salida en particular, por ejemplo “comprar”
  • Datos son nuestras características, en nuestro caso los ingresos, gastos, hijos, etc
  • P(Clase|Datos) se llama posterior (y es el resultado que queremos hallar)
  • P(Datos|Clase) se llama “verosimilitud” (en inglés likelihood)
  • P(Clase) se llama anterior (pues es una probabilidad que ya tenemos)
  • P(Datos) se llama probabilidad marginal

Si estamos tratando de elegir entre dos clases como en nuestro caso “comprar” ó “alquilar”, entonces una manera de tomar la decisión es calcular la tasa de probabilidades a posterior:

con esta maniobra, nos deshacemos del denominador de la ecuación anterior P(Datos) el llamado “probabilidad marginal”.

Clasificador Gaussian Naive Bayes

Uno de los tipos de clasificadores más populares es el llamado en inglés Gaussian Naive Bayes Classifier. NOTA:Hay otros clasificadores Bayesianos que no veremos en este artículo. Veamos cómo es su fórmula para comprender este curioso nombre: aplicaremos 2 clases (comprar, alquilar) y tres características: ingresos, ahorros e hijos.


Posterior de comprar es lo que queremos hallar: P(comprar|datos).

Explicaremos los demá:

  • P(comprar) es la probabilidad que ya tenemos. Es sencillamente el número de veces que se selecciona comprar =1 en nuestro conjunto de datos, dividido el total de observaciones. En nuestro caso (luego lo veremos en Python) son 67/202
  • p(ingresos|comprar)p(ahorros|comprar)p(hijos|comprar) es la verosimilitud. Los nombres Gaussian y Naive (ingenuo) del algoritmo vienen de dos suposiciones:
    1. asumimos que las características de la verosimilitud no estan correlacionada entre ellas. Esto seria que los ingresos sean independientes a la cantidad de hijos y de los ahorros. Como no es siempre cierto y es una suposición ingenua es que aparece en el nombre “naive bayes”
    2. Asumimos que el valor de las características (ingresos, hijos, etc) tendrá una distribución normal (gaussiana). Esto nos permite calcular cada parte p(ingresos|comprar) usando la función de probabilidad de densidad normal.
  • probabilidad marginal muchas veces es difícil de calcular, sin embargo, por la ecuación que vimos más arriba, no la necesitaremos para obtener nuestro valor a posterior. Esto simplifica los cálculos.

Bien!, Fin de teoría, sigamos con el ejercicio! Ahora toca visualizar nuestras entradas y programar un poquito.

Visualización de Datos

Veamos qué cantidad de muestras de comprar o alquilar tenemos:

print(dataframe.groupby('comprar').size())

comprar
0 135
1 67
dtype: int64

Esto son 67 que entradas en las que se recomienda comprar y 135 en las que no.

Hagamos un histograma de las características quitando la columna de resultados (comprar):

dataframe.drop(['comprar'], axis=1).hist()
plt.show()

Pareciera a grandes rasgos que la distribución de hijos e ingresos <<se parece>> un poco a una distribución normal.

Preparar los datos de entrada

Vamos a hacer algo: procesemos algunas de estas columnas. Por ejemplo, podríamos agrupar los diversos gastos. También crearemos una columna llamada financiar que será la resta del precio de la vivienda con los ahorros de la familia.

dataframe['gastos']=(dataframe['gastos_comunes']+dataframe['gastos_otros']+dataframe['pago_coche'])
dataframe['financiar']=dataframe['vivienda']-dataframe['ahorros']
dataframe.drop(['gastos_comunes','gastos_otros','pago_coche'], axis=1).head(10)

Y ahora veamos un resumen estadístico que nos brinda la librería Pandas con describe():

reduced = dataframe.drop(['gastos_comunes','gastos_otros','pago_coche'], axis=1)
reduced.describe()

Feature Selection ó Selección de Características

En este ejercicio haremos Feature Selection para mejorar nuestros resultados con este algoritmo. En vez de utilizar las 11 columnas de datos de entrada que tenemos, vamos a utilizar una Clase de SkLearn llamada SelectKBest con la que seleccionaremos las 5 mejores características y usaremos sólo esas.

X=dataframe.drop(['comprar'], axis=1)
y=dataframe['comprar']

best=SelectKBest(k=5)
X_new = best.fit_transform(X, y)
X_new.shape
selected = best.get_support(indices=True)
print(X.columns[selected])

Index([‘ingresos’, ‘ahorros’, ‘hijos’, ‘trabajo’, ‘financiar’], dtype=’object’)

Bien, entonces usaremos 5 de las 11 características que teníamos. Las que “más aportan” al momento de clasificar. Veamos qué grado de correlación tienen:

used_features =X.columns[selected]

colormap = plt.cm.viridis
plt.figure(figsize=(12,12))
plt.title('Pearson Correlation of Features', y=1.05, size=15)
sb.heatmap(dataframe[used_features].astype(float).corr(),linewidths=0.1,vmax=1.0, square=True, cmap=colormap, linecolor='white', annot=True)

Con esto comprobamos que en general están poco correlacionadas, sin embargo también tenemos 2 valores de 0,7. Esperemos que el algoritmo sea lo suficientemente “naive” para dar buenos resultados 😉

Otra alternativa para Feture Selection es utilizar Principal Component Analysis (PCA) y hacer reducción de Dimensión

Crear el modelo Gaussian Naive Bayes con SKLearn

Primero vamos a dividir nuestros datos de entrada en entrenamiento y test.

# Split dataset in training and test datasets
X_train, X_test = train_test_split(dataframe, test_size=0.2, random_state=6) 
y_train =X_train["comprar"]
y_test = X_test["comprar"]

Y creamos el modelo, lo ponemos a aprender con fit() y obtenemos predicciones sobre nuestro conjunto de test.

# Instantiate the classifier
gnb = GaussianNB()
# Train classifier
gnb.fit(
    X_train[used_features].values,
    y_train
)
y_pred = gnb.predict(X_test[used_features])

print('Precisión en el set de Entrenamiento: {:.2f}'
     .format(gnb.score(X_train[used_features], y_train)))
print('Precisión en el set de Test: {:.2f}'
     .format(gnb.score(X_test[used_features], y_test)))

Precisión en el set de Entrenamiento: 0.87
Precisión en el set de Test: 0.90

Pues hemos obtenido un bonito 90% de aciertos en el conjunto de Test con nuestro querido clasificador bayesiano. También puedes ver los resultados obtenidos aplicando PCA en este otro artículo!

Probemos el modelo: ¿Comprar o Alquilar?

Ahora, hagamos 2 predicciones para probar nuestra máquina:

  • En un caso será una familia sin hijos con 2.000€ de ingresos que quiere comprar una casa de 200.000€ y tiene sólo 5.000€ ahorrados.
  • El otro será una familia con 2 hijos con ingresos por 6.000€ al mes, 34.000 en ahorros y consultan si comprar una casa de 320.000€.

#                 ['ingresos', 'ahorros', 'hijos', 'trabajo', 'financiar']
print(gnb.predict([[2000,        5000,     0,       5,         200000],
                   [6000,        34000,    2,       5,         320000] ]))
#Resultado esperado 0-Alquilar, 1-Comprar casa

[0 1]

Los resultados son los esperados, en el primer caso, recomienda Alquilar (0) y en el segundo comprar la casa (1).

Conclusiones

A lo largo del artículo repasamos el teorema de Bayes y vimos un ejemplo para aplicarlo en una toma de decisiones. Pero no olvidemos que en el proceso también hicimos pre procesamiento de los datos, visualizaciones y Selección de Características. Durante diversas charlas que tuve con profesionales del Data Science en mi camino de aprendizaje sale un mismo mensaje que dice: “No es tan importante el algoritmo a aplicar si no la obtención y pre procesamiento de los datos que se van a utilizar”. A tenerlo en cuenta!

Naive Bayes como clasificador se utiliza mucho en NLP (Natural Language Processing) tanto en el típico ejemplo de detectar “Spam” o no como en tareas más complejas como reconocer un idioma o detectar la categoría apropiada de un artículo de texto. También puede usarse para detección de intrusiones o anomalías en redes informáticas y para diagnósticos médicos dados unos síntomas observados. Por último veamos los pros y contras de utilizar Gaussian Naive Bayes:

  • Pros: Es rápido, simple de implementar, funciona bien con conjunto de datos pequeños, va bien con muchas dimensiones (features) y llega a dar buenos resultados aún siendo “ingenuo” sin que se cumplan todas las condiciones de distribución necesarias en los datos.
  • Contras: Requiere quitar las dimensiones con correlación y para buenos resultados las entradas deberían cumplir las 2 suposiciones de distribución normal e independencia entre sí (muy difícil que sea así ó deberíamos hacer transformaciones en lo datos de entrada).

Si les gustó el artículo les pido como favor si pueden ayudarme a difundir estas páginas en vuestras redes sociales.

Además, como siempre, los invito a suscribirse al Blog ingresando una dirección de email y recibirán una notificación cada 15 días (aprox.) con un nuevo artículo sobre Aprende Machine Learning!.

Suscripción al Blog

Recibe el próximo artículo quincenal sobre Data Science y Machine Learning con Python 

Más Recursos y descarga el Código

Otros artículos de interés sobre Bayes y Python en Inglés:

The post ¿Comprar casa o Alquilar? Naive Bayes usando Python first appeared on Aprende Machine Learning.

]]>
https://www.aprendemachinelearning.com/comprar-casa-o-alquilar-naive-bayes-usando-python/feed/ 18 5903
Clasificar con K-Nearest-Neighbor ejemplo en Python https://www.aprendemachinelearning.com/clasificar-con-k-nearest-neighbor-ejemplo-en-python/ https://www.aprendemachinelearning.com/clasificar-con-k-nearest-neighbor-ejemplo-en-python/#comments Tue, 10 Jul 2018 08:00:32 +0000 https://www.aprendemachinelearning.com/?p=5843 K-Nearest-Neighbor es un algoritmo basado en instancia de tipo supervisado de Machine Learning. Puede usarse para clasificar nuevas muestras (valores discretos) o para predecir (regresión, valores continuos). Al ser un método sencillo, es ideal para introducirse en el mundo del  Aprendizaje Automático. Sirve esencialmente para clasificar...

The post Clasificar con K-Nearest-Neighbor ejemplo en Python first appeared on Aprende Machine Learning.

]]>
K-Nearest-Neighbor es un algoritmo basado en instancia de tipo supervisado de Machine Learning. Puede usarse para clasificar nuevas muestras (valores discretos) o para predecir (regresión, valores continuos). Al ser un método sencillo, es ideal para introducirse en el mundo del  Aprendizaje Automático. Sirve esencialmente para clasificar valores buscando los puntos de datos “más similares” (por cercanía) aprendidos en la etapa de entrenamiento (ver 7 pasos para crear tu ML) y haciendo conjeturas de nuevos puntos basado en esa clasificación.

A diferencia de K-means, que es un algoritmo no supervisado y donde la “K” significa la cantidad de “grupos” (clusters) que deseamos clasificar, en K-Nearest Neighbor la “K” significa la cantidad de “puntos vecinos” que tenemos en cuenta en las cercanías para clasificar los “n” grupos -que ya se conocen de antemano, pues es un algoritmo supervisado-.

¿Qué es el algoritmo k-Nearest Neighbor ?

Es un método que simplemente busca en las observaciones más cercanas a la que se está tratando de predecir y clasifica el punto de interés basado en la mayoría de datos que le rodean. Como dijimos antes, es un algoritmo:

  • Supervisado: esto -brevemente- quiere decir que tenemos etiquetado nuestro conjunto de datos de entrenamiento, con la clase o resultado esperado dada “una fila” de datos.
  • Basado en Instancia: Esto quiere decir que nuestro algoritmo no aprende explícitamente un modelo (como por ejemplo en Regresión Logística o árboles de decisión). En cambio memoriza las instancias de entrenamiento que son usadas como “base de conocimiento” para la fase de predicción.

¿Dónde se aplica k-Nearest Neighbor?

Aunque sencillo, se utiliza en la resolución de multitud de problemas, como en sistemas de recomendación, búsqueda semántica y detección de anomalías.

Pros y contras

Como pros tiene sobre todo que es sencillo de aprender e implementar. Tiene como contras que utiliza todo el dataset para entrenar “cada punto” y por eso requiere de uso de mucha memoria y recursos de procesamiento (CPU). Por estas razones kNN tiende a funcionar mejor en datasets pequeños y sin una cantidad enorme de features (las columnas).

Para reducir la cantidad de dimensiones (features) podemos aplicar PCA

¿Cómo funciona kNN?

  1. Calcular la distancia entre el item a clasificar y el resto de items del dataset de entrenamiento.
  2. Seleccionar los “k” elementos más cercanos (con menor distancia, según la función que se use)
  3. Realizar una “votación de mayoría” entre los k puntos: los de una clase/etiqueta que <<dominen>> decidirán su clasificación final.

Teniendo en cuenta el punto 3, veremos que para decidir la clase de un punto es muy importante el valor de k, pues este terminará casi por definir a qué grupo pertenecerán los puntos, sobre todo en las “fronteras” entre grupos. Por ejemplo -y a priori- yo elegiría valores impares de k para desempatar (si las features que utilizamos son pares). No será lo mismo tomar para decidir 3 valores que 13. Esto no quiere decir que necesariamente tomar más puntos implique mejorar la precisión. Lo que es seguro es que cuantos más “puntos k”, más tardará nuestro algoritmo en procesar y darnos respuesta 😉

Las formas más populares de “medir la cercanía” entre puntos son la distancia Euclidiana (la “de siempre”) o la Cosine Similarity (mide el ángulo de  los vectores, cuanto menores, serán similares). Recordemos que este algoritmo -y prácticamente todos en ML- funcionan mejor con varias características de las que tomemos datos (las columnas de nuestro dataset). Lo que entendemos como “distancia” en la vida real, quedará abstracto a muchas dimensiones que no podemos “visualizar” fácilmente (como por ejemplo en un mapa).

Hagamos un ejemplo k-Nearest Neighbor en Python

Exploremos el algoritmo con Scikit learn

Realizaremos un ejercicio usando Python y su librería scikit-learn que ya tiene implementado el algoritmo para simplificar las cosas. Veamos cómo se hace.

Requerimientos

Para realizar este ejercicio, crearemos una Jupyter notebook con código Python y la librería SkLearn muy utilizada en Data Science. Recomendamos utilizar la suite para python de Anaconda. Puedes leer este artículo donde muestro paso a paso como instalar el ambiente de desarrollo. Podrás descargar los archivos de entrada csv o visualizar la notebook online (al final de este artículo los enlaces).

El Ejercicio y el Código: App Reviews

Para nuestro ejercicio tomaremos 257 registros con Opiniones de usuarios sobre una app (Reviews). Utilizaremos 2 columnas de datos como fuente de alimento del algoritmo. Recuerden que sólo tomaré 2 features para poder graficar en 2 dimensiones, PERO para un problema “en la vida real” conviene tomar más características de lo que sea que queramos resolver. Esto es únicamente con fines de enseñanza. Las columnas que utilizaremos serán: wordcount con la cantidad de palabras utilizadas y sentimentValue con un valor entre -4 y 4 que indica si el comentario fue valorado como positivo o negativo. Nuestras etiquetas, serán las estrellas que dieron los usuarios a la app, que son valores discretos del 1 al 5. Podemos pensar que si el usuario puntúa con más estrellas, tendrá un sentimiento positivo, pero no necesariamente siempre es así.

Comencemos con el código!

Primero hacemos imports de librerías que utilizaremos para manejo de datos, gráficas y nuestro algoritmo.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
import matplotlib.patches as mpatches
import seaborn as sb

%matplotlib inline
plt.rcParams['figure.figsize'] = (16, 9)
plt.style.use('ggplot')

from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix

Cargamos el archivo entrada csv con pandas, usando separador de punto y coma, pues en las reviews hay textos que usan coma. Con head(10) vemos los 10 primeros registros.

dataframe = pd.read_csv(r"reviews_sentiment.csv",sep=';')
dataframe.head(10)

Aprovechamos a ver un resumen estadístico de los datos:

dataframe.describe()

Son 257 registros. Las estrellas lógicamente vemos que van del 1 al 5. La cantidad de palabras van de 1 sóla hasta 103. y las valoraciones de sentimiento están entre -2.27 y 3.26 con una media de 0,38 y a partir del desvío estándar podemos ver que la mayoría están entre 0,38-0,89 y 0,38+0,89.

Un poco de Visualización

Veamos unas gráficas simples y qué información nos aportan:

dataframe.hist()
plt.show()

Vemos que la distribución de “estrellas” no está balanceada… esto no es bueno. Convendría tener las mismas cantidades en las salidas, para no tener resultados “tendenciosos”. Para este ejercicio lo dejaremos así, pero en la vida real, debemos equilibrarlos. La gráfica de Valores de Sentimientos parece bastante una campana movida levemente hacia la derecha del cero y la cantidad de palabras se centra sobre todo de 0 a 10.

Veamos realmente cuantas Valoraciones de Estrellas tenemos:

print(dataframe.groupby('Star Rating').size())

Con eso confirmamos que hay sobre todo de 3 y 5 estrellas.

Y aqui una gráfica más bonita:

sb.factorplot('Star Rating',data=dataframe,kind="count", aspect=3)

Graficamos mejor la cantidad de palabras y confirmamos que la mayoría están entre 1 y 10 palabras.

sb.factorplot('wordcount',data=dataframe,kind="count", aspect=3)

Preparamos las entradas

Creamos nuestro X e y de entrada y los sets de entrenamiento y test.

X = dataframe[['wordcount','sentimentValue']].values
y = dataframe['Star Rating'].values

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
scaler = MinMaxScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)

Usemos k-Nearest Neighbor con Scikit Learn

Definimos el valor de k en 7 (esto realmente lo sabemos más adelante, ya veréis) y creamos nuestro clasificador.

n_neighbors = 7

knn = KNeighborsClassifier(n_neighbors)
knn.fit(X_train, y_train)
print('Accuracy of K-NN classifier on training set: {:.2f}'
     .format(knn.score(X_train, y_train)))
print('Accuracy of K-NN classifier on test set: {:.2f}'
     .format(knn.score(X_test, y_test)))

Accuracy of K-NN classifier on training set: 0.90
Accuracy of K-NN classifier on test set: 0.86

Vemos que la precisión que nos da es de 90% en el set de entrenamiento y del 86% para el de test.

NOTA: como verán utilizamos la clase KNeighborsClassifier de SciKit Learn puesto que nuestras etiquetas son valores discretos (estrellas del 1 al 5). Pero deben saber que también existe la clase KneighborsRegressor para etiquetas con valores continuos.

Precisión del modelo

Confirmemos la precisión viendo la Confusión Matrix y el Reporte sobre el conjunto de test, que nos detalla los aciertos y fallos:

pred = knn.predict(X_test)
print(confusion_matrix(y_test, pred))
print(classification_report(y_test, pred))

Cómo se ve la puntuación F1 es del 87%, bastante buena. NOTA: recuerden que este es sólo un ejercicio para aprender y tenemos MUY pocos registros totales y en nuestro conjunto de test. Por ejemplo de 2 estrellas sólo tiene 1 valoración y esto es evidentemente insuficiente.

Y ahora, la gráfica que queríamos ver!

Ahora realizaremos la grafica con la clasificación obtenida, la que nos ayuda a ver fácilmente en donde caerán las predicciones. NOTA: al ser 2 features, podemos hacer la gráfica 2D y si fueran 3 podría ser en 3D. Pero para usos reales, podríamos tener  más de 3 dimensiones y no importaría  poder visualizarlo sino el resultado del algoritmo.

h = .02  # step size in the mesh

# Create color maps
cmap_light = ListedColormap(['#FFAAAA', '#ffcc99', '#ffffb3','#b3ffff','#c2f0c2'])
cmap_bold = ListedColormap(['#FF0000', '#ff9933','#FFFF00','#00ffff','#00FF00'])

# we create an instance of Neighbours Classifier and fit the data.
clf = KNeighborsClassifier(n_neighbors, weights='distance')
clf.fit(X, y)

# Plot the decision boundary. For that, we will assign a color to each
# point in the mesh [x_min, x_max]x[y_min, y_max].
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
                         np.arange(y_min, y_max, h))
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])

# Put the result into a color plot
Z = Z.reshape(xx.shape)
plt.figure()
plt.pcolormesh(xx, yy, Z, cmap=cmap_light)

# Plot also the training points
plt.scatter(X[:, 0], X[:, 1], c=y, cmap=cmap_bold,
                edgecolor='k', s=20)
plt.xlim(xx.min(), xx.max())
plt.ylim(yy.min(), yy.max())
    
patch0 = mpatches.Patch(color='#FF0000', label='1')
patch1 = mpatches.Patch(color='#ff9933', label='2')
patch2 = mpatches.Patch(color='#FFFF00', label='3')
patch3 = mpatches.Patch(color='#00ffff', label='4')
patch4 = mpatches.Patch(color='#00FF00', label='5')
plt.legend(handles=[patch0, patch1, patch2, patch3,patch4])

    
plt.title("5-Class classification (k = %i, weights = '%s')"
              % (n_neighbors, weights))

plt.show()

Vemos las 5 zonas en las que se relacionan cantidad de palabras con el valor de sentimiento de la Review que deja el usuario.

Se distinguen 5 regiones que podríamos dividir así:

Es decir que “a ojo” una review de 20 palabras y Sentimiento 1, nos daría una valoración de 4 (zona celeste).

Con estas zonas podemos intuir ciertas características de los usuarios que usan y valoran la app:

  • Los usuarios que ponen 1 estrella tienen sentimiento negativo y hasta 25 palabras.
  • Los usuarios que ponen 2 estrellas dan muchas explicaciones (hasta 100 palabras) y su sentimiento puede variar entre negativo y algo positivo.
  • Los usuarios que ponen 3 estrellas son bastante neutrales en sentimientos, puesto que están en torno al cero y hasta unas 25 palabras.
  • Los usuarios que dan 5 estrellas son bastante positivos (de 0,5 en adelante, aproximadamente) y ponen pocas palabras (hasta 10).

Elegir el mejor valor de k

(sobre todo importante para desempatar o elegir los puntos frontera!)

Antes vimos que asignamos el valor n_neighbors=7 como valor de “k” y obtuvimos buenos resultados. ¿Pero de donde salió ese valor?. Pues realmente tuve que ejecutar este código que viene a continuación, donde vemos distintos valores k y la precisión obtenida.

k_range = range(1, 20)
scores = []
for k in k_range:
    knn = KNeighborsClassifier(n_neighbors = k)
    knn.fit(X_train, y_train)
    scores.append(knn.score(X_test, y_test))
plt.figure()
plt.xlabel('k')
plt.ylabel('accuracy')
plt.scatter(k_range, scores)
plt.xticks([0,5,10,15,20])

En la gráfica vemos que con valores k=7 a k=14 es donde mayor precisión se logra.

Clasificar y/o Predecir nuevas muestras

Ya tenemos nuestro modelo y nuestro valor de k. Ahora, lo lógico será usarlo! Pues supongamos que nos llegan nuevas reviews! veamos como predecir sus estrellas de 2 maneras. La primera:

print(clf.predict([[5, 1.0]]))

[5]

Este resultado nos indica que para 5 palabras y sentimiento 1, nos valorarán la app con 5 estrellas.

Pero también podríamos obtener las probabilidades que de nos den 1, 2,3,4 o 5 estrellas con predict_proba():

print(clf.predict_proba([[20, 0.0]]))

[[0.00381998 0.02520212 0.97097789 0. 0. ]]

Aquí vemos que para las coordenadas 20, 0.0 hay 97% probabilidades que nos den 3 estrellas. Puedes comprobar en el gráfico anterior, que encajan en las zonas que delimitamos anteriormente.

Conclusiones del algoritmo kNN

En este ejercicio creamos un modelo con Python para procesar y clasificar puntos de un conjunto de entrada con el algoritmo k-Nearest Neighbor. Cómo su nombre en inglés lo dice, se evaluán los “k vecinos más cercanos” para poder clasificar nuevos puntos. Al ser un algoritmo supervisado debemos contar con suficientes muestras etiquetadas para poder entrenar el modelo con buenos resultados. Este algoritmo es bastante simple y -como vimos antes- necesitamos muchos recursos de memoria y cpu para mantener el dataset “vivo” y evaluar nuevos puntos. Esto no lo hace recomendable para conjuntos de datos muy grandes. En el ejemplo, sólo utilizamos 2 dimensiones de entrada para poder graficar y ver en dos dimensiones cómo se obtienen y delimitan los grupos. Finalmente pudimos hacer nuevas predicciones y a raíz de los resultados, comprender mejor la problemática planteada.

Suscripción al Blog

Recibe el próximo artículo quincenal sobre Machine Learning y buenas prácticas Python 

Puedes hacer más ejercicios Machine Learning en Python  en nuestra categoría d Ejercicios paso a paso por ejemplo de Regresión Logística ó  clustering K-means ó comprender y crear una Sencilla Red Neuronal.

Recursos y enlaces del ejercicio

Más artículos de Interés sobre k-Nearest Neighbor (en Inglés)

Otras Herramientas:

GuardarGuardar

GuardarGuardar


El libro del Blog

Puedes ayudar a este autor comprando el libro ó lo puedes descargar gratuitamente. Aún está en borrador, pero apreciaré mucho tu ayuda! Contiene Extras descargables como el “Lego Dataset” utilizado en el artículo de Detección de Objetos.

The post Clasificar con K-Nearest-Neighbor ejemplo en Python first appeared on Aprende Machine Learning.

]]>
https://www.aprendemachinelearning.com/clasificar-con-k-nearest-neighbor-ejemplo-en-python/feed/ 26 5843
Arbol de Decisión en Python: Clasificación y predicción. https://www.aprendemachinelearning.com/arbol-de-decision-en-python-clasificacion-y-prediccion/ https://www.aprendemachinelearning.com/arbol-de-decision-en-python-clasificacion-y-prediccion/#comments Fri, 13 Apr 2018 08:17:43 +0000 https://www.aprendemachinelearning.com/?p=5633 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...

The post Arbol de Decisión en Python: Clasificación y predicción. first appeared on Aprende Machine Learning.

]]>
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 webscraping 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:

# Imports needed for the script
import numpy as np
import pandas as pd
import seaborn as sb
import matplotlib.pyplot as plt
%matplotlib inline
plt.rcParams['figure.figsize'] = (16, 9)
plt.style.use('ggplot')
from sklearn import tree
from sklearn.metrics import accuracy_score
from sklearn.model_selection import KFold
from sklearn.model_selection import cross_val_score
from IPython.display import Image as PImage
from subprocess import check_call
from PIL import Image, ImageDraw, ImageFont

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:

artists_billboard.shape

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:

artists_billboard.head()

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:

artists_billboard.groupby('top').size()

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:

sb.factorplot('artist_type',data=artists_billboard,kind="count")

Aqui vemos que tenemos más del doble de artistas masculinos que femeninos y unos 100 registros de canciones mixtas

sb.factorplot('mood',data=artists_billboard,kind="count", aspect=3)

Vemos que de 23 tipos de Mood, destacan 7 con picos altos. Además notamos que algunos estados de ánimo son similares

sb.factorplot('tempo',data=artists_billboard,hue='top',kind="count")

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)

sb.factorplot('genre',data=artists_billboard,kind="count", aspect=3)

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:

sb.factorplot('anioNacimiento',data=artists_billboard,kind="count", aspect=3)

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 (class_weight) del algoritmo de árbol de decisión para compensar esta diferencia.

En e l artículo “Clasificación con Datos Desbalanceados” te cuento todas las estrategias para equilibrar las clases

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

f1 = artists_billboard['chart_date'].values
f2 = artists_billboard['durationSeg'].values

colores=['orange','blue'] # si no estaban declarados previamente
tamanios=[60,40] # si no estaban declarados previamente

asignar=[]
asignar2=[]
for index, row in artists_billboard.iterrows():    
    asignar.append(colores[row['top']])
    asignar2.append(tamanios[row['top']])

plt.scatter(f1, f2, c=asignar, s=tamanios)
plt.axis([20030101,20160101,0,600])
plt.show()

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-.

def edad_fix(anio):
    if anio==0:
        return None
    return anio

artists_billboard['anioNacimiento']=artists_billboard.apply(lambda x: edad_fix(x['anioNacimiento']), axis=1);

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.

def calcula_edad(anio,cuando):
    cad = str(cuando)
    momento = cad[:4]
    if anio==0.0:
        return None
    return int(momento) - anio

artists_billboard['edad_en_billboard']=artists_billboard.apply(lambda x: calcula_edad(x['anioNacimiento'],x['chart_date']), axis=1);

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.

age_avg = artists_billboard['edad_en_billboard'].mean()
age_std = artists_billboard['edad_en_billboard'].std()
age_null_count = artists_billboard['edad_en_billboard'].isnull().sum()
age_null_random_list = np.random.randint(age_avg - age_std, age_avg + age_std, size=age_null_count)

conValoresNulos = np.isnan(artists_billboard['edad_en_billboard'])

artists_billboard.loc[np.isnan(artists_billboard['edad_en_billboard']), 'edad_en_billboard'] = age_null_random_list
artists_billboard['edad_en_billboard'] = artists_billboard['edad_en_billboard'].astype(int)
print("Edad Promedio: " + str(age_avg))
print("Desvió Std Edad: " + str(age_std))
print("Intervalo para asignar edad aleatoria: " + str(int(age_avg - age_std)) + " a " + str(int(age_avg + age_std)))

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:

f1 = artists_billboard['edad_en_billboard'].values
f2 = artists_billboard.index

colores = ['orange','blue','green']

asignar=[]
for index, row in artists_billboard.iterrows():    
    if (conValoresNulos[index]):
        asignar.append(colores[2]) # verde
    else:
        asignar.append(colores[row['top']])

plt.scatter(f1, f2, c=asignar, s=30)
plt.axis([15,50,0,650])
plt.show()

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)

# Mood Mapping 
artists_billboard['moodEncoded'] = artists_billboard['mood'].map( {'Energizing': 6, 
                                        'Empowering': 6,
                                        'Cool': 5, 
                                        'Yearning': 4, # anhelo, deseo, ansia
                                        'Excited': 5, #emocionado
                                        'Defiant': 3, 
                                        'Sensual': 2, 
                                        'Gritty': 3, #coraje 
                                        'Sophisticated': 4,
                                        'Aggressive': 4, # provocativo
                                        'Fiery': 4, #caracter fuerte
                                        'Urgent': 3, 
                                        'Rowdy': 4, #ruidoso alboroto
                                        'Sentimental': 4,
                                        'Easygoing': 1, # sencillo
                                        'Melancholy': 4, 
                                        'Romantic': 2, 
                                        'Peaceful': 1, 
                                        'Brooding': 4, # melancolico
                                        'Upbeat': 5, #optimista alegre
                                        'Stirring': 5, #emocionante
                                        'Lively': 5, #animado
                                        'Other': 0,'':0} ).astype(int)
# Tempo Mapping 
artists_billboard['tempoEncoded'] = artists_billboard['tempo'].map( {'Fast Tempo': 0, 'Medium Tempo': 2, 'Slow Tempo': 1, '': 0} ).astype(int)
# Genre Mapping 
artists_billboard['genreEncoded'] = artists_billboard['genre'].map( {'Urban': 4, 
                                          'Pop': 3, 
                                          'Traditional': 2, 
                                          'Alternative & Punk': 1,
                                         'Electronica': 1, 
                                          'Rock': 1, 
                                          'Soundtrack': 0, 
                                          'Jazz': 0,
                                          'Other':0,'':0} 
                                       ).astype(int)
# artist_type Mapping 
artists_billboard['artist_typeEncoded'] = artists_billboard['artist_type'].map( {'Female': 2, 'Male': 3, 'Mixed': 1, '': 0} ).astype(int)


# Mapping edad en la que llegaron al billboard
artists_billboard.loc[ artists_billboard['edad_en_billboard'] <= 21, 'edadEncoded']                         = 0
artists_billboard.loc[(artists_billboard['edad_en_billboard'] > 21) & (artists_billboard['edad_en_billboard'] <= 26), 'edadEncoded'] = 1
artists_billboard.loc[(artists_billboard['edad_en_billboard'] > 26) & (artists_billboard['edad_en_billboard'] <= 30), 'edadEncoded'] = 2
artists_billboard.loc[(artists_billboard['edad_en_billboard'] > 30) & (artists_billboard['edad_en_billboard'] <= 40), 'edadEncoded'] = 3
artists_billboard.loc[ artists_billboard['edad_en_billboard'] > 40, 'edadEncoded'] = 4

# Mapping Song Duration
artists_billboard.loc[ artists_billboard['durationSeg'] <= 150, 'durationEncoded']                          = 0
artists_billboard.loc[(artists_billboard['durationSeg'] > 150) & (artists_billboard['durationSeg'] <= 180), 'durationEncoded'] = 1
artists_billboard.loc[(artists_billboard['durationSeg'] > 180) & (artists_billboard['durationSeg'] <= 210), 'durationEncoded'] = 2
artists_billboard.loc[(artists_billboard['durationSeg'] > 210) & (artists_billboard['durationSeg'] <= 240), 'durationEncoded'] = 3
artists_billboard.loc[(artists_billboard['durationSeg'] > 240) & (artists_billboard['durationSeg'] <= 270), 'durationEncoded'] = 4
artists_billboard.loc[(artists_billboard['durationSeg'] > 270) & (artists_billboard['durationSeg'] <= 300), 'durationEncoded'] = 5
artists_billboard.loc[ artists_billboard['durationSeg'] > 300, 'durationEncoded'] = 6

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”:

drop_elements = ['id','title','artist','mood','tempo','genre','artist_type','chart_date','anioNacimiento','durationSeg','edad_en_billboard']
artists_encoded = artists_billboard.drop(drop_elements, axis = 1)

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.

artists_encoded[['moodEncoded', 'top']].groupby(['moodEncoded'], as_index=False).agg(['mean', 'count', 'sum'])

La mayoría de top 1 los vemos en los estados de ánimo 5 y 6 con 46 y 43 canciones

artists_encoded[['artist_typeEncoded', 'top']].groupby(['artist_typeEncoded'], as_index=False).agg(['mean', 'count', 'sum'])

Aqui están bastante repartidos, pero hay mayoría en tipo 3: artistas masculinos

artists_encoded[['genreEncoded', 'top']].groupby(['genreEncoded'], as_index=False).agg(['mean', 'count', 'sum'])

Los géneros con mayoría son evidentemente los géneros 3 y 4 que corresponden con Urbano y Pop

artists_encoded[['tempoEncoded', 'top']].groupby(['tempoEncoded'], as_index=False).agg(['mean', 'count', 'sum'])

El tempo con más canciones exitosas en el número 1 es el 2, tempo medio

artists_encoded[['durationEncoded', 'top']].groupby(['durationEncoded'], as_index=False).agg(['mean', 'count', 'sum'])

Están bastante repartidos en relación a la duración de las canciones

artists_encoded[['edadEncoded', 'top']].groupby(['edadEncoded'], as_index=False).agg(['mean', 'count', 'sum'])

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).

cv = KFold(n_splits=10) # Numero deseado de "folds" que haremos
accuracies = list()
max_attributes = len(list(artists_encoded))
depth_range = range(1, max_attributes + 1)

# Testearemos la profundidad de 1 a cantidad de atributos +1
for depth in depth_range:
    fold_accuracy = []
    tree_model = tree.DecisionTreeClassifier(criterion='entropy',
                                             min_samples_split=20,
                                             min_samples_leaf=5,
                                             max_depth = depth,
                                             class_weight={1:3.5})
    for train_fold, valid_fold in cv.split(artists_encoded):
        f_train = artists_encoded.loc[train_fold] 
        f_valid = artists_encoded.loc[valid_fold] 

        model = tree_model.fit(X = f_train.drop(['top'], axis=1), 
                               y = f_train["top"]) 
        valid_acc = model.score(X = f_valid.drop(['top'], axis=1), 
                                y = f_valid["top"]) # calculamos la precision con el segmento de validacion
        fold_accuracy.append(valid_acc)

    avg = sum(fold_accuracy)/len(fold_accuracy)
    accuracies.append(avg)
    
# Mostramos los resultados obtenidos
df = pd.DataFrame({"Max Depth": depth_range, "Average Accuracy": accuracies})
df = df[["Max Depth", "Average Accuracy"]]
print(df.to_string(index=False))

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.

# Crear arrays de entrenamiento y las etiquetas que indican si llegó a top o no 
y_train = artists_encoded['top']
x_train = artists_encoded.drop(['top'], axis=1).values 

# Crear Arbol de decision con profundidad = 4
decision_tree = tree.DecisionTreeClassifier(criterion='entropy',
                                            min_samples_split=20,
                                            min_samples_leaf=5,
                                            max_depth = 4,
                                            class_weight={1:3.5})
decision_tree.fit(x_train, y_train)

# exportar el modelo a archivo .dot
with open(r"tree1.dot", 'w') as f:
     f = tree.export_graphviz(decision_tree,
                              out_file=f,
                              max_depth = 7,
                              impurity = True,
                              feature_names = list(artists_encoded.drop(['top'], axis=1)),
                              class_names = ['No', 'N1 Billboard'],
                              rounded = True,
                              filled= True )
        
# Convertir el archivo .dot a png para poder visualizarlo
check_call(['dot','-Tpng',r'tree1.dot','-o',r'tree1.png'])
PImage("tree1.png")

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:

acc_decision_tree = round(decision_tree.score(x_train, y_train) * 100, 2)
print(acc_decision_tree)

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.

#predecir artista CAMILA CABELLO featuring YOUNG THUG
# con su canción Havana llego a numero 1 Billboard US en 2017

x_test = pd.DataFrame(columns=('top','moodEncoded', 'tempoEncoded', 'genreEncoded','artist_typeEncoded','edadEncoded','durationEncoded'))
x_test.loc[0] = (1,5,2,4,1,0,3)
y_pred = decision_tree.predict(x_test.drop(['top'], axis = 1))
print("Prediccion: " + str(y_pred))
y_proba = decision_tree.predict_proba(x_test.drop(['top'], axis = 1))
print("Probabilidad de Acierto: " + str(round(y_proba[0][y_pred]* 100, 2))+"%")

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

#predecir artista Imagine Dragons 
# con su canción Believer llego al puesto 42 Billboard US en 2017

x_test = pd.DataFrame(columns=('top','moodEncoded', 'tempoEncoded', 'genreEncoded','artist_typeEncoded','edadEncoded','durationEncoded'))
x_test.loc[0] = (0,4,2,1,3,2,3)
y_pred = decision_tree.predict(x_test.drop(['top'], axis = 1))
print("Prediccion: " + str(y_pred))
y_proba = decision_tree.predict_proba(x_test.drop(['top'], axis = 1))
print("Probabilidad de Acierto: " + str(round(y_proba[0][y_pred]* 100, 2))+"%")

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) y desbalanceado. 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.

Ya estás listo para el siguiente algoritmo: Random Forest!

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

Ahora que sabes árboles de decisión, ya puedes aprender Random Forest

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

GuardarGuardarGuardarGuardar


El libro del Blog (en desarrollo)

Puedes colaborar comprando el libro ó lo puedes descargar gratuitamente. Aún está en borrador, pero apreciaré mucho tu ayuda! Contiene Extras descargares como el “Lego Dataset” utilizado en el artículo de Detección de Objetos.

The post Arbol de Decisión en Python: Clasificación y predicción. first appeared on Aprende Machine Learning.

]]>
https://www.aprendemachinelearning.com/arbol-de-decision-en-python-clasificacion-y-prediccion/feed/ 74 5633