¡Machine Learning es divertido! Parte 2

Usar Machine Learning para generar un creador de niveles de Super Mario

This article is a translation to International Spanish of the one written by Adam Geitgey, the original in English is here.

El artículo original está disponible en español aquí. El original está en inglés y está escrito por Adam Geitgey. Esta traducción es hecha sin ánimo de lucro. Si encuentras errores, por favor menciónalos en un comentario para poder corregirlos.


En la parte 1, dijimos que Machine learning usa algoritmos genéricos para decirte algo interesante sobre tus datos sin necesidad de escribir código específico para el problema que estás intentando resolver. (Si no has leído la parte 1 todavía, ¡léela ahora!)

Ahora vamos a ver cómo estos algoritmos genéticos hacen algo realmente interesante — crear niveles de videojuegos que parecer hechos por personas. Construiremos una red neuronal, le daremos niveles existentes de Super Mario, y veremos como la redes crean nuevos.

Uno de los niveles que nuestro algoritmo va a generar

Como ya hicimos en la parte 1, esta guía es para cualquiera que tenga curiosidad sobre Machine learning pero no sepa dónde empezar. El objetivo es que todo el mundo pueda entender — lo que significa que hay muchas generalizaciones y que nos saltamos muchos detalles. Pero, ¿a quién le importa? Si ésto hace que más gente se interese por Machine Learning, entonces misión cumplida.

Adivinar con inteligencia

En la parte 1 creamos un algoritmo simple que hacía una aproximación sobre el precio de una casa basándose en sus atributos. Con datos sobre una casa como esta:

Terminábamos con una simple función de aproximación como ésta:

def estimate_house_sales_price(num_of_bedrooms, sqft, neighborhood):
price = 0
# a little pinch of this
price += num_of_bedrooms * 0.123
# and a big pinch of that
price += sqft * 0.41
# maybe a handful of this
price += neighborhood * 0.57
return price

Dicho de otra forma, estimábamos el valor de la casa al multiplicar cada uno de los atributos por un peso. Luego sumábamos esos números para conseguir el valor de la casa.

En vez de usar código, vamos a representar la misma función en un simple diagrama:

Las flechas representan los pesos en nuestra función.

Sin embargo, este algoritmo sólo funciona para problemas simples donde el resultado tiene una relación linear con los datos introducidos. ¿Qué pasa si la verdad detrás del precio de las casas no es tan simple? Por ejemplo, quizá el barrio importa mucho más para casas grandes y pequeñas, pero no importa en absoluto para casas de tamaño medio. ¿Cómo podemos captar este tipo de detalles complicados en nuestro modelo?

Para ser más inteligentes, podríamos ejecutar este algoritmo varias veces con diferentes pesos que cada vez capten un caso extremo diferente:

Intentemos resolver el problema en cuatro formas diferentes.

Ahora tenemos cuatro diferentes aproximaciones de precios. Podemos combinar esos cuatro precios en uno final. Utilizaremos estos nuevos precios como argumentos para ejecutar el mismo algoritmo (pero usando otros pesos).

Nuestra nueva Súper Respuesta combina las aproximaciones de nuestros cuatro intentos diferentes de resolver el problema. Debido a ésto, puede modelar más casos de los que pudimos captar en un simple modelo.

¿Qué es una red neuronal?

Combinemos nuestros cuatro intentos de adivinar el precio en un diagrama grande:

!Ésto es una red neuronal! Cada nodo conoce cómo tomar un grupo de argumentos, aplicarles pesos, y calcular un resultado. Si encadenamos muchos nodos como éstos, podemos modelar funciones complejas.

Hay muchas cosas que estoy ignorando para mantener esta explicación breve (incluyendo escalamiento de características y la función de activación), pero lo más importante es que la ideas básicas se entiendan bien:

  • Hicimos una función de aproximación simple que tomar un conjunto de argumentos y los multiplica por unos pesos para obtener un resultado. Esta función simple se llama una neurona.
  • Al encadenar muchas de esas simples neuronas, podemos modelar funciones que son muy complicadas para ser modeladas por una simple neurona.

!Es justo con LEGOS! No podemos modelar muchas cosas con un solo bloque de LEGO, pero podemos modelar cualquier cosa si tenemos suficientes bloques.

Cómo darle memoria a nuestra red neuronal

La red neuronal que hemos visto siempre devuelve la misma respuesta si le damos los mismo argumentos. No tiene memoria. En términos de programación, es un algoritmo sin estado.

En muchos casos (como el de aproximar el precio de una casa), eso es exactamente lo que queremos. Pero una de las cosas que este modelo no puede hacer es responder ante patrones cambiantes a través del tiempo.

Imagina que te diese un teclado y te pidiese que escribieras una historia. Pero antes de empezar, yo debo adivinar cuál es la primera letra que vas a escribir. ¿Qué letra debería adivinar?

Puedo usar mi conocimiento del inglés como lengua para incrementar mi probabilidad de adivinar la letra correcta. Por ejemplo, probablemente vas a escribir una letra que es muy común al principio de palabras. Si buscamos historias que hayas escrito en el pasado, podría limitarlo aún más basándome en las palabras que normalmente usas al principio de tus historias. Una vez que tuviese todo los datos, podría usarlos para construir una red neuronal que modele la posibilidad de qué tu empieces con cualquier letra.

Nuestro modelo podría verse así:

Pero hagamos el problema más difícil. Digamos que necesito adivinar la próxima letra que vas a escribir en cualquier lugar de tu historia. Éste es un problema mucho más interesante.

Como ejemplo, vamos a usar las primera palabras del libro de Ernest Hemingway Fiesta:

Robert Cohn solía ser un boxeador de medio pes

¿Qué letra viene ahora?

Probablemente adivinaste ‘o’ — la palabra va a ser probablemente peso. Sabemos esto basándonos en las letras que ya hemos visto en la frase y en nuestro conocimiento de español. También, las palabras ’boxeador de medio’ nos dan una pista extra para saber qué viene después.

En otras palabras, es fácil adivinar la siguiente letra si consideramos la secuencia de letras que hay antes y las combinamos con nuestro conocimiento del lenguaje.

Para resolver este problema con una red neuronal, necesitamos añadir un estado a nuestro modelo. Cada vez que le preguntemos algo a nuestra red neuronal, guardamos un conjunto de calculaciones intermedias y las reusamos la siguiente vez como parte de nuestros argumentos. De esta manera, nuestro modelo se ajusta a predicciones basadas en los datos introducidos más recientemente.

Supervisar el estado de nuestro modelo no sólo hace posible predecir la primera letra de la historia con bastante probabilidad, pero también la siguiente letra una vez introducidas todas las letras anteriores a ella.

¿Para qué sirve una sola letra?

Predecir la siguiente letra de una historia puede parecer que no sirve para nada. ¿Qué sentido tiene?

Un sentido puede ser el de auto-predecir la siguiente letra en el teclado de un teléfono móvil.

La próxima letra es posiblemente la letra “t”

Pero, ¿qué pasa si llevamos esta idea al extremo? ¿Qué pasa si le pedimos al modelo que prediga la siguiente letra una y otra vez — por siempre? !Le estaríamos pidiendo que escriba una historia completa para nosotros!

Generando una historia

Ya vimos cómo podemos adivinar la siguiente letra en la frase de Hemingway. Vamos a intentar general la historia completa en el estilo de Hemingway.

Para hacer eso, vamos a usar la implementación de una red neuronal recurrente que Andrej Karpathy escribió. Andrej es un investigador de Aprendizaje Profundo (Deep Learning) en Stanford y escribió una excelente introdución para generar texto con redes neuronales recurrentes. Puedes ver todo el código para el modelo en github.

Vamos a crear nuestro modelo a partir del texto completo de Fiesta — en su versión original con 362,239 caracteres usando 84 letras únicas (incluyendo signos de puntuación, mayúsculas, minúsculas, etc.). Estos datos es realmente pequeño comparado con una aplicación típica. Para generar un buen modelo del estilo de Hemingway, sería bueno tener tantas muestras de libros diferentes en diferentes momentos de su vida, pero nos basta con uno para jugar en este ejemplo.

Ya que acabamos de empezar a entrenar nuestra red neuronal recurrente (RNN por sus siglas en inglés), todavía no es muy buena prediciendo letras. Aquí está lo que genera después de 100 bucles de entrenamiento:

hjCTCnhoofeoxelif edElobe negnk e iohehasenoldndAmdaI ayio pe e h’e btentmuhgehi bcgdltt. gey heho grpiahe.
Ddelnss.eelaishaner” cot AAfhB ht ltny
ehbih a”on bhnte ectrsnae abeahngy
amo k ns aeo?cdse nh a taei.rairrhelardr er deffijha

Se puede ver que ha aprendido que a veces hay espacios entre palabras, pero no más.

Después de 1000 bucles, tiene mejor pinta:

hing soor ither. Y el caraos, y el crowebel para figttier y ale la habitación de mi? Streat no era para él Bill-stook de el momansbed mig out ust sobre el toro, ahí afuera. Yo estado soms
inick parando esa ayuda.
 “Hon’t me y acrained on .Hw’s no tu para el roed,” In’s par.”
 “Alough marith el.”

El modelo ha empezado a identificar patrones en la estructura básica de las frases. Añade puntos al final de la frases e incluso comillas para los diálogos. Se pueden reconocer algunas palabras, pero no hay cohesión.

Después de un par de miles de iteraciones, se ve muy bien:

Fue a la puerta del café. Era como una cama de campo. “¿Sabes que he sido yo?”
“Joder,” dijo Bill.
 “Yo era peligroso,” I said. “Tú eras ella lo hizo y piensa que yo haría un buena capa contigo,” yo dije.
 “No puedo verme raro en el taxi.”
 “Tú sabes que yo era esto es duro,” Brett said.
 “¿No vale la pena unas peleas?”
 “Hace hacer eso.”
 “¿Lo haces?”
 “Siéntate,” dije. “Me gustaría que no fuese hacer un poco con el hombre.”
 “Lo encontraste.”
 “No lo sé.”
 “Verás, lo siento de chatches,” Bill dijo. “Tú piensas que es un amigo lejos atrás y hace tú muy borracho.”

El algoritmo ya ha podido captar el patrón básico del diálogo corto y directo de Hemingway. Incluso un par de frases tienen sentido.

Compara el texto de arriba con un texto real de su libro:

Había unas cuantas personas dentro del bar, y fuera, Harvey Stone estaba sentado sólo. Tenía una montaña de platos frente a él, y tenía que afeitarse.
“Siéntate,” dijo Harvey, “Te he estado buscando.”
 “¿Qué pasa?”
 “Nada, sólo buscándote.”
 “¿Has estado en las carreras?”
 “No. No desde el sábdo.”
 “¿Qué noticias hay de USA?”
 “Nada. Absolutamente nada.”
 “¿Qué pasa?”

Incluso mirando los patrones carácter a carácter, nuestro algoritmo ha reproducido prosa plausible a simple vista con un formato acorde. ¡Es bastante increíble!

Tampoco tenemos que generar texto completamente desde cero. Podemos empezar el algoritmo con las primera letras y dejarlo encontrar el resto.

Por diversión, vamos a hacer una cubierta para el libro falsa para nuestro libro imaginario. Para ello, generaremos un nuevo nombre para el autor y un nuevo título utilizando “Er”, “He”, y “El S”* como semilla de nuestro algoritmo:

*En inglés, el libro Fiesta se llama The Sun also Rises. Una traducción literal sería El Sol también se levanta.

¡Nada mal!

Pero lo verdaderamente impresionante es que este algoritmo puede descubrir patrones en cualquier secuencia de datos. Fácilmente puede generar recetas que parecen verdaderas, o discursos falsos de Obama. Pero, ¿por qué limitarnos al lenguaje humano? Podemos aplicar esta idea a cualquier tipo de data secuencial que tenga un patrón.

Hacer Mario sin de verdad hacer Mario

En 2015, Nintengo sacó Super Mario Maker™ al mercado para la Wii U.

Este juego te deja dibujar tus propios niveles de Super Mario Bros. en una tablet de juego y después subirlo a internet para que tus amigos puedan jugar con ellos. Puedes incluir todos los poderes y enemigos del Mario original en tus niveles. Es como un LEGO virtual para personas que crecieron jugando Super Mario.

¿Podemos usar el mismo modelo que generó texto falso de Hemingway para generar niveles falsos de Super Mario Bros?

Primero necesitamos datos para entrenar nuestro modelo. Tomemos todos los niveles que transcurren en el exterior del Super Mario original desde que el juego salió en 1985:

Mejor regalo de navidad que existe. ¡Gracias papá y mamá!

Este juego tiene 32 niveles y alrededor del 70% de ellos tienen el mismo estilo del exterior, así que nos quedamos con esos.

Para obtener los diseños de cada nivel, tomé una copia original del juego y programé algo para sacar los diseños de la memoria de los juegos. Super Mario Bros. es un juego de hace 30 años y hay muchos recursos en línea para ayudarte a descubrir cómo están los niveles guardados en la memoria del juego. Extraer datos de cada nivel de un viejo videojuego es un ejercicio de programación divertido que deberías probar alguna vez.

Aquí está el primer nivel del juego (el cuál probablemente recuerdas is alguna vez jugaste):

Super Mario Bros. Nivel 1–1

Si miramos más de cerca, podemos ver que el nivel está hecho de un simple grid de objetos:

Fácilmente podríamos representar este grid como una secuencia de caracteres con un carácter representando cada objeto:

 — — — — — — — — — — — — — 
— — — — — — — — — — — — —
— — — — — — — — — — — — —
#??# — — — — — — — — — — —
— — — — — — — — — — — — —
— — — — — — — — — — — — —
— — — — — — — — — — — — —
-## — — — = — = — — — — — ==-
— — — — == — == — — — — ===-
— — — -=== — === — — — ====-
— — — ==== — ==== — — =====-
=========================-

Hemos reemplazado cada objeto en el nivel con una letra:

  • ‘-’ es espacio vacío.
  • ‘=’ es un bloque sólido.
  • ‘#’ es un ladrillo rompible.
  • ‘?’ es un bloque de una moneda.

…y así sucesivamente, usando un símbolo diferente para cada objeto en el nivel.

Mirando el documento con texto, puedes ver que los niveles de Mario no tienen un patrón si los lees línea a línea:

Leyendo línea a línea, no hay un patrón marcado. Muchas de las líneas no tienen nada

El patrón en un nivel emerge sólo cuando lees el nivel como una serie de columnas:

Leyendo columna a columna, hay un patrón. Por ejemplo, cada columna termina en ‘=’

Así que para que el algoritmo encuentre los patrones en los datos, tenemos que dárselos columna a columna. Descubrir la representación más efectiva de tus datos (llamado selección de característica/feature selection) es uno de los puntos más importantes para usar algoritmos de machine learning bien.

Para entrenar un modelo, tuve que rotar mis archivos 90 grados. Esto hizo que los caracteres fuesen introducidos en el modelo en el orden en el que un patrón se ve más fácilmente:

 — — — — — -=
— — — -# — -=
— — — -# — -=
— — — -? — -=
— — — -# — -=
— — — — — -=
— — — — — -=
— — — — — @=
— — — — — @=
— — — — — -=
— — — — — -=
— — — — — -=
— — — — -PP=
— — — — -PP=
— — — — — ==
— — — — -===
— — — — ====
— — — -=====
— — — ======
— — -=======
— -=========
— -=========

Entrenar nuestro modelo

Justo como vimos cuando creamos el modelo de la prosa de Hemingway, un modelo mejora entre más lo entrenemos.

Después de un poquito de entrenamiento, nuestro modelo genera cosas sin sentido:

 — — — — — — — — — — — — — 
LL+<&= — — — P — — — — — — -
— — — —
— — — — — — — — — — -T — # —
— — -
-= — =-= — — — — — — =-& — T — — — — — — —
— — — — — — — — — —
— = — — — $-=#-=-_
— — — — — — — = — — =← —
— — — -b
-

Parece que ha entendido que ‘-’ y ‘=’ deben aparecer bastante, pero ya está. No ha descubierto un patrón todavía.

Después de varios miles de iteraciones, empieza a tener forma:


— — — — — -=
— — — — — =
— — — — PP=
— — — — PP=
— — — — — -=
— — — — — -=
— — — — — -=
— — — -? — -=
— — — — — -=
— — — — — -=

El modelo parece haberse dado cuenta de que cada línea debería tener el mismo tamaño. Incluso ha empezado a descubrir parte de la lógica de Mario: Las tuberías en Mario tienen siempre dos bloques de ancho y mínimo dos bloques de alto, así que las “P”s en los datos deberían aparecer en grupos de 2x2. ¡Está bastante bien!

Con mucho más entrenamiento, el modelo llega al punto que genera datos perfectamente válidos:

 — — — — PP=
— — — — PP=
— — — — — =
— — — — — =
— — — — — =
— -PPP= — -=
— -PPP= — -=
— — — — — =

Si tomamos suficientes datos de nuestro model para crear un nivel y los rotamos otras vez, tenemos:

¡Un nivel completo generado en base a nuestro modelo!

¡Los datos se ven muy bien! Hay varias cosas bastante fascinantes a tener en cuenta:

  • Puso un Lakitu (el monstruo que flota en una nube) en el cielo al principio del nivel-exacto como en el nivel real de Mario.
  • Sabe que tuberías que flotan en el aire deberían estar construidas sobre bloques sólidos y no sólo flotar ahí.
  • Pone enemigos en lugares lógicos.
  • No crea nada que pudiese evitar que un jugador siguiese hacia adelante.
  • Parece un nivel real de Super Mario Bros. Primero, porque está basado en el estilo de los niveles originales que existían en el juego.

Finalmente, así se ve el nivel reconvertido en gráficos con Super Mario Maker:

Nuestro nivel después de ser introducido en Super Mario Maker

http://https://youtu.be/_-Gc6diodcY

¡Puedes jugarlo tú mismo!

Si tienes Super Mario Maker, puedes jugar este nivel marcándolo en línea (un bookmark) o buscándolo con el código 4AC9–0000–0157-F3C3.

Juguete vs. Aplicaciones de verdad

El algoritmo de la red neuronal recurrente que usamos para entrenar nuestro modelo es el mismo que se utilizar en compañías reales para resolver problemas difíciles tales como reconocimiento de voz y traducción de idiomas. Lo que hace que nuestro modelo sea un ‘juguete’ en vez de uno profesionalísimo es que generamos el nuestro con muy pocos datos. Sencillamente no hay suficientes niveles en el Súper Mario Brothers original como para obtener suficientes datos y entrenar un buen modelo.

Si pudiésemos acceder a los cientos de miles de niveles creados por usuarios con el Super Mario Maker que nintendo tiene, podríamos hacer un modelo realmente bueno. Pero no podemos-porque Nintendo no nos deja. Grandes compañías no dejan acceder a sus datos de manera gratuita.

A medida que machine learning adquiere importancia en más industrias, la diferencia entre un buen programa y uno malo va a ser cuántos datos tienes para entrenar tu modelo. !Es por eso que compañías como Google o Facebook necesitan tus datos tan desesperadamente!

Por ejemplo, Google recientemente hizo público el código de TensorFlow, su juego de herramientas para construir aplicaciones de machine learning a gran escala. Fue muy impresionante que Google diese una tecnología tan importante y capaz de manera gratuita. Es las misma que está detrás de Google Translate.

Pero sin la masiva cantidad de datos que tiene Google sobre cada idioma, no puedes creare un competidor de Google Translate. Los datos son los que hacen a Google diferente. Piensa en eso la próxima vez que abras tu historial de lugares en Google Maps o tu historial de lugares en Facebook y fíjate que guarda cada lugar en el que has estado.

Otras lecturas

Nunca hay sólo una forma de resolver un problema en machine learning. Tienes opciones ilimitadas para decidir cómo pre-procesar tus datos y qué algoritmo quieres usar. A veces la combinación de múltiples soluciones te dará mejores resultados que una solución única.

Algunos lectores me han enviado links de otros enfoques para generar niveles de Súper Mario:
- Amy K. Hoover y su equipo usaron un enfoque que [representa cada tipo de objeto de nivel (tuberías, suelo, plataformas, etc.) como si fuesen una voz única en una sinfonía general. Usando un proceso llamado andamiaje funcional (functional scaffolding), el sistema puede aumentar niveles con bloques de todo tipo. Por ejemplo, podrías hacer un esquema de la forma básica de un nivel y el algoritmo podría añadir tuberías y bloques con signo de interrogación para completar tu diseño.

- Steve Dahlskog y su equipo demostraron que modelar cada columna como una serie de “palabras” n-gramas hace posible generar niveles con un algoritmo mucho más simple y pequeño que una RNN.


Si te gustó este artículo, plantéate subscribirte a mi lista de emails ¡Machine Learning es divertido!. Sólo envío emails cuando tengo algo nuevo y verdaderamente fascinante para compartir. Es la mejor forma de enterarse cuando escribo artículos como éste.

También puedes seguirme en Twitter @ageitgey, escribirme un email o encontrarme en LinkedIn. Me encanta que me contacten para ayudar a individuos o equipos con machine learning.

Ahora continua con ¡Machine Learning es divertido: Parte 3! (por el momento en inglés).

Like what you read? Give Luisa Castano a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.