English Русский 中文 Español Deutsch 日本語
preview
Redes neurais de maneira fácil (Parte 26): aprendizado por reforço

Redes neurais de maneira fácil (Parte 26): aprendizado por reforço

MetaTrader 5Negociação | 21 novembro 2022, 09:38
481 0
Dmitriy Gizlyk
Dmitriy Gizlyk

Conteúdo


Introdução

Nos artigos anteriores desta série, já foram apresentados os algoritmos de aprendizado supervisionado e não supervisionado. Com este artigo, abrimos mais um capítulo do aprendizado de máquina, em particular o aprendizado por reforço. Algoritmos deste tipo são baseados na implementação do aprendizado de tentativa e erro, que pode ser comparado ao sistema de aprendizado dos organismos vivos. Desta forma, é possível utilizar tais algoritmos para tarefas que exigem estratégias específicas. Como você pode ver, este é exatamente o tipo de tarefa em que a negociação pode ser classificada, já que todos os operadores seguem diversas estratégias para negociar com sucesso. Visto desse modo, o uso de tal tecnologia pode ser útil para resolver nossos problemas.


1. Fundamentos do aprendizado por reforço

Antes de começarmos a aprender sobre algoritmos específicos, vamos nos familiarizar com os conceitos básicos e a filosofia do aprendizado por reforço. Em primeiro lugar, é importante notar que, no aprendizado por reforço, o modelo não é considerado como algo tomado separadamente. Uma vez que aqui analisamos a tarefa de interação entre os atores do processo. Para entender melhor o processo geral, pode ser útil imaginar o homem como um dos participantes do processo. Estamos bem cientes de nossas ações. E, neste caso, será mais fácil entendermos o comportamento do modelo.

Assim, vivemos em um mundo que está em constante mudança. E suas mudanças, até certo ponto, dependem de nós e de nossas ações. Mas, provavelmente, em maior medida, suas mudanças não dependem de nós, já que nele também vivem milhões de outras pessoas, que também realizam determinadas ações. E também há muitos fatores que ocorrem sob a influência de outras forças que não dependem dos habitantes deste mundo.

Da mesma forma, no aprendizado por reforço destaca-se o ambiente (environment), que é a «encarnação» do nosso mundo. Um determinado agente (agent) interage com ele. Este agente pode ser comparado com uma pessoa que vive neste ambiente. Como o nosso mundo, o ambiente está em constante mudança.

Em nossas vidas, olhamos ao redor, avaliamos objetos pelo toque, ouvimos sons. Ou seja, através de nossos sentidos, avaliamos nosso mundo a cada momento. Em nossas mentes, fixamos seu estado.

Da mesma forma, o ambiente (environment) gera seu estado (state), que é avaliado pelo agente (agent).

Da mesma forma que agimos de acordo com nossa visão de mundo, o agente (agente) realiza a ação (action) ditada por sua estratégia (policy - política).

O ambiente muda com um certo grau de probabilidade como resultado do impacto sobre ele. Quando isso acontece, para cada ação, o agente (agent) recebe uma determinada recompensa (rewards) do ambiente (enviroment). Neste caso, a recompensa (rewards) pode ser tanto positiva quanto negativa. É pelo tamanho da recompensa que o agente (agent) pode avaliar a utilidade da ação.

Aprendizado por reforço

É importante notar aqui que a política de remuneração pode variar. Pode haver opções de uma etapa em que o ambiente (enviroment) retorna a recompensa (rewards) após cada ação. No entanto, existem algumas tarefas em que é difícil ou impossível avaliar cada ação. E a recompensa (rewards) só é possível no final da sessão. Por exemplo, se considerarmos um jogo de xadrez, podemos tentar dar uma avaliação especializada de cada movimento para melhorar ou piorar uma posição. Mas o objetivo principal é ganhar o jogo. E é essa recompensa que deve cobrir todas as possíveis recebidas anteriormente. Caso contrário, o modelo estará "jogando por jogar" e encontrará uma maneira de ficar obcecado em obter a recompensa máxima, em vez de avançar para a final.

Da mesma forma, a posição aberta no mercado no momento pode ser tanto positiva quanto negativa. Mas o resultado de uma operação de negociação só pode ser avaliado após o seu fechamento.

Casos como este demonstram que muitas vezes as recompensas (rewards) não dependem de uma única ação (action) nem de uma série de ações consecutivas (action).

Daí o propósito de treinar o modelo. Assim como nós, na negociação, nos esforçamos para obter o máximo lucro, o modelo é treinado para maximizar a recompensa (rewards) em um determinado intervalo finito, que pode ser uma partida, uma sessão específica ou apenas um período de tempo.

E aqui é necessário prestar atenção a 2 requisitos decorrentes para o uso de métodos de aprendizado por reforço.

Em primeiro lugar, o processo em estudo deve satisfazer o requisito do chamado processo de decisão de Markov. Em termos simples, cada ação (action) tomada por um agente (agent) depende apenas do estado estado atual (state). E nenhuma das ações anteriores (action) ou estados (state) observados tem qualquer efeito sobre as ações (action) do agente (agent) e sobre as mudanças no ambiente (environment). Toda a sua influência já é considerada no estado atual (state).

É claro que no mundo real é difícil encontrar um processo que satisfaça tal condição, e certamente não é o trading. Antes de fazer uma operação de negociação, os traders analisam cuidadosamente os dados de um determinado intervalo de tempo com os mais variados detalhes. Mas convenha que, com o tempo, a influência de todos os eventos diminui gradualmente e é compensada por novos eventos. Portanto, podemos sempre dizer que o estado atual inclui eventos de um determinado intervalo de tempo. E, por isso, é necessário acrescentar marcas de tempo para indicar há quanto tempo um evento ocorreu.

O segundo requisito é a finitude do processo. Acima, dissemos que o objetivo de treinar o modelo é maximizar a recompensa (rewards). E só podemos avaliar a rentabilidade de uma estratégia em períodos de tempo semelhantes. Afinal, convenha que uma estratégia funcional estável gerará mais lucro com o aumento do tempo de operação. Assim, para processos infinitos, a recompensa total (rewards) pode tender ao infinito.

Por conseguinte, para processos infinitos como a negociação, temos que limitar o tamanho da amostra de treinamento a um determinado período de tempo a fim de atender a esta condição. Quando fazemos isso, ao escolher o tamanho da amostra de treinamento, é necessário cuidar de sua representatividade. Afinal, queremos obter uma estratégia que possa funcionar pelo menos por algum tempo após o término do período de treinamento. E quanto maior o período de desempenho adequado do modelo, melhor.

Lembre-se, falamos mais ou menos sobre o mesmo assunto quando começamos a estudar algoritmos de aprendizado supervisionado, já sobre as diferenças entre os diferentes métodos de aprendizado falaremos um pouco mais tarde.

Gostaria agora de dizer algumas palavras sobre a importância de criar a política correta no sentido de recompensar o modelo pelas ações tomadas. Você já deve ter ouvido falar da importância de acertar o sistema de remuneração (recompensas) na gestão e nos recursos humanos. É o sistema de remuneração correto que incentiva as empresas a melhorar a produtividade e a qualidade do trabalho que as pessoas realizam. Da mesma forma, o sistema de recompensas certo pode ser a chave para treinar o modelo e alcançar o resultado desejado. Isso é especialmente importante para tarefas com recompensas atrasadas.

Muitas vezes é muito difícil distribuir corretamente a recompensa final entre todas as ações realizadas no caminho para alcançar o objetivo final. Por exemplo, como dividir o lucro ou prejuízo da transação entre as operações de abertura e fechamento de uma posição. A primeira coisa que vem à mente é dividir igualmente entre as duas operações. Mas, infelizmente, isso nem sempre é correto. Você pode abrir uma posição no momento certo e o preço, ir na direção prevista por você. Como determinar o momento de saída? Você pode fechar uma posição cedo e perder parte dos possíveis lucros, ou pode aguentar e fechar quando o preço começar a recuar contra você e perder parte dos possíveis lucros, e em situações particularmente infelizes, até mesmo perder. E nesse caso, parece que uma boa operação de abertura de posição receberá uma recompensa negativa, por conta de uma operação incorreta de fechamento de posição. Você não acha isso injusto? Além disso, depois de ter recebido uma recompensa negativa, é provável que da próxima vez o modelo ache tal situação inviável para a entrada. E você vai perder ainda mais lucro.

Claro, a situação inversa também é possível. Digamos, você abre uma posição malsucedida. Não discutiremos agora as razões de sua abertura. Mas você está com sorte. Depois de um tempo, o preço inverte e se move em sua direção. Você conseguiu fechar o negócio com lucro. E o modelo, depois de ter recebido uma recompensa positiva, considera tal entrada como bem sucedida e abre um novo negócio quando ocorre um padrão semelhante. Mas desta vez você não tem tanta sorte. E o preço não inverte.

Sim, o modelo não aprende com um único negócio, ele só coleta e faz a média das estatísticas das operações, um sistema de recompensa incorreto pode situar e direcionar o aprendizado do modelo na direção errada.

Por isso, ao construir modelos de aprendizado supervisionado, deve-se ter muito cuidado no desenvolvimento do sistema de recompensa.


2. Diferença em relação aos métodos considerados anteriormente

Vejamos a diferença entre os métodos de aprendizado por reforço e os algoritmos de aprendizado supervisionado e não supervisionado discutidos anteriormente. Todos os métodos têm algum modelo e conjunto de treinamento com os quais são treinados. Ao usar métodos de aprendizado supervisionado, a amostra de treinamento inclui pares de estados iniciais e respostas corretas fornecidas pelo "professor". No aprendizado não supervisionado, temos apenas uma amostra de treinamento e os algoritmos procuram semelhanças internas e a estrutura de estados individuais para separá-los. Em ambos os casos, a amostra de treinamento é estática. E isso não muda de forma alguma por causa do trabalho do modelo.

No caso do aprendizado por reforço, não temos uma amostra de treinamento no sentido usual. Temos um ambiente (environment), que gera o estado atual (state). Sim, podemos experimentar diferentes estados de ambiente para a amostra de treinamento. Mas há outra relação entre o ambiente e o agente. Após avaliar o estado do sistema, o agente executa alguma ação. O que afeta o ambiente e, em certa medida, o altera. Nesse caso, o ambiente também deve retornar uma resposta à ação na forma de recompensa (rewards).

Podemos comparar a recompensa recebida do ambiente com a "resposta de referência do professor" nos métodos de aprendizado supervisionado. Mas há aqui uma diferença crucial. O aprendizado supervisionado nos permite ter a resposta correta para cada situação e aprender com ela. Com o aprendizado por reforço, apenas obtemos uma reação à ação de nosso agente. Não entendemos como a recompensa surgiu. Além disso, não sabemos se é o máximo ou o mínimo, nem quão distante está dos extremos. Em outras palavras, conhecemos a ação do agente e sua avaliação. Mas não sabemos qual deveria ter sido a ação de "referência". Para saber isso, precisamos realizar todas as ações possíveis a partir de um determinado estado. E, neste caso, obteremos uma estimativa das ações em um estado.

Agora vamos lembrar que no próximo período de tempo entramos em um novo estado de ambiente, que depende da ação realizada pelo agente na etapa anterior.

E também pretendemos maximizar a recompensa total ao longo de todo o período analisado. E acontece que para obter uma ação de referência para cada estado, precisamos de um grande número de passagens completas por todos os estados possíveis do ambiente a partir da realização de todas as ações possíveis feitas pelo agente.

É fácil entender que tal abordagem será longa e trabalhosa. Por isso, uma série de heurísticas são utilizadas para encontrar estratégias ótimas, que discutiremos posteriormente.

Vamos resumir.

Aprendizado supervisionado Aprendizado não supervisionado Aprendizado por reforço
 Aprende até alcançar os valores de referência  Aprende a estrutura de dados  Aprende por tentativa e erro até alcançar a recompensa máxima
 Precisa de valores de referência  Dispensa valores de referência  Precisa da resposta do ambiente às ações do agente
 O modelo não afeta os dados de entrada  O modelo não afeta os dados de entrada  O agente pode influenciar o ambiente


3. Método de entropia cruzada

Para se familiarizar com algoritmos de aprendizado por reforço, sugiro começar com o método de entropia cruzada. É importante dizer que o uso deste método tem uma série de limitações. Para que seja usado corretamente, o ambiente deve ter um número finito de estados. E o agente está limitado a um número finito de ações possíveis. E, naturalmente, isso envolve o cumprimento das exigências acima mencionadas de um processo Markov e de um período limitado de aprendizado.

Este método é totalmente consistente com a ideologia do método de tentativa e erro. Lembre-se de como você, entrando em um ambiente desconhecido, começa a realizar várias ações para explorá-lo. Essas ações podem ser aleatórias ou baseadas em sua experiência adquirida em condições semelhantes.

Da mesma forma, o agente faz várias passagens do início ao fim no ambiente que está sendo estudado. Ao mesmo tempo, em cada estado, ele realiza uma determinada ação. A ação perfeita pode ser completamente aleatória ou ditada por uma determinada política incorporada ao agente durante a inicialização. O número de tais passagens pode ser diferente e é um hiperparâmetro, que é determinado pelo arquiteto do modelo.

Durante cada passagem pelo ambiente explorado, salvamos cada estado, a ação realizada e a recompensa total de cada passagem.

De entre todas as passagens, selecionamos entre 20% e 50% das melhores passagens com base na premiação total e atualizamos nossa política de agentes com base em seus resultados. A fórmula de atualização da política é mostrada abaixo.

Atualização de política

Após a atualização da política, repetimos as passagens pelo ambiente em estudo. Selecionamos as melhores passagens. E atualizamos a política do agente.

O ciclo se repete até que se obtenha o resultado desejado ou até que o crescimento da rentabilidade do modelo pare.

3.1. Implementação usando MQL5

Por mais simples que pareça o algoritmo do método de entropia cruzada, sua implementação por meio do MQL5 não é tão simples. Aqui devemos lembrar que este método assume um número finito de possíveis estados de ambiente e ações de agente. Embora a liberdade de ação do agente seja clara, a finitude do número de possíveis estados do ambiente é uma grande questão.

Mas aqui podemos pensar nos desafios do aprendizado não supervisionado e, em particular, nos problemas de agrupamento. Ao estudar o método k-médias, dividimos todos os estados possíveis em 500 agrupamentos. Na minha opinião, esta é uma solução completamente aceitável para o problema da finitude do número de estados possíveis do sistema.

Isso é suficiente para fazer uma demostração do algoritmo. E não entraremos agora em detalhes sobre a influência das ações do agente no estado do sistema.

O código MQL5 para a implementação do algoritmo do método de entropia cruzada é apresentado no arquivo EA "crossenteopy.mq5". No início, incluiremos as bibliotecas necessárias. É preciso dizer que aqui estaremos implementando uma versão tabular do método de entropia cruzada, por isso não estamos usando a biblioteca da rede neural. Aprenderemos sobre seu uso em algoritmos de aprendizado por reforço nos próximos artigos desta série.

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

Em seguida, declaramos variáveis externas, que foram quase completamente «bombeadas» desde o EA para demonstrar a operação do método k-médias. Isso não é surpreendente. Afinal, usaremos esse método para generalizar os padrões gráficos da situação do 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

Para fins de implementação do método de entropia cruzada, foram adicionadas 3 variáveis:

  • Samples - número de passagens para cada iteração de atualização de política;
  • Percentile - percentil da seleção de passagens de referência para atualização de política;
  • Actions - número de ações possíveis para o agente.

O número de estados possíveis do sistema é determinado pelo número de agrupamentos criados pelo método k-médias.

No método de inicialização do Expert Advisor, inicializamos os objetos para trabalhar com indicadores e o objeto para agrupar padrões 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 escreveremos imediatamente o método de desinicialização do Expert Advisor, no qual excluiremos todos os objetos criados acima. Deixe-me lembrá-lo de que o método de desinicialização é chamado quando o programa é fechado. E realizar a limpeza de memória nele é uma boa prática.

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;
//---
  }

A implementação do algoritmo e o treinamento do modelo são realizados na função Train. No início da função, realizaremos trabalhos preparatórios. Estes começam com a criação de um objeto para trabalhar com um dispositivo OpenCL. Usamos essa tecnologia para implementar o algoritmo k-médias. Mais detalhes sobre sua implementação podem ser encontrados no artigo "Redes neurais de maneira fácil (Parte 15): agrupamento de dados via MQL5".

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

Em seguida, atualizamos os dados 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();

E carregamos o modelo k-médias pré-treinado. Claro, lembramos sempre controlar o processo em cada etapa.

   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;
     }

Uma vez que as operações acima tenham sido realizadas com sucesso, verificamos se os dados históricos são suficientes.

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

E criamos uma amostra de histórico para agrupamento.

   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;

E imediatamente realizaremos o agrupamento.

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

Em seguida, começamos a trabalhar com o método de entropia cruzada. Primeiro, vamos preparar as variáveis necessárias. Aqui nós preenchemos com valores zero a matriz de estados do sistema states e de ações realizadas actions. As linhas de dados das matrizes corresponderão às passagens que estão sendo feitas. E suas colunas representam cada passo da passagem correspondente. Assim, nos states, salvaremos o estado em cada etapa da passagem correspondente. E na matriz de actions, salvaremos a ação realizada na etapa correspondente.

No vetor CumRewards, acumularemos a recompensa de cada passagem.

E inicializamos a política de nossa policy de agente com probabilidades iguais para cada ação.

   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);

Digamos desde já que este exemplo é um pouco "brinquedo" e é feito apenas para fazer uma demonstração da tecnologia. Portanto, para não aumentar o número de estados possíveis do sistema, excluí a influência das ações do agente na mudança do estado subsequente. E isso me permitiu preparar imediatamente uma sequência de todos os estados do sistema para o período analisado no vetor env. E usar os dados alvo da tarefa de aprendizado supervisionado nos permite criar um vetor de valores alvo target. Ao implementar tarefas práticas, não teremos essa possibilidade. E para obter esses dados, teremos que acessar nosso ambiente todas as vezes.

   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;
     }

Assim concluímos o trabalho preparatório e passamos à implementação direta do algoritmo de entropia cruzada. Conforme descrito acima, o algoritmo será implementado em um sistema de loop.

O loop externo contará o número de iterações de atualização da política de nosso agente. Nele, primeiro redefiniremos os vetores de recompensa cumulativa.

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

E então fazemos um loop aninhado de passagens pelo processo analisado.

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

Além disso, cada passagem também contém um loop aninhado que percorre todas as etapas do processo. Aqui, em cada etapa, selecionamos a ação mais provável para o estado atual. E se esse estado é novo para nós, escolhemos uma nova ação aleatoriamente. Depois disso, transferimos a ação selecionada para o ambiente e recebemos uma recompensa.

Nesta implementação, atribuí uma recompensa de "1" para a ação correta (corresponde à referência) e "-1" nos demais casos.

Salvamos o estado e a ação atuais e passamos para a próxima etapa (uma nova iteração do loop).

         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];
           }

Depois de concluir todas as passagens, determinamos o nível de recompensa de passagem para as passagens de referência.

      double percentile = CumRewards.Percentile(Percentile);

E realizamos a atualização da política do agente. Para isso, fazemos um sistema de loops para enumerar todas as passagens realizadas e selecionar apenas as de referência.

Para passagens de referência enumeramos todas as etapas concluídas e para cada estado visitado aumentamos em "1" o contador da ação concluída correspondente. Como verificamos apenas as passagens de referência, consideramos as ações realizadas como corretas. Afinal, graças a elas, recebemos o máximo de 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;
        }

Após contar as ações das estratégias de referência, normalizamos a política para que a probabilidade total de ações para cada estado seja "1". Para fazer isso, fazemos um loop por todas as linhas da matriz de políticas policy. Deixe-me lembrá-lo que cada linha desta matriz corresponde a um determinado estado do sistema, e a coluna corresponde à ação que está sendo executada. Se nenhuma ação foi salva para qualquer estado, então consideramos todas as ações igualmente possíveis para tal 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;
        }

Após concluir as iterações do loop, obtemos a política atualizada do nosso agente.

A título de informação, exibimos a recompensa máxima recebida e procedemos a uma nova iteração do ciclo de passagens pelo ambiente analisado.

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

Após a conclusão do treinamento do modelo, excluímos os objetos da amostra de treinamento e chamamos o procedimento para encerrar o Expert Advisor.

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

O código completo do Expert Advisor e as bibliotecas usadas podem ser encontradas no anexo.


Considerações finais

Com este artigo, abrimos um novo capítulo no aprendizado de máquina, o aprendizado por reforço. Esta abordagem está mais próxima do aprendizado dos organismos vivos e está organizada com base nos princípios de tentativa e erro. Esta é uma aproximação bastante promissora que permite a construção de estratégias lógicas de comportamento de modelo com base em dados não rotulados. É verdade que isso requer um estudo aprofundado do sistema de recompensa do modelo pelas ações realizadas.

No artigo, conhecemos um dos algoritmos de aprendizado por reforço, nomeadamente o método de entropia cruzada. Este método é bastante simples de entender, mas tem uma série de limitações. No entanto, um exemplo bastante simplificado de sua implementação nos mostra o potencial bastante grande dessa abordagem.

Nos artigos a seguir, continuaremos o tópico de aprendizado por reforço e nos familiarizaremos com outros algoritmos, incluindo aqueles que usam redes neurais para treinamento de agentes.

Referências

  1. Redes neurais de maneira fácil (Parte 14): agrupamento de dados
  2. Redes neurais de maneira fácil (Parte 15): agrupamento de dados via MQL5
  3. Redes neurais de maneira fácil (Parte 16): uso prático do agrupamento

Programas utilizados no artigo

# Nome Tipo Descrição
1 crossenteopy.mq5 EA EA para treinamento de modelos 
2 kmeans.mqh  Biblioteca de classe Biblioteca para gerar o método k-médias 
3 unsupervised.cl Biblioteca
Biblioteca de código do programa OpenCL para preparar o método k-médias


Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/11344

Arquivos anexados |
MQL5.zip (85.7 KB)
Redes neurais de maneira fácil (Parte 27): Aprendizado Q profundo (DQN) Redes neurais de maneira fácil (Parte 27): Aprendizado Q profundo (DQN)
Continuamos nosso estudo sobre aprendizado por reforço. E, neste artigo, vamos nos familiarizar com o método de aprendizado Q profundo. Com esse método, a equipe do DeepMind criou um modelo que pode superar um humano ao jogar jogos do Atari. Acho que será útil avaliar as possibilidades de tal tecnologia para resolver problemas de negociação.
Redes neurais de maneira fácil (Parte 25): Exercícios práticos de transferência de aprendizado Redes neurais de maneira fácil (Parte 25): Exercícios práticos de transferência de aprendizado
Nos dois últimos artigos, criamos uma ferramenta que permite criar e editar modelos de redes neurais. E agora é hora de avaliar o uso potencial da transferência de aprendizado (transfer learning, em inglês) usando exemplos práticos.
Como desenvolver um sistema de negociação baseado no indicador Bull's Power Como desenvolver um sistema de negociação baseado no indicador Bull's Power
Bem-vindo a um novo artigo em nossa série sobre como desenvolver um sistema de negociação com base nos indicadores técnicos mais populares, aqui está um novo artigo sobre como aprender a desenvolver um sistema de negociação pelo indicador técnico Bull's Power.
Operações com Matrizes e Vetores em MQL5 Operações com Matrizes e Vetores em MQL5
Matrizes e vetores foram introduzidos na MQL5 para operações eficientes com soluções matemáticas. Os novos tipos oferecem métodos integrados para a criação de código conciso e compreensível que se aproxima da notação matemática. Os arrays fornecem recursos extensos, mas há muitos casos em que as matrizes são muito mais eficientes.