English Русский 中文 Deutsch 日本語 Português
preview
Redes neuronales: así de sencillo (Parte 26): Aprendizaje por refuerzo

Redes neuronales: así de sencillo (Parte 26): Aprendizaje por refuerzo

MetaTrader 5Trading | 25 noviembre 2022, 16:54
651 0
Dmitriy Gizlyk
Dmitriy Gizlyk

Contenido


Introducción

En los artículos anteriores de esta serie, ya nos hemos familiarizado con los algoritmos de aprendizaje supervisado y no supervisado. En este, iniciaremos otro capítulo del aprendizaje automático: el aprendizaje por refuerzo. Los algoritmos de este tipo se basan en la implementación del aprendizaje por ensayo y error, que podemos comparar con el sistema de aprendizaje de los organismos vivos. Esto nos permite usar dichos algoritmos para resolver problemas que requieren el desarrollo de ciertas estrategias. Como comprenderá, el trading puede enmarcarse perfectamente en dichas tareas, ya que todos los tráders siguen ciertas estrategias para comerciar con éxito. Por consiguiente, el uso de dicha tecnología podrá resultar útil para resolver nuestros problemas.


1. Fundamentos del aprendizaje por refuerzo

Antes de comenzar a estudiar algoritmos específicos, vamos a familiarizarnos con los conceptos básicos y la filosofía del aprendizaje por refuerzo. En primer lugar, debemos señalar que en el aprendizaje por refuerzo, el modelo no se considera como algo aparte. Aquí analizaremos el problema de la interacción entre los sujetos del proceso. Para comprender mejor el proceso general, probablemente resulte útil presentar a una persona como uno de los participantes en el proceso. Somos muy conscientes de nuestras acciones, y en este caso, nos será más fácil entender el comportamiento del modelo.

Bien, el hombre vive en un mundo que está en constante cambio. Estos cambios, en cierta medida, dependen de nosotros y de nuestras acciones, pero, probablemente, en mayor medida, sus cambios no dependan de nosotros, ya que en él también viven millones de otras personas, que también realizan ciertas acciones, y también coexistimos con muchos factores que ocurren bajo la influencia de otras fuerzas que no dependen de los habitantes de este mundo.

Asimismo, en el aprendizaje por refuerzo, destaca el Entorno (Environment), que es la personificación de nuestro mundo. Con él interactúa un cierto Agente(Agent) . Dicho agente se puede comparar con una persona que vive en este entorno. Al igual que nuestro mundo, el entorno cambia constantemente.

En nuestras vidas, miramos a nuestro alrededor, percibimos objetos al tacto, escuchamos sonidos, es decir, usando nuestros sentidos, evaluamos el mundo en cada momento, registrando su estado en nuestra mente.

Igualmente, el Entorno (Environment) genera su Estado (State), el cual es evaluado por el Agente (Agent).

De la misma forma que actuamos según nuestra visión del mundo, el Agente (Agente) realiza la Acción (Acción) dictada por su Estrategia (Policy).

Bajo la influencia de la acción ejercida, el entorno cambia con cierto grado de probabilidad. Al mismo tiempo, por cada acción, el Agente (Agent) recibe del Entorno (Environment) una cierta recompensa (Rewards). En este caso, la Recompensa (Rewards) puede ser tanto positiva como negativa. Es según el tamaño de la recompensa, que el Agente (Agent) puede evaluar la utilidad de la acción.

Aprendizaje por refuerzo

Aquí debemos señalar que la política de recompensas puede ser diferente. Puede haber opciones de un solo paso, cuando el Entorno (Environment) retorna una Recompensa (Rewards) después de cada acción. No obstante, hay bastantes tareas en las que es difícil o imposible evaluar cada acción, y la recompensa(Rewards) solo es posible al final de la sesión. Por ejemplo, si consideramos una partida de ajedrez, podemos intentar dar una valoración experta de cada movimiento para mejorar o empeorar una posición, pero el objetivo principal es ganar la partida, y precisamente esta recompensa debería cubrir todas las posibles obtenidas antes. De lo contrario, el modelo consistirá en "jugar por jugar" y encontrará la manera de obsesionarse con obtener la máxima recompensa, en lugar de avanzar hasta el final.

De la misma forma, una posición abierta en el mercado en un momento del tiempo podrá ser tanto positiva como negativa, pero el resultado de una transacción comercial solo podrá evaluarse después de que esta se haya cerrado.

Casos como este demuestran que, con mucha frecuencia, la Recompensa (Rewards) no depende de una acción Acción (Action) aislada, sino de una serie de Acciones (Action) consecutivas.

Esto determina el objetivo de entrenamiento del modelo. Así como nos esforzamos por obtener el máximo beneficio en el trading, el modelo está entrenado para obtener la máxima Recompensa (Rewards) durante un cierto intervalo finito. Puede ser una partida o una sesión específica, o simplemente un periodo de tiempo.

Y aquí debemos prestar atención a 2 requisitos relacionados para el uso de métodos de aprendizaje por refuerzo.

Primero, el proceso analizado deberá cumplir el requisito del llamado proceso de decisión de Márkov. En palabras simples: la Acción (Action) utilizada por el Agente (Agent) dependerá solo del Estado (State). Y ninguna de las Acciones (Action) realizadas anteriormente o de los Estados (State) observados ejerce influencia en las Acciones (Action) del Agente (Agent) y los cambios del Entorno (Environment). Toda su influencia ya se considera en el Estado (State) actual.

Obviamente, en condiciones reales, resulta difícil encontrar un proceso que cumpla tal requisito, y definitivamente no se trata del trading. Antes de realizar una transacción comercial, los tráders analizan cuidadosamente los datos durante un cierto intervalo de tiempo con un diferente nivel de detalle. Sin embargo, debemos admitir que con el tiempo, la influencia de todos los eventos disminuye gradualmente y se compensa con nuevos eventos. Por consiguiente, siempre podemos decir que el estado actual incluye eventos para un cierto intervalo de tiempo, También podemos añadir marcas de tiempo para indicar hace cuánto tiempo ha ocurrido un evento.

El segundo requisito es la finitud del proceso. Más arriba, hemos dicho que el objetivo de entrenar al modelo es maximizar la Recompensa (Rewards), y podemos evaluar la rentabilidad de la estrategia solo para periodos de tiempo iguales. Después de todo, hay que admitir que una estrategia de trabajo estable generará mayor beneficio al aumentar el tiempo operativo. Entonces, para procesos infinitos, la Recompensatotal (Rewards) puede tender a infinito.

Por ello, para procesos infinitos como el trading, para cumplir este requisito, deberemos limitar el tamaño de la muestra de entrenamiento a un marco temporal determinado. Al mismo tiempo, a la hora de elegir el tamaño de la muestra de entrenamiento, deberemos preocuparnos de su representatividad. Después de todo, queremos conseguir una estrategia que pueda funcionar al menos durante algún tiempo tras finalizar el periodo de entrenamiento, y cuanto más largo sea el periodo en el que el modelo funcione correctamente, mejor.

Recuerde que hablamos de lo mismo cuando comenzamos a estudiar algoritmos de aprendizaje supervisado. Dejaremos las diferencias en los diferentes métodos de aprendizaje para un poco más adelante.

Ahora querríamos decir algunas palabras sobre lo importante que resulta crear una política adecuada de recompensas para el modelo según las Acciones realizadas. Probablemente haya oído hablar de la importancia de una correcta organización del sistema de recompensas en la dirección y gestión de personal. Un correcto sistema de remuneración estimula al personal de las empresas a la hora de aumentar la productividad y la calidad del trabajo realizado. De la misma forma, el sistema de recompensas correcto puede ser la clave para entrenar un modelo y lograr el resultado deseado. Esto es especialmente importante en tareas con recompensas retrasadas.

Con mucha frecuencia, resulta bastante difícil distribuir correctamente la recompensa final entre todas las acciones realizadas por el camino hasta la consecución del objetivo final. Por ejemplo, dividir el beneficio o las pérdidas de la transacción entre las operaciones de apertura y cierre de una posición. Lo primero que se nos ocurre es dividir a partes iguales entre ambas operaciones, pero, desafortunadamente, esto no siempre es cierto. Podemos abrir una posición en el momento adecuado y el precio comenzará a ir en su dirección. ¿Cómo determinamos el momento de salida? Podemos cerrar la posición antes de tiempo y perder parte del beneficio potencial. O podemos mantener la posición y cerrar cuando el precio comience a retroceder en su contra, y, en este caso, cerrar habiendo perdido parte del posible beneficio. En situaciones particularmente desafortunadas, podemos incluso cerra con pérdidas. En tal caso, parecería que una buena operación de apertura de posición obtendrá una recompensa negativa a causa de una operación de cierre de posición incorrecta. ¿No le parece esto injusto? Además, obteniendo una recompensa negativa, el modelo probablemente considerará la próxima vez una situación de entrada similar como no adecuada, y perderá aún más beneficios.

Obviamente, también resulta posible la situación inversa. Abrimos una posición sin éxito. No estamos discutiendo la razón de esto. Pero afortunadamente, el precio se revierte después de un tiempo y comienza a moverse en su dirección. Entonces, logra cerrar la posición con una ganancia. El modelo recibe una recompensa positiva, considera exitosa dicha entrada y, si sucede tal patrón, abre nuevamente una transacción. Pero esta vez no hay suerte y el precio no se revierte.

Sí, el modelo no aprende con una sola transacción, recopila y promedia las estadísticas de las transacciones, por lo que un sistema incorrecto de recompensas puede estropear la situación y conducir el entrenamiento del modelo en la dirección equivocada.

Por ello, al construir modelos de aprendizaje supervisado, deberemos tener mucho cuidado al desarrollar el sistema de recompensas.


2. Diferencias con los métodos analizados anteriormente

Veamos las diferencias existentes entre los métodos de aprendizaje por refuerzo y los algoritmos de aprendizaje supervisado y no supervisado analizados anteriormente. Todos los métodos tienen algún modelo y conjunto de entrenamiento con el que se entrenan. Al usar métodos de aprendizaje supervisado, la muestra de entrenamiento incluye pares de estados iniciales y respuestas correctas proporcionadas por el "profesor". En el aprendizaje no supervisado, solo tenemos una muestra de entrenamiento, y los algoritmos buscan similitudes internas y la estructura de los estados individuales para separarlos. En ambos casos, la muestra de entrenamiento es estática, y el funcionamiento del modelo no la cambia de ninguna forma.

En el caso del aprendizaje por refuerzo, no tenemos una muestra de entrenamiento en el sentido habitual. Tenemos un cierto Entorno (Environment) que genera el Estado (State) actual. Sí, podemos muestrear diferentes estados del entorno para la muestra de entrenamiento, pero existe otra relación entre el entorno y el agente. Después de evaluar el estado del sistema, el agente efectúa alguna acción, lo cual afecta al entorno y, en cierta medida, lo modifica. En este caso, el entorno también debe retornar una respuesta a la acción en forma de Recompensa (Rewards).

Podemos comparar la Recompensa obtenida del Entorno con la "respuesta de referencia del profesor" en los métodos de aprendizaje supervisado, pero aquí hay una diferencia fundamental. En el aprendizaje supervisado, tenemos la única respuesta correcta para cada situación y aprendemos de ella. En el caso del aprendizaje por refuerzo, solo obtenemos una reacción a la acción de nuestro Agente, y no entendemos cómo se ha formado esta recompensa. Además, no sabemos si la recompensa es máxima o mínima, y qué tan lejos se encuentra de los valores extremos. En otras palabras, conocemos la acción del agente y su valoración, pero no sabemos cuál debería haber sido la acción de "referencia". Para averiguarlo, necesitaremos realizar todas las acciones posibles desde un estado en particular, y en este caso, obtendremos la valoración de las acciones en un estado.

Ahora, recordemos que en el próximo intervalo temporal entraremos en un nuevo Estado de Entorno que dependerá de la Acción realizada por el Agente en el paso anterior,

y también nos esforzaremos por obtener la máxima recompensa total para todo el periodo analizado. Por lo tanto, para obtener una Acción de referencia para cada Estado, necesitaremos una gran cantidad de pasadas completas por todos los estados posibles del Entorno para todas las Acciones posibles realizadas por el Agente.

Resulta fácil entender que tal enfoque será largo y laborioso. Por consiguiente, para encontrar estrategias óptimas, utilizaremos una serie de heurísticas de las que hablaremos un poco más adelante.

Resumiendo:

Aprendizaje supervisado Aprendizaje no supervisado Aprendizaje por refuerzo
 Se entrena para aproximar los valores de referencia  Aprende la estructura de datos  Se entrena por ensayo y error hasta obtener la máxima recompensa
 Se requieren valores de referencia  No se requieren valores de referencia  Se requiere una respuesta del entorno a las acciones del agente.
 El modelo no influye en los datos de origen.  El modelo no influye en los datos de origen.  El agente puede influir en el Entorno.


3. Método de entropía cruzada

Para familiarizarse con los algoritmos de aprendizaje por refuerzo, le sugerimos comenzar con el método de entropía cruzada. No hace falta decir que este método tiene una serie de limitaciones. Para usarlo correctamente, el Entorno deberá tener un número finito de Estados, y el Agente estará limitado a un número finito de acciones posibles. Y, obviamente, se deberán cumplir los requisitos anteriores del proceso de Márkov y el periodo de entrenamiento limitado.

Este método se corresponde por entero con la ideología del método de prueba y error. Recuerde cómo usted, al ingresar a un entorno desconocido, empieza a realizar varias acciones para explorar el nuevo entorno. Estas acciones pueden ser aleatorias o basarse en la experiencia que usted ha adquirido en condiciones similares.

De la misma forma, el Agente realizará varias pasadas de principio a fin en el Entorno analizado. Al mismo tiempo, en cada Estado, realizará una determinada acción. La acción perfecta puede resultar completamente aleatoria o estar dictada por una Política determinada incorporada en el Agente durante la inicialización. El número de estas pasadas puede ser diferente y supone un hiperparámetro determinado por el arquitecto del modelo.

Durante cada pasada por el entorno explorado, guardaremos cada estado, la acción realizada y la recompensa total de cada pasada.

De todas las pasadas, seleccionaremos del 20% al 50% de las mejores según la recompensa total y actualizaremos la política de nuestro agente en función de sus resultados. La fórmula de actualización de la política se muestra más abajo.

Actualización de la política

Después de actualizar la política, repetiremos las pasadas por el entorno analizado. Luego seleccionaremos las mejores y actualizaremos la política del agente.

El ciclo se repetirá hasta que se obtenga el resultado deseado o se detenga el aumento de la rentabilidad de los modelos.

3.1. Implementación con los recursos MQL5

No importa lo simple que parezca el algoritmo del método de entropía cruzada, su implementación usando MQL5 no es tan sencilla. Aquí deberemos recordar que este método presupone un número finito de posibles estados del Entorno y las Acciones del Agente, y si bien todo está en orden con la libertad de acción del Agente, con la finitud del número de Estados posibles del Entorno surgen dudas.

Sin embargo, aquí podemos recordar las tareas de aprendizaje no supervisado, y concretamente, las tareas de clusterización. Al estudiar el método de k-medias, dividimos todos los estados posibles en 500 grupos. A nuestro juicio, esta es una solución completamente aceptable al problema de la finitud del número de estados posibles del sistema.

Para demostrar el algoritmo, esto es más que suficiente. No vamos a entrar ahora en detalles sobre la influencia de las acciones del agente en el estado del sistema.

El código MQL5 para la implementación del algoritmo del método de entropía cruzada se presenta en el archivo del asesor "crossenteopy.mq5". Al inicio, incluiremos las bibliotecas necesarias. Debemos decir que aquí implementaremos una versión tabular del método de entropía cruzada, por lo que no usaremos la biblioteca para trabajar con redes neuronales. Aprenderemos sobre su uso en algoritmos de aprendizaje por refuerzo en los próximos artículos de esta serie.

#include "..\Unsupervised\K-means\kmeans.mqh"
#include <Trade\SymbolInfo.mqh>
#include <Indicators\Oscilators.mqh>

A continuación, declararemos las variables externas, extraídas casi por completo del asesor experto para demostrar el funcionamiento del método k-means. Esto no sorprende en absoluto. Después de todo, vamos a usar este método para generalizar los patrones gráficos de la situación del mercado.

input int                  StudyPeriod =  15;            //Study period, years
input uint                 HistoryBars =  20;            //Depth of history
input int                  Clusters    =  500;           //Clusters
ENUM_TIMEFRAMES            TimeFrame   =  PERIOD_CURRENT;
//---
input int                  Samples     =  100;
input int                  Percentile  =  70;
      int                  Actions     =  3;
//---
input group                "---- RSI ----"
input int                  RSIPeriod   =  14;            //Period
input ENUM_APPLIED_PRICE   RSIPrice    =  PRICE_CLOSE;   //Applied price
//---
input group                "---- CCI ----"
input int                  CCIPeriod   =  14;            //Period
input ENUM_APPLIED_PRICE   CCIPrice    =  PRICE_TYPICAL; //Applied price
//---
input group                "---- ATR ----"
input int                  ATRPeriod   =  14;            //Period
//---
input group                "---- MACD ----"
input int                  FastPeriod  =  12;            //Fast
input int                  SlowPeriod  =  26;            //Slow
input int                  SignalPeriod =  9;            //Signal
input ENUM_APPLIED_PRICE   MACDPrice   =  PRICE_CLOSE;   //Applied price

A efectos de implementación del método de entropía cruzada, hemos añadido 3 variables:

  • Samples — número de pasadas para cada iteración de la actualización de la política;
  • Percentile — percentil de la selección de pasadas de referencia para la actualización de la política
  • Actions — número de acciones posibles para el agente.

El número de estados posibles del sistema está determinado por el número de clústeres creados por el método de k-medias.

En el método de inicialización del asesor experto, inicializaremos los objetos para trabajar con los indicadores, así como el objeto para la clusterización de patrones gráficos,

int OnInit()
  {
//---
   Symb = new CSymbolInfo();
   if(CheckPointer(Symb) == POINTER_INVALID || !Symb.Name(_Symbol))
      return INIT_FAILED;
   Symb.Refresh();
//---
   RSI = new CiRSI();
   if(CheckPointer(RSI) == POINTER_INVALID || !RSI.Create(Symb.Name(), TimeFrame, RSIPeriod, RSIPrice))
      return INIT_FAILED;
//---
   CCI = new CiCCI();
   if(CheckPointer(CCI) == POINTER_INVALID || !CCI.Create(Symb.Name(), TimeFrame, CCIPeriod, CCIPrice))
      return INIT_FAILED;
//---
   ATR = new CiATR();
   if(CheckPointer(ATR) == POINTER_INVALID || !ATR.Create(Symb.Name(), TimeFrame, ATRPeriod))
      return INIT_FAILED;
//---
   MACD = new CiMACD();
   if(CheckPointer(MACD) == POINTER_INVALID || !MACD.Create(Symb.Name(), TimeFrame, FastPeriod, SlowPeriod, SignalPeriod, MACDPrice))
      return INIT_FAILED;
//---
   Kmeans = new CKmeans();
   if(CheckPointer(Kmeans) == POINTER_INVALID)
      return INIT_FAILED;
//---
   bool    bEventStudy = EventChartCustom(ChartID(), 1, 0, 0, "Init");
//---
   return(INIT_SUCCEEDED);
  }

e inmediatamente escribiremos el método de desinicialización del asesor experto, en el que eliminaremos todos los objetos creados anteriormente. No olvidemos que el método de desinicialización se llama al cerrarse el programa, por lo que supondrá una buena práctica organizar la limpieza de la memoria en el mismo.

void OnDeinit(const int reason)
  {
//---
   if(CheckPointer(Symb) != POINTER_INVALID)
      delete Symb;
//---
   if(CheckPointer(RSI) != POINTER_INVALID)
      delete RSI;
//---
   if(CheckPointer(CCI) != POINTER_INVALID)
      delete CCI;
//---
   if(CheckPointer(ATR) != POINTER_INVALID)
      delete ATR;
//---
   if(CheckPointer(MACD) != POINTER_INVALID)
      delete MACD;
//---
   if(CheckPointer(Kmeans) != POINTER_INVALID)
      delete Kmeans;
//---
  }

La implementación del algoritmo y el entrenamiento del modelo se realizan en la función Train. Al comienzo de la función, realizaremos trabajos preparatorios. Estos comenzarán con la creación de un objeto para trabajar con el dispositivo OpenCL. Usaremos esta tecnología para implementar el algoritmo de k-medias. Encontrará más información sobre su implementación en el artículo "Redes neuronales: así de sencillo (Parte 15): Clusterización de datos usando MQL5

void Train(void)
  {
   COpenCLMy *opencl = OpenCLCreate(cl_unsupervised);
   if(CheckPointer(opencl) == POINTER_INVALID)
     {
      ExpertRemove();
      return;
     }
   if(!Kmeans.SetOpenCL(opencl))
     {
      delete opencl;
      ExpertRemove();
      return;
     }

A continuación, actualizaremos los datos históricos

   MqlDateTime start_time;
   TimeCurrent(start_time);
   start_time.year -= StudyPeriod;
   if(start_time.year <= 0)
      start_time.year = 1900;
   datetime st_time = StructToTime(start_time);
//---
   int bars = CopyRates(Symb.Name(), TimeFrame, st_time, TimeCurrent(), Rates);
   if(!RSI.BufferResize(bars) || !CCI.BufferResize(bars) || !ATR.BufferResize(bars) || !MACD.BufferResize(bars))
     {
      ExpertRemove();
      return;
     }
   if(!ArraySetAsSeries(Rates, true))
     {
      ExpertRemove();
      return;
     }
//---
   RSI.Refresh();
   CCI.Refresh();
   ATR.Refresh();
   MACD.Refresh();

y cargaremos el modelo de k-medias preentrenado. Obviamente, no nos olvidaremos de controlar el proceso en cada paso.

   int handl = FileOpen(StringFormat("kmeans_%d.net", Clusters), FILE_READ | FILE_BIN);
   if(handl == INVALID_HANDLE)
     {
      ExpertRemove();
      return;
     }
   if(FileReadInteger(handl) != Kmeans.Type())
     {
      ExpertRemove();
      return;
     }
   bool result = Kmeans.Load(handl);
   FileClose(handl);
   if(!result)
     {
      ExpertRemove();
      return;
     }

Después de completar con éxito las operaciones anteriores, comprobaremos que los datos históricos sean suficientes,

   int total = bars - (int)HistoryBars - 480;
   double data[], fractals[];
   if(ArrayResize(data, total * 8 * HistoryBars) <= 0 ||
      ArrayResize(fractals, total * 3) <= 0)
     {
      ExpertRemove();
      return;
     }

y crearemos una muestra de la historia para la clusterización.

   for(int i = 0; (i < total && !IsStopped()); i++)
     {
      Comment(StringFormat("Create data: %d of %d", i, total));
      for(int b = 0; b < (int)HistoryBars; b++)
        {
         int bar = i + b + 480;
         int shift = (i * (int)HistoryBars + b) * 8;
         double open = Rates[bar]
                       .open;
         data[shift] = open - Rates[bar].low;
         data[shift + 1] = Rates[bar].high - open;
         data[shift + 2] = Rates[bar].close - open;
         data[shift + 3] = RSI.GetData(MAIN_LINE, bar);
         data[shift + 4] = CCI.GetData(MAIN_LINE, bar);
         data[shift + 5] = ATR.GetData(MAIN_LINE, bar);
         data[shift + 6] = MACD.GetData(MAIN_LINE, bar);
         data[shift + 7] = MACD.GetData(SIGNAL_LINE, bar);
        }
      int shift = i * 3;
      int bar = i + 480;
      fractals[shift] = (int)(Rates[bar - 1].high <= Rates[bar].high && Rates[bar + 1].high < Rates[bar].high);
      fractals[shift + 1] = (int)(Rates[bar - 1].low >= Rates[bar].low && Rates[bar + 1].low > Rates[bar].low);
      fractals[shift + 2] = (int)((fractals[shift] + fractals[shift]) == 0);
     }
   if(IsStopped())
     {
      ExpertRemove();
      return;
     }
   CBufferFloat *Data = new CBufferFloat();
   if(CheckPointer(Data) == POINTER_INVALID ||
      !Data.AssignArray(data))
      return;
   CBufferFloat *Fractals = new CBufferFloat();
   if(CheckPointer(Fractals) == POINTER_INVALID ||
      !Fractals.AssignArray(fractals))
      return;

Inmediatamente después, realizaremos su clusterización.

//---
   ResetLastError();
   Data = Kmeans.SoftMax(Data);

A continuación, comenzaremos a trabajar con el método de entropía cruzada. Primero, prepararemos las variables requeridas. Aquí, rellenaremos con los valores cero de la matriz de Estados del sistema states y las Acciones actions realizadas. Las líneas de datos de las matrices se corresponderán con las pasadas que se están realizando, mientras que sus columnas representarán cada paso de la pasada correspondiente. Así, enstates guardaremos el estado en cada paso de la pasada correspondiente. Y en la matriz actions, guardaremos la acción realizada en el paso correspondiente.

En el vector CumRewards, acumularemos la recompensa de cada pasada,

e inicializaremos la política de nuestro agente policy con las mismas probabilidades para cada acción.

   vector   env = vector::Zeros(Data.Total() / Clusters);
   vector   target = vector::Zeros(env.Size());
   matrix   states = matrix::Zeros(Samples, env.Size());
   matrix   actions = matrix::Zeros(Samples, env.Size());
   vector   CumRewards = vector::Zeros(Samples);
   double   average = 1.0 / Actions;
   matrix   policy = matrix::Full(Clusters, Actions, average);

Diremos de inmediato que el ejemplo anterior no es del todo "real" ya que lo hemos creado solo para mostrar la tecnología. Por consiguiente, para no aumentar el número de estados posibles del sistema, hemos excluido la influencia de las acciones del agente en el cambio del estado posterior. Esto nos ha permitido preparar directamente una secuencia con todos los estados del sistema para el periodo analizado en el vectorenv. El uso de los datos objetivo de la tarea de aprendizaje supervisado nos permitirá crear el vector de valores objetivo target. Al implementar tareas prácticas, no tendremos esa oportunidad, y para obtener estos datos, tendremos que referirnos a nuestro Entorno cada vez.

   for(ulong state = 0; state < env.Size(); state++)
     {
      ulong shift = state * Clusters;
      env[state] = (double)(Data.Maximum((int)shift, Clusters) - shift);
      shift = state * Actions;
      target[state] = Fractals.Maximum((int)shift, Actions) - shift;
     }

Con esto, podemos considerar completado el trabajo preparatorio y proceder a la implementación directa del algoritmo del método de entropía cruzada. Como ya hemos descrito anteriormente, el algoritmo se implementará en un sistema de ciclos.

El ciclo externo contará el número de iteraciones de actualización de la Política de nuestro Agente. En él, primero restableceremos los vectores de recompensa acumulativos,

   for(int iter = 0; iter < 200; iter++)
     {
      CumRewards.Fill(0);

y luego organizaremos un ciclo anidado de pasadas por el proceso analizado.

      for(int sampl = 0; sampl < Samples; sampl++)
        {

Además, cada pasada también contendrá un ciclo anidado que recorrerá todos los pasos del proceso. Aquí, en cada paso, seleccionaremos la acción más probable para el estado actual, y si este estado es nuevo para nosotros, elegiremos una nueva acción al azar. Después de ello, transferiremos la Acción seleccionada al Entorno y obtendremos una recompensa.

En esta implementación, estableceremos una recompensa de "1" por la acción correcta (que se corresponda con la referencia) y "-1" en los demás casos.

Luego guardaremos el estado y la acción actuales y pasaremos al siguiente paso (una nueva iteración del ciclo).

         for(ulong state = 0; state < env.Size(); state++)
           {
            ulong a = policy.Row((int)env[state]).ArgMax();
            if(policy[(int)env[state], a] <= average)
               a = (int)(MathRand() / 32768.0 * Actions);
            if(a == target[state])
               CumRewards[sampl] += 1;
            else
               CumRewards[sampl] -= 1;
            actions[sampl, state] = (double)a;
            states[sampl,state]=env[state];
           }

Después de completar todas las pasadas, determinaremos el nivel de pasada de recompensa para las pasadas de referencia,

      double percentile = CumRewards.Percentile(Percentile);

y realizaremos el proceso de actualización de la política del agente. Para ello, organizaremos un sistema de ciclos para enumerar todas las pasadas realizadas y seleccionaremos de ellas solo las que sean de referencia.

Para las pasadas de referencia, enumeraremos todos los pasos completados, y para cada estado visitado, aumentamos en "1" el contador de la acción completada correspondiente. Como verificaremos solo las pasadas de referencia, consideraremos que las acciones realizadas son correctas. Después de todo, gracias a ellas obtendremos las máximas recompensas.

      policy.Fill(0);
      for(int sampl = 0; sampl < Samples; sampl++)
        {
         if(CumRewards[sampl] < percentile)
            continue;
         for(int state = 0; state < env.Size(); state++)
            policy[(int)states[sampl, state], (int)actions[sampl, state]] += 1;
        }

Tras contar las acciones de las estrategias de referencia, normalizaremos la política para que la probabilidad total de realizar acciones para cada estado sea "1". Para lograr esto, organizaremos un ciclo a través de todas las filas de matriz de la política policy. Recuerde que cada fila de esta matriz se corresponde con un determinado estado del sistema, mientras que la columna se corresponde con la acción que se está realizando. Si no hemos guardado acciones para ningún estado, entonces consideraremos que todas las acciones son igualmente posibles para dicho estado.

      for(int row = 0; row < Clusters; row++)
        {
         vector temp = policy.Row(row);
         double sum = temp.Sum();
         if(sum > 0)
            temp = temp / sum;
         else
            temp.Fill(average);
         if(!policy.Row(temp, row))
            break;
        }

Después de completar las iteraciones del ciclo, obtendremos la política actualizada de nuestro Agente.

Para visualizar la información, mostraremos la recompensa máxima obtenida y continuaremos con una nueva iteración a través del entorno analizado.

      PrintFormat("Iteration %d, Max reward %.0f", iter, CumRewards.Max());
     }

Una vez completemos el entrenamiento del modelo, eliminaremos los objetos de la muestra de entrenamiento y llamaremos al procedimiento para finalizar el trabajo del asesor.

   if(CheckPointer(Data) == POINTER_DYNAMIC)
      delete Data;
   if(CheckPointer(Fractals) == POINTER_DYNAMIC)
      delete Fractals;
   if(CheckPointer(opencl) == POINTER_DYNAMIC)
      delete opencl;
   Comment("");
//---
   ExpertRemove();
  }

Podrá encontrar el código completo del asesor experto y las bibliotecas usadas en el archivo adjunto.


Conclusión

Con este artículo, inauguramos un nuevo capítulo en el aprendizaje automático: el aprendizaje por refuerzo. Por su espíritu, este enfoque es el más cercano al aprendizaje de los organismos vivos, y está organizado sobre los principios de prueba y error. Este es un enfoque bastante prometedor que nos permite crear estrategias lógicamente sólidas para el comportamiento del modelo basadas en datos no etiquetados. Cierto que esto requiere un estudio exhaustivo del sistema de recompensas del modelo por las acciones realizadas.

En el artículo, nos hemos familiarizado con uno de los algoritmos de aprendizaje por refuerzo: el método de entropía cruzada. Este método es bastante simple de entender, pero tiene una serie de limitaciones. No obstante, incluso con un ejemplo bastante simplificado de su implementación, podemos ver el gran potencial de este enfoque.

En los siguientes artículos, continuaremos con el tema del aprendizaje por refuerzo y analizaremos otros algoritmos, incluyendo aquellos que usan redes neuronales para el entrenamiento de los agentes.

Enlaces

  1. Redes neuronales: así de sencillo (Parte 14): Clusterización de datos
  2. Redes neuronales: así de sencillo (Parte 15): Clusterización de datos usando MQL5
  3. Redes neuronales: así de sencillo (Parte 16): Uso práctico de la clusterización

Programas usados en el artículo.

# Nombre Tipo Descripción
1 crossenteopy.mq5 Asesor Asesor para el entrenamiento de modelos 
2 kmeans.mqh  Biblioteca de clases Biblioteca para organizar el método de k-medias 
3 unsupervised.cl Biblioteca
Biblioteca de código del programa OpenCL para organizar el método de k-medias


Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/11344

Archivos adjuntos |
MQL5.zip (85.7 KB)
DoEasy. Elementos de control (Parte 16): Objeto WinForms TabControl - múltiples filas de encabezados de pestañas, modo de expansión de encabezados para ajustarse al tamaño del contenedor DoEasy. Elementos de control (Parte 16): Objeto WinForms TabControl - múltiples filas de encabezados de pestañas, modo de expansión de encabezados para ajustarse al tamaño del contenedor
En este artículo, proseguiremos con el desarrollo del control TabControl, e implementaremos la disposición de los encabezados de las pestañas en los cuatro lados del control para todos los modos de establecimiento de tamaño del encabezado: "Normal", "Fixed" y "Fill To Right".
Indicador técnico de preparación propia Indicador técnico de preparación propia
En este artículo, analizaremos algunos algoritmos que nos permitirán crear nuestro propio indicador técnico. Asimismo, veremos cómo, con unos supuestos iniciales muy sencillos, podremos obtener resultados bastante complejos e interesantes.
Redes neuronales: así de sencillo (Parte 27): Aprendizaje Q profundo (DQN) Redes neuronales: así de sencillo (Parte 27): Aprendizaje Q profundo (DQN)
Seguimos explorando el aprendizaje por refuerzo. En este artículo, hablaremos del método de aprendizaje Q profundo o deep Q-learning. El uso de este método permitió al equipo de DeepMind crear un modelo capaz de superar a los humanos jugando a los videojuegos de ordenador de Atari. Nos parece útil evaluar el potencial de esta tecnología para las tareas comerciales.
Aprendizaje automático y Data Science (Parte 06). Redes neuronales (Parte 02): arquitectura de la redes neuronales con conexión directa Aprendizaje automático y Data Science (Parte 06). Redes neuronales (Parte 02): arquitectura de la redes neuronales con conexión directa
En el artículo anterior, comenzamos a estudiar las redes neuronales con conexión directa, pero hay algunas cosas que quedaron sin resolver. Una de ellas es el diseño de la arquitectura. Por ello, en el presente artículo, veremos cómo diseñar una red neuronal flexible, teniendo en cuenta los datos de entrada, el número de capas ocultas y los nodos de cada red.