¿Es posible capacitar a un agente para negociar en el mercado de valores con refuerzos? Implementación de lenguaje R

Creemos un prototipo de agente de aprendizaje de refuerzo (RL) que dominará la habilidad comercial.

Dado que la implementación del prototipo funciona en el lenguaje R, animo a los usuarios y programadores de R a que se acerquen a las ideas presentadas en este artículo.

Esta es una traducción de mi artículo en inglés: ¿Puede el refuerzo de aprendizaje negociar acciones? Implementación en R.

Quiero advertir a los cazadores de códigos que en esta nota solo hay un código para una red neuronal adaptada para R.

Si no me distinguí en buen ruso, señale los errores (el texto fue preparado con la ayuda de un traductor automático).

imagen

Introducción al problema.


imagen

Te aconsejo que comiences a sumergirte en el tema con este artículo: DeepMind

Ella le presentará la idea de utilizar la red Q profunda (DQN) para aproximar la función de valor que es crítica en los procesos de toma de decisiones de Markov.

También recomiendo profundizar en las matemáticas utilizando la preimpresión de este libro de Richard S. Sutton y Andrew J. Barto: aprendizaje por refuerzo

A continuación, presentaré una versión extendida del DQN original, que incluye más ideas que ayudan al algoritmo a converger de manera rápida y eficiente, a saber:

Deep Double Dueling Noisy NN con selección de prioridad del búfer de reproducción de experiencia.

¿Qué hace que este enfoque sea mejor que el DQN clásico?

  • Doble: hay dos redes, una de las cuales está entrenada y la otra evalúa los siguientes valores de Q
  • Duelo: hay neuronas que claramente valoran y benefician
  • Ruidoso: hay matrices de ruido aplicadas a los pesos de las capas intermedias, donde las desviaciones medias y estándar son pesos entrenados
  • Prioridad de muestreo: los lotes de observación del búfer de reproducción contienen ejemplos, debido a que el entrenamiento previo de funciones condujo a grandes residuos que pueden almacenarse en la matriz auxiliar.

Bueno, ¿qué pasa con el comercio realizado por el agente DQN? Este es un tema interesante como tal.


Hay razones por las que esto es interesante:

  • Absoluta libertad de elección de representaciones de estatus, acciones, premios y arquitectura de NN. Puede enriquecer el espacio de entrada con todo lo que considere digno de probar, desde noticias hasta otras acciones e índices.
  • La correspondencia de la lógica de negociación con la lógica de aprendizaje de refuerzo es que: el agente realiza acciones discretas (o continuas), rara vez es recompensado (después de que se cierra la transacción o expira el período), el entorno es parcialmente observable y puede contener información sobre los próximos pasos, la negociación es un juego episódico.
  • Puede comparar los resultados de DQN con varios puntos de referencia, como índices y sistemas técnicos de negociación.
  • El agente puede aprender continuamente nueva información y, por lo tanto, adaptarse a las reglas cambiantes del juego.

Para no estirar el material, mire el código de este NN, que quiero compartir, ya que esta es una de las partes misteriosas de todo el proyecto.

Código R para una red neuronal de valor usando Keras para construir nuestro agente RL


Código
# configure critic NN ------------ library('keras') library('R6') learning_rate <- 1e-3 state_names_length <- 12 # just for example 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 = c(as.integer(state_names_length)) , name = 'critic_input' ) common_layer_dense_1 <- layer_dense( units = 20 , activation = "tanh" ) 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(acts) , name = 'critic_layer_dense_a_3' ) critic_model_v <- critic_input %>% common_layer_dense_1 %>% critic_layer_dense_v_1 %>% critic_layer_dense_v_2 %>% critic_layer_dense_v_3 %>% v_normalize_layer critic_model_a <- critic_input %>% common_layer_dense_1 %>% 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 <- rnorm(state_names_length * 10) train.x <- array(train.x, dim = c(10, state_names_length)) 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 


Utilicé esta fuente para adaptar el código de Python para la parte de ruido de la red: github repo

Esta red neuronal se ve así:

imagen

Recordemos que en la arquitectura de duelo usamos igualdad (ecuación 1):

Q = A '+ V, donde

A '= A - promedio (A);

Q = valor de estado-acción;

V = valor del estado;

A = ventaja.

Otras variables en el código hablan por sí mismas. Además, esta arquitectura solo es buena para una tarea específica, por lo que no debe darse por sentado.

El resto del código probablemente será lo suficientemente genérico para su publicación, y será interesante que el programador lo escriba usted mismo.

Y ahora, los experimentos. Las pruebas del trabajo del agente se llevaron a cabo en una caja de arena, lejos de las realidades del comercio en un mercado en vivo, con un corredor real.

Fase I


Ejecutamos nuestro agente contra un conjunto de datos sintético. Nuestro costo de transacción es 0.5:

imagen

El resultado es excelente. La recompensa episódica promedio máxima en este experimento
debe ser 1.5.

imagen

Vemos: pérdida de críticas (la llamada red de valor en el enfoque actor-crítico), recompensa promedio por un episodio, recompensa acumulada, muestra de recompensas recientes.

Fase II


Le enseñamos a nuestro agente un símbolo bursátil elegido arbitrariamente que demuestra un comportamiento interesante: un comienzo plano, un crecimiento rápido en el medio y un final triste. En nuestro kit de entrenamiento unos 4300 días. El costo de la transacción se establece en 0.1 dólares estadounidenses (intencionalmente bajo); La recompensa es USD Ganancias / Pérdidas después de cerrar un acuerdo para comprar / vender 1.0 acciones.

Fuente: finance.yahoo.com/quote/algn?ltr=1

imagen

NASDAQ: ALGN

Después de establecer algunos parámetros (dejando la arquitectura NN igual), llegamos al siguiente resultado:

imagen

Resultó que no estaba mal, ya que al final el agente aprendió a obtener ganancias presionando tres botones en su consola.

imagen

marcador rojo = vender, marcador verde = comprar, marcador gris = no hacer nada.

Tenga en cuenta que en su apogeo, la recompensa promedio por episodio excedió el valor de transacción realista que se puede encontrar en el comercio real.

Es una pena que las acciones estén cayendo como locas debido a las malas noticias ...

Observaciones finales


Comerciar con RL no solo es difícil, sino que también es útil. Cuando su robot lo hace mejor que usted, es hora de pasar su tiempo personal para obtener educación y salud.

Espero que este haya sido un viaje interesante para ti. Si te gustó esta historia, agita tu mano. Si hay mucho interés, puedo continuar y mostrarle cómo funcionan los métodos de gradiente de políticas utilizando el lenguaje R y la API de Keras.

También quiero agradecer a mis amigos que están interesados ​​en las redes neuronales por sus consejos.

Si todavía tienes preguntas, siempre estoy aquí.

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


All Articles