English Русский 中文 Deutsch 日本語 Português 한국어 Français Italiano Türkçe
preview
Algoritmos de optimización de la población: Búsqueda de bancos de peces (Fish School Search — FSS)

Algoritmos de optimización de la población: Búsqueda de bancos de peces (Fish School Search — FSS)

MetaTrader 5Ejemplos | 29 marzo 2023, 10:28
475 0
Andrey Dik
Andrey Dik

Contenido:

1. Introducción
2. Descripción del algoritmo
3. Resultados de las pruebas


1. Introducción

Entendemos por «acumulaciones» de peces cualquier grupo de peces reunidos en un lugar. Dichas acumulaciones pueden ser estructuradas o no estructuradas. Las acumulaciones no estructuradas pueden constar de individuos de especies y tamaños mixtos, reunidos aleatoriamente cerca de los recursos locales, como fuentes de alimento o lugares de anidación.

Mientras tanto, un grupo de peces que se mantiene unido por razones sociales ya supone un banco. La mayoría de ellos se encuentran en la misma fase del ciclo de vida, están en contacto activo entre sí y en cualquier momento pueden mostrar actividades biológicamente activas y organizadas que resultan útiles para los miembros del grupo.
En contraste con un individuo separado, el estilo de vida gregario supone un compromiso entre la ventaja de vivir en grupo, lo cual implica una mayor protección contra los depredadores, y una mayor competencia en la obtención de alimentos.

Los peces forman bancos en la naturaleza de varias formas. Por regla general, prefieren bancos más grandes, formados únicamente por individuos de su propia especie. Cualquier miembro del banco que destaque por su apariencia, o se distinga de alguna manera, se convertirá en un objetivo principal para los depredadores. Este hecho explica por qué los peces prefieren bancos de individuos idénticos. Así, se determina la completa homogeneidad del banco.

Un banco se organizará de manera bastante estricta cuando los peces nadan sincrónicamente a la misma velocidad y en la misma dirección: esto se debe a que los peces no solo de la misma especie, sino de la misma edad y tamaño, se mueven a cierta distancia unos de otros. Los bancos de peces pueden realizar maniobras complejas, como si tuvieran inteligencia de grupo y una mente común.
Las sutilezas de la formación de bancos aún están lejos de comprenderse por completo, especialmente lo relacionado con el movimiento y las formas de alimentarse de los peces.

Se han propuesto muchas hipótesis para explicar el comportamiento del banco, incluida una mejor orientación, la sincronización para la caza, la confusión de los depredadores y un menor riesgo de ser atrapado. Los peces en bancos parecen compartir información entre ellos, controlando el comportamiento de los demás desde una distancia próxima. El comportamiento de alimentación de los peces estimula rápidamente una búsqueda activa de alimento en otros especímenes. Los peces en el banco nadan en esbeltas falanges, a menudo haciendo rápidos ascensos y descensos y girando alrededor de su eje, mientras cambian la forma del banco, evitando chocarse entre sí. Para realizar este tipo de maniobras, se necesita un sistema de respuesta muy rápido. El estilo de vida gregario implica que los peces tienen sistemas sensoriales capaces de reaccionar instantáneamente a pequeños cambios en su posición con respecto a su compañero más próximo.

Para crear una imagen más completa, se usan modelos matemáticos de dicho comportamiento de banco. Los modelos matemáticos más comunes suponen que los animales individuales de un banco siguen tres reglas básicas:

  1. Moverse en la misma dirección que el compañero más cercano
  2. Mantenerse cerca de los compañeros
  3. Evitar colisiones con los otros especímenes


La cuestión referente a la elección de la dirección en que nadar por parte de los peces en el banco, sigue sin resolverse. A la hora de migrar, parece que la mayoría de los miembros del banco saben hacia dónde dirigirse.  Si bien todos los miembros del banco son igualmente conscientes de la disponibilidad de alimentos, sí que existen ciertos líderes en el grupo que son más audaces que sus otros congéneres. Este comportamiento de banco llevó a muchos investigadores a crear no solo un modelo matemático, sino también algorítmico para resolver varios problemas de optimización.


2. Descripción del algoritmo

La búsqueda de banco de peces (FSS) es una subfamilia de algoritmos de inteligencia de enjambre que pertenece a la clase de algoritmos metaheurísticos propuestos por Bastos Filho y Lima Neto en 2008 y publicados por primera vez en 2009. En el FSS, los agentes simples se denominan peces, y cada pez tiene un peso que representa el «éxito» logrado durante la búsqueda. Los valores y variaciones de los pesos afectarán a los movimientos individuales y colectivos. Los mecanismos integrados de alimentación y acción coordinada obligan al banco a moverse en la dirección de un gradiente positivo para aumentar de peso y encontrar los mejores lugares a nivel local y global. El FSS fue desarrollado para problemas de optimización continua en espacios de búsqueda multimodal. Esto también llevó a otros investigadores a proponer opciones para resolver otros problemas, como la optimización en problemas binarios, o la optimización multiobjetivo.

En el algoritmo FSS, se puede imaginar de forma simple que los peces nadan en un acuario condicional cuyas paredes son los límites del área de definición de la función analizada. El peso del pez es una medida del éxito en la búsqueda de alimento (soluciones). Además, desempeña el papel de la memoria del pez. La presencia de peso en los peces es la idea principal del algoritmo y la diferencia respecto al enjambre de partículas. Esta característica del algoritmo FSS elimina la necesidad de encontrar y corregir a nivel global las mejores soluciones, como sucede en un enjambre de partículas.

Los operadores del algoritmo FSS se agrupan en dos grupos:
-el operador de alimentación formaliza el éxito de las áreas de estudio
-los operadores de natación implementan los algoritmos de migración para los peces individuales y el banco en su conjunto

El operador de alimentación supone el cálculo del peso del pez. La idea básica es hacer que el pez "nade" hacia un gradiente positivo para "comer" y "ganar peso". Colectivamente, los peces más pesados tienen una mayor influencia en el proceso de búsqueda general, lo cual hace que el centro de masa del banco de peces se desplace hacia mejores ubicaciones en el espacio de búsqueda a lo largo de las iteraciones. El incremento de peso en una iteración dada será proporcional a la diferencia normalizada en los valores de la función de aptitud, es decir:

fishes [f].weight = fishes [f].weight + (fishes [f].delta_fitness / max_delta_fitness);

donde:

  • weight - peso del pez
  • delta_fitness - diferencia entre los valores de la función de aptitud
  • max_delta_fitness - valor máximo de la diferencia de las funciones de aptitud entre todos los peces

Los operadores de natación se dividen en tres tipos según el tipo de movimiento:

-individual;

-instintivo-colectivo;

-colectivo-volitivo;

La natación individual puede interpretarse como una búsqueda local en las proximidades de la posición actual del pez. El vector de movimiento de cada individuo se dirige aleatoriamente y posee un valor diferente.

fishes [f].new_position [d] = fishes [f].current_position [d] + step_ind [d] * r;

donde:

  • new_position - nueva posición en la coordenada correspondiente
  • posición_actual - posición actual en la coordenada correspondiente
  • step_ind - paso de movimiento individual, calculado como

initial_step_ind * (rangeMax [d] - rangeMin [d]);

donde:

  • initial_step_ind - parámetro del algoritmo para el movimiento individual
  • rangeMax y rangeMin - rangos de valores de los argumentos a optimizar
  • r - número aleatorio [-1.0;1.0]

La natación individual se muestra de forma esquemática en la figura 1.

individual

Figura 1. Natación individual. El vector de movimiento de cada pez estará dirigido en una dirección aleatoria y tendrá un valor escalar diferente.

Después de la natación individual, se mide la función de aptitud. Si como resultado del movimiento individual, la posición del pez no ha mejorado, consideramos que ese pez en particular no ha realizado ningún movimiento y permanece en su lugar. Es decir, solo aquellos peces que hayan mejorado sus funciones físicas se desplazarán a una nueva posición.

Después de completar la natación individual, se ejecutará el operador de movimiento instintivo-colectivo. Veamos primero la figura 2.

collect

Figura 2. Natación instintiva - colectiva. El movimiento se caracteriza para todos los peces según el mismo vector de dirección y magnitud en relación con el centro de masa G.

Instintivamente, el movimiento colectivo sirve para corregir la posición general del banco de peces, considerando el cambio en la función de aptitud de cada pez en la iteración anterior. Las nuevas coordenadas se calcularán así:

fishes [f].new_position [d] = fishes [f].current_position [d] + collective_instinct [d];

donde:

  • new_position - nueva posición del pez en la coordenada correspondiente
  • current_position - posición actual del pez en la coordenada correspondiente
  • collective_instinct - magnitud del movimiento en la coordenada correspondiente, calculada como:

collective_instinct [d] = fishes [f].delta_position [d] * fishes [f].delta_fitness;

donde:

  • delta_position - diferencia entre las coordenadas actual y el anterior, obtenidas en la etapa de nado individual
  • delta_fitness - cambio de fitness - funciones de la posición actual y la anterior en la etapa de nado individual

El nado instintivo-colectivo formaliza el movimiento sincrónico grupal de un banco de peces hacia un nuevo lugar, facilitando la búsqueda de nuevos lugares para alimentarse, mientras que el movimiento individual permite mejorar la situación local.

A continuación, analizaremos el nado colectivo-volitivo. Este, a su vez, se divide en dos tipos:

- el desplazamiento desde el centro de masa - si la mejora en la posición del banco en su conjunto no se ha producido en comparación con la posición anterior, mientras que los peces se extienden hacia los lados, simbolizando una mayor búsqueda de alimento (figura 3)

- desplazamiento hacia el centro de masa, si se ha producido la mejora. Luego, los peces se desplazarán hacia el centro de masa, comprimiendo el anillo y simbolizando el ataque a la presa. De forma algorítmica, esto implica mejorar la solución del problema de optimización (figura 4).

col vol out

Figura 3. Nado colectivo - volitivo. Los vectores de dirección están dirigidos desde el centro de masa G.

col vol in

Figura 4. Nado colectivo - volitivo. Los vectores de dirección están dirigidos hacia el centro de masa G.


Para el cálculo del nado colectivo - volitivo, se introduce el concepto de centro de masa. Partiendo de aquí, la fórmula para calcular el nado colectivo - volitivo se verá así:

fishes [f].new_position [d] = pos + (((pos - barycenter [d]) * step_vol [d] * r) * search_operator);

donde:

  • pos - es la misma current_position
  • search_operator - 1 si el movimiento anterior ha producido una mejora de posición, y -1 si no

  • step_vol [d] - paso de movimiento colectivo, calculado como:

step_vol [d] = initial_step_vol * (rangeMax [d] - rangeMin [d]);

donde:

  • initial_step_vol - parámetro del algoritmo para el movimiento colectivo

  • barycenter [d] - centro de masa, calculado como la suma de los pesos de los peces multiplicada por la coordenada:

barycenter [d] += fishes [f].weight * fishes [f].current_position [d];

y dividido por el peso total del banco de peces:

barycenter [d] /= current_total_weight;

El pseudocódigo del algoritmo tendrá el aspecto que sigue:

1) inicialización de las posiciones de los peces con números aleatorios

2) movimientos individuales

3) cálculo de la función de aptitud

4) redefinición de los pesos para cada pez

5) movimiento instintivo-colectivo

6) movimiento colectivo-volitivo

7) recálculo del peso total

8) cálculo de la función de aptitud

9) repetición desde el paso 2) hasta que se cumpla el criterio de parada

Schema FSS

Figura 5. Esquema de bloques del algoritmo FSS.

Comenzaremos describiendo el código.

La unidad lógica más simple del algoritmo, como podemos suponer, es la estructura que describe al pez. Como tendremos que inicializar el pez varias veces, será recomendable "poner a cero" esta estructura (debido a su tamaño relativamente grande) en el método especial Init(), lo cual nos permitirá optimizar ligeramente el código. Esta incluye los arrays de coordenadas para la posición actual, la nueva posición y la diferencia de coordenadas desde el último movimiento. Por defecto, el peso variable se aplica igual a 1000 unidades aleatorias; en principio, esta cifra puede ser cualquiera. El pez también se caracteriza por el valor de aptitud actual, el valor de aptitud anterior y la diferencia entre ambos. En el método Init(), la aptitud se inicializará con un número, el número double mínimo posible; por lo tanto, la diferencia de aptitud será cero, ya que el pez aún no se ha movido.

//——————————————————————————————————————————————————————————————————————————————
struct S_Fish
{
  void Init (int dimensions)
  {
    ArrayResize (current_position, dimensions);
    ArrayResize (new_position,     dimensions);
    ArrayResize (delta_position,   dimensions);
    weight        = 1000.0;
    fitness       = -DBL_MAX;
    delta_fitness = 0.0;
  }

  double current_position [];
  double new_position     [];
  double delta_position   [];
  double weight;
  double fitness;
  double new_fitness;
  double delta_fitness;
};
//——————————————————————————————————————————————————————————————————————————————

A continuación, declararemos la clase C_AO_FSS: un banco de peces que representará matemáticamente el modelo de comunidad natural. No hay nada inusual aquí. También tenemos dos métodos públicos necesarios para la interacción con el programa de usuario: los rangos de valores de la función optimizada necesarios para tener en cuenta las coordenadas de los peces y la interacción en un banco, así como los arrays.

En el método público de la clase de inicialización Init (), deberemos restablecer las variables a sus valores originales, y también asignar memoria a los arrays. Preste especial atención a las variables init y swimmingRegime. El hecho es que, según el pseudocódigo que hemos visto, el cálculo de la función de aptitud se realizará dos veces, la primera vez después del movimiento individual, la segunda vez después de los dos tipos de movimiento colectivo; esto contradice el esquema de vinculación algoritmo-aplicación adoptado por nosotros, en el que en cada iteración debe haber una secuencia: primer_método -> cálculo_de_las_funciones_de_aptitud -> segundo_método. Estas dos variables servirán precisamente para arreglar esto y modificar la secuencia de acciones en el algoritmo canónico. La variable init al comienzo del algoritmo deberá ser false. Se trata de una bandera que indica que es necesario inicializar el pez, calcular la función de aptitud y realizar el movimiento nuevamente, ya que la lógica completa del algoritmo usa la diferencia entre el valor actual y el anterior de las coordenadas, así como la función de aptitud; de no hacer esto, no habríamos podido obtener la diferencia de los valores. La segunda bandera importante es swimmingRegime. Esta nos permite redefinir el orden de llamada a los métodos que describen el movimiento de los peces para que coincida con nuestro esquema. El valor 1 se corresponderá con la llamada del nado "individual", de lo contrario, con la secuencia de movimientos grupales que hemos analizado anteriormente.

//——————————————————————————————————————————————————————————————————————————————
void C_AO_FSS::Init (const int    dimensionsP,
                     const int    fishSchSizeP,
                     const double initial_step_indP,
                     const double initial_step_volP)
{
  MathSrand (GetTickCount ());
  init = false;
  swimmingRegime = 1;

  dimensions     = dimensionsP;

  ArrayResize (rangeMax,  dimensions);
  ArrayResize (rangeMin,  dimensions);
  ArrayResize (rangeStep, dimensions);

  num_of_individuos = fishSchSizeP;

  ArrayResize (fishes, num_of_individuos);

  for (int i = 0; i < num_of_individuos; i++)
  {
    fishes [i].Init (dimensions);
  }

  global_best = -DBL_MAX;
  ArrayResize (global_best_position, dimensions);

  total_weight     = num_of_individuos;

  initial_step_ind = initial_step_indP;
  ArrayResize (step_ind, dimensions);

  initial_step_vol = initial_step_volP;
  ArrayResize (step_vol, dimensions);

  ArrayResize (collective_instinct, dimensions);
  ArrayResize (barycenter, dimensions);
}
//——————————————————————————————————————————————————————————————————————————————

El primer método público llamado en cada iteración, Swimming(), determinará la secuencia de llamadas a los movimientos de peces individuales y grupales. Si llamamos al método por primera vez, se inicializarán los pasos del movimiento individual y grupal como parte del rango posible en la coordenada correspondiente usando los dos parámetros del algoritmo initial_step_ind e initial_step_vol. Algunos autores recomiendan establecer los valores en 0,01 y 0,001, respectivamente. Sin embargo, no hemos obtenido buenos resultados con estos valores, así que usamos 0.1 y 0.8. Además, en la primera ejecución del algoritmo, el valor actual de las coordenadas de posición de los peces se inicializará con números aleatorios en el rango de parámetros optimizados. En las llamadas subsiguientes al método, los tipos de movimiento correspondientes se llamarán según la bandera swimmingRegime.

//——————————————————————————————————————————————————————————————————————————————
void C_AO_FSS::Swimming (int i)
{
  //----------------------------------------------------------------------------
  if (!init)
  {
    global_best    = -DBL_MAX;
    swimmingRegime = 1;

    for (int d = 0; d < dimensions; d++)
    {
      step_ind [d] = initial_step_ind * (rangeMax [d] - rangeMin [d]);
      step_vol [d] = initial_step_vol * (rangeMax [d] - rangeMin [d]);
    }

    for (int f = 0; f < num_of_individuos; f++)
    {
      fishes [f].Init (dimensions);

      for (int d = 0; d < dimensions; d++)
      {
        fishes [f].new_position [d] = RNDfromCI (rangeMin [d], rangeMax [d]);
        fishes [f].new_position [d] = SeInDiSp (fishes [f].new_position [d], rangeMin [d], rangeMax [d], rangeStep [d]);
      }
    }
  }
  //----------------------------------------------------------------------------
  else
  {
    switch (swimmingRegime)
    {
    case 1:
      apply_individual_movement ();            //individual movement
      break;
    default:
      apply_instintive_collective_movement (); //instinctively-collective movement
      apply_collective_volitive_movement ();   //collective-volitional movement
      update_total_weight ();                  //recalculate the total weight
      break;
    }
  }
}
//——————————————————————————————————————————————————————————————————————————————

El segundo método público llamado en cada iteración, Regrouping(), está destinado principalmente a determinar el orden de las llamadas a los métodos de nado individual, colectivo - instintivo, colectivo - volitivo. La funcionalidad adicional del método para garantizar el orden de las llamadas será la bandera swimmingRegime, que tomará los valores 1 y 2. Necesitaremos esto para redefinir el orden de llamada a los métodos de movimiento de peces en la versión clásica para el esquema adoptado por nosotros: primer_método_abierto -> cálculo_de _las_funciones_de_aptitud -> segundo_método_abierto. Dependiendo de la bandera Init, si el algoritmo se encuentra en la primera iteración, almacenaremos la posición actual de las coordenadas y el valor de la función de aptitud para el cálculo posterior de sus diferencias, ya que se supone que el método se llamará después del cálculo de la función de aptitud. Si la bandera Init indica que el algoritmo está en la segunda iteración y posteriores, tras llevarse a cabo un movimiento individual, deberemos obtener la diferencia entre los valores actuales y anteriores de la función de aptitud, así como la diferencia entre las coordenadas de las posiciones actuales y anteriores. Las diferencias se calcularán bajo la condición de que la posición haya mejorado, de lo contrario, consideramos que no ha habido movimiento, restableceremos los valores del peso del pez al estado inicial, y las diferencias en los movimientos y las funciones de aptitud se tomarán iguales a cero. Luego actualizaremos de inmediato la mejor solución, si se alcanza llamando al método updates_optimal_solution() y aplicaremos la alimentación de los peces con el método apply_feeding(). Si la bandera SwimmingRegime no es igual a 1, significará que se han aplicado movimientos colectivos - instintivos y colectivos - volitivos; después de su ejecución, estableceremos swimmingRegime en 1 y el siguiente será un movimiento individual.

//——————————————————————————————————————————————————————————————————————————————
void C_AO_FSS::Regrouping ()
{
  //----------------------------------------------------------------------------
  if (swimmingRegime == 1
  {
    if (!init)
    {
      for (int f = 0; f < num_of_individuos; f++)
      {
        ArrayCopy (fishes [f].current_position, fishes [f].new_position, 0, 0, WHOLE_ARRAY);
        ArrayInitialize (fishes [f].delta_position, 0.0);

        fishes [f].fitness = fishes [f].new_fitness;
        fishes [f].delta_fitness = 0.0;
      }

      init = true;
      return;
    }

    for (int f = 0; f < num_of_individuos; f++)
    {
      //remember the best position for the fish
      if (fishes [f].new_fitness > fishes [f].fitness)
      {
        fishes [f].delta_fitness = fishes [f].new_fitness - fishes [f].fitness; //fabs
        fishes [f].fitness = fishes [f].new_fitness;

        for (int d = 0; d < dimensions; d++)
        {
          fishes [f].delta_position [d] = fishes [f].new_position [d] - fishes [f].current_position [d];
        }

        ArrayCopy (fishes [f].current_position, fishes [f].new_position, 0, 0, WHOLE_ARRAY);
      }
      else
      {
        ArrayInitialize (fishes [f].delta_position, 0.0);
        fishes [f].delta_fitness = 0.0;
      }
    }

    swimmingRegime = 2;
    updates_optimal_solution ();
    apply_feeding ();
    return;
  }

  //----------------------------------------------------------------------------
  swimmingRegime = 1;
  updates_optimal_solution ();
}
//——————————————————————————————————————————————————————————————————————————————

El siguiente método privado simple se utilizará para actualizar los mejores resultados del algoritmo, si se alcanzan. Para hacer esto, simplemente compararemos los valores de la función de aptitud de cada pez con los globales. Si es mejor, lo guardaremos.

//——————————————————————————————————————————————————————————————————————————————
//update the best overall solution
void C_AO_FSS::updates_optimal_solution ()
{
  for (int f = 0; f < num_of_individuos; f++)
  {
    if (fishes [f].fitness > global_best)
    {
      global_best = fishes [f].fitness;
      ArrayCopy (global_best_position, fishes [f].current_position, 0, 0, WHOLE_ARRAY);
    }
  }
}
//——————————————————————————————————————————————————————————————————————————————

El método privado que posibilita el nado individual, en principio, lo analizamos en la fórmula anterior, y aquí todo es simple: añadiremos a las coordenadas actuales de cada pez el paso individual multiplicado por un número aleatorio en el rango de -1.0 a 1.0, lo cual nos dará un incremento tanto en direcciones positivas como negativas. Si se ha producido una salida del rango de valores de los parámetros optimizados, asignaremos el valor del límite a la coordenada.

//——————————————————————————————————————————————————————————————————————————————
void C_AO_FSS::apply_individual_movement ()
{
  //move the fish to new places-----------------------------------------------
  double r = 0.0;

  for (int f = 0; f < num_of_individuos; f++)
  {
    for (int d = 0; d < dimensions; d++)
    {
      r = RNDfromCI (-1.0, 1.0);

      fishes [f].new_position [d] = fishes [f].current_position [d] + step_ind [d] * r;
      fishes [f].new_position [d] = SeInDiSp (fishes [f].new_position [d], rangeMin [d], rangeMax [d], rangeStep [d]);
    }
  }
}
//——————————————————————————————————————————————————————————————————————————————

El método de alimentación no debería resultar difícil de entender, el peso del pez, de hecho, se determinará como el cociente de la diferencia de la función de aptitud del pez en sí y el valor máximo de la diferencia entre todo el banco de peces. No obstante, podría suceder que la diferencia máxima entre todos los peces resulte igual a cero. La descripción de la versión clásica del algoritmo dice que el peso de los peces siempre debe ser positivo y, en general, solo se consideran los casos en que la función de aptitud toma solo valores positivos, pero no hemos encontrado sentido práctico en este requisito, y debemos admitir que el peso de los peces puede tomar valores negativos, por lo tanto, si la diferencia máxima de la función de aptitud (en este valor debemos normalizar el peso de cada pez) es cero entre todos los peces, entonces el peso de los peces se tomará igual a 1.

//——————————————————————————————————————————————————————————————————————————————
void C_AO_FSS::apply_feeding ()
{
  double max_delta_fitness = -DBL_MAX;

  //find the maximum weight among fish
  for (int f = 0; f < num_of_individuos; f++)
  {
    if (fishes [f].delta_fitness > max_delta_fitness) max_delta_fitness = fishes [f].delta_fitness;
  }

  //feed the fish
  for (int f = 0; f < num_of_individuos; f++)
  {
    if (max_delta_fitness != 0.0)
    {
      fishes [f].weight = fishes [f].weight + (fishes [f].delta_fitness / max_delta_fitness);
    }
    else fishes [f].weight = 1;
  }
}
//——————————————————————————————————————————————————————————————————————————————

El método privado de movimiento instintivo-colectivo consiste en cambiar las coordenadas de cada pez incrementándolas en el valor del instinto colectivo, que a su vez no será más que el producto de la diferencia de las posiciones de la iteración actual y la anterior por la diferencia de la función de aptitud lograda en movimientos anteriores. Aquí, al salirse de los límites de los parámetros optimizados, asignaremos los valores de los límites.

//——————————————————————————————————————————————————————————————————————————————
void C_AO_FSS::apply_instintive_collective_movement ()
{
  double sum_delta_fitness = 0.0;
  for (int f = 0; f < num_of_individuos; f++)
  {
    sum_delta_fitness += fishes [f].delta_fitness;
  }

  for (int f = 0; f < num_of_individuos; f++)
  {
    for (int d = 0; d < dimensions; d++)
    {
      collective_instinct [d] = fishes [f].delta_position [d] * fishes [f].delta_fitness;

      if (sum_delta_fitness != 0.0)
      {
        collective_instinct [d] /= sum_delta_fitness;
      }

      fishes [f].new_position [d] = fishes [f].current_position [d] + collective_instinct [d];
      fishes [f].new_position [d] = SeInDiSp (fishes [f].new_position [d], rangeMin [d], rangeMax [d], rangeStep [d]);
    }
  }
}
//——————————————————————————————————————————————————————————————————————————————

Luego calcularemos el método privado de nado colectivo-volitivo, en el que se calcula el centro de masa del banco de peces, así como el peso total actual. Si el peso total del banco ha aumentado, los peces comenzarán a moverse hacia el centro de masa, de lo contrario, se alejarán del mismo. Ya analizamos la fórmula con detalle anteriormente. El peso total del banco de peces se actualizará en un método especial para ello, que discutiremos a continuación.

//——————————————————————————————————————————————————————————————————————————————
void C_AO_FSS::apply_collective_volitive_movement ()
{
  //----------------------------------------------------------------------------
  double current_total_weight = 0.0;

  for (int f = 0; f < num_of_individuos; f++)
  {
    current_total_weight += fishes [f].weight;
  }

  ArrayInitialize (barycenter, 0.0);

  for (int f = 0; f < num_of_individuos; f++)
  {
    for (int d = 0; d < dimensions; d++)
    {
      barycenter [d] += fishes [f].weight * fishes [f].current_position [d];
    }
  }

  for (int d = 0; d < dimensions; d++)
  {
    barycenter [d] /= current_total_weight;
  }

  //----------------------------------------------------------------------------
  double search_operator = current_total_weight > total_weight ? 1.0 : -1.0;
  double r   = 0.0;
  double pos = 0.0;

  for (int f = 0; f < num_of_individuos; f++)
  {
    for (int d = 0; d < dimensions; d++)
    {
      r = RNDfromCI (0.0, 1.0);
      pos = fishes [f].current_position [d];

      fishes [f].new_position [d] = pos + (((pos - barycenter [d]) * step_vol [d] * r) * search_operator);
      fishes [f].new_position [d] = SeInDiSp (fishes [f].new_position [d], rangeMin [d], rangeMax [d], rangeStep [d]);
    }
  }
}
//——————————————————————————————————————————————————————————————————————————————

En realidad, este es el propio método encargado de actualizar el peso total de los peces. Aquí todo será simple: tomaremos el peso de cada pez y lo sumaremos.

//——————————————————————————————————————————————————————————————————————————————
void C_AO_FSS::update_total_weight ()
{
  total_weight = 0.0;

  for (int f = 0; f < num_of_individuos; f++)
  {
    total_weight += fishes [f].weight;
  }
}
//——————————————————————————————————————————————————————————————————————————————


3. Resultados de las pruebas

Vamos a pasar a la parte final y más interesante: los resultados. El algoritmo contiene trucos interesantes, como la ausencia de la necesidad de considerar el óptimo global, la introducción de los conceptos de centro de masa y movimiento instintivo-volitivo, que implican convergencia o dispersión desde el centro del banco, dependiendo de si se mejoraba la posición en conjunto. Todo ello permitía esperar un comportamiento original del algoritmo sobre las funciones estudiadas.

En principio, la originalidad reside en el comportamiento en el área de búsqueda del espacio; se observan dispersiones de peces en partes separadas del mismo, como ya observamos en el algoritmo de colonia de abejas, aunque la consideración detallada de las fórmulas y el principio de funcionamiento del FSS no implica la dispersión del banco en grupos separados. En general, el algoritmo ha mostrado un rendimiento débil, superando solo ligeramente al algoritmo de PSO en el resultado general.

Si consideramos las pruebas individuales, ha logrado destacar en algunos FSS. Entonces, en la función Skin suave, el algoritmo FSS ha superado incluso al algoritmo de lobo gris, mostrando buenos (pero no los mejores) resultados, lo cual podemos explicar fácilmente: el algoritmo usa el gradiente de la superficie de la función estudiada, considerado en incrementos para cada una de las coordenadas, así como el cambio en la función de aptitud en cada iteración. Como la función Skin es suave, el algoritmo "se adhiere" bien a los cambios de superficie.

En cuanto a la función Forest, el algoritmo ha mostrado resultados por debajo del promedio en la tabla; los cambios suaves en la función de prueba han ayudado hasta cierto punto al algoritmo a orientarse en el espacio, pero no ha logrado encontrar el máximo global. Y si consideramos los resultados del trabajo en Megacity, las deficiencias de FSS se vuelven aún más notables; al algoritmo realmente "no le gusta" cuando la función estudiada no cambia en las cercanías de la posición actual de los agentes individuales, y el algoritmo no es capaz de hacer saltos largos para identificar nuevos lugares potencialmente mejores. Por consiguiente, se queda atascado en cualquier extremo local que no tenga un incremento en los alrededores.

Aunque la prueba de Megacity resulta muy difícil para cualquier algoritmo de optimización y los otros participantes en la tabla comparativa no están muy por delante de FSS en general, debemos reconocer que las habilidades del algoritmo en cuanto a problemas discretos son bastante débiles; en algunas pruebas, ha sido en la búsqueda aleatoria donde ha mostrado el mejor resultado Como podemos ver en la animación, y en vista de lo anterior, esperamos que no haya “clusterización” en el funcionamiento del algoritmo. Recuerde que en el artículo anterior describimos el fenómeno de la "cristalización" de los algoritmos de optimización.

Impresión del funcionamiento del algoritmo FSS:

2022.12.08 13:14:06.033    Test_AO_FSS (EURUSD,M1)    =============================
2022.12.08 13:14:08.388    Test_AO_FSS (EURUSD,M1)    1 Skin's; Func runs 10000 result: 4.892861444841324
2022.12.08 13:14:08.388    Test_AO_FSS (EURUSD,M1)    Score: 0.99391
2022.12.08 13:14:12.557    Test_AO_FSS (EURUSD,M1)    20 Skin's; Func runs 10000 result: 3.11410005347766
2022.12.08 13:14:12.557    Test_AO_FSS (EURUSD,M1)    Score: 0.56920
2022.12.08 13:14:47.176    Test_AO_FSS (EURUSD,M1)    500 Skin's; Func runs 10000 result: 1.20519552002827
2022.12.08 13:14:47.176    Test_AO_FSS (EURUSD,M1)    Score: 0.11341
2022.12.08 13:14:47.176    Test_AO_FSS (EURUSD,M1)    =============================
2022.12.08 13:14:49.498    Test_AO_FSS (EURUSD,M1)    1 Forest's; Func runs 10000 result: 1.5057381856551146
2022.12.08 13:14:49.498    Test_AO_FSS (EURUSD,M1)    Score: 0.85172
2022.12.08 13:14:53.825    Test_AO_FSS (EURUSD,M1)    20 Forest's; Func runs 10000 result: 0.21468156279781656
2022.12.08 13:14:53.825    Test_AO_FSS (EURUSD,M1)    Score: 0.12143
2022.12.08 13:15:31.235    Test_AO_FSS (EURUSD,M1)    500 Forest's; Func runs 10000 result: 0.056970068652984526
2022.12.08 13:15:31.235    Test_AO_FSS (EURUSD,M1)    Score: 0.03223
2022.12.08 13:15:31.235    Test_AO_FSS (EURUSD,M1)    =============================
2022.12.08 13:15:34.066    Test_AO_FSS (EURUSD,M1)    1 Megacity's; Func runs 10000 result: 11.0
2022.12.08 13:15:34.066    Test_AO_FSS (EURUSD,M1)    Score: 0.91667
2022.12.08 13:15:38.467    Test_AO_FSS (EURUSD,M1)    20 Megacity's; Func runs 10000 result: 1.03
2022.12.08 13:15:38.467    Test_AO_FSS (EURUSD,M1)    Score: 0.08583
2022.12.08 13:16:16.589    Test_AO_FSS (EURUSD,M1)    500 Megacity's; Func runs 10000 result: 0.31
2022.12.08 13:16:16.589    Test_AO_FSS (EURUSD,M1)    Score: 0.02583
2022.12.08 13:16:16.589    Test_AO_FSS (EURUSD,M1)    =============================
2022.12.08 13:16:16.589    Test_AO_FSS (EURUSD,M1)    All score for C_AO_FSS: 0.4122477188979048

Skin

  FSS en la función de prueba Skin.

Forest

  FSS en la función de prueba Forest.

Megacity

  FSS en la función de prueba Megacity.

Aquí tenemos la tabla final. Le hemos añadido columnas adicionales para que resulte más cómodo evaluar los algoritmos para cada función de prueba por separado, lo cual nos permite discutir de forma más justa sobre la aplicabilidad de cada algoritmo para ciertas tareas.

AO

Description

Skin

Skin final

Forest

Forest final

Megacity (discrete)

Megacity final

Final result

2 params (1 F)

40 params (20 F)

1000 params (500 F)

2 params (1 F)

40 params (20 F)

1000 params (500 F)

2 params (1 F)

40 params (20 F)

1000 params (500 F)

COAm

cuckoo optimization algorithm

1,00000

0,85911

0,14316

0,66742

0,99283

0,28787

0,04551

0,44207

1,00000

0,24917

0,03537

0,42818

0,51255778

ACOm

ant colony optimization

0,98229

0,79108

0,12602

0,63313

1,00000

0,62077

0,11521

0,57866

0,38333

0,44000

0,02377

0,28237

0,49805222

ABCm

artificial bee colony M

1,00000

0,63922

0,08076

0,57333

0,99908

0,20112

0,03785

0,41268

1,00000

0,16333

0,02823

0,39719

0,46106556

ABC

artificial bee colony

0,99339

0,73381

0,11118

0,61279

0,99934

0,21437

0,04215

0,41862

0,85000

0,16833

0,03130

0,34988

0,46043000

GWO

grey wolf optimizer

0.99900

0,48033

0,18924

0,55619

0,83844

0,08755

0,02555

0,31718

1,00000

0,10000

0,02187

0,37396

0,41577556

FSS

búsqueda de bancos de peces

0,99391

0,5692

0,11341

0,55884

0,85172

0,12143

0,03223

0,33513

0,91667

0,08583

0,02583

0,34278

0,41224778

PSO

particle swarm optimisation

0,99627

0,38080

0,05089

0,47599

0,93772

0.14540

0,04856

0,37723

1,00000

0,09333

0,02233

0,37189

0,40836667

RND

random

0,99932

0,44276

0,06827

0,50345

0,83126

0,11524

0,03048

0.32566

0,83333

0,09000

0,02403

0,31579

0,38163222


Los métodos heurísticos estocásticos (aleatorizados) no garantizan una solución exacta, pero, por norma general, permiten encontrar soluciones lo suficientemente próximas para un uso práctico en un tiempo aceptable. No obstante, con la práctica podemos reconocer que algunos algoritmos pueden demostrar excelentes habilidades de convergencia, aunque esto no se aplique a FSS. En general, el algoritmo de búsqueda de bancos de peces supone un caso especial entre los algoritmos basados en enjambres de partículas, y por lo tanto, ha heredado sus ventajas y desventajas, si bien muestra al mismo tiempo características únicas en comparación con sus "ancestros", como la división del banco de peces (enjambre) en grupos separados, cosa que no se observa en el enjambre de partículas. Merece la pena destacar aparte sus ventajas: el algoritmo se adapta relativamente bien a las funciones suaves, aunque no podemos estar seguros de que FSS se adapte bien a las funciones con muchas variables.

Ventajas:
1. Gestiona las funciones suaves bastante bien.
2. La capacidad de un banco de peces para dividirse en grupos aparte, lo cual permite que el algoritmo explore más a fondo otros extremos locales potencialmente positivos.

Desventajas:
1. La gran dispersión de los resultados en pruebas individuales indica la inestabilidad del algoritmo.
2. Convergencia muy débil en funciones discretas y con rupturas. Resulta prácticamente imposible de aplicar para funciones discretas.
3. Escalabilidad débil.


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

Archivos adjuntos |
Teoría de categorías en MQL5 (Parte 1) Teoría de categorías en MQL5 (Parte 1)
La teoría de categorías es un área diversa y en expansión de las matemáticas, relativamente inexplorada aún en la comunidad MQL. Esta serie de artículos tiene como objetivo destacar algunos de sus conceptos para crear una biblioteca abierta y seguir utilizando esta maravillosa sección para crear estrategias comerciales.
DoEasy. Elementos de control (Parte 29): Control auxiliar "ScrollBar" DoEasy. Elementos de control (Parte 29): Control auxiliar "ScrollBar"
En este artículo, comenzaremos a desarrollar el control auxiliar ScrollBar y sus objetos derivados: las barras de desplazamiento verticales y horizontales. ScrollBar (barra de desplazamiento) se usa para desplazar el contenido del formulario si va más allá del contenedor. Por lo general, las barras de desplazamiento se encuentran en la parte inferior y derecha del formulario. La barra horizontal en la parte inferior desplaza el contenido hacia la izquierda y hacia la derecha, mientras que la barra vertical desplaza el contenido hacia arriba y hacia abajo.
Redes neuronales: así de sencillo (Parte 35): Módulo de curiosidad intrínseca (Intrinsic Curiosity Module) Redes neuronales: así de sencillo (Parte 35): Módulo de curiosidad intrínseca (Intrinsic Curiosity Module)
Seguimos analizando los algoritmos de aprendizaje por refuerzo. Todos los algoritmos que hemos estudiado hasta ahora requerían la creación de una política de recompensas tal que el agente pudiera evaluar cada una de sus acciones en cada transición de un estado del sistema a otro, pero este enfoque resulta bastante artificial. En la práctica, existe cierto tiempo de retraso entre la acción y la recompensa. En este artículo, le sugerimos que se familiarice con un algoritmo de entrenamiento de modelos que puede funcionar con varios retrasos de tiempo desde la acción hasta la recompensa.
Recetas MQL5 - Servicios Recetas MQL5 - Servicios
Este artículo describe las capacidades versátiles de los servicios, como los programas MQL5 que no requieren un gráfico vinculante. Asimismo, se detallan las diferencias de los servicios respecto a otros programas MQL5, enfatizando los matices del trabajo del desarrollador con los servicios. Como ejemplos, el lector podrá estudiar varias tareas que abarcan una amplia gama de funcionalidades que pueden implementarse como un servicio.