Entrenando a un Agente para Dominar el Tic-Tac-Toe a través de Autojuego
Entrenando a un Agente para el Tic-Tac-Toe mediante Autojuego
Sorprendentemente, un agente de software nunca se cansa del juego.
¡Ah! la escuela primaria! Este fue el momento en el que aprendimos habilidades valiosas, como la alfabetización, la aritmética y jugar al tres en raya de manera óptima.
Jugar una partida de tres en raya con un amigo sin ser atrapado por el profesor es un arte. Debes pasar discretamente la hoja de juego por debajo del escritorio mientras das la impresión de estar atento al tema. La diversión probablemente era más sobre la operación encubierta que sobre el juego en sí.
No podemos enseñar el arte de evitar ser atrapado en el aula a un agente de software, ¿pero podemos entrenar a un agente para que domine el juego?
En mi publicación anterior, estudiamos un agente que aprendía el juego SumTo100 a través del autojuego. Era un juego fácil que nos permitía mostrar el valor del estado, lo que nos ayudó a construir una intuición sobre cómo el agente aprende el juego. Con el tres en raya, estamos abordando un espacio de estados mucho más grande.
Puedes encontrar el código Python en este repositorio. El script que realiza el entrenamiento es learn_tictactoe.sh:
- ChatGPT Análisis avanzado de datos para gráficos personalizados de ...
- Magia Cuántica Creando Criaturas Míticas con Computación Cuántica
- Empezando con IA/ML para construir cadenas de suministro inteligentes
#!/bin/bashdeclare -i NUMBER_OF_GAMES=30000declare -i NUMBER_OF_EPOCHS=5export PYTHONPATH='./'python preprocessing/generate_positions_expectations.py \ --outputDirectory=./learn_tictactoe/output_tictactoe_generate_positions_expectations_level0 \ --game=tictactoe \ --numberOfGames=$NUMBER_OF_GAMES \ --gamma=0.95 \ --randomSeed=1 \ --agentArchitecture=None \ --agentFilepath=None \ --opponentArchitecture=None \ --opponentFilepath=None \ --epsilons="[1.0]" \ --temperature=0 dataset_filepath="./learn_tictactoe/output_tictactoe_generate_positions_expectations_level0/dataset.csv" python train/train_agent.py \ $dataset_filepath \ --outputDirectory="./learn_tictactoe/output_tictactoe_train_agent_level1" \ --game=tictactoe \ --randomSeed=0 \ --validationRatio=0.2 \ --batchSize=64 \ --architecture=SaintAndre_1024 \ --dropoutRatio=0.5 \ --learningRate=0.0001 \ --weightDecay=0.00001 \ --numberOfEpochs=$NUMBER_OF_EPOCHS \ --startingNeuralNetworkFilepath=None for level in {1..16}do dataset_filepath="./learn_tictactoe/output_tictactoe_generate_positions_expectations_level${level}/dataset.csv" python preprocessing/generate_positions_expectations.py \ --outputDirectory="./learn_tictactoe/output_tictactoe_generate_positions_expectations_level${level}" \ --game=tictactoe \ --numberOfGames=$NUMBER_OF_GAMES \ --gamma=0.95 \ --randomSeed=0 \ --agentArchitecture=SaintAndre_1024 \ --agentFilepath="./learn_tictactoe/output_tictactoe_train_agent_level${level}/SaintAndre_1024.pth" \ --opponentArchitecture=SaintAndre_1024 \ --opponentFilepath="./learn_tictactoe/output_tictactoe_train_agent_level${level}/SaintAndre_1024.pth" \ --epsilons="[0.5, 0.5, 0.1]" \ --temperature=0 declare -i next_level=$((level + 1)) python train/train_agent.py \ "./learn_tictactoe/output_tictactoe_generate_positions_expectations_level${level}/dataset.csv" \ --outputDirectory="./learn_tictactoe/output_tictactoe_train_agent_level${next_level}" \ --game=tictactoe \ --randomSeed=0 \ --validationRatio=0.2 \ --batchSize=64 \ --architecture=SaintAndre_1024 \ --dropoutRatio=0.5 \ --learningRate=0.0001 \ --weightDecay=0.00001 \ --numberOfEpochs=$NUMBER_OF_EPOCHS \ --startingNeuralNetworkFilepath="./learn_tictactoe/output_tictactoe_train_agent_level${level}/SaintAndre_1024.pth" done
El script recorre llamadas a dos programas:
- generate_positions_expectations.py: Simula partidas y almacena estados del juego con el retorno esperado descontado.
- train_agent.py: Entrena la red neuronal durante algunas épocas con el conjunto de datos más reciente generado.
El ciclo de entrenamiento
El aprendizaje del juego por parte del agente se lleva a cabo a través de un ciclo de generación de partidas y entrenamiento para predecir el resultado de la partida a partir del estado del juego:

Generación de partidas
El ciclo comienza con la simulación de partidas entre jugadores aleatorios, es decir, jugadores que eligen al azar de la lista de acciones legales en un estado de juego dado.
¿Por qué generamos partidas jugadas al azar?
Este proyecto se trata de aprender mediante el autojuego, por lo que no podemos darle al agente ninguna información a priori sobre cómo jugar. En el primer ciclo, dado que el agente no tiene idea de qué movimientos son buenos o malos, las partidas deben generarse mediante juego aleatorio.
La Figura 2 muestra un ejemplo de una partida entre jugadores aleatorios:

¿Qué lección podemos aprender al observar esta partida? Desde el punto de vista del jugador ‘X’, podemos asumir que este es un ejemplo de juego deficiente, ya que concluyó en una derrota. No sabemos qué movimiento(s) es/son responsables de la derrota, por lo que asumiremos que todas las decisiones tomadas por el jugador ‘X’ fueron malas. Si algunas decisiones fueron buenas, confiamos en la estadística (otras simulaciones podrían pasar por un estado similar) para rectificar su valor de estado predicho.
La última acción del jugador ‘X’ recibe un valor de -1. Las otras acciones reciben un valor negativo descontado que decae geométricamente por un factor γ (gamma) ∈ [0, 1] a medida que retrocedemos hacia el primer movimiento.

Los estados de partidas que resultaron en una victoria reciben valores descontados positivos similares. Los estados obtenidos de empates reciben un valor de cero. El agente adopta el punto de vista tanto del primer como del segundo jugador.
Los estados de juego como tensores
Necesitamos una representación tensorial para el estado del juego. Utilizaremos un tensor [2x3x3] donde la primera dimensión representa los canales (0 para ‘X’ y 1 para ‘O’), y las otras dos dimensiones son las filas y las columnas. La ocupación de un cuadrado (fila, columna) se codifica como un 1 en la entrada (canal, fila, columna).
![Figura 4: La representación del estado del juego mediante un tensor [2x3x3]. Imagen del autor.](https://miro.medium.com/v2/resize:fit:640/format:webp/1*MJ0TM_9SmsJBGi01mufQxA.png)
Los pares de (tensor de estado, valor objetivo) obtenidos mediante la generación de partidas constituyen el conjunto de datos sobre el cual la red neuronal se entrenará en cada ronda. El conjunto de datos se construye al comienzo del ciclo, aprovechando el aprendizaje que ha ocurrido en rondas anteriores. Mientras que la primera ronda genera un juego completamente aleatorio, las siguientes rondas generan partidas gradualmente más realistas.
Inyección de aleatoriedad en el juego
La primera ronda de generación de partidos enfrenta a jugadores aleatorios. Las rondas siguientes enfrentan al agente consigo mismo (de ahí el “autojuego”). El agente está equipado con una red neuronal de regresión entrenada para predecir el resultado del partido, lo que le permite elegir la acción legal que produce el mayor valor esperado. Para promover la diversidad, el agente elige acciones basadas en un algoritmo epsilon-greedy: con una probabilidad (1-ε), se elige la mejor acción; de lo contrario, se elige una acción aleatoria.
Entrenamiento
La Figura 5 muestra la evolución de las pérdidas de validación durante cinco épocas para un máximo de 17 rondas de entrenamiento:

Podemos ver que las primeras rondas de entrenamiento muestran una rápida disminución en la pérdida de validación, y luego parece haber un plateau alrededor de una pérdida de error cuadrático medio de 0.2. Esta tendencia muestra que la red neuronal de regresión del agente mejora en la predicción del resultado de un partido jugado contra sí mismo, desde un estado de juego dado. Dado que las acciones de ambos jugadores no son deterministas, existe un límite en la predictibilidad del resultado del partido. Eso explica por qué la pérdida de validación deja de mejorar después de algunas rondas.
Mejora de ronda a ronda
Con el juego SumTo100, podríamos representar el estado en una cuadrícula 1D. Sin embargo, con el tic-tac-toe, no podemos mostrar directamente la evolución del valor del estado. Una cosa que podemos hacer para medir la mejora es enfrentar al agente contra la versión anterior de sí mismo y observar la diferencia entre las victorias y las derrotas.
Utilizando ε = 0.5 para la primera acción de ambos jugadores y ε = 0.1 para el resto del partido, jugando 1000 partidos por comparación, esto es lo que obtenemos:

El número de victorias superó al número de derrotas (mostrando una mejora) hasta 10 rondas de entrenamiento. Después de eso, el agente no mejoró de ronda a ronda.
Prueba
¡Es hora de ver cómo nuestro agente juega al tic-tac-toe!
Una característica útil de tener una red neuronal de regresión es la posibilidad de mostrar la evaluación del agente de cada movimiento legal. Juguemos una partida contra el agente, mostrando cómo juzga sus opciones.
Juego manual
El agente comienza, jugando con ‘X’:

¡Así es como eres aplastado brutalmente por una máquina sin alma de tic-tac-toe!
Tan pronto como puse la ‘O’ en el cuadro (1, 0), el retorno esperado aumentó de 0.142 a 0.419, y mi destino estaba sellado.
Veamos cómo lo hace cuando el agente juega de segundo:

No cayó en la trampa y el partido terminó en empate.
Partidos contra un jugador aleatorio
Si simulamos un gran número de partidos contra un jugador aleatorio, esto es lo que obtenemos:

De los 1000 partidos (el agente jugó primero en la mitad de los partidos), el agente ganó 950 partidos, no perdió ninguno y hubo 50 empates. Esto no es una prueba de que nuestro agente esté jugando de manera óptima, pero ciertamente ha alcanzado un nivel de juego decente.
Conclusión
Como continuación de Entrenamiento de un agente para dominar un juego simple a través del autojuego donde el juego era fácil de resolver y el espacio de estados era pequeño, utilizamos la misma técnica para dominar el tres en raya. Aunque este sigue siendo un problema de juguete, el espacio de estados del tres en raya es lo suficientemente grande como para que la red neuronal de regresión del agente deba encontrar patrones en los tensores de estado. Estos patrones permiten la generalización para tensores de estado no vistos previamente.
El código está disponible en este repositorio. ¡Pruébalo y déjame saber qué opinas!