Mejora del agente de Q-Learning basado en acciones al agregar recurrencia y recompensas

Recordatorio


Hola Habr! Les traigo a su atención otra traducción de mi nuevo artículo del medio .

La última vez ( primer artículo ) ( Habr ), creamos un agente utilizando la tecnología Q-Learning, que realiza transacciones en series de tiempo de intercambio simuladas y reales e intentamos verificar si esta área de tareas es adecuada para el aprendizaje reforzado.

Esta vez agregaremos una capa LSTM para tener en cuenta las dependencias de tiempo dentro de la trayectoria y hacer una recompensa en función de las presentaciones.

imagen

Permítame recordarle que para verificar el concepto, utilizamos los siguientes datos sintéticos:

imagen

Datos sintéticos: seno con ruido blanco.

La función seno fue el primer punto de partida. Dos curvas simulan el precio de compra y venta de un activo, donde el diferencial es el costo mínimo de transacción.

Sin embargo, esta vez queremos complicar esta tarea simple al extender la ruta de asignación de crédito:

imagen

Datos sintéticos: seno con ruido blanco.

La fase sinusal se duplicó.

Esto significa que las escasas recompensas que utilizamos deben extenderse en trayectorias más largas. Además, reducimos significativamente la probabilidad de recibir una recompensa positiva, ya que el agente tuvo que realizar una secuencia de acciones correctas 2 veces más para superar los costos de transacción. Ambos factores complican enormemente la tarea de RL incluso en condiciones tan simples como una onda sinusoidal.

Además, recordamos que utilizamos esta arquitectura de red neuronal:

imagen

Qué se agregó y por qué


Lstm


En primer lugar, queríamos darle al agente una mayor comprensión de la dinámica de los cambios dentro de la trayectoria. En pocas palabras, el agente debe comprender mejor su propio comportamiento: lo que hizo en este momento y durante algún tiempo en el pasado, y cómo se desarrolló la distribución de las acciones estatales, así como las recompensas recibidas. El uso de una capa de recurrencia puede resolver exactamente este problema. Bienvenido a la nueva arquitectura utilizada para lanzar un nuevo conjunto de experimentos:

imagen

Tenga en cuenta que he mejorado ligeramente la descripción. La única diferencia con el antiguo NN es la primera capa LSTM oculta en lugar de una capa completamente unida.

Tenga en cuenta que con LSTM en el trabajo, debemos cambiar la selección de ejemplos de reproducción de la experiencia para el entrenamiento: ahora necesitamos secuencias de transición en lugar de ejemplos separados. Así es como funciona (este es uno de los algoritmos). Utilizamos puntos de muestreo antes:

imagen

El esquema ficticio del búfer de reproducción.

Utilizamos este esquema con LSTM:

imagen

Ahora se seleccionan las secuencias (cuya longitud especificamos empíricamente).

Como antes, y ahora la muestra está regulada por un algoritmo de prioridad basado en errores de aprendizaje temporal-temporal.

El nivel de recurrencia LSTM permite la difusión directa de información de series de tiempo para interceptar una señal adicional oculta en retrasos pasados. La serie temporal con nosotros es un tensor bidimensional con tamaño: la longitud de la secuencia en la representación de nuestra acción estatal.

Presentaciones


La ingeniería galardonada, Potential Based Reward Shaping (PBRS), basada en el potencial, es una herramienta poderosa que le permite aumentar la velocidad, la estabilidad y no violar la optimización del proceso de búsqueda de políticas para resolver nuestro entorno. Recomiendo leer al menos este documento original sobre el tema:

people.eecs.berkeley.edu/~russell/papers/ml99-shaping.ps

El potencial determina qué tan bien nuestro estado actual es relativo al estado objetivo en el que queremos ingresar. Una vista esquemática de cómo funciona esto:

imagen

Hay opciones y dificultades que podría comprender después de la prueba y error, y omitimos estos detalles, dejándolo con su tarea.

Vale la pena mencionar una cosa más, que es que PBRS puede justificarse mediante presentaciones, que son una forma de conocimiento experto (o simulado) sobre el comportamiento casi óptimo del agente en el medio ambiente. Hay una manera de encontrar tales presentaciones para nuestra tarea utilizando esquemas de optimización. Omitimos los detalles de la búsqueda.

La recompensa potencial toma la siguiente forma (ecuación 1):

r '= r + gamma * F (s') - F (s)

donde F es el potencial del estado y r es la recompensa inicial, gamma es el factor de descuento (0: 1).

Con estos pensamientos, pasamos a la codificación.

Implementación en R
Aquí está el código de red neuronal basado en la API de Keras:

Código
# configure critic NN — — — — — — library('keras') library('R6') state_names_length <- 12 # just for example lstm_seq_length <- 4 learning_rate <- 1e-3 a_CustomLayer <- R6::R6Class( “CustomLayer” , inherit = KerasLayer , public = list( call = function(x, mask = NULL) { x — k_mean(x, axis = 2, keepdims = T) } ) ) a_normalize_layer <- function(object) { create_layer(a_CustomLayer, object, list(name = 'a_normalize_layer')) } v_CustomLayer <- R6::R6Class( “CustomLayer” , inherit = KerasLayer , public = list( call = function(x, mask = NULL) { k_concatenate(list(x, x, x), axis = 2) } , compute_output_shape = function(input_shape) { output_shape = input_shape output_shape[[2]] <- input_shape[[2]] * 3L output_shape } ) ) v_normalize_layer <- function(object) { create_layer(v_CustomLayer, object, list(name = 'v_normalize_layer')) } noise_CustomLayer <- R6::R6Class( “CustomLayer” , inherit = KerasLayer , lock_objects = FALSE , public = list( initialize = function(output_dim) { self$output_dim <- output_dim } , build = function(input_shape) { self$input_dim <- input_shape[[2]] sqr_inputs <- self$input_dim ** (1/2) self$sigma_initializer <- initializer_constant(.5 / sqr_inputs) self$mu_initializer <- initializer_random_uniform(minval = (-1 / sqr_inputs), maxval = (1 / sqr_inputs)) self$mu_weight <- self$add_weight( name = 'mu_weight', shape = list(self$input_dim, self$output_dim), initializer = self$mu_initializer, trainable = TRUE ) self$sigma_weight <- self$add_weight( name = 'sigma_weight', shape = list(self$input_dim, self$output_dim), initializer = self$sigma_initializer, trainable = TRUE ) self$mu_bias <- self$add_weight( name = 'mu_bias', shape = list(self$output_dim), initializer = self$mu_initializer, trainable = TRUE ) self$sigma_bias <- self$add_weight( name = 'sigma_bias', shape = list(self$output_dim), initializer = self$sigma_initializer, trainable = TRUE ) } , call = function(x, mask = NULL) { #sample from noise distribution e_i = k_random_normal(shape = list(self$input_dim, self$output_dim)) e_j = k_random_normal(shape = list(self$output_dim)) #We use the factorized Gaussian noise variant from Section 3 of Fortunato et al. eW = k_sign(e_i) * (k_sqrt(k_abs(e_i))) * k_sign(e_j) * (k_sqrt(k_abs(e_j))) eB = k_sign(e_j) * (k_abs(e_j) ** (1/2)) #See section 3 of Fortunato et al. noise_injected_weights = k_dot(x, self$mu_weight + (self$sigma_weight * eW)) noise_injected_bias = self$mu_bias + (self$sigma_bias * eB) output = k_bias_add(noise_injected_weights, noise_injected_bias) output } , compute_output_shape = function(input_shape) { output_shape <- input_shape output_shape[[2]] <- self$output_dim output_shape } ) ) noise_add_layer <- function(object, output_dim) { create_layer( noise_CustomLayer , object , list( name = 'noise_add_layer' , output_dim = as.integer(output_dim) , trainable = T ) ) } critic_input <- layer_input( shape = list(NULL, as.integer(state_names_length)) , name = 'critic_input' ) common_lstm_layer <- layer_lstm( units = 20 , activation = “tanh” , recurrent_activation = “hard_sigmoid” , use_bias = T , return_sequences = F , stateful = F , name = 'lstm1' ) critic_layer_dense_v_1 <- layer_dense( units = 10 , activation = “tanh” ) critic_layer_dense_v_2 <- layer_dense( units = 5 , activation = “tanh” ) critic_layer_dense_v_3 <- layer_dense( units = 1 , name = 'critic_layer_dense_v_3' ) critic_layer_dense_a_1 <- layer_dense( units = 10 , activation = “tanh” ) # critic_layer_dense_a_2 <- layer_dense( # units = 5 # , activation = “tanh” # ) critic_layer_dense_a_3 <- layer_dense( units = length(actions) , name = 'critic_layer_dense_a_3' ) critic_model_v <- critic_input %>% common_lstm_layer %>% critic_layer_dense_v_1 %>% critic_layer_dense_v_2 %>% critic_layer_dense_v_3 %>% v_normalize_layer critic_model_a <- critic_input %>% common_lstm_layer %>% critic_layer_dense_a_1 %>% #critic_layer_dense_a_2 %>% noise_add_layer(output_dim = 5) %>% critic_layer_dense_a_3 %>% a_normalize_layer critic_output <- layer_add( list( critic_model_v , critic_model_a ) , name = 'critic_output' ) critic_model_1 <- keras_model( inputs = critic_input , outputs = critic_output ) critic_optimizer = optimizer_adam(lr = learning_rate) keras::compile( critic_model_1 , optimizer = critic_optimizer , loss = 'mse' , metrics = 'mse' ) train.x <- array_reshape(rnorm(10 * lstm_seq_length * state_names_length) , dim = c(10, lstm_seq_length, state_names_length) , order = 'C') predict(critic_model_1, train.x) layer_name <- 'noise_add_layer' intermediate_layer_model <- keras_model(inputs = critic_model_1$input, outputs = get_layer(critic_model_1, layer_name)$output) predict(intermediate_layer_model, train.x)[1,] critic_model_2 <- critic_model_1 


Depuración de su decisión sobre su conciencia ...

Resultados y comparación


Vamos a sumergirnos en los resultados finales. Nota: todos los resultados son estimaciones puntuales y pueden diferir en múltiples ejecuciones con diferentes sid de semillas al azar.

La comparación incluye:

  • versión anterior sin LSTM y presentaciones
  • LSTM simple de 2 elementos
  • LSTM de 4 elementos
  • LSTM de 4 celdas con recompensas PBRS generadas

imagen

El rendimiento promedio por episodio promedió más de 1000 episodios.

imagen

El episodio total vuelve.

Gráficos para el agente más exitoso:

imagen

imagen

Rendimiento del agente.

Bueno, es bastante obvio que el agente en forma de PBRS converge de manera tan rápida y estable en comparación con intentos anteriores que puede aceptarse como un resultado significativo. La velocidad es aproximadamente 4-5 veces mayor que sin presentaciones. La estabilidad es maravillosa.

Cuando se trata de usar LSTM, 4 células se desempeñaron mejor que 2 células. Un LSTM de 2 celdas funcionó mejor que una versión no LSTM (sin embargo, tal vez esto sea una ilusión de un solo experimento).

Palabras finales


Hemos visto que las recompensas de recurrencia y creación de capacidad ayudan. Me gustó especialmente cómo el PBRS se desempeñó tan bien.

No le crea a nadie que me haga decir que es fácil crear un agente de RL que converja bien, ya que eso es mentira. Cada nuevo componente agregado al sistema lo hace potencialmente menos estable y requiere mucha configuración y depuración.

Sin embargo, existe evidencia clara de que la solución al problema puede mejorarse simplemente mejorando los métodos utilizados (los datos permanecieron intactos). Es un hecho que para cualquier tarea un cierto rango de parámetros funciona mejor que otros. Con esto en mente, te embarcas en un camino de aprendizaje exitoso.

Gracias

Source: https://habr.com/ru/post/es436628/


All Articles