Aprendizaje por refuerzo - Parte 2 - La revancha del 21

Ivan Moreno, 03 December 2018

Reintenté el 21. Con el SARSA. Ahora si quedó bonito. Fue mucho más fácil esta vez. Entra a esta publicación para saber como.

Hace unas semanas (o meses) andaba aprendiendo programación dinámica. Durante ese periodo de la clase intenté encontrar una política buena para jugar al 21, pero con resultados mixtos. Después de la unidad de aprendizaje por refuerzo, sin embargo, si encontré una buena política. Todo gracias a la combinación de muchas cosas.

Simular muchos juegos para aprender

Una de las primeras ventajas de usar aprendizaje por refuerzo es que ya no es necesario conocer todo el modelo de nuestro entorno, solo ocupamos saber lo mínimo necesario para poder simular a pasos. Entonces, podemos aprovechar una de las cosas más comunes de la inteligencia artificial: fuerza bruta. Así, podemos tener un modelo simplificado sobre el cual simulamos una y otra vez.

En este caso, podemos modelar al 21 si codificamos las siguientes partes:

  • Estados no terminales y terminales
  • Acciones legales para cada estado
  • Transiciones hacia adelante de un estado a otro
  • Recompensa para cada estado

Cuando tengamos eso cubierto, podemos poner a la computadora a jugar 21 durante horas.

Modelo simplificado para el 21

Iremos en orden: primero los estados. Para simular, solo tomaremos en cuenta lo que el agente vería en un juego de verdad: sus cartas, las cartas destapadas del repartidor, y si ya terminó la partida. Así, un estado lo podemos describir como

\[ (N_j, S_j, S_r, t) \]

Donde $N_j$ es la cantidad de cartas del agente, $S_j$ y $S_r$ son la suma de las cartas del agente y del repartidor respectivamente, y $t$ es un entero que indica si es un estado terminal o no.

La función de acciones legales simplemente revisa si $t$ es 0 o 1. Si es 0, regresa las únicas dos acciones que existen para este 21: pedir y quedarse. Cuando $t$ es 1, regresa nothing, que es la palabra reservada para la nada en Julia.

Otra parte sencilla es la función de recompensas. Esta te regresa 0 para cualquier estado no terminal y, 1 o -1 para estados terminales dependiendo de si el agente ganó o perdió.

Por último, debemos definir como se darán las transiciones entre estados. Si el agente decide pedir cartas, se le suma una carta aleatoria. Si decide quedarse, se le suma una carta al repartidor. Bastante sencillo, la verdad. El código de esta función no es elegante, pero si verbatim a lo que acabo de decir.

Resultados

Después de usar el modelo descrito en la sección anterior junto con una política $ \epsilon $-greedy y SARSA, salió una política que al menos tiene sentido. Cuando no está muy cerca de 21, pide cartas, y cuando está peligrosamente cerca de pasarse, decide mejor quedarse. Vaya, hasta parece que el método funciona si le das algo útil de entrada.

Pero aquí no acaba la experimentación. Pronto escribiré sobre los resultados de entrenar un agente con Q-learning y tile coding para resolver el problema del acrobot. ¡Y también usando la librería Gym de OpenAI! Pero en Python, porque hay problemas en Julia, que serán tema para la siguiente publicación.

Por último, si estabas pensando que solo andaba diciendo cosas al aire, te tengo buenas noticias: todo el código descrito aquí está implementado en esta libreta de Jupyter. Solo no le veo mucho punto a repetir el código en ambas partes. Además de que así puedes disfrutar de lo bonito y útil que es el nbviewer.