Práctica | Aprende Machine Learning https://ftp.aprendemachinelearning.com en Español Tue, 22 Oct 2024 20:42:22 +0000 en-US hourly 1 https://wordpress.org/?v=6.6.2 https://ftp.aprendemachinelearning.com/wp-content/uploads/2017/11/cropped-icon_red_neuronal-1-32x32.png Práctica | Aprende Machine Learning https://ftp.aprendemachinelearning.com 32 32 134671790 Prompt Engineering para Desarrolladores https://ftp.aprendemachinelearning.com/prompt-engineering-para-desarrolladores-python-llm/ https://ftp.aprendemachinelearning.com/prompt-engineering-para-desarrolladores-python-llm/#respond Tue, 02 Apr 2024 08:00:00 +0000 https://www.aprendemachinelearning.com/?p=8761 Aprende a escribir Prompts que funcionen y cómo obtener los mejores resultados de tu LLM en código Python.

The post Prompt Engineering para Desarrolladores first appeared on Aprende Machine Learning.

]]>
Utiliza el poder de los LLMs como parte de tus Aplicaciones

Ahora que ya cuentas con tu LLM en Local, como explicamos en el artículo “Instala un LLM en Local”, podemos encenderlo en modo Servidor y comenzar a jugar con él desde nuestro código python.

En este artículo usaremos una Jupyter Notebook que puedes ver y descargar desde GitHub y realizar las actividades de Prompt Engineering.

Vamos a comenzar explicando los conceptos más importantes a la hora de pedir tareas a un Gran Modelo del Lenguaje y veremos como iterar sobre diversos casos de uso para mejorar el resultado final. Por último plantearemos el código para crear un Chatbot que guíe al cliente en sus compras en un ecommerce.

Introducción

El término Prompt Engineer surgió cuando los primeros Grandes Modelos de Lenguaje cómo (GPT-2 en 2019, GPT-3 en 2020) comenzaban a aparecer y encerrar en su interior los misterios del lenguaje humano. Entonces hacer prompt Engineer trataba de “encontrar de forma artística” la mejor forma de obtener buenas respuestas de estos modelos. De hecho, la técnica muchas veces consistía en hackear al modelo, descubrir vulnerabilidades y fortalezas. De las diversas y a veces aleatorias fórmulas utilizadas por los usuarios de la comunidad, el Prompt Engineer gana fuerza como una tarea en sí misma (y no como un complemento) en donde el saber cómo realizar la petición al modelo tenía salidas precisas y concretas.

Los actuales grandes modelos (de 2024) tienen “billones” de parámetros y si bien tenemos algo más de comprensión sobre su comportamiento -sabemos que son modelos estadísticos- lo cierto es que aún no tenemos un mapa completo de cómo se comportan. Esto da lugar a que el Prompt Engineering (“cómo consultamos el LLM”) siga siendo una parte importante de nuestra tarea como científicos de datos o Ingenieros de datos.

Lo cierto es que ahora un LLM puede ser una pieza más del sistema, por lo que debemos poder fiarnos de que tendremos la respuesta apropiada (y en el formato buscado).

Modelo Fundacional vs Modelo de Instrucciones

Hagamos un mini repaso antes de empezar; hay dos tipos de LLMS, los “LLM Base” (fundacional) y los “LLM tuneados con Instrucciones” (en inglés Instruction Tuned LLM). Los primeros entrenados únicamente para predecir la siguiente palabra. Los tuneados en Instrucciones están entrenados sobre los Base; pero pueden seguir indicaciones, eso los vuelve mucho más útiles para poder llevar adelante una conversación. Además, al agregar el RLHF, es decir, un paso adicional luego de Tunearlos en donde mediante el feedback de personas humanas se mejora la redacción de respuestas penalizando o premiando al modelo. El RLHF también funciona como una capa de censura para ciertas palabras o frases no deseadas.

Estas LLMs que siguen instrucciones son ajustadas con el objetivo de ser “utiles, honestas e inofensivas” (en inglés Helpful, Honest, Harmless) intentan ser lo menos tóxicas posibles. De ahí la importancia de la limpieza del dataset inicial con el que fueron entrenadas las “LLM base”.

Ten esto en cuenta cuando descargues o elijas qué LLM utilizar. Para la mayoría de aplicaciones deberás seleccionar una version de LLM que sea de Instrucciones y no base. Por ejemplo para modelos Llama 2 encontrarás versiones “raw” o base, pero generalmente queremos utilizar las tuneadas en instrucciones. A veces se les denomina como “versión chat”.

Las dos reglas para lograr buenos Prompts

¿Qué es lo que tienes que hacer para lograr buenas respuestas con tu LLM?

Veamos los dos principios básicos:

  1. Escribir tareas claras y específicas.
  2. Darle al modelo “tiempo para pensar” (o reflexionar).

Requerimientos técnicos

Veamos como hacerlo con ejemplos en python; siguiendo una Jupyter Notebook.

Recuerda iniciar tu modelo en LM Studio, en mi caso, estoy utilizando laser-dolphin-mixtral en mi Mac.

Y tampoco olvides instalar el paquete de OpenAI ejecutando “pip install openai==1.13.3” en el ambiente de Python en el que te encuentres trabajando. Inicia en LM Studio el Servidor Local, para poder utilizar la API.

Empezamos con el Código!

Importamos las librerías que utilizaremos y creamos una función “get_completion” que nos facilitará la obtención del resultado del modelo. Como verás ponemos el valor de Temperatura a cero, eso nos proporciona menos “creatividad” por parte del modelo para poder reproducir resultados (de no hacerlo, podríamos obtener distintas respuestas cada vez que ejecutemos el mismo prompt). También notarás que definimos un rol de Sistema para lograr que el LLM nos responda siempre en español.

Recuerda: Si bien utilizamos el paquete de openai, estaremos haciendo las consultas a nuestro modelo local y gratuito

from openai import OpenAI

# Point to the local server
client = OpenAI(base_url="http://localhost:1234/v1", api_key="lm-studio")
modelo = "local-model" # si tienes la API de pago: gpt-4 , gpt-3.5, etc

def get_completion(prompt:str, model:str=modelo, temperature:float=0):
    messages = [{
            "role": "system",
            "content": "Eres un asistente en español y ayudas respondiendo con la mayor exactitud posible.",
        },
        {"role": "user", "content": prompt}]
    response = client.chat.completions.create(
        model=model,
        messages=messages,
        temperature=temperature
    )
    return response.choices[0].message.content

Volvamos al primer Principio “dar instrucciones claras y específicas”, esto mejorará las chances de que el modelo de respuestas erróneas o incorrectas. OJO, no confundir el dar respuestas cortas con “claras”. En la mayoría de casos, las respuestas largas proveen mejores explicaciones y detalle que respuestas cortas.

1. Prompts claros

Una buena ayuda para aclarar nuestros prompts es la de usar delimitadores como triple comillas o guiones. Veamos un ejemplo en donde en el prompt definimos un delimitador con el texto al que queremos que el LLM ponga atención:

text = f"""
La sonda Voyager 1 ha vuelto a dar señales de vida. Hace poco te contaba en un vídeo largo de mi canal que los ingenieros de la NASA \
habían perdido completamente la comunicación con la sonda Voyager 1, que lleva haciendo ciencia durante décadas. \
Tenemos buenas noticias y es que nos están llegando señales coherentes de esta sonda. El día 3 de este mes nos llegó una comunicación \
desde el espacio, que aun no siendo legible, tenía buena pinta, así que alguien de la NASA la decodificó y resulta que su contenido es importante. \
Se trata de un volcado completo del sistema de datos de vuelo, que recoge los datos de todos los Instrumentos y sensores que aún están \
funcionando, de las variables internas de la sonda y otros datos adicionales. \
Por supuesto esto ha dado esperanzas al equipo de la Voyager 1, que ahora se está planteando intentar recuperar la comunicación con \
la sonda de alguna forma.
"""

prompt = f"""
Debes resumir en muy pocas palabras el siguiente texto delimitado por triple comilla simple: ```{text}```
"""
response = get_completion(prompt)
print(response)
# SALIDA: un resumen del texto.

Otra táctica es solicitar al modelo salida estructurada. Por ejemplo salidas JSON o HTML.

prompt = f"""
Genera una lista con tres títulos inventados de libros sobre lo bueno que es volar y el nombre del autor.
Provee una salida en formato JSON con las siguientes claves: libro_id, titulo, autor, año.
"""
response = get_completion(prompt)
print(response)

La salida puede ser usada directamente como un diccionario python, esto puede ser muy útil para nuestras apps! Esta es la salida que obtuve:

{
  "libro_1": {
    "titulo": "Volar con alas de acero",
    "autor": "Juan Pérez",
    "año": 2023
  },
  "libro_2": {
    "titulo": "El secreto del vuelo",
    "autor": "María Gómez",
    "año": 2021
  },
  "libro_3": {
    "titulo": "Viajes en el cielo",
    "autor": "Carl<|im_start|> Mendoza",
    "año": 2022
  }
}

Los tiempos de Salida del modelo pueden variar, en mi caso en un ordenador sin GPU el tiempo de respuesta puede ser de 1 o dos minutos. Si cuentas con GPU el tiempo de respuesta podría ser de apenas segundos.

Si estás pensando en comprar una GPU… aquí te dejo algunos enlaces con precios de Amazon España:

La tercer táctica es pedir al modelo que revise si las condiciones son satisfactorias y si no se cumplen indicarlo con un mensaje explicito.

text_1 = f"""
Instrucciones para dar cuerda al reloj

Allá al fondo está la muerte, pero no tenga miedo. Sujete el reloj con una mano, tome con dos dedos la llave de la cuerda, remóntela suavemente. 
Ahora se abre otro plazo, los árboles despliegan sus hojas, las barcas corren regatas, el tiempo como un abanico se va llenando de sí mismo y de él brotan el aire, las brisas de la tierra, la sombra de una mujer, el perfume del pan.
¿Qué más quiere, qué más quiere? Atelo pronto a su muñeca, déjelo latir en libertad, imítelo anhelante. El miedo herrumbra las áncoras, cada cosa que pudo alcanzarse y fue olvidada va corroyendo las venas del reloj, gangrenando la fría sangre de sus rubíes.
Y allá en el fondo está la muerte si no corremos y llegamos antes y comprendemos que ya no importa.
"""

prompt = f"""
Te pasaré un texto delimitado por triple comillas. 
Si contiene una secuencia de instrucciones, re-escribe esas instrucciones siguiendo el siguiente formato:

Paso 1 - ...
Paso 2 - …
…
Paso N - …

Si el texto no contiene instrucciones, simplemente responde \"No hay instrucciones.\"

\"\"\"{text_1}\"\"\"
"""

response = get_completion(prompt)
print("Respuesta:")
print(response)

Tenemos como salida:

Respuesta:
 "Paso 1 - Sujete el reloj con una mano."
"Paso 2 - Tome con dos dedos la llave de la cuerda, remóntela suavemente."
"Paso 3 - Ahora se abre otro plazo, los árboles despliegan sus hojas, las barcas corren regatas, el tiempo como un abanico se va llenando de sí mismo y de él brotan el aire, las brisas de la tierra, la sombra de una mujer, el perfume del pan."
"Paso 4 - ¿Qué más quiere, qué más quiere? Atelo pronto a su muñeca, déjelo latir en libertad, imítelo anhelante."
"Paso 5 - El miedo herrumbra las áncoras, cada cosa que pudo alcanzarse y fue olvidada va corroyendo las venas del reloj, gangrenando la fría sangre de sus rubíes."
"Paso 6 - Y allá en el fondo está la muerte si no corremos y llegamos antes y comprendemos que ya no importa."

La cuarta técnica para prompts claros, será la de “Few shot prompting“. Esto consiste en proveer de ejemplos correctos del tipo de respuesta que buscamos antes de pedir una tarea.

prompt = f"""
Tu tarea es responder siguiendo el mismo estilo que ves a continuación.

<Juan>: Esa nube tiene forma de triángulo.

<Músico>: Me recuerda al disco de Pink Floyd.

<Juan>: Esa otra nube tiene forma de lengua.

<Músico>: Me recuerda al disco de los Rolling Stones.

<Juan>: Esa tiene forma de círculo.

<Músico>:"""
response = get_completion(prompt)
print(response)

Salida:

<Músico>: Ah, entonces me recuerda al disco de los Beatles.

2. Piensa antes de hablar…

El segundo principio es el de “Darle tiempo al modelo a pensar (reflexionar)

Si las respuestas del modelo son incorrectas ante una tarea compleja, puede ser debido a que se apresura en responder, eso a veces nos pasa a los humanos que respondemos “lo primero que se viene a la mente” y no siempre es correcto. Lo que podemos hacer es decirle al LLM que realice una “cadena de pensamiento” (Chain of thought) o que <<piense atentamente>> antes de responder.

La primera técnica para conseguirlo es darle una lista detallada de los pasos que debe seguir para responder.

text = "Juan era un niño aventurero, muy curioso y sobre todo soñador. Una tarde, después de haber estado jugando durante horas en el parque con sus amigos, \
    y después de haber cenado con Mamá María y Papá Jorge, Mamá decidió acompañar a Juan a su habitación, ya que era hora de ir a la cama. \
La habitación de Juan estaba decorada con pósteres de barcos y mapas antiguos, y su cama estaba rodeada de juguetes y libros de aventuras de piratas. \
    Mamá se acercó a Juan, le dio un fuerte abrazo y le deseó buenas noches. Al poco tiempo, Juan se quedó profundamente dormido."

prompt_2 = f"""
Tu tarea es realizar las siguientes acciones: 
1 - Resumir el texto delimitado con <> en una breve oración.
2 - Traducir el texto a Italiano.
3 - Listar los nombres de personas del texto.
4 - Crear un objecto JSON que contenga las claves: resumen_italiano, nombres.

Usa el siguiente formato:
Resumen: <resumen>
Traducción: <resumen traducido>
Nombres: <Lista de nombres encontrados>
Salida JSON: <Json con las claves resumen_italiano y nombres>

Texto: <{text}>
"""

response = get_completion(prompt_2)

print("\nRespuesta:")
print(response)

Salida (sólo copio la salida JSON, por ahorrar texto):

Respuesta:
Salida JSON: {"resumen_italiano": "Juan era un bambino avventuroso, molto curioso e soprattutto sognoioso. Dopo aver trascorso ore in giuoco nel parco con i suoi amici, dopo avere cenato con Mamma Maria e Papà Jorge, Mamma decise di accompagnare Juan alla sua camera, poiché era ora di andare a letto. La camera di Juan era decorata con poster di navi e mappe antichi, e la culla era circondata da giocattie e libri d'avventura pirata. Mamma si avvicò a Juan, gli diede un forte abrazo e le desiderò buone notti. Poco dopo, Juan si addormentò profondamente.", "nombres": ["Mamá María", "Papà Jorge", "Juan"]}

Técnica dos; Pedir al modelo que trabaje internamente la solución antes de apresurarse en dar una respuesta. Aquí le daremos un problema de matemáticas real, que tuvo mi hija de 10 años. Introduciré un error (a propósito) en la solución del estudiante, al usar el valor del IVA y el modelo deberá detectarlo y explicarnos la solución correcta.

prompt = f"""
Eres un profesor de matemáticas muy riguroso y tu tarea es determinar si el examen que hizo el estudiante es correcto o incorrecto.
Para resolver el problema deberás:
- Primero, trabaja en tu propia solución al problema y calcula el valor Total final. 
- Compara tu solución con la del estudiante y evalúa si la solución del estudiante es correcta o no. 
No decidas si el estudiante ha acertado o fallado hasta haber realizado las operaciones tu mismo.

Enunciado:
```
Al comprar un Televisor que valía $250 nos hacen una rebaja del 4%. 
Luego de rebajarlo tenemos que añadir el 20% de IVA.
¿Cuánto pagamos por el televisor?
``` 
Solución del estudiante:
```
1. Calculo el 4% de 250: (4 * 250) / 100 = (1000 / 100) = 10
2. Resto la rebaja: (250 - 10) = 240
3. Añado el 10% de IVA al valor rebajado: (10 * 240) / 100 = (2400/100) = 24
4. Sumo el Total: 240 + 24 = 264
Respuesta: pagamos $264 por el televisor.
```

Solución Real:
```
Desarrolla aqui tu solución con detalle. 
Determina si la solución del estudiante tiene fallo.
```
"""
response = get_completion(prompt)
print(response)

A mi personalmente me parece impresionante que el modelo pueda detectar el fallo!, veamos la salida:

Primero, calculamos el valor de rebaja: (250 * 4%) = 10. Luego, restamos la rebaja al precio original: 250 - 10 = 240. A continuación, añadimos el IVA del 20%: (240 * 20%) / 100 = 48. Finalmente, sumamos el valor de rebaja y el IVA al precio rebajado: 240 + 48 = 288.

La solución del estudiante tiene un error en la operación de cálculo del IVA. El IVA debería ser 240 * 20% / 100, lo cual da como resultado 48. Si se añade este valor al precio rebajado (240), se obtendría un total de $288.

La solución correcta es: "pagamos $288 por el televisor".

Advertencia, este modelo puede alucinar!

Tengamos en cuenta las alucinaciones! Este es un fenómeno que ¿sufren? los LLMS y hacen que se inventen datos, fechas y lugares de manera muy convincente. Hay que tener esto en cuenta al momento de obtener salidas, porque podríamos estar dandole a un cliente información falsa!.

Vemos un ejemplo en donde le pedimos al LLM que nos cuente sobre un producto que no existe, y sin embargo es capaz de darnos una descripción completa y convincente!!

prompt = "Cuentame como se usa el ultimo producto de Tubble y porque es tan valioso."
response = get_completion(prompt)
print(response)

Como verás, pedimos información sobre un producto que no existe y el Modelo es capaz de inventar una respuesta que parece real. Salida (acorto el texto, pero está completo en la Notebook):

El último producto de Tubble, llamado "Tubble-X", es un potente antioxidante que se utiliza en la industria farmacéutica y de cuidados personales. Tiene una gran cantidad de aplicaciones debido a sus propiedades antioxidantes, antiinflamatorias y anticancerígenas.

Una vez extraído, el Tubble-X se utiliza en una amplia gama de productos, desde medicamentos hasta cosméticos y alimentos. Algunos de los usos más comunes incluyen:

1. Medicinas: Se utiliza como ingrediente principal en la formulación de medicamentos para tratar diversas afecciones, como el cáncer, las enfermedades inflamatorias y las enfermedades cardíacas.
2. Cuidados personales: Se incorpora en productos cosméticos, como cremas y lociones, que ayudan a mantener la piel haya y sana.
3. Alimentación: ...

Segunda vuelta

Además de los dos principios, deberás saber que la tarea de Prompt Engineering es una tarea iterativa, es decir, empezamos con un prompt y hacemos la prueba. Seguramente no saldrá el mejor resultado a la primera por lo que debemos refinar nuestro prompt; hacer leves variaciones, cambiar ciertas palabras e ir viendo como responde el modelo.

Debemos analizar porqué no funciona el prompt y corregir. Por ejemplo si el prompt retorna una salida muy larga podemos probar de acotarla con “Escribe máximo 50 palabras”. Otra opción podría ser “utiliza como máximo 3 oraciones”.

Prompts para Resumir texto

Uno de los casos de uso comunes es el de usar LLM para resumir textos; podemos pedir que nos haga un resumen diario de las noticias del día o del Feed de Twitter o de nuestras redes sociales.

Vemos un ejemplo. Primero podemos pedirle que lo haga “en como máximo 30 palabras”.

prod_review = """
Se mantiene a unas temperaturas ridículas a 0.975-1v @ 2800mhz. Los tres ventiladores a 35-37% no se oyen en absoluto y es más que suficiente para refrigerarla.
El consumo a máximo rendimiento no pasa de 210W.
Tan solo aplícale un undervolt con la curvatura y listo.
Sin duda, 4070 super es lo mejor que Nvidia ha fabricado. Nunca había probado este ensamblador MSI ventus. De momento muy contento con la compra, puedo garantizar que es de altísima calidad.
Mi anterior 3070 gybabyte el núcleo a 80°c y las memorias a 100-105. I cluso cambíandole los thermalpads... En fin, estoy muy feliz con esta MSI 4070.
"""

prompt = f"""
Tu tarea es generar un breve resumen de una reseña de un producto de un ecommerce.

Resume la reseña que viene a continuación, delimitada por triple comilla, en máximo de 30 palabras.

Reseña: ```{prod_review}```
"""

response = get_completion(prompt)
print(response)

Salida:

"Muy potente en temperaturas bajas y consumo bajo a 2800mhz, ventiladores silenciosos y undervolt para mejorar. Nvidia 4070 de MSI es una excelente adquisición."

Podemos refinarlo pidiendo que se centre en algún aspecto en particular de la review.

prompt = f"""
Tu tarea es generar un breve resumen de una reseña de un producto de un ecommerce para dar Feedback al departamento de Ventas.

Resume la reseña que viene a continuación, delimitada por triple comilla, en máximo de 30 palabras y centrate en el consumo eléctrico.

Reseña: ```{prod_review}```
"""

response = get_completion(prompt)
print(response)

Salida:

La tarjeta 4070 de MSI mantiene la temperatura a niveles razonables y el consumo máximo es de 210W. El usuario está muy contento con la compra, ya que su anterior 3070 tenía problemas de temperatura.

Otra opción es en vez de pedirle de “resumir” decirle que “extraiga” el contenido. Pruébalo!

Tareas de NLP con tu LLM

Ahora veremos de usar al LLM para que realice tareas que antes realizábamos con modelos específicos de NLP. Es fabuloso que el LLM pueda resolver estas tareas como si “estuviera razonando” y entendiendo realmente el lenguaje Natural!

Veremos ejemplos en los que usamos al LLM para obtener el análisis de sentimiento, detección de Entidades y clasificación de textos/tópicos.

Análisis de Sentimiento

Primero, le pedimos análisis de sentimiento:

monitor_review = """
El monitor LG 29WP500-B UltraWide impresiona con su relación de aspecto 21:9 y su panel IPS de alta resolución (2560x1080). 
El uso de FreeSync marca una clara diferencia en términos de compatibilidad con tarjetas gráficas y garantiza una visualización fluida. 
En comparación con mi antiguo monitor Eizo, el monitor LG ofrece una fidelidad de color superior con una cobertura sRGB de más del 99%. 
Con dos puertos HDMI y la posibilidad de inclinarlo, también es avanzado en términos de conectividad y facilidad de uso. 
Este monitor es una opción de actualización que merece la pena para disfrutar de una experiencia visual impresionante.
"""

prompt = f"""
Cuál es el sentimiento de la reseña hecha al producto
que viene a continuación, delimitada por triple comilla?

Reseña: '''{monitor_review}'''
"""
response = get_completion(prompt)
print(response)
# SALIDA: El sentimiento de la reseña es positivo.

También podemos intentar detectar emociones, intentar entender si un cliente está enojado nos puede servir por si debemos actuar u ofrecerle un cupón de descuento antes de que abandone su suscripción…

prompt = f"""
Contesta si el escritor de la siguiente reseña está expresando ira, furia o enojo.
La reseña está delimitada por triple comillas.
Da tu respuesta en una sóla palabra como "si" o "no".

Reseña: '''{monitor_review}'''
"""
response = get_completion(prompt)
print(response)

Extracción de entidades

prompt = f"""
Identifica los siguientes elementos del texto de la reseña:
- Producto comprado por el autor
- Compañía del producto

La reseña está delimitada por triple comillas.
La respuesta deberá ser en un objeto JSON con "Item" y "Marca" como claves. 
Si no se encuentra la información, usa "desconocido" como valor.
Haz tu respuesta lo más corta posible.
  
Reseña: '''{monitor_review}'''
"""
response = get_completion(prompt)
print(response)
#SALIDA:  { "Item": "Monitor LG 29WP500-B", "Marca": "LG" }

¿De qué trata el texto?

Podemos hacer que el LLM detecte temas, es decir, sobre qué trata un texto. Podemos pedirle una lista libre (a su criterio) o acotarlo a una lista de temas que le damos nosotros. Veamos un ejemplo utilizando un extracto de texto de la lista de correos sobre IA de Unai Martinez:

story = """
Una vez hice un garabato de muchos círculos y a lápiz en la mesa de al lado de mi compañero de pupitre. No sé si recuerdas esas mesas verdes que habitaban nuestras aulas de manera continuada año tras año.
He de decir que este año cumpliré 50 tacos, es decir, te estoy hablando del año 1984 aproximadamente, tenía 10 años.

Ni siquiera sé por qué lo hice. Se me ocurrió y le planté un garabato enorme sin que él se diese cuenta.
Se ve que aquel día mi cabeza andaba sola en formato ameba y no se me ocurrió otra cosa que aportar a la humanidad.
Yo era un crío formal, he de decirlo.

Tuve la mala suerte de que el profe vio el tachón en la mesa al rato y dijo todo serio que si nadie asumía la culpa el finde no nos íbamos de excursión.
Joder que mala suerte, era finde de excursión. Eran las míticas excursiones que te ponías nervioso.
Ya sabes, camping, colegueo, cotilleo del pelo que si a fulanito le gusta menganita pero no te chives … y a los 5 minutos ya lo sabía toda la clase.
Estas son las cosas que todos recordamos.
 
Bien, el asunto es que en ese momento no dije nada. Callado como un muerto.
Me fui a casa a comer y a meditar.
Por la tarde, a la vuelta, tenía que darle solución.
 
Realmente no había opción, lo tenía que confesar. Tenía que enfrentarme a mis miedos y asumir mi culpa.
He de decir, que en ningún momento se me pasó por la cabeza fallar a mis compañeros. La idea de que por mi culpa se quedaran sin excursión no asomó en ningún momento como posible opción.
Tampoco contemplé la posibilidad de que el profe fuese de farol. Probablemente fuera así, pero en aquél entonces tenía las preocupaciones de un niño de 10 años.
"""
# Extracto del texto "Lecciones de negocio de un crío de 10 años" de Unai Martinez

prompt = f"""
Determina cinco tópicos que se comentan en el siguiente texto delimitado por triple comillas.

Cada tema será definido por una o dos palabras.

El formato de tu respuesta debe ser una lista separada por comas.

Texto: '''{story}'''
"""
response = get_completion(prompt)
print(response)
# SALIDA: "Garabato", "Mesas verdes", "Años 80", "Excursiones" y "Confesar".

Tienes algunos ejemplos y usos adicionales en la Notebook!

LLM para traducción y corrección de textos

Los grandes modelos están entrenados con Millones y Millones de textos sacados de internet en distintos idiomas; por lo cual, sin haberlo hecho con ese propósito, el modelo aprendió a entender y traducir entre una variedad de lenguas.

Veamos ejemplos de traducción:

prompt = f"""
Translate the following English text to Spanish: \ 
```Hi, I would like to order a coffee```
"""
response = get_completion(prompt)
print(response)
#SALIDA: "Hola, me gustaría pedir un café"

También podemos pedir que detecte un idioma:

prompt = f"""
Dime en que idioma esta el texto: 
```Combien coûte le lampadaire?```
"""
response = get_completion(prompt)
print(response)
# SALIDA: Este texto está en francés (idioma de Francia)

Corrección de Textos

En este ejemplo vamos a pasarle un listado de oraciones con error y probar si el modelo es capaz de detecta errores de sintaxis o gramática. Si eres un profesor, esto podría ser útil para corregir textos antes de procesarlos (o limpieza de datos en un pipeline de ML).

text = [
  "La niña esta jugando con la perro.",  # error
  "Juan tiene un ordenador.", # ok
  "Ba a acer un largo día.",  # error
  "Hayamos sido tan felices juntos.",  # error
  "El coche es rojo.", # ok
  "Vamos a porbar si hay error de deletreo."  # error deletreo
]
for t in text:
    prompt = f"""
    Corrige ls siguiente oración delimitada por triple comilla simple y reescribe la versión revisada sin error. Si no encuentras error, escribe: "No hay error".
    Oración: ```{t}```"""
    response = get_completion(prompt)
    print(f"Respuesta para '{t}':")
    print(response)
    print("- " * 10)

Tengamos en cuenta que el modelo también puede fallar y no siempre detectar los errores. Puedes ver las salidas en la Notebook.

Crea un Chatbot para ecommerce con tu LLM

En nuestro último ejercicio vamos a crear un chatbot para que conteste a los clientes de una empresa ya existente. Será un ejemplo básico, pero verás lo potente que resulta. Sobre todo, no tener que programar paso a paso todo el chatbot, si no, que es simplemente darle las instrucciones en lenguaje natural al LLM.

Roles y memoria!

Antes de empezar debemos saber sobre la memoria de un LLM y sobre los roles.

Sobre los roles, tenemos 3 roles principales que gestionamos cuando enviamos los prompts al modelo:

  • System: se define 1 solo mensaje de sistema. Este mensaje define “quién es el LLM”, su identidad y cómo debe contestar.
  • User: aquí veremos TODOS los mensajes que va escribiendo el usuario que utiliza el LLM y sus consultas
  • Assistant: serán las respuestas que provee el LLM a las peticiones del usuario.

El Modelo de lenguaje no tiene memoria, esto quiere decir que si le pedimos algo al modelo, debemos tener en cuenta que el modelo no recuerda las respuesta anteriores. Esto es un inconveniente porque para poder tener una conversación resultará muy útil que el modelo recuerde todo lo que se habló previamente. Si en el primer mensaje le decimos al Asistente nuestro nombre y luego le preguntamos “¿Quién soy?” el modelo no tendrá ni idea!

Para resolver esto, es que debemos almacenar el historial de mensajes e ir enviándolo al modelo constantemente.

Para nuestro ejemplo, modificamos levemente la función de “get_completion()” y esta vez enviaremos el historial completo como una lista de mensajes python.

Para nuestro ejemplo, mantendremos todos los mensajes! Así que cuidado… si la conversación se extiende podrías llenar tu memoria… pero en próximos artículos del blog veremos estrategias para limitar la memoria del chat.

def get_completion_from_messages(messages, model=modelo, temperature=0):
    response = client.chat.completions.create(
        model=model,
        messages=messages,
        temperature=temperature,
    )
    msg = response.choices[0].message
    return msg.content

Antes de comenzar con el bot, hagamos un ejemplo enviando mensajes y limitando el historial para comprobar que el LLM no recuerda…

messages =  [
    {'role':'system', 'content':'Eres un robot amistoso. Responde brevemente lo que se te pide.'},
    {'role':'user', 'content':'Hola, mi nombre es Juan.'}  
    ]
response = get_completion_from_messages(messages, temperature=1)
print(response)

Aquí comprobaremos que en su salida no sabe nuestro nombre:

messages =  [
    {'role':'system', 'content':'Eres un robot amistoso. Responde brevemente lo que se te pide.'},
    {'role':'user', 'content':'Sí, ¿puedes decir cúal es mi nombre?'}  
    ]
response = get_completion_from_messages(messages, temperature=1)
print(response)

Sin embargo, si le pasamos el historial completo, el modelo puede recordar que mi nombre es Juan 🙂

messages =  [
    {'role':'system', 'content':'Eres un robot amistoso. Responde brevemente lo que se te pide.'},
    {'role':'user', 'content':'Hola, mi nombre es Juan.'},
    {'role':'assistant', 'content': "¡Hola, Juan! Me encantaría saber qué tipo de información puedo proporcionarme para ayudarte? ¿Puedes darme más detalles sobre tu consulta?"},
    {'role':'user', 'content':'Sí, ¿puedes decir cúal es mi nombre?'}
    ]
response = get_completion_from_messages(messages, temperature=1)
print(response)

Bien, ya sabemos cómo debemos hacer para que el chatbot conserve nuestro historial y pueda seguir una conversación.

Un Bot que sirve Pizzas

Vamos a crear un listado con todos los mensajes que se van produciendo y los vamos a incorporar al chat. En el primer mensaje de sistema, le indicaremos al LLM quién es, la información con la que cuenta, en este caso serán las pizzas y productos que ofrece y sus precios.

context = [ {'role':'system', 'content':"""
Eres PedidosBot, un servicio automatizado que recoge pedidos de pizza de un restaurante.
Primero saludas al cliente, luego recibes su orden
Y luego preguntas si el pedido es para enviar a domicilio o si lo vienen a buscar.
Esperas a tener la orden completa, luego haces un resumen y calculas el precio total final.
Si es envío a domicilio deberás pedir al cliente su dirección.
Responde siendo breve y amistoso pero formal.
El menú incluye:
Pizza pepperoni  12.95 \
Pizza de Jamón y Queso   10.95 \
Pizza con huevo   11.95 \
Patatas fritas 4.50 \
Ensalada griega 7.25 \
Extras: \
extra de Queso 2.00, \
Setas 1.50 \
Bebidas: \
Zumo de Naranja 3.00 \
sprite 3.00 \
Agua 5.00 \
"""} ]

# Creamos un loop de consultas al bot, para salir escribe "salir" o un mensaje vacío.
# NOTA: Si estás en VSCODE la caja de texto aparece en la parte de arriba de la pantalla.
texto_usuario = "inicio"
context.append({'role':'assistant', 'content': "Bienvenido al PizaBot Service, ¿En que puedo ayudarle?"})

while texto_usuario != "salir" or texto_usuario == "":
    texto_usuario = input("CHAT: ")
    if texto_usuario == "salir" or texto_usuario == "":
        continue

    print(texto_usuario)
    context.append(
        {'role':'user', 'content':texto_usuario}
    )

    response = get_completion_from_messages(context, temperature=1)
    print(response)

    context.append(
        {'role':'assistant', 'content': response})

print("FIN DE CHAT")

Con este código de muy pocas líneas podemos hacer que el bot trabaje para nosotros!; aquí copio la salida que obtuve:

Qué Pizzas tienen?
 El menú de pizzas incluye las siguientes opciones:
1. Pizza pepperoni - 12.95
2. Pizza de Jamón y Queso - 10.95
3. Pizza con huevo - 11.95
Por favor, indique el número de la pizza que desea para más detalles.

Quiero una pizza de Jamón y Queso. ¿Tienen ensaladas?
 Además de las pizzas mencionadas, también tenemos ensaladitas! 
1. Ensalada mixta - 7.25
2. Ensalada Griega - 7.25
Puedes ordenar ambas. ¿Desea algo más?

Si, quiero agregar una Ensalada Griega.
 Ideal! ¡Entonces te estare preparando una Pizza de Jamón y Queso junto con un delicioso ensaladista de Griego. ¿Deseas algo más?

Nada más. Cúanto es el precio final?
 ¡Así! Así es la cuenta: Pizza de Jamón y Queso (10.95) + Ensaladilla Griega (7.25) = $18.20. ¡Vaya a ser un alimento delicioso!


FIN DE CHAT

Conclusiones

En este artículo podemos aprender a usar el LLM como una pieza de nuestras aplicaciones. Podemos contar con él como un motor de respuestas generales, o específicas para el caso de uso que abordemos.

Además si aprovechamos la capacidad de que responda en formatos como JSON, XML o HTML, podemos incorporar sus salidas para alimentar otras APIs, u otras funciones de nuestro código Python.

Los LLMs son muy potentes y resulta muy cómodo poder utilizar uno en Local y hacer este tipo de pruebas!

Espero que el artículo te resulte útil, cuentas con el código completo en la Jupyter notebook dentro de mi cuenta de Github.

En el próximo artículo veremos más ejemplos de uso del LLM y nuevas librerías python que surgieron para facilitarnos la vida!

Hasta la próxima.

Suscripción al Blog

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

NOTA: algunos usuarios reportaron que el email de confirmación y/o posteriores a la suscripción entraron en su carpeta de SPAM. Te sugiero que revises los “correos no deseados” y/o que agendes la dirección de remitente en tus contactos.

The post Prompt Engineering para Desarrolladores first appeared on Aprende Machine Learning.

]]>
https://ftp.aprendemachinelearning.com/prompt-engineering-para-desarrolladores-python-llm/feed/ 0 8761
Seguimiento de Objetos con Yolo v8 y BYTETrack – Object Tracking https://ftp.aprendemachinelearning.com/seguimiento-de-objetos-con-yolo-v8-y-bytetrack-object-tracking/ https://ftp.aprendemachinelearning.com/seguimiento-de-objetos-con-yolo-v8-y-bytetrack-object-tracking/#comments Fri, 08 Sep 2023 11:15:20 +0000 https://www.aprendemachinelearning.com/?p=8271 Realiza un proyecto python de 100 líneas para detectar y seguir personas en video. Usaremos Yolo v8 y Bytetrack.

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

]]>

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

La Problemática del rastreo de objetos

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

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

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

Algoritmos de Seguimiento:

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

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

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

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

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

Casos de Uso

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

Seguimiento de personas / objeto de interés

Fuente de la imágen: artículo

Contabilizar vehículos (u objetos)

Entrada en una zona determinada

Trazado de rutas

¿Cómo funciona ByteTrack para seguimiento de Objetos?

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

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

Comprendamos el algoritmo paso a paso:

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

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

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

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

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

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

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

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

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

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

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

Comentario sobre Kalman Filter

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

¿Estado del arte?

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

Ejercicio: seguimiento de Skaters

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

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

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

Crear el Environment

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

Crea un nuevo ambiente Python utilizando Anaconda ejecutando:

conda create -n tracking python=3.9 numpy

Activa el ambiente

conda activate tracking

Instala ahora los paquetes con las versiones necesarias mediante pip

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

Código python

Primero importamos los paquetes que utilizaremos

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

from bytetrack.byte_tracker import BYTETracker

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

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

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

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

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

Instanciamos el algoritmo de Detección:

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

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

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

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

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

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

    result = detection_model(im)

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

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

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

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

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

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

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

    vid_writer.write(im0)  # guardar frame en video

vid_writer.release()
cv2.destroyAllWindows()

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

Video de salida ejemplo

Conclusión

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

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

Nos vemos en la próxima!

Recursos / Enlaces

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

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

]]>
https://ftp.aprendemachinelearning.com/seguimiento-de-objetos-con-yolo-v8-y-bytetrack-object-tracking/feed/ 3 8271
Generación de Texto en Español con GPT-2 https://ftp.aprendemachinelearning.com/generacion-de-texto-en-espanol-con-gpt-2/ https://ftp.aprendemachinelearning.com/generacion-de-texto-en-espanol-con-gpt-2/#comments Tue, 13 Dec 2022 09:00:00 +0000 https://www.aprendemachinelearning.com/?p=7869 Crearemos nuestra propia IA de generación de texto basada en los diálogos y entrevistas de Ibai Llanos publicados en Youtube. Usaremos un modelo pre-entrenado GPT-2 en castellano disponible desde HuggingFace y haremos el fine-tuning con Pytorch para que aprenda el estilo de escritura deseado.

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

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

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

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

¿Qué son los modelos GPT?

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

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

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

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

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

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

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

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

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

 Entre sus características:

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

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

Zero shot Learning

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

¿Por qué usar GPT-2?

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

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

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

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

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

¿Qué tiene que ver HuggingFace en todo esto?

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

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

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

Modelo pre-entrenado en Español

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

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

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

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

El proyecto Python: “Tu propio bot influencer”

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

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

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

El Dataset educacional: Diálogos de Ibai

Banner del Canal de Ibai en Youtube 2022

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

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

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

Creación del entorno Python con Anaconda

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

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

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

Ejecuta las siguientes líneas en tu terminal:

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

Importamos las librerías

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

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

Uso de CPU ó GPU

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

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

Cargamos el Modelo de HuggingFace

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

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

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

control_code = "ibai"

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

Cargamos el Dataset “Ibai_textos.txt”

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

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

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

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

Entrenamos haciendo el Fine-Tuning

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

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

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

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

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

Guardar el modelo, para uso futuro

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

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

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

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

Crear Texto al estilo influencer

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

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

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

Y obtenemos como salidas:

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

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

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

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

Resumen

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

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

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

Nos vemos en el próximo post!

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

Otros Enlaces de interés

Suscripción al Blog

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

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

El libro del Blog

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

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

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

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

]]>

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

Nuestra Agenda

Los temas que veremos incluyen:

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

Comencemos!!

¿Qué es el Aprendizaje por Refuerzo?

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

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

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

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

Diferencias con “los clásicos”

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

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

Componentes del RL

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

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

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

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

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

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

Casos de Uso del Aprendizaje por Refuerzo

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

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

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

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

Y los Videojuegos? que pintan en todo esto?

Imagen del DeepMind en acción

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

Entonces, ¿Cómo funciona el RL?

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

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

Premios y Castigos

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

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

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

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

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

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

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

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

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

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

Q-Learning, el algoritmo más usado

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

Repasemos los elementos que tenemos:

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

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

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

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

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

Ecuación de Bellman

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

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

Ejercicio Python de RL: Pong con Matplotlib

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

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

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

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

  1. mover hacia arriba
  2. mover hacia abajo

Y las reglas del juego:

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

Agreguemos los imports que usaremos:

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

%matplotlib inline

La clase Agente

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

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

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

Los métodos más importantes:

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

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

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

        return next_step

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

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

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

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

La clase Environment

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

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

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

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

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

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

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

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

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

        self.avanza_frame()

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

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

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

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

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

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

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

El juego: Simular miles de veces para enseñar

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

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

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

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

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

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

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

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

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

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

Veamos el resultado!

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

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

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

Y veremos nuestro juego de Pong en acción!

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

La Tabla de políticas resultante

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

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

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

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

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

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

Conclusiones

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

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

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

Deseos Finales

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

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

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

Material del Artículo

Descarga la notebook completa desde GitHub aqui

Recursos Adicionales

Otros artículos relacionados:

Suscripción al Blog

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

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

El libro del Blog

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

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

]]>
https://ftp.aprendemachinelearning.com/aprendizaje-por-refuerzo/feed/ 1 5911
Detección de Objetos con Python https://ftp.aprendemachinelearning.com/deteccion-de-objetos-con-python-yolo-keras-tutorial/ https://ftp.aprendemachinelearning.com/deteccion-de-objetos-con-python-yolo-keras-tutorial/#comments Wed, 24 Jun 2020 09:16:50 +0000 https://www.aprendemachinelearning.com/?p=7262 Crea tu propia red neuronal convolucional para detección de objetos lego con este simpático tutorial paso a paso

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

]]>
En este artículo podrás ver de manera práctica cómo crear tu propio detector de objetos que podrás utilizar con imagenes estáticas, video o cámara. Avanzaremos paso a paso en una Jupyter Notebook con el código completo usando redes neuronales profundas con Keras sobre Tensorflow.

Antes de empezar te recomiendo que leas mis artículos anteriores sobre Visión Artificial, que te ayudarán con las bases teóricas sobre las que nos apoyamos en este ejercicio:

Agenda

Tenemos mucho por delante! Antes que nada debo aclarar que próximamente un nuevo artículo explicará toda la teoría que hoy aplicaremos, pero mientras llega… pasemos a la acción!

  • ¿En qué consiste la Detección Yolo?
    • Algunos parámetros de la red
    • El proyecto propuesto
  • Lo que tienes que instalar (y todo el material)
  • Crear un dataset: Imágenes y Anotaciones
    • Recomendaciones para la imágenes
    • Anotarlo todo
    • El lego dataset
  • El código Python
    • Leer el dataset
    • Train y Validación
    • Data Augmentation
    • Crear la red YOLO
    • Crear la red de Detección
    • Generar las Anclas
    • Entrenar
    • Revisar los Resultados
    • Probar la red!
  • Conclusiones
  • Material Adicional

¿En qué consiste la detección YOLO?

Vamos a hacer un detector de objetos en imágenes utilizando YOLO, un tipo de técnica muy novedosa (2016), acrónimo de “You Only Look Once” y que es la más rápida del momento, permitiendo su uso en video en tiempo real.

Esta técnica utiliza un tipo de red Neuronal Convolucional llamada Darknet para la clasificacion de imágenes y le añade la parte de la detección, es decir un “cuadradito” con las posiciones x e y, alto y ancho del objeto encontrado.

La dificultad de esta tarea es enorme: poder localizar las áreas de las imágenes, que para una red neuronal es tan sólo una matriz de pixeles de colores, posicionar múltiples objetos y clasificarlos. YOLO lo hace todo “de una sola pasada” a su red convolucional. En resultados sobre el famoso COCO Dataset clasifica y detecta 80 clases de objetos distintos y etiquetar y posicionar hasta 1000 objetos (en 1 imagen!)

NOTA PARA los Haters del ML (si es que los hay): Este código se basa en varios trozos de código de diversos repos de Github y estaré usando una arquitectura de YOLOv2 aunque sé que es mejor la versión 3 (y de hecho está por salir Yolo v4)… pero recuerden que este artículo es con fines didácticos. No me odies y sé comprensivo, toma tu pastilla todas las noches, gracias.

Aunque ahondaré en la Teoría en un próximo artículo, aquí comentaré varios parámetros que manejaremos con esta red y que debemos configurar.

(Algunos) Parámetros de la red

  • Tamaño de imagen que procesa la red: este será fijo, pues encaja con el resto de la red y es de 416 pixeles. Todas las imágenes que le pasemos serán redimensionadas antes de entrar en la red.
  • Cantidad de cajas por imagen: Estás serán la cantidad de objetos máximos que queremos detectar.
  • etiquetas: estas serán las de los objetos que queramos detectar. En este ejemplo sólo detectaremos 1 tipo de objeto, pero podrían ser múltiples.
  • epochs: la cantidad de iteraciones sobre TODO el dataset que realizará la red neuronal para entrenar. (Recuerda, que a muchas épocas tardará más tiempo y también el riesgo de overfitting)
  • train_times: este valor se refiera a la cantidad de veces de entrenar una MISMA imagen. Esto sirve sobre todo en datasets pequeños, además que haremos algo de data augmentation sobre las imágenes cada vez.
  • saved_weights_name: una vez entrenada la red, guardaremos sus pesos en este archivo y lo usaremos para hacer las predicciones.

El proyecto Propuesto: Detectar personajes de Lego

Será porque soy padre, ó será porque soy Ingeniero… al momento de pensar en un objeto para detectar se me ocurrió: Legos! ¿Quien no tiene legos en su casa?… Por supuesto que puedes crear tu propio dataset de imagenes y anotaciones xml para detectar el ó los objetos que tu quieras.

Lo que tienes que instalar

Primero que nada te recomiendo que crees un nuevo Environment de Python 3.6.+ e instales estas versiones de librerías que usaremos.

En consola escribe:

python -m venv detectaEnv

Y luego lo ACTIVAS para usarlo en windows con:

detectaEnv\Scripts\activate.bat

ó en Linux / Mac con:

source detectaEnv/bin/activate

y luego instala los paquetes:

pip install tensorflow==1.13.2
pip install keras==2.0.8
pip install imgaug==0.2.5
pip install opencv-python
pip install h5py
pip install tqdm
pip install imutils

Aclaraciones: usamos una versión antigua de Tensorflow. Si tienes GPU en tu máquina, puedes usar la versión apropiada de Tensorflow (y CUDA) para aprovecharlo.

Si vas a crear tu propio dataset -como se explica a continuación-, deberás instalar LabelImg, que requiere:

pip install PyQt5
pip install lxml
pip install labelImg

Si no, puedes usar el dataset de legos que provee el blog y saltarte la parte de crear el dataset.

Otros archivos que deberás descargar:

Crea un dataset: Imágenes y Anotaciones

Vale, pues es hora de crear un repositorio de miles de imágenes para alimentar tu red de detección.

En principio te recomendaría que tengas al menos unas 1000 imágenes de cada clase que quieras detectar. Y de cada imagen deberás tener un archivo xml con un formato específico -que en breve comentaré- con la clase y la posición de cada objeto. Al detectar imágenes podemos tener más de un objeto, entonces puedes tener imágenes que tienen a más de un objeto.

Recomendaciones para las imágenes:

Algunas recomendaciones para la captura de imágenes: si vas a utilizar la cámara de tu móvil, puede que convenga que hagas fotos con “pocos megapixeles”, pues si haces una imagen de 4K de 5 Megas, luego la red neuronal la reducirá a 416 pixeles de ancho, por lo que tendrás un coste adicional de ese preprocesado en tiempo, memoria y CPU.

Intenta tener fotos del/los objetos con distintas condiciones de luz, es decir, no tengas imágenes de gatitos “siempre al sol”. Mejor serán imágenes de interior, al aire libre, con poca luz, etc.

Intenta tener imágenes “torcidas”(rotadas), parciales y de distintos tamaños del objeto. Si sólo tienes imágenes en donde tu objeto supongamos que “mide 100 pixeles” mal-acostumbrarás la red y sólo detectará en imágenes cuando sea de esas dimensiones (peligro de overfitting).

Variaciones del mismo objeto: Si tu objeto es un gato, intenta clasificar gatos de distintos colores, razas y en distintas posiciones, para que la red convolucional pueda generalizar el conocimiento.

Anotarlo todo

Muy bien, ya tienes tus imágenes hechas y guardadas en un directorio.

Ahora deberás crear un archivo XML donde anotarás cada objeto, sus posiciones x,y su alto y ancho.

El xml será de este tipo:

Y lo puedes hacer a mano… ó puedes usar un editor como labelImg.

Si lo instalaste mediante Pip, puedes ejecutarlo simplemente poniendo en línea de comandos del environment labelImg. Se abrirá el editor visual y podrás:

  • Seleccionar un directorio como fuente de imágenes.
  • Seleccionar un directorio donde guardará los xml.

En el editor deberás crear una caja (bounding-box) sobre cada objeto que quieras detectar en la imagen y escribir su nombre (clase). Cuando terminas le das a Guardar y Siguiente!

El lego dataset

Puedes utilizar el Lego-Dataset de imágenes y anotaciones (170MB) que creé para este artículo y consta de 300 imágenes. Son fotos tomadas con móvil de diversos personajes lego. Realmente son 100 fotos y 200 variaciones en zoom y recortes. Y sus correspondientes 300 archivos de anotaciones xml.

Dicho esto, recuerda que siempre es mejor más y más imágenes para entrenar.

El código Python

Usaremos Keras sobre Tensorflow para crear la red!, manos a la obra.

En el artículo copiaré los trozos de código más importantes, siempre puedes descargar la notebook Jupyter con el código completo desde Github.

Leer el Dataset

Primer paso, será el de leer las anotaciones xml que tenemos creadas en un directorio e ir iterando los objetos para contabilizar las etiquetas.

NOTA: en este ejemplo, declaro la variable labels con 1 sóla clase “lego”, pero si quieres identificar más podrías poner [“perro”,”gato”] ó lo que sea que contenga tu dataset.

xml_dir = "annotation/lego/"
img_dir = "images/lego/"
labels = ["lego"]
tamanio = 416
mejores_pesos = "red_lego.h5"

def leer_annotations(ann_dir, img_dir, labels=[]):
    all_imgs = []
    seen_labels = {}
    
    for ann in sorted(os.listdir(ann_dir)):
        img = {'object':[]}

        tree = ET.parse(ann_dir + ann)
        
        for elem in tree.iter():
            if 'filename' in elem.tag:
                img['filename'] = img_dir + elem.text
            if 'width' in elem.tag:
                img['width'] = int(elem.text)
            if 'height' in elem.tag:
                img['height'] = int(elem.text)
            if 'object' in elem.tag or 'part' in elem.tag:
                obj = {}
                
                for attr in list(elem):
                    if 'name' in attr.tag:
                        obj['name'] = attr.text

                        if obj['name'] in seen_labels:
                            seen_labels[obj['name']] += 1
                        else:
                            seen_labels[obj['name']] = 1
                        
                        if len(labels) > 0 and obj['name'] not in labels:
                            break
                        else:
                            img['object'] += [obj]
                            
                    if 'bndbox' in attr.tag:
                        for dim in list(attr):
                            if 'xmin' in dim.tag:
                                obj['xmin'] = int(round(float(dim.text)))
                            if 'ymin' in dim.tag:
                                obj['ymin'] = int(round(float(dim.text)))
                            if 'xmax' in dim.tag:
                                obj['xmax'] = int(round(float(dim.text)))
                            if 'ymax' in dim.tag:
                                obj['ymax'] = int(round(float(dim.text)))

        if len(img['object']) > 0:
            all_imgs += [img]
                        
    return all_imgs, seen_labels

train_imgs, train_labels = leer_annotations(xml_dir, img_dir, labels)
print('imagenes',len(train_imgs), 'labels',len(train_labels))

Train y Validación

Separaremos un 20% de las imágenes y anotaciones para testear el modelo. En este caso se utilizará el set de Validación al final de cada época para evaluar métricas, pero nunca se usará para entrenar.

¿Porque usar Train, test y validación?

train_valid_split = int(0.8*len(train_imgs))
np.random.shuffle(train_imgs)
valid_imgs = train_imgs[train_valid_split:]
train_imgs = train_imgs[:train_valid_split]
print('train:',len(train_imgs), 'validate:',len(valid_imgs))

Data Augmentation

El Data Augmentation sirve para agregar pequeñas alteraciones ó cambios a las imágenes de entradas aumentando virtualmente nuestro dataset de imágenes y mejorando la capacidad de la red para detectar objetos. Para hacerlo nos apoyamos sobre una librería llamada imgaug que nos brinda muchas funcionalidades como agregar desenfoque, agregar brillo, ó ruido aleatoriamente a las imágenes. Además podemos usar OpenCV para voltear la imagen horizontalmente y luego recolocar la “bounding box”.

### FRAGMENTO del código

iaa.OneOf([
    iaa.GaussianBlur((0, 3.0)), # blur images
    iaa.AverageBlur(k=(2, 7)), # blur image using local means with kernel
    iaa.MedianBlur(k=(3, 11)), # blur image using local medians with kernel
    ]),
    iaa.Sharpen(alpha=(0, 1.0), lightness=(0.75, 1.5)), # sharpen images
    iaa.AdditiveGaussianNoise(loc=0, scale=(0.0, 0.05*255), per_channel=0.5), # add gaussian noise to images
    iaa.OneOf([
        iaa.Dropout((0.01, 0.1), per_channel=0.5), # randomly remove up to 10% of the pixels
        ]),
    iaa.Add((-10, 10), per_channel=0.5), # change brightness of images
    iaa.Multiply((0.5, 1.5), per_channel=0.5), # change brightness of images
    iaa.ContrastNormalization((0.5, 2.0), per_channel=0.5), # improve or worsen the contrast

Crear la Red de Clasificación

La red CNN es conocida como Darknet y está compuesta por 22 capas convolucionales que básicamente aplican BatchNormalizarion, MaxPooling y activación por LeakyRelu para la extracción de características, es decir, los patrones que encontrará en las imágenes (en sus pixeles) para poder diferenciar entre los objetos que queremos clasificar.

Va alternando entre aumentar y disminuir la cantidad de filtros y kernel de 3×3 y 1×1 de la red convolucional.

#### FRAGMENTO de código, solo algunas capas de ejemplo

# Layer 1
x = Conv2D(32, (3,3), strides=(1,1), padding='same', name='conv_1', use_bias=False)(input_image)
x = BatchNormalization(name='norm_1')(x)
x = LeakyReLU(alpha=0.1)(x)
x = MaxPooling2D(pool_size=(2, 2))(x)

# Layer 2
x = Conv2D(64, (3,3), strides=(1,1), padding='same', name='conv_2', use_bias=False)(x)
x = BatchNormalization(name='norm_2')(x)
x = LeakyReLU(alpha=0.1)(x)
x = MaxPooling2D(pool_size=(2, 2))(x)

# Layer 3
x = Conv2D(128, (3,3), strides=(1,1), padding='same', name='conv_3', use_bias=False)(x)
x = BatchNormalization(name='norm_3')(x)
x = LeakyReLU(alpha=0.1)(x)

No olvides descargar y copiar en el mismo directorio donde ejecutes la notebook los pesos de la red Darknet, pues en este paso se cargaran para incializar la red.

Crear la Red de Detección

Esta red, utilizará la anterior (clasificación) y utilizará las features obtenidas en sus capas convolucionales de salida para hacer la detección de los objetos, es decir las posiciones x e y, alto y ancho. Para ello se valdrá de unas Anclas, en nuestro caso serán 5. Las Anclas son unas “ventanas”, o unas bounding boxes de distintos tamaños, pequeños, mediano grande, rectangulares o cuadrados que servirán para hacer “propuestas de detección”.

### Fragmento de código

        input_image     = Input(shape=(self.input_size, self.input_size, 3))
        self.true_boxes = Input(shape=(1, 1, 1, max_box_per_image , 4))  

        self.feature_extractor = FullYoloFeature(self.input_size)

        print(self.feature_extractor.get_output_shape())    
        self.grid_h, self.grid_w = self.feature_extractor.get_output_shape()        
        features = self.feature_extractor.extract(input_image)            

        # make the object detection layer
        output = Conv2D(self.nb_box * (4 + 1 + self.nb_class), 
                        (1,1), strides=(1,1), 
                        padding='same', 
                        name='DetectionLayer', 
                        kernel_initializer='lecun_normal')(features)
        output = Reshape((self.grid_h, self.grid_w, self.nb_box, 4 + 1 + self.nb_class))(output)
        output = Lambda(lambda args: args[0])([output, self.true_boxes])

        self.model = Model([input_image, self.true_boxes], output)

En total, la red YOLO crea una grilla de 13×13 y en cada una realizará 5 predicciones, lo que da un total de 845 posibles detecciones para cada clase que queremos detectar. Si tenemos 10 clases esto serían 8450 predicciones, cada una con la clase y sus posiciones x,y ancho y alto. Lo más impresionante de esta red YOLO es que lo hace todo de 1 sólo pasada! increíble!

Para refinar el modelo y que detecte los objetos que hay realmente, utilizará dos funciones con las cuales descartará áreas vacías y se quedará sólo con las mejores propuestas. Las funciones son:

  • IOU: Intersection Over Union, que nos da un porcentaje de acierto del área de predicción contra la “cajita” real que queremos predecir.
  • Non Maximum suppression: nos permite quedarnos de entre nuestras 5 anclas, con la que mejor se ajusta al resultado. Esto es porque podemos tener muchas áreas diferentes propuestas que se superponen. De entre todas, nos quedamos con la mejor y eliminamos al resto.

Entonces, pensemos que si en nuestra red de detección de 1 sóla clase detectamos 1 lego, esto quiere decir que la red descarto a las 844 restantes propuestas.

Prometo más teoría y explicaciones en un próximo artículo 🙂

NOTA: por más que para explicar lo haya separado en 2 redes (red YOLO y red de detección), realmente es 1 sóla red convolucional, pues están conectadas y al momento de entrenar, los pesos se ajustan “como siempre” con el backpropagation.

Generar las Anclas

Como antes mencioné, la red utiliza 5 anclas para cada una de las celdas de 13×13 para realizar las propuestas de predicción. Pero… ¿qué tamaño tienen que tener esas anclas? Podríamos pensar en 5 tamaños distintos, algunos pequeños, otros más grandes y que se adapten a las clases que queremos detectar. Por ejemplo, el ancla para detectar siluetas de personas serán rectangulares en vertical.

Según los objetos que quieras detectar, ejecutaremos un pequeño script que utiliza k-means y determina los mejores 5 clusters (de dimensiones) que se adapten a tu dataset.

Entrenar la Red Neuronal!

Basta de bla bla… y a entrenar la red. Como dato informativo, en mi ordenador Macbook de 4 núcleos y 8GB de RAM, tardó 7 horas en entrenar las 300 imágenes del dataset de lego con 7 épocas y 5 veces cada imagen con data augmentation, (en total se procesan 1500 imágenes en cada epoch).

yolo = YOLO(input_size          = tamanio, 
            labels              = labels, 
            max_box_per_image   = 5,
            anchors             = anchors)

Al finalizar verás que se ha creado un archivo nuevo llamado “red_lego.h5” que contiene los pesos de tu nueva red convolucional creada.

Revisar los Resultados

Los resultados vienen dados por una métrica llamada mAP y que viene a ser un equivalente a un F1-Score pero para imágenes, teniendo en cuenta los falsos positivos y negativos. Ten en cuenta que si bien la ventaja de YOLO es la detección en tiempo real, su contra es que es “un poco” peor en accuracy que otras redes -que son lentas-, lo podemos notar al ver que las “cajitas” no se ajustan del todo con el objeto detectado ó puede llegar a confundir la clase que clasificó. Con el Lego Dataset he logrado un bonito 63 de mAP… no está mal. Recordemos que este valor de mAP se obtiene al final de la última Epoch sobre el dataset de Validación (que no se usa para entrenar) y en mi caso eran -apenas- 65 imágenes.

Probar la Red

Para finalizar, podemos probar la red con imágenes nuevas, distintas que no ha visto nunca, veamos cómo se comporta la red!

Crearemos unas funciones de ayuda para dibujar el rectángulo sobre la imagen original y guardar la imagen nueva:

def draw_boxes(image, boxes, labels):
    image_h, image_w, _ = image.shape

    for box in boxes:
        xmin = int(box.xmin*image_w)
        ymin = int(box.ymin*image_h)
        xmax = int(box.xmax*image_w)
        ymax = int(box.ymax*image_h)

        cv2.rectangle(image, (xmin,ymin), (xmax,ymax), (0,255,0), 3)
        cv2.putText(image, 
                    labels[box.get_label()] + ' ' + str(box.get_score()), 
                    (xmin, ymin - 13), 
                    cv2.FONT_HERSHEY_SIMPLEX, 
                    1e-3 * image_h, 
                    (0,255,0), 2)
        
    return image

Utilizaremos el archivo de pesos creado al entrenar, para recrear la red (esto nos permite poder hacer predicciones sin necesidad de reentrenar cada vez).

mejores_pesos = "red_lego.h5"
image_path = "images/test/lego_girl.png"

mi_yolo = YOLO(input_size          = tamanio, 
            labels              = labels, 
            max_box_per_image   = 5,
            anchors             = anchors)

mi_yolo.load_weights(mejores_pesos)

image = cv2.imread(image_path)
boxes = mi_yolo.predict(image)
image = draw_boxes(image, boxes, labels)

print('Detectados', len(boxes))

cv2.imwrite(image_path[:-4] + '_detected' + image_path[-4:], image)

Como salida tendremos una nueva imagen llamada “lego_girl_detected.png” con la detección realizada.

Esta imagen me fue prestada por @Shundeez_official, muchas gracias! Les recomiendo ver su cuenta de Instagram que es genial!

Imágenes pero también Video y Cámara!

Puedes modificar levemente la manera de realizar predicciones para utilizar un video mp4 ó tu cámara web.

Para aplicarlo a un video:

from tqdm import *

video_path = 'lego_movie.mp4'
video_out = video_path[:-4] + '_detected' + video_path[-4:]
video_reader = cv2.VideoCapture(video_path)

nb_frames = int(video_reader.get(cv2.CAP_PROP_FRAME_COUNT))
frame_h = int(video_reader.get(cv2.CAP_PROP_FRAME_HEIGHT))
frame_w = int(video_reader.get(cv2.CAP_PROP_FRAME_WIDTH))

video_writer = cv2.VideoWriter(video_out,
                       cv2.VideoWriter_fourcc(*'MPEG'), 
                       50.0, 
                       (frame_w, frame_h))

for i in tqdm(range(nb_frames)):
    _, image = video_reader.read()
    
    boxes = yolo.predict(image)
    image = draw_boxes(image, boxes, labels)

    video_writer.write(np.uint8(image))

video_reader.release()
video_writer.release()

Luego de procesar el video, nos dejará una versión nueva del archivo mp4 con la detección que realizó cuadro a cuadro.

Y para usar tu cámara: (presiona ‘q’ para salir)

win_name = 'Lego detection'
cv2.namedWindow(win_name)

video_reader = cv2.VideoCapture(0)

while True:
    _, image = video_reader.read()
    
    boxes = yolo.predict(image)
    image = draw_boxes(image, boxes, labels)

    cv2.imshow(win_name, image)

    key = cv2.waitKey(1) & 0xFF
    if key == ord('q'):
        break

cv2.destroyAllWindows()
video_reader.release()

Conclusiones y…

Esta fue la parte práctica de una de las tareas más interesantes dentro de la Visión Artificial, que es la de lograr hacer detección de objetos. Piensen todo el abanico de posibilidades que ofrece poder hacer esto! Podríamos con una cámara contabilizar la cantidad de coches y saber si hay una congestión de tráfico, podemos contabilizar cuantas personas entran en un comercio, si alguien toma un producto de una estantería y mil cosas más! Ni hablar en robótica, donde podemos hacer que el robot vea y pueda coger objetos, ó incluso los coches de Tesla con Autopilot… Tiene un gran potencial!

Además en este artículo quería ofrecer el código que te permita entrenar tus propios detectores, para los casos de negocio que a ti te importan.

En el próximo artículo comento sobre la Teoría que hoy pusimos en práctica sobre Detección de Objetos.

1 millón de Gracias!

Este artículo es muy especial para mi, por varias cosas: una es que el Blog ha conseguido la marca de 1.000.000 de visitas en estos 2 años y medio de vida y estoy muy contento de seguir escribiendo -a pesar de muchas adversidades de la vida-. Gracias por las visitas, por leerme, por los comentarios alentadores y el apoyo!

Libro en proceso

Con este artículo y por el hito conseguido me animo a lanzar un primer borrador de lo que será “El libro del blog” y que algún día completaré y publicaré Ya publicado, en papel y digital!!.

Los invito a todos a comprarlo si pueden colaborar con este proyecto y también está la opción de conseguirlo gratis, porque sé que hay muchos lectores que son estudiantes y puede que no tengan medios ó recursos para pagar y no por eso quiero dejar de compartirlo.

Todos los que lo adquieran ahora, podrán seguir obteniendo todas las actualizaciones que iré haciendo con el tiempo y descargar el material extra.

Suscripción al Blog

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

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

Todo el Material

Recuerda todo lo que tienes que descargar:

Y enlaces a otros artículos de interés:

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

]]>
https://ftp.aprendemachinelearning.com/deteccion-de-objetos-con-python-yolo-keras-tutorial/feed/ 76 7262
Detección de outliers en Python https://ftp.aprendemachinelearning.com/deteccion-de-outliers-en-python-anomalia/ https://ftp.aprendemachinelearning.com/deteccion-de-outliers-en-python-anomalia/#comments Tue, 02 Jun 2020 10:00:00 +0000 https://www.aprendemachinelearning.com/?p=7216 En este nuevo artículo de Aprende Machine Learning explicaremos qué son los outliers y porqué son tan importantes, veremos un ejemplo práctico paso a paso en Python, visualizaciones en 1, 2 y 3 dimensiones y el uso de una librería de propósito general. Puedes encontrar la Jupyter Notebook completa en GitHub.   ¿Qué son los […]

The post Detección de outliers en Python first appeared on Aprende Machine Learning.

]]>
En este nuevo artículo de Aprende Machine Learning explicaremos qué son los outliers y porqué son tan importantes, veremos un ejemplo práctico paso a paso en Python, visualizaciones en 1, 2 y 3 dimensiones y el uso de una librería de propósito general.

Puedes encontrar la Jupyter Notebook completa en GitHub.  

¿Qué son los Outliers?

Es interesante ver las traducciones de “outlier” -según su contexto- en inglés:

  • Atípico
  • Destacado
  • Excepcional
  • Anormal
  • Valor Extremo, Valor anómalo, valor aberrante!!

Eso nos da una idea, ¿no?

Es decir, que los outliers en nuestro dataset serán los valores que se “escapan al rango en donde se concentran la mayoría de muestras”. Según Wikipedia son las muestras que están distantes de otras observaciones.

Detección de Outliers

¿Y por qué nos interesa detectar esos Outliers? Por que pueden afectar considerablemente a los resultados que pueda obtener un modelo de Machine Learning… Para mal… ó para bien! Por eso hay que detectarlos, y tenerlos en cuenta. Por ejemplo en Regresión Lineal ó algoritmos de Ensamble puede tener un impacto negativo en sus predicciones.

Outliers Buenos vs Outliers Malos

Los Outliers pueden significar varias cosas:

  1. ERROR: Si tenemos un grupo de “edades de personas” y tenemos una persona con 160 años, seguramente sea un error de carga de datos. En este caso, la detección de outliers nos ayuda a detectar errores.
  2. LIMITES: En otros casos, podemos tener valores que se escapan del “grupo medio”, pero queremos mantener el dato modificado, para que no perjudique al aprendizaje del modelo de ML.
  3. Punto de Interés: puede que sean los casos “anómalos” los que queremos detectar y que sean nuestro objetivo (y no nuestro enemigo!)

Instala tu ambiente de desarrollo python con Anaconda, aquí explicamos cómo

Muchas veces es sencillo identificar los outliers en gráficas. Veamos ejemplos de Outliers en 1, 2 y 3 dimensiones.

Outliers en 1 dimensión

Si analizáramos una sola variable, por ejemplo “edad”, veremos donde se concentran la mayoría de muestras y los posibles valores “extremos”. Pasemos a un ejemplo en Python!

import matplotlib.pyplot as plt
import numpy as np

edades = np.array([22,22,23,23,23,23,26,27,27,28,30,30,30,30,31,32,33,34,80])
edad_unique, counts = np.unique(edades, return_counts=True)

sizes = counts*100
colors = ['blue']*len(edad_unique)
colors[-1] = 'red'

plt.axhline(1, color='k', linestyle='--')
plt.scatter(edad_unique, np.ones(len(edad_unique)), s=sizes, color=colors)
plt.yticks([])
plt.show()
En azul los valores donde se concentra la mayoría de nuestras filas. En rojo un outlier, ó “valor extremo”.

En el código, importamos librerías, creamos un array de edades con Numpy y luego contabilizamos las ocurrencias.

Al graficar vemos donde se concentran la mayoría de edades, entre 20 y 35 años. Y una muestra aislada con valor 80.

Outliers en 2 Dimensiones

Ahora supongamos que tenemos 2 variables: edad e ingresos. Hagamos una gráfica en 2D. Además, usaremos una fórmula para trazar un círculo que delimitará los valores outliers: Los valores que superen el valor de la “media más 2 desvíos estándar” (el área del círculo) quedarán en rojo.

from math import pi

salario_anual_miles = np.array([16,20,15,21,19,17,33,22,31,32,56,30,22,31,30,16,2,22,23])
media = (salario_anual_miles).mean()
std_x = (salario_anual_miles).std()*2
media_y = (edades).mean()
std_y = (edades).std()*2

colors = ['blue']*len(salario_anual_miles)
for index, x in enumerate(salario_anual_miles):
    if abs(x-media) > std_x:
        colors[index] = 'red'
        
for index, x in enumerate(edades):
    if abs(x-media_y) > std_y:
        colors[index] = 'red'

plt.scatter(edades, salario_anual_miles, s=100, color=colors)
plt.axhline(media, color='k', linestyle='--')
plt.axvline(media_y, color='k', linestyle='--')

v=media     #y-position of the center
u=media_y    #x-position of the center
b=std_x     #radius on the y-axis
a=std_y    #radius on the x-axis

t = np.linspace(0, 2*pi, 100)
plt.plot( u+a*np.cos(t) , v+b*np.sin(t) )

plt.xlabel('Edad')
plt.ylabel('Salario Anual (miles)')
plt.show()
Dentro del circulo azul, los valores que están en la media y en rojo los outliers: 3 valores que superan en más de 2 veces el desvío estándar.

Veamos -con la ayuda de seaborn-, la línea de tendencia de la misma distribución con y sin outliers:

CON OUTLIERS: La línea de tendencia se mantiene plana sobre todo por el outlier de la edad
SIN OUTLIERS: Al quitar los outliers la tendencia empieza a tener pendiente

Con esto nos podemos dar una idea de qué distinto podría resultar entrenar un modelo de Machine Learning con ó sin esas muestras anormales.

Visualizar Outliers en 3D

Vamos viendo que algunas de las muestras del dataset inicial van quedando fuera!

¿Qué pasa si añadimos una 3ra dimensión nuestro dataset? Por ejemplo, la dimensión de “compras por mes” de cada usuario.

from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure(figsize=(7,7))
ax = fig.gca(projection='3d')

compras_mes = np.array([1,2,1,20,1,0,3,2,3,0,5,3,2,1,0,1,2,2,2])
media_z = (compras_mes).mean()
std_z = (compras_mes).std()*2

for index, x in enumerate(compras_mes):
    if abs(x-media_z) > std_z:
        colors[index] = 'red'

ax.scatter(edades, salario_anual_miles, compras_mes, s=20, c=colors)
plt.xlabel('Edad')
plt.ylabel('Salario Anual (miles)')
ax.set_zlabel('Compras mensuales')

plt.show()
Vemos en 3 dimensiones que hay valores que escapan a la <<distribución normal>>. Valores atípicos en rojo.

En el caso de las compras mensuales, vemos que aparece un nuevo “punto rojo” en el eje Z. Debemos pensar si es un usuario que queremos descartar ó que por el contrario, nos interesa analizar.

Outliers en N-dimensiones

La realidad es que en los modelos con los que trabajamos constan de muchas dimensiones, podemos tener 30, 100 ó miles. Entonces ya no parece tan sencillo visualizar los outliers.

Podemos seguir detectando los outliers “a ciegas” y manejarlos. O mediante una librería (más adelante se comenta en el artículo).

Podemos graficar múltiples dimensiones haciendo una reducción de dimensiones con PCA ó con T-SNE.

NOTA: tenemos que pensar que -suponiendo que no hay error en los datos- un valor que analizado en 1 sóla dimensión es un Outlier, analizado en conjunto en “N-dimensiones” puede que NO LO SEA. Entonces no siempre es válida la estrategia de analizar la variable aislada del resto.

Imaginemos que luego de aplicar PCA sobre un conjunto obtenemos los siguientes clusters:

Aquí vemos claramente que hay valores que no “encajan” en ningún conjunto: los outliers. Esto a veces se podría corresponder con “Anomaly detection”.

Una gráfica de detección sencilla: Boxplots

Una gráfica bastante interesante de conocer es la de los Boxplots, muy utilizados en el mundo financiero. En nuestro caso, podemos visualizar las variables y en esa “cajita” veremos donde se concentra el 50 por ciento de nuestra distribución (percentiles 25 a 75), los valores mínimos y máximos (las rayas en “T”) y -por supuesto- los outliers, esos “valores extraños” y alejados.

green_diamond = dict(markerfacecolor='g', marker='D')
fig, ax = plt.subplots()
ax.set_title('Boxplot por Edades')
ax.boxplot(edades, flierprops=green_diamond, labels=["Edad"])
Ese diamante verde está muy alejado de nuestra media!

Una vez detectados, ¿qué hago?

Según la lógica de negocio podemos actuar de una manera u otra.

Por ejemplo podríamos decidir:

  • Las edades fuera de la distribución normal, eliminar.
  • El salario que sobrepasa el límite, asignar el valor máximo (media + 2 sigmas).
  • Las compras mensuales, mantener sin cambios.

PyOD: Librería Python para Detección de Outliers

En el código utilicé una medida conocida para la detección de outliers que puede servir: la media de la distribución más 2 sigmas como frontera. Pero existen otras estrategias para delimitar outliers.

Una librería muy recomendada es PyOD. Posee diversas estrategias para detectar Outliers. Ofrece distintos algoritmos, entre ellos Knn que tiene mucho sentido, pues analiza la cercanía entre muestras, PCA, Redes Neuronales, veamos cómo utilizarla en nuestro ejemplo.

!pip install pyod  # instala la librería

from pyod.models.knn import KNN
import pandas as pd

X = pd.DataFrame(data={'edad':edades,'salario':salario_anual_miles, 'compras':compras_mes})

clf = KNN(contamination=0.18)
clf.fit(X)
y_pred = clf.predict(X)
X[y_pred == 1]
La librería PyOd detecta los registros anómalos

Para problemas en la vida real, con múltiples dimensiones conviene apoyarnos en una librería como esta que nos facilitará la tarea de detección y limpieza/transformación del dataset.

Conclusiones

Hemos visto lo importante que son los outliers y el impacto que pueden tener al entrenar un modelo de ML. La mayoría de los datasets tendrán muestras “fuera de rango”, por lo que debemos tenerlas en cuenta y decidir cómo tratarlas.

Para algunos problemas, nos interesa detectar esos outliers y de hecho será nuestro objetivo localizar esas anomalías.

Para trabajar con muestras con decenas o cientos de dimensiones nos conviene utilizar una librería como PyOd que realiza muy bien su trabajo!

Espero que el artículo haya sido de tu interés! No olvides compartir y para cualquier consulta deja tu comentario!

Suscripción al Blog

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

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

Recursos

Descarga la Notebook Ejercicio_Outliers que acompaña este artículo desde mi cuenta de Github

Enlaces de Interés

La libreria pyod anomaly detection 

How to Identify Outliers in your Data

Effective Outlier Detection Techniques in Machine Learning

How to Make Your Machine Learning Models Robust to Outliers

Machine Learning | Outlier

Three methods to deal with outliers

Outlier Detection and Anomaly Detection with Machine Learning

El libro del Blog

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

The post Detección de outliers en Python first appeared on Aprende Machine Learning.

]]>
https://ftp.aprendemachinelearning.com/deteccion-de-outliers-en-python-anomalia/feed/ 1 7216
Análisis Exploratorio de Datos con Pandas en Python https://ftp.aprendemachinelearning.com/analisis-exploratorio-de-datos-pandas-python/ https://ftp.aprendemachinelearning.com/analisis-exploratorio-de-datos-pandas-python/#comments Thu, 12 Dec 2019 12:55:00 +0000 https://www.aprendemachinelearning.com/?p=7074 Veremos de qué se trata este paso inicial tan importante y necesario para comenzar un proyecto de Machine Learning. Aprendamos en qué consiste el EDA y qué técnicas utilizar. Veamos un ejemplo práctico y la manipulación de datos con Python utilizando la librería Pandas para analizar y Visualizar la información en pocos minutos. Como siempre, […]

The post Análisis Exploratorio de Datos con Pandas en Python first appeared on Aprende Machine Learning.

]]>
Veremos de qué se trata este paso inicial tan importante y necesario para comenzar un proyecto de Machine Learning. Aprendamos en qué consiste el EDA y qué técnicas utilizar. Veamos un ejemplo práctico y la manipulación de datos con Python utilizando la librería Pandas para analizar y Visualizar la información en pocos minutos.

Como siempre, podrás descargar todo el código de la Jupyter Notebook desde mi cuenta de Github (que contiene información extra). Y como BONUS encuentra una notebook con las funciones más útiles de Pandas!

¿Qué es el EDA?

Eda es la sigla en inglés para Exploratory Data Analysis y consiste en una de las primeras tareas que tiene que desempeñar el Científico de Datos. Es cuando revisamos por primera vez los datos que nos llegan, por ejemplo un archivo CSV que nos entregan y deberemos intentar comprender “¿de qué se trata?”, vislumbrar posibles patrones y reconociendo distribuciones estadísticas que puedan ser útiles en el futuro.

OJO!, lo ideal es que tengamos un objetivo que nos hayan “adjuntado” con los datos, que indique lo que se quiere conseguir a partir de esos datos. Por ejemplo, nos pasan un excel y nos dicen “Queremos predecir ventas a 30 días”, ó Clasificar casos malignos/benignos de una enfermedad”, “Queremos identificar audiencias que van a realizar re-compra de un producto”, “queremos hacer pronóstico de fidelización de clientes/abandonos”, “Quiero detectar casos de fraude en mi sistema en tiempo real”.

EDA deconstruido

Al llegar un archivo, lo primero que deberíamos hacer es intentar responder:

  • ¿Cuántos registros hay?
    • ¿Son demasiado pocos?
    • ¿Son muchos y no tenemos Capacidad (CPU+RAM) suficiente para procesarlo?
  • ¿Están todas las filas completas ó tenemos campos con valores nulos?
    • En caso que haya demasiados nulos: ¿Queda el resto de información inútil?
  • ¿Que datos son discretos y cuales continuos?
    • Muchas veces sirve obtener el tipo de datos: texto, int, double, float
  • Si es un problema de tipo supervisado:
    • ¿Cuál es la columna de “salida”? ¿binaria, multiclase?
    • ¿Esta balanceado el conjunto salida?
  • ¿Cuales parecen ser features importantes? ¿Cuales podemos descartar?
  • ¿Siguen alguna distribución?
  • ¿Hay correlación entre features (características)?
  • En problemas de NLP es frecuente que existan categorías repetidas ó mal tipeadas, ó con mayusculas/minúsculas, singular y plural, por ejemplo “Abogado” y “Abogadas”, “avogado” pertenecerían todos a un mismo conjunto.
  • ¿Estamos ante un problema dependiente del tiempo? Es decir un TimeSeries.
  • Si fuera un problema de Visión Artificial: ¿Tenemos suficientes muestras de cada clase y variedad, para poder hacer generalizar un modelo de Machine Learning?
  • ¿Cuales son los Outliers? (unos pocos datos aislados que difieren drásticamente del resto y “contaminan” ó desvían las distribuciones)
    • Podemos eliminarlos? es importante conservarlos?
    • son errores de carga o son reales?
  • ¿Tenemos posible sesgo de datos? (por ejemplo perjudicar a clases minoritarias por no incluirlas y que el modelo de ML discrimine)

Puede ocurrir que tengamos set de datos incompletos y debamos pedir a nuestro cliente/proveedor ó interesado que nos brinde mayor información de los campos, que aporte más conocimiento ó que corrija campos.

¿Qué son los conjuntos de Train, Test y Validación en Machine Learning?

También puede que nos pasen múltiples fuentes de datos, por ejemplo un csv, un excel y el acceso a una base de datos. Entonces tendremos que hacer un paso previo de unificación de datos.

¿Qué sacamos del EDA?

El EDA será entonces una primer aproximación a los datos, ATENCIóN, si estamos mas o menos bien preparados y suponiendo una muestra de datos “suficiente”, puede que en “unas horas” tengamos ya varias conclusiones como por ejemplo:

  • Esto que quiere hacer el cliente CON ESTOS DATOS es una locura imposible! (esto ocurre la mayoría de las veces jeje)
  • No tenemos datos suficientes ó son de muy mala calidad, pedir más al cliente.
  • Un modelo de tipo Arbol es lo más recomendado usar
    • (reemplazar Arbol, por el tipo de modelo que hayamos descubierto como mejor opción!)
  • No hace falta usar Machine Learning para resolver lo que pide el cliente. (ESTO ES MUY IMPORTANTE!)
  • Es todo tan aleatorio que no habrá manera de detectar patrones
  • Hay datos suficientes y de buena calidad como para seguir a la próxima etapa.

A estas alturas podemos saber si nos están pidiendo algo viable ó si necesitamos más datos para comenzar.

Repito por si no quedó claro: el EDA debe tomar horas, ó puede que un día, pero la idea es poder sacar algunas conclusiones rápidas para contestar al cliente si podemos seguir o no con su propuesta.

Luego del EDA, suponiendo que seguimos adelante podemos tomarnos más tiempo y analizar en mayor detalle los datos y avanzar a nuevas etapas para aplicar modelos de Machine Learning.

Técnicas para EDA

Vamos a lo práctico!, ¿Que herramientas tenemos hoy en día? La verdad es que como cada conjunto de datos suele ser único, el EDA se hace bastante “a mano”, pero podemos seguir diversos pasos ordenados para intentar acercarnos a ese objetivo que nos pasa el cliente en pocas horas.

A nivel programación y como venimos utilizando Python, encontramos a la conocida librería Pandas, que nos ayudará a manipular datos, leer y transformarlos.

Instala el ambiente de desarrollo Python en tu ordenador siguiendo esta guía

Otra de las técnicas que más nos ayudaran en el EDA es visualización de datos (que también podemos hacer con Pandas).

Finalmente podemos decir que nuestra Intuición -basada en Experiencia previa, no en corazonadas- y nuestro conocimiento de casos similares también nos pueden aportar pistas para saber si estamos ante datos de buena calidad. Por ejemplo si alguien quiere hacer reconocimiento de imágenes de tornillos y tiene 25 imágenes y con muy mala resolución podremos decir que no tenemos muestras suficientes -dado nuestro conocimiento previo de este campo-.

Vamos a la práctica!

Un EDA de pocos minutos con Pandas (Python)

Vamos a hacer un ejemplo en pandas de un EDA bastante sencillo pero con fines educativos.

Vamos a leer un csv directamente desde una URL de GitHub que contiene información geográfica básica de los países del mundo y vamos a jugar un poco con esos datos.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import statsmodels.api as sm

url = 'https://raw.githubusercontent.com/lorey/list-of-countries/master/csv/countries.csv'
df = pd.read_csv(url, sep=";")
print(df.head(5))

Veamos los datos básicos que nos brinda pandas:
Nombre de columnas

print('Cantidad de Filas y columnas:',df.shape)
print('Nombre columnas:',df.columns)

Columnas, nulos y tipo de datos

df.info()
En esta salida vemos las columnas, el total de filas y la cantidad de filas sin nulos. También los tipos de datos.

descripción estadística de los datos numéricos

df.describe()
Pandas filtra las features numéricas y calcula datos estadísticos que pueden ser útiles: cantidad, media, desvío estándar, valores máximo y mínimo.

Verifiquemos si hay correlación entre los datos

corr = df.set_index('alpha_3').corr()
sm.graphics.plot_corr(corr, xnames=list(corr.columns))
plt.show()
En este caso vemos baja correlación entre las variables. Dependiendo del algoritmo que utilicemos podría ser una buena decisión eliminar features que tuvieran alta correlación

Cargamos un segundo archivo csv para ahondar en el crecimiento de la población en los últimos años, filtramos a España y visualizamos

url = 'https://raw.githubusercontent.com/DrueStaples/Population_Growth/master/countries.csv'
df_pop = pd.read_csv(url)
print(df_pop.head(5))
df_pop_es = df_pop[df_pop["country"] == 'Spain' ]
print(df_pop_es.head())
df_pop_es.drop(['country'],axis=1)['population'].plot(kind='bar')
Crecimiento de la Población de España. El eje x no está establecido y aparece un id de fila.

Hagamos la comparativa con otro país, por ejemplo con el crecimiento poblacional en Argentina

df_pop_ar = df_pop[(df_pop["country"] == 'Argentina')]

anios = df_pop_es['year'].unique()
pop_ar = df_pop_ar['population'].values
pop_es = df_pop_es['population'].values

df_plot = pd.DataFrame({'Argentina': pop_ar,
                    'Spain': pop_es}, 
                       index=anios)
df_plot.plot(kind='bar')
Gráfica comparativa de crecimiento poblacional entre España y Argentina entre los años 1952 al 2007

Ahora filtremos todos los paises hispano-hablantes

df_espanol = df.replace(np.nan, '', regex=True)
df_espanol = df_espanol[ df_espanol['languages'].str.contains('es') ]
df_espanol

Visualizamos…

df_espanol.set_index('alpha_3')[['population','area']].plot(kind='bar',rot=65,figsize=(20,10))

Vamos a hacer detección de Outliers, (con fines educativos) en este caso definimos como limite superior (e inferior) la media más (menos) “2 veces la desviación estándar” que muchas veces es tomada como máximos de tolerancia.

anomalies = []

# Funcion ejemplo para detección de outliers
def find_anomalies(data):
    # Set upper and lower limit to 2 standard deviation
    data_std = data.std()
    data_mean = data.mean()
    anomaly_cut_off = data_std * 2
    lower_limit  = data_mean - anomaly_cut_off 
    upper_limit = data_mean + anomaly_cut_off
    print(lower_limit.iloc[0])
    print(upper_limit.iloc[0])

    # Generate outliers
    for index, row in data.iterrows():
        outlier = row # # obtener primer columna
        # print(outlier)
        if (outlier.iloc[0] > upper_limit.iloc[0]) or (outlier.iloc[0] < lower_limit.iloc[0]):
            anomalies.append(index)
    return anomalies

find_anomalies(df_espanol.set_index('alpha_3')[['population']])

Detectamos como outliers a Brasil y a USA. Los eliminamos y graficamos ordenado por población de menor a mayor.

# Quitemos BRA y USA por ser outlies y volvamos a graficar:
df_espanol.drop([30,233], inplace=True)
df_espanol.set_index('alpha_3')[['population','area']].sort_values(["population"]).plot(kind='bar',rot=65,figsize=(20,10))
Así queda nuestra gráfica sin outliers 🙂

En pocos minutos hemos podido responder: cuántos datos tenemos, si hay nulos, los tipos de datos (entero, float, string), la correlación, hicimos visualizaciones, comparativas, manipulación de datos, detección de ouliers y volver a graficar. ¿No está nada mal, no?

Más cosas! que se suelen hacer:

Otras pruebas y gráficas que se suelen hacer son:

  • Si hay datos categóricos, agruparlos, contabilizarlos y ver su relación con las clases de salida
  • gráficas de distribución en el tiempo, por ejemplo si tuviéramos ventas, para tener una primera impresión sobre su estacionalidad.
  • Rankings del tipo “10 productos más vendidos” ó “10 ítems con más referencias por usuario”.
  • Calcular importancia de Features y descartar las menos útiles.

Conclusiones

En el artículo vimos un repaso sobre qué es y cómo lograr hacer un Análisis Exploratorio de Datos en pocos minutos. Su importancia es sobre todo la de darnos un vistazo sobre la calidad de datos que tenemos y hasta puede determinar la continuidad o no de un proyecto.

Siempre dependerá de los datos que tengamos, en cantidad y calidad y por supuesto nunca deberemos dejar de tener en vista EL OBJETIVO, el propósito que buscamos lograr. Siempre debemos apuntar a lograr eso con nuestras acciones.

Como resultado del EDA si determinamos continuar, pasaremos a una etapa en la que ya preprocesaremos los datos pensando en la entrada a un modelo (ó modelos!) de Machine Learning.

La detección de Outliers podría comentarse en un artículo completo sobre el tema… YA salió!

¿Conocías el EDA? ¿Lo utilizas en tu trabajo? Espero tus comentarios!

Suscripción al Blog

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

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

Recursos

Como siempre, puedes descargar la notebook relacionada con este artículo desde aquí:

BONUS track: Notebook sobre manipulación de datos con Pandas

Como Bonus…. te dejo una notebook con los Casos más comunes de uso de Manipulación de datos con Pandas!

Artículos Relacionados

Estos son otros artículos relacionados que pueden ser de tu interés:

The post Análisis Exploratorio de Datos con Pandas en Python first appeared on Aprende Machine Learning.

]]>
https://ftp.aprendemachinelearning.com/analisis-exploratorio-de-datos-pandas-python/feed/ 14 7074
Tu propio Servicio de Machine Learning https://ftp.aprendemachinelearning.com/tu-propio-servicio-de-machine-learning/ https://ftp.aprendemachinelearning.com/tu-propio-servicio-de-machine-learning/#comments Wed, 24 Jul 2019 08:00:00 +0000 https://www.aprendemachinelearning.com/?p=6843 Dale vida a tu IA Ya tienes tu modelo, probado, funciona bastante bien y está listo para entrar en la acción. Entonces, ¿cómo lo desplegamos? Si es una solución que quieres ofrecer al público desde la nube, puedes implementar tu propio servicio online y ofrecer soluciones de Machine Learning! Veamos cómo hacerlo! Implementar modelos de […]

The post Tu propio Servicio de Machine Learning first appeared on Aprende Machine Learning.

]]>

Dale vida a tu IA

Ya tienes tu modelo, probado, funciona bastante bien y está listo para entrar en la acción. Entonces, ¿cómo lo desplegamos? Si es una solución que quieres ofrecer al público desde la nube, puedes implementar tu propio servicio online y ofrecer soluciones de Machine Learning!

Veamos cómo hacerlo!

Implementar modelos de Machine Learning

Muchas veces el modelo creado por el equipo de Machine Learning, será una “pieza más” de un sistema mayor, como por ejemplo una app, un chatbot, algún sistema de marketing, un sistema de monitoreo de seguridad. Y si bien el modelo puede correr por lo general en Python o R, es probable que interactúe con otro stack distinto de desarrollo. Por ejemplo, una app Android en Java o Kotlin, algún sistema PHP, en la nube ó hasta podría ser aplicaciones de escritorio o CRM . Entonces, nuestro modelo deberá ser capaz de interactuar y “servir” a los pedidos de esas otras herramientas.

Como opciones, podríamos reescribir nuestro código en otro lenguaje (javascript, java, c…) ó si nuestro proceso ejecutara batch, podría ser una “tarea cron” del sistema operativo que ejecute automáticamente cada X tiempo y deje por ejemplo un archivo de salida CSV (ó entradas en una base de datos).

Pero, si nuestro modelo tiene que servir a otros sistemas en tiempo real y no podemos reescribirlo (incluso por temas de mantenimiento futuro, actualización ó imposibilidad de recrear módulos completos de Python) podemos desplegar nuestro modelo en una API accesible desde un servidor (que podría ser público ó privado) y mediante una clave secreta -si hiciera falta-.

Servir mediante una API

Una API es la manera más popular que hay para ofrecer servicios online en la actualidad. Sin meterme en profundidad en el tema, podemos decir que lo que hacemos es publicar un punto de entrada desde donde los usuarios (clientes, apps, u otras máquinas) harán peticiones de consulta, inserción, actualización o borrado de los datos a los que tienen acceso. En nuestro caso, lo típico será ofrecer un servicio de Machine Learning de predicción ó clasificación (por nombrar alguno). Entonces nos llegarán en la petición GET ó POST las entradas que tendrá el modelo (nuestras features, ó lo que normalmente son las “columnas” de un csv que usamos para entrenar). Y nuestra salida podría ser según el caso, el resultado de la predicción, ó una probabilidad, ó un número (por ej. “cantidad de ventas pronosticadas para ese día”).

Para crear una API, podemos utilizar diversas infraestructuras ya existentes en el mercado que ofrecen Google, Amazon, Microsoft (u otros) ó podemos “levantar” nuestro propio servicio con Flask. Flask es un web framework en Python que simplifica la manera de publicar nuestra propia API (Hay otros como Django, Falcon y más).

Instalar Flask

Veamos rápidamente como instalar y dejar montado Flask.

  • Instalar Anaconda en el servidor ó en nuestra máquina local para desarrollo. (Para servidores también puedes usar la versión de mini-conda)
  • Prueba ejecutar el comando “conda” en el terminal para verificar que esté todo ok.
  • Crear un nuevo environment en el que trabajaremos conda create --name mi_ambiente python=3.6
  • Activa el ambiente creado con source activate mi_ambiente
  • Instalar los paquetes Python que utilizaremos: pip install flask gunicorn

Hagamos un “Hello world” con Flask. Crea un archivo de texto nuevo llamado “mi_server.py

"""Creando un servidor Flask
"""

from flask import Flask

app = Flask(__name__)

@app.route('/users/')
def hello_world(nombre=None):

    return("Hola {}!".format(nombre))

Guarda el archivo y escribe en la terminal:

gunicorn --bind 0.0.0.0:8000 mi_server:app

una vez iniciado, verás algo así:

Entonces abre tu navegador web favorito y entra en la ruta http://localhost:8000/users/juan

Con eso ya tenemos nuestro servidor ejecutando. En breve haremos cambios para poder servir nuestro modelo de Machine Learning desde Flask al mundo 🙂

Crear el modelo de ML

Hagamos un ejemplo de un modelo de ML basándonos en el ejercicio de Pronóstico de Series Temporales que hace un pronóstico de ventas con redes neuronales con Embeddings. Esta vez no usaremos una notebook de Jupyter, si no, archivos de “texto plano” Python:

import pandas as pd
import numpy as np
from sklearn.preprocessing import MinMaxScaler

from utiles import *

df = pd.read_csv('time_series.csv',  parse_dates=[0], header=None,index_col=0, names=['fecha','unidades'])
df['weekday']=[x.weekday() for x in df.index]
df['month']=[x.month for x in df.index]
print(df.head())

EPOCHS=40
PASOS=7

scaler = MinMaxScaler(feature_range=(-1, 1))

reframed = transformar(df, scaler)

reordenado=reframed[ ['weekday','month','var1(t-7)','var1(t-6)','var1(t-5)','var1(t-4)','var1(t-3)','var1(t-2)','var1(t-1)','var1(t)'] ]
reordenado.dropna(inplace=True)

training_data = reordenado.drop('var1(t)', axis=1)
target_data=reordenado['var1(t)']
cant = len(df.index)
valid_data = training_data[cant-30:cant]
valid_target=target_data[cant-30:cant]

training_data = training_data[0:cant]
target_data=target_data[0:cant]
print(training_data.shape, target_data.shape, valid_data.shape, valid_target.shape)
print(training_data.head())

model = crear_modeloEmbeddings()

continuas = training_data[['var1(t-7)','var1(t-6)','var1(t-5)','var1(t-4)','var1(t-3)','var1(t-2)','var1(t-1)']]
valid_continuas = valid_data[['var1(t-7)','var1(t-6)','var1(t-5)','var1(t-4)','var1(t-3)','var1(t-2)','var1(t-1)']]

history = model.fit([training_data['weekday'],training_data['month'],continuas], target_data, epochs=EPOCHS,
                 validation_data=([valid_data['weekday'],valid_data['month'],valid_continuas],valid_target))

results = model.predict([valid_data['weekday'],valid_data['month'],valid_continuas])

print( 'Resultados escalados',results )
inverted = scaler.inverse_transform(results)
print( 'Resultados',inverted )

Ya logramos entrenar un nuevo modelo del que estamos conformes. Ahora veamos como guardarlo para poder reutilizarlo en la API!

Guardar el modelo; Serialización de objetos en Python

El proceso de serialización consiste en poder transformar nuestro modelo en ceros y unos que puedan ser almacenados en un archivo y que luego, al momento de la carga vuelva a regenerar ese mismo objeto, con sus características.

Aunque existen diversas maneras de guardar los modelos, comentemos rápidamente las que usaremos:

  • Pickle de Python para almacenar objetos (en nuestro caso un Transformador que debemos mantener para “reconvertir los resultados escalados” al finalizar de entrenar)
  • h5py para el modelo Keras (podemos guardar el modelo completo ó los pesos asociados a la red)
import pickle

#definimos funciones de guardar y cargar
def save_object(filename, object):
	with open(''+filename, 'wb') as file:
		pickle.dump(object, file)

def load_object(filename):
	with open(''+filename ,'rb') as f:
		loaded = pickle.load(f)
	return loaded

# guardamos los objetos que necesitaremos mas tarde
save_object('scaler_time_series.pkl', scaler)
model.save_weights("pesos.h5")

# cargamos cuando haga falta
loaded_scaler = load_object('scaler_time_series.pkl')
loaded_model = crear_modeloEmbeddings()
loaded_model.load_weights("pesos.h5")

Podemos comprobar a ver las predicciones sobre el set de validación antes y después de guardar los objetos y veremos que da los mismos resultados.

Crear una API con Flask

Ahora veamos el código con el que crearemos la API y donde incorporaremos nuestro modelo.

Utilizaremos los siguientes archivos:

Vamos a la acción:

  • Crearemos un método inicial que será invocado desde la url “predict”
  • Cargaremos el modelo que entrenamos previamente
  • Responderemos peticiones en formato JSON
"""Filename: server.py
"""
import pandas as pd
from sklearn.externals import joblib
from flask import Flask, jsonify, request

from utiles import *

app = Flask(__name__)

@app.route('/predict', methods=['POST'])
def predict():
	"""API request
	"""
	try:
		req_json = request.get_json()
		input = pd.read_json(req_json, orient='records')
	except Exception as e:
		raise e

	if input.empty:
		return(bad_request())
	else:
		#Load the saved model
		print("Cargar el modelo...")
		loaded_model = crear_modeloEmbeddings()
		loaded_model.load_weights("pesos.h5")

		print("Hacer Pronosticos")
		continuas = input[['var1(t-7)','var1(t-6)','var1(t-5)','var1(t-4)','var1(t-3)','var1(t-2)','var1(t-1)']]
		predictions = loaded_model.predict([input['weekday'], input['month'], continuas])

		print("Transformando datos")
		loaded_scaler = load_object('scaler_time_series.pkl')
		inverted = loaded_scaler.inverse_transform(predictions)
		inverted = inverted.astype('int32')

		final_predictions = pd.DataFrame(inverted)
		final_predictions.columns = ['ventas']
		
		print("Enviar respuesta")
		responses = jsonify(predictions=final_predictions.to_json(orient="records"))
		responses.status_code = 200
		print("Fin de Peticion")
		
		return (responses)

Muy bien, podemos ejecutar nuestra API desde la terminal para testear con:

gunicorn --bind 0.0.0.0:8000 server:app

Y ahora hagamos una petición para probar nuestra API con un archivo Python y veamos la salida:

import json
import requests
import pandas as pd
import pickle
from utiles import *

"""Setting the headers to send and accept json responses
"""
header = {'Content-Type': 'application/json', \
                  'Accept': 'application/json'}

# creamos un dataset de pruebas
df = pd.DataFrame({"unidades": [289,288,260,240,290,255,270,300], 
					"weekday": [5,0,1,2,3,4,5,0], 
					"month":   [4,4,4,4,4,4,4,4]})

loaded_scaler = load_object('scaler_time_series.pkl')

reframed = transformar(df, loaded_scaler)

reordenado=reframed[ ['weekday','month','var1(t-7)','var1(t-6)','var1(t-5)','var1(t-4)','var1(t-3)','var1(t-2)','var1(t-1)'] ]
reordenado.dropna(inplace=True)

"""Converting Pandas Dataframe to json
"""
data = reordenado.to_json(orient='records')

print('JSON para enviar en POST', data)

"""POST <url>/predict
"""
resp = requests.post("http://localhost:8000/predict", \
                    data = json.dumps(data),\
                    headers= header)
                    
print('status',resp.status_code)


"""The final response we get is as follows:
"""
print('Respuesta de Servidor')
print(resp.json())

Este ejemplo nos devuelve de salida:

{'predictions': '[{"ventas":194}]'}

Actualizar el modelo (según sea necesario!)

No olvidar que si nuestro modelo es dependiente del tiempo, ó de datos históricos, ó datos nuevos que vamos recopilando (nuevas muestras) deberemos reentrenar el modelo.

Eso se podría automatizar, re-ejecutando el archivo de entrenamiento y sobreescribiendo el archivo “h5py” que habíamos generado antes cada X días ó a raíz de otro evento que en nuestro negocio sea significativo y sea el detonador del re-entreno.

Conclusiones

En este artículo vimos que nuestro modelo de ML puede llegar a ser una pequeña pieza de un puzzle mayor y puede ofrecer soluciones a usuarios finales ó a otros subsistemas. Para poder ofrecer sus servicios podemos contar con diversas soluciones, siendo una de ellas el despliegue de una API. Podemos crear una API fácil y rápidamente con el web framework de Flask. Ya puedes ofrecer tus modelos al mundo!

NOTAS finales: Recuerda ejecutar los archivos en el siguiente orden:

  1. Copia el archivo timeseries.csv con los datos del ejercicio en tu server.
  2. Entrena el modelo y crea los archivos necesarios con python api_train_model.py
  3. Inicia el server desde una terminal con gunicorn como vimos anteriormente.
  4. Ejecuta el archivo de pruebas desde otra terminal con python test_api.py

Déjame tus comentarios y cuéntame qué proyectos Machine Learning te traes entre mano! Saludos.

Unete al Blog!

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

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

Recursos Adicionales

Descarga los archivos creados en este artículo

Otros artículos relacionados en Inglés

The post Tu propio Servicio de Machine Learning first appeared on Aprende Machine Learning.

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

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

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

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

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

¿Cómo surge Random Forest?

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

Cómo funciona Random Forest?

Random Forest funciona así:

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

¿Por qué es aleatorio?

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

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

Ventajas y Desventajas del uso de Random Forest

Vemos algunas de sus ventajas son:

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

Y sus desjeventajas:

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

Vamos al Código Python

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

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

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

Requerimientos para hacer el ejercicio Random Forest

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

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

Pues vamos con nuestro Bosque!

Creamos el modelo y lo entrenamos

Utilizaremos el modelo RandomForrestClassifier de SkLearn.

from sklearn.ensemble import RandomForestClassifier

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

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

Los Hiperparámetros más importantes

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

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

Evaluamos resultados

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

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

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

Comparamos con el Baseline

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

Conclusiones

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

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

Suscribete al blog

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

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

Recursos y Adicionales

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

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

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

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

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

]]>
Contrarrestar problemas con clases desbalanceadas

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

Tabla de contenidos:

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

Empecemos!

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

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

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

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

¿Cómo nos afectan los datos desbalanceados?

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

Métricas y Confusion Matrix

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

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

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

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

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

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

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

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

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

Tenemos cuatro casos posibles para cada clase:

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

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

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

Vamos al Ejercicio con Python!

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

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

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

Requerimientos Técnicos

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

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

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

pip install -U imbalanced-learn

Veamos el dataset

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

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

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

from pylab import rcParams

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

from collections import Counter

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

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

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

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

(284807, 31)

0 284315
1 492
Name: Class, dtype: int64

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

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

Estrategias para el manejo de Datos Desbalanceados:

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

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

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

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

Probando el Modelo “a secas” -sin estrategias-

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

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

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

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

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

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

Estrategia: Penalización para compensar

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

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

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

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

Sigamos con más métodos:

Estrategia: Subsampling en la clase mayoritaria

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

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

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

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

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

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

Estrategia: Oversampling de la clase minoritaria

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

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

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

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

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

Estrategia: Combinamos resampling con Smote-Tomek

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

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

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

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

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

Estrategia: Ensamble de Modelos con Balanceo

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

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

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

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

Resultados de las Estrategias

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

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

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

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

Conclusiones

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

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

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

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

Súmate a los suscriptores del Blog

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

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

Recursos

Artículos en Inglés

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

]]>
https://ftp.aprendemachinelearning.com/clasificacion-con-datos-desbalanceados/feed/ 20 6881