La ciencia de los videojuegos: IA (III)

En las entradas anteriores (la primera y la segunda) hemos hablado básicamente de cómo podemos movernos por el mapa: métodos para encontrar rutas, para que nuestro personaje se mueva en espacios abiertos, para que interactúe en su movimiento con otros elementos del juego. Sin embargo, nos queda una cosa, y es cómo organizamos todo esto. Nos falta la parte decisiva de la inteligencia artificial, y es precisamente que coordine los diferentes elementos de manera inteligente. Aunque en ocasiones parezca una estupidez, esto es necesario en todo. Incluso en juegos de carreras o de lucha, donde la estrategia parece ser menos importante, es fundamental que haya cierta estrategia, y además, que el ordenador no repita una y otra vez los mismos movimientos. ¿Cómo conseguimos eso?

Lo básico: sistemas de puntuación

Una de las ideas que normalmente se le ocurren primero al programador, y no funciona mal, es establecer un sistema de puntuación. Es decir, recompensar con puntos ciertos parámetros que me interesan, y elegir en función de lo que tenga más puntos.

Para hacer esto, es útil tener cierta organización, lo que se llama una máquina de estados finitos. Aunque parezca algo avanzado, simplemente permite que mi IA puede estar en varios estados. Por ejemplo, en Call of Duty podríamos tener los estados “Explorar”, “Cubrirse”, “Alerta” y “Abrir fuego”. Mi personaje estará en uno de esos cuatro estados, y actuará de forma diferente según su estado actual. Lo único que necesito, finalmente, es cambiar de un estado a otro según lo que ocurra. Para ello, mi IA debe tener unos sensores, a los cuales debe poder reaccionar. Por ejemplo, es útil tener información desde dónde me llegan los disparos. Si me llegan desde cualquier parte que no sea el frente, entonces eso implica que me están flanqueando y mi vida corre peligro, así que debo pasar inmediatamente al estado “Cubrirse”. Una vez bien cubierto, hay que cambiar el estado a “Alerta” para tratar de localizar dónde está el agresor y estar preparado. Una vez localizado, pasamos a “Abrir fuego”. Si no se detecta nada, tal vez interese cambiar a “Explorar” y buscar enemigos.

Por supuesto, esto por sí solo no es suficiente. Las situaciones pueden ser extraordinariamente complejas, con muchos estados, a veces con diferencias sutiles entre ellos. En un juego de estrategia por turnos, por ejemplo, la decisión ¿a qué unidad enemiga me conviene más atacar? Es muy complicada. Sí, estoy en el estado “Ataque”, tengo que atacar, pero hay muchas unidades. Incluso a la misma unidad la puedo atacar desde diferentes puntos, y no todos son equivalentes. Lo que hago es asignar puntos a las diferentes unidades enemigas basándome en diferentes parámetros:

  • Cojo cada unidad  enemiga y le asigno puntos según tenga o no ventaja sobre ella. Si mi unidad es más fuerte, más puntos. Incluso puedo restar puntos si estoy en desventaja.
  • Cojo cada unidad enemiga y le asigno puntos según su índice de peligrosidad: a las más fuertes hay que eliminarlas con mayor prioridad.
  • Una vez escogida la unidad, miro la posición: le asigno más puntos a las posiciones donde mi unidad sea más fuerte, como montañas o bosques. También puedo mirar todas las posiciones posibles de todas las unidades que están en mi rango de ataque para abrir aún más el abanico de posibilidades.
  • También conviene ver si esa posición es muy susceptible de ser atacada por otras unidades. Si muchas unidades enemigas tienen posibilidad de atacar en esa posición, resto puntos, por ejemplo.

Puedo añadir todas las variables que sean de interés, según mi juego. Un sistema de puntos bien ajustado permitirá a la IA tomar decisiones basadas en distintos factores, relativamente inteligentes en entornos no demasiado complejos. Los sistemas de puntos tienen la ventaja de ser intuitivos y sencillos de programar. Su contra principal es que cuando hay una gran cantidad de factores sumando y restando puntos es muy difícil ajustarlos para que funcionen siempre con un 100%, lo que lleva a “Mira qué movimiento más tonto ha hecho el ordenador” o que sea fácil engatusarlo para que se mueva como el jugador desea. Para evitar eso, nuestra IA tiene que hacer una tarea importante. Muy importante: aprender. Evolucionar. Al menos, durante la fase de desarrollo, hasta que alcance un nivel adecuado para hacerlo pasar por inteligente.

Algoritmos genéticos

Ahora es cuando llegamos realmente al quiz del asunto. ¿Cómo hacemos que un ordenador vaya mejorando sus resultados siempre? Pues la idea principal son los llamados algoritmos genéticos. Estos algoritmos lo que hacen es ir cogiendo al “mejor de la especie” y aprovecharlo para mejorarla, cada vez más, poco a poco.

Para esto se hace lo siguiente: empezamos con una entidad aleatoria, o diseñada por nosotros, que creamos que puede hacer bien su función. Lo que hacemos es ponerle nota según lo bien que lo haga: por ejemplo, en un juego de carreras de coches lo que indica lo bien que lo hace es cuánta distancia es capaz de recorrer en 20 segundos, sin chocarse. El coche que llegue más lejos lo cogemos como modelo. A partir de él, generamos 100, o 200 coches, con pequeñas modificaciones del nuestro: algunos con un poco más de maniobrabilidad, unos con una aceleración un poco menos brusca… De esos 200 coches, nos quedamos con el que ha llegado más lejos. Este será parecido al original, pero tiene unas sutiles diferencias, que son las que le han hecho llegar más lejos. Y así poco a poco el algoritmo genético encontrará el coche que es capaz de llegar más lejos en menos tiempo, mediante la selección natural.

Esta clase de algoritmos se emplean no sólo en inteligencia artificial, sino también mucho en el diseño de aparatos, por ejemplo, para diseñar antenas o para colocarlas. Lo que se hace es empezar por la forma de una antena normal y simular cuánta radiación recibe. Después, vamos modificando ligeramente la forma de la antena, y nos quedamos con la que reciba más. A partir de esta vamos generando más antenas, quedándonos siempre con la mejor de cada generación.

Otra forma de emplear los algoritmos genéticos es para hacer cosas como la generación de contenido. Lo que se hace es dotar a cada individuo de un genoma, es decir, una especie código de barras que codifica algunas características de las que nos interesan, y que tenga una descendencia con otro individuo. El sujeto descendiente tendrá un genoma obtenido con una combinación de los dos anteriores, según las reglas que podemos diseñar a placer. Esto puede hacerse con el objetivo de tratar de conseguir una IA mucho más eficiente a partir del cruce de diferentes individuos que creamos competentes. Otro de sus usos puede ser obtener no sujetos más inteligentes, sino simplemente más variados, que tengan un comportamiento parecido en lo global pero diferente en algunos aspectos particulares.

Los algoritmos genéticos no están mal, pero por sí solos habitualmente no son extremadamente útiles. Es decir, en el ejemplo del coche: yo pongo diferentes coches a conducir, y trato de mejorarlos con un algoritmos genéticos, pero tengo el siguiente problema:

  • Si le pongo un algoritmo del tipo: “si detectas un borde, gira hacia el lado correcto para evitarlo” ese lo único que hace es girar y evitar el borde en el momento justo. Puedo ir más rápido y girar más o menos cerrado, pero no se busca la mejor estrategia para los distintos tipos de curvas, o bien  si la implementamos del todo lo que tenemos es una IA perfecta que siempre hace el mismo recorrido.
  • Si uso grafos o steering behaviors más de lo mismo: la ruta que recorre es siempre la misma o no es la más óptima.

Es decir, quiero algo que no siempre haga lo mismo,  que sea capaz de razonar cuál es el mejor trayecto, y además, que sea capaz de evolucionar bajo un algoritmo genético. ¿Cuál es la solución entonces?

El cerebro: redes neuronales

Si queremos que piensen, lo mejor es añadir un cerebro. Literalmente hablando. Una red neuronal es un código que funciona de forma análoga a como funciona un cerebro, a pequeña escala. No sólo se emplean para videojuegos, sino que tienen un gran cantidad de aplicaciones en reconocimiento de imágenes o texto escrito. Incluso son interesantes de analizar por sí solas, porque una red neuronal lo suficientemente grande y realista podría funcionar exactamente igual que un cerebro nuestro. Su construcción podría dar muchas respuestas sobre cosas como la consciencia.

A más pequeña escala, estas redes pueden imitar comportamientos aislados del cerebro, como memoria a largo plazo, reconocimiento de imágenes o aprendizaje. Y todo esto utilizando neuronas simuladas “realistas” acordes con los modelos biológicos. Su interés en física y neurociencia está fuera de toda duda y es uno de los objetos más investigados actualmente.

¿Cómo funciona una red neuronal? Pues igual que cerebro. Voy a explicar un poco cómo funciona, a nivel físico. Nótese que este párrafo será muy divulgativo y puede contener imprecisiones, técnicamente hablando. El mecanismo biológico es muy, muy complejo, y cada componente del cerebro tiene también una física complicada, pero para entendernos vamos a decir que el cerebro tiene neuronas, que pueden estar en dos estados, activa o en espera. Una neurona activa transmite un impulso eléctrico a todas las neuronas que están conectadas a ella. Si una neurona está en espera, se activará si le llega suficiente electricidad de sus vecinas. De lo contrario, se apagará. Además, los enlaces no son todos iguales. Las sinapsis (enlaces entre neuronas) son ligeramente diferentes, no permiten pasar, digámoslo así, la misma cantidad de electricidad. Por ejemplo, supongamos que una neurona A está conectada con una B1 y otra B2, y que está inactiva. Si B1 y B2 transmiten un pulso eléctrico de la misma intensidad, y las sinapsis fueran iguales, a A le llegaría la misma cantidad de electricidad de B1 y B2. Pero las sinapsis no son exactamente iguales, lo que hace que no transmitan igual de bien. Puede llegar más cantidad de electricidad desde B1 que desde B2, a pesar de que emiten con la misma intensidad.

Pues este modelo puede programarse con relativa facilidad en un ordenador. Creamos unas neuronas y una ley para activarlas o ponerlas en espera. En el caso de los videojuegos, lo que se suele hacer muy habitualmente es que tanto las neuronas como las sinapsis tengan asociadas un valor numérico. Para activar una neurona, tiene que darse que la suma de los valores de las sinapsis sea mayor que el valor de la neurona. Si la neurona que emite está conectada a esta sinapsis, le suma a su vecina el valor numérico adecuado. En el caso de B1, B2 y A, si  A tiene un valor 5, y las sinapsis B1-A y B2-A tienen valores 2 y 6, respectivamente,

  • Si encendemos B1 a A le llega una corriente de 2. Como A tiene un valor de 5, no se activa y sigue en espera.
  • Si encendemos B2 a A le llega una corriente de 6, y como A tiene un valor de 5, se activa.
  • Si encendemos ambas, a A le llega 6+2, la suma de ambas sinapsis, y se activa.

Este es un modelo muy simple de red neuronal, en el que cada neurona recibe el impulso de todas las sinapsis. En ciencias de la computación, donde se estudia aprendizaje profundo, consciencia artificial, etc. cada neurona puede ser simplemente una puerta lógica, del tipo AND, OR… que funciona simplemente activando o desactivando según las puertas lógicas, sin poner valores numéricos. En definitiva, hay varias maneras de programar la red neuronal.

Pero centrémonos en el caso de los videojuegos. Tenemos neuronas y enlaces, cada una con un valor. Ahora bien, ¿cómo funciona esto en nuestro videojuego? Necesitamos, como decíamos antes, sensores. Nuestra IA necesita unos sentidos. Estos activarán una serie de neuronas que serán el input. Al activarse esas neuronas, se irán activando otras según estén estructurados los enlaces. Finalmente, hay una serie de neuronas que son el output. Cuando estas neuronas se activan, se “pulsa” el botón de saltar, girar, mover o lo que sea que tengamos que hacer según nuestro juego.

Ahora bien, esto parece excepcionalmente complicado de implementar. Hay que hacer una red y ajustar los valores perfectamente para que la IA sea capaz de responder a estímulos con la respuesta correcta. ¿Cómo sé cuántas neuronas necesito? ¿Cómo las conecto? ¿Y qué valores tengo que ponerles?

Una red de esta clase puede ser especialmente complicada, especialmente si el juego es complicado. Y crearla no es una tarea sencilla. Por eso, habitualmente se emplean conjuntamente con los algoritmos genéticos. Creamos una red sencilla, y la ponemos a jugar. Jugará fatal, por supuesto. Sin embargo, generamos muchas redes con diferentes arquitecturas, cada una ligeramente diferente. Y vemos cuál consigue mejor resultado. A partir de esa, vamos generando más redes, ponderando neuronas y enlaces, e incluso añadiendo o quitando sinapsis. Así, poco a poco, la red neuronal irá mejorando, aprendiendo a detectar los estímulos y cómo actuar frente a ellos.

Sí, señores, no sólo tenemos una IA que sabe jugar, sino algo incluso mejor: una IA que aprende ella sola a jugar. Para nuestros propósitos, podemos simplemente entrenar a la IA hasta el punto que vemos que da un buen desafío al jugador, guardar la red neuronal como está y ponerla en la versión final del juego, para evitar que siga aprendiendo. Porque igual dada la arquitectura y los sensores que le hemos puesto no llega a más, pero… imaginaos que os compráis un juego y cuanto más jugáis más difícil os lo pone. Y encima esas cosas suelen aprenden de forma exponencial, cada vez más rápido. A mí personalmente me daría un poco de miedo comprobar que los jugadores de Call of Duty están aprendiendo y cada vez me dan caza de una forma más agresiva, sería como tener a Skynet dentro del juego. Es importante, por tanto, usar una red fija o bien limitar la cantidad de enlaces y neuronas, lo cual limita hasta cierto punto la inteligencia de nuestra red.

Podemos evitar que la red neuronal aprenda sin control limitando el número de enlaces y nodos que puede tener, así como escogiendo cautelosamente los inputs y los outputs. Sin embargo, puede seguir cambiando. En Smash Bros, los muñequitos Amiibo van subiendo de nivel conforme el jugador lucha con ellos. Cuando alcanzan su nivel máximo dejan de mejorar como tales, pero nunca dejan de cambiar: siempre van tratando de imitar los movimientos que lleva el jugador: aprenden los combos que utilizas y a evitar los ataques que usas de forma recurrente. Probablemente emplean una red neuronal, que está limitada para que no se vuelvan invencibles, y esta red lleva un algoritmo que permite que cambie su forma de luchar, cambiando los parámetros de la red.

Una red neuronal bien entrenada y sin límite de crecimiento puede jugar realmente bien a cualquier cosa. El programa AlphaGo que recientemente ha ganado a uno de los campeones humanos de Go (es la primera vez que un programa logra ganar a este juego) incluye entre sus algoritmos una red neuronal muy bien entrenada, de jugar partidas contra otros jugadores o incluso contra sí mismo. El siguiente objetivo del programa AlphaGo de Google es jugar contra los jugadores profesionales de Starcraft de la liga coreana, los cuales siguen siendo todavía imbatibles ante la IA: Starcraft es un videojuego de estrategia en tiempo real, de modo que no sólo hay que saber pensar bien, hay que hacerlo rápido. Los cambios de estrategia en la partida, las maniobras arriesgadas y originales y la capacidad de análisis de la situación son cruciales en este tablero virtual. Como hemos dicho, todas estas tareas son relativamente sencillas para un humano, pero son todo un reto para un programa como AlphaGo.

Para que veáis el potencial que tienen estas redes, os dejo un vídeo en el que se ve el funcionamiento de un red neuronal que aprende a jugar a Super Mario World. Se ve el progreso de cómo poco a poco va avanzando y mejorando su arquitectura:

Y este de un software que permite dibujar unas criaturitas con la forma que quieras y que ellas solas aprenden a moverse, a partir de una red neuronal y un algoritmos genético. El programa se puede descargar y probar a dibujar tus propios bichejos; puede verse en tiempo real sus intentos de aprender a caminar, así como cómo va cambiando la red neuronal durante el proceso:

Conclusiones

Hemos visto diferentes opciones para conseguir que nuestra IA piense globalmente. La más directa es hacer un sistema de puntuación con una máquina de estados finitos, pero esta puede ser complicada si las acciones a realizar son complejas.

Por ello, la combinación de redes neuronales y algoritmos genéticos puede sacarnos las castañas del fuego. Lo único que hay que hacer es colocar unos sensores y un output adecuado, y jugar con nuestra IA hasta que sea capaz de aprender. El problema principal que tienen estas redes es que en juegos de estrategia compleja para controlarlos bien necesitan una cantidad mayor de nodos, lo que puede alargar el procesamiento.

En todo caso, no hay que olvidar que la IA puede tener diferentes niveles y que los diferentes métodos pueden combinarse. Por ejemplo, en un juego del tipo Age of Empires puede usarse un grafo para encontrar rutas a grosso modo, pero el movimiento de formación e individual de las unidades vendrá dado por Steering Behaviors. La IA económica puede venir dada por una máquina de estados finitos, que en lugar de estar controlada por un sistema de puntos podría estarlo por una red neuronal.

Hay muchas más técnicas de IA, tanto para mover entidades como para controlar el juego. En todo caso saber cuál es la adecuada a nuestro problema, y saber combinar adecuadamente las cosas que tenemos a nuestra disposición. Y además, a menudo la originalidad y la creatividad a la hora de poner sensores, o de decidir qué estados ponemos y las transiciones ellos, son lo que diferencian una IA correcta de una IA que sea realmente divertida.

Por supuesto, para quien no lo haya pensado, esta clase de IA se emplea también a menudo en robótica, tanto para mover los robots como para tomar diferentes decisiones, ya que un robot se parece en parte a una entidad de un videojuego.

Y es que aunque no lo parezca hay una gran cantidad de técnicas matemáticas puestas a disposición del entretenimiento. Espero que las entradas de IA en los videojuegos os hagan ver un poquito más la gran cantidad de cosas que hacen falta para hacer un videojuego de calidad y el hecho de que avanzar en el campo de la inteligencia en videojuegos también es un avance en el campo de la computación abstracta, el modelado de sistemas físicos.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *