English Русский 中文 Español Deutsch 日本語
preview
Otimização por Quimiotaxia Bacteriana (BCO)

Otimização por Quimiotaxia Bacteriana (BCO)

MetaTrader 5Exemplos |
177 0
Andrey Dik
Andrey Dik

Conteúdo

  1. Introdução
  2. Implementação do algoritmo
  3. Resultados dos testes


Introdução

Na área de otimização, muitos pesquisadores e desenvolvedores se inspiram em processos biológicos observados na natureza, como a evolução, interações sociais ou o comportamento de organismos vivos na busca por alimento. Isso leva ao desenvolvimento de novos e inovadores métodos de otimização. Pesquisas demonstraram que esses métodos geralmente superam abordagens heurísticas clássicas e técnicas baseadas em gradientes, especialmente na resolução de problemas multimodais, não diferenciáveis e discretos. Um desses métodos é o algoritmo de quimiotaxia, inicialmente proposto por Bremermann e seus colegas. Já analisamos anteriormente o algoritmo de otimização por busca alimentar bacteriana (BFO), que modela a reação das bactérias aos quimioatraentes em gradientes de concentração. Bremermann realizou uma análise da quimiotaxia em gradientes tridimensionais e aplicou esse conceito no treinamento de redes neurais. Embora esse algoritmo tenha sido fundamentado nos princípios da quimiotaxia bacteriana, descobertas biológicas mais recentes possibilitaram a criação de um modelo mais detalhado desse processo.

Neste artigo, exploramos essa nova abordagem como uma estratégia de otimização. As principais diferenças entre o novo modelo e o algoritmo tradicional de quimiotaxia são as seguintes:

  1. No novo modelo, as bactérias utilizam informações sobre os valores de concentração.
  2. Elas não continuam se movendo na mesma direção quando há um aumento na concentração de quimioatraentes.

As bactérias são organismos unicelulares, uma das formas de vida mais simples da Terra. Apesar dessa simplicidade, elas são capazes de captar informações do ambiente, se orientar nele e utilizar essas informações de forma eficiente para sobreviver. A resposta das bactérias às mudanças ambientais tem sido objeto de intensas pesquisas nas últimas décadas, atraindo também o interesse de cientistas da área de otimização. Os algoritmos de otimização podem ser vistos como sistemas que coletam informações sobre a paisagem funcional e as utilizam para alcançar o ótimo. A simplicidade do conceito de quimiotaxia bacteriana torna esse tipo de algoritmo um ponto de partida atrativo.

Em diversos estudos, foi estabelecido que as bactérias trocam informações entre si, embora os mecanismos exatos dessa comunicação ainda não sejam totalmente compreendidos. Normalmente, as bactérias são tratadas como indivíduos, e a interação social não é considerada nos modelos. Isso as diferencia dos modelos de interação que descrevem o comportamento de insetos sociais (como formigas, abelhas, vespas ou cupins), que operam como sistemas de inteligência coletiva, abrindo novas possibilidades para a resolução de diferentes problemas.

A adaptação é outro aspecto fundamental da quimiotaxia. As bactérias podem modificar sua sensibilidade a condições químicas constantes, o que lhes permite reagir eficientemente às mudanças no ambiente. Essa capacidade as torna não apenas resistentes, mas também altamente eficazes na busca por recursos.

Neste estudo, os autores se concentraram em modelos microscópicos que consideram a quimiotaxia de bactérias individuais, ao invés dos modelos macroscópicos, que analisam o movimento de colônias. O algoritmo em questão foi desenvolvido por S. D. Müller e P. Koumoutsakos, e suas principais ideias foram apresentadas e publicadas em 2002.


Implementação do algoritmo

A ideia central do algoritmo de otimização por quimiotaxia bacteriana (BCO) é utilizar princípios biológicos observados no comportamento das bactérias para resolver problemas de otimização. Ele modela a forma como as bactérias respondem a gradientes de substâncias químicas no ambiente, permitindo-lhes encontrar condições mais favoráveis para sua sobrevivência. As principais ideias do algoritmo são as seguintes:

  1. O algoritmo descreve o movimento das bactérias como uma sequência de trajetórias retilíneas conectadas por mudanças instantâneas de direção. Cada movimento é caracterizado por velocidade, direção e duração.
  2. A direção da mudança de trajetória da bactéria é determinada por uma distribuição probabilística, permitindo incorporar variações aleatórias no deslocamento.
  3. O algoritmo utiliza informações sobre os gradientes da função para guiar a bactéria em direção à solução ótima, minimizando o número de iterações necessárias para alcançar o objetivo.

O pseudocódigo detalhado do algoritmo de otimização por quimiotaxia bacteriana (BCO) descreve as principais etapas do algoritmo, incluindo a inicialização, o ciclo principal de otimização com as fases de movimento e revisão, além de funções auxiliares.

Inicialização.

1. Definir parâmetros:

  • popSize: tamanho da população (quantidade de bactérias)
  • hs: número de passos para calcular a média da variação
  • T0: temperatura inicial
  • b: parâmetro
  • tau_c: parâmetro
  • v: velocidade

2. Criar um array de bactérias com tamanho popSize

3. Para cada bactéria i de 0 a popSize - 1:

  • Inicializar fPrev com o valor -DBL_MAX
  • Criar um array fHistory de tamanho hs e preenchê-lo com zeros

Ciclo principal de otimização. Repetir até que a condição de parada seja atendida:

Fase de movimento para cada bactéria i de 0 a popSize - 1:

1. Obter o valor atual da função objetivo f_tr

2. Obter o valor anterior da função objetivo f_pr a partir de bacterium[i].fPrev

3. Se f_tr <= f_pr: T = T0

    Caso contrário: calcular b_corr = CalculateCorrectedB (f_tr, f_pr, i)

  • T = T0 * exp (b_corr * (f_tr - f_pr))

4. Gerar tau a partir de uma distribuição exponencial com parâmetro T.

5. Calcular novos ângulos new_angles[] para coords - 1 dimensões: para cada ângulo j de 0 a coords - 2:

  • theta = CalculateRotationAngle ()
  • mu = 62 * (1 - cos(theta)) * π / 180
  • sigma = 26 * (1 - cos(theta)) * π / 180
  • new_angles[j] = número aleatório de uma distribuição gaussiana com parâmetros (mu, mu-π, mu+π)

6. Calcular a nova posição:

  • l = v * tau
  • CalculateNewPosition (l, new_angles, new_position, current_position)

7. Atualizar a posição da bactéria, limitando os valores dentro de rangeMin e rangeMax

8. Atualizar bacterium[i].fPrev com o valor f_tr

Fase de revisão.

1. Atualizar a melhor solução global cB com o valor de aptidão fB

2. Para cada bactéria i de 0 a popSize - 1, atualizar o histórico de valores da função objetivo fHistory:

  • Deslocar todos os valores uma posição para a esquerda
  • Adicionar o valor atual da função objetivo ao final do histórico

Funções auxiliares:

CalculateCorrectedB (f_tr, f_pr, bacteriumIndex)

1. Calcular delta_f_tr = f_tr - f_pr

2. Calcular delta_f_pr = variação média nos últimos hs passos

3. Se |delta_f_pr| < epsilon: Retornar b

   Caso contrário: Retornar b * (1 / (|delta_f_tr / delta_f_pr| + 1) + 1 / (|f_pr| + 1))

CalculateRotationAngle ()

1. Calcular cos_theta = exp (-tau_c / T0)

2. Retornar arccos (cos_theta)

CalculateNewPosition (l, angles, new_position, current_position)

1. Calcular new_position[0] considerando todos os ângulos

2. Para cada coordenada i de 1 a coords - 1:

   Calcular new_position[i] considerando os ângulos correspondentes

3. Aplicar uma direção aleatória (1 ou -1) a cada coordenada

GenerateFromExponentialDistribution (T)

Retornar -T * ln (número aleatório entre 0 e 1)

Agora passamos à implementação do código do algoritmo. Para representar a bactéria como uma solução do problema de otimização, descreveremos a estrutura "S_BCO_Bacterium".

1. Campos da estrutura:

  • fPrev - valor anterior da função objetivo.
  • fHistory[] - array do histórico de valores da função objetivo.

2. Init - o método de inicialização executa as seguintes ações:

  • O tamanho do array "fHistory" é ajustado para "historySize".
  • Todos os elementos do array "fHistory" são inicializados com o valor "0.0".
  • O valor de fPrev é definido como o menor valor possível da função objetivo.

A bactéria é representada como uma estrutura que permite rastrear a variação dos valores da função objetivo ao longo de um número determinado de iterações (movimentos individuais).

//——————————————————————————————————————————————————————————————————————————————
struct S_BCO_Bacterium
{
    double fPrev;        // previous value of the objective function
    double fHistory [];  // history of objective function values

    void Init (int coords, int historySize)
    {
      ArrayResize     (fHistory, historySize);
      ArrayInitialize (fHistory, 0.0);
      fPrev = -DBL_MAX;
    }
};
//——————————————————————————————————————————————————————————————————————————————

Agora descreveremos a classe do algoritmo "C_AO_BCO". Vamos analisá-la por partes.

1. No construtor, os parâmetros externos do algoritmo são inicializados.

2. O método "SetParams" atualiza os valores dos parâmetros a partir do array "params".

3. Os métodos "Moving" e "Revision" são responsáveis pelo movimento das bactérias e pela revisão de suas posições.

4. A classe contém vários métodos privados utilizados para diferentes cálculos relacionados ao algoritmo, como "CalculateAverageDeltaFpr", "CalculateNewAngles", "CalculateNewPosition", "GenerateFromExponentialDistribution", "CalculateCorrectedB", "CalculateRotationAngle" e "RNDdir". Além disso, há o array bacterium, que representa a população de bactérias. Os parâmetros da classe incluem:

  • hs - número de passos para calcular a média da variação.
  • T0 - temperatura inicial.
  • b - parâmetro.
  • tau_c - parâmetro.
  • v - velocidade.
//——————————————————————————————————————————————————————————————————————————————
class C_AO_BCO : public C_AO
{
  public: //--------------------------------------------------------------------
  ~C_AO_BCO () { }
  C_AO_BCO ()
  {
    ao_name = "BCO";
    ao_desc = "Bacterial Chemotaxis Optimization";
    ao_link = "https://www.mql5.com/ru/articles/15711";

    popSize = 50;     // population size (number of bacteria)
    hs      = 10;     // number of steps to calculate average change
    T0      = 1.0;    // initial temperature
    b       = 0.5;    // parameter b
    tau_c   = 1.0;    // parameter tau_c
    v       = 1.0;    // velocity

    ArrayResize (params, 6);

    params [0].name = "popSize"; params [0].val = popSize;
    params [1].name = "hs";      params [1].val = hs;
    params [2].name = "T0";      params [2].val = T0;
    params [3].name = "b";       params [3].val = b;
    params [4].name = "tau_c";   params [4].val = tau_c;
    params [5].name = "v";       params [5].val = v;
  }

  void SetParams ()
  {
    popSize = (int)params [0].val;
    hs      = (int)params [1].val;
    T0      = params      [2].val;
    b       = params      [3].val;
    tau_c   = params      [4].val;
    v       = params      [5].val;
  }

  bool Init (const double &rangeMinP  [], //minimum search range
             const double &rangeMaxP  [], //maximum search range
             const double &rangeStepP [], //step search
             const int     epochsP = 0);  //number of epochs

  void Moving ();
  void Revision ();

  //----------------------------------------------------------------------------
  int    hs;
  double T0;
  double b;
  double tau_c;
  double v;

  S_BCO_Bacterium bacterium [];

  private: //-------------------------------------------------------------------
  double CalculateAverageDeltaFpr            (int bacteriumIndex);
  void   CalculateNewAngles                  (double &angles []);
  void   CalculateNewPosition                (double l, const double &angles [], double &new_position [], const double &current_position []);
  double GenerateFromExponentialDistribution (double T);
  double CalculateCorrectedB                 (double f_tr, double f_pr, int bacteriumIndex);
  double CalculateRotationAngle              ();
  int    RNDdir                              ();
};

//——————————————————————————————————————————————————————————————————————————————
bool C_AO_BCO::Init (const double &rangeMinP  [], //minimum search range
                     const double &rangeMaxP  [], //maximum search range
                     const double &rangeStepP [], //step search
                     const int     epochsP = 0)   //number of epochs
{
  if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false;

  //----------------------------------------------------------------------------
  ArrayResize (bacterium, popSize);
  for (int i = 0; i < popSize; i++) bacterium [i].Init (coords, hs);

  return true;
}
//——————————————————————————————————————————————————————————————————————————————

O método "Moving" da classe "C_AO_BCO" é responsável pelo deslocamento das bactérias no espaço de busca. Vamos analisar seu funcionamento:

1. Se "revision" for "false", significa que as bactérias estão em suas posições iniciais.

  • Para cada bactéria "a[i]", são geradas coordenadas aleatórias dentro do intervalo definido.
  • A função "u.RNDfromCI" gera um valor aleatório, e "u.SeInDiSp" o ajusta de acordo com o passo especificado.

2. O ciclo principal de movimentação, caso "revision" seja "true". Esse método executa a lógica principal do deslocamento das bactérias. Definição da temperatura "T":

  • Se o valor atual da função "f_tr" for melhor ou igual ao anterior "f_pr", a temperatura inicial "T0" é utilizada.
  • Caso contrário, a temperatura é ajustada pela função "CalculateCorrectedB", que considera a diferença entre os valores atual e anterior da função de aptidão. 
  • Geração do tempo de deslocamento "tau": um tempo de deslocamento é gerado utilizando uma distribuição exponencial.
  • Os novos ângulos de deslocamento e a nova posição são calculados com base no comprimento do deslocamento "l" e nos novos ângulos.
  • A nova posição é ajustada conforme o intervalo e o passo definidos.
  • Ao final do ciclo, o valor anterior da função de aptidão de cada bactéria é atualizado.

O método "Moving" implementa a lógica principal do deslocamento das bactérias no espaço de busca, adaptando seu comportamento com base nos resultados das iterações anteriores. Ele incorpora elementos aleatórios e mecanismos adaptativos na busca pela solução ótima.

//——————————————————————————————————————————————————————————————————————————————
void C_AO_BCO::Moving ()
{
  //----------------------------------------------------------------------------
  if (!revision)
  {
    for (int i = 0; i < popSize; i++)
    {
      for (int c = 0; c < coords; c++)
      {
        a [i].c [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]);
        a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
      }
    }

    revision = true;
    return;
  }

  //----------------------------------------------------------------------------
  for (int i = 0; i < popSize; i++)
  {
    double f_tr = a [i].f;
    double f_pr = bacterium [i].fPrev;

    double T;
    
    if (f_tr <= f_pr)
    {
      T = T0;
    }
    else
    {
      double b_corr = CalculateCorrectedB (f_tr, f_pr, i);
      
      T = T0 * exp (b_corr * (f_tr - f_pr));
    }

    double tau = GenerateFromExponentialDistribution (T);

    double new_angles [];
    ArrayResize (new_angles, coords - 1);
    CalculateNewAngles (new_angles);

    double l = v * tau;
    double new_position [];
    ArrayResize (new_position, coords);
    CalculateNewPosition (l, new_angles, new_position, a [i].c);

    for (int c = 0; c < coords; c++)
    {
      a [i].c [c] = u.SeInDiSp (new_position [c], rangeMin [c], rangeMax [c], rangeStep [c]);
    }

    bacterium [i].fPrev = a [i].f;
  }
}
//——————————————————————————————————————————————————————————————————————————————

O método "Revision" da classe "C_AO_BCO" é responsável por atualizar as informações sobre as melhores soluções encontradas e o histórico de valores da função de aptidão para cada bactéria.

1. A variável "ind" é inicializada com o valor "-1". Ela será utilizada para armazenar o índice da bactéria que apresentar o melhor valor da função.

2. Busca do melhor valor da função:

  • O método percorre todas as bactérias da população "popSize" e procura aquela cujo valor da função "f" seja superior ao valor atual "fB".
  • Se uma bactéria com um valor de função superior for encontrada, o valor "fB" é atualizado e o respectivo índice é armazenado em "ind".

3. Se uma bactéria com índice "ind" foi encontrada (ou seja, "ind" não é igual a "-1"), suas coordenadas são copiadas para o array "cB", que representa a melhor solução encontrada até o momento.

4. O histórico de valores da função é atualizado para cada bactéria. O método percorre cada elemento do array "fHistory", deslocando os valores uma posição para a esquerda para liberar espaço para um novo valor. Ao final de cada iteração, o último elemento do array "fHistory" recebe o valor atual de aptidão "a[i].f" de cada bactéria.

Dessa forma, o método "Revision" executa duas funções principais:

  • Atualiza o melhor valor da função de aptidão e suas respectivas coordenadas.
  • Atualiza o histórico de valores da função de aptidão para cada bactéria, permitindo rastrear mudanças no desempenho ao longo de suas movimentações. 
//——————————————————————————————————————————————————————————————————————————————
void C_AO_BCO::Revision ()
{
  int ind = -1;

  for (int i = 0; i < popSize; i++)
  {
    if (a [i].f > fB)
    {
      fB = a [i].f;
      ind = i;
    }
  }

  if (ind != -1)
  {
    ArrayCopy (cB, a [ind].c, 0, 0, WHOLE_ARRAY);
  }

  for (int i = 0; i < popSize; i++)
  {
    for (int j = 1; j < hs; j++)
    {
      bacterium [i].fHistory [j - 1] = bacterium [i].fHistory [j];
    }

    bacterium [i].fHistory [hs - 1] = a [i].f;
  }
}
//——————————————————————————————————————————————————————————————————————————————

O método "CalculateAverageDeltaFpr" da classe "C_AO_BCO" é utilizado para calcular a variação média dos valores da função de aptidão (delta) para uma bactéria específica entre suas posições consecutivas, com base no histórico de valores.

//——————————————————————————————————————————————————————————————————————————————
double C_AO_BCO::CalculateAverageDeltaFpr (int bacteriumIndex)
{
  double sum = 0;

  for (int i = 1; i < hs; i++)
  {
    sum += bacterium [bacteriumIndex].fHistory [i] - bacterium [bacteriumIndex].fHistory [i - 1];
  }

  return sum / (hs - 1);
}
//——————————————————————————————————————————————————————————————————————————————

O método "CalculateNewAngles" da classe "C_AO_BCO" calcula novos ângulos com base na lógica relacionada à rotação e distribuição, executando as seguintes etapas:

  • Percorre o array de novos ângulos e, para cada ângulo, calcula um novo valor.
  • Utiliza parâmetros dependentes do cosseno do ângulo para gerar valores baseados em uma distribuição gaussiana.
//——————————————————————————————————————————————————————————————————————————————
void C_AO_BCO::CalculateNewAngles (double &angles [])
{
  for (int i = 0; i < coords - 1; i++)
  {
    double theta = CalculateRotationAngle ();
    double mu    = 62 * (1 - MathCos (theta)) * M_PI / 180.0;
    double sigma = 26 * (1 - MathCos (theta)) * M_PI / 180.0;

    angles [i] = u.GaussDistribution (mu, mu - M_PI, mu + M_PI, 8);
  }
}
//——————————————————————————————————————————————————————————————————————————————

O método "CalculateNewPosition" da classe "C_AO_BCO" é responsável por calcular novas coordenadas de posição com base nas coordenadas atuais, nos ângulos e no parâmetro "l". 

1. Parâmetros de entrada do método:

  • l - coeficiente que influencia a alteração da posição.
  • angles[] - array de ângulos utilizado para calcular a nova posição.
  • new_position[] - array onde serão armazenadas as novas coordenadas.
  • current_position[] - array das coordenadas atuais.

2. A primeira coordenada "new_position[0]" é calculada somando a coordenada atual "current_position[0]" ao produto de "l" pela diferença entre "rangeMax[0]" e "rangeMin[0]".

3. Em seguida, a primeira coordenada é multiplicada pelos cossenos dos ângulos do array "angles", do primeiro até o penúltimo.

4. O resultado é multiplicado pelo valor retornado pela função "RNDdir()", que gera uma direção aleatória "-1" ou "1".

5. Para cada coordenada seguinte "new_position[i]", onde "i" varia de "1" até "coords - 2", a nova posição é calculada com base na posição atual e no seno do ângulo correspondente.

6. Cada nova coordenada também é multiplicada pelos cossenos dos ângulos, do índice atual "i" até o penúltimo.

7. A direção aleatória é aplicada às demais coordenadas, e o resultado também é multiplicado pelo valor retornado por "RNDdir()".

8. Para a última coordenada, caso o número de coordenadas seja maior que 1, a nova posição "new_position[coords - 1]" é calculada com base na posição atual e no seno do último ângulo.

Dessa forma, o método "CalculateNewPosition" realiza as seguintes ações:

  • Calcula novas coordenadas com base nas coordenadas atuais e nos ângulos.
  • Considera a influência da direção aleatória em cada coordenada.
  • Aplica funções trigonométricas (seno e cosseno) para levar em conta os ângulos.

Esse método é utilizado para modelar o deslocamento das bactérias no espaço, levando em consideração sua posição atual e os ângulos definidos.

//——————————————————————————————————————————————————————————————————————————————
void C_AO_BCO::CalculateNewPosition (double l, const double &angles [], double &new_position [], const double &current_position [])
{
  new_position [0] = current_position [0] + l * (rangeMax [0] - rangeMin [0]);
 
  for (int k = 0; k < coords - 1; k++)
  {
    new_position [0] *= MathCos (angles [k]);
  }
 
  new_position [0] *= RNDdir ();

  for (int i = 1; i < coords - 1; i++)
  {
    new_position [i] = current_position [i] + l * MathSin (angles [i - 1]) * (rangeMax [0] - rangeMin [0]);
    
    for (int k = i; k < coords - 1; k++)
    {
      new_position [i] *= MathCos (angles [k]);
    }
    
    new_position [i] *= RNDdir ();
  }

  if (coords > 1)
  {
    new_position [coords - 1] = current_position [coords - 1] + l * MathSin (angles [coords - 2]);
  }
 
}
//——————————————————————————————————————————————————————————————————————————————

Agora, descreveremos brevemente o método "RNDdir" da classe "C_AO_BCO", que gera uma direção aleatória assumindo um dos dois valores possíveis: -1 ou 1.

//——————————————————————————————————————————————————————————————————————————————
int C_AO_BCO::RNDdir ()
{
  if (u.RNDbool () < 0.5) return -1;
 
  return 1;
}
//——————————————————————————————————————————————————————————————————————————————

Segue uma visão geral dos métodos da classe "C_AO_BCO":

O método "GenerateFromExponentialDistribution" executa:

  • A geração de um número aleatório segundo uma distribuição exponencial com parâmetro "T".
  • Em seguida, utiliza um número aleatório no intervalo (0,1), calcula seu logaritmo e multiplica por "-T".
  • Assim, obtém-se um número aleatório distribuído de acordo com a lei exponencial.

 O método "CalculateCorrectedB" realiza:

  • O cálculo do valor corrigido de "b" com base na diferença entre "f_tr" e "f_pr" (os valores atual e anterior da função de aptidão).
  • Calcula a diferença entre "f_tr" e "f_pr", obtém a média para a bactéria e, dependendo desse valor, retorna o valor corrigido de "b".
//——————————————————————————————————————————————————————————————————————————————
double C_AO_BCO::GenerateFromExponentialDistribution (double T)
{
  return -T * MathLog (u.RNDprobab ());
}

double C_AO_BCO::CalculateCorrectedB (double f_tr, double f_pr, int bacteriumIndex)
{
  double delta_f_tr = f_tr - f_pr;
  double delta_f_pr = CalculateAverageDeltaFpr (bacteriumIndex);

  if (MathAbs (delta_f_pr) < DBL_EPSILON)
  {
    return b;
  }
  else
  {
    return b * (1 / (MathAbs (delta_f_tr / delta_f_pr) + 1) + 1 / (MathAbs (f_pr) + 1));
  }
}
//——————————————————————————————————————————————————————————————————————————————

O último método a ser abordado é "CalculateRotationAngle" da classe "C_AO_BCO". Esse método calcula o ângulo de rotação com base nos parâmetros fornecidos e retorna o valor em radianos.

//——————————————————————————————————————————————————————————————————————————————
double C_AO_BCO::CalculateRotationAngle ()
{
  double cos_theta = MathExp (-tau_c / T0);
  return MathArccos (cos_theta);
}
//——————————————————————————————————————————————————————————————————————————————

Agora, testamos a versão original do algoritmo e analisamos os resultados:

BCO|Otimização da quimiotaxia bacteriana|50.0|10.0|1.0|0.5|1.0|1.0|1.0|
=============================
5 Hilly's; Func runs: 10000; result: 0.42924491510564006
25 Hilly's; Func runs: 10000; result: 0.282259866768426
500 Hilly's; Func runs: 10000; result: 0.2515386629014219
=============================
5 Forest's; Func runs: 10000; result: 0.2476662231845009
25 Forest's; Func runs: 10000; result: 0.17824381036550777
500 Forest's; Func runs: 10000; result: 0.15324081202657283
=============================
5 Megacity's; Func runs: 10000; result: 0.2430769230769231
25 Megacity's; Func runs: 10000; result: 0.11415384615384619
500 Megacity's; Func runs: 10000; result: 0.09444615384615461
=============================
All score: 1.99387 (22.15%)

Os resultados obtidos são tão fracos que não faz sentido incluí-los na tabela de classificações. Durante a implementação do algoritmo e a escrita do código com base na descrição textual dos autores, precisei corrigir algumas fórmulas. Algumas não faziam sentido matemático (por exemplo, a divisão de um número por um vetor), enquanto outras resultavam em valores extremamente pequenos ou extremamente grandes, incompatíveis com as dimensões do problema. Durante esse processo, imprimi os resultados das funções para cada uma das fórmulas apresentadas e analisei seu comportamento. Dessa forma, o algoritmo não pode funcionar corretamente conforme descrito pelos autores.

Além disso, as operações com ângulos, nas quais é utilizado o modelo de distribuição normal, podem ser significativamente simplificadas. O significado físico dessas operações pode ser reduzido ao fato de que, em cada coordenada, basta aplicar uma nova variação a partir da posição atual da bactéria. Portanto, decidi desenvolver minha própria implementação da quimiotaxia bacteriana, buscando manter ao máximo os conceitos e ideias centrais do algoritmo original.

Agora, apresento o pseudocódigo da minha versão do algoritmo:

Inicialização:
1. Criar uma população com popSize bactérias.
2. Para cada bactéria i:
    Inicializar o histórico de valores da função objetivo f_history[i] com tamanho hs e valores nulos.
    Definir o valor inicial f_prev[i] = -DBL_MAX.

Ciclo principal:
Enquanto a condição de parada não for atendida:

1. Se for a primeira iteração:
   Para cada bactéria i:
     Inicializar aleatoriamente a posição x[i] no espaço de busca.
     x[i][j] ∈ [x_min[j], x_max[j]] para cada coordenada j.

2. Caso contrário:
   Para cada bactéria i:
     a. Calcular a variação média da função objetivo:
        delta_ave[i] = (1 / (hs - 1)) * sum(f_history[i][k] - f_history[i][k-1] para k em 1..hs-1) + epsilon.
     
     b. Calcular a variação relativa da aptidão:
        delta[i] = 1 - |f(x[i]) - f_prev[i]| / delta_ave[i].
        delta[i] = max(delta[i], 0.0001).
     
     c. Para cada coordenada j:
        Com probabilidade 0.5:
          dist[j] = (x_max[j] - x_min[j]) * delta[i].
          x[i][j] = N(x[i][j], x[i][j] - dist[j], x[i][j] + dist[j]).
          Limitar x[i][j] ao intervalo [x_min[j], x_max[j]].
        Caso contrário:
          x[i][j] = x_best[j].
     
     d. Atualizar f_prev[i] = f(x[i]).

3. Avaliar a função objetivo f(x[i]) para cada bactéria.

4. Atualizar a melhor solução encontrada:
   Se existir um i tal que f(x[i]) > f(x_best), então x_best = x[i].

5. Atualizar o histórico de valores da função objetivo para cada bactéria:
   Deslocar os valores em f_history[i].
   Adicionar o novo valor: f_history[i][hs - 1] = f(x[i]).

Conclusão:
Retornar a melhor solução encontrada x_best.

Onde:

  • x[i] - posição da i-ésima bactéria.
  • f(x) - função objetivo.
  • hs - tamanho do histórico.
  • epsilon - pequena constante para evitar divisão por zero.
  • N(μ, a, b) - distribuição normal truncada com média μ e limites [a, b].

Dessa forma, meu pseudocódigo modificado reflete a estrutura e a lógica central do algoritmo BCO. Vamos destacar os principais pontos:

  • O algoritmo utiliza uma população de bactérias, onde cada uma representa uma solução potencial.
  • Em cada iteração, as bactérias se deslocam no espaço de busca utilizando informações sobre seus resultados anteriores.
  • O movimento é baseado na variação relativa da função objetivo, permitindo que o algoritmo se adapte ao cenário de otimização.
  • O algoritmo mantém um histórico dos valores da função objetivo para cada bactéria, usado no cálculo da variação média.
  • Com certa probabilidade, a bactéria se move em direção à melhor solução conhecida, em vez de explorar uma nova região (um conceito análogo à herança de características na genética).
  • Após cada deslocamento, as novas posições são avaliadas e a melhor solução encontrada é atualizada.

A versão BCOm combina elementos de busca local (movimento de bactérias individuais) e busca global (compartilhamento de informações através da melhor solução conhecida).

Agora, vejamos as principais diferenças entre as duas versões do algoritmo. Primeiramente, o mecanismo de movimento das bactérias foi simplificado, eliminando a complexa estrutura baseada em ângulos de rotação e temperatura. Em segundo lugar, a nova versão depende menos do histórico de mudanças para ajustar o comportamento das bactérias, adotando uma abordagem mais direta na atualização de suas posições. Em vez de utilizar uma combinação de distribuições exponencial e normal, o novo algoritmo utiliza apenas a distribuição normal. Como consequência dessas mudanças, o número de parâmetros necessários para controlar o comportamento do algoritmo foi reduzido a um único parâmetro (além do tamanho da população), o que simplifica a configuração e o gerenciamento do processo de otimização.

No geral, o novo pseudocódigo propõe cálculos mais simples em cada etapa da otimização, o que deve impactar positivamente na velocidade de execução do algoritmo. Na versão original, eram realizados cálculos repetidos de seno e cosseno dos ângulos de rotação para cada coordenada de cada bactéria. Minha abordagem equilibra de forma diferente a exploração de novas regiões do espaço de soluções e a utilização das melhores soluções já encontradas.

Essas mudanças na lógica resultaram em um algoritmo mais simples e, presumivelmente (até que os testes sejam concluídos), mais eficiente. Agora passamos para a implementação do código.

A estrutura "S_BCO_Bacterium", que representa cada bactéria, permanecerá inalterada. Ela é responsável por armazenar informações sobre a bactéria e o histórico de valores da função objetivo.

No método "Init" da classe "C_AO_BCOm", que lida com a inicialização dos parâmetros do algoritmo, será adicionada a definição da distância permitida para o deslocamento em cada coordenada.

Dessa forma, o método "Init" da classe "C_AO_BCOm" é responsável por configurar os parâmetros do algoritmo de otimização. Ele verifica as condições padrão de inicialização, cria os arrays necessários e os preenche com valores apropriados.

//——————————————————————————————————————————————————————————————————————————————
bool C_AO_BCOm::Init (const double &rangeMinP  [], //minimum search range
                      const double &rangeMaxP  [], //maximum search range
                      const double &rangeStepP [], //step search
                      const int     epochsP = 0)   //number of epochs
{
  if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false;

  //----------------------------------------------------------------------------
  ArrayResize (bacterium, popSize);
  for (int i = 0; i < popSize; i++) bacterium [i].Init (coords, hs);

  ArrayResize (allowedDispl, coords);
  for (int c = 0; c < coords; c++) allowedDispl [c] = rangeMax [c] - rangeMin [c];

  return true;
}
//——————————————————————————————————————————————————————————————————————————————

Agora, analisamos o método "Moving" da classe "C_AO_BCOm", que gerencia o deslocamento das bactérias no espaço de busca. Vamos analisá-la por partes.

1. Na primeira iteração, as ações do método permanecem inalteradas: as coordenadas das bactérias são inicializadas com valores aleatórios dentro do intervalo definido.

2. No algoritmo principal de movimentação, são declaradas variáveis para armazenar valores como "Δ", "ΔAve" e "dist", e, para cada indivíduo da população:

  • O valor médio "ΔAve" é calculado utilizando a função "CalculateAverageDeltaFpr(i)".
  • O valor relativo "Δ" é computado e utilizado para determinar a intensidade da alteração da posição das bactérias.
  • Se "Δ" for muito pequeno, ele é ajustado para 0.0001.

3. Alteração das coordenadas:

  • Para cada coordenada, uma probabilidade aleatória de 50% é avaliada.
  • Se a condição for atendida, a distância "dist" é calculada com base em "allowedDispl[c]" e "Δ".
  • O novo valor de "x" é determinado utilizando a função "GaussDistribution", considerando os limites "xMin" e "xMax".
  • Se "x" ultrapassar os limites do intervalo, ele é ajustado com a função "RNDfromCI".
  • Finalmente, o novo valor da coordenada é armazenado considerando o passo "rangeStep".

4. O valor anterior da função de aptidão "f" é armazenado para cada indivíduo no array "bacterium". São utilizados os arrays "a" e "bacterium". As funções "RNDfromCI", "SeInDiSp" e "GaussDistribution" são responsáveis pela geração de números aleatórios e distribuições, além da normalização dos valores das coordenadas.

Dessa forma, a função "Moving()" é responsável pela inicialização e atualização das posições dos indivíduos na população dentro do algoritmo. Esta função utiliza probabilidades e distribuições aleatórias para controlar a movimentação das bactérias. No entanto, a principal diferença em relação à versão original é a implementação mais simples e eficiente do gradiente da função de aptidão. Quando a diferença na adaptação da bactéria diminui em relação ao passo anterior, sua movimentação acelera. Por outro lado, ao encontrar um ambiente favorável, a bactéria desacelera. Isso contradiz o comportamento natural das bactérias, que tendem a entrar em estado de anabiose quando se deparam com um ambiente hostil.

//——————————————————————————————————————————————————————————————————————————————
void C_AO_BCOm::Moving ()
{
  //----------------------------------------------------------------------------
  if (!revision)
  {
    for (int i = 0; i < popSize; i++)
    {
      for (int c = 0; c < coords; c++)
      {
        a [i].c [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]);
        a [i].c [c] = u.SeInDiSp  (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
      }
    }

    revision = true;
    return;
  }

  //----------------------------------------------------------------------------
  double x    = 0.0;
  double xMin = 0.0;
  double xMax = 0.0;
  double Δ    = 0.0;
  double ΔAve = 0.0;
  double dist = 0.0;

  for (int i = 0; i < popSize; i++)
  {
    ΔAve = CalculateAverageDeltaFpr (i) + DBL_EPSILON;

    Δ = fabs (a [i].f - bacterium [i].fPrev) / ΔAve;

    Δ = 1.0 - Δ;

    if (Δ < 0.0001) Δ = 0.0001;

    for (int c = 0; c < coords; c++)
    {
      if (u.RNDprobab () < 0.5)
      {
        dist = allowedDispl [c] * Δ;

        x    = a [i].c [c];
        xMin = x - dist;
        xMax = x + dist;

        x = u.GaussDistribution (x, xMin, xMax, 8);

        if (x > rangeMax [c]) x = u.RNDfromCI (xMin, rangeMax [c]);
        if (x < rangeMin [c]) x = u.RNDfromCI (rangeMin [c], xMax);

        a [i].c [c] = u.SeInDiSp (x, rangeMin [c], rangeMax [c], rangeStep [c]);
      }
      else a [i].c [c] = cB [c];
    }

    bacterium [i].fPrev = a [i].f;
  }
}
//——————————————————————————————————————————————————————————————————————————————

O método "Revision()" da classe "C_AO_BCOm", responsável por atualizar as informações da população e o histórico de valores da função objetivo, permaneceu inalterado.


Resultados dos testes

Agora analisamos o desempenho da nova versão do algoritmo BCOm:

BCO|Bacterial Chemotaxis Optimization|50.0|10.0|
=============================
5 Hilly's; Func runs: 10000; result: 0.759526049526603
25 Hilly's; Func runs: 10000; result: 0.6226756163411526
500 Hilly's; Func runs: 10000; result: 0.31483373090540534
=============================
5 Forest's; Func runs: 10000; result: 0.8937814268120954
25 Forest's; Func runs: 10000; result: 0.6133909133246214
500 Forest's; Func runs: 10000; result: 0.22541842289630293
=============================
5 Megacity's; Func runs: 10000; result: 0.653846153846154
25 Megacity's; Func runs: 10000; result: 0.42092307692307684
500 Megacity's; Func runs: 10000; result: 0.14435384615384755
=============================
All score: 4.64875 (51.65%)

Provavelmente, a experiência acumulada e a análise crítica das informações disponíveis sobre a versão original foram determinantes para que a nova versão do algoritmo demonstrasse um desempenho mais robusto na prática do que a versão original.

Ao visualizar o funcionamento do algoritmo BCOm, é possível observar uma boa exploração das regiões significativas do hiperespaço, o que indica uma alta capacidade de investigação da superfície da função a ser otimizada.

Hilly

BCOm na função de teste Hilly

Forest

BCOm na função de teste Forest

Megacity

BCOm na função de teste Megacity

Nos testes, o algoritmo conquistou uma posição estável em 17º lugar no ranking geral dos algoritmos de otimização.

AO Description HillyHilly final ForestForest final Megacity (discrete)Megacity final Final result % of MAX
10 p (5 F)50 p (25 F)1000 p (500 F)10 p (5 F)50 p (25 F)1000 p (500 F)10 p (5 F)50 p (25 F)1000 p (500 F)
1ANSacross neighbourhood search0,949480,847760,438572,235811,000000,923340,399882,323230,709230,634770,230911,574916,13468,15
2CLAcode lock algorithm0,953450,871070,375902,200420,989420,917090,316422,222940,796920,693850,193031,683806,10767,86
3AMOmanimal migration optimization M0,903580,843170,462842,209590,990010,924360,465982,380340,567690,591320,237731,396755,98766,52
4(P+O)ES(P+O) evolution strategies0,922560,881010,400212,203790,977500,874900,319452,171850,673850,629850,186341,490035,86665,17
5CTAcomet tail algorithm0,953460,863190,277702,094350,997940,857400,339492,194840,887690,564310,105121,557125,84664,96
6SDSmstochastic diffusion search M0,930660,854450,394762,179880,999830,892440,196192,088460,723330,611000,106701,441035,70963,44
7ESGevolution of social groups0,999060,796540,350562,146161,000000,828630,131021,959650,823330,553000,047251,423585,52961,44
8SIAsimulated isotropic annealing0,957840,842640,414652,215130,982390,795860,205071,983320,686670,493000,090531,270205,46960,76
9ACSartificial cooperative search0,755470,747440,304071,806981,000000,888610,224132,112740,690770,481850,133221,305835,22658,06
10ASOanarchy society optimization0,848720,746460,314651,909830,961480,791500,238031,991010,570770,540620,166141,277525,17857,54
11TSEAturtle shell evolution algorithm0,967980,644800,296721,909490,994490,619810,227081,841390,690770,426460,135981,253225,00455,60
12DEdifferential evolution0,950440,616740,303081,870260,953170,788960,166521,908650,786670,360330,029531,176534,95555,06
13CROchemical reaction optimisation0,946290,661120,298531,905930,879060,584220,211461,674730,758460,426460,126861,311784,89254,36
14BSAbird swarm algorithm0,893060,649000,262501,804550,924200,711210,249391,884790,693850,326150,100121,120124,80953,44
15HSharmony search0,865090,687820,325271,878180,999990,680020,095901,775920,620000,422670,054581,097254,75152,79
16SSGsaplings sowing and growing0,778390,649250,395431,823080,859730,624670,174291,658690,646670,441330,105981,193984,67651,95
17BCOmbacterial chemotaxis optimization M0,759530,622680,314831,697040,893780,613390,225421,732590,653850,420920,144351,219124,64951,65
18(PO)ES(PO) evolution strategies0,790250,626470,429351,846060,876160,609430,195911,681510,590000,379330,113221,082554,61051,22
19TSmtabu search M0,877950,614310,291041,783300,928850,518440,190541,637830,610770,382150,121571,114494,53650,40
20BSObrain storm optimization0,937360,576160,296881,810410,931310,558660,235371,725340,552310,290770,119140,962224,49849,98
21WOAmwale optimization algorithm M0,845210,562980,262631,670810,931000,522780,163651,617430,663080,411380,113571,188034,47649,74
22AEFAartificial electric field algorithm0,877000,617530,252351,746880,927290,726980,180641,834900,666150,116310,095080,877544,45949,55
23ACOmant colony optimization M0,881900,661270,303771,846930,858730,586800,150511,596040,596670,373330,024720,994724,43849,31
24BFO-GAbacterial foraging optimization - ga0,891500,551110,315291,757900,969820,396120,063051,428990,726670,275000,035251,036924,22446,93
25ABHAartificial bee hive algorithm0,841310,542270,263041,646630,878580,477790,171811,528180,509230,338770,103970,951974,12745,85
26ASBOadaptive social behavior optimization0,763310,492530,326191,582020,795460,400350,260971,456770,264620,171690,182000,618313,65740,63
27MECmind evolutionary computation0,695330,533760,326611,555690,724640,330360,071981,126980,525000,220000,041980,786983,47038,55
28IWOinvasive weed optimization0,726790,522560,331231,580580,707560,339550,074841,121960,423330,230670,046170,700173,40337,81
29Micro-AISmicro artificial immune system0,795470,519220,308611,623300,729560,368790,093981,192330,376670,158670,028020,563353,37937,54
30COAmcuckoo optimization algorithm M0,758200,486520,313691,558410,740540,280510,055991,077040,505000,174670,033800,713473,34937,21
31SDOmspiral dynamics optimization M0,746010,446230,296871,489120,702040,346780,109441,158260,428330,167670,036630,632633,28036,44
32NMmNelder-Mead method M0,738070,505980,313421,557470,636740,283020,082211,001970,446670,186670,040280,673623,23335,92
33FAmfirefly algorithm M0,586340,472280,322761,381380,684670,374390,109081,168140,286670,164670,047220,498553,04833,87
34GSAgravitational search algorithm0,647570,491970,300621,440160,539620,363530,099451,002600,326670,122000,019170,467832,91132,34
35BFObacterial foraging optimization0,611710,432700,313181,357590,544100,215110,056760,815970,421670,138000,031950,591622,76530,72
36ABCartificial bee colony0,633770,424020,308921,366710,551030,218740,056230,826000,340000,142000,031020,513022,70630,06
37BAbat algorithm0,597610,459110,352421,409150,403210,193130,071750,668100,210000,101000,035170,346172,42326,93
38AAAalgae adaptive algorithm0,500070,320400,255251,075720,370210,222840,167850,760890,278460,148000,097550,524022,36126,23
39SAsimulated annealing0,557870,421770,315491,295130,349980,152590,050230,552800,311670,100330,028830,440832,28925,43
40IWDmintelligent water drops M0,545010,378970,301241,225220,461040,147040,043690,651770,258330,097000,023080,378422,25525,06
41PSOparticle swarm optimisation0,597260,369230,299281,265770,372370,163240,070100,605720,256670,080000,021570,358232,23024,77
42Boidsboids algorithm0,433400,305810,254250,993460,357180,201600,157080,715860,278460,142770,098340,519572,22924,77
43MAmonkey algorithm0,591070,426810,318161,336040,311380,140690,066120,518190,228330,085670,027900,341902,19624,40
44SFLshuffled frog-leaping0,539250,358160,298091,195510,371410,114270,040510,526180,271670,086670,024020,382352,10423,38
45FSSfish school search0,556690,399920,311721,268330,310090,118890,045690,474670,211670,076330,024880,312882,05622,84




Considerações finais

Foram apresentadas as versões original e modificada (BCOm) do algoritmo BCO. A versão original, reconstruída com base em informações de fontes abertas, apresentou cálculos trigonométricos complexos, além de propriedades de busca fracas e falhas matemáticas. Por isso, foi necessário repensar completamente o algoritmo, realizar uma análise aprofundada da estratégia de busca e criar uma nova versão modificada. As mudanças na lógica do algoritmo resultaram em cálculos mais simples a cada etapa da otimização, o que impactou positivamente a velocidade de execução do código. O novo algoritmo também equilibra de maneira diferente a exploração de novos espaços de soluções e a utilização de soluções já encontradas.

Embora essa abordagem atualizada contrarie um pouco o comportamento natural das bactérias, ela demonstrou alta eficiência na prática. A visualização de seu funcionamento mostrou sua capacidade de explorar profundamente regiões significativas do hiperespaço, evidenciando seu aprimoramento na capacidade de busca. Como resultado, a nova versão do algoritmo mostrou-se mais poderosa e eficaz que a original.


Tab

Figura 1. Graduação de cores dos algoritmos para os respectivos testes. Os resultados destacados em branco são maiores ou iguais a  0.99

chart

Figura 2. Histograma dos resultados dos testes dos algoritmos (na escala de 0 a 100, quanto maior, melhor,

onde 100 é o resultado teórico máximo possível, e o arquivo contém um script para cálculo da tabela de classificação)


Prós e contras do algoritmo BCOm:

Prós:

  1. Rápido.
  2. Auto-adaptável.
  3. Boa escalabilidade.
  4. Apenas um parâmetro externo.

Contras:

  1. Alta dispersão de resultados em funções de pequena dimensionalidade.

O artigo inclui um arquivo com as versões atualizadas dos códigos dos algoritmos. O autor do artigo não se responsabiliza pela precisão absoluta na descrição dos algoritmos canônicos, pois muitos deles foram modificados para melhorar as capacidades de busca. As conclusões e opiniões apresentadas nos artigos são baseadas nos resultados dos experimentos realizados.

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

Arquivos anexados |
BCOm.zip (36.35 KB)
Combine Estratégias de Análise Fundamental e Técnica no MQL5 Para Iniciantes Combine Estratégias de Análise Fundamental e Técnica no MQL5 Para Iniciantes
Neste artigo, discutiremos como integrar princípios de seguimento de tendência e análise fundamental em um único Expert Advisor para construir uma estratégia mais robusta. Este artigo demonstrará como qualquer pessoa pode facilmente começar a construir algoritmos de trading personalizados usando MQL5.
Construindo um Modelo de Restrição de Tendência de Candlestick (Parte 6): Integração Completa Construindo um Modelo de Restrição de Tendência de Candlestick (Parte 6): Integração Completa
Um dos principais desafios é gerenciar várias janelas de gráfico do mesmo par, executando o mesmo programa com recursos diferentes. Vamos discutir como consolidar diversas integrações em um único programa principal. Além disso, compartilharemos informações sobre como configurar o programa para registrar mensagens em um diário e comentar sobre a transmissão bem-sucedida de sinais na interface do gráfico. Encontre mais informações neste artigo, à medida que avançamos na série.
Ciência de Dados e ML (Parte 27): Redes Neurais Convolucionais (CNNs) em Bots de Trading no MetaTrader 5 — Vale a Pena? Ciência de Dados e ML (Parte 27): Redes Neurais Convolucionais (CNNs) em Bots de Trading no MetaTrader 5 — Vale a Pena?
As Redes Neurais Convolucionais (CNNs) são renomadas por sua capacidade de detectar padrões em imagens e vídeos, com aplicações em diversos campos. Neste artigo, exploramos o potencial das CNNs para identificar padrões valiosos nos mercados financeiros e gerar sinais de trading eficazes para bots de negociação no MetaTrader 5. Vamos descobrir como essa técnica de aprendizado profundo pode ser aproveitada para decisões de trading mais inteligentes.
Redes neurais em trading: Análise de nuvem de pontos (PointNet) Redes neurais em trading: Análise de nuvem de pontos (PointNet)
A análise direta da nuvem de pontos permite evitar um aumento excessivo no volume de dados e aprimorar a eficiência dos modelos em tarefas de classificação e segmentação. Abordagens deste tipo demonstram um bom desempenho e resistência a perturbações nos dados brutos.