通过增加重复率和奖励来改善基于股票的Q学习代理

温馨提示


哈Ha! 我提请您注意媒体上我的新文章的另一种翻译。

上次( 第一篇文章 )( Habr ),我们使用Q-Learning技术创建了一个代理,该代理按模拟和真实的交换时间序列进行交易,并尝试检查此任务区域是否适合强化学习。

这次,我们将添加一个LSTM层,以考虑轨迹内的时间依赖性,并根据演示进行奖励整形。

图片

让我提醒您,为了验证这一概念,我们使用了以下综合数据:

图片

合成数据:正弦,白噪声。

正弦函数是第一个起点。 两条曲线模拟资产的购买和出售价格,其中价差是最小交易成本。

但是,这次我们要通过扩展信用分配路径来使此简单任务复杂化:

图片

合成数据:正弦,白噪声。

窦相加倍。

这意味着我们使用的稀疏奖励必须分布在更长的轨迹上。 此外,由于代理商为了克服交易成本不得不执行2次以上的正确动作序列,因此,我们显着降低了获得正面奖励的可能性。 即使在诸如正弦波这样的简单条件下,这两个因素也极大地增加了RL的任务。

此外,我们还记得我们使用了这种神经网络架构:

图片

添加了什么,为什么


Lstm


首先,我们希望使代理对轨迹内的变化动态有更多的了解。 简而言之,代理人应该更好地了解自己的行为:他现在和过去的一段时间内所做的事情,以及国家行为的分配以及所获得的报酬如何发展。 使用递归层可以完全解决此问题。 欢迎使用用于启动一组新实验的新架构:

图片

请注意,我对说明做了一些改进。 与旧的NN的唯一区别是第一个隐藏的LSTM层,而不是完全粘合的层。

请注意,在使用LSTM的情况下,我们必须更改用于训练的再现经验示例的选择:现在,我们需要过渡序列而不是单独的示例。 这是它的工作方式(这是算法之一)。 我们在以下步骤之前使用了点采样:

图片

播放缓冲区的虚拟方案。

我们将此方案与LSTM结合使用:

图片

现在选择序列(我们根据经验指定其长度)。

与以前一样,现在通过基于时空学习错误的优先级算法对样本进行调节。

LSTM循环级别允许从时间序列中直接传播信息,以拦截隐藏在过去滞后中的其他信号。 与我们有关的时间序列是一个二维张量,其大小为:表示我们的状态行为的序列的长度。

简报


屡获殊荣的工程,基于潜力的基于潜力的奖励塑造(PBRS)是一种功能强大的工具,可让您提高速度,稳定性,而又不违反解决我们环境的策略搜索过程的最优性。 我建议至少阅读该主题的原始文档:

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

电位确定我们当前状态相对于我们要输入的目标状态的程度。 原理图:

图片

经过反复试验,您可以理解一些选择和困难,我们省略了这些详细信息,使您无需做任何作业。

值得一提的是,可以通过演示来证明PBRS是合理的,这些演示是有关代理在环境中几乎最佳行为的一种专家(或模拟)知识形式。 有一种方法可以使用优化方案为我们的任务找到此类演示文稿。 我们忽略了搜索的详细信息。

潜在奖励采用以下形式(等式1):

r'= r +伽玛* F(s')-F(s)

其中F是国家的潜力,r是初始奖励,gamma是折扣因子(0:1)。

基于这些想法,我们继续进行编码。

在R中执行
这是基于Keras API的神经网络代码:

代号
# 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 


调试您的良心决定...

结果与比较


让我们深入研究最终结果。 注意:所有结果均为点估计,并且在多次运行中使用不同的随机种子sid可能会有所不同。

比较包括:

  • 没有LSTM和演示文稿的先前版本
  • 简单的2元素LSTM
  • 四元素LSTM
  • 具有PBRS奖励的4单元LSTM

图片

每集的平均回报平均超过1000集。

图片

总剧集收益。

最成功的代理商的图表:

图片

图片

代理商绩效。

好吧,很明显,与以前的尝试相比,PBRS形式的代理收敛得如此迅速和稳定,因此可以被接受为重要的结果。 速度比没有演示文稿时快约4-5倍。 稳定性很棒。

使用LSTM时,4个单元的性能优于2个单元。 2单元LSTM的性能优于非LSTM版本(但是,这可能是单个实验的错觉)。

最后的话


我们已经看到,复发和能力建设的奖励是有帮助的。 我特别喜欢PBRS如此出色的表现。

不要相信有人让我说创建一个收敛良好的RL代理很容易,因为这是一个谎言。 添加到系统中的每个新组件都可能使其不稳定,并且需要大量配置和调试。

但是,有明确的证据表明,仅通过改进所使用的方法(数据保持不变)就可以改善问题的解决方案。 事实上,对于任何任务,一定范围的参数都比其他参数更好。 考虑到这一点,您正在走上成功的学习之路。

谢谢啦

Source: https://habr.com/ru/post/zh-CN436628/


All Articles