Está en la página 1de 54

Universidad Politécnica de Madrid

Escuela Técnica Superior de Ingenierı́a de


Sistemas Informáticos

Optimización de un conductor
autónomo mediante algoritmos genéticos

Proyecto Fin de Grado

Grado en Ingenierı́a del Software

Curso académico 2020-2021

Autor:
Gonzalo Romero Sánchez

Tutor:
Raúl Lara Cabrera
Agradecer a mis amigos, familiares y profesores, que me han apoyado,
enseñado e impulsado, en la realización de este proyecto.
Resumen

En este proyecto veremos cómo optimizar a un conductor virtual dentro


de un videojuego (TORCS) haciendo uso de Python y de Keras, mejorando
su conducción a partir de la rama de ’Algoritmos Genéticos’, perteneciente al
grupo de los ’Algoritmos Evolutivos’.
Pasando por dar una visión general del mundo de la inteligencia artificial
actualmente, refiriéndose tanto a un entorno general cómo a otro más enfocado
a los videojuegos y en sus tı́tulos de conducción.
También, se explica detalladamente, qué es un algoritmo genético, y múltiples
formas de implementarlos.
Siguiendo con la parte correspondiente al desarrollo, en las que se explica
cómo se ha clasificado y gestionado cada fase, los resultados que se han obtenido
y los problemas que se han hallado.
Además, este proyecto cuenta con su propio repositorio[1], y termina con un
breve anexo donde se resuelven algunas dudas sobre el mismo.

II
Abstract

In this project, we will see how to optimize a virtual driver in a video game
(TORCS) using Python and Keras, improve its driving using the branch of
’Genetic Algorithms’, which belongs to the group of ’Evolutionary Algorithms’.
Then, a general overview of the current state of the artificial intelligence
world is given, referring both to a general environment as well as to a game-oriented
one and its driving titles.
Also, a detailed explanation is given on what a genetic algorithm is, and
multiple ways to implement these.
Continuing with the development part, which explains how each phase has
been categorized and managed, the results obtained, and the problems that have
been encountered.
Finally, this project has its own repository[1], and ends with a brief appendix
where some doubts are solved.

III
Índice

Agradecimientos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . I
Resumen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . II
Abstract . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . III

1. Introducción 1
1.1. Contexto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2. Motivaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3. Objetivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.4. Estructura del documento . . . . . . . . . . . . . . . . . . . . . . 4

2. Estado del arte 5


2.1. Inteligencia Artificial . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.2. Inteligencia Artificial aplicada al videojuego . . . . . . . . . . . . 6
2.2.1. Acercamiento general . . . . . . . . . . . . . . . . . . . . 6
2.2.2. Otros acercamientos . . . . . . . . . . . . . . . . . . . . . 8
2.2.3. IA en juegos de conducción . . . . . . . . . . . . . . . . . 9

3. Marco teórico 12
3.1. Visión general . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3.2. Población inicial . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.3. Evaluación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.4. Selección . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.5. Emparejamiento . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.6. Mutación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
3.7. Otras observaciones . . . . . . . . . . . . . . . . . . . . . . . . . 18

4. Desarrollo del proyecto 19


4.1. Planteamiento e investigación . . . . . . . . . . . . . . . . . . . . 19
4.1.1. Lenguaje y librerias . . . . . . . . . . . . . . . . . . . . . 19
4.1.2. Entorno virtual y Red Neuronal . . . . . . . . . . . . . . 20
4.2. Preparación del entorno . . . . . . . . . . . . . . . . . . . . . . . 21
4.2.1. PyCharm IDE . . . . . . . . . . . . . . . . . . . . . . . . 21
4.2.2. TORCS . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
4.2.3. Cliente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
4.2.4. Trello . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
4.3. Programación del algoritmo . . . . . . . . . . . . . . . . . . . . . 26
4.3.1. Control de Versiones . . . . . . . . . . . . . . . . . . . . . 26
4.3.2. Modelo . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
4.3.3. Pesos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
4.3.4. Función fitness . . . . . . . . . . . . . . . . . . . . . . . . 29
4.3.5. Emparejamiento y Mutación . . . . . . . . . . . . . . . . 30
4.3.6. Diagrama de Clases . . . . . . . . . . . . . . . . . . . . . 31
4.4. Entrenamiento y pruebas . . . . . . . . . . . . . . . . . . . . . . 32

IV
ÍNDICE V

5. Resultados 33
5.1. Resultados obtenidos . . . . . . . . . . . . . . . . . . . . . . . . . 33
5.2. Objetivos logrados . . . . . . . . . . . . . . . . . . . . . . . . . . 35
5.3. Problemas encontrados . . . . . . . . . . . . . . . . . . . . . . . . 36
5.3.1. Clientes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
5.3.2. Ideas descartadas . . . . . . . . . . . . . . . . . . . . . . . 37
5.3.3. Comando meta . . . . . . . . . . . . . . . . . . . . . . . . 39
5.3.4. Inconsistencias en la simulación . . . . . . . . . . . . . . . 39

6. Conclusiones y trabajos futuros 40


6.1. Conclusiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
6.2. Lineas futuras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40

Bibliografı́a 41

Anexos 43

A. Uso del Código 44


A.1. Creación de Driver . . . . . . . . . . . . . . . . . . . . . . . . . . 44
A.2. Funciones de emparejamiento . . . . . . . . . . . . . . . . . . . . 44
A.3. Inicio de entrenamiento . . . . . . . . . . . . . . . . . . . . . . . 44
A.4. Mutaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
A.5. Modificar ’Timeout’ del servidor . . . . . . . . . . . . . . . . . . 45
Índice de tablas

4.1. Tabla de Estados [2] . . . . . . . . . . . . . . . . . . . . . . . . . 24


4.2. Tabla de Comandos [2] . . . . . . . . . . . . . . . . . . . . . . . . 24

5.1. Tabla de Objetivos . . . . . . . . . . . . . . . . . . . . . . . . . . 35

VI
Índice de figuras

1.1. Inteligencia Artificial. (Google Trends) . . . . . . . . . . . . . . . 1


1.2. Deep Learning Cars - Youtube [3]. . . . . . . . . . . . . . . . . . 2
1.3. Videojuego TORCS [4]. . . . . . . . . . . . . . . . . . . . . . . . 3

2.1. Input: The art of the near future — landscape . . . . . . . . . . 5


2.2. Navigation mesh y pathfinding[5]. . . . . . . . . . . . . . . . . . . 6
2.3. Máquina de estados finitos en Pac-Man[6]. . . . . . . . . . . . . . 7
2.4. Ejemplo simple de un árbol de decisión[7]. . . . . . . . . . . . . . 8
2.5. Forza Motorsport 1 (2005) . . . . . . . . . . . . . . . . . . . . . . 9

3.1. Selección de ruleta[8]. . . . . . . . . . . . . . . . . . . . . . . . . 14


3.2. Muestreo Universal Estocástico[8]. . . . . . . . . . . . . . . . . . 15
3.3. Sinapsis y emparejamiento de cromosomas[9]. . . . . . . . . . . . 16
3.4. Emparejamiento de un solo punto[10]. . . . . . . . . . . . . . . . 16
3.5. Emparejamiento multipunto[8]. . . . . . . . . . . . . . . . . . . . 17
3.6. Emparejamiento uniforme[8]. . . . . . . . . . . . . . . . . . . . . 17
3.7. Ejemplo de mutación binaria[10]. . . . . . . . . . . . . . . . . . . 17

4.1. Conjunto de herramientas planteadas para usarlas como entorno. 19


4.2. Visualización esquemática de los raycasts (fuente del coche[11]). . 20
4.3. Representación de la Red Neuronal de cada conductor. . . . . . . 21
4.4. Arquitectura del software de competición [2]. . . . . . . . . . . . 22
4.5. Lista de Trello al finalizar el proyecto. . . . . . . . . . . . . . . . 25
4.6. Vista de las ramas de GitHub a lo largo del proyecto. [1] . . . . . 27
4.7. Arquitectura de modelo antiguo dibujado por Keras. . . . . . . . 28
4.8. Arquitectura del modelo simplificado dibujado por Keras. . . . . 28
4.9. Primera función fitness. . . . . . . . . . . . . . . . . . . . . . . . 30
4.10. Diagrama de clases de la parte implementada del proyecto. . . . 31

5.1. Circuito simple. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33


5.2. Primer agente en completar el circuito simple. . . . . . . . . . . . 33
5.3. Circuito complejo (basado en el de Suzuka, Japón). . . . . . . . . 34
5.4. Primer agente en completar el circuito complejo. . . . . . . . . . 34
5.5. Agente dando un volantazo para no salirse. . . . . . . . . . . . . 36
5.6. Circuito pensado para facilitar el entrenamiento. . . . . . . . . . 38
5.7. Circuito seleccionado como pista de entrenamiento. . . . . . . . . 38
5.8. Controles especiales de TORCS y Cámara aérea. . . . . . . . . . 39

VII
Capı́tulo 1

Introducción

1.1. Contexto
En los últimos años el campo de la Inteligencia Artificial (IA) ha entrado en
un periodo de auge. Desde los años 50 cuando Alan Turing propone su Test de
Turing como medición de la inteligencia de una máquina, hasta la actualidad
donde debido a diversos factores como el aumento en eficiencia computacional,
drásticos avances en la potencia de las GPUs y las CPUs, ası́ como por el
aumento en la cantidad global de datos recopilados y almacenados para su
posterior procesamiento (Big Data), han desembocado en un aumento de la
investigación, el uso y la popularización del campo de la IA.
Este crecimiento que ha conseguido que se use diariamente, despertando un
interés general por el tema en los últimos años como podemos observar viendo
las estadı́sticas proporcionadas por Google.

Figura 1.1: Inteligencia Artificial. (Google Trends)

Viendo esta gráfica podemos observar que en torno a 2014-2016 empieza a


haber un gran aumento en las búsquedas relacionadas con el tema. Cosa que
no es de extrañar, ya que es innegable que la inteligencia artificial tiene mucho
potencial.
Parte de esto ya podemos verlo diariamente, con las recomendaciones de
cualquier servicio multimedia como YouTube, Netflix o Spotify, o metiéndonos
más en profundidad con algunos papers que van saliendo que permiten realizar
cosas verdaderamente increı́bles como: tener un asistente para programadores
que genere código al escribir comentarios en lenguaje natural (Github Copilot[12]);
conseguir modernizar un vı́deo antiguo en blanco y negro creando nuevos fotogramas
(interpolando[13]), reescalando cada uno ganando resolución y posteriormente
siendo coloreados[14], de forma que tenga coherencia con los frames anteriores;
generando caras de gente que nunca ha existido; creando vı́deos que teniendo un
modelo como base, plasma esos mismos movimientos en otra cara diferente[15];
útiles predicciones estadı́sticas conseguidas gracias a un previo entrenamiento
con grandes cantidades de datos; y un largo etcétera.

1
CAPÍTULO 1. INTRODUCCIÓN 2

Por tanto creo que es un buen momento para tratar el tema y realizar un
trabajo relacionado con este campo.

1.2. Motivaciones
Las motivaciones que me llevaron a decantarme por esta idea para el proyecto
final de grado fueron muy numerosas.
No es de extrañar que lo contado en el apartado anterior alguien pueda tener
mucho interés por el mundo de la inteligencia artificial, también es mi caso ya
que me parece que tiene mucho potencial y me gusta investigar sobre él mismo
a la par que hago pruebas.

La primera vez qué supe de la existencia de algoritmos basados en la selección


natural y la idea de aplicar la teorı́a de la evolución para la optimización y
resolución de problemas. Empecé a ver ejemplos de cómo personas aplicaban
dichos mecanismos a problemas, como que en un espacio bidimensional los
agentes tuviesen que llegar a un punto concreto del mismo en el que se podı́an
colocar obstáculos, la formación de coches o sistemas móviles formados por
polı́gonos y cı́rculos de forma que a cada generación llegasen más lejos o consigues
en cumplir un objetivo concreto como saltar unos obstáculos, la creación de
un texto dado a base de interacciones que iban acercándole a esa solución, la
generación de una imagen dada superponiendo triángulos hasta que la imagen se
convertı́a en la objetivo, o el caso que me pareció más llamativo, un agente que
avanza controlando su giro y velocidad dentro de unos márgenes, aprendiendo
a recorrer este camino consiguiendo abstraer un entendimiento acerca de cómo
conducir. el primer ejemplo que vi acerca de este planteamiento fue un vı́deo
de 2016 llamado Deep Learning Cars donde, citando la descripción del propio
vı́deo, se hace ((Una pequeña simulación 2D en la que los coches aprender a
maniobrar por sı́ mismos a través de un recorrido, haciendo uso de una red
neuronal y algoritmos evolutivos))[3].

Figura 1.2: Deep Learning Cars - Youtube [3].


CAPÍTULO 1. INTRODUCCIÓN 3

Hacer una simulación bidimensional en la que optimizar un conductor autónomo


mediante algoritmos genéticos, fue mi primera idea que se convirtió en la optimización
de dicho conductor, pero en un entorno tridimensional. Para este propósito me
decanté por un videojuego independiente 3D, TORCS [4], que es un simulador
de carreras de coches de código abierto creado en 2007, y que en torno a 2012
contaba con una competición de inteligencia artificial en la que unos coches
competı́an contra otros para ver quién era más rápido y eficaz. Esto surgió
junto con dos clientes oficiales uno para C++ y otro para Java, que a su vez
compartı́an un manual en el que se explicaba su funcionamiento. Aunque para
el proyecto decidı́ que querı́a usar Python junto con la API para inteligencia
artificial Keras.

Figura 1.3: Videojuego TORCS [4].

1.3. Objetivos
Los objetivos de este proyecto son:
− Realizar una investigación acerca de los Algoritmos Genéticos, tratando
de ahondar en los diferentes métodos en que estos pueden ser aplicados
para resolver problemas.
− Generar, haciendo uso de Algoritmos Genéticos, un agente inteligente
dentro de un videojuego de carreras que sepa conducir. Entendiendo esta
caracterı́stica como a capacidad de completar el recorrido de un circuito
cerrado sin salirse de la pista.
− Implementar varias funcionalidades en el código que genere el agente, con
las que se pueda optar a entrenar el conductor usando diferentes métodos
que se encuentren dentro del ámbito de los Algoritmos Genéticos, para
CAPÍTULO 1. INTRODUCCIÓN 4

ası́ poder comparar la efectividad de los distintos mecanismos que puedan


usarse.

1.4. Estructura del documento


Para estructurar el documento de una forma coherente este empezará dando
un enfoque general al proyecto con el estado del arte, donde se hará una ampliación
del contexto que rodea al proyecto hablando de la inteligencia artificial y pasando
a ver más de cerca cómo afecta esta misma a los videojuegos, hablando también
de cómo algunos de estos hacen para controlar vehı́culos con una IA dentro de
los mismos.
Después de esto se pasará a hablar forma teórica acerca de los algoritmos
genéticos, ya que son la herramienta que va a usarse al realizar el proyecto. Esto
se encontrará dentro del marco teórico dónde se explicarán todas las fases y las
posibles variaciones de su implementación.

Una vez tratada la teorı́a se hablará de la parte práctica del proyecto, es decir
se detallará el desarrollo en sı́ mismo y cómo este ha sido elaborado, pasando
por las distintas fases con las que ha contado. Continuando con los resultados
obtenidos donde se hará un estudio de la eficacia de la implementación, los
objetivos logrados dónde se comprobará si se han alcanzado los planteados al
principio del proyecto, y se hablará de los problemas encontrados a lo largo del
mismo.

Para terminar, se cerrará con unas conclusiones que hablen sobre el significado
de todo el proyecto y lo aprendido a lo largo de él, añadiendo que se pretende
incorporar a futuro.
Capı́tulo 2

Estado del arte

2.1. Inteligencia Artificial


El mundo de la inteligencia artificial actualmente puede abarca una grandı́sima
variedad de aspectos, ya que es aplicable a la mayorı́a de materias y temas de
cualquier ámbito; desde asuntos relacionadas con la biologı́a, como los algoritmos
genéticos o el entendimiento del canto de las ballenas; hasta estudios completamente
opuestos como pueda ser el análisis riguroso de datos con estos modelos estadı́sticos
que la componen, para llegar a predicciones que puedan ser usadas como fuente
fiable de información para una empresa o inversores; pasando por cosas como
el uso de la inteligencia artificial para generar texto (como lo ha conseguido
OpenIA con GPT-3), o cosas tan increı́bles como obras de arte dada una cadena
de texto dada.
Para ilustrar lo versátil que ya es la IA en la actualidad, voy a usar un
ejemplo real de esta última mención que he podido generar yo mismo mientras
escribı́a este párrafo, haciendo uso de la herramienta publicada en el paper
’Taming Transformers for High-Resolution Image Synthesis’[16] que combina la
eficacia de VQGAN para generar imágenes con la capacidad de CLIP de predecir
descripciones a partir de una imagen.

Figura 2.1: Input: The art of the near future — landscape

La inteligencia artificial aplicada al ámbito de videojuegos por su parte


también es compleja, ya que los propios juegos son las representaciones de
mundos bidimensionales o tridimensionales más fieles que tenemos, que a su

5
CAPÍTULO 2. ESTADO DEL ARTE 6

vez cumplen una serie de normas y reglas que hacen que ese mundo tenga
un comportamiento relativamente predecible. No son tan solo simulaciones para
entrenar usar una IA en un problema concreto, sino que son mucho más versátiles
al intentar plasmar parte de nuestra realidad en sus mundos.
Por tanto contamos con simulaciones con una gran variedad de sistemas
paralelos en funcionamiento, como fı́sicas, distintos comportamientos en el entorno
o elementos dedicados a activarse bajo ciertos eventos.

2.2. Inteligencia Artificial aplicada al videojuego


La idea sobre la IA en el videojuego de Youichiro Miyake, actual lı́der
de investigación en IA de SQUARE ENIX (actual compañı́a productora de
videojuegos) creo que es muy acertada, ya que coloca a las IAs como principales
responsables de la inmersión y la experiencia general de un jugador dentro
de un entorno virtual, más concretamente lo que él quiere describir es que
((La evolución de la IA crea nuevas experiencias de usuario. El progreso de
la tecnologı́a de la IA se traduce en la evolución del diseño de juegos.))[17] Por
tanto todos los elementos que rodean a la IA son crı́ticos en un videojuego
y sin embargo pasan completamente desapercibidos, a no ser que empiecen a
fallar. A esto comúnmente se le llama ’Estupidez Artificial’[18], denotando la
poca habilidad que demuestran algunas ’Inteligencias Artificiales’ al no poder
completar tareas de forma inteligente tal como su nombre indica.

Un ejemplo de esto podrı́a ser que en un videojuego un personaje empiece


a caminar hasta encontrar una pared y siguiese su camino de frente como si
no hubiera nada impidiéndole el paso. Esto claramente saca al jugador de la
inmersión en dicho mundo virtual, desembocando en una peor experiencia de
juego.

2.2.1. Acercamiento general


Los agentes inteligentes que actúan dentro de los videojuegos suelen estar
basados en grafos[7], árboles de decisión o máquinas de estados finitos, que les
indican el comportamiento que pueden seguir en cada momento, haciéndoles
parecer inteligentes. También, pueden interactuar con el sistema gracias a, por
ejemplo, información de su entorno como puedan ser los pasillos de una instalación,
usando una NavMesh (Navigation Mesh)[5] para que puedan moverse y orientarse
en dicho espacio. Junto con un algoritmo de pathfinding para encontrar una ruta
óptima, que suele constar de empezar en una localización A de la NavMesh, para
llegar hasta otro punto B.

Figura 2.2: Navigation mesh y pathfinding[5].


CAPÍTULO 2. ESTADO DEL ARTE 7

Al crear una máquina de estados finitos un desarrollador busca apuntar los


posibles comportamientos de un agente como estados para todas las situaciones
que este se pueda encontrar, ası́ como los eventos que cambian esos estados. Un
ejemplo simple lo tendrı́amos en el juego de Pac-Man donde, resumiendo, todos
los fantasmas se encuentran en el un evento de persecución, aunque dentro del
mismo estado esté programado de manera distinta el comportamiento exacto
de cada fantasma, hasta que el jugador coge la bola que hace a los fantasmas
vulnerables, activando el cambio de estado al de huida. Si el jugador se los come
o pasa el efecto, el desenlace será que el fantasma deberá volver al centro del
laberinto o recuperar su estado original de caza, respectivamente.

Figura 2.3: Máquina de estados finitos en Pac-Man[6].

Los árboles de decisiones son muy útiles para estructurar bien los comportamientos
de un agente. Además la gran ventaja frente a las máquinas de estados finitos,
es que no tienen por qué saber el estado en el que se encuentran. Incluso se
puede mejorar haciendo que se puedan ir desbloqueando nuevas ramas al activar
ciertos eventos o al pasar de puntos especı́ficos en la historia del juego, dando
ası́ una sensación de que está aprendiendo. Un buen ejemplo de esto es el árbol
de decisión con el que cuenta el Alien del juego Alien:Isolation[19].
CAPÍTULO 2. ESTADO DEL ARTE 8

Figura 2.4: Ejemplo simple de un árbol de decisión[7].

Lo que se busca con esto es imitar el comportamiento humano o racional


que cabrı́a esperar de cierto NPC (non-player character) ya sea un Bot en un
juego de disparos, un general en un juego de estrategia, guardias en uno de
sigilo, un alien en uno de terror, un compañero que coopere con el jugador, un
contrincante en un juego de peleas, o como en el caso que trataremos más de
cerca, un piloto en un juego de carreras.

2.2.2. Otros acercamientos


Hay una gran cantidad de enfoques diferentes aplicables a este mismo concepto
de agente inteligente, que existe dentro del videojuego para acompañar al jugador
y crear una experiencia más sofisticada, sin tener la necesidad de depender de
otras personas.
Para implementar todo esto, ha habido dos maneras de acercarse al problema,
la forma tradicional más extendida, que se basa en árboles de búsqueda también
conocidos como árboles de comportamiento y la basada en algoritmos de Machine
Learning. Sin embargo, este último acercamiento no es tan usado en este sector
por si solo, se ha tratado de implementar estos algoritmos para controlar NPCs[19],
generación procedural[20], música dinámica que dependa del entorno y del ambiente[21],
recrear el planeta entero haciendo uso de datos satelitales como en Microsoft
Flight Simulator 2020[22], o incluso se han usado para conseguir grupos estadı́sticos
con los que clasificar diferentes tipos de jugadores a partir de su forma de
interactuar con el videojuego, y con ello predecir su comportamiento (como
es el caso de Tomb Raider: Underworld[23]).

Otros casos son los agentes inteligentes como Alphastar de DeepMind[24]


o Five de OpenAI[25], que ambos ganaron a los campeones del mundo en sus
respectivos juegos, Starcraft II y Dota 2. Pero estos agentes no están implementados
dentro del propio videojuego, aunque son logros tan sorprendentes como lo que
consiguió Google con AlphaGo. En nuestro caso queremos resolver un problema
CAPÍTULO 2. ESTADO DEL ARTE 9

de conducción autónoma dentro de un simulador de carreras de coches en


diferentes circuitos, en los que consiga los tiempos más rápidos posibles.

La inteligencia de los pilotos en los juegos de coches ha ido evolucionando


y mejorando a lo largo de los años, siendo además uno de los géneros que he
hecho un mayor uso del Machine Learning en la industria del videojuego.
Distintas sagas de juegos han dado diferentes aproximaciones al mismo problema,
por ejemplo, en la saga Forza los pilotos se mueven gracias a una red bayesiana
con la que aprenden a seguir y generar trazadas óptimas.[26]

La red del juego en 2005 era entrenada tan solo de forma local con los datos
que generaba el jugador al competir en diferentes circuitos. Esta red ha ido
evolucionando, haciendo uso de los avances tanto en hardware como en cloud
computing, ya que en los posteriores juegos los datos de entrenamiento son
generados por todos los jugadores alrededor del mundo, creando de forma remota
una red más eficiente. En ella se aplican metodologı́as, de la inteligencia artificial
en el videojuego, más tradicionales para conseguir una mayor fluidez o realismo,
por ejemplo, ajustando la dificultad en tiempo real (rubber banding)[27] para
que el jugador sienta competitividad y se mantenga entretenido en todo momento,
el ejemplo en un juego de coches puede ser contar con limitaciones en la velocidad
a la que el coche debe frenar, o el otro extremo recortar parámetros extremos
para que tenga menos posibilidades de fallar.

Figura 2.5: Forza Motorsport 1 (2005)

2.2.3. IA en juegos de conducción


En cuanto al problema de coches controlados por agentes inteligentes que
compiten contrarreloj se han logrado diferentes soluciones muy buenas. Muchas
de ellas están basadas en aprendizaje por imitación y otras en computación
evolutiva con un aprendizaje reforzado, más concretamente en algoritmos genéticos.

Por ejemplo el caso que he comentado antes acerca de la serie de videojuegos


’Forza’ de Microsoft me resulta muy interesante. Esta franquicia se divide en dos
principales entregas que van sumando tı́tulos con el paso de los años, la primera
busca un completo realismo, simular fielmente las fı́sicas y la realidad, estos
juegos llevan el sobrenombre de ’Motorsport’, que actualmente va a lanzar su
CAPÍTULO 2. ESTADO DEL ARTE 10

octava entrega; por otra parte tenemos la saga ’Horizon’ de juegos más arcade
y distendida en cuanto al realismo se refiere, ya que esta busca simplemente
entretener al jugador, sin ser completamente fieles a la realidad y tomándose las
libertades necesarias para conseguir este propósito. Suele contar con un mundo
grande y abierto con diferentes eventos repartidos a lo largo del mismo, en vez
de contar con circuitos individuales.
Actualmente ha ganado mucha popularidad y a finales de este año saldrá a
la venta su quinta entrega.

Teniendo este trasfondo podemos concluir con que Forza cuenta con un motor
de fı́sicas, encima de este se encuentra la adaptación personal de cada una de sus
dos sagas, Forza Motorsport y Forza Horizon, y por último en un nivel superior
junto con los controles de conducción y las ayudas de juego, está el controlador
de IA del que quiero hablar, ’Drivatar’.

Los Drivatar son las IAs de los conductores de estos juegos, hechos con
la idea de conducir como personas reales. Para ello aprenden por imitación,
comportándose, como lo harı́an otras personas que hayan jugado al juego, en
las carreras de otros jugadores.[28] Permitiendo ası́ que la gente pueda competir
contra coches controlados de la forma más parecida a como lo harı́an sus amigos
u otros desconocidos. Consiguiendo ası́ una mayor naturalidad en el entorno
virtual de manera local, sin tener que depender de que otros jugadores estén en
lı́nea.

Como he comentado antes esto se implementó desde el primer juego de la


franquicia, Forza Motorsport de la Xbox original en 2005, y que lleva mejorándose
desde entonces. Lo que convierte a este sistema de IA en los videojuegos en uno
de los más remarcables y con mayor recorrido de la historia.

Para resumir el funcionamiento, Drivatar recoge datos suficientes de cómo un


jugador afronta el juego, para entrenarse y empezar a hacer elecciones inteligentes
que converjan en un comportamiento lo más similar posible a cómo conducirı́a
dicho jugador si fuese él el propio piloto. Si se quiere ahondar en este tema
recomiendo un vı́deo[29] donde se hace un análisis completo acerca de este
sistema y su funcionamiento.

Sin embargo para cumplir con los objetivos de este proyecto, se hará uso de
los algoritmos evolutivos, concretamente de algoritmos genéticos, un subgrupo
de los mismos. La inspiración que da la idea de su funcionamiento, viene de
la naturaleza, concretamente de la teorı́a de la evolución. Su fuerte y uso más
extendido es el de optimizar parámetros.
Partiendo de poner a prueba a una población inicial, se evalúan y seleccionan
una serie de individuos, y generalmente nos quedaremos con quienes hayan
conseguido acercarse más a la solución del problema, esto se determina mediante
una función que asigna un valor calificativo a cada individuo.
Posteriormente se aplican una serie de mutaciones y recombinaciones genéticas,
basadas en la evolución biológica, que cortan y combinan los datos que formaban
a los individuos “padres”, quienes habı́an pasado la fase de selección, para crear a
los “hijos” de quienes se espera un mayor rendimiento. Esta siguiente generación
será la nueva población a la que se le aplique una vez más el mismo proceso.
CAPÍTULO 2. ESTADO DEL ARTE 11

Finalmente, las sucesivas reiteraciones de este método son lo que acabará creando
una generación que sea competente resolviendo el problema que se intenta
abordar.

En el caso de los pilotos que deben aprender a cómo superar un circuito


en el menor tiempo posible, se suele partir con un coche que se mueve hacia
los lados, y puede aumentar o disminuir su velocidad; un circuito con un inicio
y un final; y ciertos sensores para que el piloto pueda saber la posición de su
coche en la pista y sus variables. Partiendo de esta base, y dependiendo de la
simulación en la que se intente implementar este modelo, se puede aumentar la
complejidad añadiendo variables como, por ejemplo, la meteorologı́a, la inercia,
el rozamiento o cambios de marcha.
Capı́tulo 3

Marco teórico

Los Algoritmos Genéticos (siglas AG / GA en inglés) son una herramienta


muy potente de aprendizaje no supervisado que se basa en la teorı́a de la genética
y la selección natural para hallar soluciones óptimas a un problema.
Constan de cuatro partes fundamentales, evaluación, selección, emparejamiento
y mutación. Esta lı́nea de algoritmos surgió alrededor de 1970 por parte de John
Henry Holland, un filósofo e informático teórico, y forman parte de los algoritmos
evolutivos, que engloban un conjunto de algoritmos orientados a la computación
evolutiva.

3.1. Visión general


Resumiendo, un algoritmo genético necesita contar con una población inicial
de posibles soluciones. Estas pueden estar representadas de dos maneras distintas;
como fenotipo, que es la interpretación de como dicha solución actuará al presentarle
una situación real, o como genotipo, que es otra forma de representar la solución
con el propósito de que se pueda operar bien con ella en un espacio computacional.
Esta población inicial deberá de ser evaluada, para ello se hace uso de la
función fitness, cada problema tendrá su función fitness especı́fica, que califique
a los individuos de la forma óptima posible, dándonos una ponderación que
podremos usar como orientación para clasificar las soluciones más prometedoras.

Teniendo estos valores ahora se puede pasar a la fase de selección. Donde los
individuos de la población anteriormente evaluada serán elegidos para generar
a los individuos que formarán la posterior generación. Una vez seleccionados se
continúa con el emparejamiento, consistiendo este en mezclar los genotipos que
conforman a los individuos seleccionados, y que por tanto serán los “padres”.
Además, para añadir variación a la siguiente generación se usan varios métodos
cómo la mutación, que consiste en implementar una función que dada una
probabilidad muy baja se pueden variar uno o más genes de un genotipo.
Una vez se ha creado la nueva población de soluciones, vuelven al principio
del ciclo continuando con su evaluación. La finalidad estas iteraciones es que al
cabo del tiempo con el paso de estas se acaba llegando a una solución óptima que
resuelva el problema dado, siendo el comportamiento habitual ver una mejora
entre generación y generación.

Para que estos algoritmos funcionen necesitan tener principalmente tres


caracterı́sticas pertenecientes a la propia teorı́a de la selección natural de Darwin;
herencia, selección y variación.
La herencia se refiere a la necesidad de que una solución tenga la posibilidad
de pasar parte de sus genes a una posterior descendencia; la selección por su
parte se refiere al mecanismo que permita a ciertos individuos de la población
poder ser padres y cumplir la caracterı́stica anterior, ası́ como no dejar a otros

12
CAPÍTULO 3. MARCO TEÓRICO 13

hacerlo; por último la variación se refiere al proceso por el que la población debe
poder ser sometida a cambios que terminen por añadir variedad a la población.
Un ejemplo de esto serı́an las mutaciones anteriormente mencionadas.

Explicado el concepto general, voy a pasar a desglosar cada fase explicándola


un poco más a fondo.

3.2. Población inicial


Antes de empezar con la primera fase, se necesita partir de una población
inicial que como ya he dicho antes es un conjunto de soluciones. El número
de individuos que forman esta población variarán dependiendo del problema y
tendrá que ser lo suficientemente grande para que pueda existir cierta variedad y
a su vez no ser tan numerosa como para no converger incluso si existen soluciones
óptimas entre sus individuos.
Los genotipos de las soluciones suelen venir representados cómo dı́gitos
binarios y que a su vez son una representación del fenotipo de la misma solución.
Otra tı́pica representación cuando se hace uso de redes neuronales es el uso de los
pesos de la red cómo o una lista secuencial de números que forman el cromosoma
(genotipo) dónde cada uno de los genes son dichos pesos.
Normalmente la población inicial se genera de manera completamente aleatoria.

3.3. Evaluación
Lo más importante en la evaluación es definir una función de fitness correcta
y representativa del problema a evaluar. esta función decidirá si un individuo
es óptimo no para ası́ ser seleccionado como padre. Como esta función es un
proceso que se va a repetir múltiples veces a lo largo del algoritmo, no debe ser
muy pesado computacionalmente para poder ejecutarse con rapidez. Toda una
generación suele ser evaluada de manera concurrente.

Un ejemplo para entender como serı́a una buena función fitness, podemos
encontrarlo con el problema de la mochila, en la que tienes que meter ciertos
objetos con cierto peso y valor, maximizando el valor, pero sin pasarse del peso
que puede llevar la mochila. Esto último es lo que se puede traducir en la función
fitness de este problema siendo cero si la solución no es válida por pasarse de
peso o siendo la suma de los valores de los objetos que contiene la mochila,
teniendo ası́ una buena medida para evaluar.

Otra buena práctica es crear funciones que no sean lineales ya que un


incremento en valores más altos debe ser más significativo que un aumento entre
soluciones con un fitness muy bajo. Para ello se pueden hacer uso de funciones
cuadráticas.
CAPÍTULO 3. MARCO TEÓRICO 14

3.4. Selección
La selección es uno de los procesos más importantes ya que dependiendo
de esta vamos a alcanzar unas soluciones u otras. Una mala selección puede
desembocar en una convergencia muy temprana que no sea una solución óptima,
es decir, dejando al algoritmo atascado en un mı́nimo local. Para evitar esto es
importante tener en cuenta el principio de la variación y mantener una buena
diversidad en la selección de los padres.

Existen varios métodos para seleccionar a los padres, siendo 1 de los más
populares la “Selección de ruleta”, este método ordena los fines de la población
evaluada de mayor a menor, colocándolos secuencialmente y generando un número
aleatorio entre cero y el sumatorio de todos los fitness, este puntero caerá en
uno de los respectivos valores pertenecientes al fitness de un individuo, dando
ası́ el nombre a la selección.

Figura 3.1: Selección de ruleta[8].

Esto se repite secuencialmente hasta tener todos los padres seleccionados,


ası́ los individuos que antes han conseguido un fitness más alto cuentan con una
mayor probabilidad de ser seleccionados. El problema es que esto no asegura
variedad, y para solucionar este problema se puede usar el “Muestreo Universal
Estocástico” (o Stochastic Universal Sampling, con siglas SUS en inglés), que
parte de la base del método anterior, pero en vez de contar con un solo puntero y
repetir varias veces la selección, esta se hace una sola vez y cuenta con el mismo
número de punteros que de padres se quieren seleccionar, separados estos de
manera equidistante a lo largo de todo el sumatorio, asegurándose ası́ de poder
seleccionar padres variados considerando todas las soluciones.

Estos dos últimos métodos siguen teniendo un problema en común, ya que,


al tratar con una gran diferencia entre los fitness de la población, si un individuo
supera con creces al resto, este puede ser elegido múltiples veces en la selección
saturando esta misma y haciéndola inútil. Para evitar esto hay que preparar
alguna funcionalidad que tenga en cuenta este problema y por ejemplo limite el
número de repeticiones en las que un individuo puede ser seleccionado.

A parte de estos métodos existen otros como la selección de torneo en la que


se seleccionan x individuos aleatorios de la población y se enfrentan sus fitness
CAPÍTULO 3. MARCO TEÓRICO 15

Figura 3.2: Muestreo Universal Estocástico[8].

escogiendo el que sea mayor para conseguir buenos candidatos y asegurándose


con la aleatoriedad de cubrir todo el espectro de soluciones consiguiendo variedad.
Otras personas, optan por elegir sus mejores individuos de manera manual,
esto es especialmente útil en casos donde la función de fitness puede ser algo
muy complejo de implementar de forma óptima y eficiente, o en situaciones
donde un humano pueda ver claramente la distinción entre los individuos que
han conseguido una mejor solución al problema que se intenta resolver.

Los demás métodos no son tan efectivos ya que en su mayorı́a tratan de


seleccionar aleatoriamente a los individuos ya sea redondeando su fitness o
seleccionando les de manera completamente aleatoria sin tenerlo en cuenta.

3.5. Emparejamiento
Al igual que la selección esta fase es la más importante a la hora de generar
no los individuos ya que será la responsable de juntar los diferentes genotipos
en uno nuevo que se pueda evaluar. Para ello normalmente se emparejarán de
forma aleatoria dos de los padres seleccionados, se cogerán sus cromosomas
para mezclarlos entre ellos, siguiendo alguno de los métodos que veremos a
continuación, para generar dos, o más, nuevos individuos o “hijos”.
Tal y como ocurre en la naturaleza durante la segregación cromosómica.
CAPÍTULO 3. MARCO TEÓRICO 16

Figura 3.3: Sinapsis y emparejamiento de cromosomas[9].

La finalidad de este paso es conseguir una mejora en los hijos al tener varias
soluciones competentes por parte de los padres, aquı́ es donde podemos ver cómo
está aplicado otro de los principios de la selección natural, la herencia.

El primer método de emparejamiento se llama “Emparejamiento de un solo


punto”, en él se escoge un punto aleatorio de toda la longitud del genotipo, que
funcionará como puntero de corte para combinar los dos cromosomas en dos
diferentes, intercambiando los valores a partir de dicho punto.

Figura 3.4: Emparejamiento de un solo punto[10].

El siguiente método serı́a el “Emparejamiento multipunto” que, es igual


que el anterior, pero con el añadido de que existen más de un puntero, ası́ las
recombinaciones pueden ser más complejas manteniendo grandes cadenas de los
padres.

Por último, mencionar el “Emparejamiento uniforme” que, partiendo de los


dos cromosomas, trata de mezclarlos intercambiando los genes individualmente
con una probabilidad de un 50 por ciento. Este método suele dar resultados
bastante diferentes a los dos anteriores y dependiendo del problema estos serán
mejores o peores.
CAPÍTULO 3. MARCO TEÓRICO 17

Figura 3.5: Emparejamiento multipunto[8].

Figura 3.6: Emparejamiento uniforme[8].

Existen muchos más métodos de recombinación, como “Emparejamiento por


orden” pero estos tres son los más simples y usados.

3.6. Mutación
Esta última fase se encarga de las mutaciones, está orientada a poder variar
los genotipos de los hijos para añadir nueva información dando más variedad a
la nueva generación de manera controlada.

Para ello se usa una función que muten uno o más genes que actúe con una
probabilidad muy baja que suele oscilar entre un 1 o un 15 por ciento, no son
valores exactos ya que puede ser mucho menos si se quieren explotare más las
soluciones actuales, o más si lo que se quiere es explorar un mayor número de
estas. Esta probabilidad se mantiene fija a lo largo de toda la ejecución.

Por poner un ejemplo aquı́ se puede ver como en este genotipo formado por
una cadena de números binarios, ha entrado en la función de mutación y uno
de sus genes se ha cambiado de forma aleatoria.

Figura 3.7: Ejemplo de mutación binaria[10].


CAPÍTULO 3. MARCO TEÓRICO 18

La mutación puede generarse sin tener en cuenta para nada el gen que se va
a mutar, o puede ser una suma o resta aplicada a aquel que se encontraba ya
en esa posición.

3.7. Otras observaciones


Para terminar, hace falta explicar otros dos puntos clave, siendo el primero
el elitismo. Este consiste en coger la mejor o las mejores soluciones e incluirlas
sin ninguna variación en la próxima generación para no perder esa solución si
da la casualidad de que ninguna nueva la supere. Ası́ se pueden evitar retrocesos
en el proceso evolutivo de estas soluciones.
El otro punto clave se trata de la implementación de algún método para
añadir individuos completamente nuevos con la finalidad de evitar caer en
mı́nimos locales. Esto es especialmente útil si los genotipos no contienen exclusivamente
números binarios, ya que es una manera de llegar a otras soluciones que de otra
forma solo podrı́as alcanzarlas a base de mutaciones, lo que requerirı́a mucho
más tiempo computacional de conseguir.
Capı́tulo 4

Desarrollo del proyecto

El proyecto se ha dividido principalmente en cuatro partes, planteamiento e


investigación, preparación del entorno, programación del algoritmo y la fase de
entrenamiento y pruebas.

Todas las partes tuvieron su importancia, pero sin duda la que tuvo mayor
impacto en el proyecto y a su vez generó más problemas fue la última.

4.1. Planteamiento e investigación


Como ya he comentado en el apartado de Motivaciones este trabajo empezó
siendo planteado al ver trabajos similares como puede ser el de Deep Learning
Cars[3], donde se usaban algoritmos evolutivos para resolver diferentes problemas.
Esto me llamó mucho la atención y decidı́ que querı́a experimentar con ese tipo
de algoritmos.
A partir de aquı́ propuse que el proyecto se tratase de un entorno bidimensional
en el que varios coches entrenaran generación tras generación hasta conseguir
superar un trayecto o circuito pre-establecido. Fue el tutor quien propuso que
el entorno en el que se simulaban los agentes fuese tridimensional. Para este
propósito pensamos que el videojuego de software libre TORCS, serı́a una buena
base para empezar con el desarrollo de esta inteligencia artificial.

Figura 4.1: Conjunto de herramientas planteadas para usarlas como entorno.

4.1.1. Lenguaje y librerias


En cuanto al lenguaje para programar el algoritmo genético, ya escogı́ Python
antes de estar seguro acerca de cómo iba a terminar siendo el entorno virtual.
Principalmente por ser muy usado en el mundo de la inteligencia artificial estos
últimos años debido a su simpleza y compatibilidad. Sumado a esto ya tenı́a
algo de experiencia tratando la IA con dicho lenguaje gracias a las asignaturas

19
CAPÍTULO 4. DESARROLLO DEL PROYECTO 20

optativas de Machine Learning y Agentes Inteligentes, aunque esta última la


estuviera cursando en ese momento.

La otra herramienta que me parecı́a muy útil y querı́a usar era Keras, una
biblioteca de Python para inteligencia artificial que se ejecuta sobre TensorFlow.
Con esta se simplifica muchı́simo el proceso de crear redes neuronales, agilizando
con ello la creación de las mismas, gracias a tener un constructor de modelos
basado en añadir capas secuencialmente para posteriormente conectarlas entre
sı́ solo preocupándote por la función de activación, el número de entradas y
salidas, y en mi caso también por el tipo de inicialización que se usa para los
pesos de las conexiones y los de los bı́ases de las neuronas.

Por último iba a usar NumPy, librerı́a de Python para crear y manejar arrays
con mucha más soltura, y que cuenta con muchas funciones matemáticas extra
para los mismos.

4.1.2. Entorno virtual y Red Neuronal


Teniendo esto claro ya solo quedaba el trabajo de investigación, sobre los
algoritmos evolutivos y más concretamente su rama de algoritmos genéticos
basados en la selección natural, leer la documentación y ver ejemplos sobre el
uso de Keras para implementar una red neuronal simple (FCNN, Feed Fordward)
y leer la documentación acerca de los distintos clientes de TORCS y averiguar
qué serı́a necesario para hacer que los agentes puedan tomar el control de los
coches del juego.

Acerca de la red neuronal que querı́a implementar, esta iba a tener en un


principio 6 entradas y 3 salidas, una que controlase el giro, otra la aceleración
y otra el freno. Junto a esto, varias capas ocultas en el primer caso pensé que 9
y 6 neuronas, iban a ser suficiente para esto.

Para las seis entradas, una era la velocidad que tenia el coche en ese momento,
y las otras cinco una serie de distancias en metros que salı́an del coche hasta
el borde de la pista, para que tuviese una referencia espacial de donde estaba
y cuales se encontraban los lı́mites. Estas distancias saldrı́an en [-90, -45, 0,
45, 90] grados respectivamente, siendo cero la dirección hacia la que mira el
coche, y tendrı́an un alcance máximo de 200m, sirviendo como su visión. A
estos medidores les llamaré ’raycasts’.

Figura 4.2: Visualización esquemática de los raycasts (fuente del coche[11]).


CAPÍTULO 4. DESARROLLO DEL PROYECTO 21

Más adelante cambiarı́a la red sin la entrada de velocidad, quedándose en 5


entradas, ya que la extra no parecı́a ser útil ni aportar información, y agrupando
la salida en 2, la de giro que permanecerı́a, y otra que controlase la velocidad
en su conjunto. Quedando una red neuronal mucho más compacta (Figura 4.3).

Figura 4.3: Representación de la Red Neuronal de cada conductor.

A su vez las marchas del coche serian independientes y las iba a manejar
automáticamente desde el código al alcanzar ciertas revoluciones, aunque finalmente
acabé poniendo un lı́mite en la marcha máxima que podı́a alcanzar, para que
no incrementase demasiado su velocidad, y ası́ no superara los 100 km/h.

Esta labor de investigación duró unos meses y una vez tuve mucha de la
información entendida y clasificada, pasé a la siguiente fase, aunque siempre que
surgı́a algún problema me volvı́a a repasar las fuentes y buscaba más información
para cerciorarme de que aquello que estaba programando era correcto y eficiente.

4.2. Preparación del entorno


4.2.1. PyCharm IDE
Para editar, compilar y ejecutar el código, escogı́ PyCharm cómo IDE de
desarrollo porque me parecı́a que ofrecı́a un buen control sobre el proyecto y
su código. Además contaba con múltiples herramientas útiles como el debuger
o la posibilidad de añadir comentarios ‘#TODO:‘ que permiten organizar de
manera más estructurada el código.
También, ofrece la posibilidad de crear un entorno virtual para no tener
que instalar librerı́as aparte, y que todo ese contenido esté incluido dentro del
proyecto cosa que es muy útil al usar varias librerı́as, quitando los problemas
que puedan dar sı́ no están instaladas.
CAPÍTULO 4. DESARROLLO DEL PROYECTO 22

Por tanto al crear el proyecto le acompañó una carpeta ’venv’ dentro del
mismo dónde se guardarı́an la mayorı́a de las dependencias necesarias.

4.2.2. TORCS
Por otro lado, en un principio la idea de usar TORCS, era que aprovecharı́amos
el hecho de que tenı́a su propia competición, la ’SCR Championship Competition’,
que contaba con su propio manual de documentación [2], y que mucha gente
habı́a participado en ella. Debido a esto existı́a una buena cantidad de documentación
y de resoluciones a problemas similares al que se pretendı́a abordar. Además ya
que los coches son algo complicado de controlar, y más aún si competimos y lo
llevamos al lı́mite; acelerar, frenar en el momento idóneo, cambios de marcha,
la inercia que da la velocidad, el peso del propio vehı́culo. . .
Por tanto, podemos concluir que iba a haber mucha información y soluciones
ingeniosas a diversas propuestas.

Si bien esto era cierto, todo estaba escrito usando otros lenguajes de programación,
por desgracia, para Python la realidad era que no contaba con un cliente oficial,
nada que permitiese conectarse al juego usando la arquitectura del software de
la competición.

Este software se trata de un parche para el videojuego, y el cliente permitı́a


programar un controlador dentro del mismo que fuese el ‘cerebro’ del propio
agente, llevando toda su logı́stica. Después, es el propio Cliente el que se encargaba
de mandar esa información al juego a través de un servidor, que cuenta con un
puerto para cada vehı́culo.

Figura 4.4: Arquitectura del software de competición [2].

4.2.3. Cliente
Para preparar el entorno era necesario buscar información sobre posibles
clientes o wrappers, no oficiales. Existen muchos de ellos, pero por tener que ser
CAPÍTULO 4. DESARROLLO DEL PROYECTO 23

compatible con el lenguaje, tuve problemas para dar con uno que cumpliese con
los requisitos para lo que querı́a acabar implementando.

4.2.3.1. Requisitos
Principalmente necesitaba que fuese compatible con Python 3 o superior, que
pudiera soportar a varios agentes al mismo tiempo y que tuviese una manera de
reiniciarse para usarla entre generaciones.

La mayorı́a de los wrappers estaban escritos en C++ y Java, solı́an ser


modificaciones de los clientes de la competición oficial de TORCS para dichos
lenguajes. Otra cosa que tenı́an en cuenta era el manual de la competición, que
contaba con muchı́simas utilidades como veremos a continuación. Además, la
mayorı́a de wrappers en Python, no habı́an sido actualizados a Python 3 porque
fueron creados hace unos cuantos años y no se han ido actualizando.

4.2.3.2. Cliente elegido


Por suerte existe una versión actualizada del principal cliente original para
Python, que seguı́a solo disponible para su versión 2, en el que se podı́a usar
Python 3 o superior para tratar con el software de la competición. Se encuentra
en GitHub y se llama ’Python client for TORCS with network plugin for the
2012 SCRC’[30], y fue el punto de partida para el entorno del proyecto.

Aun no siendo perfecto, era el único cliente que encontré y como vi que
era posible realizar el proyecto haciendo unas cuantas modificaciones al mismo,
seguı́ adelante con él. Aunque como repasaré en los problemas encontrados tenı́a
muchas limitaciones junto con las que ya traı́a TORCS por defecto al tratar de
usar lo que estaba implementando.

El wrapper como digo, se basaba en el manual oficial que seguı́an los clientes
de la competición de TORCS. Se divide en 4 clases principales:
Driver, clase fundamental para los conductores, localizada en ’driver.py’.
Clase padre de ‘my driver’ que cuenta con la inicialización de todos los
parámetros necesarios para crear a un piloto e inicializar su lógica, ası́
como controlar las acciones que se pueden hacer en el reseteo del mismo
Cliente, clase principal y que se encuentra dentro de ’protocol.py’. Se
hace cargo de todo lo relacionado con las conexiones con el servidor y
la codificación y decodificación de los mensajes entrantes. Para usar a
un conductor lo primero que hay que hacer es llamar a la clase ’main’
del wrapper ya que lo primero que hace es llamar a esta clase, para ello
habrá tendrá que contener el propio Driver que se quiere usar, con sus
parámetros correspondientes (ver anexo A.1).
State, clase que ofrece información sobre el coche, ubicada dentro de
’car.py’. Esta se hace cargo de inicializar las variables del coche, y de
ir traduciendo la información periódica que llega del servidor acerca del
estado del coche y su entorno dentro del juego, a variables que poder tratar
con mayor soltura (ver estados disponibles en tabla 4.1).
CAPÍTULO 4. DESARROLLO DEL PROYECTO 24

Command, clase necesaria para controlar el coche, también ubicada dentro


de ’car.py’. Se encarga de enviar los comandos al servidor para que el coche
sepa que tiene que hacer durante el próximo ciclo de control (ver comandos
disponibles en tabla 4.2).

4.2.3.3. Sensores y Comandos


Para el uso de estos dos últimos métodos los clientes oficiales tienen una
serie de sensores y comandos que vienen explicados en el manual [2]. Pero en el
caso del cliente de Python no todos comandos ni estados estaban disponibles.
Por lo que aquı́ voy a mostrar una tabla con los estados y los comandos
que existen dentro del entorno, ya sea porque estuvieran disponibles dentro del
propio wrapper o porque han sido modificaciones mı́as del mismo para ponerlos
en funcionamiento.

Nombre Rango (unidades) Descripción

angle [−π, +π] (rad) Ángulo entre la dirección del coche y el eje de la pista.

distances from edge [0,200] (m) Raycasts

current lap time (0,+∞) (s) Tiempo de vuelta actual. Vuelve a cero por vuelta dada.

last lap time [0,+∞) (s) Tiempo de última vuelta. Su valor inicial es cero.

distance raced [0,+∞) (m) Distancia total de la pista recorrida en metros.

speed x (-∞, +∞) (km/h) Velocidad del coche yendo paralelo a la pista

Distancia entre -1 y 1 de dónde se encuentra el coche con respecto


distance from center (-∞, +∞) al centro de la pista, siendo este el 0. Si se sale de estos lı́mites
significa que el coche está fuera de la pista.

rpm [0,+∞) (rpm) Revoluciones por minuto del motor del coche.

gear {-1,0,1,· · · 6} Marcha actual del motor entre -1 y 6, siendo -1 marcha atrás.

Tabla 4.1: Tabla de Estados [2]

Nombre Rango (unidades) Descripción

steering [-1,1] Dirección del volante (-1 todo izquierda, 1 todo derecha).

accelerator [0,1] Acelerador virtual (0 no pisado, 1 completamente pisado).

brake [0,1] Freno virtual (0 no pisado, 1 completamente pisado).

gear -1,0,1,· · ·,6 Marcha a poner.

Si es 1 pide al servidor que reinicie la carrera,


meta 0,1
si es 0 no hace nada.

Tabla 4.2: Tabla de Comandos [2]


CAPÍTULO 4. DESARROLLO DEL PROYECTO 25

Además de preparar el propio cliente, también necesitaba organizarme las


tareas y tener una planificación del desarrollo para poder contar con cierta
trazabilidad.

4.2.4. Trello
Para ello utilicé un sistema muy parecido a un desarrollo Kanban, pero
simplificado al tratarse de un proyecto que iba a realizar una sola persona.
La organización de las tareas empezó listando todos los objetivos necesarios a
completar (el establecimiento del propio entorno, la implementación del algoritmo
y sus partes...) como si fuesen historias de usuario que hubiese que realizar,
agrupándolas posteriormente para crear épicas.

Para unificar todas las tareas en un solo tablero hice uso de Trello una
plataforma gratuita para la creación de notas siguiendo una organización en
columnas. En mi caso separé las columnas en 5 grupos, siguiendo la metodologı́a
Kanban, siendo estas: pendiente, realizando, probando, hecho y descartadas.

Figura 4.5: Lista de Trello al finalizar el proyecto.

Como se puede ver se descartaron 3 ideas, cada una tuvo su propia circunstancia
por la que fue retirada del proyecto, pero todas comparten que se intentaron
y no se consiguieron sacar adelante. En el apartado de problemas encontrados
(5.3.2), hablaré más de estas caracterı́sticas apartadas del proyecto y la situación
de cada una de ellas.
CAPÍTULO 4. DESARROLLO DEL PROYECTO 26

4.3. Programación del algoritmo


Una vez organizado y teniendo todo el entorno establecido, era hora de
ponerse con el código, a programar la parte de Algoritmos Genéticos e integrarla
con el cliente.

Para empezar, lo primero que habı́a que realizar era con la conexión con el
cliente. Después de unos cuántos quebraderos de cabeza con los repositorios(5.3.1),
conseguı́ usar el adecuado y una vez todo funcionaba como debı́a, me fue sencillo
implementar un agente que pudiese acelerar y frenar, dándole unas comandos
básicos.

A partir de aquı́, intenté familiarizarme con el entorno para ver todo lo que
podı́a ofrecer, en qué afectaba cada variable a la conducción del coche, releyendo
el código del repositorio y su documentación, y creando el control de versiones
en GitHub, creando el repositorio ’Genetic Driver’[1].

Una vez tenı́a más experiencia con TORCS y el wrapper, intenté añadir
concurrencia al entrenamiento, de manera que pudiese inicializar más de un
coche y que consiga simularse al tiempo toda una generación. Esto resultó
imposible por limitaciones del propio juego y del mismo cliente (para leer más
acerca de este problema 5.3.2.1).

Hasta ahora simulaba los coches uno a uno y a tiempo real, siendo esto
un método muy lento para entrenar, a la par que ineficaz. Por ejemplo, si una
población de coches tuviese 72 individuos y cada uno de estos tuviese que dar
una vuelta tardando alrededor de 2 minutos de media. Una sola generación
tardarı́a alrededor de dos horas y media en ejecutarse, lo cual es inviable.
Para solucionar este problema necesitaba hacer que los coches no se simulasen
a tiempo real, sı́ no usando una especie de cámara rápida. Acelerando ası́ el
tiempo que tardaban unitariamente entrenarse y reduciendo drásticamente la
duración que necesitaba entrenar cada generación.

Para ello TORCS cuenta con un modo texto que no representa la visualización
de la carrera y tan solo simula internamente, mostrando los resultados al final
de esta. Esto agiliza el entrenamiento de la red. Aun ası́, no es la solución ideal,
ya que no es una simulación especialmente rápida, sobre todo a partir de los
casos en los que avanza más de unos 100 metros.

4.3.1. Control de Versiones


Como ya he comentado, en GitHub[1], el proyecto estaba planteado como
una desarrollo ágil. Costarı́a de una rama principal (master), en la que no harı́a
ningún cambio de no ser que fuese una build estable y con un cometido; otra
rama de desarrollo (develop), de la que saldrı́an diferentes ramificaciones siendo
cada una de ellas una etapa en el desarrollo. Cuando se acabó el desarrollo,
contaba con 5 ramificaciones de esta, el modelo, los pesos, la función de fitness,
emparejamiento y mutación, y el entrenamiento.
CAPÍTULO 4. DESARROLLO DEL PROYECTO 27

Figura 4.6: Vista de las ramas de GitHub a lo largo del proyecto. [1]

4.3.2. Modelo
Como ya he mencionado antes (ver 4.1.2) el modelo sufrió varias variaciones
importantes a lo largo del desarrollo. Esto empezó a ser moderado con la API
Funcional de que Keras, para que las 3 salidas tuvieran distintas funciones de
activación, siendo ésta sigmoide para el giro y ’relu’ acelerador y freno.
Los resultados obtenidos se pasaban por una pequeña función para normalizar
cada resultado, y ajustarlo al rango que necesitaban esas variables. Más tarde,
se concatenaban dichas salidas para que formasen esa capa final.

Esta primera versión contaba con 6 neuronas de entrada en la primera capa,


seguidas de una capa densa, de 9 neuronas que posteriormente pasaba a una
densa de 6 y terminaba llegando a la capa de salida. Todas ellas con la función
de activación ’sigmoid’.

Implementé esta red para que estuviese disponible en el constructor de cada


conductor, teniendo ası́ cada uno de ellos una red propia. Y con ello me di cuenta
rápido de qué la función ’relu’ no iba a funcionar para el acelerador y el freno,
y que serı́a más óptimo usar una función ’than’ para el giro, ya que su rango
oscilaba entre menos uno y uno.

Viendo que después de esto todavı́a tenı́a problemas al estar haciendo unas
cuantas pruebas, acabe añadiendo otra capa oculta antes de la final que contase
con 4 neuronas con función sigmoide, y juntando en una sola neurona de salida
las dos funciones de velocidad.
CAPÍTULO 4. DESARROLLO DEL PROYECTO 28

Figura 4.7: Arquitectura de modelo antiguo dibujado por Keras.

Esto hizo, a mi parecer, que terminara siendo una red neuronal demasiado
grande y compleja para que el algoritmo genético pudiese converger de forma
eficaz. Lo que posteriormente fue creando y arrastrando diferentes problemas.

Por ello después de varias pruebas recorté el modelo hasta quedarme con
una red mucho más simple (ver Figura 4.3).

Figura 4.8: Arquitectura del modelo simplificado dibujado por Keras.

4.3.3. Pesos
Cuando el modelo estuvo terminado, los pesos se generaban de manera
aleatoria, generándose uniformemente entre un rango previamente establecido.
Lo primero que hubo que hacer fue guardar los pesos de tal manera que cuando
se reiniciase la carrera, estos pudiesen volver a ser cargados para obtener el
mismo conductor de nuevo.
CAPÍTULO 4. DESARROLLO DEL PROYECTO 29

Fue simple programar varias funciones que cogiesen la estructura de los pesos
de la red y la transformaran en un array unidimensional para ser guardado y
viceversa.
Para ello preparé una carpeta en la que se guardarán estos arrays unidimensionales,
la carpeta ’weights’ los contenı́a en el formato de NumPy , ’.npy’. Además de
eso hice un poco de refactorización del modelo y la conexión con el cliente.

Esto dejaba vı́a libre para preparar la función fitness, emparejar dichos pesos
y completar con ello el desarrollo del programa.

4.3.4. Función fitness


En cuanto a las funciones fitness, estaba preparada para ser maximizada
por el algoritmo, en general surgieron una gran cantidad de ellas y fueron muy
variadas entre si. Lo que tenı́a que conseguir, era hacerle entender a la gente
que tenı́a que avanzar lo máximo posible sin salirse de la pista.
El problema surgió, cuando si metı́a parámetros como la velocidad del tiempo
hacı́a que el coche se quedase quieto o intentase maximizar esos parámetros de
forma tan minuciosa que al final lo único que conseguı́a era llegar más rápido
en lı́nea recta hacia el borde de la pista.

Para construir estas funciones, querı́a tener una serie de datos que poder
utilizar para crearlas. Por tanto, durante la ejecución dice que se sacasen una
serie de variables que más adelante se mandarı́an al método que calcula la
función fitness. Aquı́, estarı́an disponibles para su uso además de imprimirse
por consola, para llevar una mayor trazabilidad cuando activa que el juego no
simulase la parte visual.

En total quedaron siete variables:

d - distancia recorrida.
v - velocidad media.
l - vueltas completadas partiendo de 1.
r - media de las mediciones de los raycasts.
p - posición media del coche con respecto al centro de la pista.
a - ángulo medio del coche con respecto al eje de la pista.
t - tiempo de carrera transcurrido, en segundos.

La primera función que se me ocurrió fue: (ver Figura4.9).


Como se puede ver esta función maximiza la distancia sumándole la velocidad,
para intentar que el coche avanzase lo máximo posible de la forma más rápida
que fuese capaz, ya que al cabo de unos metros al no poder superar cierta
velocidad, este parámetro no serı́a tan relevante.
También contaba con un multiplicador para las vueltas, ası́ si habı́a algún
agente que superaba el circuito este serı́a elegido con mucha más probabilidad
que otro que no lo hubiese hecho.
CAPÍTULO 4. DESARROLLO DEL PROYECTO 30

Por último, querı́a qué aumentarse la distancia media de los raycasts ya que
esto significa que está lejos de salirse, al tratarse de la medición que hace hasta
los bordes de la pista. Todo ello elevado al cuadrado para generar una función
cuadrática, que priorizara las mejoras de los conductores que ya eran de por sı́
buenos.

Figura 4.9: Primera función fitness.

Esta función vino seguida de muchas otras que tampoco funcionaron, jugando
con los parámetros de todas las maneras posibles. Todas estas pruebas y variaciones
de fitness tenı́an algo en común, la distancia siempre estaba presente y los
raycasts daban mejor rendimiento que la mayorı́a de los parámetros.

Por tanto, para crear la función que mejor funcionó tuve esto presente.
Esta función era una variación del anterior, pero no contaba con la velocidad,
ni estaba elevada al cuadrado, ya que esto a veces llevaba a saturar la selección
con un agente que lo habı́a hecho bien una única vez, y no era capaz de replicarlo
con sus mismos pesos debido a inconsistencias con la simulación.
Además, el otro añadido importante era que la distancia no la dejé tal cual,
más bien hice un sistema de recompensas por ’check points’ cada vez que el
coche avanzaba exactamente 2 metros la ’d’ crecı́a 1, para evitar ası́ el aumento
minucioso de la distancia que hacı́a que apurará las curvas lo máximo posible
con el propósito de tener más espacio de maniobra una vez pasada, cosa que no
solı́a ocurrir ya que se salı́an antes.

4.3.5. Emparejamiento y Mutación


Para esta última fase me basé en todo el conocimiento teórico que tenı́a
hasta ahora.

4.3.5.1. Emparejamiento
Empezando por emparejamiento, implementé tres métodos, de los cuales hay
más información en el marco teórico (3.5).

El primero fue el emparejamiento simple, que, sin duda, es el que peores


resultados ofrece o el que más tarda en darlos.

El segundo el emparejamiento multipunto, este emparejamiento funcionaba


claramente mejor, ya que contaba con una explotación igual de buena y una
exploración mucho mayor. Mi implementación para este consistió en separar el
array de pesos en cuatro cogiendo tres puntos aleatorios, e intercambiando o no
las cuatro partes, dependiendo de un array binario de longitud 4, (1 intercambia
y 0 no lo hace).
CAPÍTULO 4. DESARROLLO DEL PROYECTO 31

El tercero, al ver en las pruebas que los anteriores no estaban dando los
resultados esperados implementé el último método de emparejamiento uniforme.
En este, cada gen tiene un 50 por ciento de probabilidades de intercambiarse.
Sin duda este método es el que da los mejores soldados para este problema
concreto.

4.3.5.2. Mutación
En cuanto a la mutación, que también tiene su parte teórica (3.6), es muy
simple. Tan solo se trata de un método, con una probabilidad baja y variable de
ser llamado por cada agente después del emparejamiento; que cuando se ejecuta,
varı́a uno de los genes eligiendo un número aleatorio, de manera uniforme, entre
-0,5 y 0,5.
Este proceso se repite en la cadena de pesos del mismo conductor con cada
vez menos probabilidad, siendo un parámetro ajustable también (ver anexo A.4).

4.3.6. Diagrama de Clases


Con la intención de ilustrar todas las partes del código, he hecho una esquematización
del mismo con un diagrama de clases. Decir de este diagrama, que el cuadrado
de wrapper es la parte del código que viene dada por el propio cliente, aunque
haya hecho varias modificaciones en la misma.
Este recuadro contiene las diferentes clases del proyecto base para poder
sacar de ella la herencia que tiene ’my driver’ que viene dada directamente
desde la clase Driver. También, añadir que ’Run.py’ y ’Genetics.py’, no son
clases, pero tienen atributos y funciones implı́citos y por tanto me ha parecido
la manera más correcta y simple de hacer la representación.

Figura 4.10: Diagrama de clases de la parte implementada del proyecto.


CAPÍTULO 4. DESARROLLO DEL PROYECTO 32

4.4. Entrenamiento y pruebas


Esta fue sin duda la fase más larga de todas, ya que estuvo repartida a lo
largo de todo el proyecto. Porque en todo momento iba probando el código,
asegurándome de que lo que programaba funcionase como estaba planeado
que lo hiciese. Aún ası́, en esta parte detecté pequeñas incoherencias en la
programación como que la distancia de los raycasts, que en un principio estaba
midiendo las distancias en la dirección equivocada, lo que dificultó la validación
de algunas de las pruebas (más información en 5.3).

Como digo esta fase fue en su mayorı́a ajustes pequeños y he ido contando
dichas pruebas a lo largo de la memoria, por tanto, voy a explicar una de las
pruebas que realicé, como ejemplo para dar a conocer de alguna forma, cómo
fue la metodologı́a al experimentar y realizar diferentes entrenamientos.

Una de las pruebas que hice, fue mientras estaba investigando sı́ serı́a buena
idea o no que el agente tuviese la opción de frenar, ya que nunca lo usaba.
Siempre que pisaba el freno se quedaba en el sitio permaneciendo quieto hasta
que pasaban 4 segundos cuando una condición decidı́a que su fitness iba a ser
nulo. Probé a ponerle una función ’Softmax ’ junto con la velocidad, para que
tuviese que decidir si frenaba o aceleraba. Además de dividir el output para
que no fuese tan brusco. Pero ninguna de esas soluciones terminó de funcionar
adecuadamente, ya que necesitaba mucha velocidad para que el freno empezase
a ser útil, añadiendo demasiada complejidad.

Para ampliar información del entrenamiento he decidido que la mayoria de


cosas fallidas, se encontrarı́an mejor en el apartado de ’Problemas encontrados’
(leer 5.3), para explicar un poco más en detalle algunas de las cosas que no han
funcionado.
Capı́tulo 5

Resultados

5.1. Resultados obtenidos


Aún con muchas cosas en contra, y decenas de problemas a lo largo del
desarrollo. Los resultados fueron buenos.
Concretamente di por finalizado el proyecto cuando conseguı́ a dos pilotos,
que habiendo sido entrenados correctamente en un circuito, pudiesen superar
otro. Ambos contaban con la misma función de fitness y fueron entrenados en
el mismo circuito.

El primero se pegaba a la linea derecha e iba buscando el borde. Lo que


lo coloqué en un circuito más simple, donde no hubiesen curvas excesivamente
cerradas, sino largas rectas con pequeñas desviaciones al final (ver Figura 5.1).

Figura 5.1: Circuito simple.

Este primer agente consigue acabar el circuito en un 1 minuto y 31 segundos,


como se puede ver arriba a la izquierda en la imagen (ver Figura 5.2).

Figura 5.2: Primer agente en completar el circuito simple.

33
CAPÍTULO 5. RESULTADOS 34

El segundo tenı́a una estrategia diferente, consiguió ir prácticamente paralelo


a la pista en las rectas y las curvas poco pronunciadas, sin embargo este piloto
cuando vienen curvas cerradas, usa el volante para hacer giros rápidos y ası́
reducir de alguna manera su velocidad mientras aumenta su visibilidad.
Es por esto que elegı́ colocarlo en el circuito que a mi parecer resulta más
difı́cil, lleno de curvas de todo tipo y con alguna que otra recta (ver Figura 5.3).

Figura 5.3: Circuito complejo (basado en el de Suzuka, Japón).

Este segundo agente consigue acabar el circuito completo en un 4 minutos


y 59 segundos, como se puede ver arriba a la izquierda en la imagen (ver
Figura 5.4). Lo cual denota que ha aprendido a no salirse de la pista de manera
consistente, ya que ha aguantado un largo tiempo estando activo.

Figura 5.4: Primer agente en completar el circuito complejo.


CAPÍTULO 5. RESULTADOS 35

5.2. Objetivos logrados


En general, el proyecto ha sido un éxito y se han cumplido todos los objetivos:

Objetivo Estado

Realizado, en el apartado de Marco teórico


Investigación acerca de Algoritmos Genéticos
hago un resumen de lo aprendido.

Cumplido, contando con no uno sino dos agentes


Generar agente válido diferentes para probar que el proyecto ha sido
exitoso.

Implementado, existen varios parámetros a


modificar y varias métodos para probar, como
Múltiples funcionalidades en el código
pueden ser el tamaño de la población y las
funciones de emparejamiento, respectivamente.

Tabla 5.1: Tabla de Objetivos


CAPÍTULO 5. RESULTADOS 36

5.3. Problemas encontrados


La fase de entrenamiento fue donde más problemas encontré, debido a que
estuvo presente durante la mayorı́a del proyecto.
Lo más complicado fue detectar cuatro pequeños errores en la lógica de la
programación que no cumplı́an la función para la que se habı́an implementado
y, sin embargo, no afectaban de forma obvia en el comportamiento del agente,
que iteraba y conseguı́a alcanzar una pequeña mejora en su rendimiento, pero
acabando siempre en mı́nimos locales.

Figura 5.5: Agente dando un volantazo para no salirse.

Estos pequeños errores en la lógica de la programación, tuvieron un gran


impacto mientras realizaba pruebas. Ya que, al ver que nada funcionaba, iba
modificando muchas cosas sin darme cuenta del problema principal que estaba
en puntos muy concretos.
Llevando ası́ a que las pruebas no fuesen válidas, sumando esto con la
lentitud del modelo al entrenar (entre 30 minutos y 1 hora para empezar a
ver los resultados de, hacia donde tiende la función), convertı́an este problema
en algo tedioso.
Ya que, cada vez que uno de estos errores salı́a a la luz (un > o < girado, un
+= donde deberı́a de ir un =, los raycasts pasando medidas de ángulos erróneos,
o no inicializar por separado los pesos de la última capa quedando duplicados)
significaba volver a repetir todas las pruebas hechas hasta el momento, debido
a que los resultados obtenidos ya no eran fiables.
Además de estos problemas, voy a mencionar unos cuantos más que han
habido a lo largo del proyecto:

5.3.1. Clientes
Al principio, como ya he comentado, tuve varios problemas a la hora de
escoger un wrapper que abstrajese las comunicaciones con el motor del juego.
El primero que escogı́, fue un repositorio que se basaba en ’gym’ de Open-IA,
creando a partir de este un entorno de aprendizaje reforzado para TORCS.
CAPÍTULO 5. RESULTADOS 37

Contaba con mayor documentación habı́a tenido hasta ahora en otros repositorios.
Aun ası́ se me hizo imposible instalarlo en Windows, incluso usando el WSL,
distintas librerı́as y varias versiones. Hacı́a aproximadamente 5 años que no
actualizaban todo, y la mayorı́a de las cosas solo funcionaban en Ubuntu, C++,
o usando Python 2.7.

Para intentar resolver este problema instale una máquina virtual de Ubuntu
16.04 LTS, que después de haber comparado varios wrappers era el más recomendado
en la parte de requisitos.

No dio buenos resultados ya que muchas de las librerı́as tenı́an que volver
a ser instaladas, y otras nuevas ni siquiera funcionaban. Ası́ que decidı́ volver
a probar con otro cliente más sencillo, reinstalando TORCS, y rehaciendo la
estructura del proyecto. Esta vez sı́ funcionó, al hacer uso del wrapper que
expliqué en uno de los apartados anteriores (ver 4.2.3.2).

5.3.2. Ideas descartadas


5.3.2.1. Concurrencia de agentes
En cuanto al requisito de que necesitaba que el cliente estuviese pensado
para tratar con un modo ’multiagente’.
El software de competición de TORCS estaba pensado para poder soportar
hasta 10 pilotos al mismo tiempo, siendo controlados por un cliente externo.
El problema que tenı́an aparece, cuando si o si esos pilotos interactuarı́an entre
ellos al ponerlos a todos en una misma carrera.
Por tanto estos no tendrı́an forma de detectarse en un modelo simple, y
todos cada vez empezarı́an desde posiciones diferentes en la parrilla de salida. La
suma de todo esto, complicarı́a el problema notablemente, y aún ası́ no podrı́an
entrenar mas de 10 pilotos al mismo tiempo. Ası́ que me decanté por la opción
que creı́ más viable, probar a cada agente de uno en uno, secuencialmente,
aunque supusiese un aumento drástico en el tiempo que se necesitarı́a para
entrenar el modelo.

5.3.2.2. Circuito personalizado


La idea para esta nota era crear un circuito personalizado, con el editor
de circuitos integrado en el propio TORCS. Junto con el parche software del
campeonato, venı́a en una de las carpetas un supuesto editor de circuitos, que
por lo que habı́a leı́do no era muy complicado de usar y podı́as generar circuitos
personalizados de manera muy sencilla.
No funcionaba, el creador estaba por alguna razón incompleto, y en la página
oficial de TORCS no se hacı́a referencia ni se daba un enlace para descargarlo.

Al final conseguı́ encontrar uno diferente, de una de las versiones de TORCS


anteriores. Al no haber cambiado la manera en la que se leen y se cargan los
mapas, esta versión era más que suficiente.
Con ella empecé a generar un mapa que me parecı́a óptimo para el aprendizaje
de la gente empezando por una pequeña recta, seguida por dos curvas de 90o en
direcciones opuestas, para que tuviese que generalizar el giro y la aceleración. A
CAPÍTULO 5. RESULTADOS 38

partir de ahı́ intenté continuar añadiendo variedad en las curvas y en las zonas
rectas, cambiando lo cerradas que eran y su longitud respectivamente.

Figura 5.6: Circuito pensado para facilitar el entrenamiento.

Cuando llegó el momento de exportarlo le faltaban algunos archivos y el


’xml’ quedaba incompleto. Intenté repararlo fijándome en otros circuitos como
ejemplo, pero lo máximo que conseguı́ fue que apareciese en el selector de
circuitos dentro del juego, una vez que intentabas cargarlo daba un error que
no pude solucionar.
Por tanto, para solucionar esto solo quedaba no usar ese circuito, y buscar
entre todos los que habı́a uno que fuese parecido.
De todos ellos elegı́ este (5.7), que empieza en la lı́nea negra y sigue una
dirección antihoraria.

Figura 5.7: Circuito seleccionado como pista de entrenamiento.

5.3.2.3. Cámara
Esta idea, fue descartada por el simple hecho de no ser viable sin contar con
un escenario multiagente. Principalmente se trataba de desanclar la cámara de
un solo coche, y que fuese cambiando entre pilotos, según obtenı́an una mayor
puntuación o recorrı́an mayor distancia.

Otra manera en la que querı́a enfocar el problema ya que variar el foco no


parecı́a algo que soportase TORCS, era hacer una vista aérea de la pista en la
que se pudiese ver mejor la trazada de todos los coches al mismo tiempo.
CAPÍTULO 5. RESULTADOS 39

Como podemos ver en la siguiente captura sobre los controles de TORCS,


es posible cambiar la perspectiva de la cámara, siendo la tecla ’F5’ una cámara
general que hubiera venido especialmente bien, si se hubiese podido hacer un
entrenamiento multiagente de manera concurrente y sin que los coches interactuasen
fı́sicamente entre ellos 5.3.2.1.

Figura 5.8: Controles especiales de TORCS y Cámara aérea.

5.3.3. Comando meta


Otro de los problemas fue, que el comando para reiniciar (comando meta
[tabla4.2]) la carrera, que realiza una petición al servidor. No estaba implementado
dentro del wrapper, aunque sı́ habı́a referencias a este.
En este punto volvı́ a mirar diferentes clientes, pero por las mismas razones
que contado anteriormente (ver 4.2.3.2) me pareció que el que estaba usando
era el mejor con diferencia.
Por tanto, acabé decidiendo modificar el cliente para añadir este comando y
que cada vez que el coche pidiese un reinicio, activase una función que permitiera
guardar los pesos antes de que se borrase ese agente.

5.3.4. Inconsistencias en la simulación


El problema principal de los agentes, es que no eran consistentes con sus
resultados.
Sin cambiar los pesos, un mismo conductor puede hacer 2 cosas muy diferentes.
Esto era culpa del entorno virtual que en la simulación va a tiempo real, y en
el modo texto su medida del tiempo tiene una frecuencia distinta.

Por tanto tampoco se podı́a solucionar durmiendo al agente unos milisegundos.


Para solucionar esto, supuse que los datos de entrada no eran siempre los
mismos, y por tanto lo que intenté hacer fue una media entre fitness evaluando
a cada piloto dos veces seguidas, para que el resultado fuese lo más verı́dico
posible.
Otra solución posible, es abrir el TORCS sin cumplir la normativa de la
Competición acerca de que, el cliente tiene que tener un tiempo de respuesta
total de 10ms.
Quitando esta restricción es como mejor funciona el algoritmo, ya que tiene
más información para procesar, y los fitness son mucho más precisos (ver anexo
A.5).
Capı́tulo 6

Conclusiones y trabajos futuros

6.1. Conclusiones
A lo largo de todo el proyecto he podido comprobar cómo los algoritmos
evolutivos funcionan asombrosamente bien. A pesar de todos los problemas,
inconvenientes y no estar todo lo óptimo que deberı́a, esta implementación,
conseguı́a hacer que unos coches dentro de un videojuego consigan recorrer
unas cuantas curvas de un circuito dentro de un entorno virtual, desde el que
tan solo les llegaban 5 números cada 0.25 segundos, y que a su vez convertı́an
en dos números para conseguir encontrar cómo crear el mejor piloto después de
cada generación.

Todo el proyecto me ha parecido extremamente interesante. Ha sido uno de


los más grandes qué he hecho hasta la fecha. Con él he aprendido un montón y
creo que he conseguido plasmar parte de ello en esta memoria.

Queda espacio para el perfeccionamiento en este proyecto, pero lo considero


un éxito, tanto por los resultados obtenidos, como por el conocimiento adquirido.

6.2. Lineas futuras


Aún queda mucho espacio para la mejora, muchos de los parámetros se
pueden ir ajustando, se pueden continuar las pruebas, pueden haber diferentes
circuitos, coches, marchas y velocidades. Y ampliar esto, es lo que pretendo
hacer.
Una vez presentado este proyecto, voy a ir mejorando el sistema para que
sea sencillo de usar y cualquiera pueda intentar descargarlo y probar suerte
haciendo su propio conductor autónomo.

Ha sido mucho esfuerzo y estoy seguro de que ha merecido la pena.


El primer paso está dado, con la versión 1.0 del código, publicada en GitHub[1].
Se puede desarrollar y mejorar en muchos apartados. Aun ası́, creo que es
una lástima que no se hayan continuado las competiciones de TORCS de manera
asidua, ya que era una buena iniciativa para que más gente pudiera lanzarse a
probar, y aprender.

40
Bibliografı́a

[1] G. Romero. Genetic driver. [Online]. Available: https://github.com/


gonzaloromeros/GeneticDriver

[2] L. C. Daniele Loiacono and P. L. Lanzi. Simulated car racing


championship competition software manual. [Online]. Available: https:
//arxiv.org/pdf/1304.1672.pdf
[3] S. Arzt. Deep learning cars. [Online]. Available: https://youtu.be/
Aut32pR5PQA

[4] TORCS. The open racing car simulator website. [Online]. Available:
http://torcs.sourceforge.net
[5] jb dev. Navigation meshes and pathfinding.
[Online]. Available: https://www.gamedev.net/tutorials/programming/
artificial-intelligence/navigation-meshes-and-pathfinding-r4880/
[6] Hanagomi. Videojuego y ciencias de la computaciÓn – el vÍdeo juego
autÓmata. [Online]. Available: https://rb.gy/bszffh/
[7] H. Lou. Ai in video games: Toward a more intelligent
game. [Online]. Available: https://sitn.hms.harvard.edu/flash/2017/
ai-video-games-toward-intelligent-game/
[8] Tutorialspoint. Genetic algorithms tutorial. [Online]. Available: https:
//www.tutorialspoint.com/genetic algorithms/index.htm
[9] Christinelmiller. Synapsis and crossing over as occurs in prophase i
of meiosis. [Online]. Available: https://commons.wikimedia.org/wiki/File:
Synapsis and Crossing Over with Labels.png
[10] J. M. L.-G. Marco Ferreira, “On the use of perfect sequences and
genetic algorithms for estimating the indoor location of wireless sensors,”
International Journal of Distributed Sensor Networks, 2015.
[11] freepik. Different-views-modern-blue-car. [Online]. Available: https://
www.freepik.com/vectors/car
[12] G. Inc. Github copilot. [Online]. Available: https://copilot.github.com/
[13] W. B. W.-S. J. Lai. Dain (depth-aware video frame interpolation).
[Online]. Available: https://github.com/baowenbo/DAIN

[14] J. H. Jason Antic and U. Manor. Decrappification, deoldification, and


super resolution. [Online]. Available: https://www.fast.ai/2019/05/03/
decrappify/
[15] Y. Nirkin, Y. Keller, and T. Hassner, “FSGAN: Subject agnostic face
swapping and reenactment,” in Proceedings of the IEEE International
Conference on Computer Vision, 2019, pp. 7184–7193.

41
BIBLIOGRAFÍA 42

[16] B. O. Patrick Esser, Robin Rombach, “Taming transformers for


high-resolution image synthesis.” IWR, HEIDELBERG UNIVERSITY.,
2021.
[17] M. Y., “Current status of applying artificial intelligence in digital games, in:
Nakatsu r., rauterberg m., ciancarini p. (eds),” Handbook of Digital Games
and Entertainment Technologies. Springer, Singapore., 2017.
[18] Wikipedia. Artificial stupidity. [Online]. Available: https://en.wikipedia.
org/wiki/Artificial stupidity
[19] T. Thompson. (2020) Revisiting the ai of alien: Isolation. [Online].
Available: https://www.gamasutra.com/blogs/TommyThompson/
20200520/363134/Revisiting the AI of Alien Isolation.php
[20] S. S. Jialin Liu, “Deep learning for procedural content generation,” Neural
Computing and Applications 2020 (Early Access), 2020.
[21] Sony. Patent - dynamic music creation in gaming.
[Online]. Available: https://patentscope.wipo.int/search/en/detail.jsf?
docId=WO2020102005&tab=PCTDESCRIPTION
[22] D. Hardawar. How ’microsoft flight simulator’ became a ’living
game’ with azure ai. [Online]. Available: https://www.engadget.com/
microsoft-flight-simulator-azure-ai-machine-learning-193545436.html
[23] T. Thompson. (2018) Machine learning for player analytics in tomb raider:
Underworld. [Online]. Available: https://medium.com/@t2thompson/
tombraider-60682f8fe36f
[24] DeepMind. Alphastar: Mastering the real-time strategy game
starcraft ii. [Online]. Available: https://deepmind.com/blog/article/
alphastar-mastering-real-time-strategy-game-starcraft-ii
[25] OpenIA. Dota 2 with large scale deep reinforcement learning.(openia five).
[Online]. Available: https://openai.com/projects/five/
[26] Microsoft. DrivatarTM in forza motorsport. [Online].
Available: http://web.archive.org/web/20140208083127/http://research.
microsoft.com/en-us/projects/drivatar/forza.aspx
[27] Wikipedia. Dynamic game difficulty balancing. [Online]. Available: https:
//en.wikipedia.org/wiki/Dynamic game difficulty balancing#Approaches
[28] J. M. GITLIN. War stories: How forza learned to love neural nets to train
ai drivers. [Online]. Available: https://arstechnica.com/gaming/2020/09/
war-stories-how-forza-learned-to-love-neural-nets-to-train-ai-drivers/
[29] T. Thompson. How forza’s drivatar actually works. [Online]. Available:
https://youtu.be/JeYP9eyIl4E
[30] MPvHarmelen. Python client for torcs with network plugin for the 2012
scrc. [Online]. Available: https://github.com/MPvHarmelen/torcs-client
[31] V. M. Dominguez Rivas, Plantilla TFG ETSISI UPM. ETSISI, 2020.
Anexos

43
Apéndice A

Uso del Código

Estos son algunas de las partes más importantes del código a cambiar si se
quiere hacer uso del mismo para intentar entrenar un agente con las funciones
de los algoritmos genéticos que han sido implementadas.

A.1. Creación de Driver


Para la creación del Driver (agente) será necesario llamar a este método:
main(MyDriver(logdata=False, generation=g, n=i, max_gear=1))
Tal como se ve, el Driver siempre tendrá un logdata a False. La generación será
importante ya que si es 1 los pesos serán una inicialización aleatoria, si es -1
usará los pesos de la carpeta ’tmp/elite’, siendo ’n’ para este caso la posición
del archivo en la propia carpeta ordenada alfabéticamente; si la generación se
llama como cualquier otro número, la n significará de donde se cargan los pesos
dentro de la carpeta ’weights’.

A.2. Funciones de emparejamiento


Dentro de run.py se pueden controlar las funciones de emparejamiento:
# Funciones de emparejamiento
genetic.crossover_simple(emparejamientos1, padres, elite + nuevos + n_hijos*0)
genetic.crossover_multipunto(emparejamientos2, padres, elite + nuevos + n_hijos*2)
genetic.crossover_genes(emparejamientos3, padres, elite + nuevos + n_hijos*4)
La teorı́a detrás de estas funciones está explicada en profundidad en el apartado
3.5. El último parámetro es el que se debe variar si no se hace uso de alguna
función, ya que es el número desde donde va a empezar a sustituir los pesos de
la generación pasada por los hijos obtenidos.
También es importante que si se modifica el número de funciones en uso, se
cambie acorde el tamaño de la población desde la siguiente linea:
# Población de cada generación
poblacion = int(elite + nuevos + n_padres*6)

A.3. Inicio de entrenamiento


Para comenzar el entrenamiento, hay que establecer los Drivers de cada tipo
de población dentro de run.py.
Siendo estos la élite, que mantiene a los mejores corredores de la generación
anterior, los nuevos, que se generan de manera autónoma para intentar añadir
variedad a la muestra, y los padres que serán quienes se terminen emparejando.
Ejemplo:

44
APÉNDICE A. USO DEL CÓDIGO 45

# Entrena o Infiere
train = False

# Drivers por cada tipo de selección (población)


elite = 5
nuevos = 8
n_padres = 15

Además habrá que asegurarse de que la variable ’train’ del inicio del código,
se encuentra a ’True’ al entrenar, y a ’False’ al querer comprobar los resultados.

A.4. Mutaciones
Ya están aplicadas dentro del propio proceso, si se quieren modificar sus
probabilidades:
La probabilidad general de mutación por Driver está en run.py y la probabilidad
de que continue modificando el mismo array, está en genetic.py, dento de la
función mutacion().

# run.py
# Mutaciones
for i in range(elite+1, poblacion):
if 15 > random.randint(0, 100):
genetic.mutacion(i)

# genetic.py
prob_sig_mutacion = 0.7

A.5. Modificar ’Timeout’ del servidor


Para solucionar algunos problemas de conexión y tener más información
sobre el entorno, se puede agrandar el ’timeout’ del servidor al ejecutar el juego
con este comando en Windows:

.\wtorcs.exe -t 100000000

Está en nanosegundos, y se traduce en 100ms de tiempo de respuesta en vez


de los 10ms de base.

También podría gustarte