Definición | Aprende Machine Learning https://aprendemachinelearning.com en Español Tue, 17 Oct 2023 07:26:38 +0000 en-US hourly 1 https://wordpress.org/?v=6.4.3 https://aprendemachinelearning.com/wp-content/uploads/2017/11/cropped-icon_red_neuronal-1-32x32.png Definición | Aprende Machine Learning https://aprendemachinelearning.com 32 32 134671790 Seguimiento de Objetos con Yolo v8 y BYTETrack – Object Tracking https://aprendemachinelearning.com/seguimiento-de-objetos-con-yolo-v8-y-bytetrack-object-tracking/ https://aprendemachinelearning.com/seguimiento-de-objetos-con-yolo-v8-y-bytetrack-object-tracking/#comments Fri, 08 Sep 2023 11:15:20 +0000 https://www.aprendemachinelearning.com/?p=8271 Realiza un proyecto python de 100 líneas para detectar y seguir personas en video. Usaremos Yolo v8 y Bytetrack.

The post Seguimiento de Objetos con Yolo v8 y BYTETrack – Object Tracking first appeared on Aprende Machine Learning.

]]>

En artículos anteriores, hablamos sobre la clasificación de imágenes y sobre cómo hacer detección de objetos en tiempo real gracias a Yolo. Esta vez hablaremos sobre “Seguimiento de objetos” (Object Tracking en inglés) en donde sumamos una nueva “capa” de inteligencia dentro del campo de Visión Artificial.

La Problemática del rastreo de objetos

Imaginemos que tenemos un cámara de seguridad en donde aplicamos un modelo de Machine Learning como Yolo que detecta coches en tiempo real. Agregamos un “rectángulo rojo” (ó caja) sobre cada automóvil que se mueve. Bien. Queremos contabilizar cuántos de esos vehículos aparecen en pantalla durante una hora; ¿cómo hacemos?. Hasta ahora, sabemos los coches que hay en cada frame del video. En el primer fotograma hemos detectado 3 coches. En el segundo cuadro tenemos 3 coches. ¿Son los mismos ó son coches distintos? ¿Qué ocurre cuando en el siguiente fotograma aparece un cuarto coche? ¿Cuántos coches sumamos? 3 + 3 + 4 ? Tendremos un mal recuento en el transcurso de una hora, si no aplicamos un algoritmo adecuado para el rastreo de vehículos.

Espero que con ese ejemplo empieces a comprender la problemática que se nos plantea al querer hacer object tracking. Pero no es sólo eso, además de poder identificar cada objeto en un cuadro y mantener su identidad a lo largo del tiempo, aparecen otros problemas “clásicos”: la oclusión del objeto la superposición y la transformación.

  • Oclusión: cuando un objeto que estamos rastreando queda oculto momentáneamente o parcialmente por quedar detrás de una columna, farola ú otro objeto.
  • Superposición de objetos: ocurre cuando tenemos a dos jugadores de fútbol con camiseta blanca y uno pasa por detrás de otro, entonces el algoritmo podría ser incapaz de entender cuál es cada uno.
  • Transformación del objeto: tenemos identificada a una persona que camina de frente con una camiseta roja y luego cambia de rumbo y su camiseta por detrás es azul. Es la misma persona pero que en el transcurso de su recorrido va cambiando sus “features”.
  • Efectos visuales: ocurre cuando al cristal de un coche le da el sol y genera un destello, lo cual dificulta su identificación. O podría ser que pase de una zona soleada a una con sombra generando una variación en sus colores.

Algoritmos de Seguimiento:

Para poder realizar el object tracking y resolver los problemas antes mencionados se desarrollaron diversos algoritmos, siendo los más conocidos sort, deepsort, bytetrack y actualmente siguen apareciendo nuevos.

Lo básico que queremos de un algoritmo de detección es que primero identifique al objeto y que pasado el tiempo mantenga su “etiqueta”. Pero… que lo haga muy rápido, porque si estamos analizando un video en vivo no podemos congelar la imagen durante más de un segundo, ó resultará en una experiencia poco agradable.

Listemos los algoritmo de Tracking y algunas de sus características, más adelante comentaremos con un poco más de detalle el algoritmo de Byte Track, que es el que utilizaremos en el ejercicio.

  • Sort (Simple Online Realtime Tracking): utiliza la posición y el tamaño de la caja que contiene al objeto. Se predice la posición/trayectoria por su velocidad constante.
  • DeepSort: Mejora a Sort al agregar información sobre la apariencia del objeto mediante un vector creado a partir de las capas ocultas de una red neuronal profunda que debe ser entrenada.
  • StrongSort: Modifica las funciones de costo y métricas de DeepSort para mejorar sus resultados.
  • FairMOT: integra la identificación del objeto dentro de la propia red de detección encoder-decoder.
  • ByteTrack: utiliza las cajas de detección de alta y baja confianza para mantener trayectorias que puedan estar poco visibles durante el video.

En un principio de los tiempos, se intentaba poder identificar a una clase de objeto y mantener su localización. Actualmente y gracias al mayor poder de cómputo, el tipo de tarea/problema se conoce como “Multiple object tracking with Re-Identification“; en donde podemos detectar diversas clases y mantener su identificación con el paso del tiempo.

Casos de Uso

Estos son algunas de las aplicaciones que puedes realizar con Object Tracking

Seguimiento de personas / objeto de interés

Fuente de la imágen: artículo

Contabilizar vehículos (u objetos)

Entrada en una zona determinada

Trazado de rutas

¿Cómo funciona ByteTrack para seguimiento de Objetos?

ByteTrack utiliza IoU en su algoritmo. La mayoría de métodos obtienen las identidades asociando cajas de detección si los scores son mayores a un umbral (por ej. mayor a 80%). Los objetos con menor score de detección -por ej. objetos que estén parcialmente ocultos tras “una farola”- son eliminados causando trayectorias de identificación erróneas. Para resolver este problema, ByteTrack utiliza los scores de confianza altos y bajos.

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

Comprendamos el algoritmo paso a paso:

Inicialización: Tenemos las entradas como una secuencia de Video “V”, el detector de objetos (Yolo) “Det”; el límite de confianza de score “L”. La salida será “T” siendo las rutas que sigue en el video. Comenzamos con T vacíos.

Para cada cuadro de video, predecimos las cajas de detección y scores usando Yolo. Separamos todas las cajas en dos partes: “D_high” y “D_low” según su puntaje alto o bajo del umbral “L”.

Luego de separar las cajas con los puntajes Altos y Bajos, usamos el Kalman Filter para predecir las nuevas ubicaciones en el frame actual de cada Trayectoria T.

La primer asociación se realiza entre las cajas de Score alto D_high y todos los tracks T (incluyendo los tracks perdidos “T_lost”).

Mantenemos las detecciones que quedaron sin asociarse en “D_remain” y los trayectos sin pareja en “T_remain”.

La segunda asociación intentará emparejar las cajas de bajo puntaje D_low y las restantes rutas “T_remain” de la primer asociación.

Seguiremos manteniendo las trayectorias huérfanas en “T_re-remain” y borrar todas las cajas sin emparejar de bajo puntaje.

Para los tracks sin pareja de esta segunda iteración, las pondremos en T_lost. Para cada track en T_lost si se mantiene sin relación por “30 frames”, lo eliminamos de “T”.

Finalmente inicializamos nuevos trayectos desde las cajas de alto score sin emparejar que teníamos en D_remain de la primer asociación.

NOTA: para realizar las asociaciones podemos usar métodos de “location” o “feature”. La principal innovación del algoritmo de ByteTrack es el uso de los scores de alta y baja confianza de las cajas detectadas.

Puedes revisar la implementación oficial en Python de ByteTrack en este enlace.

Comentario sobre Kalman Filter

No sólo la apariencia del objeto (features) es importante si no también la información sobre su movimiento y trayectoria. El Kalman filter predice donde estará un objeto que estaba en el frame t-1 en el próximo frame t. La distancia entre la predicción y la posición real detectada será el costo de la función. El Kalman filter es un filtro Lineal y asume el mismo ruido para todos los objetos.

¿Estado del arte?

En la siguiente gráfica vemos que ByteTrack tiene un buen equilibrio entre velocidad de detección y predicción de trayectorias, siendo el mejor de su momento (oct-2021), hasta principios de 2022. En 2022 fue superado por BoT-SORT y a finales de ese mismo año por SMILETrack.

Ejercicio: seguimiento de Skaters

Vamos a crear un script de 100 líneas en donde utilizaremos un modelo Yolo v8 preentrenado para la detección de 80 clases de objetos diferentes, incluyendo personas y skates. Iteraremos por los frames de un video en donde realizaremos la detección y alimentaremos con sus features al algoritmo de rastreo ByteTrack que se encargará de identificar al objeto.

Si tenemos éxito, veremos cómo el “objeto” se mantiene con el mismo identificador durante el video.

Si el algoritmo falla en su detección, asignará un nuevo ID, pues creerá que se trata de un objeto nuevo.

Crear el Environment

Puedes clonar el repositorio GitHub del ejercicio antes de empezar, para contar con los archivos necesarios.

Crea un nuevo ambiente Python utilizando Anaconda ejecutando:

conda create -n tracking python=3.9 numpy

Activa el ambiente

conda activate tracking

Instala ahora los paquetes con las versiones necesarias mediante pip

pip install -r requirements.txt
Las 80 clases que podemos detectar con el modelo standard de YOLO.

Código python

Primero importamos los paquetes que utilizaremos

import cv2
import numpy as np
import torch
from ultralytics.nn.autobackend import AutoBackend
from ultralytics.yolo.data.dataloaders.stream_loaders import LoadImages
from ultralytics.yolo.utils.ops import non_max_suppression, scale_boxes
from ultralytics.yolo.utils.plotting import Annotator, colors

from bytetrack.byte_tracker import BYTETracker

Inicialización de variables, aqui elegimos el video mp4 al que le aplicaremos la detección, en mi ejemplo el archivo se llama skateboard_01.mp4.

save_vid = False
video_file = 'skateboard_01.mp4'
vid_writer = None
save_path = video_file[:-4] + '_output.mp4'

conf_thres = 0.25
iou_thres = 0.45
classes = [0, 36]  # person, skateboard
agnostic_nms = False
max_det = 100
line_thickness = 2
imgsz = (640, 640)
vid_stride = 1

Cargamos el modelo preentrenado “nano” de Yolo (el más pequeño), la primera vez, el modelo se descargará.

detection_model = AutoBackend("yolov8n.pt")
detection_model.warmup()
stride, names, pt = detection_model.stride, detection_model.names, detection_model.pt

Instanciamos el algoritmo de Detección:

bytetracker = BYTETracker(
    track_thresh=0.6, match_thresh=0.8, track_buffer=120, frame_rate=30
)
tracker = bytetracker

Creamos un “loader” de las imágenes del video que vamos a procesar e inicializamos el Archivo de video mp4 de Salida:

dataset = LoadImages(
    video_file,
    imgsz=imgsz,
    stride=stride,
    auto=pt,
    transforms=None,
    vid_stride=vid_stride,
)
path, im, im0s, vid_cap, s = next(iter(dataset))

fps = vid_cap.get(cv2.CAP_PROP_FPS)
w = int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH))
h = int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
vid_writer = cv2.VideoWriter(
    save_path, cv2.VideoWriter_fourcc(*"mp4v"), fps, (w, h)
)

Entramos al Loop principal; aqui, realizaremos la detección de los objetos y luego aplicaremos el algoritmo de seguimiento para “re-identificar” objetos. Luego “imprimimos” en pantalla (frame) una caja y su ID.

for frame_idx, batch in enumerate(dataset):
    path, im, im0s, vid_cap, s = batch
    detections = np.empty((0, 5))
    im = torch.from_numpy(im).to("cpu").float()  # uint8 to fp16/32
    im = torch.unsqueeze(im/255.0, 0)

    result = detection_model(im)

    p = non_max_suppression(
        result, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det
    )

    for i, det in enumerate(p):
        p, im0, _ = path, im0s.copy(), getattr(dataset, "frame", 0)

        if det is not None and len(det):
            det[:, :4] = scale_boxes(
                im.shape[2:], det[:, :4], im0.shape
            ).round()  # rescale boxes to im0 size

        track_result = tracker.update(det.cpu(), im0)

        annotator = Annotator(im0, line_width=line_thickness, example=str(names))

        # dibujar los contornos de los objetos detectados
        if len(track_result) > 0:
            for j, (output) in enumerate(track_result):
                bbox = output[0:4]
                id = int(output[4])  # integer id
                cls = int(output[5]) # integer class
                conf = output[6]
                label = f"{id} {names[cls]} {conf:.2f}"
                annotator.box_label(bbox, label, color=colors(cls, True))

    im0 = annotator.result()
    cv2.imshow(str(p), im0)  # mostrar en pantalla
    cv2.waitKey(1)

    vid_writer.write(im0)  # guardar frame en video

vid_writer.release()
cv2.destroyAllWindows()

En menos de 100 líneas de código podemos procesar videos y detectar objetos mediante Yolov8 y ByteTrack.

Video de salida ejemplo

Conclusión

En este artículo aprendimos los problemas y puntos clave a resolver para llevar a cabo el seguimiento de objetos en el campo de la Visión Artificial. Cada año aparecen nuevos algoritmos que complementan la detección de múltiples objetos (siendo la mejor en mi opinión Yolo) permitiendo el rastreo en tiempo real y preciso de objetos. Gracias a ello, podemos realizar trazado de rutas ó comprender cuando un objeto entra en una zona determinada para “disparar las alarmas/acciones” necesarias…

Recuerda que tienes el ejercicio completo en mi repositorio de Github. Tienes la opción de ejecutar en una Jupyter Notebook o mediante un script de Python al que puedes pasar como parámetro el video mp4 que quieras probar.

Nos vemos en la próxima!

Recursos / Enlaces

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

The post Seguimiento de Objetos con Yolo v8 y BYTETrack – Object Tracking first appeared on Aprende Machine Learning.

]]>
https://aprendemachinelearning.com/seguimiento-de-objetos-con-yolo-v8-y-bytetrack-object-tracking/feed/ 3 8271
¿Cómo funcionan los Transformers? en Español https://aprendemachinelearning.com/como-funcionan-los-transformers-espanol-nlp-gpt-bert/ https://aprendemachinelearning.com/como-funcionan-los-transformers-espanol-nlp-gpt-bert/#comments Tue, 08 Nov 2022 08:54:00 +0000 https://www.aprendemachinelearning.com/?p=7771 El Machine Learning cambió para siempre con la llegada en 2017 de un paper llamado Attention is all you need en donde se presentaba una nueva arquitectura para NLP: Los Transformers

The post ¿Cómo funcionan los Transformers? en Español first appeared on Aprende Machine Learning.

]]>
Imagen creada por el Autor utilizando el modelo de text-to-img StableDiffusion

Los Transformers aparecieron como una novedosa arquitectura de Deep Learning para NLP en un paper de 2017 “Attention is all you need” que presentaba unos ingeniosos métodos para poder realizar traducción de un idioma a otro superando a las redes seq-2-seq LSTM de aquel entonces. Pero lo que no sabíamos es que este “nuevo modelo” podría ser utilizado en más campos como el de Visión Artificial, Redes Generativas, Aprendizaje por Refuerzo, Time Series y en todos ellos batir todos los records! Su impacto es tan grande que se han transformado en la nueva piedra angular del Machine Learning.

En este artículo repasaremos las piezas fundamentales que componen al Transformer y cómo una a una colaboran para conseguir tan buenos resultados. Los Transformers y su mecanismo de atención posibilitaron la aparición de los grandes modelos generadores de texto GPT2, GPT3 y BERT que ahora podían ser entrenados aprovechando el paralelismo que se alcanza mediante el uso de GPUs.

Agenda

  • ¿Qué son los transformers?
  • Arquitectura
    • General
    • Embeddings
    • Positional Encoding
    • Encoder
      • Mecanismo de Atención
      • Add & Normalisation Layer
      • Feedforward Network
    • Decoder
    • Salida del Modelo
  • Aplicaciones de los Transformers
    • BERT
    • GPT-2
    • GPT-3
  • Resumen

¿Qué son los transformers en Machine Learning?

En el paper original de 2017 “Attention is all you need” aparece el diagrama con la novedosa arquitectura del Transformer, que todos deberíamos tatuarnos en un brazo. Esta arquitectura surge como una solución a problemas de aprendizaje supervisado en Procesamiento del Lenguaje Natural, obteniendo grandes ventajas frente a los modelos utilizados en ese entonces. El transformer permitía realizar la traducción de un idioma a otro con la gran ventaja de poder entrenar al modelo en paralelo; lo que aumentaba drásticamente la velocidad y reducción del coste; y utilizando como potenciador el mecanismo de atención, que hasta ese momento no había sido explotado del todo. Veremos que en su arquitectura utiliza diversas piezas ya existentes pero que no estaban combinadas de esta manera. Además el nombre de “Todo lo que necesitas es Atención” es a la vez un tributo a los Beatles y una “bofetada” a los modelos NLP centrados en Redes Recurrentes que en ese entonces estaban intentando combinarlos con atención. De esta sutil forma les estaban diciendo… “tiren esas redes recurrentes a la basura”, porque el mecanismo de atención NO es un complemento… es EL protagonista!

All you need is Love Attention

The Beatles

Con el tiempo, esta arquitectura resultó ser flexible y se pudo utilizar para tareas más allá del NLP, además de para la generación de texto, clasificación, resumen de contenidos también pudo traspasar esa frontera y ser aplicado en Visión Artificial, Generación de Audio, Predicción de Series Temporales y Aprendizaje por Refuerzo.

La Arquitectura Encoder-Decoder del Transformer

Veamos primero la gran foto de los Transformers.

Visión General

Estás viendo el dibujo que deberías de llevar en tu próxima camiseta:

La Arquitectura de los Transformers

La primera impresión puede ser algo intimidante, pero vayamos poco a poco. Si pensamos en el modelo como una caja negra, sería simplemente:

Entrada -> Transformer -> Salida

Vemos que con una entrada de texto “Hola” obtenemos la salida “Hello”.

Si hacemos un poco de zoom en esa caja, veremos dos componentes principales: Encoders y Decoders.

La entrada pasará por una serie de Encoders que se encadenan uno tras otro y luego envían su salida a otra serie de Decoders hasta emitir la Salida final. En el paper original, se utilizan 6 encoders y 6 decoders. Notaremos un recuadro llamado “Target” donde estamos pasando el Output Deseado al modelo para entrenar; pero obviamente que no lo pasaremos al momento de la inferencia; al menos no completo (más adelante en el artículo).

Observamos ahora con mayor detalle cómo está compuesto un Encoder y un Decoder y veremos que son bastante parecidos por dentro.

En su interior tanto Encoder como decoder cuentan con un componente de Atención (o dos) y una red neuronal “normal” como salidas.

Para comenzar a evaluar con mayor detalle las partes del Transformer, primero deberemos generar un Embedding de la entrada y luego entrar al Encoder. Así que veamos eso primero.

Embeddings

El uso de embeddings existía en NLP desde antes de los Transformers, puede que estés familiarizado con modelos como el word-2-vec y puede que ya hayas visto las proyecciones creadas con miles de palabras donde los embeddings logran agrupar palabras de manera automática en un espacio multidimensional. Entonces conceptos como “hombre y mujer”, distanciado de “perro y gato” veremos nombres de países juntos y profesiones ó deportes también agrupados en ese espacio. Primero convertimos las palabras a tokens, es decir a un valor numérico asociado, porque recordemos que las redes neuronales únicamente pueden procesar números y no cadenas de texto.

Ejemplo de Word Embeddings y una proyección en 3D.

Entonces convertimos una palabra a un número, ese número a un vector de embeddings de 512 dimensiones (podría ser de otro valor, pero el propuesto en el paper fue este).

Entonces:

  1. Palabra -> Token(*)
  2. Token -> Vector n-dimensional

(*) NOTA: una palabra podría convertirse en más de un token

La parte novedosa que introduce el paper de los transformers, es el “Positional Encoding” a esos embeddings…

Positional Encoding

Una vez que tenemos los embeddings, estaremos todos de acuerdo que el orden en que pasemos las palabras al modelo, será importante. De hecho podríamos alterar totalmente el significado de una oración si mezclamos el orden de sus palabras o hasta podría carecer totalmente de significado.

La casa es verde != verde casa la es

Entonces necesitamos pasar al modelo los tokens pudiendo especificar de alguna manera su posición.

Aquí, de paso comentaremos dos novedades de los Transformers: la solución a este posicionamiento pero también esto permite el poder enviar en simultáneo TODOS los tokens al modelo, algo que anteriormente no se podía hacer, por lo que se pasaba “palabra por palabra” una después de la otra, resultando en una menor velocidad.

Para resolver el posicionamiento, se agrega una valor secuencial al embedding que se asume que la red podrá interpretar. Si aparece el token “perro” como segunda palabra ó como décima, mantendrá en esencia el mismo vector (calculado anteriormente en el embedding) pero con un ligero valor adicional que lo distinguirá y que será el que le de la pista a la red neuronal de interpretar su posición en la oración.

En el paper se propone una función sinusoidal sobre el vector de 512 dimensiones del embedding. Entonces ese vector de embeddings del token “perro” será distinto si la palabra aparece primera, segunda, tercera.

Y ahora sí, podemos dar entrada al Encoder de los Transformers.

Encoder

El Encoder está compuesto por la capa de “Self attention” y esta conectada a una red feed forward. Entre las entradas y salidas se aplica la Skip Connection (ó ResNet) y un Norm Layer.

El mecanismo de Atención, es una de las implementaciones novedosas que propone el paper. Veremos que logra procesar en paralelo, manteniendo la ventaja de entreno mediante GPU.

El Encoder tiene como objetivo procesar la secuencia de los tokens de entrada y ser a su vez la entrada del mecanismo de atención del Decoder. No olvides que realmente se encadenan varios Encoders con varios Decoders (no se utilizan sólo uno en la práctica).

El -dichoso- mecanismo de Atención

Primero que nada, ¿Para qué queremos un mecanismo de Atención? El mecanismo de atención nos ayuda a crear y dar fuerza a las relaciones entre palabras en una oración. Si tenemos el siguiente enunciado:

El perro estaba en el salón, durmiendo tranquilo.

Nosotros comprendemos fácilmente que la palabra “tranquilo” se refiere al perro y no al “salón”. Pero a la red neuronal que comienza “en blanco”, sin saber nada de las estructuras del lenguaje (español, pero podría ser cualquier idioma), y que además ve la misma frase como valores numéricos:

5 186 233 7 5 1433 567 721

NOTA: no olvides que cada token, por ej. el nº5 corresponde a un embedding de n-dimensiones de 512 valores. Por simplificar usamos el 5 como reemplazo de “El”, por no escribir sus 512 componentes.

¿Cómo podrá entender que ese último token “721” está afectando a la segunda palabra “186”?
Por eso surgen los mecanismos de atención; para lograr dar más -o menos- importancia a una palabra en relación a las otras de la oración.

La solución que presentan los Transformers en cuanto a la atención, es la construcción de un “Diccionario Blando” (Soft Dictionary). ¿Qué es esto de un diccionario blando?
En programación, es probable que conozcas el uso de diccionarios de tipo “clave-valor” un típico “hashmap” en donde ante una entrada, obtengo un valor dict[“perro”]=0.78.
Para el “diccionario de atenciones” podría ser que si pedimos la atención de “tranquilo vs perro” de la oración, nos devuelva un 1 indicando mucha atención, pero si le pedimos “tranquilo vs salón” nos devuelva un -1.

Pero… eso no es todo. Nuestro Diccionario es “Suave/Blando”, esto quiere decir que no se va a saber “de memoria” el resultado de una clave. Si alteramos un poco la oración, el diccionario tradicional fallaría:

El perro estaba durmiendo en un salón tranquilo.

Ahora la clave de atención para “tranquilo vs salón” deberá pasar de -1 a ser cercana a 1.

Para poder calcular la atención, se utilizan 3 matrices llamadas “Query-Key-Value” que van a operar siguiendo una fórmula que nos devuelve un “score” o puntaje de atención.

  • En la matriz Q de Query tendremos los tokens (su embedding) que estamos evaluando.
  • En la matriz K de Key tendremos los tokens nuevamente, como claves del diccionario.
  • En la matriz V de Value tendremos todos los tokens (su embedding) “de salida”.

El resultado de la atención será aplicar la siguiente fórmula:

Si nos olvidamos del Softmax y el “multihead” (se definirá a continuación), podemos simplificar la fórmula diciendo que:
La atención será multiplicación matricial Q por la transpuesta de K; a eso le llamamos “factor”; y ese factor multiplicado por V.

¿Y eso qué significa? ¿Por qué estamos operando de esa manera? Si recuerdas, tanto Q como K son los valores de los Embeddings, es decir, cada token es un vector n-dimensional. Al hacer el producto vectorial obtenemos matemáticamente la “Similitud” entre los vectores. Entonces si el embedding estuviera funcionando bien, la similitud entre “nieve” y “blanco” deberían estar más cercanas que “nieve” y “negro”. Entonces cuando dos palabras sean similares, tendremos un valor positivo y mayor que si son opuestos, donde obtendríamos un valor negativo (dirección contraria). Este factor se multiplica por la matriz de Valor conformando el Score final de atención para cada relación entre pares de <<palabras>> que estamos evaluando.

Como estamos trabajando con matrices, seguimos aprovechando la capacidad de calcular todo a la vez y poder hacerlo acelerado por GPU.

Más de una atención: Multi-Head Attention

…hay más en el paper, porque el tipo de atención que vamos a calcular se llama “Multi-head Attention“. ¿Qué son esas “Multi-cabezas”??? Lo que se hace es que en vez de calcular la atención de todos los tokens de la oración una vez con las 512 dimensiones (provenientes del embedding), subdividiremos esos valores en grupos y de cada uno, calcular su atención. En el paper proponen “8 heads” con lo que entonces calcularemos “8 atenciones de a 64 valores del embeddings” por cada token! Esto a mi modo de entender es algo bastante arbitrario pero por arte de “magia matemática” funciona… Luego de calcular esas 8 cabezas, (esas 8 atenciones) haremos un promedio de los valores de cada atención entre tokens.

Si volvemos al ejemplo para la clave de “tranquilo vs perro” calcularemos 8 atenciones y al promediarlas deberíamos obtener un valor cercano a 1 (para la 1er oración).

Cuando terminemos de entrenar el modelo Transformer completo, podríamos intentar analizar y entender esas matrices de atención creadas y tratar de comprenderlas. Algunos estudios muestran que la “multi-atención” logra representar relaciones como la de “artículo-sustantivo” ó “adjetivo-sustantivo”, “verbo sustantivo” lo cual me sigue pareciendo algo increíble.

3 tipos de atención: Propia, Cruzada y Enmascarada

Prometo que esto ya es lo último que nos queda comprender sobre los mecanismos de atención… A todo lo anterior, hay que sumar que tenemos 3 tipos distintos de atención

  1. Self Attention
  2. Cross Attention
  3. Masked Attention

Su comportamiento es igual que al descripto anteriormente pero con alguna particularidad:

Self Attention se refiere que crearemos los valores y las matrices de Q-K-V a partir de las propias entradas de los tokens de entrada.

En la Atención Cruzada, vemos cómo utilizamos como entradas en el Decoder los valores obtenidos en el Encoder. Esto es lo que hará que con sólo el valor (Value) del Output pueda modelar la salida de atención buscada, esto es importante porque al final es lo que condicionará mayormente la “traducción” que está haciendo internamente el Decoder!

Y la llamada “Masked attention” se refiere a que enmascaramos parte triangular superior de la matriz de atención al calcularla para no caer en “data-leakage”, es decir, para no “adelantar información futura” que el Output no podría tener en su momento. Esto puede resultar confuso, intentaré aclararlo un poco más. En el Encoder, cuando tenemos los valores de entrada y hacemos “self-attention” dijimos que calculamos la atención de “todos contra todos” porque todos los tokens de entrada son conocidos desde el principio. Si recordamos la frase anterior:

El perro estaba en el salón, durmiendo tranquilo.

Aquí podemos calcular tanto la atención para la clave “perro-tranquilo” y también la de “tranquilo-perro”

Sin embargo -si suponemos que estamos traduciendo al inglés- en el output tendremos

“The dog was in the living room, sleeping peacefully”

PERO hay un detalle; para poder generar el output al momento de la inferencia, generaremos de a una palabra por vez, entonces iremos produciendo el siguiente output:

T1 – The
T2 – The dog
T3 – The dog was
T4 – The dog was in …

Esto quiere decir que vamos generando los tokens de a uno a la vez por lo que al principio no podríamos conocer la relación entre “dog-peacefully” pues esta relación aún no existe!

Entonces diferenciemos esto:

-> Al momento de entrenar el modelo pasamos el output deseado COMPLETO a las matrices de QKV de “Masked Attention” para entrenar en paralelo; al estar enmascarado, es como si estuviésemos simulando la secuencia “The, The dog, The dog was…”

-> Al momento de inferencia REALMENTE tendremos uno a uno los tokens como una secuencia temporal, realmente, iremos agregando de a una palabra del output a la cadena de salida a la vez.

Al enmascarar la matriz Q*K en su diagonal superior, prevenimos obtener valores “futuros” en la relación de atención entre sus tokens.

Short residual skip Connections y Layer Normalization

Al utilizar Skip Connections permitimos mantener el valor de origen del input a través de las deep neural networks evitando que sus pesos se desvanezcan con el paso del tiempo. Esta técnica fue utilizada en las famosas ResNets para clasificación de imágenes y se convirtieron en bloques de construcción para redes profundas. También es sumamente importante la Normalización en RRNN. Previene que el rango de valores entre capas cambie “demasiado bruscamente” logrando hacer que el modelo entrene más rápido y con mayor capacidad de generalización.

Feed Forward Network

La salida final del Encoder la dará una Red Neuronal “normal”, también llamada MLP ó capa densa. Se agregarán dos capas con Dropout y una función de activación no lineal.

Decoder

Ahora que conocemos los componentes del Encoder, podemos ver que con esos mismos bloques podemos crear el Decoder.

Al momento de entrenar, estaremos pasando el Input “hola amigos” y Output “hello friends” (su traducción) al mismo tiempo al modelo.

Tradicionalmente, usamos la salida únicamente para “validar” el modelo y ajustar los pesos de la red neuronal (durante el backpropagation). Sin embargo en el Transformer estamos usando la salida “hello friends” como parte del aprendizaje que realiza el modelo.

Entonces, el output “hello friends” es también la “entrada” del decoder hacia embeddings, posicionamiento y finalmente ingreso a la Masked Self Attention que comentamos antes (para los valores de Q,K,V).

De aquí, y pasando por la Skip Connection y Normalización (en la gráfica “Add & Norm) entramos al segundo mecanismo de Atención Cruzada que contiene para las matrices Query y Key” la salida del Encoder y como Value la salida de la Masked Attention.

Nuevamente Suma y Normalización, entrada a la Feed Forward del Decoder.

RECORDAR: “N encoder y N decoders”

No olvidemos que si bien estamos viendo en mayor detalle “un encoder” y “un decoder”, la arquitectura completa del Transformer implica la creación de (en el paper original) 6 encoders encadenados que enlazan con otros 6 decoders.

Salida final del Modelo

La salida final del modelo pasa por una última capa Lineal y aplicar Softmax. El Softmax, por si no lo recuerdas nos dará un valor de entre 0 y 1 para cada una de las posibles palabras (tokens) de salida. Entonces si nuestro “lenguaje” total es de 50.000 posibles tokens (incluyendo signos de puntuación, admiración, interrogación), encontraremos a uno de ellos con valor más alto, que será la predicción.

Si yo te dijera “en casa de herrero cuchillo de …” y vos tuvieras que predecir la próxima palabra, de entre todas las posibles en el castellano seguramente elegirías “palo” con probabilidad 0,999. Ahí acabas de aplicar Softmax intuitivamente en tu cabeza, y descartaste al resto de 49.999 palabras restantes.

Repaso de la arquitectura

Repasemos los puntos fuertes de la arquitectura de los Transformers:

  • La arquitectura de Transformers utiliza Encoders y Decoders
  • El Transformer permite entrenar en paralelo y aprovechar el GPU
  • Utiliza un mecanismo de atención que cruza en memoria “todos contra todos los tokens” y obtiene un score. Los modelos anteriores como LSTM no podían memorizar textos largos ni correr en paralelo.
  • El mecanismo de atención puede ser de Self Attention en el Encoder, Cross Attention ó Masked Attention en el Decoder. Su funcionamiento es igual en todos los casos, pero cambian los vectores que utilizamos como entradas para Q,K,V.
  • Se utiliza El Input pero también la Salida (el Output del dataset) para entrenar al modelo.

Aplicaciones de los Transformers

Los Transformers se convirtieron en el modelo “de facto” para todo tipo de tareas de NLP, incluyendo Traducción de idiomas (como vimos), clasificación de texto, resumen de textos y Question-Answering. Sólo basta con modificar los datasets que utilizamos para entrenar al modelo y la “salida final del modelo”, manteniendo al resto de arquitectura.

A raíz de poder entrenar mediante GPU, reducción de tiempo y dinero, surgieron varios modelos para NLP que fueron entrenados con datasets cada vez más grandes, con la creencia de que cuantas más palabras, más acertado sería el modelo, llevándolos al límite. Ahora, parece que hemos alcanzado un tope de acuracy, en el cual no vale la pena seguir extendiendo el vocabulario.

Vemos 3 de estos grandes modelos de NLP y sus características

BERT – 2018

BERT (Bidirectional Encoder Representations from Transformers) aparece en 2018, desarrollado por Google y utiliza sólo la parte del Encoder de los Transformers. Este modelo fue entrenado con todos los artículos de la Wikipedia en Inglés que contiene más de 2500 millones de palabras. Esto permitió generar un modelo “pro-entrenado” en inglés muy poderoso que podía ser utilizado para múltiples tareas de NLP. Además, al ser Open Source, permitió la colaboración y extensión por parte de la comunidad científica. El poder utilizar un modelo pre-entrenado tan grande, preciso y potente, permite justamente “reutilizar” ese conocimiento sin necesidad de tener que entrenar nuevamente un modelo de NLP, con el coste y tiempo (e impacto medioambiental) que puede conllevar.

Además BERT contenía algunas novedades, por ejemplo, en vez de utilizar un “Embeddings único y estático”, implementó un mecanismo en donde la misma palabra (token) podría devolver un vector distinto (de “embeddings”) de acuerdo al contexto de la oración de esa palabra.

Cuando salió BERT, batió muchos de los records existentes en datasets como SQuAD, GLUE y MultiNLI.

GPT-2 – 2019

GPT es un modelo de generación de texto creado por Open AI en 2019 y que contiene 1500 millones de parámetros en la configuración de su red neuronal profunda.

Al ser entrenado para generar “siguiente palabra”, pasa a convertirse en un problema de tipo “no supervisado”. Su re-entreno fue realizado usando BooksCorpus, un dataset que contiene más de 7,000 libros de ficción no publicados de diversos géneros.

Este modelo generó polémica en su momento debido a que empezaba a crear textos similares a lo que podía escribir una persona, con lo cual antes de ser lanzado, se temió por su mal uso para generar contenido falso en internet. Había sido anunciado en febrero y recién fue accesible al público, de manera parcial en Agosto. Los modelos de tipo GPT utilizan únicamente la parte de “Decoder” del Transformer.

Ejercicio Práctico: Crea tu propio chatbot con GPT-2 en Español!

GPT-3 – 2020

Lanzado en 2020, la red de GPT-3 tiene 175.000 millones de parámetros. Entrenado con textos de internet que contienen más de 500.000 millones de tokens.

En GPT-3, la generación de textos por la IA puede alcanzar un nivel literario que pasa inadvertido por los humanos, el modelo es capaz de mantener la coherencia en textos largos y debido a su gran conocimiento del mundo, crear un contexto y párrafos muy reales.

Otra de las novedades que trajo GPT3 es que lograba realizar tareas “inesperadas” de manera correcta, sin haber sido entrenado para ello. Por ejemplo, puede realizar Question-Answer, crear diálogos ó hasta escribir código en Python, Java o traducir idiomas. A este tipo de aprendizaje “no buscado” le llamamos “One Shot Learning”.

El Codigo de GPT-3 aún no ha sido liberado, hasta la fecha.

Más allá del NLP

Al comportarse tan bien para tareas de NLP, se comenzó a utilizar esta arquitectura adaptada para Clasificación de imágenes en Computer Vision y también logró superar en muchos casos a las Redes Convolucionales! A este nuevo modelo se le conoce como Vision Transformer o “Vit”.

También podemos utilizar Transformers para pronóstico de Series Temporales (Time Series). Este caso de uso tiene mucha lógica si lo pensamos, porque al final es muy parecido a “predecir la próxima palabra”, pero en su lugar “predecir la próxima venta”, ó stock…

Los Transformers están siendo utilizados en modelos generativos de música o como parte de los modelos de difusión text-to-image como Dall-E2 y Stable Diffusion.

Por último, los Transformers también están siendo utilizados en Aprendizaje por Refuerzo, donde también tienen sentido, pues la fórmula principal de este tipo de problemas también contiene una variable temporal/secuencial.

Y en todos estos campos, recién está empezando la revolución! No digo que será el Transformer quien reemplace al resto de Arquitecturas y modelos de Machine Learning existentes, pero está siendo un buen motivo para cuestionarlos, replantearlos y mejorar!

Resumen

Los Transformers aparecieron en un paper de 2017 implementando un eficiente mecanismo de atención como punto clave para resolver problemas de traducción en el campo del Procesamiento del Lenguaje Natural. Como bien sabemos, el lenguaje escrito conlleva implícitamente un orden temporal, secuencial que hasta aquel entonces era una de las barreras para no poder crear modelos extensos, pues impedía el aprovechamiento de GPU. La nueva arquitectura rompía esa barrera utilizando unos sencillos trucos: utilizar todos los token al mismo tiempo (en vez de uno en uno) y enmascarar la multiplicación matricial para impedir el data leakage en el caso del decoder.

Además, resolvió el Posicionamiento de los token y aprovechó técnicas ya existentes como el uso de Skip Connections y Normalization Layers.

Todo ello posibilitó la creación de los grandes modelos actuales, BERT, GPT y la aparición de muchísimos modelos disponibles “pre-entrenados” que pueden ser descargados y reutilizados para fine-tuning por cualquier persona.

Como si esto fuera poco, los Transformers tienen la flexibilidad suficiente para traspasar el área de NLP y contribuir al resto de áreas del Machine Learning obteniendo resultados impresionantes.

Habrá que agradecer a Ashish VaswaniNoam ShazeerNiki ParmarJakob UszkoreitLlion JonesAidan N. GomezLukasz KaiserIllia Polosukhin por su obra maestra.

Material Extra

Enlaces a más info sobre Transformers!

Suscripción al 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 info @ aprendemachinelearning.com a tus contactos para evitar problemas. Gracias!

El libro del Blog

Si te gustan los contenidos del blog y quieres darme tu apoyo, puedes comprar el libro en papel, ó en digital (también lo puede descargar gratis!).

The post ¿Cómo funcionan los Transformers? en Español first appeared on Aprende Machine Learning.

]]>
https://aprendemachinelearning.com/como-funcionan-los-transformers-espanol-nlp-gpt-bert/feed/ 1 7771
Interpretación de Modelos de Machine Learning https://aprendemachinelearning.com/interpretacion-de-modelos-de-machine-learning/ https://aprendemachinelearning.com/interpretacion-de-modelos-de-machine-learning/#comments Tue, 30 Apr 2019 08:00:00 +0000 https://www.aprendemachinelearning.com/?p=6814 Descifrar las decisiones tomadas por la máquina La interpretación de las decisiones tomadas por nuestros algoritmos de Machine Learning pasa a un plano muy importante: para comprender el modelo y mejorarlo, evitar “biases” (ó descubrirlos), para justificar nuestra confianza en el modelo y hasta legalmente...

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

]]>
Descifrar las decisiones tomadas por la máquina

La interpretación de las decisiones tomadas por nuestros algoritmos de Machine Learning pasa a un plano muy importante: para comprender el modelo y mejorarlo, evitar “biases” (ó descubrirlos), para justificar nuestra confianza en el modelo y hasta legalmente pues es requerido por leyes como la GDPR -para decisiones delicadas como puede ser dar ó no un crédito a una persona-.

Si nuestro algoritmo tuviera que detectar enfermedades y suponiendo que logramos una tasa de aciertos del 90% ¿no te parecería lógico comprender cómo lo ha hecho? ¿es puro azar? ¿está teniendo en cuenta combinaciones de características que nosotros no contemplamos?

Si de pequeño eras curioso y querías sabes cómo funcionaban las cosas: relojes, autos, ó hasta el mismísimo ordenador… serás un poco como yo… y… no siempre nos convence el concepto de “caja negra”.

Abriendo la Caja negra

El concepto de caja negra a veces es muy beneficioso, en sistemas decimos “yo al método le tiro estos parámetros y me devuelve true ó false”. Genial, con eso nos basta. Podemos trabajar en equipos distribuidos, intercambiar interfaces y listo. Podemos confiar en otras librerías ó paquetes “sin saber cómo lo hacen” pero que nos resuelven problemas. Y las encajamos como piezas de un puzzle.

Los algoritmos de Machine Learning, hasta ahora funcionaban muy de ese modo. Es decir, podemos hacer una red neuronal de 10 capas con 80 neuronas cada una, dropout, recurrencia y que nos dé unas buenas clasificaciones. Pero ¿qué pasa por dentro? ¿cómo hizo? ¿es magia?… esas oscuras épocas de incertidumbre deben acabar y deberemos tomar control de porqué se hacen las cosas como se hacen.

¿Que hay dentro de la caja negra?

Explainable Machine Learning

Interpretar el Modelos en Machine Learning es la habilidad de explicar su funcionamiento ó presentarlo de manera comprensible al humano.

¿Por qué es importante interpretar los modelos?

Imaginemos que nuestro algoritmo decidirá a qué empleado le daremos un ascenso, dadas sus características e historia en la empresa. Y luego de entrenar el modelo vemos que “aparentemente da buenos resultados” pero… todas las elecciones para puestos gerenciales son siempre para hombres y ninguna mujer…. mmmm.. sospechoso, ¿no?

Ese modelo “aprendió” que durante los últimos 10 años, los cargos gerenciales de esa empresa siempre fueron para hombres. Si ese algoritmo pasa a producción, estará discriminando a las mujeres e impidiendo su ascenso.

Entonces ¿Cómo hacemos para interpretar el modelo?

Respuesta corta: con otro modelo que ayude a los humanos a interpretar los procesos.

Hay que decir que modelos como “1 árbol de decisión pequeño” ó clasificación lineal, pueden llegar a interpretarse por su gráfica y/o fórmula (repito: si son sencillos). Sin embargo un Random Forest ó las Redes Neuronales son complejas y prácticamente imposibles de comprender <<de un vistazo>>.

Los beneficios de la “interpretabilidad de los modelos “son:

  • Dar confiabilidad en los resultados.
  • Ayudar en el Debugging.
  • Informar a la Ingeniería de Características (Feature Engineer).
  • Detectar necesidad de colectar nuevas muestras.
  • Ayudar a una persona en la toma de decisiones.
  • Mayor seguridad/robustez en el modelo obtenido.

Técnicas de Interpretación de modelos

Del análisis de los modelos podemos obtener:

  • Características más importantes (features)
  • Para una predicción en particular del modelo, el efecto que tuvo en ella cada característica
  • Efecto de cada característica en el global de las predicciones del modelo

Veamos algunas de esas técnicas y qué librerías de Python nos brindan estas funcionalidades:

1- Permutation Importance

¿Cuales de las features piensa el modelo que son más importantes? ¿Qué Características tienen mayor impacto en las predicciones? Estos conceptos son conocidos como “Feature Importante” y “Permutation Importance” y nos sirven para calcular nuestras características de entrada al modelo. Nos sirve para poder ver cuando nuestro modelo está funcionando de manera contra-intuitiva y también para demostrar a terceros cuando el funcionamiento es correcto.

Para hacer Permutation Importante debemos primero entrenar un modelo y “encajarlo” (fit). Luego tomamos el set de validación y tomamos las features una por vez: por ejemplo, tomamos la primer columna de entrada y mezclamos todos sus valores entre sus filas (pero el resto de features se mantienen igual). Entonces hacemos predicción usando el mismo modelo entrenado y deberían empeorar los resultados. Si “desmejoran mucho” es que esa feature era muy importante. En cambio, si no afecta demasiado, tampoco variarán mucho las predicciones obtenidas y quiere decir que esa característica no es relevante. Y así lo hacemos con todas las características, desordenando de a una a la vez.

Podemos utilizar la librería ELI5 para Python para visualizar la Permutation Importance

2- Partial Dependence Plots (PDP)

Los PDPs muestran el efecto marginal de una o dos características que tienen sobre la predicción dictada por un modelo. Los PDPs muestran cómo afectan las distintas características a las predicciones. El PDP puede mostrar la relación entre nuestra variable de salida y una ó dos características de entrada.

Lo que hacemos en tomar de a una sola fila, e ir variando los valores de una sola de las features (que queremos investigar) contra un modelo YA entrenado. Entonces veremos en que intervalos esa característica afecta a los resultados del modelo.

Lo podemos hacer hasta con 2 variables a la vez usando “2D Partial Plots” y visualizarlo.

Para esto podemos utilizar la librería PDPBox

3-SHAP Values (en predicciones individuales)

SHAP viene de “Shapley Additive exPlanation” y está basado en la teoría de Juegos para explicar cómo cada uno de los jugadores que intervienen en un “juego colaborativo” contribuyen en el éxito de la partida. Con esto podemos comprender una predicción y como impacta cada feature. Podemos decir que la interpretabilidad que nos ofrecen los valores SHAP es de las mejores.

De manera muy sencilla -e incompleta- de cómo se calculan estos valores podemos imaginar a una grupo de desarrolladores, testers, arquitectos y managers (features) que trabajan en conjunto (“juegan”/colaboran) para crear un Sistema de Software y queremos saber cuánto contribuyó cada uno de ellos en su producción. Lo que haremos es ir intercalando a los participantes en diversos “orden de aparación” ABCD, ABDC, ADBC, etc. e ir midiendo la <<contribución marginal>> de cada participante cada vez. Con ello sacar el promedio de cada uno y tendremos los valores Shapley que nos indican cuánto contribuyo cada jugador a conseguir el resultado obtenido.

Supongamos que tenemos que explicar a una persona por qué se ha rechazado su solicitud de un crédito -esto es, una única predicción, y no “el accuracy global” del modelo- los valores SHAP nos muestran cuales características que alimentan al modelo <<empujan>> a la denegación (ó aceptación) esa petición en concreto.

Utilizamos la librería SHAP para python para obtener estos valores.

4- Usos avanzados de Shap (comprensión global)

Si recopilamos muchos valores Shap podremos tener una mejor comprensión del modelo en su conjunto. De allí aparecen las gráficas “Shap Summary Plot” y “Shap Dependence Contribution Plot”.

Shap Summary Plot

Calculando los Shap Values de cada muestra, podemos obtener esta Visualización que nos muestra cuales características son las más importantes y el rango de valores donde afecta al set de datos.

Shap Dependence Contribution Plot

Esta gráfica es similar a la de los PDPs (vistos en el punto 2) pero nos dan mucho mayor detalle.

No puedo dejar de mencionar a una gran librería para ML Interpretability llamada LIME (Local Interpretable Model Explanation) y que nos ofrece comprensión a humanos para modelos de NLP (destacando visualmente palabras en el texto) y para imágenes clasificadas por una CNN (mostrando las áreas en donde “mira” la red).
También mencionar otra Librería Python llamada Skater -es de Oracle- y aunque aún está en desarrollo, provee de buenas herramientas.

Conclusión

La importancia de la interpretabilidad de los modelos de Machine Learning es crucial para poder justificar y comprender las predicciones y/o resultados obtenidos y hasta legalmente. Es curioso que necesitemos “modelos que expliquen como funcionan los modelos” para poder “bajar” a entendimiento humano la complejidad de lo que ocurre en nuestras máquinas de aprendizaje. Finalmente, aplicando diversos métodos, Permutation Importance, los PDP y los Shap Values logramos obtener transparencia en nuestro desarrollo y un panorama claro sobre cómo funciona nuestro engranaje para obtener los resultados.

Suscribe al Blog

Recibe los nuevos artículos sobre Aprendizaje Automático, teoría y el código Python

NOTA: algunos usuarios reportaron que el email de confirmación a la suscripción entraron en la carpeta SPAM. Te sugiero que revises y recomiendo agregar el remitente a tus contactos. Gracias!

Recursos Adicionales

Te recomiendo sobre todo y para pasar al Código este curso completo en Kaggle: Machine Learning Explainability

Algunos artículos y videos sobre Interpretación de Modelos (en inglés)

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

]]>
https://aprendemachinelearning.com/interpretacion-de-modelos-de-machine-learning/feed/ 4 6814
Procesamiento del Lenguaje Natural (NLP) https://aprendemachinelearning.com/procesamiento-del-lenguaje-natural-nlp/ https://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://aprendemachinelearning.com/procesamiento-del-lenguaje-natural-nlp/feed/ 18 5909
¿Cómo funcionan las Convolutional Neural Networks? Visión por Ordenador https://aprendemachinelearning.com/como-funcionan-las-convolutional-neural-networks-vision-por-ordenador/ https://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://aprendemachinelearning.com/como-funcionan-las-convolutional-neural-networks-vision-por-ordenador/feed/ 28 6209
Comprende Principal Component Analysis https://aprendemachinelearning.com/comprende-principal-component-analysis/ https://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://aprendemachinelearning.com/comprende-principal-component-analysis/feed/ 11 5904
Breve Historia de las Redes Neuronales Artificiales https://aprendemachinelearning.com/breve-historia-de-las-redes-neuronales-artificiales/ https://aprendemachinelearning.com/breve-historia-de-las-redes-neuronales-artificiales/#comments Wed, 12 Sep 2018 07:00:00 +0000 https://www.aprendemachinelearning.com/?p=5917 Arquitecturas y Aplicaciones de las Redes Neuronales más usadas. Vamos a hacer un repaso por las diversas estructuras inventadas, mejoradas y utilizadas a lo largo de la historia para crear redes neuronales y sacar el mayor potencial al Deep Learning para resolver toda clase de...

The post Breve Historia de las Redes Neuronales Artificiales first appeared on Aprende Machine Learning.

]]>
Arquitecturas y Aplicaciones de las Redes Neuronales más usadas.

Vamos a hacer un repaso por las diversas estructuras inventadas, mejoradas y utilizadas a lo largo de la historia para crear redes neuronales y sacar el mayor potencial al Deep Learning para resolver toda clase de problemas de regresión y clasificación.

Evolución de las Redes Neuronales en Ciencias de la Computación

Vamos a revisar las siguientes redes/arquitecturas:

  • 1958 – Perceptron
  • 1965 – Multilayer Perceptron
  • 1980’s
    • Neuronas Sigmoidales
    • Redes Feedforward
    • Backpropagation
  • 1989 – Convolutional neural networks (CNN) / Recurent neural networks (RNN)
  • 1997 – Long short term memory (LSTM)
  • 2006 – Deep Belief Networks (DBN): Nace deep learning
    • Restricted Boltzmann Machine
    • Encoder / Decoder = Auto-encoder
  • 2014 – Generative Adversarial Networks (GAN)

Si bien esta lista no es exhaustiva y no se abarcan todos los modelos creados desde los años 50, he recopilado las que fueron -a mi parecer- las redes y tecnologías más importantes desarrolladas para llegar al punto en que estamos hoy: el Aprendizaje Profundo.

El inicio de todo: la neurona artificial

1958 – Perceptron

Entre las décadas de 1950 y 1960 el científico Frank Rosenblatt, inspirado en el trabajo de Warren McCulloch y Walter Pitts creó el Perceptron, la unidad desde donde nacería y se potenciarían las redes neuronales artificiales.

Un perceptron toma varias entradas binarias x1, x2, etc y produce una sóla salida binaria. Para calcular la salida, Rosenblatt introduce el concepto de “pesos” w1, w2, etc, un número real que expresa la importancia de la respectiva entrada con la salida. La salida de la neurona será 1 o 0 si la suma de la multiplicación de pesos por entradas es mayor o menor a un determinado umbral.

Sus principales usos son decisiones binarias sencillas, o para crear funciones lógicas como OR, AND.

1965 – Multilayer Perceptron

Como se imaginarán, el multilayer perceptron es una “amplicación” del percepción de una única neurona a más de una. Además aparece el concepto de capas de entrada, oculta y salida. Pero con valores de entrada y salida binarios. No olvidemos que tanto el valor de los pesos como el de umbral de cada neurona lo asignaba manualmente el científico. Cuantos más perceptrones en las capas, mucho más difícil conseguir los pesos para obtener salidas deseadas.

Los 1980s: aprendizaje automático

Neuronas Sigmoides

Para poder lograr que las redes de neuronas aprendieran solas fue necesario introducir un nuevo tipo de neuronas. Las llamadas Neuronas Sigmoides son similares al perceptron, pero permiten que las entradas, en vez de ser ceros o unos, puedan tener valores reales como 0,5 ó 0,377 ó lo que sea. También aparecen las neuronas “bias” que siempre suman 1 en las diversas capas para resolver ciertas situaciones. Ahora las salidas en vez de ser 0 ó 1, será d(w . x + b) donde d será la función sigmoide definida como d(z) = 1/( 1 +e-z). Esta es la primer función de activación!

Imagen de la Curva Logística Normalizada de Wikipedia

Con esta nueva fórmula, se puede lograr que pequeñas alteraciones en valores de los pesos (deltas) produzcan pequeñas alteraciones en la salida. Por lo tanto, podemos ir ajustando muy de a poco los pesos de las conexiones e ir obteniendo las salidas deseadas.

Redes Feedforward

Se les llama así a las redes en que las salidas de una capa son utilizadas como entradas en la próxima capa. Esto quiere decir que no hay loops “hacia atrás”. Siempre se “alimenta” de valores hacia adelante. Hay redes que veremos más adelante en las que sí que existen esos loops (Recurrent Neural Networks).

Además existe el concepto de “fully connected Feedforward Networks” y se refiere a que todas las neuronas de entrada, están conectadas con todas las neuronas de la siguiente capa.

1986 – Backpropagation

Gracias al algoritmo de backpropagation se hizo posible entrenar redes neuronales de multiples capas de manera supervisada. Al calcular el error obtenido en la salida e ir propagando hacia las capas anteriores se van haciendo ajustes pequeños (minimizando costo) en cada iteración para lograr que la red aprenda consiguiendo que la red pueda -por ejemplo- clasificar las entradas correctamente.

Nuevo Artículo: Pronóstico de Series Temporales con Redes Neuronales

1989 – Convolutional Neural Network

Las Convolutional Neural Networks son redes multilayered que toman su inspiración del cortex visual de los animales. Esta arquitectura es útil en varias aplicaciones, principalmente procesamiento de imágenes. La primera CNN fue creada por Yann LeCun y estaba enfocada en el reconocimiento de letras manuscritas.

La arquitectura constaba de varias capas que implementaban la extracción de características y luego clasificar. La imagen se divide en campos receptivos que alimentan una capa convolutional que extrae features de la imagen de entrada (Por ejemplo, detectar lineas verticales, vértices, etc). El siguiente paso es pooling que reduce la dimensionalidad de las features extraídas manteniendo la información más importante. Luego se hace una nueva convolución y otro pooling que alimenta una red feedforward multicapa. La salida final de la red es un grupo de nodos que clasifican el resultado, por ejemplo un nodo para cada número del 0 al 9 (es decir, 10 nodos, se “activan” de a uno).

Nuevo artículo: ¿Qué son y cómo funcionan las Convolutional Neural Networks?

Esta arquitectura usando capas profundas y la clasificación de salida abrieron un mundo nuevo de posibilidades en las redes neuronales. Las CNN se usan también en reconocimiento de video y tareas de Procesamiento del Lenguaje natural. ¿Quieres hacer tu propia CNN en Python?

1997 Long Short Term Memory / Recurrent Neural Network

Aqui vemos que la red LSTM tiene neuronas ocultas con loops hacia atrás (en azul). Esto permite que almacene información en celdas de memoria.

Las Long short term memory son un tipo de Recurrent neural network. Esta arquitectura permite conexiones “hacia atrás” entre las capas. Esto las hace buenas para procesar datos de tipo “time series” (datos históricos). En 1997 se crearon las LSTM que consisten en unas celdas de memoria que permiten a la red recordar valores por períodos cortos o largos.

Una celda de memoria contiene compuertas que administran como la información fluye dentro o fuera. La puerta de entrada controla cuando puede entran nueva información en la memoria. La puerta de “olvido” controla cuanto tiempo existe y se retiene esa información. La puerta de salida controla cuando la información en la celda es usada como salida de la celda. La celda contiene pesos que controlan cada compuerta. El algoritmo de entrenamiento -conocido como backpropagation-through-time optimiza estos pesos basado en el error de resultado.

Las LSTM se han aplicado en reconocimiento de voz, de escritura, text-to-speech y otras tareas.

Se alcanza el Deep Learning

2006 – Deep Belief Networks (DBN)

La Deep Belief Network utiliza un Autoencoder con Restricted Boltzmann Machines para preentrenar a las neuronas de la red y obtener un mejor resultado final.

Antes de las DBN en 2006 los modelos con “profundidad” (decenas o cientos de capas) eran considerados demasiado difíciles de entrenar (incluso con backpropagation) y el uso de las redes neuronales artificiales quedó estancado. Con la creación de una DBN que logro obtener un mejor resultado en el MNIST, se devolvió el entusiasmo en poder lograr el aprendizaje profundo en redes neuronales. Hoy en día las DBN no se utilizan demasiado, pero fueron un gran hito en la historia en el desarrollo del deep learning y permitieron seguir la exploración para mejorar las redes existentes CNN, LSTM, etc.

Las Deep Belief Networks, demostraron que utilizar pesos aleatorios al inicializar las redes son una mala idea: por ejemplo al utilizar Backpropagation con Descenso por gradiente muchas veces se caía en mínimos locales, sin lograr optimizar los pesos. Mejor será utilizar una asignación de pesos inteligente mediante un preentrenamiento de las capas de la red -en inglés “pretrain”-. Se basa en el uso de la utilización de Restricted Boltzmann Machines y Autoencoders para pre-entrenar la red de manera no supervisada. Ojo! luego de pre-entrenar y asignar esos pesos iniciales, deberemos entrenar la red por de forma habitual, supervisada (por ejemplo con backpropagation).

Se cree que ese preentrenamiento es una de las causas de la gran mejora en las redes neuronales y permitir el deep learning: pues para asignar los valores se evalúa capa a capa, de a una, y no “sufre” de cierto sesgo que causa el backpropagation, al entrenar a todas las capas en simultáneo.

2014 – Generative Adversarial Networks

Las GAN, entrenan dos redes neuronales en simultáneo. La red de Generación y la red de Discriminación. A medida que la máquina aprende, comienza a crear muestras que son indistinguibles de los datos reales.

Estas redes pueden aprender a crear muestras, de manera similar a los datos con las que las alimentamos.

La idea detrás de GAN es la de tener dos modelos de redes neuronales compitiendo. Uno, llamado Generador, toma inicialmente “datos basura” como entrada y genera muestras. El otro modelo, llamado Discriminador, recibe a la vez muestras del Generador y del conjunto de entrenamiento (real) y deberá ser capaz de diferenciar entre las dos fuentes. Estas dos redes juegan una partida continua donde el Generador aprende a producir muestras más realistas y el Discriminador aprende a distinguir entre datos reales y muestras artificiales. Estas redes son entrenadas simultáneamente para finalmente lograr que los datos generados no puedan detectarse de datos reales.

Sus aplicaciones principales son la de generación de imágenes realistas, pero también la de mejorar imágenes ya existentes, o generar textos (captions) en imágenes, o generar textos siguiendo un estilo determinado y hasta desarrollo de moléculas para industria farmacéutica.

Conclusión, repaso y Nuevos descubrimientos

Hemos recorrido estos primeros -casi- 80 años de avances en las redes neuronales en la historia de la inteligencia artificial. Se suele dividir en 3 etapas, del 40 al 70 en donde se pasó del asombro de estos nuevos modelos hasta el escepticismo, el retorno de un invierno de 10 años cuando en los ochentas surgen mejoras en mecanismos y maneras de entrenar las redes (backpropagation) y se alcanza una meseta en la que no se puede alcanzar la “profundidad” de aprendizaje seguramente también por falta de poder de cómputo. Y una tercer etapa a partir de 2006 en la que se logra superar esa barrera y aprovechando el poder de las GPU y nuevas ideas se logra entrenar cientos de capas jerárquicas que conforman y potencian el Deep Learning y dan una capacidad casi ilimitada a estas redes.

Como último comentario, me gustaría decir que recientemente (feb 2018) hay nuevos estudios de las neuronas humanas biológicas en las que se está redescubriendo su funcionamiento y se está produciendo una nueva revolución, pues parece que es totalmente distinto a lo que hasta hoy conocíamos. Esto puede ser el principio de una nueva etapa totalmente nueva y seguramente mejor del Aprendizaje Profundo, el Machine Learning y la Inteligencia Artificial.

Suscripción al Blog

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

Más recursos /  Seguir leyendo.

The post Breve Historia de las Redes Neuronales Artificiales first appeared on Aprende Machine Learning.

]]>
https://aprendemachinelearning.com/breve-historia-de-las-redes-neuronales-artificiales/feed/ 6 5917
¿Comprar casa o Alquilar? Naive Bayes usando Python https://aprendemachinelearning.com/comprar-casa-o-alquilar-naive-bayes-usando-python/ https://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://aprendemachinelearning.com/comprar-casa-o-alquilar-naive-bayes-usando-python/feed/ 18 5903
Clasificar con K-Nearest-Neighbor ejemplo en Python https://aprendemachinelearning.com/clasificar-con-k-nearest-neighbor-ejemplo-en-python/ https://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://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://aprendemachinelearning.com/arbol-de-decision-en-python-clasificacion-y-prediccion/ https://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://aprendemachinelearning.com/arbol-de-decision-en-python-clasificacion-y-prediccion/feed/ 74 5633