Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Optimización de un conductor
autónomo mediante algoritmos genéticos
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
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
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
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
Bibliografı́a 41
Anexos 43
VI
Índice de figuras
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.
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.
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
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
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.
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
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.
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.
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.
Marco teórico
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.
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.
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.
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.
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
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.
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.
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.
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.
19
CAPÍTULO 4. DESARROLLO DEL PROYECTO 20
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.
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’.
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.
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.
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.
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
angle [−π, +π] (rad) Ángulo entre la dirección del coche y el eje de la pista.
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.
speed x (-∞, +∞) (km/h) Velocidad del coche yendo paralelo a 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.
steering [-1,1] Dirección del volante (-1 todo izquierda, 1 todo derecha).
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.
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
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.
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.
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
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).
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.
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.
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.
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.
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.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 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).
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.
Resultados
33
CAPÍTULO 5. RESULTADOS 34
Objetivo Estado
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).
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.
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.
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.
40
Bibliografı́a
[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
41
BIBLIOGRAFÍA 42
43
Apéndice A
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.
44
APÉNDICE A. USO DEL CÓDIGO 45
# Entrena o Infiere
train = False
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
.\wtorcs.exe -t 100000000