English Русский 中文 Português
preview
Optimización por herencia sanguínea — Blood inheritance optimization (BIO)

Optimización por herencia sanguínea — Blood inheritance optimization (BIO)

MetaTrader 5Probador |
169 0
Andrey Dik
Andrey Dik

Contenido

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


Introducción

Un día, mientras observaba cómo una enfermera extraía sangre a los pacientes en el laboratorio, me surgió un pensamiento inesperado. Los grupos sanguíneos -este antiguo sistema de herencia, transmitido de generación en generación según estrictas leyes genéticas- adquirieron de repente una dimensión completamente nueva. ¿Y si estas cualidades naturales de la herencia pudieran usarse en el campo de los algoritmos de optimización?

Cada uno de nosotros lleva en sus venas una combinación única heredada de sus padres. Al igual que los grupos sanguíneos determinan la compatibilidad en la transfusión, estos podrían determinar los modos de transmisión y las mutaciones de los parámetros en el proceso de optimización. Me encantó la idea, así que decidí retomarla cuando tuviera tiempo para investigar. Cuando se presentó la oportunidad adecuada, me puse manos a la obra, y, tras varios experimentos, nació el algoritmo Blood Inheritance Optimisation (BIO), un método que utiliza las leyes naturales de la herencia de los grupos sanguíneos como metáfora para guiar la evolución de las soluciones. En él, los cuatro tipos de sangre han evolucionado en cuatro estrategias diferentes para mutar parámetros, mientras que las leyes de la herencia determinan cómo la descendencia adquiere y modifica las características de sus progenitores.

Al igual que en la naturaleza, el grupo sanguíneo de un niño no es una simple media de los grupos sanguíneos de los padres, sino que está sujeto a leyes genéticas; en el BIO los parámetros de las nuevas soluciones se forman igualmente a través de un sistema de herencia y mutaciones. Cada grupo sanguíneo aporta su propio enfoque para explorar el espacio de soluciones: desde la mantenimiento conservador de los mejores valores encontrados hasta mutaciones radicales que abren nuevas áreas y direcciones prometedoras para seguir explorando el espacio de soluciones.

En este artículo, queremos compartir los principios del algoritmo BIO, que combina la inspiración biológica con el rigor algorítmico, y ofrecer resultados de pruebas sobre características que ya conocemos. Bien, allá vamos.


Implementación del algoritmo

Para empezar, nos familiarizaremos con la tabla de herencia del grupo sanguíneo de la descendencia de los padres. Como podemos ver, la herencia del grupo sanguíneo no es homogénea. Existen algunas estadísticas interesantes sobre la distribución de los grupos sanguíneos en la población mundial. El grupo 1 (O) es el más común : alrededor del 40% de la población mundial es portadora de este. Le sigue el segundo grupo (A), que posee aproximadamente el 30% de la población. El tercer grupo (B) se encuentra en el 20% de la población, mientras que el cuarto grupo (AB) es el más raro, con solo un 10% de personas portadoras.

Al estudiar los mecanismos de la herencia, entendí que el primer grupo sanguíneo es recesivo con respecto a todos los demás. Esto significa que las personas con el grupo uno solo pueden transmitirlo a sus hijos. Al mismo tiempo, el segundo y el tercer grupo muestran codominancia entre sí, lo cual da lugar a un cuarto grupo cuando se combinan. Por cierto, desde un punto de vista evolutivo, el grupo sanguíneo cuatro es el más joven.

Resultan especialmente interesantes algunas de las propiedades únicas de los distintos grupos sanguíneos. Por ejemplo, el tipo 1 se considera el "donante universal" porque puede transfundirse a personas con cualquier tipo de sangre. El cuarto grupo, en cambio, convierte a sus portadores en "receptores universales": pueden admitir sangre de cualquier grupo. 

Todas estas peculiaridades del sistema de grupos sanguíneos me inspiraron para crear los mecanismos correspondientes en mi algoritmo. Considerando el hecho de que el primer grupo sanguíneo es el básico y el más común, en el algoritmo se corresponderá con la estrategia de preservación de la mejor solución encontrada en el algoritmo. La tabla de herencia de grupos sanguíneos que muestra todas las combinaciones posibles de los grupos sanguíneos de los padres y los posibles grupos sanguíneos de sus hijos constituyó la base del mecanismo para determinar el "grupo sanguíneo" de una nueva solución usando como base los "grupos sanguíneos" de las soluciones parentales, lo que afecta directamente a la forma en que mutan los parámetros en el algoritmo BIO.

blood-type

Figura 1. Tabla de herencia del grupo sanguíneo 

La base del algoritmo BIO es una idea bastante simple: cada solución de la población (individuos parentales) se corresponde con su "grupo sanguíneo" (de 1 a 4), que viene determinado por su número ordinal en la población. Cuando creamos una nueva generación de soluciones, seleccionamos dos "padres" de la población actual. En este caso, la probabilidad de selección es cuadrática en lugar de lineal, lo cual significa que las mejores elecciones tienen muchas más posibilidades de convertirse en progenitores.

Y ahora viene la parte divertida. Basándonos en los grupos sanguíneos de los padres, usando una matriz especial de herencia (está escrita en el código en el método Init), determinamos los posibles grupos sanguíneos para el "hijo": la nueva solución. A continuación, para cada parámetro de esta nueva solución, si le ha tocado el primer grupo sanguíneo, tomaremos el valor de la mejor solución encontrada. Lo hemos hecho por analogía con el grupo sanguíneo 1 como donante universal. Si le toca el segundo grupo, tomaremos el valor de uno de los padres y le aplicaremos la distribución exponencial. Esto creará una tendencia a explorar los bordes del rango de parámetros. Con el tercer grupo, también tomaremos el valor de uno de los padres, pero lo moveremos hacia la mejor solución en una cantidad aleatoria. Y con el cuarto grupo, tomaremos el valor padre y lo reflejaremos en relación con los límites del rango, una especie de inversión que nos permitirá explorar nuevas áreas de búsqueda.

Tras crear una nueva generación, comprobaremos si han surgido soluciones mejores que la solución global actual y guardaremos los mejores individuos para la siguiente iteración. Así es como, usando una analogía con la herencia de grupos sanguíneos, nuestro algoritmo explorará el espacio de soluciones combinando diferentes estrategias de mutación de parámetros. A continuación presentamos el pseudocódigo del algoritmo.

Inicialización:

  1. Se crea una población de agentes con un tamaño popSize (por defecto es 50)
  2. Se crear una matriz de herencia de grupos sanguíneos que identifica los posibles grupos sanguíneos de los hijos en función de los grupos sanguíneos de los padres (1,2,3,4).
  3. Se inicializan los rangos de los parámetros (mín., máx., valores de paso)

Ciclo principal:

  1. Si se trata de la primera iteración (revisión = false):
    • Se inicializan aleatoriamente las posiciones de todos los agentes dentro de los rangos de los parámetros.
    • Se establece la bandera de revisión en true
  2. Para cada agente de la población:
    • Se seleccionan los agentes parentales (padre y madre) mediante una distribución de probabilidad cuadrática
    • Se determinan los grupos sanguíneos de ambos progenitores mediante la función: bloodType = 1 + (position_in_population % 4)
    • Para cada parámetro de la solución hija:
      • Se obtiene el posible grupo sanguíneo del hijo a partir de la matriz de herencia basada en los grupos sanguíneos de los padres.
      • Si el grupo sanguíneo del hijo es 1:
        • Se utiliza la mejor solución conocida para este parámetro.
      • De lo contrario:
        • Se selecciona aleatoriamente el valor del parámetro o bien del padre o bien de la madre
        • Se aplica la mutación según el grupo sanguíneo del niño:
          • Tipo 2: Se aplica la distribución exponencial con un exponente 20
          • Tipo 3: Se desplaza el valor del parámetro hacia la mejor solución con un factor aleatorio
          • Tipo 4: Se invierte el parámetro en todo el rango de parámetros
      • Asegúrese de que el parámetro se mantiene dentro del intervalo válido y los límites del paso.

Fase de revisión:

  1. Se actualiza la mejor solución global si algún agente tiene mejor aptitud
  2. Se copia la población actual en la segunda mitad del array de población ampliada
  3. Se clasifica la población ampliada según la aptitud
  4. Se retienen los mejores agentes para la próxima generación

Vamos a escribir el código del algoritmo. La clase "C_AO_BIO", derivada de "C_AO", implementa el algoritmo BIO e implica el uso de una estructura de datos para representar individuos (agentes) en una población así como su control.

    C_AO_BIO () el constructorinicializa por defecto los parámetros externos del BIO: el tamaño de la población "popSize" se establece en 50, el tamaño del array de parámetros "params" se establece con un elemento que representa "popSize".
    SetParams ()
    — el método permite establecer los parámetros de la clase, en este caso se establece el tamaño de la población "popSize" a partir de un array de parámetros.
    Init ()
    — el método inicializa el algoritmo tomando los valores mínimo y máximo de los parámetros, el paso de cambio y el número de épocas.
    Moving () y Revision () — los métodos son responsables del movimiento (evolución) de los agentes en la población y de su revisión (evaluación del rendimiento y selección de los mejores).
    S_Papa y S_Mama:
    • S_Papa — estructura que contiene un array de tipos de sangre (bTipos).
    • S_Mama — contiene un array de cuatro objetos S_Papa, que sugieren "padres" para posteriores mezclas genéticas.

    Esta forma de representar como estructuras obtendrá directamente el grupo sanguíneo probable del hijo a partir de los padres especificando el grupo sanguíneo de los padres, así "ma [1].pa [2].bTypes", donde "1" y "2" serán los grupos sanguíneos de la madre y el padre respectivamente.

    El método GetBloodType () devuelve el tipo de sangre de un agente concreto, mientras que GetBloodMutation () implementa un mecanismo para mutar un gen en función del tipo de sangre.

    //——————————————————————————————————————————————————————————————————————————————
    class C_AO_BIO : public C_AO
    {
      public: //--------------------------------------------------------------------
      C_AO_BIO ()
      {
        ao_name = "BIO";
        ao_desc = "Blood Inheritance Optimization";
        ao_link = "https://www.mql5.com/es/articles/17246";
    
        popSize = 50; // population size
    
        ArrayResize (params, 1);
        params [0].name = "popSize"; params [0].val = popSize;
      }
    
      void SetParams ()
      {
        popSize = (int)params [0].val;
      }
    
      bool Init (const double &rangeMinP  [],  // minimum values
                 const double &rangeMaxP  [],  // maximum values
                 const double &rangeStepP [],  // step change
                 const int     epochsP = 0);   // number of epochs
    
      void Moving   ();
      void Revision ();
    
      private: //-------------------------------------------------------------------
      struct S_Papa
      {
          int bTypes [];
      };
      struct S_Mama
      {
          S_Papa pa [4];
      };
      S_Mama ma [4];
    
      S_AO_Agent p [];
    
      int  GetBloodType     (int ind);
      void GetBloodMutation (double &gene, int indGene, int bloodType);
    };
    //——————————————————————————————————————————————————————————————————————————————

    El método "Init" inicializa una instancia de la clase "C_AO_BIO" y la prepara para trabajar, configurando la población de agentes y sus características: vamos a analizar la implementación de este método.

    Llamada al método "StandardInit" — la primera línea comprueba el resultado de la llamada al método, que comprueba/inicializa los parámetros básicos necesarios para que el algoritmo funcione.

    Inicialización de un array de agentes:

    • Cambia el tamaño del array de agentes "p" al doble del tamaño de población dado "popSize".
    • En un ciclo "for", se llama al método "Init" para cada agente, inicializando el agente utilizando los parámetros de coordenadas.
    Inicialización de los grupos sanguíneos:
    • A continuación, el método de tamaño establece los arrays de tipos de sangre (bTypes) para las estructuras "S_Mama" y "S_Papa".
    • Para distintas combinaciones (por ejemplo, ma [0].pa [0], ma [1].pa [2], etc.), se establecen diferentes tipos de sangre según un array de herencia especial, mientras que el tamaño de los arrays se especifica mediante "ArrayResize".

    Así, el método "Init" de la clase "C_AO_BIO" realiza una importante tarea de preparación del objeto para el algoritmo de optimización: crea una población de agentes, establece sus parámetros iniciales y define las reglas de vinculación para los tipos de sangre (herencia). Esto nos permitirá obtener instantáneamente el posible grupo sanguíneo de la descendencia, y utilizar sus parámetros "sanguíneos" para la evolución posterior dentro del algoritmo.

    //——————————————————————————————————————————————————————————————————————————————
    bool C_AO_BIO::Init (const double &rangeMinP  [],
                         const double &rangeMaxP  [],
                         const double &rangeStepP [],
                         const int     epochsP = 0)
    {
      if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false;
    
      //----------------------------------------------------------------------------
      ArrayResize (p, popSize * 2);
      for (int i = 0; i < popSize * 2; i++) p [i].Init (coords);
    
      //1-1
      ArrayResize (ma [0].pa [0].bTypes, 1);
    
      ma [0].pa [0].bTypes [0] = 1;
    
      //2-2
      ArrayResize (ma [1].pa [1].bTypes, 2);
    
      ma [1].pa [1].bTypes [0] = 1;
      ma [1].pa [1].bTypes [1] = 2;
    
      //3-3
      ArrayResize (ma [2].pa [2].bTypes, 2);
    
      ma [2].pa [2].bTypes [0] = 1;
      ma [2].pa [2].bTypes [1] = 3;
    
      //1-2; 2-1
      ArrayResize (ma [0].pa [1].bTypes, 2);
      ArrayResize (ma [1].pa [0].bTypes, 2);
    
      ma [0].pa [1].bTypes [0] = 1;
      ma [0].pa [1].bTypes [1] = 2;
    
      ma [1].pa [0].bTypes [0] = 1;
      ma [1].pa [0].bTypes [1] = 2;
    
      //1-3; 3-1
      ArrayResize (ma [0].pa [2].bTypes, 2);
      ArrayResize (ma [2].pa [0].bTypes, 2);
    
      ma [0].pa [2].bTypes [0] = 1;
      ma [0].pa [2].bTypes [1] = 3;
    
      ma [2].pa [0].bTypes [0] = 1;
      ma [2].pa [0].bTypes [1] = 3;
    
      //1-4; 4-1
      ArrayResize (ma [0].pa [3].bTypes, 2);
      ArrayResize (ma [3].pa [0].bTypes, 2);
    
      ma [0].pa [3].bTypes [0] = 2;
      ma [0].pa [3].bTypes [1] = 3;
    
      ma [3].pa [0].bTypes [0] = 2;
      ma [3].pa [0].bTypes [1] = 3;
    
      //2-3; 3-2
      ArrayResize (ma [1].pa [2].bTypes, 4);
      ArrayResize (ma [2].pa [1].bTypes, 4);
    
      ma [1].pa [2].bTypes [0] = 1;
      ma [1].pa [2].bTypes [1] = 2;
      ma [1].pa [2].bTypes [2] = 3;
      ma [1].pa [2].bTypes [3] = 4;
    
      ma [2].pa [1].bTypes [0] = 1;
      ma [2].pa [1].bTypes [1] = 2;
      ma [2].pa [1].bTypes [2] = 3;
      ma [2].pa [1].bTypes [3] = 4;
    
      //2-4; 4-2; 3-4; 4-3; 4-4
      ArrayResize (ma [1].pa [3].bTypes, 3);
      ArrayResize (ma [3].pa [1].bTypes, 3);
      ArrayResize (ma [2].pa [3].bTypes, 3);
      ArrayResize (ma [3].pa [2].bTypes, 3);
      ArrayResize (ma [3].pa [3].bTypes, 3);
    
      ma [1].pa [3].bTypes [0] = 2;
      ma [1].pa [3].bTypes [1] = 3;
      ma [1].pa [3].bTypes [2] = 4;
    
      ma [3].pa [1].bTypes [0] = 2;
      ma [3].pa [1].bTypes [1] = 3;
      ma [3].pa [1].bTypes [2] = 4;
    
      ma [2].pa [3].bTypes [0] = 2;
      ma [2].pa [3].bTypes [1] = 3;
      ma [2].pa [3].bTypes [2] = 4;
    
      ma [3].pa [2].bTypes [0] = 2;
      ma [3].pa [2].bTypes [1] = 3;
      ma [3].pa [2].bTypes [2] = 4;
    
      ma [3].pa [3].bTypes [0] = 2;
      ma [3].pa [3].bTypes [1] = 3;
      ma [3].pa [3].bTypes [2] = 4;
    
      return true;
    }
    //——————————————————————————————————————————————————————————————————————————————

    El método "Moving" realiza etapas evolutivas en el proceso de optimización aplicando los conceptos de herencia y mutación a una población de agentes; vamos a analizarlos con más detalle:

    Comprobación sobre la necesita de revisión — la primera parte del método comprueba si los agentes necesitan ser actualizados o "movidos" y si "revision" es "false"; se produce la inicialización (o actualización) inicial de las coordenadas de los agentes (a [i] .c [j]):

    • Cada agente recibe valores aleatorios generados en el rango [rangeMin [j], rangeMax [j]] usando el método "u.RNDfromCI".
    • A continuación, el valor se lleva al rango deseado mediante "u.SeInDiSp", que aplica el paso indicado en "rangeStep".

    Cambiar al estado de revisión — después de la primera iteración, el parámetro "revision" se establece en "true" para cambiar al siguiente paso, y el método finaliza la ejecución (return).

    Inicialización de variables — al principio del método se inicializan las variables responsables de los valores aleatorios y los tipos de sangre de los padres (papIND, mamIND, pBloodType, mBloodType, cBloodType y bloodIND).

    Ciclo principal a través de la población (popSize) — el método realiza un ciclo a través de cada agente de la población:

    • Se generan dos índices aleatorios para los padres (papIND y mamIND) mediante el método "u.RNDprobab ()", que genera probabilidades aleatorias.
    • La función "GetBloodType" recupera los grupos sanguíneos de ambos progenitores.

    Ciclo por las coordenadas (coords) — dentro del ciclo principal para cada coordenada del agente:

    • Se selecciona un índice de grupo sanguíneo aleatorio del array "bTypes" de los padres seleccionados (en función del grupo sanguíneo de la madre y del padre).
    • Si el tipo de sangre seleccionado es "1", el agente obtendrá el valor de "cB [c]". De lo contrario, se producirá la mezcla:
      • El valor de las coordenadas de los agentes se selecciona al azar entre el padre o la madre.
      • Se aplica la función "GetBloodMutation", que produce una mutación del valor elegido basada en el grupo sanguíneo.
      • El valor se ajusta usando el método "u.SeInDiSp" para garantizar que se mantenga dentro de los límites aceptables.

    El método Moving supone una parte clave del algoritmo que emula el proceso de evolución de la población de agentes e incluye tanto la inicialización aleatoria, como mecanismos de mutación y combinación de los parámetros de los agentes basados en los principios de la herencia de los grupos sanguíneos. El método combina aspectos de aleatoriedad y herencia para crear nuevos descendientes con valores distintos. Esto prepara a los agentes para una mayor optimización y exploración en el espacio de soluciones.

    //——————————————————————————————————————————————————————————————————————————————
    void C_AO_BIO::Moving ()
    {
      //----------------------------------------------------------------------------
      if (!revision)
      {
        for (int i = 0; i < popSize; i++)
        {
          for (int j = 0; j < coords; j++)
          {
            a [i].c [j] = u.RNDfromCI (rangeMin [j], rangeMax [j]);
            a [i].c [j] = u.SeInDiSp (a [i].c [j], rangeMin [j], rangeMax [j], rangeStep [j]);
          }
        }
        revision = true;
        return;
      }
    
      //----------------------------------------------------------------------------
      double rnd        = 0.0;
      int    papIND     = 0;
      int    mamIND     = 0;
      int    pBloodType = 0;
      int    mBloodType = 0;
      int    cBloodType = 0;
      int    bloodIND   = 0;
    
      for (int i = 0; i < popSize; i++)
      {
        rnd = u.RNDprobab ();
        rnd *= rnd;
        papIND = (int)u.Scale (rnd, 0.0, 1.0, 0, popSize - 1);
    
        rnd = u.RNDprobab ();
        rnd *= rnd;
        mamIND = (int)u.Scale (rnd, 0.0, 1.0, 0, popSize - 1);
    
        pBloodType = GetBloodType (papIND);
        mBloodType = GetBloodType (mamIND);
    
        for (int c = 0; c < coords; c++)
        {
          bloodIND   = MathRand () % ArraySize (ma [mBloodType - 1].pa [pBloodType - 1].bTypes);
          cBloodType = ma [mBloodType - 1].pa [pBloodType - 1].bTypes [bloodIND];
    
          if (cBloodType == 1) a [i].c [c] = cB [c];
          else
          {
            if (u.RNDbool () < 0.5) a [i].c [c] = p [papIND].c [c];
            else                    a [i].c [c] = p [mamIND].c [c];
    
            GetBloodMutation (a [i].c [c], c, cBloodType);
            a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
          }
        }
      }
    }
    //——————————————————————————————————————————————————————————————————————————————

    El método "GetBloodType" determina el grupo sanguíneo basándose en el índice "ind" transmitido: la posición actual en la población. Así, el método sirve para asignar índices a tipos sanguíneos usando una simple operación aritmética con un resto. Esto permite alternar los tipos de sangre entre los índices disponibles (0-3).

    //——————————————————————————————————————————————————————————————————————————————
    int C_AO_BIO::GetBloodType (int ind)
    {
      if (ind % 4 == 0) return 1;
      if (ind % 4 == 1) return 2;
      if (ind % 4 == 2) return 3;
      if (ind % 4 == 3) return 4;
    
      return 1;
    }
    //——————————————————————————————————————————————————————————————————————————————

    El método "GetBloodMutation" está diseñado para modificar (mutación) el valor de un parámetro genético (gen) en según su grupo sanguíneo e índice.

    Parámetros:

    • gene — referencia al valor del gen que se modificará
    • indGene — índice de genes que se utiliza para obtener rangos de mutación
    • bloodType — grupo sanguíneo que determina la lógica de la mutación

    Tipo de Sangre 2 — aplica una "PowerDistribution" escalonada al valor del gen, que cambia el gen basándose en un rango dado, distribuyendo probabilísticamente los valores a su alrededor.

    Grupo sanguíneo 3 — el gen se incrementa en una fracción de la diferencia entre el mejor valor actual del gen en la población "cB [indGene]" y el valor actual del gen. La fracción de desplazamiento viene determinada por un número aleatorio [0,0; 1,0].

    Otros tipos de sangre (por defecto) — el gen se cambia para que su nuevo valor sea simétrico al rango dado (inversión), estando entre "rangoMin [indGene]" y "rangoMax [indGene]".

    //——————————————————————————————————————————————————————————————————————————————
    void  C_AO_BIO::GetBloodMutation (double &gene, int indGene, int bloodType)
    {
      switch (bloodType)
      {
        case 2:
          gene = u.PowerDistribution (gene, rangeMin [indGene], rangeMax [indGene], 20);
          return;
        case 3:
          gene += (cB [indGene] - gene) * u.RNDprobab ();
          return;
        default:
        {
          gene = rangeMax [indGene] - (gene - rangeMin [indGene]);
        }
      }
    }
    //——————————————————————————————————————————————————————————————————————————————

    El método "Revision" se encarga de actualizar y clasificar la población en el algoritmo BIO. En el primer ciclo "for" (de 0 a popSize), el método itera todos los miembros de la población "a[i]". Si el valor de la función de aptitud "f" del miembro actual de la población "a[i].f" supera el mejor valor actual "fB", "fB" se actualizará con el nuevo valor, y las coordenadas "c" del miembro actual de la población se copiarán en el array "cB". En el segundo ciclo "for", los miembros actuales de la población "a[i]" se copian al final del array "p", empezando por el índice "popSize". A continuación, se crea un array temporal "pT" que tiene el doble del tamaño de la población actual "popSize * 2". Luego se llama al método de clasificación "u.Sorting" para clasificar el array combinado "p", almacenando los resultados en "pT".

    //——————————————————————————————————————————————————————————————————————————————
    void C_AO_BIO::Revision ()
    {
      //----------------------------------------------------------------------------
      for (int i = 0; i < popSize; i++)
      {
        // Update the best global solution
        if (a [i].f > fB)
        {
          fB = a [i].f;
          ArrayCopy (cB, a [i].c, 0, 0, WHOLE_ARRAY);
        }
      }
    
      //----------------------------------------------------------------------------
      for (int i = 0; i < popSize; i++)
      {
        p [popSize + i] = a [i];
      }
    
      S_AO_Agent pT []; ArrayResize (pT, popSize * 2);
      u.Sorting (p, pT, popSize * 2);
    }
    //——————————————————————————————————————————————————————————————————————————————


    Resultados de las pruebas

    Hemos probado el algoritmo con tres funciones de prueba diferentes (Hilly, Forest y Megacity) con diferentes dimensiones del espacio de búsqueda (5*2, 25*2 y 500*2 dimensiones) con 10.000 cálculos de la función objetivo. El resultado global del 53,80% indica que el BIO se sitúa en una posición intermedia entre los algoritmos de optimización basados en la población, lo que es bastante positivo para un método nuevo. 

    BIO|Blood Inheritance Optimization|50.0|
    =============================
    5 Hilly's; Func runs: 10000; result: 0.8156790458423091
    25 Hilly's; Func runs: 10000; result: 0.6533623929914842
    500 Hilly's; Func runs: 10000; result: 0.3087659267627686
    =============================
    5 Forest's; Func runs: 10000; result: 0.8993708810337727
    25 Forest's; Func runs: 10000; result: 0.6531872390668734
    500 Forest's; Func runs: 10000; result: 0.21759965952460583
    =============================
    5 Megacity's; Func runs: 10000; result: 0.6784615384615384
    25 Megacity's; Func runs: 10000; result: 0.4763076923076923
    500 Megacity's; Func runs: 10000; result: 0.13901538461538585
    =============================
    All score: 4.84175 (53.80%)

    El único problema que se aprecia en la visualización del algoritmo es la tendencia a estancarse en óptimos locales con problemas de pequeña dimensionalidad, algo bastante habitual en los algoritmos poblacionales.

    Hilly

    BIO en la función de prueba Hilly

    Forest

    BIO en la función de prueba Forest

    Megacity

    BIO en la función de prueba Megacity

    Al final de la prueba, el algoritmo BIO ocupa el puesto 20 en la tabla de clasificación de algoritmos de optimización basados en la población.

    AO Description Hilly Hilly final Forest Forest final Megacity (discrete) Megacity final Final result % de 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)
    1 ANS across neighbourhood search 0,94948 0,84776 0,43857 2,23581 1,00000 0,92334 0,39988 2,32323 0,70923 0,63477 0,23091 1,57491 6,134 68,15
    2 CLA code lock algorithm (joo) 0,95345 0,87107 0,37590 2,20042 0,98942 0,91709 0,31642 2,22294 0,79692 0,69385 0,19303 1,68380 6,107 67,86
    3 AMOm animal migration optimization M 0,90358 0,84317 0,46284 2,20959 0,99001 0,92436 0,46598 2,38034 0,56769 0,59132 0,23773 1,39675 5,987 66,52
    4 (P+O)ES (P+O) evolution strategies 0,92256 0,88101 0,40021 2,20379 0,97750 0,87490 0,31945 2,17185 0,67385 0,62985 0,18634 1,49003 5,866 65,17
    5 CTA comet tail algorithm (joo) 0,95346 0,86319 0,27770 2,09435 0,99794 0,85740 0,33949 2,19484 0,88769 0,56431 0,10512 1,55712 5,846 64,96
    6 TETA time evolution travel algorithm (joo) 0,91362 0,82349 0,31990 2,05701 0,97096 0,89532 0,29324 2,15952 0,73462 0,68569 0,16021 1,58052 5,797 64,41
    7 SDSm stochastic diffusion search M 0,93066 0,85445 0,39476 2,17988 0,99983 0,89244 0,19619 2,08846 0,72333 0,61100 0,10670 1,44103 5,709 63,44
    8 AAm archery algorithm M 0,91744 0,70876 0,42160 2,04780 0,92527 0,75802 0,35328 2,03657 0,67385 0,55200 0,23738 1,46323 5,548 61,64
    9 ESG evolution of social groups (joo) 0,99906 0,79654 0,35056 2,14616 1,00000 0,82863 0,13102 1,95965 0,82333 0,55300 0,04725 1,42358 5,529 61,44
    10 SIA simulated isotropic annealing (joo) 0,95784 0,84264 0,41465 2,21513 0,98239 0,79586 0,20507 1,98332 0,68667 0,49300 0,09053 1,27020 5,469 60,76
    11 ACS artificial cooperative search 0,75547 0,74744 0,30407 1,80698 1,00000 0,88861 0,22413 2,11274 0,69077 0,48185 0,13322 1,30583 5,226 58,06
    12 DA dialectical algorithm 0,86183 0,70033 0,33724 1,89940 0,98163 0,72772 0,28718 1,99653 0,70308 0,45292 0,16367 1,31967 5,216 57,95
    13 BHAm black hole algorithm M 0,75236 0,76675 0.34583 1,86493 0.93593 0.80152 0,27177 2,00923 0.65077 0.51646 0,15472 1,32195 5.196 57.73
    14 ASO anarchy society optimization 0,84872 0,74646 0,31465 1,90983 0,96148 0,79150 0,23803 1,99101 0,57077 0,54062 0,16614 1,27752 5,178 57,54
    15 RFO royal flush optimization (joo) 0,83361 0,73742 0,34629 1,91733 0,89424 0,73824 0,24098 1,87346 0,63154 0,50292 0,16421 1,29867 5,089 56,55
    16 AOSm búsqueda de orbitales atómicos M 0,80232 0,70449 0,31021 1,81702 0,85660 0,69451 0,21996 1,77107 0,74615 0,52862 0,14358 1,41835 5,006 55,63
    17 TSEA turtle shell evolution algorithm (joo) 0,96798 0,64480 0,29672 1,90949 0,99449 0,61981 0,22708 1,84139 0,69077 0,42646 0,13598 1,25322 5,004 55,60
    18 DE differential evolution 0,95044 0,61674 0,30308 1,87026 0,95317 0,78896 0,16652 1,90865 0,78667 0,36033 0,02953 1,17653 4,955 55,06
    19 CRO chemical reaction optimisation 0,94629 0,66112 0,29853 1,90593 0,87906 0,58422 0,21146 1,67473 0,75846 0,42646 0,12686 1,31178 4,892 54,36
    20 BIO blood inheritance optimization (joo) 0,81568 0,65336 0,30877 1,77781 0,89937 0,65319 0,21760 1,77016 0,67846 0,47631 0,13902 1,29378 4,842 53,80
    21 BSA bird swarm algorithm 0,89306 0,64900 0,26250 1,80455 0,92420 0,71121 0,24939 1,88479 0,69385 0,32615 0,10012 1,12012 4,809 53,44
    22 HS harmony search 0,86509 0,68782 0,32527 1,87818 0,99999 0,68002 0,09590 1,77592 0,62000 0,42267 0,05458 1,09725 4,751 52,79
    23 SSG saplings sowing and growing 0,77839 0,64925 0,39543 1,82308 0,85973 0,62467 0,17429 1,65869 0,64667 0,44133 0,10598 1,19398 4,676 51,95
    24 BCOm bacterial chemotaxis optimization M 0,75953 0,62268 0,31483 1,69704 0,89378 0,61339 0,22542 1,73259 0,65385 0,42092 0,14435 1,21912 4,649 51,65
    25 ABO african buffalo optimization 0,83337 0,62247 0,29964 1,75548 0,92170 0,58618 0,19723 1,70511 0,61000 0,43154 0,13225 1,17378 4,634 51,49
    26 (PO)ES (PO) evolution strategies 0,79025 0,62647 0,42935 1,84606 0,87616 0,60943 0,19591 1,68151 0,59000 0,37933 0,11322 1,08255 4,610 51,22
    27 TSm tabu search M 0,87795 0,61431 0,29104 1,78330 0,92885 0,51844 0,19054 1,63783 0,61077 0,38215 0,12157 1,11449 4,536 50,40
    28 BSO brain storm optimization 0,93736 0,57616 0,29688 1,81041 0,93131 0,55866 0,23537 1,72534 0,55231 0,29077 0,11914 0,96222 4,498 49,98
    29 WOAm wale optimization algorithm M 0,84521 0,56298 0,26263 1,67081 0,93100 0,52278 0,16365 1,61743 0,66308 0,41138 0,11357 1,18803 4,476 49,74
    30 AEFA artificial electric field algorithm 0,87700 0,61753 0,25235 1,74688 0,92729 0,72698 0,18064 1,83490 0,66615 0,11631 0,09508 0,87754 4,459 49,55
    31 AEO artificial ecosystem-based optimization algorithm 0,91380 0,46713 0,26470 1,64563 0,90223 0,43705 0,21400 1,55327 0,66154 0,30800 0,28563 1,25517 4,454 49,49
    32 ACOm ant colony optimization M 0,88190 0,66127 0,30377 1,84693 0,85873 0,58680 0,15051 1,59604 0,59667 0,37333 0,02472 0,99472 4,438 49,31
    33 BFO-GA bacterial foraging optimization - ga 0,89150 0,55111 0,31529 1,75790 0,96982 0,39612 0,06305 1,42899 0,72667 0,27500 0,03525 1,03692 4,224 46,93
    34 SOA simple optimization algorithm 0.91520 0.46976 0.27089 1,65585 0.89675 0.37401 0.16984 1,44060 0.69538 0.28031 0.10852 1,08422 4.181 46,45
    35 ABH artificial bee hive algorithm 0,84131 0,54227 0,26304 1,64663 0,87858 0,47779 0,17181 1,52818 0,50923 0,33877 0,10397 0,95197 4.127 45,85
    36 ACMO atmospheric cloud model optimization 0,90321 0,48546 0,30403 1,69270 0,80268 0,37857 0,19178 1,37303 0,62308 0,24400 0,10795 0,97503 4,041 44,90
    37 ADAMm adaptive moment estimation M 0.88635 0.44766 0,26613 1.60014 0.84497 0.38493 0.16889 1,39880 0,66154 0.27046 0.10594 1,03794 4.037 44.85
    38 ATAm artificial tribe algorithm M 0.71771 0.55304 0,25235 1,52310 0.82491 0.55904 0,20473 1,58867 0,44000 0,18615 0.09411 0.72026 3.832 42.58
    39 ASHA artificial showering algorithm 0,89686 0,40433 0,25617 1,55737 0,80360 0,35526 0,19160 1,35046 0,47692 0,18123 0,09774 0,75589 3,664 40,71
    40 ASBO adaptive social behavior optimization 0,76331 0,49253 0,32619 1,58202 0,79546 0,40035 0,26097 1,45677 0,26462 0,17169 0,18200 0,61831 3.657 40,63
    41 MEC mind evolutionary computation 0,69533 0,53376 0,32661 1,55569 0,72464 0,33036 0,07198 1,12698 0,52500 0,22000 0,04198 0,78698 3,470 38,55
    42 CSA circle search algorithm 0,66560 0,45317 0,29126 1,41003 0,68797 0,41397 0,20525 1,30719 0,37538 0,23631 0,10646 0,71815 3,435 38,17
    43 IWO invasive weed optimization 0,72679 0,52256 0,33123 1,58058 0,70756 0,33955 0,07484 1,12196 0,42333 0,23067 0,04617 0,70017 3,403 37,81
    44 Micro-AIS micro artificial immune system 0,79547 0,51922 0,30861 1,62330 0,72956 0,36879 0,09398 1,19233 0,37667 0,15867 0,02802 0,56335 3,379 37,54
    45 COAm cuckoo optimization algorithm M 0,75820 0,48652 0,31369 1,55841 0,74054 0,28051 0,05599 1,07704 0,50500 0,17467 0,03380 0,71347 3,349 37,21

    R.W. random walk 0.48754 0.32159 0.25781 1,06694 0.37554 0,21944 0,15877 0,75375 0.27969 0,14917 0.09847 0.52734 2.348 26.09


    Conclusiones

    En el proceso de desarrollo y prueba del algoritmo Blood Inheritance Optimization (BIO), hemos llegado a varias conclusiones importantes. En primer lugar, el uso de la asociación hereditaria de grupos sanguíneos ha demostrado ser un enfoque acertado para organizar las distintas estrategias de mutación en un algoritmo de optimización basado en la población. Las pruebas realizadas con diferentes funciones y dimensionalidades han demostrado que el algoritmo es bastante versátil y puede tratar eficazmente tanto problemas sencillos de baja dimensionalidad como problemas multidimensionales más complejos.

    Resulta especialmente importante señalar que la aplicación del BIO presentada es solo una versión básica que demuestra el concepto. La idea clave del algoritmo no reside tanto en los operadores de mutación específicos (que pueden sustituirse por cualquier otro operador), como en la propia estructura de herencia de las estrategias de cambio de parámetros a través de la analogía con los grupos sanguíneos. Esto abre un amplio abanico de posibilidades para modificar y ampliar el algoritmo. Cada "grupo sanguíneo" puede asociarse a cualquier otro operador de mutación tomado de otros algoritmos o creado concretamente para una tarea concreta. Además, podemos experimentar con el número de "grupos sanguíneos" añadiendo nuevas estrategias o combinando las existentes.

    Los resultados de las pruebas actuales, que muestran una posición decente en el ranking de algoritmos de población (con una puntuación de alrededor del 54%), indican que el enfoque resulta viable incluso en su implementación básica. La tendencia observada a quedarse atascado en óptimos locales puede superarse modificando los operadores de mutación o añadiendo nuevas estrategias para explorar el espacio de soluciones.

    En mi opinión, la dirección más prometedora del desarrollo de algoritmos es la creación de versiones adaptativas en las que los operadores de mutación de cada "grupo sanguíneo" puedan modificarse dinámicamente durante el proceso de optimización, ajustándose al paisaje de la función objetivo. También resulta interesante investigar la posibilidad de utilizar diferentes esquemas de herencia distintos del clásico sistema de grupos sanguíneos "ABO", lo cual puede conducir a la creación de toda una familia de algoritmos basados en diferentes sistemas de herencia biológica.

    Así pues, el BIO representa no solo otro algoritmo de optimización, sino también un marco conceptual flexible para crear una familia de algoritmos unidos por la idea común de la herencia de las estrategias de búsqueda de soluciones a través de la metáfora de los grupos sanguíneos y abre un amplio abanico de posibilidades para nuevas investigaciones y modificaciones destinadas a mejorar la eficacia del algoritmo en distintos ámbitos de aplicación.

    Tab

    Figura 2. Clasificación por colores de los algoritmos según las pruebas respectivas

    Chart

    Figura 3. Histograma de los resultados de las pruebas de algoritmos (en una escala de 0 a 100, cuanto más mejor, donde 100 es el máximo resultado teórico posible, el script para calcular la tabla de puntuación está en el archivo)

    Ventajas e inconvenientes del algoritmo BIO:

    Ventajas:

    1. No tiene parámetros externos
    2. La idea de la herencia por grupo sanguíneo es interesante
    3. Buena convergencia en funciones de dimensionalidad alta y media

    Desventajas:

    1. Se atasca en extremos locales en problemas de pequeña dimensionalidad.


    Adjuntamos al artículo un archivo con las versiones actuales de los códigos de los algoritmos. El autor de este artículo no se responsabiliza de la exactitud absoluta de la descripción de los algoritmos canónicos, muchos de ellos han sido modificados para mejorar las capacidades de búsqueda. Las conclusiones y juicios presentados en los artículos se basan en los resultados de los experimentos realizados.



    Programas usados en el artículo

    # Nombre Tipo Descripción
    1 #C_AO.mqh
    Archivo de inclusión
    Clase parental de algoritmos de población
    optimizaciones
    2 #C_AO_enum.mqh
    Archivo de inclusión
    Enumeración de los algoritmos de optimización basados en la población
    3 TestFunctions.mqh
    Archivo de inclusión
    Biblioteca de funciones de prueba
    4
    TestStandFunctions.mqh
    Archivo de inclusión
    Biblioteca de funciones del banco de pruebas
    5
    Utilities.mqh
    Archivo de inclusión
    Biblioteca de funciones auxiliares
    6
    CalculationTestResults.mqh
    Archivo de inclusión
    Script para calcular los resultados en una tabla comparativa
    7
    Testing AOs.mq5
    Script Banco de pruebas único para todos los algoritmos de optimización basados en la población
    8
    Simple use of population optimization algorithms.mq5
    Script
    Ejemplo sencillo de utilización de algoritmos de optimización basados en la población sin visualización
    9
    Test_AO_BIO.mq5
    Script Banco de pruebas para el BIO

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

    Archivos adjuntos |
    BIO.zip (166.57 KB)
    Simulación de mercado (Parte 13): Sockets (VII) Simulación de mercado (Parte 13): Sockets (VII)
    Cuando tú desarrollas algo, ya sea en xlwings o en cualquier otro paquete que nos permita leer y escribir directamente en Excel, en realidad deberías notar que todos los programas, funciones o procedimientos se ejecutan y luego finalizan su tarea. No permanecen allí dentro de un bucle, y, por más que intentes hacer las cosas de otra forma.
    La estrategia de negociación de la brecha del valor razonable inverso (Inverse Fair Value Gap, IFVG) La estrategia de negociación de la brecha del valor razonable inverso (Inverse Fair Value Gap, IFVG)
    Una brecha inversa del valor razonable (Inverse Fair Value Gap, IFVG) se produce cuando el precio vuelve a una brecha del valor razonable identificada previamente y, en lugar de mostrar la reacción de apoyo o resistencia esperada, no la respeta. Este comportamiento puede indicar un posible cambio en la dirección del mercado y ofrecer una ventaja comercial contraria. En este artículo, voy a presentar mi enfoque, desarrollado por mí mismo, para cuantificar y utilizar la brecha inversa del valor razonable como estrategia para los asesores expertos de MetaTrader 5.
    Mecanismos de compuertas en el aprendizaje en conjuntos Mecanismos de compuertas en el aprendizaje en conjuntos
    En este artículo, continuamos nuestra exploración de los modelos ensamblados analizando el concepto de compuertas, concretamente cómo pueden ser útiles para combinar los resultados de los modelos con el fin de mejorar la precisión de las predicciones o la generalización de los modelos.
    Simulación de mercado (Parte 12): Sockets (VI) Simulación de mercado (Parte 12): Sockets (VI)
    En este artículo, veremos cómo resolver algunos problemas y cuestiones al usar código escrito en Python dentro de otros programas. Más concretamente, mostraré un problema habitual que ocurre al usar Excel junto con MetaTrader 5, aunque para esta comunicación utilizaremos Python. Sin embargo, hay un pequeño inconveniente en esta implementación. No ocurre en todos los casos, sino solo en algunos específicos. Cuando ocurre, es necesario entender la razón. En este artículo, empezaré a explicar cómo resolverlo.