Ejercicio | Aprende Machine Learning https://aprendemachinelearning.com en Español Fri, 15 Sep 2023 08:47:56 +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 Ejercicio | 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
Generación de Texto en Español con GPT-2 https://aprendemachinelearning.com/generacion-de-texto-en-espanol-con-gpt-2/ https://aprendemachinelearning.com/generacion-de-texto-en-espanol-con-gpt-2/#comments Tue, 13 Dec 2022 09:00:00 +0000 https://www.aprendemachinelearning.com/?p=7869 Crearemos nuestra propia IA de generación de texto basada en los diálogos y entrevistas de Ibai Llanos publicados en Youtube. Usaremos un modelo pre-entrenado GPT-2 en castellano disponible desde HuggingFace y haremos el fine-tuning con Pytorch para que aprenda el estilo de escritura deseado.

The post Generación de Texto en Español con GPT-2 first appeared on Aprende Machine Learning.

]]>
Crea tu propio bot-influencer, basado en Ibai Llanos, en Python ¿Qué puede salir mal?

Crearemos nuestra propia IA de generación de texto basada en los diálogos y entrevistas de Ibai Llanos publicados en Youtube. Usaremos un modelo pre-entrenado GPT-2 en castellano disponible desde HuggingFace y haremos el fine-tuning con Pytorch para que aprenda el estilo de escritura deseado.

En este artículo comentaremos brevemente el modelo GPT-2 y crearemos un entorno en Python desde donde poder entrenar y generar texto!

¿Qué son los modelos GPT?

GPT significa “Generative Pre-Training” y es un modelo de Machine Learning creado por OpenAI para la generación de texto. El modelo de Procesamiento del Lenguaje Natural, es un caso particular de Transformers. GPT propone el pre-entrenamiento de un enorme corpus de texto para luego -opcionalmente- realizar el fine-tuning.

El fine-tuning es el proceso de realizar un “ajuste fino” de los parámetros ó capas de la red neuronal, en nuestro caso con un dataset adicional para guiar al modelo a obtener las salidas deseadas.

¿Entonces es aprendizaje no supervisado? Sí; se considera que es aprendizaje no supervisado porque estamos pasando al modelo enormes cantidades de texto, que el modelo organizará automáticamente y le pedimos que “prediga la siguiente palabra” usando como contexto todos los tokens previos (con posicionamiento!). El modelo ajusta sin intervención humana los embeddings y los vectores de Atención. Algunos autores lo consideran aprendizaje “semi-supervisado” porque consideran como “etiqueta de salida” el token a predecir.

Ejemplo: Si tenemos la oración “Buenos días amigos”, el modelo usará “Buenos días” para predecir como etiqueta de salida “amigos”.

Este modelo puede usarse directamente como modelo generativo luego de la etapa de aprendizaje no supervisado (sin hacer fine-tuning).

Al partir de este modelo en crudo y realizar un fine-tuning a nuestro antojo, podemos crear distintos modelos específicos: de tipo Question/Answering, resumen de textos, clasificación, análisis de sentimiento, etc.

Eso es lo que haremos en el ejercicio de hoy: descargar el modelo GPT y realizar el fine-tuning!

¿Cómo es la arquitectura de GPT-2?

GPT es un modelo Transformer. Utiliza sólo la rama “Tansformer-Decoder” a diferencia de modelos como BERT que utilizan la rama Encoder. De esta manera se elimina la Atención cruzada, pues ya no es necesaria y mantiene la “Masked Self-Attention”.

 Entre sus características:

  • El Transformer Decoder utiliza Masked Self-Attention. Sólo utiliza los tokens precedentes de la oración para calcular la atención del token final.
  • GPT es un modelo con posicionamiento absoluto de embeddings.
  • GPT fue entrenado con “Causal Language Modelling” y es poderoso para predecir el “siguiente token” de la oración. Esto le permite generar texto coherente, imitando al lenguaje de los humanos.
  • GPT-2 fue entrenado con el texto de 8 millones de páginas web que acumulan más de 40GB.
  • GPT-2 tiene 1500 millones de parámetros en su versión Extra-Large.
  • El tamaño de vocabulario es de 50.257 tokens.
  • Existen 4 modelos de distinto tamaño de GPT-2 según la cantidad de decoders y la dimensionalidad máxima.
Desde la versión GPT-2 Small de unos 500MB (117Millones de parámetros) hasta el Extra large que ocupa más de 6.5GB.

Como vemos, la versión pequeña tiene un tamaño aún manejable para entrenar en un ordenador “normal”. Es la versión del modelo que utilizaremos en el ejercicio.

Zero shot Learning

Una ventaja que se consigue al entrenar al modelo con millones de textos de conocimiento general (en contraposición a utilizar textos sobre un sólo tema) es que el modelo consigue habilidades “zero shot”, es decir, logra realizar satisfactoriamente algunas tareas para las que no ha sido entrenado específicamente. Por ejemplo, GPT-2 puede traducir textos de inglés a francés sin haber sido entrenado para ello. También consigue responder a preguntas ó generar código en Java.

¿Por qué usar GPT-2?

Puede que sepas de la existencia de GPT-3 y hasta puede que hayas escuchado hablar sobre el recientemente lanzado “ChatGPT” que algunos denominan como GPT-3.5 ó GPT-4. Entonces, ¿porqué vamos a usar al viejo GPT-2 en este ejercicio?

La respuesta rápida es porque GPT-2 es libre!, su código fue liberado y tenemos acceso al repositorio y a su implementación desde HuggingFace. Existen muchos modelos libres tuneado de GPT-2 y publicados que podemos usar. Si bien cuenta con un tamaño de parámetros bastante grande, GPT-2 puede ser reentrenado en nuestro propio ordenador.

En cuanto a resultados, GPT-2 fue unos de los mejores de su época (Feb 2019), batiendo records y con valores -en algunos casos- similares a los del humano:

En cambio GPT-3 aún no ha sido liberado, ni su código ni su red pre-entrenada, además de que tiene un tamaño inmensamente mayor a su hermano pequeño, haciendo casi imposible que lo podamos instalar ó usar en nuestra computadora de casa ó trabajo.

Es cierto que puedes utilizar GPT-3 mediante la API de pago de OpenAI y también se puede utilizar ChatGPT de modo experimental desde su web. Te animo a que lo hagas, pero no dejes de aprender a utilizar GPT-2 que será de gran ayuda para comprender como ajustar uno de estos modelos de lenguaje para tus propios fines.

¿Qué tiene que ver HuggingFace en todo esto?

HuggingFace se ha convertido en el gran repositorio de referencia de modelos pre-entrenados. Es un sitio web en donde cualquier persona ó insitutición pueden subir sus modelos entrenados para compartirlos.

HuggingFace ofrece una librería python llamada transformers que permite descargar modelos preentrenados de NLP (GPT, BERT, BART,ELECTRA, …), utilizarlos, hacer el fine tuning, reentrenar.

En el ejercicio que haremos instalaremos la librería de HuggingFace para acceder a los modelos de GPT.

Modelo pre-entrenado en Español

Dentro de HuggingFace podemos buscar modelos para NLP y también para Visión Artificial, cómo el de Stable Diffusion, para crear imágenes, como se explica en un anterior post del blog!).

Y podemos encontrar Modelos con distintos fines. En nuestro caso, estamos interesados en utilizar un modelo en Español.

Usaremos el modelo llamado “flax-community/gpt-2-spanish“, puedes ver su ficha aquí, y desde ya, agradecemos enormemente al equipo que lo ha creado y compartido gratuitamente. Ocupa unos 500MB.

Un detalle, que verás en el código: realmente cargaremos una red pre-entrenada con los pesos y el embeddings PERO también usaremos el tokenizador! (es decir, cargaremos 2 elementos del repositorio de HuggingFace, no sólo el modelo).

El proyecto Python: “Tu propio bot influencer”

En otros artículos de NLP de este tipo, utilizan textos de Shakespeare porque es un escritor reconocido, respetado y porque no tiene derechos de autor. Nosotros utilizaremos textos de Ibai Llanos generados a partir de transcripciones generadas automáticamente por Whisper de sus videos de Youtube. Ibai es un reconocido Streamer español de Twitch. ¿Porqué Ibai? Para hacer divertido el ejercicio! Para que sea en castellano, con jerga actual 😀

El proyecto consiste en tomar un modelo GPT-2 pre-entrenado en castellano y realizar el fine-tuning con nuestro propio dataset de texto. Como resultado obtendremos un modelo que será capaz de crear textos “con la manera de hablar” de Ibai.

Aquí puedes encontrar la Jupyter notebook completa en mi repo de Github con el ejercicio que realizaremos. En total son unas 100 líneas de código.

El Dataset educacional: Diálogos de Ibai

Banner del Canal de Ibai en Youtube 2022

El dataset es una selección totalmente arbitraría de videos de Youtube de Ibai con entrevistas y charlas de sus streams en Twitch. En algunos videos juega videojuegos en vivo, entrevista cantantes, futbolistas ó realiza compras de productos usados que le llaman la atención.

Utilicé un notebook de Google Colab con Whisper que es un modelo de machine learning lanzado hace pocos meses (en 2022) que realiza la transcripción automática de Audio a Texto. Usaremos como entradas esos textos. Disclaimer: Pueden contener errores de mala transcripción y también es posible que hubiera palabras que el modelo no comprenda del español.

El archivo de texto que utilizaremos como Dataset con fines educativos, lo puedes encontrar aquí.

Creación del entorno Python con Anaconda

Si tienes instalado Anaconda, puedes crear un nuevo Environment python para este proyecto. Si no, instala anaconda siguiendo esta guía, ó utiliza cualquier manejador de ambientes python de tu agrado.

También puedes ejecutar el código una notebook en la nube con Google Colab y aprovechar el uso de GPU gratuito. En este artículo te cuento sobre cómo usar Colab.

En este ejercicio utilizaremos la librería Pytorch para entrenar la red neuronal. Te recomiendo ir a la web oficial de Pytorch para obtener la versión que necesitas en tu ordenador, porque puede variar la instalación si usas Windows, Linux ó Mac y si tienes o no GPU.

Ejecuta las siguientes líneas en tu terminal:

conda create -n gpt2 python=3.9 -y
# Activa el nuevo ambiente con: 'conda activate gpt2'
conda install numpy tqdm transformers -y
# si tienes GPU instala Pytorch con:
conda install pytorch torchvision torchaudio pytorch-cuda=11.7 -c pytorch -c nvidia
# si no tienes GPU, instala con:
conda install pytorch torchvision torchaudio cpuonly -c pytorch

Importamos las librerías

Ahora pasamos a un notebook o una IDE Python y empezamos importando las librerías python que utilizaremos, incluyendo transformers de HuggingFace:

import os
import time
import datetime
import numpy as np
import random
from tqdm import tqdm
import torch
from torch.utils.data import Dataset, DataLoader, random_split, RandomSampler
from transformers import AutoTokenizer, AutoModelForCausalLM
from transformers import AdamW, get_linear_schedule_with_warmup

Uso de CPU ó GPU

Haremos una distinción; si vamos a utilizar GPU para entrenar ó CPU, definiendo una variable llamada device. Nótese que también alteramos el tamaño que usaremos de batch. En el caso de GPU, podemos utilizar valores 2 ó 3 según el tamaño de memoria RAM que tenga la tarjeta gráfica.

if torch.cuda.is_available():
    print("Usar GPU")
    device = torch.device("cuda")
    batch_size = 3
else:
    print("usar CPU")
    device = torch.device("cpu")
    batch_size = 1

Cargamos el Modelo de HuggingFace

La primera vez que ejecutemos esta celda, tomará unos minutos en descargar los 500MB del modelo y el tokenizador en Español desde HuggingFace, pero luego ya se utilizará esa copia desde el disco, siendo una ejecución inmediata.

Para este ejercicio estamos creando un “token especial” (de control) que llamaremos “ibai” con el que luego indicaremos al modelo que queremos obtener una salida de este tipo.

# Load the GPT tokenizer.
tokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish", bos_token='<|startoftext|>', eos_token='<|endoftext|>', pad_token='<|pad|>')
model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish")

control_code = "ibai"

special_tokens_dict = {
         "additional_special_tokens": ['f"<|{control_code}|>"'],
}
num_added_toks = tokenizer.add_special_tokens(special_tokens_dict)
model.resize_token_embeddings(len(tokenizer))
unk_tok_emb = model.transformer.wte.weight.data[tokenizer.unk_token_id, :]
for i in range(num_added_toks):
        model.transformer.wte.weight.data[-(i+1), :] = unk_tok_emb

Cargamos el Dataset “Ibai_textos.txt”

Creamos una clase python que hereda de Dataset que recibe el archivo txt que contiene los textos para fine-tuning.

class GPT2Dataset(Dataset):
  def __init__(self, control_code, tokenizer, archivo_texto, max_length=768):
    self.tokenizer = tokenizer
    self.input_ids = []
    self.attn_masks = []
    print('loading text...')
    sentences = open(archivo_texto, 'r', encoding="utf-8").read().lower().split('n')
    print('qty:',len(sentences))
    for row in tqdm(sentences):
      encodings_dict = tokenizer('<|startoftext|>'+ f"<|{control_code}|>" + row + '<|endoftext|>', truncation=True, max_length=max_length, padding="max_length")
      self.input_ids.append(torch.tensor(encodings_dict['input_ids']))
      self.attn_masks.append(torch.tensor(encodings_dict['attention_mask']))
    
  def __len__(self):
    return len(self.input_ids)
  def __getitem__(self, idx):
    return self.input_ids[idx], self.attn_masks[idx]

Instanciamos la clase, pasando el nombre de archivo “ibai_textos.txt” a utilizar

dataset = GPT2Dataset(control_code, tokenizer, archivo_texto="ibai_textos.txt", max_length=768)
# Split into training and validation sets
train_size = int(0.99 * len(dataset))
val_size = len(dataset) - train_size
train_dataset, val_dataset = random_split(dataset, [train_size, val_size])
print('{:>5,} training samples'.format(train_size))
print('{:>5,} validation samples'.format(val_size))
train_dataloader = DataLoader(
            train_dataset,  # The training samples.
            sampler = RandomSampler(train_dataset), # Select batches randomly
            batch_size = batch_size # Trains with this batch size.
        )

Entrenamos haciendo el Fine-Tuning

Realizando entre 1 y 3 epochs debería ser suficiente para que el modelo quede tuneado.

epochs = 1
learning_rate = 5e-4
warmup_steps = 1e2
epsilon = 1e-8
optimizer = AdamW(model.parameters(), lr = learning_rate, eps = epsilon)
total_steps = len(train_dataloader) * epochs
scheduler = get_linear_schedule_with_warmup(optimizer, num_warmup_steps = warmup_steps, num_training_steps = total_steps)
def format_time(elapsed):
    return str(datetime.timedelta(seconds=int(round((elapsed)))))

Ahora si, a entrenar el modelo durante cerca de 2 horas si tenemos GPU ó durante un día entero en CPU.

El código es bastante estándar en PyTorch para entreno de redes neuronales profundas; un loop principal por epoch donde procesamos por batches las líneas de texto del dataset y hacemos backpropagation.

total_t0 = time.time()
model = model.to(device)
for epoch_i in range(0, epochs):
    print('======== Epoch {:} / {:} ========'.format(epoch_i + 1, epochs))
    print('Training...')
    t0 = time.time()
    total_train_loss = 0
    model.train()
    for step, batch in enumerate(train_dataloader):
        b_input_ids = batch[0].to(device)
        b_labels = batch[0].to(device)
        b_masks = batch[1].to(device)
        model.zero_grad()
        outputs = model(  b_input_ids, labels=b_labels, 
                          attention_mask = b_masks, token_type_ids=None )
        loss = outputs[0]
        batch_loss = loss.item()
        total_train_loss += batch_loss
        # Get sample every x batches.
        if step % sample_every == 0 and not step == 0:
            elapsed = format_time(time.time() - t0)
            print('  Batch {:>5,}  of  {:>5,}. Loss: {:>5,}.   Elapsed: {:}.'.format(step, len(train_dataloader), batch_loss, elapsed))
        loss.backward()
        optimizer.step()
        scheduler.step()
    # Calculate the average loss over all of the batches.
    avg_train_loss = total_train_loss / len(train_dataloader)
    # Measure how long this epoch took.
    training_time = format_time(time.time() - t0)
    print("")
    print("  Average training loss: {0:.2f}".format(avg_train_loss))
    print("  Training epoch took: {:}".format(training_time))
    t0 = time.time()
    total_eval_loss = 0
    nb_eval_steps = 0
print("Training complete!")
print("Total training took {:} (h:mm:ss)".format(format_time(time.time()-total_t0)))

Guardar el modelo, para uso futuro

El tiempo de entreno varía según tu ordenador, memoria RAM y si tienes o no placa de video con GPU.

Luego de varias horas de entreno, mejor guardar el modelo para no tener que reentrenar cada vez y reutilizar el modelo que hicimos. Para guardar hacemos:

output_dir = './model_gpt_ibai/'
if not os.path.exists(output_dir):
    os.makedirs(output_dir)
model_to_save = model.module if hasattr(model, 'module') else model
model_to_save.save_pretrained(output_dir)
tokenizer.save_pretrained(output_dir)

En la notebook con el ejercicio verás también una celda con el código de ejemplo para cargar tu modelo ya entrenado.

Crear Texto al estilo influencer

Generamos 3 salidas preguntando ¿Qué es el fútbol? con máximo de 300 caracteres. Puedes variar estos parámetros para obtener más párrafos y con un máximo de 764 letras.

Fijate que agregamos al prompt el token de control de inicio (startoftext) pero también nuestro token de control que llamamos “ibai”.

model.eval()
prompt = "<|startoftext|>" + "<|ibai|>" + "¿ qué es el fútbol ?"
generated = torch.tensor(tokenizer.encode(prompt)).unsqueeze(0)
generated = generated.to(device)
sample_outputs = model.generate(
                                generated, 
                                num_return_sequences=3,
                                max_length = 300,
                                do_sample=True, 
                                top_k=50, 
                                top_p=0.95
                                )
for i, sample_output in enumerate(sample_outputs):
  print("{}: {}nn".format(i, tokenizer.decode(sample_output, skip_special_tokens=True)))

Y obtenemos como salidas:

  • <|ibai|>¿ qué es el fútbol? porque yo creo que son los mejores momentos de la vida del mundo. eh no se. eh y eso es lo que me llama la atención. eh ¡gracias! gracias.
  • <|ibai|>¿ qué es el fútbol? fútbol es la suma de sus defectos, del mal que uno puede poner en uno, de un mal que tú quieres que la gente haga como si nada, es decir, yo soy el contrario, yo soy el mejor, soy el peor. es decir, que tú eres el mejor y yo soy el peor. ¡joder, yo soy del mejor! ¡pero, bueno, ya está! ¿ tú eres el que está ahí para que me lo diga? ¿ me crees que estás bueno? no, no.
  • <|ibai|>¿ qué es el fútbol? para empezar, el que se mueve por el interés de los futbolistas es increíble. ¿ de quién eres? en el mejor sentido, y además de lo que eres. te he dicho que ya sé que a ti te gusta eso de hacer las cosas, pero eres muy, muy, muy, muy, muy, muy especial, ¿ no? un niño. te lo voy a decir un poco de corazón.

Ahora ya puedes jugar y probar tú mismo con distintas salidas!

Aquí te dejo algunos ejemplos divertidos que conseguí (pinto en negrita el comienzo del prompt enviado al modelo):

  • <|ibai|>para mi la vida es una cosa en la que te tienes que esforzar por lo que te gusta, porque quieres algo. cuando me pongo de acuerdo en algo me siento preparado para el momento. el ego que te llevas es la presión de no ser perfecto. y claro, si quieres, no quieres ser tan bueno, eres tú.
  • <|ibai|>la música que me gusta escuchar, claro. hay muchas personas que no nos conocen de nada, se nota. es un tío muy, muy, muy directo y creo que a lo mejor es un poco directo, de hecho, hay mucho ego en su actitud. la gente en general está bastante influenciada por él.
  • <|ibai|>un día todos deberíamos tener una vida, que es el futuro, una vida en paz con uno mismo, con la sociedad, y eso no es tan complicado como parece. y te digo lo de
  • <|ibai|>la felicidad es cuando hay armonía, que el mundo entero tiene su armonía. bueno, amigos, es que estamos unidos, a mí la música me relaja. bueno, es que no quiero dejar de escucharme ni de escuchar. y la música, de hecho, no es mi música, es mi vida.
  • <|ibai|>si voy a un restaurante, voy a un restaurante de argentina. me voy a un restaurante argentino. ¡ah, la verdad que me lo estoy pasando bien!
  • <|ibai|>la navidad es muy importante, porque es la época que vivimos. ¿ no crees que la navidad sería algo diferente de como la vivimos nosotros? en vez de algo muy tradicional, de un poco de juerga y de hacer una noche loca. no sé si la navidad es de las fechas en las que más fiesta hay. de verdad, no sé si es de las fechas en las que más fiesta hay o más fiesta no hay.
  • <|ibai|>en el próximo mes voy a empezar el segundo año. me llevo la bici para el club. de momento, voy a aprender a convivir con mis seguidores. y de hecho, hoy estoy hablando de eso.
  • <|ibai|>la inteligencia artificial, la realidad aumentada, ¿ qué pasa, tío? en este mundo hay gente que intenta crear un juego de magia que le pueda pasar un poquito de mal. bueno, que sí, que le pasa con las personas.
  • <|ibai|>la inteligencia artificial se está dando en todos los ámbitos. se está dando en todos los ámbitos, es cierto. en general, es un mundo donde la inteligencia artificial y el cerebro humano son los dos primeros motores.
  • <|ibai|>¿ qué es la inteligencia artificial? inteligencia artificial, es la de verdad. si la inteligencia artificial es más potente, es más fácil trabajar con ella. y es más difícil tener más inteligencia. porque la inteligencia artificial es la de verdad.
  • <|ibai|>yo sé mucho sobre el tema, pero me hace un poco de gracia. y también quiero que vosotros tengáis una gran audiencia, que leéis un libro, porque yo creo que eso es una idea que está muy bien. y es que si a tu amiga le pasa lo mismo que a ti, se va al final. por eso te pido que se ponga a grabar el libro, porque yo creo que eso, como el libro ya está hecho, le va a quedar espectacular.
  • <|ibai|>el amor es el camino, y no te vas a quedar ahí, a las 9. 40 am. el amor es un sentimiento que debe de ser muy fuerte en tu vida. a ver, yo creo que en la vida hay un tipo de personas que te hacen sentir una persona especial en tu vida. y el amor, que es la otra persona, también lo es.
Imagen generada por el autor con StableDiffusion

Resumen

En estos días estamos viendo cómo ChatGPT está siendo trending topic por ser el modelo GPT más poderoso y versátil de OpenAI, con capacidad de responder a cualquier pregunta, traducir idiomas, dar definiciones, crear poesía, historias y realizar snippets de código python.

En este artículo te acercamos un poco más a conocer qué son los modelos GPT que están revolucionando el campo del NLP mediante un ejercicio práctico.

Ya conoces un poco más sobre la librería transformers de HuggingFace, sobre los distintos modelos que puedes descargar en tu ordenador y personalizar. Como siempre, esto es sólo la punta del iceberg, te invito a que sigas investigando y aprendiendo más sobre todo ello y me dejes tus comentarios al respecto.

Nos vemos en el próximo post!

Puedes descargar la notebook con el ejercicio completo y el archivo con los textos de Ibai.

Otros Enlaces de interés

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 Generación de Texto en Español con GPT-2 first appeared on Aprende Machine Learning.

]]>
https://aprendemachinelearning.com/generacion-de-texto-en-espanol-con-gpt-2/feed/ 4 7869
Aprendizaje por Refuerzo https://aprendemachinelearning.com/aprendizaje-por-refuerzo/ https://aprendemachinelearning.com/aprendizaje-por-refuerzo/#comments Thu, 24 Dec 2020 08:00:00 +0000 https://www.aprendemachinelearning.com/?p=5911 En este artículo aprenderemos qué es el aprendizaje por refuerzo, lo más novedoso y ambicioso a día de hoy en Inteligencia artificial, veremos cómo funciona, sus casos de uso y haremos un ejercicio práctico completo en Python: una máquina que aprenderá a jugar al pong...

The post Aprendizaje por Refuerzo first appeared on Aprende Machine Learning.

]]>

En este artículo aprenderemos qué es el aprendizaje por refuerzo, lo más novedoso y ambicioso a día de hoy en Inteligencia artificial, veremos cómo funciona, sus casos de uso y haremos un ejercicio práctico completo en Python: una máquina que aprenderá a jugar al pong sóla, sin conocer las reglas ni al entorno.

Nuestra Agenda

Los temas que veremos incluyen:

  • ¿Qué es el Reinforcement Learning?
    • Diferencias con los clásicos
    • Componentes
  • Casos de Uso
    • Y los videojuegos?
  • Cómo funciona el RL?
    • premios y castigos
    • fuerza bruta
  • Q-Learning
    • Ecuación de Bellman
    • Explorar vs Explotar
  • El juego del Pong en Python
    • Clase Agente
    • Clase Environment
    • El juego
    • La tabla de Políticas
  • Conclusiones
    • Recursos Adicionales

Comencemos!!

¿Qué es el Aprendizaje por Refuerzo?

Seguramente ya conocerás las 2 grandes áreas de aprendizaje tradicional del Machine Learning, el aprendizaje supervisado y el aprendizaje no supervisado. Parece difícil que aquí hubiera espacio para otras opciones; sin embargo sí la hay y es el Aprendizaje por refuerzo. En aprendizaje por refuerzo (ó Reinforcement Learning en inglés) no tenemos una “etiqueta de salida”, por lo que no es de tipo supervisado y si bien estos algoritmos aprenden por sí mismos, tampoco son de tipo no supervisado, en donde se intenta clasificar grupos teniendo en cuenta alguna distancia entre muestras.

Si nos ponemos a pensar, los problemas de ML supervisados y no supervisados son específicos de un caso de negocio en particular, sea de clasificación ó predicción, están muy delimitados, por ejemplo, clasificar “perros ó gatos“, ó agrupar “k=5” clusters. En contraste, en el mundo real contamos con múltiples variables que por lo general se interrelacionan y que dependen de otros casos de negocio y dan lugar a escenarios más grandes en donde tomar decisiones. Para conducir un coche no basta una inteligencia que pueda detectar un semáforo en rojo, verde ó amarillo; tendremos muchísimos factores -todos a la vez- a los que prestar atención: a qué velocidad vamos, estamos ante una curva?, hay peatones?, es de noche y debemos encender las luces?.

Una solución sería tener múltiples máquinas de ML supervisadas y que interactúan entre si -y esto no estaría mal- ó podemos cambiar el enfoque… Y ahí aparece el Reinforcement Learning (RL) como una alternativa, tal vez de las más ambiciosas en las que se intenta integrar el Machine Learning en el mundo real, sobre todo aplicado a robots y maquinaria industrial.

El Reinforcement Learning entonces, intentará hacer aprender a la máquina basándose en un esquema de “premios y castigos” -cómo con el perro de Pablov- en un entorno en donde hay que tomar acciones y que está afectado por múltiples variables que cambian con el tiempo.

Diferencias con “los clásicos”

En los modelos de Aprendizaje Supervisado (o no supervisado) como redes neuronales, árboles, knn, etc, se intenta “minimizar la función coste”, reducir el error.

En cambio en el RL se intenta “maximizar la recompensa“. Y esto puede ser, a pesar de a veces cometer errores ó de no ser óptimos.

Componentes del RL

El Reinforcement Learning propone un nuevo enfoque para hacer que nuestra máquina aprenda, para ello, postula los siguientes 2 componentes:

  • el Agente: será nuestro modelo que queremos entrenar y que aprenda a tomar decisiones.
  • Ambiente: será el entorno en donde interactúa y “se mueve” el agente. El ambiente contiene las limitaciones y reglas posibles a cada momento.

Entre ellos hay una relación que se retroalimenta y cuenta con los siguientes nexos:

  • Acción: las posibles acciones que puede tomar en un momento determinado el Agente.
  • Estado (del ambiente): son los indicadores del ambiente de cómo están los diversos elementos que lo componen en ese momento.
  • Recompensas (ó castigos!): a raíz de cada acción tomada por el Agente, podremos obtener un premio ó una penalización que orientarán al Agente en si lo está haciendo bien ó mal.

Entonces, la “foto final” nos queda así:

En un primer momento, el agente recibe un estado inicial y toma una acción con lo cual influye é interviene en el ambiente. Esto está muy bien, pues es muy cierto que cuando tomamos decisiones en el mundo real lo estamos modificando, ¿no?. Y esa decisión tendrá sus consecuencias: en la siguiente iteración el ambiente devolverá al agente el nuevo estado y la recompensa obtenida. Si la recompensa es positiva estaremos reforzando ese comportamiento para el futuro. En cambio si la recompensa es negativa lo estaremos penalizando, para que ante la misma situación el agente actúe de manera distinta. El esquema en el que se apoya el Reinforcement Learning es en el de Proceso de Decisión de Markov.

Casos de Uso del Aprendizaje por Refuerzo

El aprendizaje por refuerzo puede ser usado en robots, por ejemplo en brazos mecánicos en donde en vez de enseñar instrucción por instrucción a moverse, podemos dejar que haga intentos “a ciegas” e ir recompensando cuando lo hace bien.

También puede usarse en ambientes que interactúan con el mundo real, como en otro tipo de maquinaria industrial y para el mantenimiento predictivo, pero también en el ambiente financiero, por ejemplo para decidir cómo conformar una cartera de inversión sin intervención humana.

Otro caso de uso que está ganando terreno es el de usar RL para crear “webs personalizadas” para cada internauta. Y si lo piensas… tiene algo de sentido tomar el concepto de “premiar” al algoritmo si acierta con las sugerencias que hace al usuario si hace clic ó penalizar al modelo si sus recomendaciones no le son de utilidad.

También se utiliza el Reinforcement Learning para entrenar sistemas de navegación de coches, drones ó aviones.

Y los Videojuegos? que pintan en todo esto?

Imagen del DeepMind en acción

Los videojuegos suelen ser ejemplos del uso de RL, ¿porque? te preguntarás. Pues porque los videojuegos son un entorno YA programado en el que se está simulando un ambiente y en el que ocurren eventos a la vez. Por lo general el jugador es el agente que debe decidir qué movimientos hacer. En el Starcraft tenemos ejércitos enemigos movilizados e intentando aniquilarnos, hay que desplazar distintas unidades que tienen variadas cualidades y hay que hacerlo rápido, atacar, defender, conquistar. ¿Cómo haríamos esto con un modelo de ML tradicional? es suficiente una sola red neuronal? muchas? cómo interactúan?. Pero sobre todo… ¿cómo crearíamos el grupo de “etiquetas de salida” para entrenar a la red, ante un juego imprevisible? Estamos diciendo que hay cientos de miles de combinaciones de salidas posibles.

Entonces, ¿Cómo funciona el RL?

Bien, vamos a comentar cómo funcionaría la secuencia de un algoritmo que aprende por refuerzo.

Cómo dijimos antes, el agente deberá tomar decisiones para interactuar con el ambiente, dado un estado. Pero, de qué manera tomar esas decisiones?

Premios y Castigos

Al principio de todo, nuestro agente está “en blanco”, es decir, no sabe nada de nada de lo que tiene que hacer ni de cómo comportarse. Entonces podemos pensar en que tomará una de las posibles acciones aleatoriamente. E irá recibiendo pistas de si lo está haciendo bien ó mal en base a las recompensas. Entonces irá “tomando nota”, esto bien, esto mal.

Una recompensa para un humano es algún estímulo que le de placer. Podría ser un aumento de sueldo, chocolate, una buena noticia. Para nuestro modelo de ML la recompensa es sencillamente un Score: un valor numérico.

Supongamos que la acción “A” nos recompensa con 100 puntos. El Agente podría pensar “genial, voy a elegir A nuevamente para obtener 100 puntos” y puede que el algoritmo se estanque en una única acción y nunca logre concretar el objetivo global que queremos lograr.

Es decir que tenemos que lograr un equilibrio entre “explorar lo desconocido y explotar los recursos” en el ambiente. Eso es conocido como el dilema de exploración/explotación.

El agente explorará el ambiente e irá aprendiendo “cómo moverse” y cómo ganar recompensas (y evitar las penalizaciones). Al final almacenará el conocimiento en unas normas también llamadas “políticas“.

Pero… debo decir que es probable que el agente “muera” ó pierda la partida las primeras… ¿mil veces? Con esto me refiero a que deberemos entrenar miles y miles de veces al agente para que cometa errores y aciertos y pueda crear sus políticas hasta ser un buen Agente.

¿Fuerza Bruta? En serio? estamos en 2020, por favor!

Bueno a decir la verdad si… esto es un poco vergonzoso… pero cierto. La realidad es que para hacerle aprender a un coche autónomo a conducir, debemos hacerlo chocar, acelerar, conducir contramano y cometer todo tipo de infracciones para decirle “eso está mal, te quito los puntos” y para ello, hay que hacer que ejecute miles y miles de veces en un entorno de simulado.

Para entrenar a DeepMind a dominar al Starcraft ha tenido que jugar el equivalente a miles de horas humanas de juego, y miles de partidas, puede que lo que le llevaría a una persona años, se logra en 8 horas. Y con ese aprendizaje logra vencer a los campeones jugadores humanos.

Esto tiene un lado bueno y uno malo. El malo ya lo vemos; tenemos que usar la fuerza bruta para que aprenda. Lo bueno es que contamos con equipos muy potentes que nos posibilitan realizar esta atrocidad. Por otra parte, recordemos que estamos apuntando a un caso de uso mucho más grande y ambicioso que el de “sólo distinguir entre perritos y gatitos” 😉

Q-Learning, el algoritmo más usado

Ahora vamos a comentar uno de los modelos usados en Reinforcement Learning para poder concretar un ejemplo de su implementación. Es el llamado “Q-Learning”.

Repasemos los elementos que tenemos:

  • Políticas: Es una tabla (aunque puede tener n-dimensiones) que le indicará al modelo “como actuar” en cada estado.
  • Acciones: las diversas elecciones que puede hacer el agente en cada estado
  • Recompensas: si sumamos ó restamos puntaje con la acción tomada
  • Comportamiento “avaro” (greedy en inglés) del agente. Es decir, si se dejará llevar por grandes recompensas inmediatas, ó irá explorando y valorando las riquezas a largo plazo

El objetivo principal al entrenar nuestro modelo a través de las simulaciones será ir “rellenando” la tabla de Políticas de manera que las decisiones que vaya tomando nuestro agente obtengan “la mayor recompensa” a la vez que avanzamos y no nos quedamos estancados, es decir, pudiendo cumplir el objetivo global (ó final) que deseamos alcanzar.

A la política la llamaremos “Q” por lo que:

Q(estado, acción) nos indicará el valor de la política para un estado y una acción determinados.

Y para saber cómo ir completando la tabla de políticas nos valemos de la ecuación de Bellman.

Ecuación de Bellman

La ecuación matemática que utilizaremos será:

No lo explicaré en detalle, pues tomaría mucho, pero en resumen; lo que explica la ecuación es cómo ir actualizando las políticas Q^(s,a) en base al valor actual más una futura recompensa que recibiremos, en caso de tomar dicha acción. Hay dos ratios que afectan a la manera en que influye esa recompensa: el ratio de aprendizaje, que regula “la velocidad” en la que se aprende, y la “tasa de descuento” que tendrá en cuenta la recompensa a corto o largo plazo.

Ejercicio Python de RL: Pong con Matplotlib

Hagamos una máquina que aprenda a jugar al Pong sóla (código completo en github).

Para no tener que instalar ningún paquete adicional… usaremos el propio matplotlib como interface gráfica del juego.

Este es el plan: simularemos el ambiente del juego y su compotamiento en la Jupyter Notebook.

El agente será el “player 1” y sus acciones posible son 2:

  1. mover hacia arriba
  2. mover hacia abajo

Y las reglas del juego:

  • El agente tiene 3 vidas.
  • Si pierde… castigo, restamos 10 puntos.
  • Cada vez que le demos a la bola, recompensa, sumamos 10.
  • Para que no quede jugando por siempre, limitaremos el juego a
    • 3000 iteraciones máximo ó
    • alcanzar 1000 puntos y habremos ganado.

Agreguemos los imports que usaremos:

import numpy as np
import matplotlib.pyplot as plt
from random import randint
from time import sleep
from IPython.display import clear_output
from math import ceil,floor

%matplotlib inline

La clase Agente

Dentro de la clase Agente encontraremos la tabla donde iremos almacenando las políticas. En nuestro caso la tabla cuenta de 3 coordenadas:

  1. La posición actual del jugador.
  2. La posición “y” de la pelota.
  3. La posición en el eje “x” de la pelota.

Además en esta clase, definiremos el factor de descuento, el learning rate y el ratio de exploración.

Los métodos más importantes:

  • get_next_step() decide la siguiente acción a tomar en base al ratio de exploración si tomar “el mejor paso” que tuviéramos almacenado ó tomar un paso al azar, dando posibilidad a explorar el ambiente
  • update() aquí se actualizan las políticas mediante la ecuación de Bellman que vimos anteriormente. Es su implementación en python.
class PongAgent:
    
    def __init__(self, game, policy=None, discount_factor = 0.1, learning_rate = 0.1, ratio_explotacion = 0.9):

        # Creamos la tabla de politicas
        if policy is not None:
            self._q_table = policy
        else:
            position = list(game.positions_space.shape)
            position.append(len(game.action_space))
            self._q_table = np.zeros(position)
        
        self.discount_factor = discount_factor
        self.learning_rate = learning_rate
        self.ratio_explotacion = ratio_explotacion

    def get_next_step(self, state, game):
        
        # Damos un paso aleatorio...
        next_step = np.random.choice(list(game.action_space))
        
        # o tomaremos el mejor paso...
        if np.random.uniform() <= self.ratio_explotacion:
            # tomar el maximo
            idx_action = np.random.choice(np.flatnonzero(
                    self._q_table[state[0],state[1],state[2]] == self._q_table[state[0],state[1],state[2]].max()
                ))
            next_step = list(game.action_space)[idx_action]

        return next_step

    # actualizamos las politicas con las recompensas obtenidas
    def update(self, game, old_state, action_taken, reward_action_taken, new_state, reached_end):
        idx_action_taken =list(game.action_space).index(action_taken)

        actual_q_value_options = self._q_table[old_state[0], old_state[1], old_state[2]]
        actual_q_value = actual_q_value_options[idx_action_taken]

        future_q_value_options = self._q_table[new_state[0], new_state[1], new_state[2]]
        future_max_q_value = reward_action_taken  +  self.discount_factor*future_q_value_options.max()
        if reached_end:
            future_max_q_value = reward_action_taken #maximum reward

        self._q_table[old_state[0], old_state[1], old_state[2], idx_action_taken] = actual_q_value + \
                                              self.learning_rate*(future_max_q_value -actual_q_value)
    
    def print_policy(self):
        for row in np.round(self._q_table,1):
            for column in row:
                print('[', end='')
                for value in column:
                    print(str(value).zfill(5), end=' ')
                print('] ', end='')
            print('')
            
    def get_policy(self):
        return self._q_table

La clase Environment

En la clase de Ambiente encontramos implementada la lógica y control del juego del pong. Se controla que la pelotita rebote, que no se salga de la pantalla y se encuentran los métodos para graficar y animar en matplotlib.

Por Defecto se define una pantalla de 40 pixeles x 50px de alto y si utilizamos la variable “movimiento_px = 5” nos quedará definida nuestra tabla de políticas en 8 de alto y 10 de ancho (por hacer 40/5=8 y 50/5=10). Estos valores se pueden modificar a gusto!

Además, muy importante, tenemos el control de cuándo dar las recompensas y penalizaciones, al perder cada vida y detectar si el juego a terminado

class PongEnvironment:
    
    def __init__(self, max_life=3, height_px = 40, width_px = 50, movimiento_px = 3):
        
        self.action_space = ['Arriba','Abajo']
        
        self._step_penalization = 0
        
        self.state = [0,0,0]
        
        self.total_reward = 0
        
        self.dx = movimiento_px
        self.dy = movimiento_px
        
        filas = ceil(height_px/movimiento_px)
        columnas = ceil(width_px/movimiento_px)
        
        self.positions_space = np.array([[[0 for z in range(columnas)] 
                                                  for y in range(filas)] 
                                                     for x in range(filas)])

        self.lives = max_life
        self.max_life=max_life
        
        self.x = randint(int(width_px/2), width_px) 
        self.y = randint(0, height_px-10)
        
        self.player_alto = int(height_px/4)

        self.player1 = self.player_alto  # posic. inicial del player
        
        self.score = 0
        
        self.width_px = width_px
        self.height_px = height_px
        self.radio = 2.5

    def reset(self):
        self.total_reward = 0
        self.state = [0,0,0]
        self.lives = self.max_life
        self.score = 0
        self.x = randint(int(self.width_px/2), self.width_px) 
        self.y = randint(0, self.height_px-10)
        return self.state

    def step(self, action, animate=False):
        self._apply_action(action, animate)
        done = self.lives <=0 # final
        reward = self.score
        reward += self._step_penalization
        self.total_reward += reward
        return self.state, reward , done

    def _apply_action(self, action, animate=False):
        
        if action == "Arriba":
            self.player1 += abs(self.dy)
        elif action == "Abajo":
            self.player1 -= abs(self.dy)
            
        self.avanza_player()

        self.avanza_frame()

        if animate:
            clear_output(wait=True);
            fig = self.dibujar_frame()
            plt.show()

        self.state = (floor(self.player1/abs(self.dy))-2, floor(self.y/abs(self.dy))-2, floor(self.x/abs(self.dx))-2)
    
    def detectaColision(self, ball_y, player_y):
        if (player_y+self.player_alto >= (ball_y-self.radio)) and (player_y <= (ball_y+self.radio)):
            return True
        else:
            return False
    
    def avanza_player(self):
        if self.player1 + self.player_alto >= self.height_px:
            self.player1 = self.height_px - self.player_alto
        elif self.player1 <= -abs(self.dy):
            self.player1 = -abs(self.dy)

    def avanza_frame(self):
        self.x += self.dx
        self.y += self.dy
        if self.x <= 3 or self.x > self.width_px:
            self.dx = -self.dx
            if self.x <= 3:
                ret = self.detectaColision(self.y, self.player1)

                if ret:
                    self.score = 10
                else:
                    self.score = -10
                    self.lives -= 1
                    if self.lives>0:
                        self.x = randint(int(self.width_px/2), self.width_px)
                        self.y = randint(0, self.height_px-10)
                        self.dx = abs(self.dx)
                        self.dy = abs(self.dy)
        else:
            self.score = 0

        if self.y < 0 or self.y > self.height_px:
            self.dy = -self.dy

    def dibujar_frame(self):
        fig = plt.figure(figsize=(5, 4))
        a1 = plt.gca()
        circle = plt.Circle((self.x, self.y), self.radio, fc='slategray', ec="black")
        a1.set_ylim(-5, self.height_px+5)
        a1.set_xlim(-5, self.width_px+5)

        rectangle = plt.Rectangle((-5, self.player1), 5, self.player_alto, fc='gold', ec="none")
        a1.add_patch(circle);
        a1.add_patch(rectangle)
        #a1.set_yticklabels([]);a1.set_xticklabels([]);
        plt.text(4, self.height_px, "SCORE:"+str(self.total_reward)+"  LIFE:"+str(self.lives), fontsize=12)
        if self.lives <=0:
            plt.text(10, self.height_px-14, "GAME OVER", fontsize=16)
        elif self.total_reward >= 1000:
            plt.text(10, self.height_px-14, "YOU WIN!", fontsize=16)
        return fig

El juego: Simular miles de veces para enseñar

Finalmente definimos una función para jugar, donde indicamos la cantidad de veces que queremos iterar la simulación del juego e iremos almacenando algunas estadísticas sobre el comportamiento del agente, si mejora el puntaje con las iteraciones y el máximo puntaje alcanzado.

def play(rounds=5000, max_life=3, discount_factor = 0.1, learning_rate = 0.1,
         ratio_explotacion=0.9,learner=None, game=None, animate=False):

    if game is None:
        game = PongEnvironment(max_life=max_life, movimiento_px = 3)
        
    if learner is None:
        print("Begin new Train!")
        learner = PongAgent(game, discount_factor = discount_factor,learning_rate = learning_rate, ratio_explotacion= ratio_explotacion)

    max_points= -9999
    first_max_reached = 0
    total_rw=0
    steps=[]

    for played_games in range(0, rounds):
        state = game.reset()
        reward, done = None, None
        
        itera=0
        while (done != True) and (itera < 3000 and game.total_reward<=1000):
            old_state = np.array(state)
            next_action = learner.get_next_step(state, game)
            state, reward, done = game.step(next_action, animate=animate)
            if rounds > 1:
                learner.update(game, old_state, next_action, reward, state, done)
            itera+=1
        
        steps.append(itera)
        
        total_rw+=game.total_reward
        if game.total_reward > max_points:
            max_points=game.total_reward
            first_max_reached = played_games
        
        if played_games %500==0 and played_games >1 and not animate:
            print("-- Partidas[", played_games, "] Avg.Puntos[", int(total_rw/played_games),"]  AVG Steps[", int(np.array(steps).mean()), "] Max Score[", max_points,"]")
                
    if played_games>1:
        print('Partidas[',played_games,'] Avg.Puntos[',int(total_rw/played_games),'] Max score[', max_points,'] en partida[',first_max_reached,']')
        
    #learner.print_policy()
    
    return learner, game

Para entrenar ejecutamos la función con los siguientes parámetros:

  • 6000 partidas jugará
  • ratio de explotación: el 85% de las veces será avaro, pero el 15% elige acciones aleatorias, dando lugar a la exploración.
  • learning rate = se suele dejar en el 10 por ciento como un valor razonable, dando lugar a las recompensas y permitiendo actualizar la importancia de cada acción poco a poco. Tras más iteraciones, mayor importancia tendrá esa acción.
  • discount_factor = También se suele empezar con valor de 0.1 pero aquí utilizamos un valor del 0.2 para intentar indicar al algoritmo que nos interesa las recompensas a más largo plazo.
learner, game = play(rounds=6000, discount_factor = 0.2, learning_rate = 0.1, ratio_explotacion=0.85)

Y vemos la salida del entreno, luego de unos 2 minutos:

Begin new Train! 
-- Partidas[ 500 ] Avg.Puntos[ -234 ]  AVG Steps[ 116 ] Max Score[ 10 ] 
-- Partidas[ 1000 ] Avg.Puntos[ -224 ]  AVG Steps[ 133 ] Max Score[ 100 ] 
-- Partidas[ 1500 ] Avg.Puntos[ -225 ]  AVG Steps[ 134 ] Max Score[ 230 ] 
-- Partidas[ 2000 ] Avg.Puntos[ -223 ]  AVG Steps[ 138 ] Max Score[ 230 ] 
-- Partidas[ 2500 ] Avg.Puntos[ -220 ]  AVG Steps[ 143 ] Max Score[ 230 ] 
-- Partidas[ 3000 ] Avg.Puntos[ -220 ]  AVG Steps[ 145 ] Max Score[ 350 ] 
-- Partidas[ 3500 ] Avg.Puntos[ -220 ]  AVG Steps[ 144 ] Max Score[ 350 ] 
-- Partidas[ 4000 ] Avg.Puntos[ -217 ]  AVG Steps[ 150 ] Max Score[ 350 ] 
-- Partidas[ 4500 ] Avg.Puntos[ -217 ]  AVG Steps[ 151 ] Max Score[ 410 ] 
-- Partidas[ 5000 ] Avg.Puntos[ -216 ]  AVG Steps[ 153 ] Max Score[ 510 ] 
-- Partidas[ 5500 ] Avg.Puntos[ -214 ]  AVG Steps[ 156 ] Max Score[ 510 ] 
Partidas[ 5999 ] Avg.Puntos[ -214 ] Max score[ 510 ] en partida[ 5050 ]

En las salidas vemos sobre todo cómo va mejorando en la cantidad de “steps” que da el agente antes de perder la partida.

Veamos el resultado!

Ya contamos con nuestro agente entrenado, ahora veamos qué tal se comporta en una partida de pong, y lo podemos ver jugar, pasando el parámetro animate=True.

Antes de jugar, instanciamos un nuevo agente “learner2” que utilizará las políticas que creamos anteriormente. A este agente le seteamos el valor de explotación en 1, para evitar que tome pasos aleatorios.

learner2 = PongAgent(game, policy=learner.get_policy())
learner2.ratio_explotacion = 1.0  # con esto quitamos las elecciones aleatorias al jugar
player = play(rounds=1, learner=learner2, game=game, animate=True)

Y veremos nuestro juego de Pong en acción!

En mi caso, con las 6 mil iteraciones de entrenamiento fue suficiente alcanzar los 500 puntos y ganar (puedes ir variando el objetivo a 500 puntos ó a 1000, la cantidad de vidas, etc.)

La Tabla de políticas resultante

Quiero brevemente comentar la tabla de políticas que hemos creado luego de entrenar.

En este ejemplo, mostraré una tabla de 3 coordenadas. La primera toma valores del 0 al 7 (posición del jugador), la segunda también 8 valores (altura de la bola de pong) y la tercera va del 0 al 9 con el desplazamiento horizontal de la pelota.

Supongamos que el player está situado en la posición “de abajo de todo”, es decir, en la posición cero.

Dentro de esa posición queda conformada la siguiente tabla:

Aquí vemos la tabla con las acciones a tomar si el jugador está en la posición cero y según donde se encuentre la bola en los valores x e y. Recuerda que tenemos creadas 8 tablas cómo esta, para cada posición del player.

Si nos fijamos en la coordenada de la bola (x8, y1) vemos los valores 1.9 para subir y -9 para bajar. Claramente la recompensa mayor está en la acción de subir. Pero si la pelotita estuviera en (x9,y4) la mejor acción será Bajar, aunque tenga un puntaje negativo de -16,7 será mejor que restar 46.

Conclusiones

Hay muchos más detalles y lecturas adicionales para dominar el tema, pero en este artículo hemos explicado los conceptos básicos del reinforcement learning, sus diferencias con el aprendizaje supervisado y sus características.

Además conocimos su implementación más conocida, el Q-Learning y realizamos un juego completo en Python en donde el Agente sin tener conocimiento previo de las reglas ni del entorno logra aprender y volverse un muy buen jugador de Pong tras miles de simulaciones.

Debo decir que una evolución muy interesante del Aprendizaje por Refuerzo es el Aprendizaje por Refuerzo Profundo en donde aparecen las redes neuronales a mejorar y perfeccionar al modelo. Escibiré sobre ello en un próximo artículo!

Deseos Finales

Aprovecho a desearles un muy buen fin de año y a que puedan empezar el 2021 con muchos planes y muchas ganas de seguir aprendiendo sobre Machine Learning y la ciencia de datos.

También les invito a descargar ó comprar “el libro del blog” en formato digital y como novedad, he logrado publicar en la tienda de Amazon la versión del libro en formato papel, en gran parte por algunos de vosotros que me lo pidieron. Así que mil gracias porque gracias a ese empuje y ánimo que me dieron, puedo decir que termino el año con mi primer libro publicado, lo cual para mi es un sueño cumplido! Y -perdón la insistencia con esto- pero ciertamente este año ha sido un año muy difícil para mi al igual que para todos y jamás hubiera pensado haberlo podido conseguir. Es un hito en mi vida.

Muchas gracias querido lector, desde aquí te envío un sincero abrazo virtual!.

Material del Artículo

Descarga la notebook completa desde GitHub aqui

Recursos Adicionales

Otros artículos relacionados:

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 una mano, puedes comprar el libro en papel, ó en digital.

The post Aprendizaje por Refuerzo first appeared on Aprende Machine Learning.

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

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

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

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

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

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

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

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

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

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

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

Tipos de motores

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

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

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

¿Cómo funciona Collaborative Filtering?

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

Ejemplo de Dataset

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

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

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

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

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

Tipos de Collaborative Filtering

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

Predecir gustos (User-based)

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

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

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

Calcular los Ratings

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

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

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

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

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

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

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

Vamos al código!

Cargamos las librerías que utilizaremos

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

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

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

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

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

30 users
167 items

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

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

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

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

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

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

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

Creamos la matriz usuarios/ratings

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

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

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

Sparcity

Veamos el porcentaje de sparcity que tenemos:

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

Sparsity: 6.43%

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

Dividimos en Train y Test set

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

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

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

(24, 167)
(6, 167)

Matriz de Similitud: Distancias por Coseno

Ahora calculamos en una nueva matriz la similitud entre usuarios.

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

(30, 30)

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

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

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

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

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

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

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

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

user0=users_predictions.argsort()[usuario_ver]

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

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

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

Validemos el error

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

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

get_mse(users_predictions, ratings_train)

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

get_mse(users_predictions_test, ratings_test)

3.39
4.72

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

Hay más…

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

Conclusiones

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

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

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

Forma parte del Blog!

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

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

Recursos del Artículo

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

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

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

]]>
https://aprendemachinelearning.com/sistemas-de-recomendacion/feed/ 25 5989
Random Forest, el poder del Ensamble https://aprendemachinelearning.com/random-forest-el-poder-del-ensamble/ https://aprendemachinelearning.com/random-forest-el-poder-del-ensamble/#respond Mon, 17 Jun 2019 08:00:00 +0000 https://www.aprendemachinelearning.com/?p=6001 Si ya leíste el algoritmo de árbol de Decisión con Aprendizaje Automático, tu próximo paso es el de estudiar Random Forest. Comprende qué és y cómo funciona con un ejemplo práctico en Python. Podrás descargar el código de ejemplo en una Jupyter Notebook -como siempre-....

The post Random Forest, el poder del Ensamble first appeared on Aprende Machine Learning.

]]>
Si ya leíste el algoritmo de árbol de Decisión con Aprendizaje Automático, tu próximo paso es el de estudiar Random Forest. Comprende qué és y cómo funciona con un ejemplo práctico en Python. Podrás descargar el código de ejemplo en una Jupyter Notebook -como siempre-.

Random Forest es un tipo de Ensamble en Machine Learning en donde combinaremos diversos árboles -ya veremos cómo y con qué características- y la salida de cada uno se contará como “un voto” y la opción más votada será la respuesta del <<Bosque Aleatorio>>.

Random Forest, al igual que el árbol e decisión, es un modelo de aprendizaje supervisado para clasificación (aunque también puede usarse para problemas de regresión).

¿Cómo surge Random Forest?

Uno de los problemas que aparecía con la creación de un árbol de decisión es que si le damos la profundidad suficiente, el árbol tiende a “memorizar” las soluciones en vez de generalizar el aprendizaje. Es decir, a padecer de overfitting. La solución para evitar esto es la de crear muchos árboles y que trabajen en conjunto. Veamos cómo.

Cómo funciona Random Forest?

Random Forest funciona así:

  • Seleccionamos k features (columnas) de las m totales (siendo k menor a m) y creamos un árbol de decisión con esas k características.
  • Creamos n árboles variando siempre la cantidad de k features y también podríamos variar la cantidad de muestras que pasamos a esos árboles (esto es conocido como “bootstrap sample”)
  • Tomamos cada uno de los n árboles y le pedimos que hagan una misma clasificación. Guardamos el resultado de cada árbol obteniendo n salidas.
  • Calculamos los votos obtenidos para cada “clase” seleccionada y consideraremos a la más votada como la clasificación final de nuestro “bosque”.

¿Por qué es aleatorio?

Contamos con una <<doble aleatoriedad>>: tanto en la selección del valor k de características para cada árbol como en la cantidad de muestras que usaremos para entrenar cada árbol creado.

Es curioso que para este algoritmo la aleatoriedad sea tan importante y de hecho es lo que lo “hace bueno”, pues le brinda flexibilidad suficiente como para poder obtener gran variedad de árboles y de muestras que en su conjunto aparentemente caótico, producen una salida concreta. Darwin estaría orgulloso 😉

Ventajas y Desventajas del uso de Random Forest

Vemos algunas de sus ventajas son:

  • funciona bien -aún- sin ajuste de hiperparámetros
  • funciona bien para problemas de clasificación y también de regresión.
  • al utilizar múltiples árboles se reduce considerablemente el riesgo de overfiting
  • se mantiene estable con nuevas muestras puesto que al utilizar cientos de árboles sigue prevaleciendo el promedio de sus votaciones.

Y sus desjeventajas:

  • en algunos datos de entrada “particulares” random forest también puede caer en overfitting
  • es mucho más “costo” de crear y ejecutar que “un sólo árbol” de decisión.
  • Puede requerir muchísimo tiempo de entrenamiento
  • OJO! Random Forest no funciona bien con datasets pequeños.
  • Es muy difícil poder interpretar los ¿cientos? de árboles creados en el bosque, si quisiéramos comprender y explicar a un cliente su comportamiento.

Vamos al Código Python

Continuaremos con el ejercicio propuesto en el artículo “desbalanceo de datos” en donde utilizamos el dataset de Kaggle con información de fraude en tarjetas de crédito. Cuenta con 284807 filas y 31 columnas de características. Nuestra salida será 0 si es un cliente “normal” o 1 si hizo uso fraudulento.

¿Llegas a ver la mínima linea roja que representa los casos de Fraude? son apenas 492 frente a más de 250.000 casos de uso normal.

Retomaremos el mejor caso que obtuvimos en el ejercicio anterior utilizando Regresión Logística y logrando un 98% de aciertos, pero recuerda también las métricas de F1, precisión y recall que eran las que realmente nos ayudaban a validar el modelo.

Requerimientos para hacer el ejercicio Random Forest

Necesitaremos tener instalado Python 3.6 en el sistema y como lo haremos en una Notebook Jupyter, recomiendo tener instalada la suite de Anaconda que simplificará todo.

¿Cómo instalar el ambiente de desarrollo Python con Anaconda?

Pues vamos con nuestro Bosque!

Creamos el modelo y lo entrenamos

Utilizaremos el modelo RandomForrestClassifier de SkLearn.

from sklearn.ensemble import RandomForestClassifier

# Crear el modelo con 100 arboles
model = RandomForestClassifier(n_estimators=100, 
                               bootstrap = True, verbose=2,
                               max_features = 'sqrt')
# a entrenar!
model.fit(X_train, y_train)

Luego de unos minutos obtendremos el modelo entrenado (en mi caso 1 minuto 30 segundos)

Los Hiperparámetros más importantes

Al momento de ajustar el modelo, debemos tener en cuenta los siguientes hiperparámetros. Estos nos ayudarán a que el bosque de mejores resultados para cada ejercicio. Recuerda que esto no se trata de “copiar y pegar”!

  • n_estimators: será la cantidad de árboles que generaremos.
  • max_features: la manera de seleccionar la cantidad máxima de features para cada árbol.
  • min_sample_leaf: número mínimo de elementos en las hojas para permitir un nuevo split (división) del nodo.
  • oob_score: es un método que emula el cross-validation en árboles y permite mejorar la precisión y evitar overfitting.
  • boostrap: para utilizar diversos tamaños de muestras para entrenar. Si se pone en falso, utilizará siempre el dataset completo.
  • n_jobs: si tienes multiples cores en tu CPU, puedes indicar cuantos puede usar el modelo al entrenar para acelerar el entrenamiento.

Evaluamos resultados

Veamos la matriz de confusión y las métricas sobre el conjunto de test!!! (no confundir con el de training!!!)

Vemos muy buenos resultados, clasificando con error apenas 11 + 28 muestras.

Aquí podemos destacar que para la clase “minoritaria”, es decir la que detecta los casos de fraude tenemos un buen valor de recall (de 0.80) lo cual es un buen indicador! y el F1-score macro avg es de 0.93. Logramos construir un modelo de Bosque aleatorio que a pesar de tener un conjunto de datos de entrada muy desigual, logra buenos resultados.

Comparamos con el Baseline

Si comparamos estos resultados con los del algoritmo de Regresión Logística, vemos que el Random Forest nos dio mejores clasificaciones, menos falsos positivos y mejores métricas en general.

Conclusiones

Avanzando en nuestro aprendizaje sobre diversos modelos que podemos aplicar a las problemáticas que nos enfrentamos, hoy sumamos a nuestro kit de herramientas el Random Forest, vemos que es un modelo sencillo, bastante rápido y si bien perdemos la interpretabilidad maravillosa que nos brindaba 1 sólo árbol de decisión, es el precio a pagar para evitar el overfitting y para ganar un clasificador más robusto.

Los algoritmos Tree-Based -en inglés- son muchos, todos parten de la idea principal de árbol de decisión y la mejoran con diferentes tipos de ensambles y técnicas. Tenemos que destacar a 2 modelos que según el caso logran superar a las mismísimas redes neuronales! son XGboost y LightGBM. Si te parecen interesantes puede que en el futuro escribamos sobre ellos.

Suscribete 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 que agregues nuestro remitente a tus contactos para evitar problemas. Gracias!

Recursos y Adicionales

Puedes descargar la notebook para este ejercicio desde mi cuenta de GitHub:

Otros artículos sobre Random Forest en inglés:

The post Random Forest, el poder del Ensamble first appeared on Aprende Machine Learning.

]]>
https://aprendemachinelearning.com/random-forest-el-poder-del-ensamble/feed/ 0 6001
Clasificación con datos desbalanceados https://aprendemachinelearning.com/clasificacion-con-datos-desbalanceados/ https://aprendemachinelearning.com/clasificacion-con-datos-desbalanceados/#comments Thu, 16 May 2019 08:00:00 +0000 https://www.aprendemachinelearning.com/?p=6881 Contrarrestar problemas con clases desbalanceadas Estrategias para resolver desequilibrio de datos en Python con la librería imbalanced-learn. Tabla de contenidos: ¿Qué son las clases desequilibradas en un dataset? Métricas y Confusión Matrix Ejercicio con Python Estrategias Modelo sin modificar Penalización para compensar / Métricas Resampling...

The post Clasificación con datos desbalanceados first appeared on Aprende Machine Learning.

]]>
Contrarrestar problemas con clases desbalanceadas

Estrategias para resolver desequilibrio de datos en Python con la librería imbalanced-learn.

Tabla de contenidos:

  1. ¿Qué son las clases desequilibradas en un dataset?
  2. Métricas y Confusión Matrix
  3. Ejercicio con Python
  4. Estrategias
  5. Modelo sin modificar
  6. Penalización para compensar / Métricas
  7. Resampling y Muestras sintéticas
    1. subsampling
    2. oversamplig
    3. combinación
  8. Balanced Ensemble

Empecemos!

¿Qué son los problemas de clasificación de Clases desequilibradas? (imbalanced data)

En los problemas de clasificación en donde tenemos que etiquetar por ejemplo entre “spam” o “not spam” ó entre múltiples categorías (coche, barco, avión) solemos encontrar que en nuestro conjunto de datos de entrenamiento contamos con que alguna de las clases de muestra es una clase “minoritaria” es decir, de la cual tenemos muy poquitas muestras. Esto provoca un desbalanceo en los datos que utilizaremos para el entrenamiento de nuestra máquina.

Un caso evidente es en el área de Salud en donde solemos encontrar conjuntos de datos con miles de registros con pacientes “negativos” y unos pocos casos positivos es decir, que padecen la enfermedad que queremos clasificar.

Otros ejemplos suelen ser los de Detección de fraude donde tenemos muchas muestras de clientes “honestos” y pocos casos etiquetados como fraudulentos. Ó en un funnel de marketing, en donde por lo general tenemos un 2% de los datos de clientes que “compran” ó ejecutan algún tipo de acción (CTA) que queremos predecir.

¿Cómo nos afectan los datos desbalanceados?

Por lo general afecta a los algoritmos en su proceso de generalización de la información y perjudicando a las clases minoritarias. Esto suena bastante razonable: si a una red neuronal le damos 990 de fotos de gatitos y sólo 10 de perros, no podemos pretender que logre diferenciar una clase de otra. Lo más probable que la red se limite a responder siempre “tu foto es un gato” puesto que así tuvo un acierto del 99% en su fase de entrenamiento.

Métricas y Confusion Matrix

Como decía, si medimos la efectividad de nuestro modelo por la cantidad de aciertos que tuvo, sólo teniendo en cuenta a la clase mayoritaria podemos estar teniendo una falsa sensación de que el modelo funciona bien.

Para poder entender esto un poco mejor, utilizaremos la llamada “Confusión matrix” que nos ayudará a comprender las salidas de nuestra máquina:

Y de aqui salen nuevas métricas: precisión y recall

Veamos la Confusion matrix con el ejemplo de las predicciones de perro y gato.

Breve explicación de estás métricas:

La Accuracy del modelo es básicamente el numero total de predicciones correctas dividido por el número total de predicciones. En este caso da 99% cuando no hemos logrado identificar ningún perro.

La Precisión de una clase define cuan confiable es un modelo en responder si un punto pertenece a esa clase. Para la clase gato será del 99% sin embargo para la de perro será 0%.

El Recall de una clase expresa cuan bien puede el modelo detectar a esa clase. Para gatos será de 1 y para perros 0.

El F1 Score de una clase es dada por la media harmonía de precisión y recall (2 x precision x recall / (precision+recall)) digamos que combina precisión y recall en una sola métrica. En nuestro caso daría cero para perros!.

Tenemos cuatro casos posibles para cada clase:

  • Alta precision y alto recall: el modelo maneja perfectamente esa clase
  • Alta precision y bajo recall: el modelo no detecta la clase muy bien, pero cuando lo hace es altamente confiable.
  • Baja precisión y alto recall: La clase detecta bien la clase pero también incluye muestras de otras clases.
  • Baja precisión y bajo recall: El modelo no logra clasificar la clase correctamente.

Cuando tenemos un dataset con desequilibrio, suele ocurrir que obtenemos un alto valor de precisión en la clase Mayoritaria y un bajo recall en la clase Minoritaria

MUY importante que conozcas los conceptos de Train, test y validación cruzada.

Vamos al Ejercicio con Python!

Usaremos el set de datos Credit Card Fraut Detection de la web de Kaggle. Son 66 MB que al descomprimir ocuparán 150MB. Usaremos el archivo creditcard.csv. Este dataset consta de 285.000 filas con 31 columnas (features). Como la información es privada, no sabemos realmente que significan los features y están nombradas como V1, V2, V3, etc. excepto por las columnas Time y Amount (el importe de la transacción). Y nuestras clases son 0 y 1 correspondiendo con “transacción Normal” ó “Hubo Fraude”. Como podrán imaginar, el set de datos está muy desequilibrado y tendremos muy pocas muestras etiquetadas como fraude.

La notebook que acompaña este artículo puedes verla aquí en Github y en los recursos, al final del artículo.

También debo decir que no nos centraremos tanto en la elección del modelo ni en su configuración y tuneo si no que nos centraremos en aplicar las diversas estrategias para mejorar los resultados a pesar del desequilibrio de clases.

Requerimientos Técnicos

Necesitaremos tener Python 3.6 en el sistema y como lo haremos en una Notebook Jupyter, recomiendo tener instalada Anaconda.

¿Cómo instalar mi ambiente de desarrollo Python – Anaconda?

Instala la librería de Imbalanced Learn desde linea de comando con: (toda la documentación en la web oficial imblearn)

pip install -U imbalanced-learn

Veamos el dataset

Análisis exploratorio, para comprobar el desequilibrio entre las clases

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

from sklearn.metrics import confusion_matrix
from sklearn.metrics import classification_report
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.decomposition import PCA
from sklearn.tree import DecisionTreeClassifier

from pylab import rcParams

from imblearn.under_sampling import NearMiss
from imblearn.over_sampling import RandomOverSampler
from imblearn.combine import SMOTETomek
from imblearn.ensemble import BalancedBaggingClassifier

from collections import Counter

Luego de importar las librerías que usaremos, cargamos con pandas el dataframe y vemos las primeras filas:

df = pd.read_csv("creditcard.csv") # read in data downloaded to the local directory
df.head(n=5)

Veamos de cuantas filas tenemos y cuantas hay de cada clase:

print(df.shape)
print(pd.value_counts(df['Class'], sort = True))

(284807, 31)

0 284315
1 492
Name: Class, dtype: int64

Vemos que son 284.807 filas y solamente 492 son la clase minoritaria con los casos de fraude. Representan el 0,17% de las muestras.

count_classes = pd.value_counts(df['Class'], sort = True)
count_classes.plot(kind = 'bar', rot=0)
plt.xticks(range(2), LABELS)
plt.title("Frequency by observation number")
plt.xlabel("Class")
plt.ylabel("Number of Observations");
¿Llegas a ver la mínima linea roja que representa los casos de Fraude? son muy pocas muestras!

Estrategias para el manejo de Datos Desbalanceados:

Tenemos diversas estrategias para tratar de mejorar la situación. Las comentaremos brevemente y pasaremos a la acción (al código!) a continuación.

  1. Ajuste de Parámetros del modelo: Consiste en ajustar parametros ó metricas del propio algoritmo para intentar equilibrar a la clase minoritaria penalizando a la clase mayoritaria durante el entrenamiento. Ejemplos on ajuste de peso en árboles, también en logisticregression tenemos el parámetro class_weight= “balanced” que utilizaremos en este ejemplo. No todos los algoritmos tienen estas posibilidades. En redes neuronales por ejemplo podríamos ajustar la métrica de Loss para que penalice a las clases mayoritarias.
  2. Modificar el Dataset: podemos eliminar muestras de la clase mayoritaria para reducirlo e intentar equilibrar la situación. Tiene como “peligroso” que podemos prescindir de muestras importantes, que brindan información y por lo tanto empeorar el modelo. Entonces para seleccionar qué muestras eliminar, deberíamos seguir algún criterio. También podríamos agregar nuevas filas con los mismos valores de las clases minoritarias, por ejemplo cuadriplicar nuestras 492 filas. Pero esto no sirve demasiado y podemos llevar al modelo a caer en overfitting.
  3. Muestras artificiales: podemos intentar crear muestras sintéticas (no idénticas) utilizando diversos algoritmos que intentan seguir la tendencia del grupo minoritario. Según el método, podemos mejorar los resultados. Lo peligroso de crear muestras sintéticas es que podemos alterar la distribución “natural” de esa clase y confundir al modelo en su clasificación.
  4. Balanced Ensemble Methods: Utiliza las ventajas de hacer ensamble de métodos, es decir, entrenar diversos modelos y entre todos obtener el resultado final (por ejemplo “votando”) pero se asegura de tomar muestras de entrenamiento equilibradas.

Apliquemos estas técnicas de a una a nuestro código y veamos los resultados.

PERO… antes de empezar, ejecutaremos el modelo de Regresión Logística “desequilibrado”, para tener un “baseline”, es decir unas métricas contra las cuales podremos comparar y ver si mejoramos.

Probando el Modelo “a secas” -sin estrategias-

#definimos nuestras etiquetas y features
y = df['Class']
X = df.drop('Class', axis=1)
#dividimos en sets de entrenamiento y test
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.7)

#creamos una función que crea el modelo que usaremos cada vez
def run_model(X_train, X_test, y_train, y_test):
    clf_base = LogisticRegression(C=1.0,penalty='l2',random_state=1,solver="newton-cg")
    clf_base.fit(X_train, y_train)
    return clf_base

#ejecutamos el modelo "tal cual"
model = run_model(X_train, X_test, y_train, y_test)

#definimos funciona para mostrar los resultados
def mostrar_resultados(y_test, pred_y):
    conf_matrix = confusion_matrix(y_test, pred_y)
    plt.figure(figsize=(12, 12))
    sns.heatmap(conf_matrix, xticklabels=LABELS, yticklabels=LABELS, annot=True, fmt="d");
    plt.title("Confusion matrix")
    plt.ylabel('True class')
    plt.xlabel('Predicted class')
    plt.show()
    print (classification_report(y_test, pred_y))

pred_y = model.predict(X_test)
mostrar_resultados(y_test, pred_y)

Aqui vemos la confusion matrix y en la clase 2 (es lo que nos interesa detectar) vemos 51 fallos y 97 aciertos dando un recall de 0.66 y es el valor que queremos mejorar. También es interesante notar que en la columna de f1-score obtenemos muy buenos resultados PERO que realmente no nos deben engañar… pues están reflejando una realidad parcial. Lo cierto es que nuestro modelo no es capaz de detectar correctamente los casos de Fraude.

Estrategia: Penalización para compensar

Utilizaremos un parámetro adicional en el modelo de Regresión logística en donde indicamos weight = “balanced” y con esto el algoritmo se encargará de equilibrar a la clase minoritaria durante el entrenamiento. Veamos:

def run_model_balanced(X_train, X_test, y_train, y_test):
    clf = LogisticRegression(C=1.0,penalty='l2',random_state=1,solver="newton-cg",class_weight="balanced")
    clf.fit(X_train, y_train)
    return clf

model = run_model_balanced(X_train, X_test, y_train, y_test)
pred_y = model.predict(X_test)
mostrar_resultados(y_test, pred_y)

Ahora vemos una NOTABLE MEJORA! en la clase 2 -que indica si hubo fraude-, se han acertado 137 muestras y fallado en 11, dando un recall de 0.93 !! y sólo con agregar un parámetro al modelo 😉 También notemos que en la columna de f1-score parecería que hubieran “empeorado” los resultados… cuando realmente estamos mejorando la detección de casos fraudulentos. Es cierto que aumentan los Falsos Positivos y se han etiquetado 1890 muestras como Fraudulentas cuando no lo eran… pero ustedes piensen… ¿qué prefiere la compañía bancaria? ¿tener que revisar esos casos manualmente ó fallar en detectar los verdaderos casos de fraude?

Sigamos con más métodos:

Estrategia: Subsampling en la clase mayoritaria

Lo que haremos es utilizar un algoritmo para reducir la clase mayoritaria. Lo haremos usando un algoritmo que hace similar al k-nearest neighbor para ir seleccionando cuales eliminar. Fijemonos que reducimos bestialmente de 199.020 muestras de clase cero (la mayoría) y pasan a ser 688. y Con esas muestras entrenamos el modelo.

us = NearMiss(ratio=0.5, n_neighbors=3, version=2, random_state=1)
X_train_res, y_train_res = us.fit_sample(X_train, y_train)

print ("Distribution before resampling {}".format(Counter(y_train)))
print ("Distribution after resampling {}".format(Counter(y_train_res)))

model = run_model(X_train_res, X_test, y_train_res, y_test)
pred_y = model.predict(X_test)
mostrar_resultados(y_test, pred_y)

Distribution before resampling Counter({0: 199020, 1: 344})
Distribution after resampling Counter({0: 688, 1: 344})

También vemos que obtenemos muy buen resultado con recall de 0.93 aunque a costa de que aumentaran los falsos positivos.

Estrategia: Oversampling de la clase minoritaria

En este caso, crearemos muestras nuevas “sintéticas” de la clase minoritaria. Usando RandomOverSampler. Y vemos que pasamos de 344 muestras de fraudes a 99.510.

os =  RandomOverSampler(ratio=0.5)
X_train_res, y_train_res = os.fit_sample(X_train, y_train)

print ("Distribution before resampling {}".format(Counter(y_train)))
print ("Distribution labels after resampling {}".format(Counter(y_train_res)))

model = run_model(X_train_res, X_test, y_train_res, y_test)
pred_y = model.predict(X_test)
mostrar_resultados(y_test, pred_y)
Distribution before resampling Counter({0: 199020, 1: 344})
Distribution after resampling Counter({0: 199020, 1: 99510})

Tenemos un 0.89 de recall para la clase 2 y los Falsos positivos son 838. Nada mal.

Estrategia: Combinamos resampling con Smote-Tomek

Ahora probaremos una técnica muy usada que consiste en aplicar en simultáneo un algoritmo de subsampling y otro de oversampling a la vez al dataset. En este caso usaremos SMOTE para oversampling: busca puntos vecinos cercanos y agrega puntos “en linea recta” entre ellos. Y usaremos Tomek para undersampling que quita los de distinta clase que sean nearest neighbor y deja ver mejor el decisión boundary (la zona limítrofe de nuestras clases).

os_us = SMOTETomek(ratio=0.5)
X_train_res, y_train_res = os_us.fit_sample(X_train, y_train)

print ("Distribution before resampling {}".format(Counter(y_train)))
print ("Distribution after resampling {}".format(Counter(y_train_res)))

model = run_model(X_train_res, X_test, y_train_res, y_test)
pred_y = model.predict(X_test)
mostrar_resultados(y_test, pred_y)
Distribution labels before resampling Counter({0: 199020, 1: 344})
Distribution after resampling Counter({0: 198194, 1: 98684})

En este caso seguimos teniendo bastante buen recall 0.85 de la clase 2 y vemos que los Falsos positivos de la clase 1 son bastante pocos, 325 (de 85295 muestras).

Estrategia: Ensamble de Modelos con Balanceo

Para esta estrategia usaremos un Clasificador de Ensamble que utiliza Bagging y el modelo será un DecisionTree. Veamos como se comporta:

bbc = BalancedBaggingClassifier(base_estimator=DecisionTreeClassifier(),
                                sampling_strategy='auto',
                                replacement=False,
                                random_state=0)

#Train the classifier.
bbc.fit(X_train, y_train)
pred_y = bbc.predict(X_test)
mostrar_resultados(y_test, pred_y)

Tampoco está mal. Vemos siempre mejora con respecto al modelo inicial con un recall de 0.88 para los casos de fraude.

Resultados de las Estrategias

Veamos en una tabla, ordenada de mejor a peor los resultados obtenidos.

Vemos que en nuestro caso las estrategias de Penalización y Subsampling nos dan el mejor resultado, cada una con un recall de 0.93.

Pero quedémonos con esto: Con cualquiera de las técnicas que aplicamos MEJORAMOS el modelo inicial de Regresión logística, que lograba un 0.66 de recall para la clase de Fraude. Y no olvidemos que hay un tremendo desbalance de clases en el dataset!

IMPORTANTE: esto no quiere decir que siempre hay que aplicar Penalización ó NearMiss Subsampling!, dependerá del caso, del desbalanceo y del modelo (en este caso usamos regresión logística, pero podría ser otro!).

Conclusiones

Es muy frecuente encontrarnos con datasets con clases desbalanceadas, de hecho… lo más raro sería encontrar datasets bien equilibrados.

A lo largo de estos 2 años de vida del blog la pregunta más frecuente que he recibido creo que a sido “¿cómo hago cuando tengo pocas muestras de una clase?”. Mi primera respuesta y la de sentido común es “Sal a la calle y consigue más muestras!” pero la realidad es que no siempre es posible conseguir más datos de las clases minoritarias (como por ejemplo en Casos de Salud).

En el artículo de hoy vimos diversas estrategias a seguir para combatir esta problemática: eliminar muestras del set mayoritario, crear muestras sintéticas con algún criterio, ensamble y penalización.

Además revisamos la Matriz de Confusión y comprendimos que las métricas pueden ser engañosas… si miramos a nuestros aciertos únicamente, puede que pensemos que tenemos un buen clasificador, cuando realmente está fallando.

Súmate a los suscriptores del Blog

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

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

Recursos

Artículos en Inglés

The post Clasificación con datos desbalanceados first appeared on Aprende Machine Learning.

]]>
https://aprendemachinelearning.com/clasificacion-con-datos-desbalanceados/feed/ 20 6881
Ejemplo Web Scraping en Python: IBEX35® la Bolsa de Madrid https://aprendemachinelearning.com/ejemplo-web-scraping-python-ibex35-bolsa-valores/ https://aprendemachinelearning.com/ejemplo-web-scraping-python-ibex35-bolsa-valores/#comments Tue, 29 Jan 2019 10:05:00 +0000 https://www.aprendemachinelearning.com/?p=6421 En este artículo aprenderemos a utilizar la librería BeatifulSoap de Python para obtener contenidos de páginas webs de manera automática. En internet encontramos de todo: artículos, noticias, estadísticas e información útil (¿e inútil?), pero ¿cómo la extraemos? No siempre se encuentra en forma de descarga...

The post Ejemplo Web Scraping en Python: IBEX35® la Bolsa de Madrid first appeared on Aprende Machine Learning.

]]>
En este artículo aprenderemos a utilizar la librería BeatifulSoap de Python para obtener contenidos de páginas webs de manera automática.

En internet encontramos de todo: artículos, noticias, estadísticas e información útil (¿e inútil?), pero ¿cómo la extraemos? No siempre se encuentra en forma de descarga ó puede haber información repartida en multiples dominios, ó puede que necesitemos información histórica, de webs que cambian con el tiempo.

Para poder generar nuestros propios archivos con los datos que nos interesan y de manera automática es que utilizaremos la técnica de WebScraping.

Contenidos:

  • Requerimientos para WebScraping
  • Lo básico de HTML y CSS que debes saber
  • Inspeccionar manualmente una página web
  • Al código! Obtener el valor actual del IBEX35® de la Bolsa de Madrid
  • Exportar a archivo csv (y poder abrir en Excel)
  • Otros casos frecuentes de “rascar la web”

Puedes ver y descargar el código python completo de este artículo desde GitHub haciendo click aquí

Requerimientos

Para poder usar esta técnica hay diversas librerías, pero utilizaremos una muy popular llamada Beautiful Soap. Como siempre, te recomiendo tener instalado el ambiente de desarrollo con Anaconda (se explica cómo instalar en este artículo) que ya trae incluida la librería. Si no, lo puedes instalar a mano, desde línea de comandos con

pip install BeautifulSoup4
pip install requests

Si bien utilizaremos una Jupyter Notebook para el código Python 3, podríamos ejecutar un archivo de texto plano “.py” desde nuestra Terminal.

Conocimientos básicos de HTML y CSS

Daré por sentados conocimientos de html y css. ¿Por qué? Las páginas webs están hechas con HTML y deberemos indicarle a nuestro “bot-spider” de qué etiquetas ó campos, deseamos extraer el contenido.

Repaso -MUY- mínimo de HTML es:


Titulo de pagina


    

Soy un parrafo

Soy un texto en un DIV
soy una celda dentro de una tabla

Aqui Vemos las etiquetas básicas de HTML, es decir las de inicio y cierre y dentro de body el contenido de la página. Como ejemplo vemos un párrafo “p”, un “div” y una tabla.

¿Y porqué CSS? en realidad no necesitamos estrictamente saber CSS, pero sí sus selectores, puesto que nos pueden ser de mucha ayuda. Lo básico para comprender selectores, usando este bloque de ejemplo es:


    /head>
    
    
Bienvenido a mi web

Para poder seleccionar el texto “Bienvenido a mi web“, tenemos diversas formas:

  • la más directa será si la etiqueta tiene un atributo id que es único en el ejemplo “123”
  • Podríamos buscar los nodos de tipo div, pero podría haber muchos y deberemos filtrarlos.
  • Podemos filtrar un div con el atributo name = “bloque_bienvenida”.
  • Podemos buscar por clase CSS, en el ejemplo “verde”.
  • Muchas veces se combinan selectores, por ejemplo: dentro de la clase “contenedor”, la clase “verde”. O decir: “traer un div con la clase verde”

La librería de Beautiful Soap nos permite buscar dentro de los nodos del árbol de la página web, también conocido como DOM. Al final del artículo veremos como obtener el texto “Bienvenido a mi web” con diversos selectores (y en la Jupyter Notebook de Github).

Inspección Manual de la web

Esta es la web de la bolsa de Madrid, en donde nos interesa obtener el Indice del IBEX35®

Para el ejemplo inspeccionaremos la web de la Bolsa de Madrid. ¿Qué es eso de inspeccionar? Bueno, los navegadores web “modernos” (Safari, Firefox, Chrome) cuentan con una opción que nos permite ver el código html completo de la página que estamos viendo.

Además existe una opción de “inspección del código” que nos permite ver el HTML, Javascript, CSS y la web al mismo tiempo. Con ello buscaremos la manera de extraer el texto que nos interesa, si buscamos por id, por algún atributo, clase ó nodos.

Por lo general podemos inspeccionar haciendo click con el botón derecho del mouse sobre el área que nos interesa. Veamos cómo hacerlo con un gif animado 🙂

Al hacer clic derecho, aparece la opción de Inspeccionar Elemento. Así podemos ver las entrañas de la web en la que estamos navegando y pensar la mejor opción para extraer contenidos.

En nuestro caso nos interesa obtener el valor de la fila con nombre IBEX35® y el valor de la columna “último”.

Código Python – Arranquemos!

Veamos en código cómo haremos para acceder a esa porción de texto.

Primero importamos las librerías Python que utilizaremos:

import requests
from bs4 import BeautifulSoup
import csv
from datetime import datetime

Indicamos la ruta de la web que deseamos acceder:

# indicar la ruta
url_page = 'http://www.bolsamadrid.es/esp/aspx/Indices/Resumen.aspx'

Y ahora haremos el request a esa ruta y procesaremos el HTML mediante un objeto de tipo BeautifulSoap:

# tarda 480 milisegundos
page = requests.get(url_page).text 
soup = BeautifulSoup(page, "lxml")

Bien, ahora toca pensar la estrategia para acceder al valor. En nuestro caso nos interesa primero acceder a la tabla, y de allí a sus celdas. Por suerte la tabla tiene un id único!

# Obtenemos la tabla por un ID específico
tabla = soup.find('table', attrs={'id': 'ctl00_Contenido_tblÍndices'})
tabla
Aqui vemos el id de la tabla marcado en amarillo.
En rojo, se muestra la tercera celda de la primer fila a la que queremos acceder.

Bien, ahora dentro de la tabla y siendo que en este caso no tenemos un acceso directo a las celdas por ids únicos ni por clases, sólo nos queda iterar… Entonces, accederemos a la primer fila y obtendremos de las celdas el nombre del índice y su valor:

NOTA: realmente es la segunda fila, pues hay un encabezado, por eso usamos el índice 1 y no el cero.

name=""
price=""
nroFila=0
for fila in tabla.find_all("tr"):
    if nroFila==1:
        nroCelda=0
        for celda in fila.find_all('td'):
            if nroCelda==0:
                name=celda.text
                print("Indice:", name)
            if nroCelda==2:
                price=celda.text
                print("Valor:", price)
            nroCelda=nroCelda+1
    nroFila=nroFila+1

Veremos cómo salida:

Indice: IBEX 35® 
Valor: 9.185,20

Ya sólo nos queda guardar los datos para usar en el futuro.

Guardar CSV y ver en Excel

Vamos a suponer que ejecutaremos este script una vez al día, entonces lo que haremos es ir escribiendo una nueva línea al final del archivo cada vez.

# Abrimos el csv con append para que pueda agregar contenidos al final del archivo
with open('bolsa_ibex35.csv', 'a') as csv_file:
    writer = csv.writer(csv_file)
    writer.writerow([name, price, datetime.now()])

Finalmente obtenemos el archivo llamado “bolsa_ibex35.csv” listo para ser usado en nuestro proyecto 🙂

Podemos abrir el archivo csv en Excel, LibreOffice, SpreadSheets ó como archivo de texto plano.

Otros ejemplos útiles de Webscaping:

Veamos otros ejemplos de uso de BeatifulSoap para extraer contenidos con python.

Usemos el bloque de ejemplo que usé antes e intentemos extraer el texto “Bienvenido a mi web” de diversas maneras:

#Obtener por ID:
elTexto = soup.find('div', attrs={'id': '123'}).getText()
print(elTexto)
#Obtener por Clase CSS:
elTexto = soup.find('div', attrs={'class': 'verde'}).getText()
print(elTexto)
#Obtener dentro de otra etiqueta anidado:
elTexto = next(soup.div.children).getText() #con next obtiene primer "hijo"
print(elTexto)

Obtener los enlaces de una página web

Otro caso práctico que nos suele ocurrir es querer colectar los enlaces de una página web. Para ello, obtenemos las etiquetas “A” e iteramos obteniendo el atributo HREF que es donde se encuentran las “nuevas rutas”, con posibilidad de hacer un nuevo request a cada una y extraer sus contenidos.

url_page = 'https://www.lifeder.com/cientificos-famosos/'
page = requests.get(url_page).text 
soup = BeautifulSoup(page, "lxml")
contenido = soup.find('div', attrs={'class': 'td-post-content'})
items = contenido.find_all('a')
for item in items:
    print(item['href'])

En el archivo Jupyter Notebook de mi cuenta de Github se ven estos ejemplos (y alguno más).

Conclusiones, repaso y código

Ahora sabemos cómo afrontar el proceso de obtener información de cualquier página web. Resumiendo el procedimiento básico que seguimos es:

  1. Cargar la página en el navegador
  2. Inspeccionar e investigar el HTML
  3. En Python: importar las librerías
  4. Obtener la página, parsear el contenido con BeautifulSoap
  5. Obtner el “trozo” de contenido que buscamos
    • Mediante ID
    • Mediante Etiqueta
    • Mediante Clases CSS
    • Otros Atributos
  6. Guardamos los datos en csv

Repasando, cuando ya tenemos el contenido en un objeto “soap”, solemos utilizar los métodos find() ó para múltiples etiquetas el find_all().

Si combinamos un script para webscraping, como en el ejemplo para capturar valores de la bolsa con el cron del sistema (ó con algún tipo de “repetidor de tareas del sistema”) que nos permita ejecutar nuestro código cada “x” tiempo, podremos generar un valioso archivo de información muy a medida de lo que necesitamos.

Otro ejemplo “clásico” es el de la obtención automática de los resultados de partidos de fútbol y en el código de este ejemplo en Github, encontrarás cómo hacerlo.

Obtener el Jupyter Notebook con código Python con este y más ejemplos de WebScaping

Si bien este artículo no es estrictamente sobre Machine Learning, me pareció bueno comentarlo pues he utilizado técnicas de Webscraping en ejercicios anteriores (como en el de Procesamiento del Lenguaje Natural) pasando por alto la explicación de esta porción del código. Además es un recurso utilizado frecuentemente para trabajar y para hacer pequeños experimentos con datos.

Ya estás listo para la Fase EDA!!!

Suscripción al Blog

Recibe los próximos artículos sobre Redes Neuronales y Deep Learning, Herramientas para Big Data y Data Science y ejercicios en código Python cada 15 días en tu bandeja de entrada.

The post Ejemplo Web Scraping en Python: IBEX35® la Bolsa de Madrid first appeared on Aprende Machine Learning.

]]>
https://aprendemachinelearning.com/ejemplo-web-scraping-python-ibex35-bolsa-valores/feed/ 13 6421
NLP: Analizamos los cuentos de Hernan Casciari https://aprendemachinelearning.com/ejercicio-nlp-cuentos-de-hernan-casciari-python-espanol/ https://aprendemachinelearning.com/ejercicio-nlp-cuentos-de-hernan-casciari-python-espanol/#comments Mon, 14 Jan 2019 09:00:00 +0000 https://www.aprendemachinelearning.com/?p=6360 Ejercicio Python de Procesamiento del Lenguaje Natural ( ó “¿Qué tiene Casciari en la cabeza?” ) Luego de haber escrito sobre la teoría de iniciación al NLP en el artículo anterior llega la hora de hacer algunos ejercicios prácticos en código Python para adentrarnos en...

The post NLP: Analizamos los cuentos de Hernan Casciari first appeared on Aprende Machine Learning.

]]>
Ejercicio Python de Procesamiento del Lenguaje Natural

( ó “¿Qué tiene Casciari en la cabeza?” )

Ejercicio Procesamiento del Lenguaje Natural

Luego de haber escrito sobre la teoría de iniciación al NLP en el artículo anterior llega la hora de hacer algunos ejercicios prácticos en código Python para adentrarnos en este mundo.

Como la idea es hacer Aprendizaje Automático en Español, se me ocurrió buscar textos en castellano y recordé a Hernan Casciari que tiene los cuentos de su blog disponibles online y me pareció un buen desafío.

Para quien no conozca a Hernan Casciari, es un escritor genial, hace cuentos muy entretenidos, de humor (y drama) muy reales, relacionados con su vida, infancia, relaciones familiares con toques de ficción. Vivió en España durante más de una década y tuvo allí a su primera hija. En 2005 fue premiado como “El mejor blog del mundo” por Deutsche Welle de Alemania. En 2008 Antonio Gasalla tomó su obra “Más respeto que soy tu madre” y la llevó al teatro con muchísimo éxito. Escribió columnas para importantes periódicos de España y Argentina hasta que fundó su propia editorial Orsai en 2010 donde no depende de terceros para comercializar ni distribuir sus productos y siempre ofrece versione en pdf (gratuitos). Tiene 7 libros publicados, apariciones en radio (Vorterix y Perros de la Calle) y hasta llevó sus historias a una genial puesta en escena llamada “Obra en Construcción” que giró por muchas provincias de la Argentina, España y Uruguay.

Línea del Tiempo, vida blogger de Hernan Casciari

Agenda del Día: “NLP tradicional”

Lo cierto es que utilizaremos la librería python NLTK para NLP y haremos uso de varias funciones y análisis tradicionales, me refiero a que sin meternos – aún- en Deep Learning (eso lo dejaremos para otro futuro artículo).

  1. Obtener los Datos (los cuentos)
  2. Exploración Inicial
  3. Limpieza de datos
  4. Análisis Exploratorio
  5. Análisis de Sentimiento
  6. Modelado de Tópicos

Vamos al código!

1 – Obtener los Cuentos

Para obtener los textos, haremos webscraping (LEER ARTíCULO) en el blog de Hernan Casciari, recorreremos los cuentos que afortunadamente están clasificados en directorios por año, del 2004 al 2005 y guardaremos todos los posts de cada año en un archivo txt.

ATENCIóN: Este código puede tardar MUCHOS minutos en descargar todos los textos, pues para ser amables con el servidor, haremos un sleep(0.75) entre cada request (y son 386 cuentos).

# Web scraping, pickle imports
import requests
from bs4 import BeautifulSoup
import pickle
from time import sleep

# Web Scrapes transcript data from blog
def url_to_transcript(url):
    '''Obtener los enlaces del blog de Hernan Casciari.'''
    page = requests.get(url).text
    soup = BeautifulSoup(page, "lxml")
    print('URL',url)
    enlaces = []
    for title in soup.find_all(class_="entry-title"):
        for a in title.find_all('a', href=True):
            print("Found link:", a['href'])
            enlaces.append(a['href'])
    sleep(0.75) #damos tiempo para que no nos penalice un firewall
    return enlaces

base = 'https://editorialorsai.com/category/epocas/'
urls = []
anios = ['2004','2005','2006','2007','2008','2009','2010','2011','2012','2013','2014','2015']
for anio in anios:
    urls.append(base + anio + "/")
print(urls)

# Recorrer las URLs y obtener los enlaces
enlaces = [url_to_transcript(u) for u in urls]
print(enlaces)

def url_get_text(url):
    '''Obtener los textos de los cuentos de Hernan Casciari.'''
    print('URL',url)
    text=""
    try:
        page = requests.get(url).text
        soup = BeautifulSoup(page, "lxml")
        text = [p.text for p in soup.find(class_="entry-content").find_all('p')]
    except Exception:
        print('ERROR, puede que un firewall nos bloquea.')
        return ''
    sleep(0.75) #damos tiempo para que no nos penalice un firewall
    return text

# Recorrer las URLs y obtener los textos
MAX_POR_ANIO = 50 # para no saturar el server
textos=[]
for i in range(len(anios)):
    arts = enlaces[i]
    arts = arts[0:MAX_POR_ANIO]
    textos.append([url_get_text(u) for u in arts])
print(len(textos))

## Creamos un directorio y nombramos los archivos por año
!mkdir blog

for i, c in enumerate(anios):
    with open("blog/" + c + ".txt", "wb") as file:
        cad=""
        for texto in textos[i]:
            for texto0 in texto:
                cad=cad + texto0
        pickle.dump(cad, file)

Al finalizar obtendremos una carpeta llamada blog con 12 archivos: 2004.txt a 2015.txt.

Recuerda que puedes descargar todos los archivos, jupyter Notebook y código Python desde mi cuenta de Github

2 – Exploración Inicial / Cargamos los Datos

Cargaremos los archivos txt que creamos en el paso anterior y lo pasaremos a una estructura en un dataframe de Pandas para seguir usando en el próximo paso.

data = {}
for i, c in enumerate(anios):
    with open("blog/" + c + ".txt", "rb") as file:
        data[c] = pickle.load(file)
# Revisamos que se haya guardado bien
print(data.keys())
# Veamos algun trozo de texto
print(data['2008'][1000:1222])

# Combine it!
data_combined = {key: [value] for (key, value) in data.items()}

# We can either keep it in dictionary format or put it into a pandas dataframe
import pandas as pd
pd.set_option('max_colwidth',150)

data_df = pd.DataFrame.from_dict(data_combined).transpose()
data_df.columns = ['transcript']
data_df = data_df.sort_index()
data_df
Tabla en Pandas con los textos

3 – Limpieza de Datos

Ahora aplicaremos algunos de los filtros de limpieza que se suelen usar para poder tratar el texto:

  • Pasar texto a minúsculas
  • Quitar signos de puntuación (interrogación, etc.)
  • Quitar espacios extra, cambio de carro, tabulaciones
# Apply a first round of text cleaning techniques
import re
import string

def clean_text_round1(text):
    '''Make text lowercase, remove text in square brackets, remove punctuation and remove words containing numbers.'''
    text = text.lower()
    text = re.sub('\[.*?¿\]\%', ' ', text)
    text = re.sub('[%s]' % re.escape(string.punctuation), ' ', text)
    text = re.sub('\w*\d\w*', '', text)
    return text

round1 = lambda x: clean_text_round1(x)

data_clean = pd.DataFrame(data_df.transcript.apply(round1))

# Apply a second round of cleaning
def clean_text_round2(text):
    '''Get rid of some additional punctuation and non-sensical text that was missed the first time around.'''
    text = re.sub('[‘’“”…«»]', '', text)
    text = re.sub('\n', ' ', text)
    return text

round2 = lambda x: clean_text_round2(x)

data_clean = pd.DataFrame(data_clean.transcript.apply(round2))
data_clean

# Let's pickle it for later use
data_df.to_pickle("corpus.pkl")

Y creamos nuestro “Bag of Words”

A partir del dataset que limpiamos, creamos y contamos las palabras:
(el archivo spanish.txt lo incluye NLTK ó si no lo tienes, copia de mi Github en el mismo directorio en donde tienes el código)

# We are going to create a document-term matrix using CountVectorizer, and exclude common Spanish stop words
from sklearn.feature_extraction.text import CountVectorizer

with open('spanish.txt') as f:
    lines = f.read().splitlines()

cv = CountVectorizer(stop_words=lines)
data_cv = cv.fit_transform(data_clean.transcript)
data_dtm = pd.DataFrame(data_cv.toarray(), columns=cv.get_feature_names())
data_dtm.index = data_clean.index

data_dtm.to_pickle("dtm.pkl")
# Let's also pickle the cleaned data (before we put it in document-term matrix format) and the CountVectorizer object
data_clean.to_pickle('data_clean.pkl')
pickle.dump(cv, open("cv.pkl", "wb"))

data_dtm
Tabla con word tokens

4 – Análisis Exploratorio

Ahora que tenemos nuestro dataset, investigaremos un poco

data = pd.read_pickle('dtm.pkl')
data = data.transpose()
data.head()
Palabras con su frecuencia de uso

4.1 – Palabras más usadas por año

veamos las palabras más usadas cada año:

top_dict = {}
for c in data.columns:
    top = data[c].sort_values(ascending=False).head(30)
    top_dict[c]= list(zip(top.index, top.values))
print(top_dict)
# Print the top 15 words by year
for anio, top_words in top_dict.items():
    print(anio)
    print(', '.join([word for word, count in top_words[0:14]]))
--- 2004 si, alex, vez, lucas, cada, dos, ahora, ser, después, casa, años, siempre, nadie, ver 
--- 2005 si, dos, vez, años, siempre, ser, vida, tiempo, hace, ahora, entonces, mundo, después, dice
--- 2006 si, años, dos, vez, siempre, hace, mundo, ser, ahora, entonces, cada, mismo, vida, casa
--- 2007 si, siempre, dos, entonces, vez, años, nunca, ahora, sólo, después, mundo, ser, casa, mujer
--- 2008 dos, si, años, casa, vez, ahora, después, siempre, entonces, hace, ser, tarde, tiempo, mismo
--- 2009 años, si, ahora, casa, vez, después, andrés, ser, dos, vida, hace, mundo, entonces, tres
--- 2010 revista, chiri, si, años, orsai, cada, hacer, dos, ahora, ser, hace, vez, casa, lectores
--- 2011 orsai, revista, número, lectores, dos, si, vez, chiri, años, ahora, hace, cada, siempre, revistas
--- 2012 orsai, dos, cada, si, revista, vez, dijo, chiri, ahora, después, tiempo, mismo, hace, argentina
--- 2013 si, dos, años, cada, dijo, papelitos, ve, después, ahora, vez, nunca, tres, tarde, día
--- 2014 si, vez, dos, años, después, tres, cada, siempre, casa, ser, lucas, mismo, alex, nunca
--- 2015 si, años, casa, hija, dos, entonces, ahora, nunca, después, siempre, vez, dijo, vida, ser

4.2 Agregamos Stop Words

Vemos en el listado que hay palabras muy usadas pero que realmente no tienen un significado útil para el análisis. Entonces haremos lo siguiente: uniremos las 12 listas de más palabras en un nuevo ranking y de esas, tomaremos las “más usadas” para ser agregar en nuestro listado de Stop Words.

from collections import Counter

# Let's first pull out the top 30 words for each anio
words = []
for anio in data.columns:
    top = [word for (word, count) in top_dict[anio]]
    for t in top:
        words.append(t)
print(Counter(words).most_common())
add_stop_words = [word for word, count in Counter(words).most_common() if count > 6]
add_stop_words
['si',
'vez',
'cada',
'dos',
'ahora',
'después',
'años',
'hace',
'casa',
'nunca',
'siempre',
'mundo',
'día',
'mismo',
'hacer',
'tiempo',
'ser',
'vida',
'chiri',
'dijo',
'entonces',
'tres',
'noche']

4.3 Actualizamos nuestra Bag of Words

Ahora quitaremos las Stop words de nuestro dataset. Usaremos el listado de spanish.txt, el que generamos recién y uno adicional que hice yo a partir de los resultados obtenidos (ojo… esto les puede parecer arbitrario y en parte lo es!)

from sklearn.feature_extraction import text 
from sklearn.feature_extraction.text import CountVectorizer

# Read in cleaned data
data_clean = pd.read_pickle('data_clean.pkl')

# Add new stop words
with open('spanish.txt') as f:
    stop_words = f.read().splitlines()
for pal in add_stop_words:
    stop_words.append(pal)
more_stop_words=['alex','lucas','andrés','mirta','tres','primer','primera','dos','uno','veces', 'así', 'luego', 'quizá','cosa','cosas','tan','asi','andres','todas','sólo','jesús','pablo','pepe']
for pal in more_stop_words:
    stop_words.append(pal)

# Recreate document-term matrix
cv = CountVectorizer(stop_words=stop_words)
data_cv = cv.fit_transform(data_clean.transcript)
data_stop = pd.DataFrame(data_cv.toarray(), columns=cv.get_feature_names())
data_stop.index = data_clean.index

# Pickle it for later use
import pickle
pickle.dump(cv, open("cv_stop.pkl", "wb"))
data_stop.to_pickle("dtm_stop.pkl")

4.4 Nube de Palabras

Haremos una primer aproximación a “qué tenía Hernan Casciari en su cabeza” entre 2004 y 2015 en sus cuentos usando un modo de visualización llamado WordCloud. Esto puede requerir que debas instalar la librería Wordcloud con Pip ó si tienes instalado Anaconda, desde la interface ó por terminal con conda install -c conda-forge wordcloud

from wordcloud import WordCloud

wc = WordCloud(stopwords=stop_words, background_color="white", colormap="Dark2",
               max_font_size=150, random_state=42)

import matplotlib.pyplot as plt
plt.rcParams['figure.figsize'] = [16,12]

# Create subplots for each anio
for index, anio in enumerate(data.columns):
    wc.generate(data_clean.transcript[anio])
    plt.subplot(4, 3, index+1)
    plt.imshow(wc, interpolation="bilinear")
    plt.axis("off")
    plt.title(anios[index])
plt.show()
Nube de Palabras de 12 años de Hernán Casciari como Blogger. Sus palabras más importantes.

4.5 Estadísticas de Palabras por año

Ahora sacaremos algunas estadísticas de palabras únicas por año (el tamaño del vocabulario empleado) y el promedio de palabras por artículo

# Find the number of unique words per Year
# Identify the non-zero items in the document-term matrix, meaning that the word occurs at least once
unique_list = []
for anio in data.columns:
    uniques = data[anio].nonzero()[0].size
    unique_list.append(uniques)

# Create a new dataframe that contains this unique word count
data_words = pd.DataFrame(list(zip(anios, unique_list)), columns=['Anio', 'unique_words'])
#data_unique_sort = data_words.sort_values(by='unique_words')
data_unique_sort = data_words # sin ordenar
data_unique_sort
# ejecuta este si hicimos el webscrapping, o no tenemos los valores en la variable
posts_per_year=[]
try:
  enlaces
except NameError:
  # Si no hice, los tengo hardcodeados:
    posts_per_year = [50, 27, 18, 50, 42, 22, 50, 33, 31, 17, 33, 13]
else:
    for i in range(len(anios)):
        arts = enlaces[i]
        #arts = arts[0:10] #limito a maximo 10 por año
        print(anios[i],len(arts))
        posts_per_year.append(min(len(arts),MAX_POR_ANIO))

# Find the total number of words per Year
total_list = []
for anio in data.columns:
    totals = sum(data[anio])
    total_list.append(totals)
    
# Let's add some columns to our dataframe
data_words['total_words'] = total_list
data_words['posts_per_year'] = posts_per_year
data_words['words_per_posts'] = data_words['total_words'] / data_words['posts_per_year']

# Sort the dataframe by words per minute to see who talks the slowest and fastest
#data_wpm_sort = data_words.sort_values(by='words_per_posts')
data_wpm_sort = data_words #sin ordenar
data_wpm_sort
Tabla resumen con estadísticas de usos y frecuencias de palabras en los cuentos de Casciari

4.5.1 Visualización de la tabla

Veamos los datos en gráfico de barras horizontales:

import numpy as np
plt.rcParams['figure.figsize'] = [16, 6]

y_pos = np.arange(len(data_words))

plt.subplot(1, 3, 1)
plt.barh(y_pos,posts_per_year, align='center')
plt.yticks(y_pos, anios)
plt.title('Number of Posts', fontsize=20)


plt.subplot(1, 3, 2)
plt.barh(y_pos, data_unique_sort.unique_words, align='center')
plt.yticks(y_pos, data_unique_sort.Anio)
plt.title('Number of Unique Words', fontsize=20)

plt.subplot(1, 3, 3)
plt.barh(y_pos, data_wpm_sort.words_per_posts, align='center')
plt.yticks(y_pos, data_wpm_sort.Anio)
plt.title('Number of Words Per Posts', fontsize=20)

plt.tight_layout()
plt.show()
Gráficas con cantidad de Post al Año, palabras únicas y palabras x artículos en los cuentos de Hernan Casciari.

Y hagamos una comparativa de frecuencia de uso de algunas palabras (aquí tu podrías escoger otras) En mi caso seleccioné casa, mundo,tiempo y vida

import nltk
from nltk.corpus import PlaintextCorpusReader
corpus_root = './python_projects/blog' 
wordlists = PlaintextCorpusReader(corpus_root, '.*', encoding='latin-1')
#wordlists.fileids() # con esto listamos los archivos del directorio

cfd = nltk.ConditionalFreqDist(
        (word,genre)
        for genre in anios
        for w in wordlists.words(genre + '.txt')
        for word in ['casa','mundo','tiempo','vida']
        if w.lower().startswith(word) )
cfd.plot()
Comparativa entre 4 palabras usadas a lo largo de los años por Hernán Casciari

5 – Análisis de Sentimiento

Ahora probaremos analizando los sentimientos en cuanto a “positivos y negativos” encontrados en el texto y sus cambios de polaridad. Para simplificar usaremos una librería llamada TextBlob que ya tiene esta funcionalidad hecha, aunque NO LO recomiendo para uso en producción. Por desgracia sólo funciona con textos en inglés, por lo que además nos obliga a traducir el texto con lo que eso conlleva… Pero para fines educativos -cómo los de este blog- es un buen ejemplo para ver el análisis de sentimiento.

data = pd.read_pickle('corpus.pkl')
from textblob import TextBlob
    
pol = lambda x: TextBlob(x).sentiment.polarity
pol2 = lambda x: x.sentiment.polarity
sub = lambda x: TextBlob(x).sentiment.subjectivity
sub2 = lambda x: x.sentiment.subjectivity

traducir = lambda x: TextBlob(x).translate(to="en")

data['blob_en'] = data['transcript'].apply(traducir)
data['polarity'] = data['blob_en'].apply(pol2)
data['subjectivity'] = data['blob_en'].apply(sub2)
data

5.1 Visualización global

Veamos globalmente tomando en cuenta la polaridad y la subjetividad detectadas por la librería:

plt.rcParams['figure.figsize'] = [10, 8]

for index, anio in enumerate(data.index):
    x = data.polarity.loc[anio]
    y = data.subjectivity.loc[anio]
    plt.scatter(x, y, color='blue')
    plt.text(x+.001, y+.001, data['full_name'][index], fontsize=10)
    plt.xlim(-0.051, 0.152) 
    
plt.title('Sentiment Analysis', fontsize=20)
plt.xlabel('<-- Negative -------- Positive -->', fontsize=15)
plt.ylabel('<-- Facts -------- Opinions -->', fontsize=15)

plt.show()
Visualización de polarización de sentimientos entre 2004 - 2012 del autor Hernan Casciari

5.2 Sentimiento año por año

Ahora intentaremos analizar el comportamiento del sentimiento a medida que el autor escribía cuentos a lo largo de los años. Para ello, tomaremos de a 12 “trozos” de texto de cada año y los analizaremos. (NOTA: Esto no es preciso realmente, pues no coincide temporalmente con 12 meses, es para dar una idea al lector de las diversas técnicas que podemos aplicar).

import math

def split_text(text, n=12):
    '''Takes in a string of text and splits into n equal parts, with a default of 12 equal parts.'''

    # Calculate length of text, the size of each chunk of text and the starting points of each chunk of text
    length = len(text)
    size = math.floor(length / n)
    start = np.arange(0, length, size)
    
    # Pull out equally sized pieces of text and put it into a list
    split_list = []
    for piece in range(n):
        split_list.append(text[start[piece]:start[piece]+size])
    return split_list

list_pieces = []
for t in data.blob_en:#transcript:
    split = split_text(t,12)
    list_pieces.append(split)

polarity_transcript = []
for lp in list_pieces:
    polarity_piece = []
    for p in lp:
        #polarity_piece.append(TextBlob(p).translate(to="en").sentiment.polarity)
        polarity_piece.append(p.sentiment.polarity)
    polarity_transcript.append(polarity_piece)
    
plt.rcParams['figure.figsize'] = [16, 12]

for index, anio in enumerate(data.index):    
    plt.subplot(3, 4, index+1)
    plt.plot(polarity_transcript[index])
    plt.plot(np.arange(0,12), np.zeros(12))
    plt.title(data['full_name'][index])
    plt.ylim(ymin=-.1, ymax=.2)
    
plt.show()
Gráfica desglose anual de la tendencia de polaridad de sentimientos positivo-negativos de hernan casciari a lo largo de los años

6. Modelado de Tópicos (ó temas)

Ahora intentaremos obtener “automáticamente” algunos de los temas sobre los que escribe el autor. A decir verdad para que funcione deberíamos aplicar Lemmatization y limpiar mejor nuestro dataset. Para poder mostrar esta técnica nos vale, aunque no obtendremos resultados realmente buenos.

Utilizaremos la conocida librería Gensim y utilizaremos el algoritmo Latent Dirichlet Allocation (LDA)

data = pd.read_pickle('dtm_stop.pkl')
tdm = data.transpose()
sparse_counts = scipy.sparse.csr_matrix(tdm)
corpus = matutils.Sparse2Corpus(sparse_counts)
cv = pickle.load(open("cv_stop.pkl", "rb"))
id2word = dict((v, k) for k, v in cv.vocabulary_.items())

from nltk import word_tokenize, pos_tag

def nouns_adj(text):
    '''Given a string of text, tokenize the text and pull out only the nouns and adjectives.'''
    is_noun_adj = lambda pos: pos[:2] == 'NN' or pos[:2] == 'JJ'
    tokenized = word_tokenize(text,language='spanish')
    nouns_adj = [word for (word, pos) in pos_tag(tokenized) if is_noun_adj(pos)] 
    return ' '.join(nouns_adj)

data_clean = pd.read_pickle('data_clean.pkl')
data_clean

from sklearn.feature_extraction import text
from sklearn.feature_extraction.text import CountVectorizer

# Re-add the additional stop words since we are recreating the document-term matrix
#add_stop_words = ['di', 'la', 'know', 'just', 'dont', 'thats', 'right', 'people',
#                  'youre', 'got', 'gonna', 'time', 'think', 'yeah', 'said']
#stop_words = text.ENGLISH_STOP_WORDS.union(add_stop_words)
# Add new stop words
#stop_words = text.ENGLISH_STOP_WORDS.union(add_stop_words)
with open('spanish.txt') as f:
    stop_words = f.read().splitlines()#
for pal in add_stop_words:
    stop_words.append(pal)
for pal in more_stop_words:
    stop_words.append(pal)

# Create a new document-term matrix using only nouns and adjectives, also remove common words with max_df
cvna = CountVectorizer(stop_words=stop_words, max_df=.8)
data_cvna = cvna.fit_transform(data_nouns_adj.transcript)
data_dtmna = pd.DataFrame(data_cvna.toarray(), columns=cvna.get_feature_names())
data_dtmna.index = data_nouns_adj.index
data_dtmna

data_nouns_adj = pd.DataFrame(data_clean.transcript.apply(nouns_adj))
data_nouns_adj

# Create the gensim corpus
corpusna = matutils.Sparse2Corpus(scipy.sparse.csr_matrix(data_dtmna.transpose()))

# Create the vocabulary dictionary
id2wordna = dict((v, k) for k, v in cvna.vocabulary_.items())

# Probamos a modelar con 3 tópicos
ldana = models.LdaModel(corpus=corpusna, num_topics=3, id2word=id2wordna, passes=10)
ldana.print_topics()

6.1 Identificar los temas

Ahora haremos una “pasada” más profunda para ver si obtenemos 3 temáticas diferenciadas:

QTY_TOPICS=3
ldana = models.LdaModel(corpus=corpusna, num_topics=QTY_TOPICS, id2word=id2wordna, passes=80)
ldana.print_topics()
[(0,
'0.001"jugador" + 0.001"papelitos" + 0.001"niño" + 0.001"casciari" + 0.001"luis" + 0.001"charla" + 0.001"luna" + 0.001"monedas" + 0.001"quizás" + 0.001"blogs"'),
(1,
'0.002"casciari" + 0.001"cuaderno" + 0.001"jorge" + 0.001"colo" + 0.001"cuadernos" + 0.001"waiser" + 0.001"coche" + 0.001"mundiales" + 0.001"goles" + 0.001"messi"'),
(2,
'0.002"comequechu" + 0.002"proyecto" + 0.001"textos" + 0.001"próximo" + 0.001"páginas" + 0.001"corbata" + 0.001"librero" + 0.001"libreros" + 0.001"sant" + 0.001"celoni"')]
corpus_transformed = ldana[corpusna]
list(zip([a for [(a,b)] in corpus_transformed], data_dtmna.index))
[(1, '2004'),
(1, '2005'),
(2, '2006'),
(3, '2007'),
(2, '2008'),
(1, '2009'),
(3, '2010'),
(2, '2011'),
(1, '2012'),
(1, '2013'),
(1, '2014'),
(3, '2015')]

podemos intuir (¿forzosamente?) que lo que detectó el algoritmo se refiere a estos 3 temas:

  • Jugar / Niñez
  • Fútbol
  • Futuro

Conclusiones finales

Repasemos lo que hicimos y que resultados sacamos:

  • Extracción de 386 textos -> conseguimos los cuentos de 2004 al 2015
  • Limpiamos los textos, quitamos caracteres que no utilizamos y creamos un listado de stop_words (palabras para omitir)
  • Exploración de datos:
    • Realizamos estadísticas básicas, como el vocabulario usado, cantidad de palabras por año y promedio por posts.
    • Creamos Nubes de Palabras por año ya que es una manera de visualizar textos
  • Análisis de Sentimiento: visualizamos las variaciones en los textos a lo largo del tiempo y vimos leves sobresaltos, pero por lo general, una tendencia neutral.
  • Modelado de temáticas: en este punto no creo que hayamos conseguido unas categorías muy definidas. Parte del problema es que no pudimos hacer Lemmatization pues no conseguí herramientas Python en Español. Otra opción es que no hay temáticas claras.

ATENCIóN: este artículo es algo “estándar”, como para comenzar a entender el NLP aplicado y cómo -con diversas técnicas- comprender el lenguaje humano. Realmente hay muchas más aplicaciones y tareas que se pueden hacer. Debo decir que casi todo “en el mercado” está hecho para analizar textos en inglés y parte de la dificultad para desarrollar el ejercicio consistió en llevarlo al castellano. Si conoces otras buenas herramientas en español, escríbeme!

ATENCIóN (2): Podrás encontrar diferencias entre las visualizaciones en este artículo y el último Jupyter Notebook colgado en Github, esto se debe a que hubo actualizaciones en el código que no están reflejados en el artículo.

Espero en el futuro poder mostrar más utilidades del NLP y también llegar a usar NLP con algoritmos de Deep Learning (por ejemplo con redes neuronales convolucionales).

Suscripción al Blog

Recibe los nuevos artículos sobre Machine Learning, redes neuronales, NLP y código Python cada 3 semanas aprox.

Recursos – Descarga el código

Enlaces para seguir con NLP!

The post NLP: Analizamos los cuentos de Hernan Casciari first appeared on Aprende Machine Learning.

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

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

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

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

Ejercicio Propuesto: Clasificar imágenes de deportes

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

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

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

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

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

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

Requerimientos para realizar el Ejercicio

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

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

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

Al código Python sin más!

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

Esto es lo que haremos hoy:

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

Empecemos a programar!:

1- Importar librerías

Cargaremos las libs que utilizaremos para el ejercicio.

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

2-Cargar las imágenes

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

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

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

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

print("leyendo imagenes de ",imgpath)

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

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

3- Crear etiquetas y clases

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

5 – Creamos la red (Aquí la Magia)

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

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

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

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

sport_model.summary()

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

6-Entrenamos la CNN

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

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

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

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

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

7-Resultados obtenidos

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

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

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

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

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

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

Conclusiones y promesa futura!

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

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

Suscripción al Blog

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

Los recursos y… Más recursos

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

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

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

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

Otros:


El libro del Blog (en desarrollo)

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

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

]]>
https://aprendemachinelearning.com/clasificacion-de-imagenes-en-python/feed/ 92 5910
¿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