Schooling Flappy Bird: un tutorial de aprendizaje por refuerzo
Publicado: 2022-03-11En la programación clásica, los programadores crean explícitamente las instrucciones del software y no se aprende nada de los datos. Por el contrario, el aprendizaje automático es un campo de la informática que utiliza métodos estadísticos para permitir que las computadoras aprendan y extraigan conocimiento de los datos sin ser programados explícitamente.
En este tutorial de aprendizaje por refuerzo, mostraré cómo podemos usar PyTorch para enseñar a una red neuronal de aprendizaje por refuerzo cómo jugar Flappy Bird. Pero primero, necesitaremos cubrir una serie de bloques de construcción.
Los algoritmos de aprendizaje automático se pueden dividir aproximadamente en dos partes: algoritmos de aprendizaje tradicionales y algoritmos de aprendizaje profundo. Los algoritmos de aprendizaje tradicionales suelen tener muchos menos parámetros de aprendizaje que los algoritmos de aprendizaje profundo y tienen mucha menos capacidad de aprendizaje.
Además, los algoritmos de aprendizaje tradicionales no pueden realizar la extracción de características : los especialistas en inteligencia artificial deben encontrar una buena representación de datos que luego se envíe al algoritmo de aprendizaje. Los ejemplos de técnicas tradicionales de aprendizaje automático incluyen SVM, bosque aleatorio, árbol de decisión y $k$-means, mientras que el algoritmo central en el aprendizaje profundo es la red neuronal profunda .
La entrada a una red neuronal profunda puede ser imágenes sin procesar, y un especialista en inteligencia artificial no necesita encontrar ninguna representación de datos: la red neuronal encuentra la mejor representación durante el proceso de entrenamiento.
Se conocen muchas técnicas de aprendizaje profundo desde hace mucho tiempo, pero los avances recientes en hardware impulsaron rápidamente la investigación y el desarrollo del aprendizaje profundo. Nvidia es responsable de la expansión del campo porque sus GPU han permitido experimentos rápidos de aprendizaje profundo.
Parámetros e hiperparámetros aprendibles
Los algoritmos de aprendizaje automático consisten en parámetros aprendibles que se ajustan en el proceso de entrenamiento y parámetros no aprendibles que se establecen antes del proceso de entrenamiento. Los parámetros establecidos antes del aprendizaje se denominan hiperparámetros .
La búsqueda en cuadrícula es un método común para encontrar los hiperparámetros óptimos. Es un método de fuerza bruta: significa probar todas las combinaciones posibles de hiperparámetros en un rango definido y elegir la combinación que maximiza una métrica predefinida.
Algoritmos de aprendizaje supervisado, no supervisado y de refuerzo
Una forma de clasificar los algoritmos de aprendizaje es trazar una línea entre los algoritmos supervisados y no supervisados. (Pero eso no es necesariamente tan sencillo: el aprendizaje por refuerzo se encuentra en algún lugar entre estos dos tipos).
Cuando hablamos de aprendizaje supervisado, nos fijamos en $ (x_i, y_i) $ pares. $ x_i $ es la entrada del algoritmo y $ y_i $ es la salida. Nuestra tarea es encontrar una función que haga el mapeo correcto de $ x_i $ a $ y_i $.
Para ajustar los parámetros que se pueden aprender de modo que definan una función que asigne $ x_i $ a $ y_i $, es necesario definir una función de pérdida y un optimizador. Un optimizador minimiza la función de pérdida. Un ejemplo de una función de pérdida es el error cuadrático medio (MSE):
\[MSE = \sum_{i=1}^{n} (y_i - \widehat{y_i} )^2\]Aquí, $ y_i $ es una etiqueta de verdad fundamental y $ \widehat{y_i} $ es una etiqueta predicha. Un optimizador que es muy popular en el aprendizaje profundo es el descenso de gradiente estocástico . Hay muchas variaciones que intentan mejorar el método de descenso de gradiente estocástico: Adam, Adadelta, Adagrad, etc.
Los algoritmos no supervisados intentan encontrar una estructura en los datos sin que se les proporcionen etiquetas explícitamente. $k$-means es uno de los ejemplos de algoritmos no supervisados que intenta encontrar grupos óptimos en los datos. A continuación se muestra una imagen con 300 puntos de datos. $k$: significa que los algoritmos encontraron la estructura en los datos y asignaron una etiqueta de grupo a cada punto de datos. Cada grupo tiene su propio color.
El aprendizaje por refuerzo utiliza recompensas: etiquetas escasas y con retraso en el tiempo. Un agente toma acción, lo que cambia el entorno, del cual puede obtener una nueva observación y recompensa. Una observación es el estímulo que un agente percibe del entorno. Puede ser lo que el agente ve, oye, huele, etc.
Se otorga una recompensa al agente cuando realiza una acción. Le dice al agente lo buena que es la acción. Al percibir observaciones y recompensas, un agente aprende a comportarse de manera óptima en el entorno. Voy a entrar en esto con más detalle a continuación.
Aprendizaje por refuerzo activo, pasivo e inverso
Hay algunos enfoques diferentes para esta técnica. En primer lugar, está el aprendizaje por refuerzo activo, que estamos usando aquí. Por el contrario, existe el aprendizaje por refuerzo pasivo, en el que las recompensas son simplemente otro tipo de observación y, en cambio, las decisiones se toman de acuerdo con una política fija.
Finalmente, el aprendizaje por refuerzo inverso trata de reconstruir una función de recompensa dada la historia de las acciones y sus recompensas en varios estados.
Generalización, sobreajuste y subajuste
Cualquier instancia fija de parámetros e hiperparámetros se denomina modelo. Los experimentos de aprendizaje automático generalmente constan de dos partes: entrenamiento y prueba.
Durante el proceso de entrenamiento, los parámetros que se pueden aprender se ajustan utilizando datos de entrenamiento. En el proceso de prueba, los parámetros que se pueden aprender se congelan y la tarea es verificar qué tan bien el modelo hace predicciones sobre datos no vistos anteriormente. La generalización es la capacidad de una máquina de aprendizaje para desempeñarse con precisión en un ejemplo o tarea nueva e invisible después de haber experimentado un conjunto de datos de aprendizaje.
Si un modelo es demasiado simple con respecto a los datos, no podrá ajustarse a los datos de entrenamiento y tendrá un desempeño deficiente tanto en el conjunto de datos de entrenamiento como en el conjunto de datos de prueba. En ese caso, decimos que el modelo no se ajusta bien .
Si un modelo de aprendizaje automático funciona bien en un conjunto de datos de entrenamiento, pero no en un conjunto de datos de prueba, decimos que se está sobreajustando . El sobreajuste es la situación en la que un modelo es demasiado complejo con respecto a los datos. Puede ajustarse perfectamente a los datos de entrenamiento, pero se adapta tanto al conjunto de datos de entrenamiento que funciona mal en los datos de prueba, es decir, simplemente no generaliza.
A continuación, se muestra una imagen que muestra el ajuste insuficiente y el ajuste excesivo en comparación con una situación equilibrada entre los datos generales y la función de predicción.
Escalabilidad
Los datos son cruciales en la construcción de modelos de aprendizaje automático. Por lo general, los algoritmos de aprendizaje tradicionales no requieren demasiados datos. Pero debido a su capacidad limitada, el rendimiento también es limitado. A continuación se muestra un gráfico que muestra cómo los métodos de aprendizaje profundo escalan bien en comparación con los algoritmos tradicionales de aprendizaje automático.
Redes neuronales
Las redes neuronales constan de varias capas. La siguiente imagen muestra una red neuronal simple con cuatro capas. La primera capa es la capa de entrada y la última capa es la capa de salida. Las dos capas entre las capas de entrada y salida son capas ocultas.
Si una red neuronal tiene más de una capa oculta, la llamamos red neuronal profunda. Se le da el conjunto de entrada $X$ a la red neuronal y se obtiene la salida $y$. El aprendizaje se realiza mediante un algoritmo de retropropagación que combina una función de pérdida y un optimizador.
La retropropagación consta de dos partes: un pase hacia adelante y un pase hacia atrás. En el pase hacia adelante, los datos de entrada se colocan en la entrada de la red neuronal y se obtiene la salida. Se calcula la pérdida entre la realidad básica y la predicción y, luego, en el paso hacia atrás, los parámetros de las redes neuronales se ajustan con respecto a la pérdida.
Red neuronal convolucional
Una variación de la red neuronal es la red neuronal convolucional . Se utiliza principalmente para tareas de visión artificial.
La capa más importante en las redes neuronales convolucionales es la capa convolucional (de ahí el nombre). Sus parámetros están hechos de filtros aprendibles, también llamados núcleos. Las capas convolucionales aplican una operación de convolución a la entrada, pasando el resultado a la siguiente capa. La operación de convolución reduce la cantidad de parámetros que se pueden aprender, funciona como una especie de heurística y facilita el entrenamiento de la red neuronal.
A continuación se muestra cómo funciona un kernel convolucional en una capa convolucional. El kernel se aplica a la imagen y se obtiene una característica convolucionada.
Las capas ReLU se utilizan para introducir no linealidades en la red neuronal. Las no linealidades son importantes porque con ellas podemos modelar todo tipo de funciones, no solo las lineales, haciendo de las redes neuronales un aproximador universal de funciones. Esto hace que una función ReLU se defina así:
\[ReLU = \max(0, x)\]ReLU es uno de los ejemplos de las llamadas funciones de activación utilizadas para introducir no linealidades en las redes neuronales. Los ejemplos de otras funciones de activación incluyen funciones sigmoideas e hipertangentes. ReLU es la función de activación más popular porque se ha demostrado que hace que la red neuronal se entrene de manera más eficiente en comparación con otras funciones de activación.
A continuación se muestra un gráfico de una función ReLU.
Como puede ver, esta función ReLU simplemente cambia los valores negativos a ceros. Esto ayuda a prevenir el problema del gradiente de fuga. Si un gradiente desaparece, no tendrá un gran impacto en el ajuste del peso de la red neuronal.
Una red neuronal convolucional consta de varias capas: capas convolucionales, capas ReLU y capas totalmente conectadas. Las capas completamente conectadas conectan cada neurona en una capa con cada neurona en otra capa, como se ve con las dos capas ocultas en la imagen al comienzo de esta sección. La última capa completamente conectada asigna salidas de la capa anterior a, en este caso, valores number_of_actions
.
Aplicaciones
El aprendizaje profundo tiene éxito y supera a los algoritmos clásicos de aprendizaje automático en varios subcampos de aprendizaje automático, incluida la visión artificial, el reconocimiento de voz y el aprendizaje por refuerzo. Estos campos de aprendizaje profundo se aplican en varios dominios del mundo real: finanzas, medicina, entretenimiento, etc.
Aprendizaje reforzado
El aprendizaje por refuerzo se basa en un agente . Un agente realiza acciones en un entorno y obtiene observaciones y recompensas de él. Un agente debe estar capacitado para maximizar la recompensa acumulada. Como se señaló en la introducción, con los algoritmos clásicos de aprendizaje automático, los ingenieros de aprendizaje automático deben realizar la extracción de características, es decir, crear buenas características que representen bien el entorno y que se introduzcan en un algoritmo de aprendizaje automático.
Mediante el aprendizaje profundo, es posible crear un sistema de extremo a extremo que toma datos de entrada de gran dimensión (por ejemplo, video) y, a partir de ellos, aprende la estrategia óptima para que un agente tome buenas medidas.
En 2013, la startup londinense de inteligencia artificial DeepMind creó un gran avance en el aprendizaje para controlar agentes directamente desde entradas sensoriales de alta dimensión. Publicaron un artículo, Playing Atari with Deep Reinforcement Learning , en el que mostraban cómo enseñaron a una red neuronal artificial a jugar juegos de Atari con solo mirar la pantalla. Fueron adquiridos por Google y luego publicaron un nuevo artículo en Nature con algunas mejoras: control a nivel humano a través del aprendizaje de refuerzo profundo .
A diferencia de otros paradigmas de aprendizaje automático, el aprendizaje por refuerzo no tiene un supervisor, solo una señal de recompensa. La retroalimentación es retardada: No es instantánea como en los algoritmos de aprendizaje supervisado. Los datos son secuenciales y las acciones de un agente afectan los datos posteriores que recibe.
Ahora bien, un agente se sitúa en su entorno, que se encuentra en un estado determinado. Para describir esto con más detalle, utilizamos un proceso de decisión de Markov, que es una forma formal de modelar este entorno de aprendizaje por refuerzo. Se compone de un conjunto de estados, un conjunto de acciones posibles y reglas (p. ej., probabilidades) para pasar de un estado a otro.
El agente es capaz de realizar acciones, transformando el entorno. A la recompensa la llamamos $R_t$. Es una señal de retroalimentación escalar que indica qué tan bien lo está haciendo el agente en el paso $t$.
Para un buen desempeño a largo plazo, no solo se deben tener en cuenta las recompensas inmediatas sino también las recompensas futuras. La recompensa total por un episodio del paso de tiempo $t$ es $ R_t = r_t + r_{t+1} + r_{t+2} + \ldots + r_n $. El futuro es incierto y cuanto más avanzamos en el futuro, más pueden divergir las predicciones futuras. Por eso, se utiliza una recompensa futura con descuento: $ R_t = r_t +\gamma r_{t+1} + \gamma^2r_{t+2} + \ldots + \gamma^{nt}r_n = r_t + \gamma R_{t+1} $. Un agente debe elegir la acción que maximiza la recompensa futura descontada.
Q-aprendizaje profundo
La función $ Q(s, a) $ representa la recompensa futura máxima descontada cuando se realiza la acción $ a $
La estimación de una recompensa futura viene dada por la ecuación de Bellman: $ Q(s, a) = r + \gamma \max_{a'}Q(s', a') $ . En otras palabras, la recompensa futura máxima otorgada a un estado $ s $ y una acción $ a $ es la recompensa inmediata más la recompensa futura máxima para el siguiente estado.
La aproximación de los valores Q utilizando funciones no lineales (redes neuronales) no es muy estable. Por eso, la repetición de la experiencia se utiliza para la estabilidad. La experiencia durante los episodios de una sesión de entrenamiento se almacena en una memoria de reproducción. Se utilizan minilotes aleatorios de la memoria de reproducción en lugar de utilizar la transición más reciente. Esto rompe la similitud de las muestras de entrenamiento posteriores que, de lo contrario, conducirían a la red neuronal a un mínimo local.

Hay dos aspectos más importantes que mencionar sobre el Q-learning profundo: exploración y explotación. Con la explotación se toma la mejor decisión dada la información actual. La exploración reúne más información.
Cuando el algoritmo realiza una acción propuesta por la red neuronal, está haciendo explotación: explota el conocimiento aprendido de la red neuronal. Por el contrario, un algoritmo puede realizar una acción aleatoria, explorando nuevas posibilidades e introduciendo nuevos conocimientos potenciales a la red neuronal.
A continuación se muestra el "algoritmo de Q-learning profundo con repetición de experiencia" del artículo de DeepMind Playing Atari with Deep Reinforcement Learning.
DeepMind se refiere a las redes convolucionales entrenadas con su enfoque como Deep Q-networks (DQN).
Ejemplo de Q-learning profundo usando Flappy Bird
Flappy Bird fue un popular juego móvil desarrollado originalmente por el programador y artista de videojuegos vietnamita Dong Nguyen. En él, el jugador controla un pájaro e intenta volar entre tuberías verdes sin golpearlas.
A continuación se muestra una captura de pantalla de un clon de Flappy Bird codificado con PyGame:
Desde entonces, el clon se bifurcó y modificó: se eliminaron el fondo, los sonidos y los diferentes estilos de pájaros y tuberías, y se ajustó el código para que pueda usarse fácilmente con marcos de aprendizaje de refuerzo simples. El motor de juego modificado se toma de este proyecto de TensorFlow:
Pero en lugar de usar TensorFlow, construí un marco de aprendizaje de refuerzo profundo usando PyTorch. PyTorch es un marco de aprendizaje profundo para una experimentación rápida y flexible. Proporciona tensores y redes neuronales dinámicas en Python con una fuerte aceleración de GPU.
La arquitectura de la red neuronal es la misma que DeepMind usó en el documento Control a nivel humano a través del aprendizaje de refuerzo profundo .
Capa | Aporte | Tamaño del filtro | Paso | Número de filtros | Activación | Producción |
---|---|---|---|---|---|---|
conv1 | 84x84x4 | 8x8 | 4 | 32 | ReLU | 20x20x32 |
conv2 | 20x20x32 | 4x4 | 2 | 64 | ReLU | 9x9x64 |
conv3 | 9x9x64 | 3x3 | 1 | 64 | ReLU | 7x7x64 |
fc4 | 7x7x64 | 512 | ReLU | 512 | ||
fc5 | 512 | 2 | Lineal | 2 |
Hay tres capas convolucionales y dos capas completamente conectadas. Cada capa usa activación ReLU, excepto la última, que usa activación lineal. La red neuronal genera dos valores que representan las únicas acciones posibles del jugador: "Volar" y "no hacer nada".
La entrada consta de cuatro imágenes consecutivas en blanco y negro de 84x84. A continuación se muestra un ejemplo de cuatro imágenes que se envían a la red neuronal.
Notarás que las imágenes están giradas. Esto se debe a que la salida del motor del juego del clon se rota. Pero si la red neuronal se enseña y luego se prueba usando tales imágenes, no afectará su rendimiento.
También puede notar que la imagen está recortada, por lo que se omite el piso, ya que es irrelevante para esta tarea. Todos los píxeles que representan tuberías y el pájaro son blancos y todos los píxeles que representan el fondo son negros.
Esto es parte del código que define la red neuronal. Los pesos de las redes neuronales se inicializan para seguir la distribución uniforme $\mathcal{U}(-0.01, 0.01)$. La parte de sesgo de los parámetros de las redes neuronales se establece en 0,01. Se probaron varias inicializaciones diferentes (Xavier uniforme, Xavier normal, Kaiming uniforme, Kaiming normal, uniforme y normal), pero la inicialización anterior hizo que la red neuronal convergiera y entrenara más rápido. El tamaño de la red neuronal es de 6,8 MB.
class NeuralNetwork(nn.Module): def __init__(self): super(NeuralNetwork, self).__init__() self.number_of_actions = 2 self.gamma = 0.99 self.final_epsilon = 0.0001 self.initial_epsilon = 0.1 self.number_of_iterations = 2000000 self.replay_memory_size = 10000 self.minibatch_size = 32 self.conv1 = nn.Conv2d(4, 32, 8, 4) self.relu1 = nn.ReLU(inplace=True) self.conv2 = nn.Conv2d(32, 64, 4, 2) self.relu2 = nn.ReLU(inplace=True) self.conv3 = nn.Conv2d(64, 64, 3, 1) self.relu3 = nn.ReLU(inplace=True) self.fc4 = nn.Linear(3136, 512) self.relu4 = nn.ReLU(inplace=True) self.fc5 = nn.Linear(512, self.number_of_actions) def forward(self, x): out = self.conv1(x) out = self.relu1(out) out = self.conv2(out) out = self.relu2(out) out = self.conv3(out) out = self.relu3(out) out = out.view(out.size()[0], -1) out = self.fc4(out) out = self.relu4(out) out = self.fc5(out) return out
En el constructor, notará que hay hiperparámetros definidos. La optimización de hiperparámetros no se realiza para el propósito de esta publicación de blog. En cambio, los hiperparámetros se utilizan principalmente en los artículos de DeepMind. Aquí, algunos de los hiperparámetros están escalados para ser más bajos que en el artículo de DeepMind, porque Flappy Bird es menos complejo que los juegos de Atari que usaron para ajustar.
Además, epsilon se cambia para que sea mucho más razonable para este juego. DeepMind usa un épsilon de uno, pero aquí usamos 0.1. Esto se debe a que los épsilons más altos obligan al ave a aletear mucho, lo que empuja al ave hacia el borde superior de la pantalla, lo que siempre provoca que el ave se estrelle contra una tubería.
El código de aprendizaje por refuerzo tiene dos modos: entrenar y probar. Durante la fase de prueba, podemos ver qué tan bien el algoritmo de aprendizaje por refuerzo ha aprendido a jugar el juego. Pero primero, la red neuronal necesita ser entrenada. Necesitamos definir la función de pérdida a minimizar y los optimizadores que minimizarán la función de pérdida. Usaremos el método de optimización de Adam y el error cuadrático medio para la función de pérdida:
optimizer = optim.Adam(model.parameters(), lr=1e-6) criterion = nn.MSELoss()
El juego debe ser instanciado:
game_state = GameState()
La memoria de reproducción se define como una lista de Python:
replay_memory = []
Ahora necesitamos inicializar el primer estado. Una acción es un tensor bidimensional:
- [1, 0] representa "no hacer nada"
- [0, 1] representa "volar hacia arriba"
El método frame_step
nos brinda la siguiente pantalla, recompensa e información sobre si el próximo estado es terminal. La recompensa es 0.1
por cada movimiento de pájaro sin morir cuando no está pasando por una tubería, 1
si el pájaro pasa con éxito por una tubería y -1
si el pájaro se estrella.
La función resize_and_bgr2gray
recorta el suelo, cambia el tamaño de la pantalla a una imagen de 84x84 y cambia el espacio de color de BGR a blanco y negro. La función image_to_tensor
convierte la imagen en un tensor PyTorch y la coloca en la memoria GPU si CUDA está disponible. Finalmente, las últimas cuatro pantallas secuenciales se concatenan juntas y están listas para ser enviadas a la red neuronal.
action = torch.zeros([model.number_of_actions], dtype=torch.float32) action[0] = 1 image_data, reward, terminal = game_state.frame_step(action) image_data = resize_and_bgr2gray(image_data) image_data = image_to_tensor(image_data) state = torch.cat((image_data, image_data, image_data, image_data)).unsqueeze(0)
El épsilon inicial se establece usando esta línea de código:
epsilon = model.initial_epsilon
El bucle infinito principal sigue. Los comentarios están escritos en el código y puede comparar el código con el algoritmo Deep Q-learning con Experience Replay escrito anteriormente.
El algoritmo toma muestras de minilotes de la memoria de reproducción y actualiza los parámetros de la red neuronal. Las acciones se ejecutan mediante la exploración codiciosa de epsilon . Epsilon se está recociendo con el tiempo. La función de pérdida que se minimiza es $ L = \frac{1}{2}\left[\max_{a'}Q(s', a') - Q(s, a)\right]^2 $ . $ Q(s, a) $ es el valor de verdad fundamental calculado usando la ecuación de Bellman y $ \max_{a'}Q(s', a') $ se obtiene de la red neuronal. La red neuronal proporciona dos valores Q para las dos acciones posibles y el algoritmo realiza la acción con el valor Q más alto.
while iteration < model.number_of_iterations: # get output from the neural network output = model(state)[0] # initialize action action = torch.zeros([model.number_of_actions], dtype=torch.float32) if torch.cuda.is_available(): # put on GPU if CUDA is available action = action.cuda() # epsilon greedy exploration random_action = random.random() <= epsilon if random_action: print("Performed random action!") action_index = [torch.randint(model.number_of_actions, torch.Size([]), dtype=torch.int) if random_action else torch.argmax(output)][0] if torch.cuda.is_available(): # put on GPU if CUDA is available action_index = action_index.cuda() action[action_index] = 1 # get next state and reward image_data_1, reward, terminal = game_state.frame_step(action) image_data_1 = resize_and_bgr2gray(image_data_1) image_data_1 = image_to_tensor(image_data_1) state_1 = torch.cat((state.squeeze(0)[1:, :, :], image_data_1)).unsqueeze(0) action = action.unsqueeze(0) reward = torch.from_numpy(np.array([reward], dtype=np.float32)).unsqueeze(0) # save transition to replay memory replay_memory.append((state, action, reward, state_1, terminal)) # if replay memory is full, remove the oldest transition if len(replay_memory) > model.replay_memory_size: replay_memory.pop(0) # epsilon annealing epsilon = epsilon_decrements[iteration] # sample random minibatch minibatch = random.sample(replay_memory, min(len(replay_memory), model.minibatch_size)) # unpack minibatch state_batch = torch.cat(tuple(d[0] for d in minibatch)) action_batch = torch.cat(tuple(d[1] for d in minibatch)) reward_batch = torch.cat(tuple(d[2] for d in minibatch)) state_1_batch = torch.cat(tuple(d[3] for d in minibatch)) if torch.cuda.is_available(): # put on GPU if CUDA is available state_batch = state_batch.cuda() action_batch = action_batch.cuda() reward_batch = reward_batch.cuda() state_1_batch = state_1_batch.cuda() # get output for the next state output_1_batch = model(state_1_batch) # set y_j to r_j for terminal state, otherwise to r_j + gamma*max(Q) y_batch = torch.cat(tuple(reward_batch[i] if minibatch[i][4] else reward_batch[i] + model.gamma * torch.max(output_1_batch[i]) for i in range(len(minibatch)))) # extract Q-value q_value = torch.sum(model(state_batch) * action_batch, dim=1) # PyTorch accumulates gradients by default, so they need to be reset in each pass optimizer.zero_grad() # returns a new Tensor, detached from the current graph, the result will never require gradient y_batch = y_batch.detach() # calculate loss loss = criterion(q_value, y_batch) # do backward pass loss.backward() optimizer.step() # set state to be state_1 state = state_1
Ahora que todas las piezas están en su lugar, aquí hay una descripción general de alto nivel del flujo de datos usando nuestra red neuronal:
Aquí hay una secuencia corta con una red neuronal entrenada.
La red neuronal que se muestra arriba se entrenó con una GPU Nvidia GTX 1080 de gama alta durante unas horas; utilizando una solución basada en CPU en su lugar, esta tarea en particular llevaría varios días. Los FPS del motor del juego se configuraron en un número muy alto durante el entrenamiento: 999…999, en otras palabras, tantos fotogramas por segundo como sea posible. En la fase de prueba, el FPS se estableció en 30.
A continuación se muestra un gráfico que muestra cómo cambió el valor Q máximo durante las iteraciones. Se muestra cada iteración número 10.000. Un pico hacia abajo significa que para un cuadro en particular (una iteración es un cuadro) la red neuronal predice que el ave obtendrá una recompensa muy baja en el futuro, es decir, se bloqueará muy pronto.
El código completo y el modelo preentrenado están disponibles aquí.
Aprendizaje por refuerzo profundo: 2D, 3D e incluso en la vida real
En este tutorial de aprendizaje por refuerzo de PyTorch, mostré cómo una computadora puede aprender a jugar Flappy Bird sin ningún conocimiento previo sobre el juego, utilizando solo un enfoque de prueba y error como lo haría un ser humano cuando se encuentra con el juego por primera vez.
Es interesante que el algoritmo se pueda implementar en unas pocas líneas de código utilizando el marco PyTorch. El artículo en el que se basa el método de este blog es relativamente antiguo y hay disponibles muchos artículos nuevos con varias modificaciones que permiten una convergencia más rápida. Desde entonces, el aprendizaje por refuerzo profundo se ha utilizado para jugar juegos en 3D y en sistemas robóticos del mundo real.
Empresas como DeepMind, Maluuba y Vicarious están trabajando intensamente en el aprendizaje por refuerzo profundo. Esta tecnología se utilizó en AlphaGo, que venció a Lee Sedol, uno de los mejores jugadores de Go del mundo. En ese momento, se consideró que tomaría al menos diez años hasta que las máquinas pudieran derrotar a los mejores jugadores de Go.
La avalancha de interés e inversiones en el aprendizaje de refuerzo profundo (y en la inteligencia artificial en general) podría incluso conducir a una posible inteligencia artificial general (AGI): inteligencia a nivel humano (o incluso más) que se puede expresar en forma de un algoritmo y simulado en computadoras. Pero AGI tendrá que ser tema de otro artículo.
Referencias:
- Desmitificando el Aprendizaje por Refuerzo Profundo
- Aprendizaje por refuerzo profundo para Flappy Bird
- "Red neuronal convolucional" en Wikipedia
- "Aprendizaje por refuerzo" en Wikipedia
- "Proceso de decisión de Markov" en Wikipedia
- Curso de University College London sobre RL