Algoritmos | Aprende Machine Learning
https://aprendemachinelearning.com
en EspañolTue, 22 Oct 2024 20:41:50 +0000en-US
hourly
1 https://wordpress.org/?v=6.6.2https://aprendemachinelearning.com/wp-content/uploads/2017/11/cropped-icon_red_neuronal-1-32x32.pngAlgoritmos | Aprende Machine Learning
https://aprendemachinelearning.com
3232134671790Seguimiento 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/#commentsFri, 08 Sep 2023 11:15:20 +0000https://www.aprendemachinelearning.com/?p=8271Realiza un proyecto python de 100 líneas para detectar y seguir personas en video. Usaremos Yolo v8 y Bytetrack.
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
¿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.
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.
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.
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…
]]>https://aprendemachinelearning.com/seguimiento-de-objetos-con-yolo-v8-y-bytetrack-object-tracking/feed/38271Crea imágenes increíbles con Inteligencia Artificial en tu ordenador
https://aprendemachinelearning.com/crea-imagenes-stable-diffusion-con-inteligencia-artificial-en-tu-ordenador/
https://aprendemachinelearning.com/crea-imagenes-stable-diffusion-con-inteligencia-artificial-en-tu-ordenador/#respondThu, 06 Oct 2022 08:00:00 +0000https://www.aprendemachinelearning.com/?p=7681El modelo de Machine Learning llamado Stable Diffusion es Open Source y permite generar cualquier imagen a partir de un texto, por más loca que sea, desde el sofá de tu casa! Estamos viviendo unos días realmente emocionantes en el campo de la inteligencia artificial, en apenas meses, hemos pasado de tener modelos enormes y […]
]]>El modelo de Machine Learning llamado Stable Diffusion es Open Source y permite generar cualquier imagen a partir de un texto, por más loca que sea, desde el sofá de tu casa!
Estamos viviendo unos días realmente emocionantes en el campo de la inteligencia artificial, en apenas meses, hemos pasado de tener modelos enormes y de pago en manos de unas pocas corporaciones a poder desplegar un modelo en tu propio ordenador y lograr los mismos -increíbles- resultados de manera gratuita. Es decir, ahora mismo, está al alcance de prácticamente cualquier persona la capacidad de utilizar esta potentísima herramienta y crear imágenes en segundos (ó minutos) y a coste cero.
En este artículo les comentaré qué es Stable Diffusion y por qué es un hito en la historia de la Inteligencia Artificial, veremos cómo funciona y tienes la oportunidad de probarlo en la nube o de instalarlo en tu propio ordenador sea Windows, Linux ó Mac, con o sin placa GPU.
Reseña de los acontecimientos
2015: Paper que propone los Diffusion Models.
2018 -2019 Text to Image Synthesis – usando GANS se generan imágenes de 64×64 pixels, utiliza muchos recursos y baja calidad de resultados.
Enero 2021: Open AI anuncia Dall-E, genera imágenes interesantes, pequeñas, baja resolución, lentas.
Febrero 2021: CLIP de Open AI (Contrastive Language-Image Pretraining), un codificador dual de lenguaje-imagen muy potente.
Julio 2021: Image Text Contrastive Learning Mejora sobre las Gans “image-text-label” space.
Marzo 2022: GLIDE: esta red es una mejora sobre Dall-E, tambien de openAI pero usando DIFFUSION model.
Abril 2022: Dall-E 2 de Open AI, un modelo muy bueno de generación de imágenes. Código cerrado, acceso por pedido y de pago.
Agosto de 2022: Lanzamiento de Stable Diffusion 1.4 de Stability AI al público. Open Source, de bajos recursos, para poder ejecutar en cualquier ordenador.
Stable Diffusion es también una gran revolución en nuestra sociedad porque trae consigo diversas polémicas; al ofrecer esta herramienta a un amplio público, permite generar imágenes de fantasía de paisajes, personas, productos… ¿cómo afecta esto a los derechos de autor? Qué pasa con las imágenes inadecuadas u ofensivas? Qué pasa con el sesgo de género? Puede suplantar a un diseñador gráfico? Hay un abanico enorme de incógnitas sobre cómo será utilizada esta herramienta y la disrupción que supone. A mí personalmente me impresiona por el progreso tecnológico, por lo potente que es, los magnificos resultados que puede alcanzar y todo lo positivo que puede acarrear.
¿Por qué tanto revuelo? ¿Es como una gran Base de datos de imágenes? – ¡No!
Es cierto que fue entrenada con más de 5 mil millones de imágenes. Entonces podemos pensar: “Si el modelo vio 100.000 imágenes de caballos, aprenderá a dibujar caballos. Si vio 100.000 imágenes de la luna, sabrá pintar la luna. Y si aprendió de miles de imágenes de astronautas, sabrá pintar astronautas“. Pero si le pedimos que pinte “un astronauta a caballo en la luna” ¿qué pasa? La respuesta es que el modelo que jamás había visto una imagen así, es capaz de generar cientos de variantes de imágenes que cumplen con lo solicitado… esto ya empieza a ser increíble. Podemos pensar: “Bueno, estará haciendo un collage, usando un caballo que ya vio, un astronauta (que ya vió) y la luna y hacer una composición“. Y no; no es eso lo que hace, ahí se vuelve interesante: el modelo de ML parte de un “lienzo en blanco” (en realidad es una imagen llena de ruido) y a partir de ellos empieza a generar la imagen, iterando y refinando su objetivo, pero trabajando a nivel de pixel (por lo cual no está haciendo copy-paste). Si creyéramos que es una gran base de datos, les aseguro que no caben las 5.500.000.000 de imágenes en 4 Gygabytes -que son los pesos del modelo de la red- pues estaría almacenando cada imagen (de 512x512px) en menos de 1 Byte, algo imposible.
¿Cómo funciona Stable Diffusion?
Veamos cómo funciona Stable Diffusion!
Stable Diffusion está basado en otro modelo llamado “Latent Diffusion” que proviene de modelos de difusión de ML que están entrenados para “eliminar el ruido de “imágenes sucias” paso a paso”. Esto quiere decir que al modelo le entrenamos con fotos donde ensuciamos ciertos pixeles, con manchas, desenfoque (blur) o distorsiones que agregamos a propósito y le pedimos como salida la imagen correcta (la imagen original sin ruido). Entonces, la red neuronal del modelo aprende a “quitar el ruido”, es decir, transformar esas manchas (ruido) en la imagen original.
Los modelos de difusión lograron resultados muy buenos como generadores de imágenes aunque su contra es que como trabajan a nivel de pixel requieren de mucha memoria RAM y toman tiempo para crear imágenes de alta definición.
La mejora introducida por los modelos “Latent Diffusion” es que el modelo es entrenado para generar “representaciones de imágenes latentes” (comprimidas). Sus tres componente principales son:
Autoencoder (VAE)
U-Net
Text-Encoder
1-Autoencoder (VAE)
El modelo VAE tiene dos partes, un codificador y un decodificador. En codificador es usado para convertir la imagen en una representación latente de baja dimensión, que servirá como entrada a la “U-Net”. El decodificador por el contrario, transforma la representación latente nuevamente en una imagen.
Durante el entrenamiento de difusión latente, se usa el codificador para obtener las representaciones latentes de las imágenes para el proceso de difusión directa, se aplica más y más ruido en cada paso. Durante la inferencia, se realiza el proceso inverso de difusión donde “expande los latentes” para convertirlos nuevamente en imágenes con el decodificador VAE. Para la inferencia sólo necesitamos el decodificador.
Ejemplo de arquitectura de una red Autoencoder VAE del artículo “VAE“
2-U-Net
La U-Net tiene una mitad de camino “de contracción” y otra mitad de “expansión“, ambos compuestos por bloques ResNet (para soportar redes profundas sin desvanecer el aprendizaje). La primera mitad de la U-Net reduce la imagen a una representación de baja resolución (similar a un encoder) y la segunda parte intentará generar la imagen original en alta resolución (similar a un decoder). La salida de la U-Net predice el “ruido residual” que puede ser usado para calcular la representación “sin ruido” de la imagen.
Para prevenir que la U-Net pierda información importante durante el downsampling, se agregan conexiones de “atajo corto” (skip connections) entre los dos caminos: encoder y decoder. Además la U-Net de stable diffusion puede condicionar su salida respecto de los text-embeddings de las capas de cross-attention. Las capas de “Atención Cruzada” se agregan tanto en las partes de codificación y decodificación de la U-Net, entre los bloques ResNet. A eso se le llama Difusión guiada ó Difusión condicionada.
Ejemplo de Arquitectura de una U-Net, se llama así por su forma de “U”.
3-Text-Encoder
El Text-Encoder es el responsable de transformar el mensaje de entrada por ejemplo “Ilustración de Taylor Swift con un pingüino bailando en la ciudad” en un espacio de embeddings que puede ser comprendido por la U-Net. Se utiliza un encoder de tipo Transformers que mapea la secuencia de palabras de entrada en una secuencia latente del embedding de textos.
Stable Diffusion no entrena al Text-Encoder durante la etapa de entrenamiento del modelo si no que utiliza un encoder ya entrenado de CLIP.
Ejemplo de arquitectura de Clip, un modelo text-encoder
Ilustración de Taylor Swift con un Pingüino bailando en la ciudad, creada por el Autor.
Resumen de la arquitectura de Stable Difussion
El modelo al completo, como lo muestra la web oficial de Stable Diffusion es así:
Gráfica de arquitectura de Stable Diffusion. Fuente: web oficial de Stable Diffusion
Al momento de entrenar, la red tiene como entrada una imagen y un texto asociado. La red convertirá la imagen “en ruido” por completo y luego la intentará reconstruir. No olvidemos que es un problema de Aprendizaje supervisado, por lo cual, contamos con el dataset completo, con F(x)=Y desde el inicio.
A la izquierda, en rojo “Pixel Space” tenemos la “x” inicial que entrará en el Encoder de la VAE.
En verde, Espacio Latente, Arriba el Proceso de Difusión, lleva “z” a “zT” agregando ruido a la imagen
En verde, Espacio Latente, Abajo, de derecha a izquierda, entra “zT” a la U-Net e intentará reconvertirla en “z”.
Conditioning, a la derecha, utiliza el modelo CLIP con el texto asociado a la imagen y dirige la salida de la U-Net.
Por último, luego de iterar varias veces la U-Net y obtener una “z buena” (que es la imagen en estado latente), la decodificamos a pixeles utilizando el Decoder de la VAE (en el Pixel Space) y obtendremos una imagen similar a la “x” inicial.
Esta es la arquitectura para entrenar al modelo. Si vas a utilizar la red una vez entrenada, realmente realizaremos el “camino de inferencia“, veamos:
Al hacer la Inferencia, creamos una imagen:
Al momento de hacer la inferencia crearemos una imágen desde ruido! Por eso, el primer paso, es crear una imagen de 512×512 completamente de pixeles aleatorios!
Veamos la gráfica de inferencia que nos propone Stability.ai
Entonces, generamos la imagen de ruido y a partir de ella, la pasaremos a la U-Net que junto con el texto de entrada irá condicionando la salida, una y otra vez, intentará “quitar el ruido” para volver a una imagen original inexistente…
¿Te das cuenta? estamos engañando a la red neuronal, para que genere un gráfico que nunca antes existió…
La pobre Red Neuronal, es como si fuera un escultor con un cincel al que le damos un bloque de piedra enorme y le decimos “Quiero a Taylor Swift con un pingüino, hazlo!“.
Entonces, en cada iteración, creará desde el ruido, una imagen
Partimos de una imagen aleatoria completamente con ruido y tras 25 iteraciones la red de Stable Diffusion será capaz de generar una bonita ilustración.
Pero… ¿Qué imágenes puedes crear con Stable Diffusion?
Veamos algunos ejemplos de imágenes creadas por Stable Diffusion para ver si te convenzo de que esto es realmente algo grande… y luego ya puedes decidir si quieres probarlo y hasta instalarlo en tu propio equipo.
Aquí algunas imágenes encontradas en diversos canales:
En Lexica, que por cierto, te recomiendo visitar su web, pues tiene imágenes junto a los prompts para generarlas.
Puedes pagar por el servicio, ejecutar en la nube ó instalarlo en tu propia computadora.
1-Probarlo gratis, lo primero! (pagar luego…)
Desde la web de los creadores puedes dar tus primeros pasos. Tienes que registrarte y obtienes unos créditos gratuitos, luego que se acaben, tendrás que pagar. Debes entrar en https://beta.dreamstudio.ai/dream
Página de Bienvenida al Dream Studio de Stable Diffusion
Veremos en la parte de abajo, centro la caja de texto donde podemos ingresar el “prompt” con lo que queremos dibujar. Sobre la derecha los parámetros de configuración, que comentaremos luego, pero lo básico es que puedes elegir el tamaño de imagen y cantidad de imágenes a generar.
Ten en cuenta que tienes unos créditos (gratuitos) limitados para utilizar, por lo que debes estar atento a lo que vas consumiendo.
2-Instalar StableDiffusion en tu Computadora
Podemos instalar Stable Difussion en Windows y en Linux con “Instaladores automáticos” siguiendo las instrucciones del repositorio de Automatic1111. Para Windows hay otro instalador aqui .
Puedes instalar en ordenadores Mac (y aprovechar las GPUS de los chips M1 y M2) desde el repositorio de InvokeAI siguiendo las instrucciones para Macintosh.
Si te atreves a instalarlo de manera un poco más “manual”, puedes aventurarte a seguir las instrucciones del Repositorio Oficial de Stable Diffusion. No es difícil, básicamente, si tienes instalado Anaconda en tu ordenador, es clonar el repo y crear el environment de python siguiendo los pasos.
Un paso Clave: descargar el modelo de la red de HuggingFace
Casi todos los modos de instalar que vimos anteriormente, necesitan de un paso manual que es el de obtener y descargar el modelo desde la web de HuggingFace que ocupa 4.27 Gygabytes. Para ello, debes registrarte en HuggingFace e ir a la página del modelo. Deberás aceptar las condiciones de uso, y luego podrás descargar el último modelo, al momento de escribir este artículo es el archivo sd-v1-4.ckpt. Una vez descargado, lo deberás copiar en la carpeta models/ldm/stable-diffusion-1/ y renombrar el archivo como model.ckpt.
Eso es todo! Voilá, crea todas las imágenes que quieras! Tienes el mundo en tus manos!
Tiempos de “Rendering”
Si tienes una tarjeta gráfica con GPU, en mi caso la Nvidia RTX3080 tarda 5 segundos en crear una imágen de 512x512px. Si no tienes tarjeta puedes crear imágenes usando CPU pero tardarán unos 6 minutos (en un ordenador del año 2015 Core i5 y 8GB de memoria). En ordenadores Macbook con chip M2 tarda aproximadamente 1 minuto por imagen.
3-Usar StableDiffusion gratis y con GPU desde la nube de Google Colab
Otra opción para utilizar este genial modelo de forma gratuita es utilizar las notebooks de Google Colab y activar la opción de GPU. Existen varias notebooks compartidas que puedes utilizar como template con la instalación, aquí te recomiendo esta notebook y un hilo en Twitter en español, que te ayuda a seguir los pasos.
Entendiendo los parámetros de entrada de Stable Diffusion
Tanto en la versión web, la de instaladores, manual ó en la nube; contaremos con los mismos parámetros para configurar la red neuronal. Estos son:
Alto y Ancho de imagen: deben ser múltiplos de 64, tamaño mínimo de 256 y máximo de 1024px. Sin embargo la recomendación es utilizar 512×512 pues es el tamaño con el que se entrenó la red.
Steps: es la cantidad de iteraciones que realizará la U-Net durante la inferencia. Cuanto más iteramos, mayor “ruido” quitaremos de la imagen, es decir, quedará mejor definida. Pero también tardará más tiempo. Teniendo en cuenta el sampler que utilicemos, un valor de entre 25 y 50 estará bien.
CFG Scale: este es un valor curioso, pues determina el “grado de libertad” que damos a la propia red para ser creativa. El valor por defecto es 7.5. Si disminuimos el valor, se centrará más en nuestro Prompt. Si aumentamos el valor (más de 10) empezará a improvisar y a hacer dibujos más delirantes y más a su antojo.
Número de Imágenes: la cantidad de diversas imágenes que se crearán durante la inferencia. Cuantas más creamos más memoria RAM necesitaremos, tener en cuenta.
Sampler: será la función con la que se creará el “denoising” en la U-Net y tiene implicancias en la imagen que se generará. El Sampler más avanzado (de momento) es el DPM2 y necesita más steps para lograr buenos resultados, llevando más tiempo. Curiosamente, el sampler llamado Euler Ancestral es el más básico y logra muy buenas imágenes en unas 20 iteraciones (menor tiempo).
Seed ó Semilla: La semilla está relacionada con la imagen con ruido que generamos inicialmente desde donde la red empezará a dibujar. Con una misma semilla podremos replicar una imagen todas la veces que queramos para un mismo prompt. Si no asignamos un valor de semilla, se generará aleatoriamente, obteniendo siempre imágenes distintas para el mismo prompt.
El Prompt Engineering
Se le llama Prompt Engineering al arte de introducir textos que generen buenas imágenes. Lo cierto es que no es tan fácil como parece la creación de imágenes, es decir, la red siempre creará imágenes, pero para que destaquen realmente, hay que agregar las keywords adecuadas. Por suerte ya hay personas que descubrieron muchos de esos tweaks
Los truquillos en el Prompt
Varios exploradores recomiendan seguir una fórmula de tipo:
Tipo imagen – objeto – lugar – tiempo – estilo ó autor
Por ejemplo:
Pintura de un gato con gafas en un teatro, 1960, por Velazquez
Y esto mismo… pero en inglés, obtenemos:
Oil paint of a cat wearing glasses in a theatre, 1960, by Velazquez
Hay algunas palabras que se agregan al final, que son muy útiles, poner “trending on ArtStation”, “highly detailed”, “unreal engine 5”.
Aqui te dejo un enlace a un artículo maravilloso que muestra con ejemplo de muchas de las combinaciones.
Además del txt2Img (que a partir de un texto, generemos una imagen), tenemos otra opción llamada img2img.
Con esta opción ingresamos una imagen creada por nosotros con el “paintbrush” u otra herramienta similar y la utilizaremos como imagen de inicio para generar una nueva imagen. Esto tiene mucha lógica si lo piensas, en vez de empezar con una imagen llena de ruido, le damos unas “guías” a la red neuronal para que pueda crear la imagen. Y los resultados son increíbles!
Imagen de partida para img2img
Imagen obtenida con Img2Img
Por si fuera poco, Inpainting y Outpainting
El Inpainting permite crear una máscara dentro de una imagen y que el modelo dibuje dentro, manteniendo el estilo pictórico y la coherencia.
También existe el llamado OutPainting, que nos permite “extender” una imagen, logrando obras increíbles, será mejor que lo veas!
Resumen y Conclusiones
A estas alturas, espero que estes tan emocionado como yo con esta nueva tecnología y esto es sólo el comienzo! Los modelos de Machine Learning de texto-a-imagen acaban de aterrizar y se perfeccionarán; uno de los puntos fuertes y gran acierto de Stable Diffusion es que al lanzarse a todo el público, logró captar a una gran comunidad de desarrolladores, artistas y curiosos que colaboran y que potencian sus capacidades aún más! Al momento de escribir el artículo, han pasado menos de 2 meses y aparecieron muchísimos proyectos relacionados. Además se comenta que está por aparecer la nueva versión del modelo de pesos entrenado 1.5 dentro de poco. Algunos usuarios hasta crearon videos mediante Stable Diffusion y otros empiezan a mezclar la red con las 3 dimensiones para crear objetos.
En próximos artículos veremos en mayor profundidad y en código Python el uso de redes VAE, U-Net y Transformers.
Hasta pronto!
Material Adicional:
Aquí comparto dos videos muy buenos sobre Arte con IA y otro sobre Stable Diffusion
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.
]]>https://aprendemachinelearning.com/crea-imagenes-stable-diffusion-con-inteligencia-artificial-en-tu-ordenador/feed/07681Random Forest, el poder del Ensamble
https://aprendemachinelearning.com/random-forest-el-poder-del-ensamble/
https://aprendemachinelearning.com/random-forest-el-poder-del-ensamble/#respondMon, 17 Jun 2019 08:00:00 +0000https://www.aprendemachinelearning.com/?p=6001Si 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 […]
]]>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>>.
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 kfeatures (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
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.
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:
]]>https://aprendemachinelearning.com/random-forest-el-poder-del-ensamble/feed/06001Clasificación con datos desbalanceados
https://aprendemachinelearning.com/clasificacion-con-datos-desbalanceados/
https://aprendemachinelearning.com/clasificacion-con-datos-desbalanceados/#commentsThu, 16 May 2019 08:00:00 +0000https://www.aprendemachinelearning.com/?p=6881Contrarrestar 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 y Muestras sintéticas subsampling oversamplig combinación Balanced Ensemble Empecemos! ¿Qué […]
]]>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 y Muestras sintéticas
subsampling
oversamplig
combinación
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
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.
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:
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.
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.
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.
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.
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:
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!
]]>https://aprendemachinelearning.com/clasificacion-con-datos-desbalanceados/feed/206881¿Cómo funcionan las Convolutional Neural Networks? Visión por Ordenador
https://aprendemachinelearning.com/como-funcionan-las-convolutional-neural-networks-vision-por-ordenador/
https://aprendemachinelearning.com/como-funcionan-las-convolutional-neural-networks-vision-por-ordenador/#commentsThu, 29 Nov 2018 09:00:00 +0000https://www.aprendemachinelearning.com/?p=6209En este artículo intentaré explicar la teoría relativa a las Redes Neuronales Convolucionales (en inglés CNN) que son el algoritmo utilizado en Aprendizaje Automático para dar la capacidad de “ver” al ordenador. Gracias a esto, desde apenas 1998, podemos clasificar imágenes, detectar diversos tipos de tumores automáticamente, enseñar a conducir a los coches autónomos y […]
]]>En este artículo intentaré explicar la teoría relativa a las Redes Neuronales Convolucionales (en inglés CNN) que son el algoritmo utilizado en Aprendizaje Automático para dar la capacidad de “ver” al ordenador. Gracias a esto, desde apenas 1998, podemos clasificar imágenes, detectar diversos tipos de tumores automáticamente, enseñar a conducir a los coches autónomos y un sinfín de otras aplicaciones.
El tema es bastante complejo/complicado e intentaré explicarlo lo más claro posible. En este artículo doy por sentado que tienes conocimientos básicos de cómo funciona una red neuronal artificial multicapa feedforward (fully connected). Si no es así te recomiendo que antes leas sobre ello:
¿Qúe es una CNN? ¿Cómo puede ver una red neuronal? ¿Cómo clasifica imagenes y distingue un perro de un gato?
La CNN es un tipo de Red Neuronal Artificial con aprendizaje supervisado que procesa sus capas imitando al cortex visual del ojo humano para identificar distintas características en las entradas que en definitiva hacen que pueda identificar objetos y “ver”. Para ello, la CNN contiene varias capas ocultas especializadas y con una jerarquía: esto quiere decir que las primeras capas pueden detectar lineas, curvas y se van especializando hasta llegar a capas más profundas que reconocen formas complejas como un rostro o la silueta de un animal.
Necesitaremos…
Recodemos que la red neuronal deberá aprender por sí sola a reconocer una diversidad de objetos dentro de imágenes y para ello necesitaremos una gran cantidad de imágenes -lease más de 10.000 imágenes de gatos, otras 10.000 de perros,…- para que la red pueda captar sus características únicas -de cada objeto- y a su vez, poder generalizarlo -esto es que pueda reconocer como gato tanto a un felino negro, uno blanco, un gato de frente, un gato de perfil, gato saltando, etc.-
Pixeles y neuronas
Para comenzar, la red toma como entrada los pixeles de una imagen. Si tenemos una imagen con apenas 28×28 pixeles de alto y ancho, eso equivale a 784 neuronas. Y eso es si sólo tenemos 1 color (escala de grises). Si tuviéramos una imagen a color, necesitaríamos 3 canales (red, green, blue) y entonces usaríamos 28x28x3 = 2352 neuronas de entrada. Esa es nuestra capa de entrada. Para continuar con el ejemplo, supondremos que utilizamos la imagen con 1 sólo color.
No Olvides: Pre-procesamiento
Antes de alimentar la red, recuerda que como entrada nos conviene normalizar los valores. Los colores de los pixeles tienen valores que van de 0 a 255, haremos una transformación de cada pixel: “valor/255” y nos quedará siempre un valor entre 0 y 1.
Convoluciones
Ahora comienza el “procesado distintivo” de las CNN. Es decir, haremos las llamadas “convoluciones”: Estas consisten en tomar “grupos de pixeles cercanos” de la imagen de entrada e ir operando matemáticamente (producto escalar) contra una pequeña matriz que se llama kernel. Ese kernel supongamos de tamaño 3×3 pixels “recorre” todas las neuronas de entrada (de izquierda-derecha, de arriba-abajo) y genera una nueva matriz de salida, que en definitiva será nuestra nueva capa de neuronas ocultas. NOTA: si la imagen fuera a color, el kernel realmente sería de 3x3x3 es decir: un filtro con 3 kernels de 3×3; luego esos 3 filtros se suman (y se le suma una unidad bias) y conformarán 1 salida (cómo si fuera 1 solo canal).
El kernel tomará inicialmente valores aleatorios(1) y se irán ajustando mediante backpropagation. (1)Una mejora es hacer que siga una distribución normal siguiendo simetrías, pero sus valores son aleatorios.
Filtro: conjunto de kernels
UN DETALLE: en realidad, no aplicaremos 1 sólo kernel, si no que tendremos muchos kernel (su conjunto se llama filtros). Por ejemplo en esta primer convolución podríamos tener 32 filtros, con lo cual realmente obtendremos 32 matrices de salida (este conjunto se conoce como “feature mapping”), cada una de 28x28x1 dando un total del 25.088 neuronas para nuestra PRIMER CAPA OCULTA de neuronas. ¿No les parecen muchas para una imagen cuadrada de apenas 28 pixeles? Imaginen cuántas más serían si tomáramos una imagen de entrada de 224x224x3 (que aún es considerado un tamaño pequeño)…
Aquí vemos al kernel realizando el producto matricial con la imagen de entrada y desplazando de a 1 pixel de izquierda a derecha y de arriba-abajo y va generando una nueva matriz que compone al mapa de features
A medida que vamos desplazando el kernel y vamos obteniendo una “nueva imagen” filtrada por el kernel. En esta primer convolución y siguiendo con el ejemplo anterior, es como si obtuviéramos 32 “imágenes filtradas nuevas”. Estas imágenes nuevas lo que están “dibujando” son ciertas características de la imagen original. Esto ayudará en el futuro a poder distinguir un objeto de otro (por ej. gato ó perro).
La imagen realiza una convolución con un kernel y aplica la función de activación, en este caso ReLu
La función de Activación
La función de activación más utilizada para este tipo de redes neuronales es la llamada ReLu por Rectifier Linear Unit y consiste en f(x)=max(0,x).
Subsampling
Ahora viene un paso en el que reduciremos la cantidad de neuronas antes de hacer una nueva convolución. ¿Por qué? Como vimos, a partir de nuestra imagen blanco y negro de 28x28px tenemos una primer capa de entrada de 784 neuronas y luego de la primer convolución obtenemos una capa oculta de 25.088 neuronas -que realmente son nuestros 32 mapas de características de 28×28-
Si hiciéramos una nueva convolución a partir de esta capa, el número de neuronas de la próxima capa se iría por las nubes (y ello implica mayor procesamiento)! Para reducir el tamaño de la próxima capa de neuronas haremos un proceso de subsampling en el que reduciremos el tamaño de nuestras imágenes filtradas pero en donde deberán prevalecer las características más importantes que detectó cada filtro. Hay diversos tipos de subsampling, yo comentaré el “más usado”: Max-Pooling
Subsampling con Max-Pooling
Vamos a intentar explicarlo con un ejemplo: supongamos que haremos Max-pooling de tamaño 2×2. Esto quiere decir que recorreremos cada una de nuestras 32 imágenes de características obtenidas anteriormente de 28x28px de izquierda-derecha, arriba-abajo PERO en vez de tomar de a 1 pixel, tomaremos de “2×2” (2 de alto por 2 de ancho = 4 pixeles) e iremos preservando el valor “más alto” de entre esos 4 pixeles (por eso lo de “Max”). En este caso, usando 2×2, la imagen resultante es reducida “a la mitad”y quedará de 14×14 pixeles. Luego de este proceso de subsamplig nos quedarán 32 imágenes de 14×14, pasando de haber tenido 25.088 neuronas a 6272, son bastantes menos y -en teoría- siguen almacenando la información más importante para detectar características deseadas.
¿Ya terminamos? NO: ahora más convoluciones!!
Muy bien, pues esa ha sido una primer convolución: consiste de una entrada, un conjunto de filtros, generamos un mapa de características y hacemos un subsampling. Con lo cual, en el ejemplo de imágenes de 1 sólo color tendremos:
1)Entrada: Imagen
2)Aplico Kernel
3)Obtengo Feature Mapping
4)Aplico Max-Pooling
5)Obtengo “Salida” de la Convolución
28x28x1
32 filtros de 3×3
28x28x32
de 2×2
14x14x32
La primer convolución es capaz de detectar características primitivas como lineas ó curvas. A medida que hagamos más capas con las convoluciones, los mapas de características serán capaces de reconocer formas más complejas, y el conjunto total de capas de convoluciones podrá “ver”.
Pues ahora deberemos hacer una Segunda convolución que será:
1)Entrada: Imagen
2)Aplico Kernel
3)Obtengo Feature Mapping
4)Aplico Max-Pooling
5)Obtengo “Salida” de la Convolución
14x14x32
64 filtros de 3×3
14x14x64
de 2×2
7x7x64
La 3er convolución comenzará en tamaño 7×7 pixels y luego del max-pooling quedará en 3×3 con lo cual podríamos hacer sólo 1 convolución más. En este ejemplo empezamos con una imagen de 28x28px e hicimos 3 convoluciones. Si la imagen inicial hubiese sido mayor (de 224x224px) aún hubiéramos podido seguir haciendo convoluciones.
1)Entrada: Imagen
2)Aplico Kernel
3)Obtengo Feature Mapping
4)Aplico Max-Pooling
5)Obtengo “Salida” de la Convolución
7x7x64
128 filtros de 3×3
7x7x128
de 2×2
3x3x128
Llegamos a la última convolución y nos queda el desenlace…
Conectar con una red neuronal “tradicional”.
Para terminar, tomaremos la última capa oculta a la que hicimos subsampling, que se dice que es “tridimensional” por tomar la forma -en nuestro ejemplo- 3x3x128 (alto,ancho,mapas) y la “aplanamos”, esto es que deja de ser tridimensional, y pasa a ser una capa de neuronas “tradicionales”, de las que ya conocíamos. Por ejemplo, podríamos aplanar (y conectar) a una nueva capa oculta de 100 neuronas feedforward.
Entonces, a esta nueva capa oculta “tradicional”, le aplicamos una función llamada Softmax que conecta contra la capa de salida final que tendrá la cantidad de neuronas correspondientes con las clases que estamos clasificando. Si clasificamos perros y gatos, serán 2 neuronas. Si es el dataset Mnist numérico serán 10 neuronas de salida. Si clasificamos coches, aviones ó barcos serán 3, etc.
Las salidas al momento del entrenamiento tendrán el formato conocido como “one-hot-encoding” en el que para perros y gatos sera: [1,0] y [0,1], para coches, aviones ó barcos será [1,0,0]; [0,1,0];[0,0,1].
Y la función de Softmax se encarga de pasar a probabilidad (entre 0 y 1) a las neuronas de salida. Por ejemplo una salida [0,2 0,8] nos indica 20% probabilidades de que sea perro y 80% de que sea gato.
¿Y cómo aprendió la CNN a “ver”?: Backpropagation
El proceso es similar al de las redes tradicionales en las que tenemos una entrada y una salida esperada (por eso aprendizaje supervisado) y mediante el backpropagation mejoramos el valor de los pesos de las interconexiones entre capas de neuronas y a medida que iteramos esos pesos se ajustan hasta ser óptimos. PERO…
En el caso de la CNN, deberemos ajustar el valor de los pesos de los distintos kernels. Esto es una gran ventaja al momento del aprendizaje pues como vimos cada kernel es de un tamaño reducido, en nuestro ejemplo en la primer convolución es de tamaño de 3×3, eso son sólo 9 parámetros que debemos ajustar en 32 filtros dan un total de 288 parámetros. En comparación con los pesos entre dos capas de neuronas “tradicionales”: una de 748 y otra de 6272 en donde están TODAS interconectarlas con TODAS y eso equivaldría a tener que entrenar y ajustar más de 4,5 millones de pesos (repito: sólo para 1 capa).
Comparativa entre una red neuronal “tradicional” y una CNN
Dejaré un cuadro resumen para intentar aclarar más las diferencias entre las redes Fully connected y las Convolutional Neural Networks.
Red “tradicional” Feedforward multicapa
Red Neuronal Convolucional CNN
Datos de entrada en la Capa Inicial
Las características que analizamos. Por ejemplo: ancho, alto, grosor, etc.
Pixeles de una imagen. Si es color, serán 3 capas para rojo,verde,azul
Capas ocultas
elegimos una cantidad de neuronas para las capas ocultas.
Tenemos de tipo: * Convolución (con un tamaño de kernel y una cantidad de filtros) * Subsampling
Capa de Salida
La cantidad de neuronas que queremos clasificar. Para “comprar” ó “alquilar” serán 2 neuronas.
Debemos “aplanar” la última convolución con una (ó más) capas de neuronas ocultas “tradicionales” y hacer una salida mediante SoftMax a la capa de salida que clasifica “perro” y “gato” serán 2 neuronas.
Aprendizaje
Supervisado
Supervisado
Interconexiones
Entre capas, todas las neuronas de una capa con la siguiente.
Son muchas menos conexiones necesarias, pues realmente los pesos que ajustamos serán los de los filtros/kernels que usamos.
Significado de la cantidad de capas ocultas
Realmente es algo desconocido y no representa algo en sí mismo.
Las capas ocultas son mapas de detección de características de la imagen y tienen jerarquía: primeras capas detectan lineas, luego curvas y formas cada vez más elaboradas.
Backpropagation
Se utiliza para ajustar los pesos de todas las interconexiones de las capas
Se utiliza para ajustar los pesos de los kernels.
Arquitectura básica
Resumiendo: podemos decir que los elementos que usamos para crear CNNs son:
Entrada: Serán los pixeles de la imagen. Serán alto, ancho y profundidad será 1 sólo color o 3 para Red,Green,Blue.
Capa De Convolución: procesará la salida de neuronas que están conectadas en “regiones locales” de entrada (es decir pixeles cercanos), calculando el producto escalar entre sus pesos (valor de pixel) y una pequeña región a la que están conectados en el volumen de entrada. Aquí usaremos por ejemplo 32 filtros o la cantidad que decidamos y ese será el volumen de salida.
“CAPA RELU” aplicará la función de activación en los elementos de la matriz.
POOL ó SUBSAMPLING: Hará una reducción en las dimensiones alto y ancho, pero se mantiene la profundidad.
CAPA “TRADICIONAL” red de neuronas feedforward que conectará con la última capa de subsampling y finalizará con la cantidad de neuronas que queremos clasificar.
Se me quedan en el tintero muchísimas cosas más que explicar… pero creo que lo iré completando con el tiempo o crearé un nuevo artículo con mayor detalle/más técnico. Temas y definiciones como padding, stride, evitar overfitting, image-aumentation, dropout… o por nombrar algunas redes famosas ResNet, AlexNet, GoogLeNet and DenseNet, al mismísimo Yann LeCun… todo eso.. se queda fuera de este texto.
Este artículo pretende ser un punto inicial para seguir investigando y aprendiendo sobre las CNN. Al final dejo enlace a varios artículos para ampliar información sobre CNN.
]]>https://aprendemachinelearning.com/como-funcionan-las-convolutional-neural-networks-vision-por-ordenador/feed/286209Clasificación de Imágenes en Python
https://aprendemachinelearning.com/clasificacion-de-imagenes-en-python/
https://aprendemachinelearning.com/clasificacion-de-imagenes-en-python/#commentsThu, 08 Nov 2018 07:30:00 +0000https://www.aprendemachinelearning.com/?p=5910Crearemos 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 […]
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.
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
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:
Importar librerías
Cargar las 70.000 imágenes (en memoria!)
Crear dinámicamente las etiquetas de resultado.
Dividir en sets de Entrenamiento, Validación y Test
algo de preprocesamiento de datos
Crear el modelo de la CNN
Ejecutar nuestra máquina de aprendizaje (Entrenar la red)
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.
#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).
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")
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.
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!
]]>https://aprendemachinelearning.com/clasificacion-de-imagenes-en-python/feed/925910Comprende Principal Component Analysis
https://aprendemachinelearning.com/comprende-principal-component-analysis/
https://aprendemachinelearning.com/comprende-principal-component-analysis/#commentsMon, 08 Oct 2018 13:00:00 +0000https://www.aprendemachinelearning.com/?p=5904En este artículo veremos una herramienta muy importante para nuestro kit de Machine Learning y Data Science: PCA para Reducción de dimensiones. Como bonus-track veremos un ejemplo rápido-sencillo en Python usando Scikit-learn. Introducción a PCA Imaginemos que queremos predecir los precios de alquiler de vivienda del mercado. Al recopilar información de diversas fuentes tendremos en […]
]]>En este artículo veremos una herramienta muy importante para nuestro kit de Machine Learning y Data Science: PCA para Reducción de dimensiones. Como bonus-track veremos un ejemplo rápido-sencillo en Python usando Scikit-learn.
Introducción a PCA
Imaginemos que queremos predecir los precios de alquiler de vivienda del mercado. Al recopilar información de diversas fuentes tendremos en cuenta variables como tipo de vivienda, tamaño de vivienda, antigüedad, servicios, habitaciones, con/sin jardín, con/sin piscina, con/sin muebles pero también podemos tener en cuenta la distancia al centro, si hay colegio en las cercanías, o supermercados, si es un entorno ruidoso, si tiene autopistas en las cercanías, la “seguridad del barrio”, si se aceptan mascotas, tiene wifi, tiene garaje, trastero… y seguir y seguir sumando variables.
Es posible que cuanta más (y mejor) información, obtengamos una predicción más acertada. Pero también empezaremos a notar que la ejecución de nuestro algoritmo seleccionado (regresión lineal, redes neuronales, etc.) empezará a tomar más y más tiempo y recursos. Es posible que algunas de las variables sean menos importantes y no aporten demasiado valor a la predicción. También podríamos acercarnos peligrosamente a causar overfitting al modelo.
¿No sería mejor tomar menos variables, pero más valiosas?
Al quitar variables estaríamos haciendo Reducción de Dimensiones. Al hacer Reducción de Dimensiones (las características) tendremos menos relaciones entre variables a considerar. Para reducir las dimensiones podemos hacer dos cosas:
Eliminar por completo dimensiones
Extracción de Características
Eliminar por completo algunas dimensiones no estaría mal, pero deberemos tener certeza en que estamos quitando dimensiones poco importantes. Por ejemplo para nuestro ejemplo, podemos suponer que el precio de alquiler no cambiará mucho si el dueño acepta mascotas en la vivienda. Podría ser un acierto o podríamos estar perdiendo información importante.
En la Extracción de Características si tenemos 10 características crearemos otras 10 características nuevas independientes en donde cada una de esas “nuevas” características es una combinación de las 10 características “viejas”. Al crear estas nuevas variables independientes lo haremos de una manera específica y las pondremos en un orden de “mejor a peor” sean para predecir a la variable dependiente.
¿Y la reducción de dimensiónes? te preguntarás. Bueno, intentaremos mantener todas las variables posibles, pero prescindiremos de las menos importantes. Como tenemos las variables ordenadas de “mejor a peores predictoras” ya sabemos cuales serán las más y menos valiosas. A diferencia de la eliminación directa de una característica “vieja”, nuestras nuevas variables son combinaciones de todas las variables originales, aunque eliminemos algunas, estaremos manteniendo la información útil de todas las variables iniciales.
¿Qué es Principal Component Analysis?
Entonces Principal Component Analysis es una técnica de Extracción de Características donde combinamos las entradas de una manera específica y podemos eliminar algunas de las variables “menos importantes” manteniendo la parte más importante todas las variables. Como valor añadido, luego de aplicar PCA conseguiremos que todas las nuevas variables sean independientes una de otra.
¿Cómo funciona PCA?
En resumen lo que hace el algoritmo es:
Estandarizar los datos de entrada (ó Normalización de las Variables)
Ordenar los autovalores de mayor a menor y elegir los “k” autovectores que se correspondan con los autovectores “k” más grandes (donde “k” es el número de dimensiones del nuevo subespacio de características).
Construir la matriz de proyección W con los “k” autovectores seleccionados.
Transformamos el dataset original “X estandarizado” vía W para obtener las nuevas características k-dimensionales.
Tranquilos, que todo esto ya lo hace solito scikit-learn (u otros paquetes Python). Ahora que tenemos las nuevas dimensiones, deberemos seleccionar con cuales nos quedamos.
Selección de los Componentes Principales
Típicamente utilizamos PCA para reducir dimensiones del espacio de características original (aunque PCA tiene más aplicaciones). Hemos rankeado las nuevas dimensiones de “mejor a peor reteniendo información”. Pero ¿cuantas elegir para obtener buenas predicciones, sin perder información valiosa? Podemos seguir 3 métodos:
Método 1: Elegimos arbitrariamente “las primeras n dimensiones” (las más importantes). Por ejemplo si lo que queremos es poder graficar en 2 dimensiones, podríamos tomar las 2 características nuevas y usarlas como los ejes X e Y.
Método 2: calcular la “proporción de variación explicada“ de cada característica e ir tomando dimensiones hasta alcanzar un mínimo que nos propongamos, por ejemplo hasta alcanzar a explicar el 85% de la variabilidad total.
Método 3: Crear una gráfica especial llamada scree plot -a partir del Método 2- y seleccionar cuántas dimensiones usaremos por el método “del codo” en donde identificamos visualmente el punto en donde se produce una caída significativa en la variación explicada relativa a la característica anterior.
¿Pero… porqué funciona PCA?
Suponiendo nuestras características de entrada estandarizadas como la matriz Z y ZT su transpuesta, cuando creamos la matriz de covarianza ZTZ es una matriz que contiene estimados de cómo cada variable de Z se relaciona con cada otra variable de Z. Comprender como una variable es asociada con otra es importante!
Los autovectores representan dirección. Los autovalores representan magnitud. A mayores autovalores, se correlacionan direcciones más importantes.
Por último asumimos que a más variabilidad en una dirección particular se correlaciona con explicar mejor el comportamiento de una variable dependiente. Mucha variabilidad usualmente indica “Información” mientras que poca variabilidad indica “Ruido”.
y veremos que con 5 de las nuevas dimensiones (y descartando 4) obtendremos
hasta un 85% de variación explicada y
buenas predicciones.
Realizaremos 2 gráficas:
una con el acumulado de variabilidad explicada y
una gráfica 2D, en donde el eje X e Y serán los 2 primero componentes principales obtenidos por PCA.
Y veremos cómo los resultados “comprar ó alquilar” tienen [icon name=”angle-double-left” class=”” unprefixed_class=””]bastante buena[icon name=”angle-double-right” class=”” unprefixed_class=””] separación en 2 dimensiones.
#importamos librerías
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
plt.rcParams['figure.figsize'] = (16, 9)
plt.style.use('ggplot')
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
#cargamos los datos de entrada
dataframe = pd.read_csv(r"comprar_alquilar.csv")
print(dataframe.tail(10))
#normalizamos los datos
scaler=StandardScaler()
df = dataframe.drop(['comprar'], axis=1) # quito la variable dependiente "Y"
scaler.fit(df) # calculo la media para poder hacer la transformacion
X_scaled=scaler.transform(df)# Ahora si, escalo los datos y los normalizo
#Instanciamos objeto PCA y aplicamos
pca=PCA(n_components=9) # Otra opción es instanciar pca sólo con dimensiones nuevas hasta obtener un mínimo "explicado" ej.: pca=PCA(.85)
pca.fit(X_scaled) # obtener los componentes principales
X_pca=pca.transform(X_scaled) # convertimos nuestros datos con las nuevas dimensiones de PCA
print("shape of X_pca", X_pca.shape)
expl = pca.explained_variance_ratio_
print(expl)
print('suma:',sum(expl[0:5]))
#Vemos que con 5 componentes tenemos algo mas del 85% de varianza explicada
#graficamos el acumulado de varianza explicada en las nuevas dimensiones
plt.plot(np.cumsum(pca.explained_variance_ratio_))
plt.xlabel('number of components')
plt.ylabel('cumulative explained variance')
plt.show()
#graficamos en 2 Dimensiones, tomando los 2 primeros componentes principales
Xax=X_pca[:,0]
Yax=X_pca[:,1]
labels=dataframe['comprar'].values
cdict={0:'red',1:'green'}
labl={0:'Alquilar',1:'Comprar'}
marker={0:'*',1:'o'}
alpha={0:.3, 1:.5}
fig,ax=plt.subplots(figsize=(7,5))
fig.patch.set_facecolor('white')
for l in np.unique(labels):
ix=np.where(labels==l)
ax.scatter(Xax[ix],Yax[ix],c=cdict[l],label=labl[l],s=40,marker=marker[l],alpha=alpha[l])
plt.xlabel("First Principal Component",fontsize=14)
plt.ylabel("Second Principal Component",fontsize=14)
plt.legend()
plt.show()
En esta gráfica de variabilidad explicada acumulada, vemos que tomando los primeros 5 componentes llegamos al 85%Aquí vemos que al reducir las 9 dimensiones iniciales a tan sólo 2 logramos darnos una idea de dónde visualizar nuestras predicciones para comprar o alquilar casa.
una medida de como cada variable se asocia con las otras (matriz de covarianza)
La dirección en las que nuestros datos están dispersos (autovectores)
La relativa importancia de esas distintas direcciones (autovalores)
PCA combina nuestros predictores y nos permite deshacernos de los autovectores de menor importancia relativa.
Contras de PCA y variantes
No todo es perfecto en la vida ni en PCA. Como contras, debemos decir que el algoritmo de PCA es muy influenciado por los outliers en los datos. Por esta razón, surgieron variantes de PCA para minimizar esta debilidad. Entre otros se encuentran: RandomizedPCA, SparcePCA y KernelPCA.
Por último decir que PCA fue creado en 1933 y ha surgido una buena alternativa en 2008 llamada t-SNE con un enfoque distinto y del que hablaremos en un futuro artículo…
Para concluir, les comentaré un ejemplo muy interesante que vi para demostrar la eficacia de aplicar PCA. Si conocen el ejercicio “clásico” MNIST (algunos le llaman el Hello Word del Machine Learning), donde tenemos un conjunto de 70.000 imágenes con números “a mano” del 0 al 9 y debemos reconocerlos utilizando alguno de los algoritmos de clasificación.
Pues en el caso de MNIST, nuestras características de entrada son las imágenes de 28×28 pixeles, lo que nos da un total de 748 dimensiones de entrada. Ejecutar Regresión Logística en con una Macbook tarda unos 48 segundos en entrenar el set de datos y lograr una precisión del 91%.
Aplicando PCA al MNIST con una varianza retenida del 90% logramos reducir las dimensiones de 748 a 236. Ejecutar Regresión Logística ahora toma 10 segundos y la precisión obtenida sigue siendo del 91% !!!
Suscripción al Blog
Recibe el próximo artículo quincenal sobre Aprendizaje automático, teoría y ejemplos
]]>https://aprendemachinelearning.com/comprende-principal-component-analysis/feed/115904¿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/#commentsThu, 23 Aug 2018 09:00:00 +0000https://www.aprendemachinelearning.com/?p=5903Hoy 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 […]
]]>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
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:
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”
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):
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.
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.
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:
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
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
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
]]>https://aprendemachinelearning.com/comprar-casa-o-alquilar-naive-bayes-usando-python/feed/185903Programa un coche Arduino con Inteligencia Artificial
https://aprendemachinelearning.com/programa-un-coche-arduino-con-inteligencia-artificial/
https://aprendemachinelearning.com/programa-un-coche-arduino-con-inteligencia-artificial/#commentsWed, 01 Aug 2018 08:00:00 +0000https://www.aprendemachinelearning.com/?p=5906El Machine Learning nos permitirá utilizar Redes Neuronales para que un coche Arduino conduzca sólo evitando obstáculos. En el artículo anterior, creamos una red neuronal desde cero en Python. En este artículo mejoraremos esa red y copiaremos sus pesos a una red con propagación hacia adelante en Arduino que permitirá que el coche robot conduzca […]
]]>El Machine Learning nos permitirá utilizar Redes Neuronales para que un coche Arduino conduzca sólo evitando obstáculos.
En el artículo anterior, creamos una red neuronal desde cero en Python. En este artículo mejoraremos esa red y copiaremos sus pesos a una red con propagación hacia adelante en Arduino que permitirá que el coche robot conduzca sólo sin chocar.
La Nueva Red Neuronal
Por simplificar el modelo de aprendizaje, en el post anterior teníamos una red de tres capas con 2 neuronas de entrada 3 ocultas y 2 de salida: giro y dirección. Para este ejercicio haremos que la red neuronal tenga 4 salidas: una para cada motor. Además las salidas serán entre 0 y 1 (apagar o encender motor). También cambiaremos las entradas para que todas comprendan valores entre -1 y 1 y sean acordes a nuestra función tangente hiperbólica. Aquí vemos los cambios en esta tabla:
Entrada:
Sensor Distancia
Entrada:
Posición Obstáculo
Salida:
Motor 1
Salida:
Motor 2
Salida:
Motor 3
Salida:
Motor 4
-1
0
1
0
0
1
-1
1
1
0
0
1
-1
-1
1
0
0
1
0
-1
1
0
1
0
0
1
0
1
0
1
0
0
1
0
0
1
1
1
0
1
1
0
1
-1
0
1
1
0
1
0
0
1
1
0
Siendo el valor de los motores 1 y 0:
Acción
Motor 1
Motor 2
Motor 3
Motor 4
Avanzar
1
0
0
1
Retroceder
0
1
1
0
Giro Derecha
0
1
0
1
Giro Izquierda
1
0
1
0
Para instanciar nuestra red ahora usaremos este código:
# Red Coche para Evitar obstáculos
nn = NeuralNetwork([2,3,4],activation ='tanh')
X = np.array([[-1, 0], # sin obstaculos
[-1, 1], # sin obstaculos
[-1, -1], # sin obstaculos
[0, -1], # obstaculo detectado a derecha
[0,1], # obstaculo a izq
[0,0], # obstaculo centro
[1,1], # demasiado cerca a derecha
[1,-1], # demasiado cerca a izq
[1,0] # demasiado cerca centro
])
# las salidas 'y' se corresponden con encender (o no) los motores
y = np.array([[1,0,0,1], # avanzar
[1,0,0,1], # avanzar
[1,0,0,1], # avanzar
[0,1,0,1], # giro derecha
[1,0,1,0], # giro izquierda (cambie izq y derecha)
[1,0,0,1], # avanzar
[0,1,1,0], # retroceder
[0,1,1,0], # retroceder
[0,1,1,0] # retroceder
])
nn.fit(X, y, learning_rate=0.03,epochs=40001)
def valNN(x):
return (int)(abs(round(x)))
index=0
for e in X:
prediccion = nn.predict(e)
print("X:",e,"esperado:",y[index],"obtenido:", valNN(prediccion[0]),valNN(prediccion[1]),valNN(prediccion[2]),valNN(prediccion[3]))
index=index+1
¿¿No es impresionante cómo con apenas 9 datos de entrada podemos enseñar a un robot a conducir??
El coche Arduino
En mi caso es un coche Arduino Elegoo Uno V3 de 4 motores. Si eres Maker, te resultará fácil construir el tuyo o puede que ya tengas uno en casa para programarlo. El coche puede ser cualquier otro, de hecho podría ser de 2 motores y modificando apenas la red funcionaría. En caso de querer construirlo tu mismo explicaré brevemente los requerimientos:
Necesitaremos:
Una placa Arduino Uno y una placa de expansión de IO
o puede ser una placa Arduino Mega
El controlador de motor L298N
4 motores DC (o podrían ser 2) y sus ruedas
Servo Motor SG90
Sensor Ultrasónico
Baterias para alimentar los motores (y la placa obviamente!)
Chasis para el coche
Cables!
Circuito del coche
No entraré en detalle, ya que va más allá de este tutorial (si a muchos lectores les interesa, podría ampliar el artículo en el futuro) pero básicamente tenemos el siguiente circuito (ignorar el bluetooth y los sensores infrarrojos):
Montar el coche
Utilizaremos un Servo en la parte delantera del coche que moverá al sensor de distancia de izquierda a derecha, a modo de radar, para detectar obstáculos.
Más allá de eso… es un coche! pondremos las 4 ruedas y las placas Arduino encima del chasis. (El objetivo de este artículo es enseñar a programar una red neuronal en la IDE de Arduino)
Una vez obtenida la red neuronal Python, haremos copiar y pegar de la matriz de pesos en el código Arduino (reemplazaremos las lineas 23 y 24):
Copiamos los pesos que obtenemos en la Jupyter Notebook de nuestra red neuronal en el código Arduino, reemplazando las variables por los nuevos valores.
El código Arduino
El código Arduino controlará el servo motor con el sensor de distancia que se moverá de izquierda a derecha y nos proveerá las entradas de la red: Distancia y Dirección(ó giro).
El resto, lo hará la red neuronal! En realidad, la red ya “aprendió” (en Python) es decir, sólo hará multiplicaciones y sumas de los pesos para obtener salidas. Realizará el camino forward propagation. Y las salidas controlarán directamente los 4 motores.
Hay código adicional para darle ciclos de tiempo a las ruedas a moverse (variable accionEnCurso) y dar más o menos potencia a los motores al cambiar de dirección. Son relativamente pocas líneas de código y logramos que la red neuronal conduzca el coche!
Nota: Para el movimiento del Servo motor se utiliza una librería “Servo” estándard.
Aquí vemos el código Arduino completo. Tal vez la parte más interesante sea la función conducir().
#include <Servo.h> //servo library
Servo myservo; // create servo object to control servo
int Echo = A4;
int Trig = A5;
#define ENA 5
#define ENB 6
#define IN1 7
#define IN2 8
#define IN3 9
#define IN4 11
/******************************************************************
Network Configuration
******************************************************************/
const int InputNodes = 3; // incluye neurona de BIAS
const int HiddenNodes = 4; //incluye neurona de BIAS
const int OutputNodes = 4;
int i, j;
double Accum;
double Hidden[HiddenNodes];
double Output[OutputNodes];
float HiddenWeights[3][4] = {{1.8991509504079183, -0.4769472541445052, -0.6483690220539764, -0.38609165249078925}, {-0.2818610915467527, 4.040695699457223, 3.2291858058243843, -2.894301104732614}, {0.3340650864625773, -1.4016114422346901, 1.3580053902963762, -0.981415976256285}};
float OutputWeights[4][4] = {{1.136072297461121, 1.54602394937381, 1.6194612259569254, 1.8819066696635067}, {-1.546966506764457, 1.3951930739494225, 0.19393826092602756, 0.30992504138547006}, {-0.7755982417649826, 0.9390808625728915, 2.0862510744685485, -1.1229484266101883}, {-1.2357090352280826, 0.8583930286034466, 0.724702079881947, 0.9762852709700459}};
/******************************************************************
End Network Configuration
******************************************************************/
void stop() {
digitalWrite(ENA, LOW); //Desactivamos los motores
digitalWrite(ENB, LOW); //Desactivamos los motores
Serial.println("Stop!");
}
//Medir distancia en Centimetros
int Distance_test() {
digitalWrite(Trig, LOW);
delayMicroseconds(2);
digitalWrite(Trig, HIGH);
delayMicroseconds(20);
digitalWrite(Trig, LOW);
float Fdistance = pulseIn(Echo, HIGH);
Fdistance= Fdistance / 58;
return (int)Fdistance;
}
void setup() {
myservo.attach(3); // attach servo on pin 3 to servo object
Serial.begin(9600);
pinMode(Echo, INPUT);
pinMode(Trig, OUTPUT);
pinMode(IN1, OUTPUT);
pinMode(IN2, OUTPUT);
pinMode(IN3, OUTPUT);
pinMode(IN4, OUTPUT);
pinMode(ENA, OUTPUT);
pinMode(ENB, OUTPUT);
stop();
myservo.write(90); //posicion inicial en el centro
delay(500);
}
unsigned long previousMillis = 0; // para medir ciclos de tiempo
const long interval = 25; // intervalos cada x milisegundos
int grados_servo = 90; // posicion del servo que mueve el sensor ultrasonico
bool clockwise = true; // sentido de giro del servo
const long ANGULO_MIN = 30;
const long ANGULO_MAX = 150;
double ditanciaMaxima = 50.0; // distancia de lejania desde la que empieza a actuar la NN
int incrementos = 9; // incrementos por ciclo de posicion del servo
int accionEnCurso = 1; // cantidad de ciclos ejecutando una accion
int multiplicador = 1000/interval; // multiplica la cant de ciclos para dar tiempo a que el coche pueda girar
const int SPEED = 100; // velocidad del coche de las 4 ruedas a la vez.
void loop() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
/******************************************************************
MANEJAR GIRO de SERVO
******************************************************************/
if(grados_servo<=ANGULO_MIN || grados_servo>=ANGULO_MAX){
clockwise=!clockwise; // cambio de sentido
grados_servo = constrain(grados_servo, ANGULO_MIN, ANGULO_MAX);
}
if(clockwise)
grados_servo=grados_servo+incrementos;
else
grados_servo=grados_servo-incrementos;
if(accionEnCurso>0){
accionEnCurso=accionEnCurso-1;
}else{
/******************************************************************
LLAMAMOS a la FUNCION DE CONDUCCION
******************************************************************/
conducir();
}
myservo.write(grados_servo);
}
}
//USA LA RED NEURONAL YA ENTRENADA
void conducir()
{
double TestInput[] = {0, 0,0};
double entrada1=0,entrada2=0;
/******************************************************************
OBTENER DISTANCIA DEL SENSOR
******************************************************************/
double distance = double(Distance_test());
distance= double(constrain(distance, 0.0, ditanciaMaxima));
entrada1= ((-2.0/ditanciaMaxima)*double(distance))+1.0; //uso una funcion lineal para obtener cercania
accionEnCurso = ((entrada1 +1) * multiplicador)+1; // si esta muy cerca del obstaculo, necestia mas tiempo de reaccion
/******************************************************************
OBTENER DIRECCION SEGUN ANGULO DEL SERVO
******************************************************************/
entrada2 = map(grados_servo, ANGULO_MIN, ANGULO_MAX, -100, 100);
entrada2 = double(constrain(entrada2, -100.00, 100.00));
/******************************************************************
LLAMAMOS A LA RED FEEDFORWARD CON LAS ENTRADAS
******************************************************************/
Serial.print("Entrada1:");
Serial.println(entrada1);
Serial.print("Entrada2:");
Serial.println(entrada2/100.0);
TestInput[0] = 1.0;//BIAS UNIT
TestInput[1] = entrada1;
TestInput[2] = entrada2/100.0;
InputToOutput(TestInput[0], TestInput[1], TestInput[2]); //INPUT to ANN to obtain OUTPUT
int out1 = round(abs(Output[0]));
int out2 = round(abs(Output[1]));
int out3 = round(abs(Output[2]));
int out4 = round(abs(Output[3]));
Serial.print("Salida1:");
Serial.println(out1);
Serial.print("Salida2:");
Serial.println(out2);
Serial.println(Output[1]);
Serial.print("Salida3:");
Serial.println(out3);
Serial.print("Salida4:");
Serial.println(out4);
/******************************************************************
IMPULSAR MOTORES CON LA SALIDA DE LA RED
******************************************************************/
int carSpeed = SPEED; //hacia adelante o atras
if((out1+out3)==2 || (out2+out4)==2){ // si es giro, necesita doble fuerza los motores
carSpeed = SPEED * 2;
}
analogWrite(ENA, carSpeed);
analogWrite(ENB, carSpeed);
digitalWrite(IN1, out1 * HIGH);
digitalWrite(IN2, out2 * HIGH);
digitalWrite(IN3, out3 * HIGH);
digitalWrite(IN4, out4 * HIGH);
}
void InputToOutput(double In1, double In2, double In3)
{
double TestInput[] = {0, 0,0};
TestInput[0] = In1;
TestInput[1] = In2;
TestInput[2] = In3;
/******************************************************************
Calcular las activaciones en las capas ocultas
******************************************************************/
for ( i = 0 ; i < HiddenNodes ; i++ ) {
Accum = 0;//HiddenWeights[InputNodes][i] ;
for ( j = 0 ; j < InputNodes ; j++ ) {
Accum += TestInput[j] * HiddenWeights[j][i] ;
}
//Hidden[i] = 1.0 / (1.0 + exp(-Accum)) ; //Sigmoid
Hidden[i] = tanh(Accum) ; //tanh
}
/******************************************************************
Calcular activacion y error en la capa de Salida
******************************************************************/
for ( i = 0 ; i < OutputNodes ; i++ ) {
Accum = 0;//OutputWeights[HiddenNodes][i];
for ( j = 0 ; j < HiddenNodes ; j++ ) {
Accum += Hidden[j] * OutputWeights[j][i] ;
}
Output[i] = tanh(Accum) ;//tanh
}
}
El Coche en Acción!
Conecta tu coche, sube el código y ¡pruébalo!
Veamos un video del coche funcionando con su propia inteligencia artificial en el planeta tierra.
Conclusión
Aplicamos Machine Learning y sus redes neuronales a un objeto del mundo real y vimos cómo funciona, haciendo que el coche evite obstáculos y tome las decisiones por sí mismo, sin haberle dado instrucciones ni código explícito.
Tengamos en cuenta que estamos teniendo como entradas los datos proporcionados por un sólo sensor de distancia y un servo motor que nos indica si está a izquierda o derecha. Podríamos tener más sensores de distancia, infrarrojos, medir velocidad, luz, sonido… en fin. Si tuviéramos que programar “manualmente” ese algoritmo, tendría una complejidad enorme y sería muy difícil de mantener o modificar. En cambio, hacer que una red neuronal aprenda sería muy sencillo. Tan sólo agregaríamos features (columnas) a nuestro código y volveríamos a entrenar nuevamente la red. Voila!. Copiar y pegar los pesos obtenidos en Arduino, y nuestro coche tendría la inteligencia de manejarse por sí mismo nuevamente.
Suscripción al Blog
Puedes suscribirte al Blog y ser el primero en recibir los artículos cada 15 días.
Si quieres comprar el mismo coche Arduino que yo, click aqui: ELEGOO UNO Proyecto Kit de Coche Robot Inteligente V3.0. Te recuerdo que puedes construir tu propio coche Arduino si te das maña y utilizar este código. De hecho puede funcionar con un coche de 2 motores modificando las salidas de la red, en vez de tener 4, tener 2.
]]>https://aprendemachinelearning.com/programa-un-coche-arduino-con-inteligencia-artificial/feed/765906Crear una Red Neuronal en Python desde cero
https://aprendemachinelearning.com/crear-una-red-neuronal-en-python-desde-cero/
https://aprendemachinelearning.com/crear-una-red-neuronal-en-python-desde-cero/#commentsThu, 26 Jul 2018 09:00:00 +0000https://www.aprendemachinelearning.com/?p=5905Programaremos una red neuronal artificial en Python, sin utilizar librerías de terceros. Entrenaremos el modelo y en pocas lineas el algoritmo podrá conducir por sí mismo un coche robot!. Para ello, explicaremos brevemente la arquitectura de la red neuronal, explicaremos el concepto Forward Propagation y a continuación el de Backpropagation donde ocurre “la magia” y […]
Para ello, explicaremos brevemente la arquitectura de la red neuronal, explicaremos el concepto Forward Propagation y a continuación el de Backpropagation donde ocurre “la magia” y [icon name=”angle-double-left” class=”” unprefixed_class=””]aprenden las neuronas[icon name=”angle-double-right” class=”” unprefixed_class=””].
Asumimos que se tienen conocimientos básicos de Redes Neuronales y de Python, les recomiendo repasar algunos conceptos en los artículos:
La velocidad del vehículo podría ser una salida más (por ejemplo disminuir la velocidad si nos aproximamos a un objeto) y podríamos usar más sensores como entradas pero por simplificar el modelo y su implementación mantendremos estas 2 entradas y 2 salidas.
Para entrenar la red tendremos las entradas y salidas que se ven en la tabla:
Entrada:
Sensor Distancia
Entrada:
Posición Obstáculo
Salida:
Giro
Salida:
Dirección
Acción de la Salida
0
0
0
1
Avanzar
0
1
0
1
Avanzar
0
-1
0
1
Avanzar
0.5
1
-1
1
Giro a la izquierda
0.5
-1
1
1
Giro a la derecha
0.5
0
0
1
Avanzar
1
1
0
-1
Retroceder
1
-1
0
-1
Retroceder
1
0
0
-1
Retroceder
-1
0
0
1
Avanzar
-1
-1
0
1
Avanzar
-1
1
0
1
Avanzar
Esta será la arquitectura de la red neuronal propuesta:
En la imagen anterior -y durante el ejemplo- usamos la siguiente notación en las neuronas:
X(i) son las entradas
a(i) activación en la capa 2
y(i) son las salidas
Y quedan implícitos, pero sin representación en la gráfica:
O(j) Los pesos de las conexiones entre neuronas será una matriz que mapea la capa j a la j+1
Recordemos que utilizamos 1 neurona extra en la capa 1 y una neurona extra en la capa 2 a modo de Bias -no están en la gráfica- para mejorar la precisión de la red neuronal, dandole mayor “libertad algebraica”.
Los cálculos para obtener los valores de activación serán:
a(1) = g(OT1X)
a(2) = g(OT2X)
a(3) = g(OT3X)
*Nota: la T indica matriz traspuesta, para poder hacer el producto
En las ecuaciones, la g es una función Sigmoide que refiere al caso especial de función logística y definida por la fórmula:
g(z) = 1/(1+e-z)
Funciones Sigmoide
Una de las razones para utilizar la función sigmoide –función Logística– es por sus propiedades matemáticas, en nuestro caso, sus derivadas. Cuando más adelante la red neuronal haga backpropagation para aprender y actualizar los pesos, haremos uso de su derivada. En esta función puede ser expresada como productos de f y 1-f . Entonces f'(t) = f(t)(1 – f(t)). Por ejemplo la función tangente y su derivada arco-tangente se utilizan normalizadas, donde su pendiente en el origen es 1 y cumplen las propiedades.
Imagen de la Curva Logística Normalizada de Wikipedia
Forward Propagation -ó red Feedforward-
Con Feedforward nos referimos al recorrido de “izquierda a derecha” que hace el algoritmo de la red, para calcular el valor de activación de las neuronas desde las entradas hasta obtener los valores de salida.
Si usamos notación matricial, las ecuaciones para obtener las salidas de la red serán:
X = [x0 x1 x2]
zlayer2 = O1X
alayer2 = g(zlayer2)
zlayer3 = O2alayer2
y = g(zlayer3)
Resumiendo: tenemos una red; tenemos 2 entradas, éstas se multiplican por los pesos de las conexiones y cada neurona en la capa oculta suma esos productos y les aplica la función de activación para “emitir” un resultado a la siguiente conexión (concepto conocido en biología como sinapsis química).
Como bien sabemos, los pesos iniciales se asignan con valores entre -1 y 1 de manera aleatoria. El desafío de este algoritmo, será que las neuronas aprendan por sí mismas a ajustar el valor de los pesos para obtener las salidas correctas.
Backpropagation (cómputo del gradiente)
Al hacer backpropagtion es donde el algoritmo itera para aprender! Esta vez iremos de “derecha a izquierda” en la red para mejorar la precisión de las predicciones. El algoritmo de backpropagation se divide en dos Fases: Propagar y Actualizar Pesos.
Fase 1: Propagar
Esta fase implica 2 pasos:
1.1 Hacer forward propagation de un patrón de entrenamiento (recordemos que es este es un algoritmo supervisado, y conocemos las salidas) para generar las activaciones de salida de la red.
1.2 Hacer backward propagation de las salidas (activación obtenida) por la red neuronal usando las salidas “y” reales para generar los Deltas (error) de todas las neuronas de salida y de las neuronas de la capa oculta.
Fase 2: Actualizar Pesos:
Para cada <<sinapsis>> de los pesos:
2.1 Multiplicar su delta de salida por su activación de entrada para obtener el gradiente del peso.
2.2 Substraer un porcentaje del gradiente de ese peso
El porcentaje que utilizaremos en el paso 2.2 tiene gran influencia en la velocidad y calidad del aprendizaje del algoritmo y es llamado “learning rate” ó tasa de aprendizaje. Si es una tasa muy grande, el algoritmo aprende más rápido pero tendremos mayor imprecisión en el resultado. Si es demasiado pequeño, tardará mucho y podría no finalizar nunca.
En esta gráfica vemos cómo utilizamos el gradiente paso a paso para descender y minimizar el coste total. Cada paso utilizará la Tasa de Aprendizaje -learning rate- que afectará la velocidad y calidad de la red.
Deberemos repetir las fases 1 y 2 hasta que la performance de la red neuronal sea satisfactoria.
Si denotamos al error en el layer “l” como d(l) para nuestras neuronas de salida en layer 3 la activación menos el valor actual será (usamos la forma vectorial):
d(3) = alayer3 – y
d(2) = OT2 d(3) . g'(zlayer2)
g'(zlayer2) = alayer2 . (1 – alayer2)
Al fin aparecieron las derivadas! Nótese que no tendremos delta para la capa 1, puesto que son los valores X de entrada y no tienen error asociado.
El valor del costo -que es lo que queremos minimizar- de nuestra red será
J = alayer dlayer + 1
Usamos este valor y lo multiplicamos al learning rate antes de ajustar los pesos. Esto nos asegura que buscamos el gradiente, iteración a iteración “apuntando” hacia el mínimo global.
El Código Completo de la red Neuronal con Backpropagation
Aquí va el código, recuerden que lo pueden ver y descargar al final del artículo o desde mi cuenta de GitHub.
Primero, declaramos la clase NeuralNetwork
import numpy as np
def sigmoid(x):
return 1.0/(1.0 + np.exp(-x))
def sigmoid_derivada(x):
return sigmoid(x)*(1.0-sigmoid(x))
def tanh(x):
return np.tanh(x)
def tanh_derivada(x):
return 1.0 - x**2
class NeuralNetwork:
def __init__(self, layers, activation='tanh'):
if activation == 'sigmoid':
self.activation = sigmoid
self.activation_prime = sigmoid_derivada
elif activation == 'tanh':
self.activation = tanh
self.activation_prime = tanh_derivada
# inicializo los pesos
self.weights = []
self.deltas = []
# capas = [2,3,2]
# rando de pesos varia entre (-1,1)
# asigno valores aleatorios a capa de entrada y capa oculta
for i in range(1, len(layers) - 1):
r = 2*np.random.random((layers[i-1] + 1, layers[i] + 1)) -1
self.weights.append(r)
# asigno aleatorios a capa de salida
r = 2*np.random.random( (layers[i] + 1, layers[i+1])) - 1
self.weights.append(r)
def fit(self, X, y, learning_rate=0.2, epochs=100000):
# Agrego columna de unos a las entradas X
# Con esto agregamos la unidad de Bias a la capa de entrada
ones = np.atleast_2d(np.ones(X.shape[0]))
X = np.concatenate((ones.T, X), axis=1)
for k in range(epochs):
i = np.random.randint(X.shape[0])
a = [X[i]]
for l in range(len(self.weights)):
dot_value = np.dot(a[l], self.weights[l])
activation = self.activation(dot_value)
a.append(activation)
# Calculo la diferencia en la capa de salida y el valor obtenido
error = y[i] - a[-1]
deltas = [error * self.activation_prime(a[-1])]
# Empezamos en el segundo layer hasta el ultimo
# (Una capa anterior a la de salida)
for l in range(len(a) - 2, 0, -1):
deltas.append(deltas[-1].dot(self.weights[l].T)*self.activation_prime(a[l]))
self.deltas.append(deltas)
# invertir
# [level3(output)->level2(hidden)] => [level2(hidden)->level3(output)]
deltas.reverse()
# backpropagation
# 1. Multiplcar los delta de salida con las activaciones de entrada
# para obtener el gradiente del peso.
# 2. actualizo el peso restandole un porcentaje del gradiente
for i in range(len(self.weights)):
layer = np.atleast_2d(a[i])
delta = np.atleast_2d(deltas[i])
self.weights[i] += learning_rate * layer.T.dot(delta)
if k % 10000 == 0: print('epochs:', k)
def predict(self, x):
ones = np.atleast_2d(np.ones(x.shape[0]))
a = np.concatenate((np.ones(1).T, np.array(x)), axis=0)
for l in range(0, len(self.weights)):
a = self.activation(np.dot(a, self.weights[l]))
return a
def print_weights(self):
print("LISTADO PESOS DE CONEXIONES")
for i in range(len(self.weights)):
print(self.weights[i])
def get_deltas(self):
return self.deltas
Y ahora creamos una red a nuestra medida, con 2 neuronas de entrada, 3 ocultas y 2 de salida. Deberemos ir ajustando los parámetros de entrenamiento learning rate y la cantidad de iteraciones “epochs” para obtener buenas predicciones.
# funcion Coche Evita obstáculos
nn = NeuralNetwork([2,3,2],activation ='tanh')
X = np.array([[0, 0], # sin obstaculos
[0, 1], # sin obstaculos
[0, -1], # sin obstaculos
[0.5, 1], # obstaculo detectado a derecha
[0.5,-1], # obstaculo a izq
[1,1], # demasiado cerca a derecha
[1,-1]]) # demasiado cerca a izq
y = np.array([[0,1], # avanzar
[0,1], # avanzar
[0,1], # avanzar
[-1,1], # giro izquierda
[1,1], # giro derecha
[0,-1], # retroceder
[0,-1]]) # retroceder
nn.fit(X, y, learning_rate=0.03,epochs=15001)
index=0
for e in X:
print("X:",e,"y:",y[index],"Network:",nn.predict(e))
index=index+1
La salidas obtenidas son: (comparar los valores “y” con los de “Network” )
Creamos una red neuronal en pocas líneas de código Python:
comprendimos cómo funciona una red neuronal “básica”,
el porqué de las funciones Sigmoides y sus derivadas que …
nos permiten hacer Backpropagation,
hallar el gradiente para minimizar el coste,
reducir el error iterando y obtener las salidas buscadas,
logrando que la red aprenda por sí misma en base a un conjunto de datos de entrada y sus salidas como “buen” Algoritmo Supervisado que es.
Nos queda como proyecto futuro aplicar esta red que construimos en el mundo real y comprobar si un coche Arduino será capaz de conducir por sí mismo y evitar obstáculos..! (en el próximo artículo lo veremos en acción!)
Suscripción al Blog
Como siempre, te invito a suscribirte al Blog y recibir los artículos cada 15 días.
Este artículo y todos los demás en el libro del blog . Aún está en borrador, pero apreciaré mucho tu colaboracón! Contiene Extras descargables como el “Lego Dataset” utilizado en el artículo de Detección de Objetos.