Jugar a Connect Four con Deep Q-Learning

exploramos cómo extender el aprendizaje por refuerzo (RL) más allá de la configuración tabular mediante la aproximación de funciones. Si bien esto nos permitió generalizar entre estados, nuestros experimentos también revelaron una limitación importante: en entornos simples como GridWorld, los métodos aproximados pueden tener dificultades para igualar la estabilidad y eficiencia de los enfoques tabulares. La razón principal es que aprender una buena representación es en sí mismo un problema difícil, que puede superar los beneficios de la generalización cuando el espacio de estados es todavía relativamente pequeño.

Por lo tanto, para desbloquear verdaderamente el poder de la aproximación de funciones, debemos trasladarnos a entornos donde los métodos tabulares ya no sean viables. Esto nos lleva naturalmente a los juegos multijugador, donde el espacio de estados crece combinatoriamente y la generalización se vuelve esencial, y al mismo tiempo encaja perfectamente en esta serie de publicaciones, ya que hasta ahora no hemos logrado aprender ningún comportamiento significativo en entornos multijugador más complejos. En esta publicación, damos este paso considerando el clásico juego de Connect Four e investigamos cómo aprender políticas sólidas utilizando Deep Q-Learning.

De Sarsa al Q-Learning profundo

Para abordar esta tarea, ampliamos nuestro marco a varias dimensiones importantes.

Primero, pasamos de las actualizaciones en línea a una configuración de capacitación por lotes. En nuestra implementación anterior de Sarsa, actualizamos el modelo después de cada transición. Si bien es fiel al algoritmo original [1]este enfoque es computacionalmente ineficiente: cada paso del optimizador genera un costo no trivial, y el hardware moderno (especialmente las GPU) está diseñado para operar en lotes con solo una sobrecarga adicional marginal.

Para solucionar esto, introducimos un búfer de reproducción. En lugar de actualizar inmediatamente, almacenamos las transiciones a medida que se encuentran, ya sea hasta una capacidad fija o, en nuestro caso, hasta que uno o varios juegos hayan finalizado. Luego realizamos una actualización por lotes de esta experiencia recopilada. Esto no solo mejora la eficiencia computacional sino que también estabiliza el aprendizaje al reducir la variación de las actualizaciones individuales.

En este punto se produce un importante cambio conceptual. Al tomar muestras de experiencias pasadas en lugar de seguir estrictamente la política actual, nos alejamos del Sarsa (un método dentro de la política) hacia el Q-learning, que está fuera de la política. Si bien aquí no hemos reintroducido formalmente Q-learning en la configuración de aproximación de funciones, la extensión del caso tabular es en gran medida sencilla. Esta combinación de buffers de reproducción y Q-learning forma la base de Deep Q-Networks (DQN), popularizada por DeepMind en su trabajo fundamental sobre los juegos de Atari. [2].

Finalmente, pasamos a la escalabilidad. El aprendizaje por refuerzo requiere inherentemente un gran consumo de datos, por lo que aumentar el rendimiento es crucial. Para ello, implementamos un contenedor de entorno vectorizado que nos permite simular múltiples juegos de Connect Four en paralelo. Concretamente, una única llamada al paso (a) ahora procesa un lote de acciones y hace avanzar todos los entornos simultáneamente.

En la práctica, sin embargo, lograr un verdadero paralelismo en Python no es trivial. El bloqueo global de intérprete (GIL) garantiza que solo un subproceso ejecute el código de bytes de Python a la vez, lo que limita la efectividad de los subprocesos múltiples para cargas de trabajo vinculadas a la CPU, como el paso del entorno. También experimentamos con multiprocesamiento, pero descubrimos que la sobrecarga adicional (por ejemplo, comunicación entre procesos) contrarrestaba en gran medida cualquier ganancia en nuestro entorno. Para el lector interesado, recomiendo una publicación mía anterior.

A pesar de estas limitaciones, la combinación de actualizaciones por lotes y vectorización del entorno produce una mejora sustancial en el rendimiento, aumentando el rendimiento a aproximadamente 50 a 100 juegos por segundo.

Implementación

En esta publicación, evito deliberadamente entrar en demasiados detalles sobre la vectorización del entorno y, en cambio, me centro en los aspectos de RL. En parte, esto se debe a que la vectorización en sí es “sólo” un detalle de implementación, pero también a que, con toda honestidad, nuestra configuración actual no es ideal. Gran parte de esto se debe a las limitaciones impuestas por el entorno de PettingZoo que utilizamos.

En publicaciones futuras, exploraremos diferentes entornos y revisaremos este tema con un mayor énfasis en la escalabilidad, un aspecto crucial del aprendizaje por refuerzo moderno. Para una discusión más detallada sobre cómo estructuramos entornos multijugador, administramos agentes y mantenemos un grupo de oponentes, me refiero a mi publicación anterior sobre RL multijugador. La configuración vectorizada utilizada aquí es simplemente una extensión de ese marco para múltiples juegos que se ejecutan en paralelo. Como siempre, la implementación completa está disponible en GitHub.

Revisando Q-Learning

Revisemos brevemente Q-learning y conectémoslo con nuestra implementación.

La regla de actualización principal viene dada por:

A diferencia de Sarsa, que utiliza la acción realmente realizada en el siguiente estado, Q-learning utiliza un operador máximo sobre todas las siguientes acciones posibles. Esto lo hace fuera de política, ya que la actualización no depende de la política de comportamiento utilizada para generar los datos. En la práctica, esto a menudo conduce a una propagación más rápida de la información de valor, especialmente en entornos deterministas como los juegos de mesa.

Cuando se combina con redes neuronales, este enfoque se denomina comúnmente Q-Learning profundo. En lugar de mantener una tabla de valores, entrenamos una red neuronal Qθ(s,a)Q_\theta(s,a) para aproximar la función acción-valor. Luego, la actualización se implementa como un problema de regresión, minimizando la diferencia entre la estimación actual y un objetivo inicializado:

En nuestra implementación, esto corresponde directamente a la función batch_update. Dado un lote de transiciones (s,a,r,s′,done)(s, a, r, s’, \text{done}), primero calculamos los valores Q predichos para las acciones tomadas:

q = self.q(batch.states, …) q_sa = q.gather(1, lote.acciones.unsqueeze(1)).squeeze(1)

A continuación, construimos el objetivo utilizando el valor Q máximo del siguiente estado. Dado que no todas las acciones son legales en Connect Four, aplicamos una máscara para garantizar que solo se consideren movimientos válidos:

q_next = self.q(batch.next_states, …) q_next_masked = q_next.masked_fill(~legal, float(“-inf”)) max_next = q_next_masked.max(dim=1).values

Finalmente, combinamos la recompensa y el valor descontado del siguiente estado, teniendo cuidado de manejar los estados terminales correctamente:

objetivo = lote.recompensas + gamma * (~batch.dones).float() * max_next

Luego, la red se entrena minimizando la pérdida de Huber (una variante más robusta del error cuadrático medio):

pérdida = F.smooth_l1_loss(q_sa, objetivo)

Esta formulación basada en lotes nos permite reutilizar de manera eficiente la experiencia recopilada de múltiples juegos paralelos, lo cual es crucial para escalar a entornos más complejos. Al mismo tiempo, destaca un desafío clave del Deep Q-Learning: los propios objetivos dependen de la red actual, lo que puede provocar inestabilidad durante el entrenamiento.

Para obtener una referencia adicional, el tutorial oficial de PyTorch sobre Deep Q-Learning proporciona una perspectiva complementaria útil.

Resultados

Una vez aclarado esto, pasemos a los resultados. Para ponerlos en perspectiva, primero recordemos cómo se desempeñaron los métodos tabulares en esta tarea. Después de 100.000 pasos, la mayoría de las políticas todavía estaban muy agrupadas en términos de tasa de éxito. En particular, incluso una política aleatoria logró aproximadamente una tasa de éxito del 50%, lo que indica que ninguna de las políticas aprendidas había logrado superar al azar de manera significativa.

Imagen del autor

En el siguiente experimento, nos centramos en dos agentes: nuestro DQN y una línea de base aleatoria. Debido a la configuración de “zoológico” introducida anteriormente, la DQN no es una política fija única sino un conjunto de agentes en evolución. Continuamente agregamos nuevas versiones y eliminamos las más débiles, lo que aumenta gradualmente la fuerza general del grupo de oponentes.

Esto tiene una implicación importante para interpretar las métricas:
La tasa de victorias de “DQN vs. DQN” naturalmente ronda el 50%, ya que agentes de fuerza similar compiten entre sí. Una señal más informativa es, por tanto, la ejecución de la política aleatoria. A medida que mejora el DQN, el agente aleatorio debería ganar con menos frecuencia.

Teniendo esto en cuenta, veamos la curva de rendimiento:

Imagen del autor

Observamos varios efectos interesantes. Lo más notable es que la tasa de ganancia de la política aleatoria cae significativamente más rápido que en el entorno tabular: evidencia clara de que el DQN de hecho está aprendiendo el juego. Sin embargo, después de alrededor de un millón de pasos, la mejora se estabiliza y la política aleatoria sigue ganando aproximadamente el 20% de los juegos.

Para comprender mejor lo que esto significa en la práctica, podemos evaluar la política aprendida frente a un jugador humano. En el siguiente ejemplo, tomo el papel del jugador rojo que va primero:

Imagen del autor

El resultado es bastante revelador. El agente claramente ha aprendido a jugar ofensivamente: persigue activamente su propio cuatro en raya. Sin embargo, tiene dificultades con el juego defensivo, ya que no logra anticipar ni bloquear amenazas simples del oponente.

Probablemente esto sea un poco decepcionante, pero volveremos sobre esto. En publicaciones futuras aprenderemos cómo escalar mejor, aprender más rápido y vencer a los humanos (en muchas cosas). Escribir esta serie de publicaciones sobre el gran libro de Sutton ha sido un viaje increíble (aunque todavía quedan algunas publicaciones), pero simplemente hemos superado el marco general con el que comenzamos para mostrar todos los algoritmos disponibles en el libro de Sutton, que cubren métodos de solución tanto tabulares como aproximados. Por lo tanto, la especialización es el camino a seguir, y en el futuro haremos exactamente eso, escribiendo métodos personalizados y altamente eficientes para diferentes problemas.

Conclusión

En esta publicación, pasamos de Sarsa tabular a Deep Q-Learning, introduciendo búferes de reproducción, actualizaciones por lotes y aproximación de funciones. Aplicamos esto a Connect Four, un juego multijugador que anteriormente no pudimos resolver con métodos tabulares, con un resultado claro: nuestro agente ya no está estancado en el nivel de azar: aprende, mejora y supera consistentemente una política aleatoria.

Pero lo que es igualmente importante es que también vemos los límites.

Incluso después de un entrenamiento extenso, el agente se estanca y todavía muestra claras debilidades, sobre todo en el juego defensivo. No se trata sólo de “más formación”. En entornos multijugador, el problema en sí se vuelve más difícil: los oponentes evolucionan, el entorno ya no es estacionario y los objetivos de aprendizaje siguen cambiando.

Aquí es donde comienza el verdadero desafío.

Hasta este punto, nuestro marco, siguiendo vagamente [1] —ha priorizado la generalidad y la claridad. Pero para ir más allá, eso ya no es suficiente. El desempeño requiere especialización.

En las próximas publicaciones, primero seguiremos [1] – y luego se centrará exactamente en eso: construir sistemas más rápidos, más estables y más escalables, yendo más allá de simples líneas de base hacia agentes que realmente puedan competir.

Otras publicaciones de esta serie

Referencias

[1] http://incompleteideas.net/book/RLbook2020.pdf

[2] https://arxiv.org/abs/1312.5602