Introducción a los agentes de aprendizaje por refuerzo con Unity Game Engine

, El aprendizaje por refuerzo (aprender a partir de observaciones y recompensas) es el método más parecido a la forma en que aprenden los humanos (y los animales).

A pesar de esta similitud, también sigue siendo el dominio más complicado y desconcertante del aprendizaje automático moderno. Para citar al famoso Andej Karpathy:

El aprendizaje por refuerzo es terrible. Da la casualidad de que todo lo que teníamos antes era mucho peor.

Para ayudar a comprender el método, crearé un ejemplo paso a paso de un agente que aprende a navegar en un entorno utilizando Q-Learning. El texto comenzará con los primeros principios y terminará con un ejemplo completamente funcional que puede ejecutar en el motor de juego Unity.

Para este artículo, se requieren conocimientos básicos del lenguaje de programación C#. Si no estás familiarizado con el motor de juego Unity, considera que cada objeto es un agente, el cual:

ejecuta Start() una vez al comienzo del programa y Update() continuamente en paralelo a los otros agentes.

El repositorio que acompaña a este artículo está en GitHub. Todas las imágenes son del autor a menos que se especifique lo contrario.

¿Qué es el aprendizaje por refuerzo?

En el aprendizaje por refuerzo (RL), tenemos un agente que es capaz de tomar acciones, observar los resultados de estas acciones y aprender de las recompensas/castigos por estas acciones.

Una ilustración típica de un bucle RL. (i) el agente realiza una acción, (ii) la acción afecta el medio ambiente, (iii) el agente lee el estado del medio ambiente (iv) el agente actualiza su política en función de las recompensas/castigos por la acción. Fuente: https://en.wikipedia.org/wiki/Reinforcement_learning.

La forma en que un agente decide una acción en un determinado estado depende de su política. Una política π es una función que define el comportamiento de un agente, asignando estados a acciones. Dado un conjunto de estados S y un conjunto de acciones A una política es un mapeo directo: π: S → A .

Además, si queremos que el agente tenga más opciones posibles, con elección, podemos crear una política estocástica. Entonces, en lugar de una sola acción, una política determina la probabilidad de realizar cada acción en un estado dado: π: S × A → [0, 1].

Ejemplo de robot de navegación

Para ilustrar el proceso de aprendizaje, crearemos un ejemplo de un robot navegante en un entorno 2D, utilizando una de cuatro acciones, A = {Izquierda, Derecha, Arriba, Abajo}. El robot necesita encontrar el camino hacia el premio desde cualquier momento del mapa, sin caer al agua.

El mapa para navegar en un ceñidor de 8×5. Hay un estrecho punto de estrangulamiento en la columna 4 (indexado en 0), lo que obliga al agente a rodearlo para alcanzar el trofeo. La política óptima lleva al agente desde cualquier casilla de césped hasta el trofeo en el menor número de pasos posibles.

Las recompensas se codificarán junto con los tipos de mosaicos mediante una enumeración:

enumeración pública TileEnum { Agua = -1, Hierba = 0, Premio = 1 }

El estado viene dado por su posición en la cuadrícula, lo que significa que tenemos 40 estados posibles: S = [0…7] × [0…4] (una cuadrícula de mosaicos de 8 × 5), que codificamos usando una matriz 2D:

_map = { { -1, -1, -1, -1, -1, -1, -1, -1 }, // toda la frontera de agua { -1, 0, 0, 0, -1, 0, 1, -1 }, // 1 = Premio (trofeo) { -1, 0, 0, 0, -1, 0, 0, -1 }, { -1, 0, 0, 0, 0, 0, 0, -1 }, { -1, -1, -1, -1, -1, -1, -1, -1 }, // todo borde de agua }

Almacenamos el mapa en un mosaico TileGrid que tiene las siguientes funciones de utilidad:

// Obtener un mosaico en una coordenada public T GetTileByCoords(int x, int y); // Dado un mosaico y una acción, obtenemos el siguiente mosaico public T GetTargetTile(T source, ActionEnum action); // Crea una cuadrícula de mosaicos a partir del mapa public void GenerateTiles();

Utilizaremos diferentes tipos de mosaicos, de ahí la T genérica. Cada mosaico tiene un TileType dado por TileEnum y, por lo tanto, también su recompensa, que se puede obtener como (int) TileType.

La ecuación de Bellman

El problema de encontrar una política óptima se puede resolver de forma iterativa utilizando la ecuación de Bellman. La ecuación de Bellman postula que la recompensa a largo plazo de una acción es igual a la recompensa inmediata por esa acción más la recompensa esperada de todas las acciones futuras.

Se puede calcular de forma iterativa para sistemas con estados discretos y transiciones de estados discretas. Tener:

s — estado actual, A — conjunto de todas las acciones, s' — estado alcanzado al realizar la acción a en el estado s, γ — factor de descuento (cuanto mayor es la recompensa, menor es su valor), R(s, a) — recompensa inmediata por realizar la acción a en el estado s

La ecuación de Bellman establece entonces que el valor V(s) de un estado s es:

Resolver la ecuación de Bellman de forma iterativa

Calcular la ecuación de Bellman es un problema de programación dinámica. En cada iteración n, calculamos la recompensa futura esperada alcanzable en n+1 pasos para todos los mosaicos. Para cada mosaico, almacenamos esto usando una variable de Valor.

Damos una recompensa basada en la ficha objetivo, es decir, 1 si se alcanza el premio, -1 si el robot cae al agua y 0 en caso contrario. Una vez que se alcanza el premio o el agua, no es posible realizar ninguna acción, por lo tanto, el valor del estado permanece en el valor inicial 0.

Creamos un gestor que generará el grid y calculará las iteraciones:

inicio vacío privado() {tileGrid.GenerateTiles(); } actualización vacía privada() { CalculateValues(); Paso(); }

Para realizar un seguimiento de los valores, utilizaremos una clase VTile que contiene un Valor. Para evitar tomar valores actualizados directamente, primero configuramos NextValue y luego configuramos todos los valores a la vez en la función Step().

gamma de flotación privada = 0,9; // Factor de descuento // La ecuación de Bellman private double GetNewValue(VTile Tile) { return Agent.Actions .Select(a =>tileGrid.GetTargetTile(tile, a)) .Select(t => t.Reward + gamma * t.Value) // Recompensa en [1, 0, -1] .Max(); } // Obtener los siguientes valores para todos los mosaicos private void CalculateValues() { for (var y = 0; y < TileGrid.BOARD_HEIGHT; y++) { for (var x = 0; x < TileGrid.BOARD_WIDTH; x++) { vartile =tileGrid.GetTileByCoords(x, y); if (tile.TileType == TileEnum.Grass) { mosaico.NextValue = GetNewValue(tile); } } } } // Copiar los siguientes valores a los valores actuales (paso de iteración) private void Step() { for (var y = 0; y < TileGrid.BOARD_HEIGHT; y++) { for (var x = 0; x < TileGrid.BOARD_WIDTH; x++) {tileGrid.GetTileByCoords(x, y).Step(); } } }

En cada paso, el valor V(s) de cada ficha se actualiza al máximo en todas las acciones de la recompensa inmediata más el valor descontado de la ficha resultante. La recompensa futura se propaga hacia afuera desde la ficha de premio con un rendimiento decreciente controlado por γ = 0,9.

La solución converge después de 10 iteraciones, momento en el cual se puede derivar la política óptima seleccionando la acción que conduce al mosaico con el valor más alto.

Calidad de la acción (valores Q)

Hemos encontrado una manera de asociar estados con valores, lo cual es suficiente para este problema de búsqueda de caminos. Sin embargo, este se centra en el entorno, ignorando al agente. Para un agente normalmente queremos saber cuál sería una buena acción en el entorno.

En Q-learning, este valor de una acción se llama calidad (Q-Value). A cada par (estado, acción) se le asigna un único valor Q.

Donde el nuevo hiperparámetro α define una tasa de aprendizaje: la rapidez con la que la nueva información anula la anterior. Esto es análogo al aprendizaje automático estándar y los valores suelen ser similares; aquí usamos 0,005. Luego calculamos el beneficio de realizar una acción utilizando la diferencia temporal D(s,a):

Dado que ya no consideramos todas las acciones en el estado actual, sino la calidad de cada acción por separado, no maximizamos todas las acciones posibles en el estado actual, sino todas las acciones posibles en el estado que alcanzaremos después de realizar la acción cuya calidad estamos calculando, combinada con la recompensa por realizar esa acción.

Un agente en (2,2) que realiza una acción para ir a la derecha será un estado s' = (3, 2) con cuatro posibles acciones a' y sus respectivas cualidades Q(s', a').

El término de diferencia temporal combina la recompensa inmediata con la mejor recompensa futura posible, lo que lo convierte en una derivación directa de la ecuación de Bellman (consulte Wiki para obtener más detalles).

Para entrenar al agente, volvemos a crear una instancia de una cuadrícula, pero esta vez también creamos una instancia del agente, ubicada en (2,2).

Agente privado _agente; privado vacío ResetAgentPos() { _agent.State =tileGrid.GetTileByCoords(2, 2); } inicio vacío privado() {tileGrid.GenerateTiles(); _agent = Instanciar(agentPrefab, transformar); RestablecerPosAgente(); } Actualización privada vacía() { Paso(); }

Un objeto Agente tiene un estado actual QState. Cada QState mantiene el Q-Value para cada acción disponible. En cada paso el agente actualiza la calidad de cada acción disponible en el estado:

paso vacío privado() { if (_agent.State.TileType!= TileEnum.Grass) { ResetAgentPos(); } else { QTile s = _agent.State; // Actualiza los valores Q para TODAS las acciones desde el estado actual foreach (var a en Agent.Actions) { double q = s.GetQValue(a); QTile sPrime = TileGrid.GetTargetTile(s, a); doble r = sPrime.Reward; doble qMax = Agent.Actions.Select(sPrime.GetQValue).Max(); doble td = r + gamma * qMax – q; s.SetQValue(a, q + alfa * td); } // Realiza la mejor acción disponible a ActionEnum elegida = PickAction(s); _agent.State = TileGrid.GetTargetTile(s, elegido); } }

Un Agente tiene un conjunto de acciones posibles en cada estado y tomará la mejor acción en cada estado.

Si hay varias mejores acciones, una de ellas se toma al azar, ya que hemos barajado las acciones de antemano. Debido a esta aleatoriedad, cada entrenamiento se desarrollará de manera diferente, pero generalmente se estabilizará entre 500 y 1000 pasos.

Ésta es la base del Q-Learning. A diferencia de los valores estatales, la calidad de la acción se puede aplicar en situaciones donde:

la observación es incompleta en un momento (campo de visión del agente) la observación cambia (los objetos se mueven en el entorno)

Exploración versus explotación (ε-Codicioso)

Hasta ahora, el agente tomó siempre la mejor acción posible; sin embargo, esto puede hacer que el agente se quede atascado rápidamente en un óptimo local. Un desafío clave en Q-Learning es el equilibrio entre exploración y explotación:

Explotar: elija la acción con el valor Q más alto conocido (codicioso). Explorar: elige una acción aleatoria para descubrir caminos potencialmente mejores.

ε-Política codiciosa

Dado un valor aleatorio r ∈ [0, 1] y el parámetro épsilon hay dos opciones:

Si r > épsilon, seleccione la mejor acción (explotar); de lo contrario, seleccione una acción aleatoria (explorar).

Épsilon en descomposición

Normalmente queremos explorar más desde el principio y explotar más más adelante. Esto se logra desintegrando épsilon con el tiempo:

épsilon = máx(épsilonMin, épsilon − épsilonDecay)

Después de suficientes pasos, la política del agente converge casi siempre para seleccionar la acción de máxima calidad.

épsilonMin privado = 0,05; épsilonDecay privado = 0,005; Private ActionEnum PickAction (estado QTile) { Acción ActionEnum = Random.Range (0f, 1f) > épsilon? Agent.Actions.Shuffle().OrderBy(state.GetQValue).Last() // explotar: Agent.RndAction(); // explorar epsilon = Mathf.Max(epsilonMin, epsilon – epsilonDecay); acción de devolución; }

El ecosistema RL más amplio

Q-Learning es un algoritmo dentro de una familia más amplia de métodos de aprendizaje por refuerzo (RL). Los algoritmos se pueden clasificar según varios ejes:

Espacio de estados: Discreto (por ejemplo, juegos de mesa) | Continuo (p. ej., juegos FPS) Espacio de acción: Discreto (p. ej., juegos de estrategia) | Continuo (p. ej., conducción) Tipo de política: Fuera de la política (Q-Learning: a' siempre está maximizado) | En póliza (SARSA: a' es seleccionado por la póliza actual del agente) Operador: Valor | Calidad | Ventaja A(s, a) = Q(s, a) − V(s)

Para obtener una lista completa de algoritmos RL, consulte la página de Wikipedia sobre aprendizaje por refuerzo. Otros métodos, como la clonación conductual, no se enumeran allí, pero también se utilizan en la práctica. Las soluciones del mundo real suelen utilizar variantes extendidas o combinaciones de las anteriores.

Q-Learning es un método de acción discreta y fuera de las políticas. Extenderlo a espacios continuos de estado/acción conduce a métodos como Deep Q-Networks (DQN), que reemplazan la tabla Q con una red neuronal.

En el ejemplo del mundo grid, la tabla Q tiene |S| × |A| = 40 × 4 = 160 entradas: perfectamente manejable. Pero para un juego como el ajedrez, el espacio de estados excede las 10⁴⁴ posiciones, lo que hace que una tabla explícita sea imposible de almacenar o llenar. En tales casos, se pueden utilizar redes neuronales para comprimir la información.

Una red neuronal comprime este vasto mapeo en un conjunto fijo de parámetros que se pueden aprender (pesos). En lugar de almacenar un valor por (s, a) par, la red toma el estado como entrada y genera valores Q para todas las acciones, generalizando entre estados similares que nunca antes había visto.

Aprendizaje práctico por refuerzo en juegos

El texto anterior debería proporcionar una introducción básica al concepto de agentes de aprendizaje por refuerzo. Por supuesto, en la práctica no es probable que implementes un algoritmo RL manualmente.

El motor Unity proporciona un paquete RL ML-Agents, que se puede utilizar, por ejemplo, para entrenar carreras de coches. En los laboratorios de vanguardia, la investigación sobre métodos de RL se está realizando a escala utilizando entornos de juego, por ejemplo, el juego del escondite:

[insertar]https://www.youtube.com/watch?v=kopoLzvh5jY[/embed]

Los modelos provenientes de estos laboratorios ya han vencido al mejor jugador del mundo en juegos de mesa complejos como Go, estrategias trepidantes como Starcraft y Dota, e incluso juegos que requieren comunicación a nivel humano, como Diplomacy.