
- Esta vez se selecciona el juego "Serpiente".
- Se ha creado una biblioteca para la red Go.
- Se encuentra el principio de aprendizaje, dependiendo de la "profundidad" de la memoria.
- Se ha escrito un servidor para el juego entre desarrolladores.
La esencia del juego
Quizás muchas personas recuerdan el juego "Snake", que era una aplicación estándar en los teléfonos Nokia. Su esencia es esta: una serpiente se mueve por el campo, que disminuye si no encuentra comida, o aumenta si la encuentra. Si una serpiente se estrella contra un obstáculo, entonces muere.
Cambié un poco las reglas: la serpiente no muere si se bloquea, sino que simplemente se detiene y continúa disminuyendo. Además, la serpiente se puede dividir por la mitad. Si a la serpiente le queda una célula en el cuerpo y no puede encontrar comida en 10 movimientos, entonces muere, convirtiéndose en comida.
Entrenaremos al bot que controla la serpiente. Si la serpiente se divide, entonces el bot recibirá otra serpiente en control, que a su vez también puede dividirse.
Se toma como base el experimento con las serpientes del ciberbiólogo Mikhail Tsarkov.
Red neuronal
Como parte de la tarea, se escribió una biblioteca para la red neuronal en el idioma Go. Al estudiar el trabajo de la red neuronal, utilizo el diario de video
foo52ru y el libro de Tariq Rashid - Crear una red neuronal.
La función
CreateLayer(L []int)
crea una red neuronal con el número requerido de capas y su tamaño. En cada capa, excepto la última, se agrega una neurona de desplazamiento. Alimentamos datos a la primera capa, y obtenemos el resultado de la última capa.
Un ejemplo:
CreateLayer([]int{9, 57, 3, 1})
Aquí creamos una red neuronal con nueve entradas. Dos capas ocultas de 57 y 3 neuronas y una neurona para obtener el resultado. Las neuronas de desplazamiento se agregan automáticamente por el plus a las que configuramos.
La biblioteca te permite:
- Enviar datos a la entrada de red.
- Obtenga el resultado accediendo a la última capa.
- Pregunte las respuestas correctas y realice la capacitación ajustando los pesos de los lazos.
Los pesos iniciales de los enlaces están dados por valores aleatorios cercanos a cero. Para la activación, utilizamos la función logística.
Entrenamiento bot
El bot recibe un campo cuadrado de 9x9 en la entrada, en el centro del cual está la cabeza de una serpiente. En consecuencia, nuestra red neuronal tendrá 81 entradas. El orden de las celdas alimentadas a la entrada no importa. Durante el entrenamiento, la red "lo resolverá" por sí misma, dónde está ubicado.
Para indicar obstáculos y otras serpientes, utilicé valores de -1 a 0 (no incluidos). Las celdas vacías se designaron con un valor de 0.01 y la comida 0.99.
En la salida de la red neuronal, se utilizaron 5 neuronas para acciones:
- moverse a la izquierda a lo largo del eje X;
- a la derecha
- arriba del eje y;
- abajo
- dividido por la mitad.
El movimiento del bot fue determinado por la neurona, que tiene el mayor valor en la salida.
Paso 0. Aleatorizador
Primero, se creó un aleatorizador bot. Entonces llamo a un bot que camina al azar. Es necesario verificar la efectividad de la red neuronal. Con el entrenamiento adecuado, una red neuronal debería vencerlo fácilmente.
Paso 1. Aprender sin memoria
Después de cada movimiento, ajustamos los pesos de los enlaces para la neurona de salida que indica el valor más alto. No tocamos otras neuronas de salida.
Se dieron los siguientes valores para el entrenamiento:
- comida encontrada: 0.99
- hizo un movimiento en cualquier dirección: 0.5
- perdió una célula del cuerpo sin encontrar comida (se dan 10 movimientos para esto): 0.2
- se detiene (golpea un obstáculo o se atasca): 0.1
- de pie, con una célula del cuerpo: 0.01
Después de tal entrenamiento, los bots rápidamente comenzaron a vencer al aleatorizador, y configuré la tarea: crear bots que los superen.
Pruebas A / B
Para realizar esta tarea, se creó un programa que divide las serpientes en dos partes, dependiendo de la configuración de la red neuronal. En el campo, se produjeron 20 serpientes de cada configuración.
Todas las serpientes controladas por un bot tenían la misma red neuronal. Cuantas más serpientes en su gestión y más a menudo se enfrentaron a diferentes tareas, el entrenamiento más rápido tuvo lugar. Si, por ejemplo, una serpiente aprendió a evitar puntos muertos o se partió por la mitad cuando llegó a un callejón sin salida, entonces automáticamente todas las serpientes de este robot adquirieron estas habilidades.
Al cambiar la configuración de la red neuronal, puede obtener buenos resultados, pero esto no es suficiente. Para mejorar aún más el algoritmo, decidí usar la memoria para varios movimientos.
Paso 2. Aprendiendo con memoria
Para cada bot, creé una memoria para 8 movimientos. El estado del campo y el movimiento que sugirió el bot se registraron en la memoria. Después de eso, hice ajustes a los pesos de los ocho estados que precedieron al movimiento. Para esto, utilicé un único factor de corrección, independiente de la profundidad de viaje. Por lo tanto, cada movimiento llevó al ajuste de los pesos no una vez, sino ocho.
Como se esperaba, los bots de memoria comenzaron a vencer rápidamente a los bots que entrenaron sin memoria.
Paso 3. Disminución del coeficiente de corrección dependiendo de la profundidad de la memoria
Luego, traté de reducir el factor de corrección, dependiendo de la profundidad de la memoria. Para el último movimiento realizado, se estableció el coeficiente más grande para ajustar los pesos. En el curso que lo precedió, el factor de corrección disminuyó y así sucesivamente en toda la memoria.

Una disminución lineal en el coeficiente de corrección dependiendo de la profundidad de la memoria condujo al hecho de que los nuevos robots comenzaron a vencer a aquellos que usaban un solo coeficiente.
Luego, traté de usar la reducción logarítmica del factor de corrección. El coeficiente disminuyó a la mitad, dependiendo de la profundidad de la memoria para cada movimiento. Por lo tanto, los movimientos que se hicieron "hace mucho tiempo" tuvieron un impacto significativamente menor en el aprendizaje que los movimientos "nuevos".
Los bots con una reducción logarítmica en el coeficiente de corrección comenzaron a derrotar a los bots con una relación lineal.
Servidor para bots
Al final resultó que, mejorar el nivel de bots de "bombeo" puede ser infinito. Y decidí crear un servidor donde los desarrolladores pudieran competir entre sí (independientemente del lenguaje de programación) al escribir un algoritmo efectivo para Snakes.
Protocolo
Para la autorización, debe enviar una solicitud GET al directorio "juego" y especificar un nombre de usuario, por ejemplo:
.../game/?user=masterdak
En lugar de "..." debe especificar la dirección del sitio y el puerto donde se implementa el servidor.
A continuación, el servidor emitirá una respuesta en formato JSON que indica la sesión:
{"answer":"Hellow, masterdak!","session":"f4f559d1d2ed97e0616023fb4a84f984"}
Después de eso, puede solicitar un mapa y las coordenadas de la serpiente en el campo, agregando una sesión a la solicitud:
.../game/?user=masterdak&session=f4f559d1d2ed97e0616023fb4a84f984
El servidor mostrará algo como esto:
{ "answer": "Sent game data.", "data": { "area": [ ["... ..."] ], "snakes": [ { "num": 0, "body": [ { "x": 19, "y": 24 }, { "x": 19, "y": 24 }, { "x": 19, "y": 24 } ], "energe": 4, "dead": false } ] } }
El campo de
área indicará el estado del campo de juego con los siguientes valores:
0
Esto será seguido por una matriz con serpientes que están bajo su control.
El cuerpo de la serpiente está en la matriz del
cuerpo . Como puede ver, todo el cuerpo de la serpiente (incluida la cabeza, la primera celda) al principio está en la misma posición "x": 19, "y": 24. Esto se debe al hecho de que al comienzo del juego las serpientes salen del hoyo, que está definido por una celda en el campo . Además, las coordenadas del cuerpo y la cabeza serán diferentes.
Las siguientes estructuras (un ejemplo en Go) definen todas las opciones de respuesta del servidor:
type respData struct { Answer string Session string Data struct { Area [][]int Snakes []struct { Num int Body []Cell Energe int Dead bool } } } type Cell struct { X int Y int }
A continuación, debe enviar el movimiento que realiza la serpiente agregando
movimiento a la solicitud GET, por ejemplo:
...&move=u
u - significa comando arriba;
d - abajo;
l - a la izquierda;
r - a la derecha;
/ - reducir a la mitad.
El comando para varias serpientes (por ejemplo, para siete) se verá así:
...&move=ud/urld
Un personaje, un equipo. La respuesta debe contener un comando para todas las serpientes que están bajo su control. De lo contrario, algunas serpientes pueden no recibir un comando y continuarán la acción anterior.
El campo se actualiza a intervalos de 150 ms. Si no se recibe ningún comando dentro de los 60 segundos, el servidor cerrará la conexión.
Referencias
Para evitar el efecto habrae, para aquellos que estén interesados en ver, envíeme un mensaje. En respuesta, enviaré la dirección IP de mi servidor. O puede implementar su servidor utilizando el código fuente del programa.
No soy especialista en programación ni en redes neuronales. Por lo tanto, puedo cometer errores. Difundí el código "tal cual". Me gustaría que desarrolladores más experimentados mostraran los errores cometidos.
- Biblioteca para la red neuronal junto con el juego "Tic Tac Toe"
- Snake Master - Servidor
- Snake Master - Bot
- Snakeworld2
UPDCargue temporalmente
la dirección IP del servidor . Ahora solo se lanza un aleatorizador bot (SnakeBot0) allí. Espero que el servidor no se cuelgue tan rápido.