تحسين وكيل Q-Learning القائم على الأسهم عن طريق إضافة التكرار والمكافآت

تذكير


مرحبا يا هبر! أوجه انتباهكم إلى ترجمة أخرى لمقالتي الجديدة من الوسط .

آخر مرة ( المادة الأولى ) ( Habr ) ، أنشأنا وكيلًا باستخدام تقنية Q-Learning ، التي تجري المعاملات على سلاسل زمنية محاكية وحقيقية للتبادل وحاولت التحقق مما إذا كان مجال المهام هذا مناسبًا للتعلم المعزز.

هذه المرة سنضيف طبقة LSTM لمراعاة تبعيات الوقت داخل المسار ونقوم بتكوين المكافآت بناءً على العروض التقديمية.

الصورة

اسمحوا لي أن أذكرك بأنه للتحقق من المفهوم ، استخدمنا البيانات الاصطناعية التالية:

الصورة

البيانات الاصطناعية: جيب مع ضوضاء بيضاء.

كانت وظيفة الجيب نقطة الانطلاق الأولى. يحاكي منحنيان سعر البيع والشراء للأصل ، حيث يكون السبريد هو الحد الأدنى لتكلفة المعاملة.

ومع ذلك ، نريد هذه المرة تعقيد هذه المهمة البسيطة من خلال توسيع مسار التنازل عن الائتمان:

الصورة

البيانات الاصطناعية: جيب مع ضوضاء بيضاء.

تضاعفت المرحلة الجيبية.

هذا يعني أن المكافآت المتفرقة التي نستخدمها يجب أن تنتشر على مسارات أطول. بالإضافة إلى ذلك ، نقوم بتقليل احتمالية تلقي مكافأة إيجابية بشكل كبير ، حيث كان على الوكيل تنفيذ سلسلة من الإجراءات الصحيحة مرتين أطول للتغلب على تكاليف المعاملات. كل من العوامل تعقيد مهمة RL إلى حد كبير حتى في ظروف بسيطة مثل موجة جيبية.

بالإضافة إلى ذلك ، نذكر أننا استخدمنا بنية الشبكة العصبية هذه:

الصورة

ما تم إضافته ولماذا


Lstm


بادئ ذي بدء ، أردنا إعطاء الوكيل مزيدًا من الفهم لديناميات التغييرات داخل المسار. ببساطة ، يجب أن يفهم العميل سلوكه بشكل أفضل: ما الذي فعله الآن ولوقت ما في الماضي ، وكيف تطور توزيع تصرفات الدولة ، وكذلك المكافآت المستلمة. يمكن أن يؤدي استخدام طبقة التكرار إلى حل هذه المشكلة تمامًا. مرحبًا بك في الهيكل الجديد المستخدم لإطلاق مجموعة جديدة من التجارب:

الصورة

يرجى ملاحظة أنني قد تحسنت قليلا الوصف. الفرق الوحيد من NN القديم هو أول طبقة LSTM مخفية بدلاً من طبقة مرتبطة بالكامل.

يرجى ملاحظة أنه مع LSTM في العمل ، يجب علينا تغيير اختيار أمثلة لاستنساخ تجربة للتدريب: الآن نحن بحاجة إلى تسلسل الانتقال بدلا من أمثلة منفصلة. إليك طريقة عملها (هذه واحدة من الخوارزميات). استخدمنا نقطة أخذ العينات من قبل:

الصورة

المخطط وهمية من المخزن المؤقت التشغيل.

نستخدم هذا المخطط مع LSTM:

الصورة

الآن يتم تحديد تسلسل (الذي نحدد طوله بشكل تجريبي).

كما كان من قبل ، والآن يتم تنظيم العينة من خلال خوارزمية الأولوية بناءً على أخطاء التعلم الزماني.

يسمح مستوى تكرار LSTM بالنشر المباشر للمعلومات من السلاسل الزمنية لاعتراض إشارة إضافية مخبأة في التأخيرات السابقة. سلسلتنا الزمنية عبارة عن موتر ثنائي الأبعاد بحجم: طول التسلسل على تمثيل حالتنا.

العروض التقديمية


الهندسة الحائزة على الجوائز ، تشكيل القدرة على المكافآت (PBRS) ، بناءً على الإمكانات ، هي أداة قوية لزيادة السرعة والاستقرار ولا تنتهك أمثلية عملية البحث عن السياسات لحل بيئتنا. أوصي بقراءة هذا المستند الأصلي على الأقل حول هذا الموضوع:

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

يحدد الجهد المحتمل مدى حالتنا الحالية بالنسبة للحالة المستهدفة التي نريد أن ندخلها. عرض تخطيطي لكيفية عمل هذا:

الصورة

هناك خيارات وصعوبات يمكن أن تفهمها بعد التجربة والخطأ ، ونحذف هذه التفاصيل ، ونتركك في واجبك المنزلي.

تجدر الإشارة إلى شيء واحد آخر ، وهو أنه يمكن تبرير PBRS باستخدام العروض التقديمية ، والتي تعد شكلاً من أشكال المعرفة الخبيرة (أو المحاكاة) حول السلوك الأمثل تقريبًا للعامل في البيئة. هناك طريقة للعثور على مثل هذه العروض التقديمية لمهمتنا باستخدام مخططات التحسين. نحذف تفاصيل البحث.

تأخذ المكافأة المحتملة الشكل التالي (المعادلة 1):

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

حيث F هي إمكانات الدولة ، و r هي المكافأة الأولية ، gamma هي عامل الخصم (0: 1).

مع هذه الأفكار ، ننتقل إلى الترميز.

التنفيذ في R
إليك رمز الشبكة العصبية استنادًا إلى واجهة برمجة تطبيقات Keras:

كود
# 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 


تصحيح قرارك بشأن ضميرك ...

النتائج والمقارنة


دعونا الغوص مباشرة في النتائج النهائية. ملاحظة: جميع النتائج هي تقديرات نقطية وقد تختلف على فترات متعددة مع بذور بذرة عشوائية مختلفة.

تشمل المقارنة ما يلي:

  • الإصدار السابق دون LSTM والعروض التقديمية
  • 2-عنصر بسيط LSTM
  • 4-عنصر LSTM
  • 4 خلايا LSTM مع المكافآت PBRS ولدت

الصورة

بلغ متوسط ​​العائد لكل حلقة أكثر من 1000 حلقة.

الصورة

إجمالي عوائد الحلقة.

الرسوم البيانية للوكيل الأكثر نجاحا:

الصورة

الصورة

أداء وكيل.

حسنًا ، من الواضح تمامًا أن العامل في شكل PBRS يتقارب بسرعة وثبات مقارنة بالمحاولات السابقة التي يمكن قبولها كنتيجة مهمة. السرعة حوالي 4-5 مرات أعلى من دون العروض التقديمية. الاستقرار رائع

عندما يتعلق الأمر باستخدام LSTM ، أداء 4 خلايا أفضل من 2 الخلايا. كان أداء LSTM المكون من خليتين أفضل من الإصدار غير المعتمد على LSTM (ومع ذلك ، ربما هذا هو وهم تجربة واحدة).

الكلمات النهائية


لقد رأينا أن تكرار المكافآت وبناء القدرات يساعدان. أعجبني بشكل خاص كيف كان أداء PBRS مرتفعًا للغاية.

لا تصدق أي شخص يجعلني أقول أنه من السهل إنشاء وكيل RL يتقارب بشكل جيد ، كذبة. كل مكون جديد تمت إضافته إلى النظام يجعله أقل استقرارًا ويتطلب الكثير من التكوين والتصحيح.

ومع ذلك ، هناك أدلة واضحة على أنه يمكن تحسين حل المشكلة ببساطة عن طريق تحسين الأساليب المستخدمة (ظلت البيانات سليمة). إنها لحقيقة أن مجموعة معينة من المعلمات تعمل لأي مهمة أفضل من غيرها. مع وضع ذلك في الاعتبار ، أنت تشرع في مسار تعليمي ناجح.

شكرا لك

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


All Articles