English Русский 中文 Español Deutsch 日本語
preview
Algoritmos de otimização populacionais: Algoritmo de mudas, semeadura e crescimento (SSG)

Algoritmos de otimização populacionais: Algoritmo de mudas, semeadura e crescimento (SSG)

MetaTrader 5Exemplos | 28 junho 2023, 09:59
196 0
Andrey Dik
Andrey Dik

Conteúdo

1. Introdução
2. Descrição do algoritmo
3. Resultado dos testes


1. Introdução

Todos os organismos vivos na natureza estão sujeitos a certas leis, o que condiciona a supervivência da flora e da fauna em ambientes em constante mudança. Há diferentes adaptações das plantas ao seu ambiente. Algumas são capazes de tolerar mudanças sazonais, outras se adaptam à falta de umidade, a temperaturas altas ou baixas e à falta de polinizadores. Um dos organismos mais resistentes entre as plantas são as árvores, algumas espécies podem viver por mais de 50 mil anos formando arvoredos.

A natureza é um campo inesgotável de inspiração para muitas ideias eficazes no desenvolvimento e na invenção de métodos computacionais. De fato, a computação evolucionária é a projeção da evolução em simulações de computador. Há muitas técnicas de otimização que foram inspiradas por processos que ocorrem na natureza, como computação evolucionária, imunologia artificial, métodos populacionais e outros. O algoritmo de “mudas, semeadura e crescimento” (Saplings Sowing and Growing up, SSG) é caracterizado por processos iterativos de geração e combinação que utilizam um jardim de soluções potenciais chamadas de mudas.  O SSG foi proposto por A. Karci et al. em 2002 e é inspirado na evolução de árvores em crescimento e modela o crescimento e a ramificação das árvores.


2. Descrição do algoritmo

O SSG é um dos poucos que não é descrito claramente pelos autores (existem apenas indicações e ideias gerais sobre ele). Os operadores de variação apresentados pelos autores também não são instruções prontas para a implementação algorítmica do mesmo, não existe indicação clara das árvores filha e pai e de sua interação; também não há requisitos para a sequência de procedimentos, e qualquer usuário pode alterar sua ordem para obter a melhor árvore.

Em um sentido amplo, o SSG não é um algoritmo de otimização, mas, sim, um conjunto genérico de regras que se destina a complementar outros algoritmos para melhorar a qualidade da otimização, ou seja, ele é uma espécie de estrutura complementar para qualquer algoritmo populacional evolutivo, de modo que posso dar asas à imaginação e à oportunidade de experimentar uma implementação específica do algoritmo de otimização. Apliquei algumas de minhas próprias ideias e experiência acumulada ao escrever algoritmos anteriores e as utilizei para trabalhar com o SSG; os resultados de meus experimentos são apresentados a seguir.

Para começar a entender o algoritmo, devemos imaginar uma árvore como um agente de otimização. Uma árvore é uma solução para um problema de otimização, em que cada ramo é um parâmetro otimizável do problema. De forma bastante abstrata e, eu diria, artística, podemos ilustrar uma árvore filha e uma árvore pai (o algoritmo opera com essas duas noções) na Figura 1. O tronco da árvore é um conjunto de parâmetros a serem otimizados. Cada ramo é um parâmetro otimizável separado, em que o comprimento do ramo é limitado pelo intervalo permitido de valores do parâmetro correspondente. A direção dos ramos não é importante e só é mostrada na figura para mostrar que eles são diferentes.

trees

Figura 1. Árvore filha e árvore pai. A linha pontilhada indica os ramos filhos substituídos pelos ramos pais.

Assim, os ramos da árvore são coordenadas da árvore no espaço de busca.

O SSG é composto por operadores de variação que geram novas soluções, que são candidatas ao conjunto de soluções comum. Os principais operadores de variação são cruzamento, ramificação e vacinação. As mudas devem ser plantadas a distâncias equidistantes em cada direção uma da outra (oeste, leste, norte, sul), e essa é a etapa inicial do método. Quando há muito mais de três coordenadas (parâmetros otimizáveis), o plantio "uniforme" é simplesmente uma distribuição aleatória de mudas no espaço de busca. As mudas crescem, cruzam, se ramificam e o processo de vacinação ocorre.

Vejamos as etapas e os operadores do SSG:

1) Plantio de mudas.

O espaço de busca pode ser considerado como um jardim de mudas e, portanto, todas as mudas devem estar espalhadas uniformemente no jardim. Se o fazendeiro quiser plantar mudas, ele simplesmente as plantará a uma distância igual umas das outras para que as mudas cresçam mais rapidamente sem atrapalhar umas às outras. Para resolver o problema ao simular o cultivo de mudas, as soluções arbitrárias a serem geradas inicialmente devem ser distribuídas uniformemente no espaço de busca permitido.

Quando há duas ou três coordenadas, não há problema em distribuir as mudas uniformemente, mas quando há muito mais do que três coordenadas, é mais fácil usar a atribuição aleatória. Porém, na prática, quando o número de coordenadas é pequeno, não há necessidade de se preocupar com a distribuição uniforme das mudas, pois o problema não é grande e é sabido que a solução será obtida com alta precisão, por isso, independentemente do número de coordenadas no algoritmo, usaremos a distribuição aleatória de mudas no jardim. 

2) Cultivo de mudas (árvores), três operadores executados sequencialmente.

2.1) Cruzamento.

O objetivo do operador de cruzamento é criar uma nova muda a partir das mudas existentes, trocando informações entre elas. O cruzamento é basicamente enxertar uma cópia de um ramo de uma árvore-pai em uma árvore-filha (Figura 1). Para cada par de mudas, é usado um fator de cruzamento diferente, que é a probabilidade de cruzamento. A probabilidade de cruzamento depende da distância entre as mudas do par; quanto maior a distância, menor a probabilidade de cruzamento. O operador de cruzamento é um método muito importante do algoritmo que fornece mecanismos combinatórios, combinando informações entre agentes que podem melhorar muito a qualidade geral do algoritmo de otimização.

2.2) Ramificação.

Este operador simula o crescimento do ramo, e o crescimento pode ser tanto positivo (alongamento) quanto negativo (dessecação). "Para que um galho cresça em qualquer ponto do corpo de uma muda, não deve haver nenhum galho próximo que tenha surgido anteriormente ali.", é mais ou menos assim que os autores do algoritmo descrevem o operador de "ramificação". Na verdade, esse processo é mais simples e mais claro do que parece à primeira vista e representa uma modificação dos ramos existentes na árvore filha (o método específico de modificação não é especificado).

A modificação de cada ramo individual é mais provável quanto mais iterações tiverem ocorrido entre a iteração atual e aquela em que a última modificação do ramo foi realizada. Meus experimentos mostraram a ineficiência desse operador; além disso, não há instruções expressas para usar o método de modificação, e eu me permiti ser proativo nessa questão e apliquei uma alteração no comprimento do ramo de acordo com a lei de distribuição de voo de Levy. A modificação será aplicada com a probabilidade e a intensidade especificadas como parâmetros externos do algoritmo.   

2.3) Vacinação.

O processo de vacinação ocorre entre duas mudas diferentes se as mudas forem semelhantes. A similaridade das mudas afeta o sucesso da vacinação e é proporcional à média aritmética da distância ponderada. Este operador é semelhante ao de cruzamento e consiste na troca de ramos, apresentando ao algoritmo uma forma adicional de combinar informações entre os agentes. Este operador será apresentado no artigo, mas no código-fonte ele é comentado e os resultados dos testes são apresentados sem sua participação, pois, infelizmente, a vacinação piora os resultados.

3) Cálculo da adaptação das árvores.

4) Plantio de novas mudas no jardim.

As mudas obtidas pelos operadores de cruzamento, ramificação e vacinação representam soluções temporárias (jardim-filho). Nessa etapa, n é a melhor muda (o parâmetro externo do algoritmo) que deve ser selecionada e colocada no jardim, substituindo as n piores árvores do jardim. Observe que a substituição das mudas ocorrerá de qualquer forma, mesmo que elas sejam piores do que as piores árvores do jardim.

Este é um bom momento para revisar o código do SSG, o que nos aproxima cada vez mais do emocionante clímax deste estudo - a revisão dos resultados dos testes.

Assim, cada árvore é convenientemente representada como uma estrutura de jardim, que nos servirá de base para a criação de um jardim florido. Não há nada mais simples nesse algoritmo do que a entidade "árvore", que precisa de apenas dois componentes: coordenadas c [] e o valor de adaptação f.

//——————————————————————————————————————————————————————————————————————————————
struct S_Garden
{
  double c []; //coordinates
  double f;    //fitness
};
//——————————————————————————————————————————————————————————————————————————————

A classe C_AO_SSG do SSG não tem nada de especial, já que tudo foi visto nos algoritmos discutidos anteriormente. Na classe, declararemos membros e métodos para manusear jardins pai e filho, um jardim temporário para que o método de classificação funcione, pois precisaremos classificar os jardins pai e filho. Declararemos o array de coordenadas da melhor solução em geral e o valor do melhor ajuste, bem como variáveis constantes para armazenar parâmetros externos do algoritmo.

//——————————————————————————————————————————————————————————————————————————————
class C_AO_SSG
{
  //============================================================================
  public: double rangeMax  []; //maximum search range
  public: double rangeMin  []; //manimum search range
  public: double rangeStep []; //step search
  public: S_Garden pGarden []; //parent's garden
  public: S_Garden cGarden []; //child's garden
  public: S_Garden gardenT []; //temp garden
  public: double cB        []; //best coordinates
  public: double fB;           //fitness of the best coordinates

  public: void Init (const int    coordinatesP,          //Number of coordinates
                     const int    numberTreesP,          //Number of trees
                     const double seedlingsReplacementP, //Seedlings replacement
                     const double probabMatingOperatorP, //Probability mating operator
                     const double probabBranchOperatorP, //Probability branching operator
                     const double powerBranchOperatorP); //Power branching operator

  public: void Sowing      (int iter);
  public: void Germination ();


  //============================================================================
  private: void   Sorting        (S_Garden &garden []);
  private: double SeInDiSp       (double In, double InMin, double InMax, double Step);
  private: double RNDfromCI      (double Min, double Max);
  private: double Scale          (double In, double InMIN, double InMAX, double OutMIN, double OutMAX,  bool Revers);

  private: double vec [];               //Vector
  private: int    ind [];
  private: double val [];
  private: double r;

  private: bool   sowing;               //Sowing
  private: int    coordinates;          //Coordinates number
  private: int    numberTrees;          //Number of trees
  private: int    seedlingsReplacement; //Seedlings replacement
  private: double probabMatingOperator; //Probability mating operator
  private: double probabBranchOperator; //Probability branching operator
  private: double powerBranchOperator;  //Power branching operator
};
//——————————————————————————————————————————————————————————————————————————————

No método de inicialização Init (), alocamos a memória para os arrays e atribuímos valores às constantes - parâmetros. Como o parâmetro seedlingsReplacementP é definido em frações do tamanho do jardim (de 0,0 a 1,0), responsável pelo número de mudas filhas a serem plantadas no jardim pai, ele deve ser recalculado para obter uma representação inteira do tamanho do jardim. Redefinimos o sinalizador inicial do jardim de mudas e inicializamos a variável de decisão global com o menor valor possível double.

//——————————————————————————————————————————————————————————————————————————————
void C_AO_SSG::Init (const int    coordinatesP,          //Number of coordinates
                     const int    numberTreesP,          //Number of trees
                     const double seedlingsReplacementP, //Seedlings replacement
                     const double probabMatingOperatorP, //Probability mating operator
                     const double probabBranchOperatorP, //Probability branching operator
                     const double powerBranchOperatorP)  //Power branching operator
{
  MathSrand (GetTickCount ());
  sowing = false;
  fB     = -DBL_MAX;

  coordinates    = coordinatesP;
  numberTrees    = numberTreesP;

  if (seedlingsReplacementP >= 1.0)
  {
    seedlingsReplacement = numberTreesP;
  }
  else
  {
    if (seedlingsReplacementP <= 0.0)
    {
      seedlingsReplacement = 1;
    }
    else seedlingsReplacement = int(numberTreesP * seedlingsReplacementP);
  }

  probabMatingOperator = probabMatingOperatorP;
  probabBranchOperator = probabBranchOperatorP;
  powerBranchOperator  = powerBranchOperatorP;

  ArrayResize (rangeMax,  coordinates);
  ArrayResize (rangeMin,  coordinates);
  ArrayResize (rangeStep, coordinates);
  ArrayResize (vec,       coordinates);
  ArrayResize (cB,        coordinates);


  ArrayResize (pGarden,  numberTrees);
  ArrayResize (cGarden,  numberTrees);
  ArrayResize (gardenT,  numberTrees);
  ArrayResize (ind,      numberTrees);
  ArrayResize (val,      numberTrees);

  for (int i = 0; i < numberTrees; i++)
  {
    ArrayResize (pGarden  [i].c, coordinates);
    ArrayResize (cGarden  [i].c, coordinates);
    ArrayResize (gardenT  [i].c, coordinates);
    cGarden [i].f = -DBL_MAX;
  }
}
//——————————————————————————————————————————————————————————————————————————————

Sowing () é o primeiro método público, necessariamente chamado em cada iteração. Na primeira iteração, quando o algoritmo está em execução, espalhamos as mudas pelo jardim (espaço de busca) de forma aleatória com distribuição uniforme. Aqui vemos que as coordenadas são geradas aleatoriamente em um intervalo permissível entre o min e o max dos parâmetros otimizados, verificamos se estão fora do intervalo permissível e, em seguida, ajustamos os valores das coordenadas à etapa de otimização.

Nesse estágio, a adaptação das mudas é mínimo. Definimos o vetor vec[], precisaremos dele para dimensionar os incrementos dos ramos no operador de ramificação e calcular a distância euclidiana máxima possível r no espaço de busca, o que será útil no operador de cruzamento para determinar a probabilidade dependendo da distância entre os pares de árvores.

//the first planting of trees-------------------------------------------------
if (!sowing)
{
  fB = -DBL_MAX;
  r = 0.0;

  for (int t = 0; t < numberTrees; t++)
  {
    for (int c = 0; c < coordinates; c++)
    {
      cGarden [t].c [c] = RNDfromCI (rangeMin [c], rangeMax [c]);
      cGarden [t].c [c] = SeInDiSp (cGarden [t].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
    }

    cGarden [t].f = -DBL_MAX;
  }

  for (int c = 0; c < coordinates; c++)
  {
    vec [c] = rangeMax [c] - rangeMin [c];
    r += vec [c] * vec [c];
  }

  r = sqrt (r);

  return;
}

Depois, no método Sowing(), os operadores de cruzamento, ramificação e vacinação são executados na segunda iteração e nas subsequentes. Esse é o laço principal em que os operadores são executados sequencialmente:

//tree growth-----------------------------------------------------------------
int child, parent;
double rnd;
double ed; //euclidean distance
double eM;
double u;
double temp;

for (int t = 0; t < numberTrees; t++)

Ao executar os operadores, os conceitos de "filho" e "pai" são muito condicionais; na verdade, eles são apenas informações básicas para gerar uma nova muda. A nova muda copia todos os ramos (e os ramos, como lembramos, são os parâmetros otimizados) da árvore filha e, além disso, recebe novos ramos da árvore pai.

Para cada nova muda, duas árvores são selecionadas individualmente do jardim, uma árvore filha e uma árvore mãe, aleatoriamente, com a mesma probabilidade para todas as árvores do jardim, ou seja, todas as árvores podem participar da geração da nova muda com a mesma probabilidade e independentemente de sua adaptabilidade. Assim, filho e pai são simplesmente índices das duas árvores de origem no array do jardim pai.

ed = 0.0;

rnd = RNDfromCI (0.0, numberTrees - 1);
child = (int)MathRound (rnd);
if (child < 0) child = 0;
if (child > numberTrees - 1) child = numberTrees - 1;

rnd = RNDfromCI (0.0, numberTrees - 1);
parent = (int)MathRound (rnd);
if (parent < 0) parent = 0;
if (parent > numberTrees - 1) parent = numberTrees - 1;

if (child == parent) parent++;
if (parent > numberTrees - 1) parent = 0;

ArrayCopy (cGarden [t].c, pGarden [child].c, 0, 0, WHOLE_ARRAY);

O primeiro operador é o de cruzamento (mating). Para executar esse operador sobre uma muda com índice t, precisamos calcular o espaço euclidiano entre as árvores filha e pai com índices child e parent:

//mating operator-----------------------------------------------------------
for (int c = 0; c < coordinates; c++)
{
  temp = pGarden [child].c [c] - pGarden [parent].c [c];
  ed += temp * temp;
}

ed = sqrt (ed);

A distância euclidiana entra na fórmula para calcular a probabilidade de cruzamento por meio da normalização da distância euclidiana máxima possível r no espaço de busca:

eM = 1.0 - (ed / r);

Geramos um número aleatório no intervalo [0,0;1,0] e, se o número resultante estiver dentro da probabilidade calculada eM, realizamos o cruzamento, copiando ramos da árvore principal com o probabMatingOperator para cada ramo. Se a probabilidade eM não for atendida, a muda passará à execução do próximo operador com todos os ramos iniciais da árvore filha.

rnd = RNDfromCI (0.0, 1.0);

if (rnd <= eM)
{
  for (int c = 0; c < coordinates; c++)
  {
    rnd = RNDfromCI (0.0, 1.0);

    if (rnd <= probabMatingOperator) cGarden [t].c [c] = pGarden [parent].c [c];
  }
}

Em seguida, o operador de ramificação é executado. A ramificação possibilita a variação dos ramos - alongamento e encurtamento - e é o principal operador responsável pela criação de ramos de novo comprimento; já os outros operadores desempenham apenas uma função combinatória e não criam novos ramos exclusivos. Para cada ramo, geramos um número aleatório no intervalo [0,0;1,0] e, se probabBranchOperator for atendido, ramificamos, isto é, alteramos o comprimento de um ramo de acordo com a lei de distribuição de Levy abordada aqui.

Como se pode ver, nem todos os ramos de uma muda serão modificados, e eles serão modificados independentemente de o ramo ter sido copiado da árvore pai no operador anterior ou de ser o ramo filho original. Depois de modificar um ramo, verificamos se há valores discrepantes e o alinhamos com o passo de otimização.

//branching operator--------------------------------------------------------
for (int c = 0; c < coordinates; c++)
{
  rnd = RNDfromCI (0.0, 1.0);

  if (rnd < probabBranchOperator)
  {
    double r1 = RNDfromCI (0.0, 1.0);
    r1 = r1 > 0.5 ? 1.0 : -1.0;
    double r2 = RNDfromCI (1.0, 20.0);

    cGarden [t].c [c] = cGarden [t].c [c] + r1 * vec [c] * powerBranchOperator * pow (r2, -2.0);
    cGarden [t].c [c] = SeInDiSp (cGarden [t].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
  }
}

O terceiro operador é o de vacinação. Ele foi concebido pelos autores, aparentemente, como um operador combinatório e deveria ser executado para copiar ramos da árvore pai quando os ramos da árvore filha e da árvore pai divergissem em mais de uma diferença média em todas as árvores. Isso parece muito complicado, mas há pouco uso para esse operador e, portanto, ele é comentado nos arquivos iniciais. Nesse caso, não posso agir como um especialista de última instância e, portanto, admito a possibilidade de que eu tenha entendido mal esse operador.

//vaccinating operator------------------------------------------------------
u = 0.0;
    
for (int c = 0; c < coordinates; c++)
{
  u += fabs (cGarden [t].c [c] - pGarden [parent].c [c]) / vec [c];
}

u /= coordinates;

for (int c = 0; c < coordinates; c++)
{
  if (fabs (cGarden [t].c [c] - pGarden [parent].c [c]) / vec [c] >= u)
  {
    cGarden [t].c [c] = pGarden [parent].c [c];
  }
}

Plantamos mudas, cruzamos e ramificamos, e às vezes até vacinamos. Agora é a vez da última, mas não a última, operação importante do algoritmo - a germinação, ou seja, executamos o segundo método aberto obrigatório Germination () a cada iteração.

Se a primeira iteração chegar ao fim, o que pode ser identificado pelo sinalizador sowing (semeadura), significa que o jardim dos pais está vazio, então plantamos todas as mudas do jardim dos filhos no jardim pai, copiando as árvores jovens uma a uma. Em seguida, classificamos o jardim pai pelo valor de adaptação usando o método Sorting (). E se as árvores estiverem classificadas, a árvore com índice 0 terá a melhor adaptação, e poderemos atualizar a melhor solução global se ela ainda não for a melhor.

if (!sowing)
{
  for (int t = 0; t < numberTrees; t++) pGarden [t] = cGarden [t];

  Sorting (pGarden);

  if (pGarden [0].f > fB)
  {
    fB = pGarden [0].f;

    ArrayCopy (cB, pGarden [0].c, 0, 0, WHOLE_ARRAY);
  }
  
  sowing = true;
  return;
}

Mais adiante no código, só chegaremos à segunda iteração e às subsequentes no algoritmo, porque atribuímos prudentemente ao sinalizador sowing o valor true. Na segunda iteração e nas subsequentes, precisamos classificar o jardim filho antes de transferir as mudas para o jardim pai e substituir as piores árvores. Verificamos que a solução global melhora e, em seguida, copiamos os n melhores exemplares das mudas para o final do jardim pai.

Por fim, só precisamos classificar o jardim pai; os novos membros da comunidade de árvores do jardim podem substituir as árvores das gerações mais antigas para nos deixar satisfeitos com os resultados da solução do problema de otimização.

//planting some part from all child trees-------------------------------------
Sorting (cGarden);

if (cGarden [0].f > fB)
{
  fB = cGarden [0].f;

  ArrayCopy (cB, cGarden [0].c, 0, 0, WHOLE_ARRAY);
}

for (int t = 0; t < seedlingsReplacement; t++) pGarden [numberTrees - seedlingsReplacement + t] = cGarden [t];

Sorting (pGarden);


3. Resultado dos testes

Saída obtida usando o SSG em uma bancada de teste:

2023.03.09 12:50:37.207    Test_AO_SSG (GBPUSD,M1)    C_AO_SSG:50;0.3;0.5;0.4;0.1
2023.03.09 12:50:37.207    Test_AO_SSG (GBPUSD,M1)    =============================
2023.03.09 12:50:45.954    Test_AO_SSG (GBPUSD,M1)    5 Rastrigin's; Func runs 10000 result: 80.7052793572295
2023.03.09 12:50:45.954    Test_AO_SSG (GBPUSD,M1)    Score: 0.99998
2023.03.09 12:50:59.290    Test_AO_SSG (GBPUSD,M1)    25 Rastrigin's; Func runs 10000 result: 77.3972262608643
2023.03.09 12:50:59.290    Test_AO_SSG (GBPUSD,M1)    Score: 0.95900
2023.03.09 12:52:25.368    Test_AO_SSG (GBPUSD,M1)    500 Rastrigin's; Func runs 10000 result: 52.24518909141432
2023.03.09 12:52:25.368    Test_AO_SSG (GBPUSD,M1)    Score: 0.64735
2023.03.09 12:52:25.368    Test_AO_SSG (GBPUSD,M1)    =============================
2023.03.09 12:52:31.419    Test_AO_SSG (GBPUSD,M1)    5 Forest's; Func runs 10000 result: 1.331178589711503
2023.03.09 12:52:31.419    Test_AO_SSG (GBPUSD,M1)    Score: 0.75298
2023.03.09 12:52:42.575    Test_AO_SSG (GBPUSD,M1)    25 Forest's; Func runs 10000 result: 1.019329018074209
2023.03.09 12:52:42.575    Test_AO_SSG (GBPUSD,M1)    Score: 0.57658
2023.03.09 12:53:48.922    Test_AO_SSG (GBPUSD,M1)    500 Forest's; Func runs 10000 result: 0.25410121872226443
2023.03.09 12:53:48.922    Test_AO_SSG (GBPUSD,M1)    Score: 0.14373
2023.03.09 12:53:48.922    Test_AO_SSG (GBPUSD,M1)    =============================
2023.03.09 12:53:57.201    Test_AO_SSG (GBPUSD,M1)    5 Megacity's; Func runs 10000 result: 6.4
2023.03.09 12:53:57.201    Test_AO_SSG (GBPUSD,M1)    Score: 0.53333
2023.03.09 12:54:08.004    Test_AO_SSG (GBPUSD,M1)    25 Megacity's; Func runs 10000 result: 4.504
2023.03.09 12:54:08.004    Test_AO_SSG (GBPUSD,M1)    Score: 0.37533
2023.03.09 12:56:07.061    Test_AO_SSG (GBPUSD,M1)    500 Megacity's; Func runs 10000 result: 1.2336
2023.03.09 12:56:07.061    Test_AO_SSG (GBPUSD,M1)    Score: 0.10280
2023.03.09 12:56:07.061    Test_AO_SSG (GBPUSD,M1)    =============================
2023.03.09 12:56:07.061    Test_AO_SSG (GBPUSD,M1)    All score: 5.09109

Embora o SSG não possua uma quantidade extensa de parâmetros, produto de minha incursão amadora na escrita de algoritmos (os criadores do SSG possuem menos parâmetros), é inegável que eles (os parâmetros) demandam um foco especial e uma discussão cuidadosa sobre seus valores. Nos testes, os seguintes parâmetros foram empregados e, para recordar, em cada um de meus artigos, eu apresento os parâmetros ótimos dos algoritmos, ou seja, aqueles que proporcionam os resultados mais altos possíveis.

C_AO_SSG:50;0,3;0,5;0,4;0,1

O parâmetro input int NumberTrees_P = 50 define a quantidade de árvores no jardim. Eu optei por não fazer experiências com este parâmetro, preferindo usar o valor padrão de meus testes. Com o valor aumentado para 100, o algoritmo exibe um desempenho ainda melhor, mas sua capacidade de escalonamento se reduz, devido ao decréscimo no número permitido de iterações por jardim, considerando seu tamanho. Um jardim com um número elevado de ramos acaba não tendo tempo suficiente para evoluir.

O parâmetro input double SeedlingsReplacement_P = 0,3 se refere à proporção de mudas do jardim filho que serão transferidas para o jardim pai.
O input double ProbabMatingOperator_P = 0,5 diz respeito à probabilidade de cruzamento (ou cópia de ramos da árvore pai), caso a probabilidade, que leva em consideração a distância entre dois pares de árvores, seja atendida.
O input double ProbabBranchOperator_P = 0,4 determina a probabilidade de ramificação (crescimento ou diminuição de ramos), um parâmetro crucial que tem um impacto significativo no desempenho do algoritmo (todos os parâmetros são importantes, na verdade).
O input double PowerBranchOperator_P = 0,1 define a força da ramificação, que é um coeficiente de escala na dimensão dos parâmetros a serem otimizados. Um valor de 1.0 ou mais indica um crescimento dos ramos até as bordas do jardim, enquanto um valor de 0.0 denota a ausência de crescimento dos ramos, resultando em um algoritmo que se degenera em uma ferramenta combinatória simples. (Uma ideia interessante para a aplicação do SSG, que, a propósito, ainda não foi explorada em pesquisas).

Se atentarmos para a animação do funcionamento do algoritmo SSG em funções de teste, percebemos a ausência de padrões definidos de movimento das árvores no jardim. Nota-se apenas um agrupamento discreto nos pontos de extremos locais. No entanto, a alta qualidade da convergência é imediatamente perceptível, especialmente quando comparada a outros algoritmos previamente analisados. Outro ponto de destaque é a consistência na reprodutibilidade dos resultados.


rastrigin

  SSG na função de teste Rastrigin.

forest

  SSG na função de teste Forest.

megacity

  SSG no recurso de teste Megacity.


Vamos discutir os resultados do algoritmo SSG, que certamente fornecem um material rico para análise. O algoritmo de crescimento de árvores se destacou triunfantemente na primeira posição do ranking, deixando os concorrentes em pedaços! O algoritmo não utiliza o conhecimento de adaptação diretamente para modificar as árvores de decisão, a adaptação é utilizada apenas para classificar o jardim filho e o jardim pai. Portanto, é ainda mais surpreendente que o algoritmo tenha conseguido exibir resultados tão notáveis nos testes.

Seria como se um time de pessoas cegas superasse um time de pessoas com visão normal em um desafio de orientação. O algoritmo ultrapassou os demais participantes na tabela em quatro dos seis testes, sem ficar muito atrás nos testes em que não é o líder. O SSG exibiu a vantagem mais impressionante sobre os rivais na função discreta Megacity, superando o concorrente mais próximo, o HS, em quase 60%! Isso demonstra a excelente escalabilidade do algoritmo. Também apresentou o melhor resultado de escalabilidade na função de teste "afiada" do Forest, superando o melhor concorrente neste teste, o ACOm, por quase 30%.

AO

Description

Rastrigin

Rastrigin final

Forest

Forest final

Megacity (discrete)

Megacity final

Final result

10 params (5 F)

50 params (25 F)

1000 params (500 F)

10 params (5 F)

50 params (25 F)

1000 params (500 F)

10 params (5 F)

50 params (25 F)

1000 params (500 F)

SSG

mudas, semeando e crescimento

1,00000

1,00000

0,65914

2,65914

0,70823

0,94522

1,00000

2,65345

0,71532

0,85412

1,00000

2,56944

100,000

HS

busca de harmonia

0,99676

0,95282

0,57048

2,52006

1,00000

0,98931

0,44806

2,43736

1,00000

1,00000

0,41537

2,41537

93,370

ACOm

otimização de colônia de formigas M

0,34611

0,17985

0,20182

0,72778

0,85966

1,00000

0,77362

2,63327

1,00000

0,88484

0,05606

1,94090

66,407

IWO

otimização invasiva de ervas daninhas

0,95828

0,67083

0,35295

1,98207

0,68718

0,46349

0,31773

1,46840

0,75912

0,39732

0,33289

1,48933

61,691

COAm

algoritmo de otimização de cuco M

0,92400

0,46794

0,30792

1,69987

0,55451

0,34034

0,16526

1,06012

0,67153

0,30326

0,17083

1,14561

48,226

FAm

algoritmo de vaga-lumes M

0,59825

0,33980

0,20290

1,14095

0,47632

0,42299

0,49790

1,39721

0,21167

0,25143

0,35258

0,81568

41,042

ABC

colônia artificial de abelhas

0,78170

0,32715

0,24656

1,35541

0,50591

0,21455

0,13344

0,85390

0,47444

0,23609

0,13926

0,84979

37,204

BA

algoritmo do morcego

0,40526

0,63761

1,00000

2,04287

0,15275

0,17477

0,25989

0,58741

0,15329

0,06334

0,17371

0,39034

36,703

GSA

algoritmo de busca gravitacional

0,70167

0,45217

0,00000

1,15384

0,26854

0,36416

0,33204

0,96475

0,51095

0,32436

0,00000

0,83531

35,834

BFO

otimização de forrageamento bacteriano

0,67203

0,30963

0,13988

1,12154

0,35462

0,26623

0,20652

0,82736

0,42336

0,30519

0,18932

0,91786

34,700

MA

algoritmo do macaco

0,33192

0,33451

0,17340

0,83983

0,03684

0,07891

0,08932

0,20508

0,05838

0,00383

0,10720

0,16941

13,185

FSS

busca por cardume de peixes

0,46812

0,25337

0,13383

0,85532

0,06711

0,05013

0,06516

0,18240

0,00000

0,00959

0,08283

0,09242

12,089

PSO

otimização de enxame de partículas

0,20449

0,08200

0,08478

0,37127

0,13192

0,10486

0,21738

0,45415

0,08028

0,02110

0,01957

0,12095

9,696

RND

aleatório

0,16826

0,09743

0,09495

0,36065

0,07413

0,04810

0,04715

0,16938

0,00000

0,00000

0,04922

0,04922

4,916

GWO

otimizador lobo-cinzento

0,00000

0,00000

0,02672

0,02672

0,00000

0,00000

0,00000

0,00000

0,18977

0,03645

0,02557

0,25179

1,000



Conclusões

Características do SSG: não requer diferenciabilidade e continuidade da função de otimização, não há restrições quanto ao tipo de representação e codificações usadas, o algoritmo não usa informações de adaptação de agentes individuais e da melhor solução em geral. Por causa desses recursos, o SSG pode ser facilmente aplicado a diversos problemas de otimização, inclusive os muito complexos; o SSG pode ser recomendado com segurança para ser usado por traders e para aprendizado de máquina. No momento da escrita deste artigo, o SSG é o líder indiscutível em termos de qualidade de convergência, estabilidade de resultados e escalabilidade.

A histograma dos resultados do teste dos algoritmos é mostrado na Figura 2.

chart

Figura 2. Histograma dos resultados finais dos testes dos algoritmos.

Prós e contras do SSG:

Prós:
1. Simplicidade de implementação.
2. Excelente convergência em todos os tipos de funções.
3. Escalabilidade impressionante.

Contras:
1. Muitas opções de personalização, embora as opções sejam intuitivas.

Para cada artigo, eu anexo um arquivo que contém versões atualizadas dos códigos dos algoritmos para todos os artigos anteriores. O artigo é a experiência acumulada do autor e opinião pessoal, as conclusões e julgamentos são baseados em experimentos.


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

Arquivos anexados |
Desenvolvimento de um sistema de negociação baseado no indicador Fibonacci Desenvolvimento de um sistema de negociação baseado no indicador Fibonacci
Esta é a continuação de uma série de artigos nos quais aprendemos como construir sistemas de negociação com base nos indicadores mais populares. Desta vez, cobriremos o indicador Fibonacci. Veremos como escrever um programa baseado nos sinais deste indicador.
Ciência de Dados e Aprendizado de Máquina (Parte 13): Analisando o mercado financeiro usando a análise de componentes principais (PCA) Ciência de Dados e Aprendizado de Máquina (Parte 13): Analisando o mercado financeiro usando a análise de componentes principais (PCA)
Vamos tentar melhorar qualitativamente nossa análise dos mercados financeiros usando a análise de componentes principais (PCA). Aprenderemos como essa técnica pode ajudar a identificar padrões ocultos nos dados, identificar tendências de mercado ocultas e otimizar estratégias de investimento. Neste artigo, veremos como o PCA oferece uma nova perspectiva para a análise de dados financeiros complexos, ajudando-nos a ver informações que não percebemos usando abordagens tradicionais. Veremos se sua aplicação aos dados do mercado financeiro proporciona uma vantagem sobre a concorrência e nos ajuda a ficar um passo à frente.
Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 17): Tiquete e mais tiquetes (I) Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 17): Tiquete e mais tiquetes (I)
Aqui vamos começar a ver como implementar algo realmente bem interessante e curioso. Mas ao mesmo tempo extremamente complicado por conta de algumas questões que muitos confundem. Mas pior do que as confundir, é o fato de que alguns operadores que se dizem profissionais, não fazem ideia a importância de tais conceitos no mercado de capital. Sim, apesar do foco aqui ser programação, entender algumas questões que envolvem operações em mercados, é de extrema valia para o que iremos começar a implementar aqui.
Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 16): Um novo sistema de classes Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 16): Um novo sistema de classes
Precisamos nos organizar melhor. O código está crescendo e se não o organizarmos agora, será impossível fazer isto depois. Então agora vamos dividir para conquistar. O fato de que o MQL5, nos permite usar classes, nos ajudará nesta tarefa. Mas para fazer isto é preciso que você tenha algum conhecimento sobre algumas coisas envolvidas nas classes. E talvez a que mais deixe, aspirantes e iniciantes perdidos seja a herança. Então neste artigo, irei de forma prática e simples como fazer uso de tais mecanismos.