English Русский 中文 Español 日本語 Português
preview
Neuroboids Optimierungsalgorithmus 2 (NOA2)

Neuroboids Optimierungsalgorithmus 2 (NOA2)

MetaTrader 5Tester |
134 0
Andrey Dik
Andrey Dik

Inhalt

  1. Einführung
  2. Implementierung des Algorithmus
  3. Testergebnisse


Einführung

Als ich mich im Laufe der Jahre eingehender mit Optimierungsalgorithmen beschäftigte, wurde ich oft von zwei parallelen Inspirationen angezogen: der Selbstorganisation biologischer Schwärme und den adaptiven Lernfähigkeiten neuronaler Netze. Die Synthese dieser beiden Paradigmen führte mich zur Entwicklung eines hybriden Algorithmus, der die räumliche Intelligenz der Boids von Craig Reynolds mit den adaptiven Lernfähigkeiten neuronaler Netze kombiniert.

Meine Reise begann mit der Beobachtung, dass herkömmliche Schwarmalgorithmen zwar für die Erkundung komplexer Suchräume durch kollektives Verhalten geeignet sind, aber oft nicht in der Lage sind, aus der Geschichte ihrer Erkundungen zu lernen. Umgekehrt sind neuronale Netze hervorragend in der Lage, komplexe Muster zu lernen, haben aber Schwierigkeiten, räumliche Muster effektiv zu untersuchen, wenn sie direkt auf Optimierungsprobleme angewandt werden. Die Frage, die meine Forschung motivierte, war trügerisch einfach: Was wäre, wenn jeder Agent in einem Schwarm lernen könnte, seine Bewegungsstrategien mithilfe eines speziellen neuronalen Netzwerks zu verbessern?

Der daraus resultierende Algorithmus verwendet Agenten, die den klassischen Boid-Regeln der Kohäsion, Separation und Ausrichtung folgen, was es ihnen ermöglicht, sich selbst zu organisieren und den Suchraum effizient zu erkunden. Im Gegensatz zu herkömmlichen Boid-Implementierungen ist jeder Agent jedoch mit einem mehrschichtigen neuronalen Netz ausgestattet, das kontinuierlich aus den Erfahrungen des Agenten lernt und seine Bewegungsstrategien an die spezifischen Merkmale der Fitnesslandschaft anpasst. Dieses Maß an neuronaler Kontrolle beeinflusst allmählich das Verhalten der Boids und geht von Strategien, die in den ersten Iterationen die Erkundung dominieren, zu einer auf Ausbeutung ausgerichteten Bewegung über, wenn vielversprechende Regionen identifiziert werden.

Was mich während der Entwicklung am meisten faszinierte, war die Beobachtung, wie neuronale Netze je nach ihrer Position im Suchraum unterschiedliche Strategien entwickelten. Agenten in der Nähe vielversprechender Regionen entwickelten neuronale Muster, die die lokale Ausbeutung förderten, während Agenten in dünn besiedelten Regionen exploratives Verhalten unterstützten. Diese Spezialisierung entstand auf natürliche Weise durch individuelle Lernprozesse und führte zu einem Schwarm mit heterogenem, kontextabhängigem Verhalten ohne erkennbare globale Koordination.

In diesem Artikel werde ich die Architektur, die Implementierungsdetails und die Leistungsanalyse des NOA2-Algorithmus vorstellen und seine Fähigkeiten anhand verschiedener Benchmark-Funktionen demonstrieren. 


Implementierung des Algorithmus

Wie bereits erwähnt, besteht die Hauptidee des Neuroboids-Algorithmus darin, zwei Paradigmen zu kombinieren: die kollektive Intelligenz von Schwarmalgorithmen und das adaptive Lernen von neuronalen Netzen.

Beim traditionellen Boids-Algorithmus, der von Craig Reynolds vorgeschlagen wurde, folgen die Agenten drei einfachen Regeln: Konvergenz (Bewegung zum Zentrum der Gruppe), Trennung (Vermeidung von Kollisionen) und Ausrichtung (Anpassung der Geschwindigkeit an die Nachbarn). Diese Regeln schaffen ein realistisches Gruppenverhalten, ähnlich dem Verhalten von Vögeln in Schwärmen. Neuroboids erweitern dieses Konzept, indem sie jeden Agenten mit einem individuellen neuronalen Netz ausstatten, das aus den Erfahrungen des Agenten bei der Erkundung des Suchraums lernt. Dieses neuronale Netz erfüllt zwei wichtige Funktionen:

  1. Die adaptive Bewegungssteuerung passt die Geschwindigkeit des Agenten auf der Grundlage seines aktuellen Zustands und seiner Bewegungshistorie an.
  2. Durch die Modifizierung der Standardregeln für Boids wird der Einfluss der Konvergenz-, Trennungs- und Ausrichtungsregeln dynamisch und kontextabhängig angepasst.

Das Ergebnis ist ein hybrider Algorithmus, bei dem jeder Agent das soziale Verhalten beibehält, das für eine effiziente Erkundung des Raums erforderlich ist, sich aber gleichzeitig durch Lernen individuell an die Landschaft der Fitnessfunktionen anpasst. Dadurch entsteht ein selbstregulierendes Gleichgewicht zwischen Erkundung und Ausbeutung.

Die Hauptvorteile dieses Ansatzes sind das unabhängige Lernen der Agenten in optimalen Bewegungsstrategien, wodurch sich der Algorithmus automatisch an verschiedene Arten von Optimierungslandschaften anpasst, während die Erkundung des Raums dank des kollektiven Verhaltens ohne zentrale Steuerung erhalten bleibt. Ich möchte Ihnen eine einfache Analogie geben: Stellen Sie sich einen Vogelschwarm vor, der am Himmel fliegt. Sie bewegen sich koordiniert: Keiner stößt zusammen, sie bleiben zusammen und fliegen in dieselbe Richtung. Dieses Verhalten lässt sich durch drei einfache Regeln beschreiben: Bleiben Sie in der Nähe Ihrer Nachbarn (entfernen Sie sich nicht vom Schwarm), kollidieren Sie nicht mit Ihren Nachbarn (halten Sie Abstand) und fliegen Sie in die gleiche Richtung (halten Sie den gemeinsamen Kurs).

Dies ist die Grundlage des so genannten Boid-Algorithmus „Bird-oid“. Jeder Vogel im Schwarm befolgt nicht nur diese Regeln, sondern lernt auch aus seinen eigenen Erfahrungen. Der Vogel merkt sich, welche Handlungen zum Erfolg führten (z. B. mehr Futter zu finden) und welche nicht. Mit der Zeit wird der Vogel schlauer und trifft bessere Entscheidungen, wie er fliegen soll. Das ist die Essenz des Neuroboids-Algorithmus: die Kombination einfacher Regeln der Gruppenbewegung mit der Fähigkeit jedes Teilnehmers, aus seinen eigenen Erfahrungen zu lernen. Das „neuro“ im Namen weist auf die Fähigkeit zum Lernen hin. Dieser Ansatz ist besonders interessant, weil er die Stärke der kollektiven Forschung (niemand verpasst einen wichtigen Bereich) mit den Vorteilen des individuellen Lernens (jeder wird der Beste in seinem Forschungsgebiet) verbindet.

NOA2-Veranschaulichung

Abbildung 1. Betrieb des NOA2-Algorithmus  

Die Abbildung zeigt die folgenden Schlüsselelemente: die Optimierungslandschaft, wobei der gelb-violette Bereich das globale Optimum darstellt, die orangefarbenen und violetten Bereiche die lokalen Optima, und die Konturlinien zeigen die „Höhe“ der Fitnessfunktionslandschaft. 

Verschiedene Gruppen von Agenten: die blauen erkunden den Bereich des globalen Optimums, die violetten konzentrieren sich um das erste lokale Optimum, die grünen erkunden das zweite lokale Optimum und die roten erkunden den Raum nach dem Zufallsprinzip. Die Pfeile zeigen die Bewegungsrichtung der Boids. Der rote Punkt markiert die aktuell beste gefundene Lösung.

NOA2-Diagramm

Abbildung 2. Betrieb des NOA2-Algorithmus  

Das Diagramm umfasst: Initialisierungsblock (rot), neuronales Netz (rosa), Algorithmusiterationen mit Unterblöcken (blau), adaptive Mechanismen (grün), Visualisierung der Architektur des neuronalen Netzes mit Verbindungsbeispielen. Der untere Teil zeigt Miniaturdiagramme der Struktur des neuronalen Netzes, die Visualisierung der Regeln für die Bewegung der Boids und das Gleichgewicht zwischen Erkundung und Ausbeutung.

Nachdem wir das Grundkonzept verstanden haben, können wir mit der Beschreibung des Pseudocodes des Algorithmus fortfahren.

INITIALISIERUNG:

  • Lege Suchparameter fest und erstelle eine Population von Agenten.
  • Für jeden Agenten: Initialisiere eine zufällige Position, eine niedrige Geschwindigkeit, ein neuronales Netzes (Eingabe → ausgeblendet → Ausgabeschichten) und einen leeren Erfahrungsspeicher.
  • Setze die global beste Fitness auf negativ Unendlich und den Stagnationszähler auf Null.

HAUPTSCHLEIFE:

  • Für jede Iteration:

    SCHÄTZUNG:

    • Berechne die Fitness für alle Agenten.
    • Aktualisiere deren persönliche und die global beste Positionen.
    • Speicher die Experimente in Speicherpuffern.

    STAGNATIONSMANAGEMENT:

    • Wenn keine Besserung eintritt: Forschung verstärken, schwache Mittel von Zeit zu Zeit neu starten.
    • Andernfalls: schrittweise Reduzierung des Forschungsniveaus.

    NEURONALE VERARBEITUNG:

    • Für jeden Agenten:
      • Vorwärtsdurchlauf: Eingaben (Position, Geschwindigkeit, Abstand zum Besten, Fitness) → versteckte Schichten → Ausgaben.
      • Wenn genügend Erfahrungen gesammelt wurden und eine Verbesserung festgestellt wurde: Aktualisierung der neuronalen Gewichte.

    AKTUALISIERUNG DER BEWEGUNG:

    • Für jeden Agenten:
      • Berechne die Standard-Boid-Kräfte (Kohäsion, Trennung, Ausrichtung).
      • Wende neuronal modifizierter Kräfte und direkte neuronale Korrekturen an.
      • Füge eine Zufallskomponente mit Wahrscheinlichkeit hinzu.
      • Halte die Geschwindigkeitsbegrenzungen ein und respektiere die Grenzen.
      • Aktualisiere die Position anhand der Endgeschwindigkeit.

UNTERSTÜTZENDE BERECHNUNGEN:

  • Berechnung der Masse: Die Fitnesswerte werden normalisiert, um jedem Agenten eine Masse zuzuweisen.
  • Zusammenhalt: Bewegen in Richtung des gewichteten Massenschwerpunkts der benachbarten Agenten.
  • Trennung: Vermeide das Zusammenballen mit Nachbarn.
  • Ausrichtung: Koordiniere die Geschwindigkeit mit den Agenten in der Nähe.
  • Neuronale Aktualisierung: vereinfachtes Backpropagation auf der Grundlage von Fitnessverbesserung.

RÜCKGABE: der global besten Position und Fitness.

Jetzt ist alles bereit für das Schreiben des Algorithmuscodes. Wir weisen der Struktur S_NeuroBoids_Agent zu, die einen auf der Architektur eines neuronalen Netzes basierenden Neuro-Boid-Agenten zur Durchführung von Optimierungsaufgaben darstellt. In dieser Implementierung hat der Agent die folgenden Hauptkomponenten und Funktionen:

Koordinaten und Geschwindigkeiten:

  • x [] – aktuelle Koordinaten des Agenten.
  • dx [] – aktuelle Geschwindigkeiten des Agenten.
Neuronales Netz:
  • inputs [] – Eingabewerte für die Neuronen (Koordinaten, Geschwindigkeiten, Abstand zur besten bekannten Position usw.).
  • outputs [] – Ausgangswerte des neuronalen Netzes (Geschwindigkeitskorrektur und Anpassungsparameter).
  • weights [] – Gewichte der Verbindungen im neuronalen Netz.
  • biases [] – Verzerrung der Neuronen.
Speicher:
  • memory [] – Array zum Speichern früherer Positionen und ihrer Fitnesswerte.
  • memory_size – maximale Größe des Speichers für die Speicherung.
  • memory_index – aktueller Index im Speicher.
Fitness und Gewicht:
  • best_local_fitness – bester lokaler Fitness-Agent.
  • m – Masse des Agenten.
  • cB [] – Koordinaten der besten vom Agenten gefundenen Position.
  • fB – Wert der Fitnessfunktion für die beste Position.

Initialisierung (Init) – alle Arrays und Variablen werden mit Nullen oder Zufallswerten initialisiert. Die Array-Größen werden festgelegt, und die Einflüsse (Gewichte und Verzerrungen) werden mit kleinen Zufallswerten initialisiert.

Aktivierungsfunktionen – Tanh, ReLU, Sigmoid: verschiedene Aktivierungsfunktionen, die in neuronalen Netzen verwendet werden.

Eingabedaten aktualisieren (UpdateInputs) füllt das Eingabefeld mit den aktuellen Koordinaten, Geschwindigkeiten, der Entfernung zur besten bekannten Position und dem aktuellen Fitnesswert.

ForwardPass (Vorwärtsdurchlauf) berechnet die Ausgaben eines neuronalen Netzes auf der Grundlage von Eingaben, Gewichten und Verzerrungen unter Verwendung von Aktivierungsfunktionen.

Erfahrungslernen (Learn) aktualisiert die Gewichte und Vorurteile auf der Grundlage der gesammelten Erfahrung, wenn die aktuelle Erfahrung besser ist als die vorherige.

Das Speichern von Erfahrungen (MemorizeExperience) speichert die aktuellen Koordinaten und die Fitness des Agenten im Speicher.

Aktualisierung der besten Position (UpdateBestPosition) – aktualisiert die beste Position, wenn der aktuelle Fitnesswert besser ist als der zuvor gefundene.

//——————————————————————————————————————————————————————————————————————————————
// Neuro-boid agent structure
//——————————————————————————————————————————————————————————————————————————————
struct S_NeuroBoids_Agent
{
    double x       [];            // current coordinates
    double dx      [];            // current speeds
    double inputs  [];            // neuron inputs
    double outputs [];            // neuron outputs
    double weights [];            // neuron weights
    double biases  [];            // neuron biases
    double memory  [];            // memory of previous positions and their fitnesses
    int    memory_size;           // memory size for accumulating experience
    int    memory_index;          // current index in memory
    double best_local_fitness;    // best local agent fitness
    double m;                     // boid mass
    double cB [];                 // best position coordinates
    double fB;                    // fitness function value

    // Agent initialization
    void Init (int coords, int neuron_size)
    {
      ArrayResize (x, coords);
      ArrayResize (dx, coords);
      ArrayInitialize (x, 0.0);
      ArrayInitialize (dx, 0.0);

      // Initialize the best position structure
      ArrayResize     (cB, coords);
      ArrayInitialize (cB, 0.0);
      fB = -DBL_MAX;

      // Inputs: coordinates, speeds, distance to best, etc.
      int input_size = coords * 2 + 2; // x, dx, dist_to_best, current_fitness
      ArrayResize (inputs, input_size);
      ArrayInitialize (inputs, 0.0);

      // Outputs: Speed correction and adaptive factors for flock rules
      int output_size = coords + 3; // dx_correction + 3 adaptive parameters
      ArrayResize (outputs, output_size);
      ArrayInitialize (outputs, 0.0);

      // Weights and biases for each output
      ArrayResize (weights, input_size * output_size);
      ArrayInitialize (weights, 0.0);
      ArrayResize (biases, output_size);
      ArrayInitialize (biases, 0.0);

      // Initialize weights and biases with small random values
      for (int i = 0; i < ArraySize (weights); i++)
      {
        weights [i] = 0.01 * (MathRand () / 32767.0 * 2.0 - 1.0);
      }

      for (int i = 0; i < ArraySize (biases); i++)
      {
        biases [i] = 0.01 * (MathRand () / 32767.0 * 2.0 - 1.0);
      }

      // Initialize memory for accumulating experience
      memory_size = 10;
      ArrayResize (memory, memory_size * (coords + 1)); // coordinates + fitness
      ArrayInitialize (memory, 0.0);
      memory_index = 0;
      best_local_fitness = -DBL_MAX;

      // Initialize mass
      m = 0.5;
    }

    // Activation function - hyperbolic tangent
    double Tanh (double input_val)
    {
      return MathTanh (input_val);
    }

    // ReLU activation function
    double ReLU (double input_val)
    {
      return (input_val > 0.0) ? input_val : 0.0;
    }

    // Sigmoid activation function
    double Sigmoid (double input_val)
    {
      return 1.0 / (1.0 + MathExp (-input_val));
    }

    // Updating neural network inputs - Corrected version
    void UpdateInputs (double &global_best [], double current_fitness, int coords_count)
    {
      int input_index = 0;

      // Coordinates
      for (int c = 0; c < coords_count; c++)
      {
        inputs [input_index++] = x [c];
      }

      // Speeds
      for (int c = 0; c < coords_count; c++)
      {
        inputs [input_index++] = dx [c];
      }

      // Distance to the best global solution
      double dist_to_best = 0;
      for (int c = 0; c < coords_count; c++)
      {
        dist_to_best += MathPow (x [c] - global_best [c], 2);
      }
      dist_to_best = MathSqrt (dist_to_best);
      inputs [input_index++] = dist_to_best;

      // Current fitness function
      inputs [input_index++] = current_fitness;
    }

    // Direct distribution over the network
    void ForwardPass (int coords_count)
    {
      int input_size = ArraySize (inputs);
      int output_size = ArraySize (outputs);

      // For each output, calculate the weighted sum of the inputs + bias
      for (int o = 0; o < output_size; o++)
      {
        double sum = biases [o];

        for (int i = 0; i < input_size; i++)
        {
          sum += inputs [i] * weights [o * input_size + i];
        }

        // Apply different activation functions depending on the output
        if (o < coords_count) // Use the hyperbolic tangent to correct the speed
        {
          outputs [o] = Tanh (sum);
        }
        else // Use sigmoid for adaptive parameters
        {
          outputs [o] = Sigmoid (sum);
        }
      }
    }

    // Learning from accumulated experience
    void Learn (double learning_rate, int coords_count)
    {
      if (memory_index < memory_size) return; // Insufficient experience

      // Find the best experience in memory
      int best_index = 0;
      double best_fitness = memory [coords_count]; // The first fitness function in memory

      for (int i = 1; i < memory_size; i++)
      {
        double fitness = memory [i * (coords_count + 1) + coords_count];
        if (fitness > best_fitness)
        {
          best_fitness = fitness;
          best_index = i;
        }
      }

      // If the current experience is not better than the previous one, do not update the weights
      if (best_fitness <= best_local_fitness) return;

      best_local_fitness = best_fitness;

      // Simple method for updating weights
      int input_size = ArraySize (inputs);
      int output_size = ArraySize (outputs);

      // Simple form of gradient update
      for (int o = 0; o < output_size; o++)
      {
        for (int i = 0; i < input_size; i++)
        {
          int weight_index = o * input_size + i;
          if (weight_index < ArraySize (weights))
          {
            weights [weight_index] += learning_rate * outputs [o] * inputs [i];
          }
        }

        // Update offsets
        biases [o] += learning_rate * outputs [o];
      }
    }

    // Save current experience (coordinates and fitness)
    void MemorizeExperience (double fitness, int coords_count)
    {
      int offset = memory_index * (coords_count + 1);

      // Save the coordinates
      for (int c = 0; c < coords_count; c++)
      {
        memory [offset + c] = x [c];
      }

      // Save the fitness function
      memory [offset + coords_count] = fitness;

      // Update the memory index
      memory_index = (memory_index + 1) % memory_size;
    }

    // Update the agent's best position
    void UpdateBestPosition (double current_fitness, int coords_count)
    {
      if (current_fitness > fB)
      {
        fB = current_fitness;
        ArrayCopy (cB, x, 0, 0, WHOLE_ARRAY);
      }
    }
};

Die Klasse C_AO_NOA2 ist eine Implementierung des NOA2-Algorithmus und wird von der Basisklasse C_AO abgeleitet. Schauen wir uns die Struktur und die wichtigsten Aspekte des Kurses genauer an.

Wichtigste Parameter:
  • popSize – Anzahl der Agenten (boid).
  • cohesionWeight, cohesionDist – Gewicht und Abstand für die Kohäsionsregel.
  • separationWeight, separationDist – Gewicht und Abstand für die Trennungsregel.
  • alignmentWeight, alignmentDist – Gewicht und Abstand für die Ausrichtungsregel.
  • maxSpeed und minSpeed – maximale und minimale Geschwindigkeit der Agenten.
  • learningRate – Lernrate für das neuronale Netz.
  • neuralInfluence – Einfluss eines neuronalen Netzes auf die Bewegung von Agenten.
  • explorationRate – Wahrscheinlichkeit der zufälligen Erkundung des Lösungsraums.
  • m_stagnationCounter und m_prevBestFitness – Stagnationszähler und der vorherige beste Fitnesswert.
Methoden der Klasse:
  • SetParams () – Methode, die die Algorithmusparameter auf der Grundlage der im Array „params“ gespeicherten Werte festlegt.
  • Init () – Initialisierung, übernimmt Parameter zur Festlegung von Wertebereichen für Agenten. Die Methode legt die Anfangsbedingungen für die Arbeit des Algorithmus fest.
  • Moving () – zuständig für die Bewegung von Agenten auf der Grundlage verschiedener Interaktionsregeln.
  • Revision () – wird verwendet, um den Zustand der Agenten während der Ausführung des Algorithmus zu revidieren.

Agenten: S_NeuroBoids_Agent agent [] – Array von Agenten, die Boids in der Population repräsentieren.

Private Methoden (zur Verwendung innerhalb der Klasse):

  • CalculateMass () – Masse der Agenten.
  • Kohäsion () – Kohäsionsregel.
  • Trennung () – Trennungsregel.
  • Alignment () – Ausrichtungsregel.
  • LimitSpeed () – begrenzt die Geschwindigkeit des Agenten.
  • KeepWithinBounds () – hält Agenten innerhalb der angegebenen Grenzen.
  • Entfernung () – berechnet die Entfernung zwischen zwei Agenten.
  • ApplyNeuralControl () – wendet die Steuerung des neuronalen Netzes auf den Agenten an.
  • AdaptiveExploration() – implementiert die adaptive Erkundung.
//——————————————————————————————————————————————————————————————————————————————
// Class of neuron-like optimization algorithm inherited from C_AO
//——————————————————————————————————————————————————————————————————————————————
class C_AO_NOA2 : public C_AO
{
  public: //--------------------------------------------------------------------
  ~C_AO_NOA2 () { }
  C_AO_NOA2 ()
  {
    ao_name = "NOA2";
    ao_desc = "Neuroboids Optimization Algorithm 2 (joo)";
    ao_link = "https://www.mql5.com/en/articles/17497";

    popSize          = 50;     // population size

    cohesionWeight   = 0.6;    // cohesion weight
    cohesionDist     = 0.001;  // cohesion distance

    separationWeight = 0.005;  // separation weight
    separationDist   = 0.03;   // separation distance

    alignmentWeight  = 0.1;    // alignment weight
    alignmentDist    = 0.1;    // alignment distance

    maxSpeed         = 0.001;  // maximum speed
    minSpeed         = 0.0001; // minimum speed 

    learningRate     = 0.01;   // neural network learning speed
    neuralInfluence  = 0.3;    // influence of the neural network on movement
    explorationRate  = 0.1;    // random exploration probability

    ArrayResize (params, 12);

    params [0].name  = "popSize";          params [0].val = popSize;

    params [1].name  = "cohesionWeight";   params [1].val  = cohesionWeight;
    params [2].name  = "cohesionDist";     params [2].val  = cohesionDist;

    params [3].name  = "separationWeight"; params [3].val  = separationWeight;
    params [4].name  = "separationDist";   params [4].val  = separationDist;

    params [5].name  = "alignmentWeight";  params [5].val  = alignmentWeight;
    params [6].name  = "alignmentDist";    params [6].val  = alignmentDist;

    params [7].name  = "maxSpeed";         params [7].val  = maxSpeed;
    params [8].name  = "minSpeed";         params [8].val  = minSpeed;

    params [9].name  = "learningRate";     params [9].val  = learningRate;
    params [10].name = "neuralInfluence";  params [10].val = neuralInfluence;
    params [11].name = "explorationRate";  params [11].val = explorationRate;

    // Initialize the stagnation counter and the previous best fitness value
    m_stagnationCounter = 0;
    m_prevBestFitness   = -DBL_MAX;
  }

  void SetParams ()
  {
    popSize          = (int)params [0].val;

    cohesionWeight   = params [1].val;
    cohesionDist     = params [2].val;

    separationWeight = params [3].val;
    separationDist   = params [4].val;

    alignmentWeight  = params [5].val;
    alignmentDist    = params [6].val;

    maxSpeed         = params [7].val;
    minSpeed         = params [8].val;

    learningRate     = params [9].val;
    neuralInfluence  = params [10].val;
    explorationRate  = params [11].val;
  }

  bool Init (const double &rangeMinP [], const double &rangeMaxP [], const double &rangeStepP [], const int epochsP = 0);
  void Moving   ();
  void Revision ();

  //----------------------------------------------------------------------------
  double cohesionWeight;     // cohesion weight
  double cohesionDist;       // cohesion distance

  double separationWeight;   // separation weight
  double separationDist;     // separation distance

  double alignmentWeight;    // alignment weight
  double alignmentDist;      // alignment distance

  double minSpeed;           // minimum speed
  double maxSpeed;           // maximum speed

  double learningRate;       // neural network learning speed
  double neuralInfluence;    // influence of the neural network on movement
  double explorationRate;    // random exploration probability

  int m_stagnationCounter;   // stagnation counter
  double m_prevBestFitness;  // previous best fitness value

  S_NeuroBoids_Agent agent []; // agents (boids)

  private: //-------------------------------------------------------------------
  double distanceMax;     // maximum distance
  double speedMax [];     // maximum speeds by measurements

  int neuron_size;        // neuron size (number of inputs)

  void   CalculateMass       ();                                  // calculation of agent masses 
  void   Cohesion            (S_NeuroBoids_Agent &boid, int pos); // cohesion rule
  void   Separation          (S_NeuroBoids_Agent &boid, int pos); // separation rule
  void   Alignment           (S_NeuroBoids_Agent &boid, int pos); // alignment rule
  void   LimitSpeed          (S_NeuroBoids_Agent &boid);          // speed limit
  void   KeepWithinBounds    (S_NeuroBoids_Agent &boid);          // keep within bounds
  double Distance            (S_NeuroBoids_Agent &boid1, S_NeuroBoids_Agent &boid2); // calculate distance
  void   ApplyNeuralControl  (S_NeuroBoids_Agent &boid, int pos); // apply neural control
  void   AdaptiveExploration ();                                  // adaptive research
};
//——————————————————————————————————————————————————————————————————————————————

Die Init-Methode ist für die Initialisierung der Parameter des Algorithmus und der Agenten zuständig und beginnt ihre Arbeit, indem sie StandardInit aufruft und ihr Arrays mit den Minimal- und Maximalwerten des Bereichs und der Suchschritte übergibt. Wenn die Standardinitialisierung fehlschlägt, gibt die Methode sofort „false“ zurück. Die Größe des Neurons, das im neuronalen Netz des Agenten verwendet werden soll, wird festgelegt.

In diesem Fall wird die Größe als die Anzahl der Koordinaten multipliziert mit 2 berechnet, und zwei zusätzliche Werte – dist_to_best und current_fitness – werden hinzugefügt. Die Größe des Agentenarrays wird entsprechend der angegebenen popSize geändert. Dann wird für jeden Agenten die Init-Methode aufgerufen, die seine Parameter, einschließlich des neuronalen Netzes, initialisiert.

Berechnen der maximalen Entfernung und Geschwindigkeit:

  • Die Variable distanceMax wird auf Null initialisiert.
  • Für jede Koordinate wird der maximale Geschwindigkeitswert auf der Grundlage der Differenz zwischen dem maximalen und dem minimalen Bereichswert berechnet.
  • Die maximale Entfernung wird auch als Quadratwurzel aus der Summe der Quadrate der Höchstgeschwindigkeiten berechnet.

Der Stagnationszähler (m_stagnationCounter) wird auf Null zurückgesetzt, und die Variable, die den vorherigen besten Fitnesswert (m_prevBestFitness) speichert, wird auf den kleinstmöglichen Wert gesetzt. Die Methode legt verschiedene globale Variablen fest, wie Kohärenz, Trennungs- und Ausrichtungsgewichte, Höchst- und Mindestgeschwindigkeiten, Lernrate, Einfluss des neuronalen Netzes und Erkundungswahrscheinlichkeit. Wenn alle Schritte erfolgreich waren, gibt die Methode „true“ zurück, was die erfolgreiche Initialisierung bestätigt.

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

  //----------------------------------------------------------------------------
  // Determine the size of a neuron
  neuron_size = coords * 2 + 2; // x, dx, dist_to_best, current_fitness

  // Initialize the agents
  ArrayResize (agent, popSize);
  for (int i = 0; i < popSize; i++)
  {
    agent [i].Init (coords, neuron_size);
  }

  distanceMax = 0;
  ArrayResize (speedMax, coords);

  for (int c = 0; c < coords; c++)
  {
    speedMax [c] = rangeMax [c] - rangeMin [c];
    distanceMax += MathPow (speedMax [c], 2);
  }

  distanceMax = MathSqrt (distanceMax);

  // Reset the stagnation counter and the previous best fitness value
  m_stagnationCounter = 0;
  m_prevBestFitness   = -DBL_MAX;

  GlobalVariableSet ("#reset", 1.0);

  GlobalVariableSet ("1cohesionWeight",   params [1].val);
  GlobalVariableSet ("2cohesionDist",     params [2].val);

  GlobalVariableSet ("3separationWeight", params [3].val);
  GlobalVariableSet ("4separationDist",   params [4].val);

  GlobalVariableSet ("5alignmentWeight",  params [5].val);
  GlobalVariableSet ("6alignmentDist",    params [6].val);

  GlobalVariableSet ("7maxSpeed",         params [7].val);
  GlobalVariableSet ("8minSpeed",         params [8].val);

  GlobalVariableSet ("9learningRate",     params [9].val);
  GlobalVariableSet ("10neuralInfluence", params [10].val);
  GlobalVariableSet ("11explorationRate", params [11].val);

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

Die Methode Moving ist verantwortlich für die Bewegung der Agenten in der Umgebung auf der Grundlage der empfangenen Daten und der Interaktion zwischen ihnen. Zunächst prüft die Methode den Wert der globalen Variable #reset. Ist sie gleich 1,0, werden die Parameter zurückgesetzt (die Variable „revision“ wird auf „false“ gesetzt und kehrt dann zu 0,0 zurück). Die Werte verschiedener Parameter wie die Kohäsions-, Trennungs- und Ausrichtungsgewichte, die minimalen und maximalen Geschwindigkeiten, die Lernrate, der Einfluss des neuronalen Netzes und das Explorationsverhältnis werden aus den globalen Variablen extrahiert. Diese Parameter können konfiguriert werden, um das Verhalten der Agenten zu ändern.

Wenn die Variable „revision“ „false“ ist, werden die x-Positionen und dx-Geschwindigkeiten der Agenten initialisiert. Für jede Koordinate werden Zufallswerte aus einem bestimmten Bereich verwendet, und die Geschwindigkeiten werden auf kleine Zufallswerte gesetzt. Jede Koordinate speichert auch den Zustand des Agenten auf der Grundlage seiner aktuellen Position. Die Methode AdaptiveExploration () wird für die adaptive Exploration aufgerufen, die das Verhalten der Agenten in Abhängigkeit vom Stagnationszustand ändern kann.

In der Hauptschleife der Agentenbewegung (für jeden):

  • Speicher die aktuelle Erfahrung des Agenten.
  • Aktualisiere die beste Position, die der Agent gefunden hat.
  • Aktualisiere die Eingabedaten des neuronalen Netzes des Agenten.
  • Gib die Daten direkt weiter durch das neuronale Netz.
  • Trainiere die Agenten auf der Grundlage ihrer gesammelten Erfahrungen.
Berechnung der Masse: Aufruf der Methode CalculateMass () auf, um die Interaktionen zwischen den Agenten anhand ihrer Masse zu bewerten.

    Anwendung von Regeln und Bewegung: Die Schleife durch die Agenten beginnt erneut, und die Standardregeln des Boid-Algorithmus werden auf jeden Agenten angewendet:

    • Kohäsion: Die Akteure streben danach, sich einander anzunähern.
    • Trennung: Agenten vermeiden zu große Nähe zueinander, um Kollisionen zu vermeiden.
    • Ausrichtung: Die Agenten versuchen, sich in dieselbe Richtung wie ihre Nachbarn zu bewegen.
    Die Methode ApplyNeuralControl () wird für die Steuerung über ein neuronales Netz aufgerufen. Die Geschwindigkeit der Agenten wird mit LimitSpeed() begrenzt, und mit KeepWithinBounds () wird sichergestellt, dass die Agenten innerhalb der Grenzen des angegebenen Gebiets bleiben. Die Positionen der Agenten werden bei jeder Koordinate aktualisiert, indem die aktuelle Geschwindigkeit zur aktuellen Position und zum aktuellen Zustand hinzugefügt wird.

    //——————————————————————————————————————————————————————————————————————————————
    void C_AO_NOA2::Moving ()
    {
      double reset = GlobalVariableGet ("#reset");
      if (reset == 1.0)
      {
        revision = false;
        GlobalVariableSet ("#reset", 0.0);
      }
    
      // Get parameters from global variables for interactive configuration
      cohesionWeight   = GlobalVariableGet ("1cohesionWeight");
      cohesionDist     = GlobalVariableGet ("2cohesionDist");
    
      separationWeight = GlobalVariableGet ("3separationWeight");
      separationDist   = GlobalVariableGet ("4separationDist");
    
      alignmentWeight  = GlobalVariableGet ("5alignmentWeight");
      alignmentDist    = GlobalVariableGet ("6alignmentDist");
    
      maxSpeed         = GlobalVariableGet ("7maxSpeed");
      minSpeed         = GlobalVariableGet ("8minSpeed");
    
      learningRate     = GlobalVariableGet ("9learningRate");
      neuralInfluence  = GlobalVariableGet ("10neuralInfluence");
      explorationRate  = GlobalVariableGet ("11explorationRate");
    
      // Initialization of initial positions and speeds
      if (!revision)
      {
        for (int i = 0; i < popSize; i++)
        {
          for (int c = 0; c < coords; c++)
          {
            agent [i].x [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]);
            agent [i].dx [c] = (rangeMax [c] - rangeMin [c]) * u.RNDfromCI (-1.0, 1.0) * 0.001;
    
            a [i].c [c] = u.SeInDiSp (agent [i].x [c], rangeMin [c], rangeMax [c], rangeStep [c]);
          }
        }
    
        revision = true;
        return;
      }
    
      // Adaptive research depending on stagnation
      AdaptiveExploration ();
    
      //----------------------------------------------------------------------------
      // Main loop of boid movement
      for (int i = 0; i < popSize; i++)
      {
        // Save the current experience
        agent [i].MemorizeExperience (a [i].f, coords);
    
        // Update the agent's best position
        agent [i].UpdateBestPosition (a [i].f, coords);
    
        // Update the neural network inputs
        agent [i].UpdateInputs (cB, a [i].f, coords);
    
        // Forward propagation through the neural network
        agent [i].ForwardPass (coords);
    
        // Learning from accumulated experience
        agent [i].Learn (learningRate, coords);
      }
    
      // Calculate masses
      CalculateMass ();
    
      // Application of rules and movement
      for (int i = 0; i < popSize; i++)
      {
        // Standard rules of the boid algorithm
        Cohesion (agent [i], i);
        Separation (agent [i], i);
        Alignment (agent [i], i);
    
        // Apply neural control
        ApplyNeuralControl (agent [i], i);
    
        // Speed limit and keeping within bounds
        LimitSpeed (agent [i]);
        KeepWithinBounds (agent [i]);
    
        // Update positions
        for (int c = 0; c < coords; c++)
        {
          agent [i].x [c] += agent [i].dx [c];
          a [i].c [c] = u.SeInDiSp (agent [i].x [c], rangeMin [c], rangeMax [c], rangeStep [c]);
        }
      }
    }
    //——————————————————————————————————————————————————————————————————————————————
    

    Die Methode CalculateMass berechnet die „Masse“ der Agenten auf der Grundlage ihrer Fitness (Produktivität) und normalisiert diese Werte. Die Variablen maxMass und minMass werden mit -DBL_MAX bzw. DBL_MAX initialisiert. Diese Variablen werden verwendet, um die höchsten und niedrigsten Fitnesswerte in der gesamten Agentenpopulation zu ermitteln. Die Methode prüft, ob es mindestens einen Agenten gibt, indem sie überprüft, ob „popSize“ (Populationsgröße) größer als Null ist. Wenn es keine Bearbeiter gibt, wird die Methode abgebrochen. In der ersten Schleife werden alle Agenten durchlaufen (von 0 bis popSize), und für jeden einzelnen wird geprüft, ob seine Fitness (der Wert von a[i].f) größer als die aktuelle maxMass oder kleiner als minMass ist.

    Als Ergebnis dieser Schleife werden die maximalen und minimalen Fitnesswerte aller Agenten ermittelt. In der zweiten Schleife werden wieder alle Agenten durchlaufen, und für jeden einzelnen wird seine „Masse“ (Variable m) mit Hilfe der Funktion u.Scale berechnet, die die Fitnesswerte normalisiert. 

    //——————————————————————————————————————————————————————————————————————————————
    void C_AO_NOA2::CalculateMass ()
    {
      double maxMass = -DBL_MAX;
      double minMass = DBL_MAX;
    
      // Check for data presence before calculations
      if (popSize <= 0) return;
    
      for (int i = 0; i < popSize; i++)
      {
        if (a [i].f > maxMass) maxMass = a [i].f;
        if (a [i].f < minMass) minMass = a [i].f;
      }
    
      for (int i = 0; i < popSize; i++)
      {
        agent [i].m = u.Scale (a [i].f, minMass, maxMass, 0.1, 1.0);
      }
    }
    //——————————————————————————————————————————————————————————————————————————————
    

    Die Methode ApplyNeuralControl ist für die Anwendung der auf den Ausgaben des neuronalen Netzes basierenden Steuerung auf den Agenten vom Typ „boid“ zuständig. Die Methode iteriert über alle „coords“-Koordinaten des Agenten. Für jede c-Koordinate wird geprüft, ob der aktuelle Index nicht über die Grenzen des Ausgabe-Arrays boid.outputs hinausgeht. Wenn der Index korrekt ist, wird die Geschwindigkeit des Agenten dx[c] auf der Grundlage des entsprechenden Wertes aus den Ausgaben des neuronalen Netzes, multipliziert mit seinem neuronalen Einfluss, angepasst.

    Die Größe des Arrays boid.outputs wird abgefragt, um weitere Prüfungen zu vereinfachen. Wir prüfen, ob die entsprechenden Indizes innerhalb des Ausgabe-Arrays für cohesion_factor, separation_factor, alignment_factor cohesion, separation und alignment ratios liegen. Liegt der Index außerhalb des zulässigen Bereichs, wird der Standardwert 0,5 zugewiesen. Diese Verhältnisse werden zur Skalierung der Basisgewichte verwendet, um den Einfluss des neuronalen Netzes zu berücksichtigen. Sie beeinflussen das Verhalten der Agenten, indem sie die Gewichte für Konvergenz, Trennung und Ausrichtung ändern:

    • local_cohesion legt das Kohäsionsgewicht fest.
    • local_separation legt das Trennungsgewicht fest.
    • local_alignment legt das Ausrichtungsgewicht fest.

    Die Methode prüft, ob eine zufällige Erkundung mit der angegebenen xplorationRate-Wahrscheinlichkeit durchgeführt werden soll. Wenn die Bedingung erfüllt ist, wird eine zufällige c-Koordinate für die Störung ausgewählt. Die Größe der Störung wird auf der Grundlage der Masse des Agenten (die ein Maß für seine Fitness ist) und des Bereichs der möglichen Werte für diese Koordinate berechnet. Auf diese Weise kann das Ausmaß der zufälligen Schwankungen unabhängig von der Masse des Agens kontrolliert werden. Eine zufällige Störung aus dem Bereich von -perturbation_size bis +perturbation_size wird der Geschwindigkeit dx[c] hinzugefügt. Die Methode ApplyNeuralControl integriert die Ergebnisse der neuronalen Netzoperationen in das Bewegungsmanagement der Agenten. Sie passt ihre Geschwindigkeit auf der Grundlage von Daten an, und die Methode berücksichtigt auch die Notwendigkeit einer zufälligen Erkundung.

    //——————————————————————————————————————————————————————————————————————————————
    // Apply neural control to a boid
    void C_AO_NOA2::ApplyNeuralControl (S_NeuroBoids_Agent &boid, int pos)
    {
      // Use the neural network outputs to correct the speed
      for (int c = 0; c < coords; c++)
      {
        // Make sure the index is not outside the array bounds 
        if (c < ArraySize (boid.outputs))
        {
          // Apply neural speed correction with a given influence
          boid.dx [c] += boid.outputs [c] * neuralInfluence;
        }
      }
    
      // Use the neural network outputs to adapt the flock parameters
      // Check that the indices do not go beyond the array bounds
      int output_size = ArraySize (boid.outputs);
    
      double cohesion_factor   = (coords < output_size) ? boid.outputs [coords] : 0.5;
      double separation_factor = (coords + 1 < output_size) ? boid.outputs [coords + 1] : 0.5;
      double alignment_factor  = (coords + 2 < output_size) ? boid.outputs [coords + 2] : 0.5;
    
      // Scale the base weights considering neural adaptation
      // These variables are local and do not change global parameters
      double local_cohesion   = cohesionWeight * (0.5 + cohesion_factor);
      double local_separation = separationWeight * (0.5 + separation_factor);
      double local_alignment  = alignmentWeight * (0.5 + alignment_factor);
    
      // Random study with a given probability
      if (u.RNDprobab () < explorationRate)
      {
        // Select a random coordinate for perturbation
        int c = (int)u.RNDfromCI (0, coords - 1);
    
        // Adaptive perturbation size depending on mass (fitness)
        double perturbation_size = (1.0 - boid.m) * (rangeMax [c] - rangeMin [c]) * 0.01;
    
        // Add random perturbation
        boid.dx [c] += u.RNDfromCI (-perturbation_size, perturbation_size);
      }
    }
    //——————————————————————————————————————————————————————————————————————————————
    
    

    Die Methode AdaptiveExploration der Klasse C_AO_NOA2 implementiert eine adaptive Exploration in Abhängigkeit vom Stadium der Stagnation im Optimierungsprozess. Die Methode beginnt mit der Überprüfung, ob sich der Wert der Zielfunktion fB im Vergleich zum vorherigen besten Wert m_prevBestFitness geändert hat. Ist die Differenz zwischen diesen beiden Werten kleiner als 0,000001, wird davon ausgegangen, dass kein Fortschritt vorliegt, und der m_stagnationCounter wird erhöht. Andernfalls wird die Stagnation beendet, der Zähler wird zurückgesetzt, und der aktuelle fB-Wert wird als neuer Bestwert gespeichert.

    Wenn die Stagnation 20 übersteigt, erhöht sich die Wahrscheinlichkeit einer zufälligen Erkundung (explorationRate), aber sie ist auf 0,5 begrenzt, um eine zu hohe Wahrscheinlichkeit zufälliger Veränderungen zu verhindern. Alle 50 Iterationen der Stagnation erfolgt ein teilweiser Neustart: 70 % der Agenten in der Population werden mit neuen Zufallswerten innerhalb der angegebenen Bereiche rangeMin und rangeMax neu gestartet. Gleichzeitig bleiben die übrigen Top-Agenten mit ihren derzeitigen Positionen unverändert. Nach einem Neustart wird der Stagnationszähler zurückgesetzt.

    Wenn es einen Fortschritt gibt und die Anzahl der „params“-Parameter größer als 11 ist, wird die Erkundungswahrscheinlichkeit „explorationRate“ aus dem Parameter-Array gesetzt. Sind weniger Parameter vorhanden, wird der Standardwert 0,1 eingestellt.

    //——————————————————————————————————————————————————————————————————————————————
    // Adaptive research depending on stagnation
    void C_AO_NOA2::AdaptiveExploration ()
    {
      // Determine if there is progress in the search
      if (MathAbs (fB - m_prevBestFitness) < 0.000001)
      {
        m_stagnationCounter++;
      }
      else
      {
        m_stagnationCounter = 0;
        m_prevBestFitness = fB;
      }
    
      // Increase research during stagnation
      if (m_stagnationCounter > 20)
      {
        // Increase the probability of random exploration
        explorationRate = MathMin (0.5, explorationRate * 1.5);
    
        // Perform a partial restart every 50 stagnation iterations
        if (m_stagnationCounter % 50 == 0)
        {
          // Restart 70% of the population leaving the best agents
          int restart_count = (int)(popSize * 0.7);
    
          for (int i = popSize - restart_count; i < popSize; i++)
          {
            for (int c = 0; c < coords; c++)
            {
              agent [i].x [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]);
              agent [i].dx [c] = (rangeMax [c] - rangeMin [c]) * u.RNDfromCI (-1.0, 1.0) * 0.001;
            }
          }
    
          // Reset the stagnation counter
          m_stagnationCounter = 0;
        }
      }
      else
      {
        // If progress is good enough, use the normal research level 
        if (11 < ArraySize (params))
        {
          explorationRate = params [11].val;
        }
        else
        {
          explorationRate = 0.1; // default value
        }
      }
    }
    //——————————————————————————————————————————————————————————————————————————————
    

    Die Cohesion-Methode der Klasse C_AO_NOA2 ist für die Implementierung des „Kohäsions“-Verhaltens für das agentenbasierte Modell von Boids verantwortlich. Erklärung der Variablen:

    • centerX – Array wird verwendet, um die Koordinaten des Massenschwerpunkts der benachbarten Agenten zu speichern. Die Größe des Arrays entspricht der Anzahl der „coords“-Koordinaten.
    • numNeighbors – Nachbarschaftszähler, der die Anzahl der Agenten innerhalb der angegebenen Entfernung (unter Berücksichtigung der Masse) erfasst.
    • sumMass – die Summe der Massen der benachbarten Agenten wird verwendet, um die Koordinaten des Massenschwerpunkts zu normalisieren.

    Die erste Schleife (von 0 bis popSize) summiert die Massen aller Agenten in der Population, mit Ausnahme des aktuellen Boids (Agenten) am „pos“-Index. Die zweite Schleife durchläuft alle Agenten und prüft, ob sie sich innerhalb der maximalen Entfernung multipliziert mit dem cohesionDist-Faktor vom aktuellen Boid befinden. Ist der Abstand zu einem Agenten kleiner als der angegebene Höchstwert, werden die Koordinaten dieses Agenten (unter Berücksichtigung seiner Masse) zu centerX addiert, und der Zähler numNeighbors wird erhöht.

    Nachdem die Koordinatensummen und Massen der Nachbarn berechnet wurden, wird geprüft, ob es Nachbarn gibt (d.h. numNeighbors > 0) und die Summe der Massen (sumMass > 0.0), und wenn Nachbarn gefunden werden, werden die Koordinaten des Massenschwerpunkts normalisiert (durch Division durch die Summe der Massen). Dann wird die aktuelle dx-Geschwindigkeit des Boids in Richtung des Massenschwerpunkts mit dem gegebenen cohesionWeight-Gewicht erhöht, d.h. der Boid bewegt sich in Richtung des Massenschwerpunkts seiner Nachbarn, wobei deren Massen berücksichtigt werden.

    Die Kohäsionsmethode berechnet den Massenschwerpunkt und passt die aktuelle Geschwindigkeit des Boids so an, dass es zu diesem Zentrum „konvergiert“. Dieses Verhalten ist einer der wichtigsten Aspekte bei der Simulation von Herden- oder Gruppenverhalten. Mit Parametern wie cohesionDist und cohesionWeight lässt sich der Abstand, in dem dieses Verhalten zum Tragen kommt, und das Ausmaß, in dem der Massenschwerpunkt die Bewegung der Boids beeinflusst, steuern.

    //——————————————————————————————————————————————————————————————————————————————
    // Find the center of mass of other boids and adjust the speed towards the center of mass
    void C_AO_NOA2::Cohesion (S_NeuroBoids_Agent &boid, int pos)
    {
      double centerX [];
      ArrayResize (centerX, coords);
      ArrayInitialize (centerX, 0.0);
    
      int numNeighbors = 0;
      double sumMass = 0;
    
      for (int i = 0; i < popSize; i++)
      {
        if (pos != i) sumMass += agent [i].m;
      }
    
      for (int i = 0; i < popSize; i++)
      {
        if (pos != i)
        {
          if (Distance (boid, agent [i]) < distanceMax * cohesionDist)
          {
            for (int c = 0; c < coords; c++)
            {
              centerX [c] += agent [i].x [c] * agent [i].m;
            }
    
            numNeighbors++;
          }
        }
      }
    
      if (numNeighbors > 0 && sumMass > 0.0)
      {
        for (int c = 0; c < coords; c++)
        {
          centerX [c] /= sumMass;
          boid.dx [c] += (centerX [c] - boid.x [c]) * cohesionWeight;
        }
      }
    }
    //——————————————————————————————————————————————————————————————————————————————
    

    Die Methode Separation der Klasse C_AO_NOA2 implementiert das Trennungsverhalten für die Modellagenten, das Kollisionen zwischen dem Boid und benachbarten Agenten verhindern soll. moveX-Parameter, wie das cohesionDist-Array, werden verwendet, um die Verschiebungsvektoren zu speichern, die den Boid von anderen Agenten wegschieben.

    Die Größe des Arrays entspricht der Anzahl der „coords“-Koordinaten. Das moveX-Array wird auf Null initialisiert, um die Akkumulation von Zufallswerten zu vermeiden, dann wird über alle Agenten in der Population iteriert (von 0 bis popSize). Dann prüfen wir, ob sich der Agent in der Nähe des aktuellen Boids befindet. Wenn der Abstand zwischen dem Boid und dem Agenten kleiner ist als der angegebene Maximalwert (multipliziert mit dem Faktor separationDist), werden für jede Koordinate die Agenten-Koordinaten von den aktuellen Boid-Koordinaten subtrahiert, um einen Vektor zu erzeugen, der auf den aktuellen Boid zeigt (d.h. vom Agenten wegschiebt).

    Nach Abschluss der Schleife durch die Agenten wird die aktuelle dx-Geschwindigkeit des Boids um den berechneten moveX-Verschiebungsvektor multipliziert mit dem separationWeight-Verhältnis erhöht. Auf diese Weise lässt sich die Abstoßungskraft steuern: Je höher der Wert für separationWeight, desto mehr vermeidet das Boid Kollisionen.

    Die Trennungsmethode implementiert ein Verhalten, bei dem der Boid an seinen nächsten Nachbarn abprallt, um Kollisionen zu vermeiden. Dies ist ein wichtiger Aspekt bei der Simulation von Herdenverhalten, der dazu beiträgt, den persönlichen Raum für jeden Agenten zu erhalten und natürlichere Interaktionen zwischen ihnen zu fördern. Mit den Parametern separationDist und separationWeight können wir den Radius bzw. die Stärke des Abstoßungseffekts flexibel einstellen.

    //——————————————————————————————————————————————————————————————————————————————
    // Pushing away from other boids to avoid collisions
    void C_AO_NOA2::Separation (S_NeuroBoids_Agent &boid, int pos)
    {
      double moveX [];
      ArrayResize (moveX, coords);
      ArrayInitialize (moveX, 0.0);
    
      for (int i = 0; i < popSize; i++)
      {
        if (pos != i)
        {
          if (Distance (boid, agent [i]) < distanceMax * separationDist)
          {
            for (int c = 0; c < coords; c++)
            {
              moveX [c] += boid.x [c] - agent [i].x [c];
            }
          }
        }
      }
    
      for (int c = 0; c < coords; c++)
      {
        boid.dx [c] += moveX [c] * separationWeight;
      }
    }
    //——————————————————————————————————————————————————————————————————————————————
    

    Die Methode Alignment der Klasse C_AO_NOA2 implementiert das Ausrichtungsverhalten für Agenten im Modell. Dieses Verhalten veranlasst die Boids, ihre Geschwindigkeiten mit den benachbarten Agenten zu koordinieren, um eine harmonischere und koordinierte Gruppenbewegung zu schaffen. avgDX ist ein Array, das verwendet wird, um den durchschnittlichen Geschwindigkeitsvektor der benachbarten Agenten zu speichern. Die Größe des Arrays entspricht der Anzahl der Koordinaten, numNeighbors ist ein Zähler, der die Anzahl der Nachbarn innerhalb einer bestimmten Entfernung angibt.

    Die Schleife durchläuft alle Agenten in der Population. Innerhalb der Schleife wird geprüft, ob der aktuelle Bearbeiter derselbe Boid ist. Ist der Abstand zu einem anderen Agenten kleiner als der angegebene Maximalwert multipliziert mit dem Faktor alignmentDist, wird die aktuelle dx-Geschwindigkeit dieses Agenten zu avgDX addiert. Der Zähler numNeighbors wird um 1 erhöht. Nach Abschluss der Schleife wird, wenn Nachbarn gefunden wurden (d. h. numNeighbors > 0), die Durchschnittsgeschwindigkeit (avgDX) berechnet, indem der summierte Geschwindigkeitsvektor durch die Anzahl der Nachbarn geteilt wird. Dann wird die aktuelle dx-Geschwindigkeit des Boids an die Durchschnittsgeschwindigkeit seiner Nachbarn angepasst, wobei das Verhältnis alignmentWeight berücksichtigt wird. 

    Die Methode Alignment ermöglicht es einem Boid, seine Geschwindigkeit an die seiner Nachbarn anzupassen. Dieses Verhalten trägt dazu bei, dass sich Gruppen von Boiden geschlossener bewegen und die Wahrscheinlichkeit von Konflikten oder abrupten Richtungswechseln verringert wird. Mit den Parametern alignmentDist und alignmentWeight wird der Radius des Ausrichtungseffekts bzw. der Grad der Beeinflussung der Geschwindigkeit des aktuellen Boids durch die Durchschnittsgeschwindigkeit der Nachbarn festgelegt. 
    //——————————————————————————————————————————————————————————————————————————————
    // Align speed with other boids
    void C_AO_NOA2::Alignment (S_NeuroBoids_Agent &boid, int pos)
    {
      double avgDX [];
      ArrayResize (avgDX, coords);
      ArrayInitialize (avgDX, 0.0);
    
      int numNeighbors = 0;
    
      for (int i = 0; i < popSize; i++)
      {
        if (pos != i)
        {
          if (Distance (boid, agent [i]) < distanceMax * alignmentDist)
          {
            for (int c = 0; c < coords; c++)
            {
              avgDX [c] += agent [i].dx [c];
            }
    
            numNeighbors++;
          }
        }
      }
    
      if (numNeighbors > 0)
      {
        for (int c = 0; c < coords; c++)
        {
          avgDX [c] /= numNeighbors;
          boid.dx [c] += (avgDX [c] - boid.dx [c]) * alignmentWeight;
        }
      }
    }
    //——————————————————————————————————————————————————————————————————————————————
    

    Die Methode LimitSpeed der Klasse C_AO_NOA2 soll die Geschwindigkeit der Agenten (Boids) im Modell kontrollieren, um sicherzustellen, dass die Geschwindigkeiten der Boids innerhalb eines bestimmten Bereichs liegen. „speed“ ist eine Variable zur Speicherung der aktuellen Geschwindigkeit der Boids, die als Länge des Geschwindigkeitsvektors berechnet wird. Die Koordinatenschleife (coords) berechnet das Quadrat der Geschwindigkeit für jede Koordinate (durch Addition von boid.dx[c] * boid.dx[c]) und summiert sie auf. Damit lässt sich das Quadrat der Länge des Geschwindigkeitsvektors berechnen. Die Länge (oder tatsächliche Geschwindigkeit) wird dann mit der Funktion MathSqrt ermittelt, die die Quadratwurzel aus der Summe der Quadrate berechnet. Ist die Geschwindigkeit größer als ein kleiner Wert (1e-10), wird das Programm weiter ausgeführt, ist die aktuelle Geschwindigkeit kleiner als die zulässige Mindestgeschwindigkeit (definiert als speedMax[0] * minSpeed), wird der Geschwindigkeitsvektor boid.dx normalisiert (geteilt durch seine aktuelle Länge) und auf einen Wert erhöht, der dem zulässigen Mindestwert entspricht.

    Ist die aktuelle Geschwindigkeit größer als die zulässige Höchstgeschwindigkeit (speedMax[0] * maxSpeed), wird der Geschwindigkeitsvektor ebenfalls normalisiert und auf den Wert der Höchstgeschwindigkeit gesetzt. Ist die Geschwindigkeit fast Null (Null oder sehr nahe bei Null), wird jede Koordinate des Geschwindigkeitsvektors durch einen kleinen Zufallswert ersetzt, der mit der Funktion u.RNDfromCI(-1.0, 1.0) multipliziert mit der Höchstgeschwindigkeit ermittelt wird.

    Die LimitSpeed-Methode sorgt dafür, dass die Geschwindigkeit der Boote innerhalb akzeptabler Grenzen gehalten wird und verhindert, dass sie sich zu langsam oder zu schnell bewegen. Dieses Verhalten ermöglicht realistischere Agentensimulationen, da es keine großen Geschwindigkeitsschwankungen zulässt, die zu unnatürlichen Bewegungen führen könnten. Die Parameter minSpeed und maxSpeed können konfiguriert werden, um das Verhalten und die Geschwindigkeit der Agenten je nach Simulationsaufgabe anzupassen.

    //——————————————————————————————————————————————————————————————————————————————
    // Speed limit
    void C_AO_NOA2::LimitSpeed (S_NeuroBoids_Agent &boid)
    {
      double speed = 0;
    
      for (int c = 0; c < coords; c++)
      {
        speed += boid.dx [c] * boid.dx [c];
      }
    
      speed = MathSqrt (speed);
    
      // If the speed is not zero (prevent division by zero)
      if (speed > 1e-10)
      {
        // If the speed is too low, increase it
        if (speed < speedMax [0] * minSpeed)
        {
          for (int c = 0; c < coords; c++)
          {
            boid.dx [c] = boid.dx [c] / speed * speedMax [c] * minSpeed;
          }
        }
        // If the speed is too high, reduce it
        else
          if (speed > speedMax [0] * maxSpeed)
          {
            for (int c = 0; c < coords; c++)
            {
              boid.dx [c] = boid.dx [c] / speed * speedMax [c] * maxSpeed;
            }
          }
      }
      else
      {
        // If the speed is almost zero, set a small random speed
        for (int c = 0; c < coords; c++)
        {
          boid.dx [c] = u.RNDfromCI (-1.0, 1.0) * speedMax [c] * minSpeed;
        }
      }
    }
    //——————————————————————————————————————————————————————————————————————————————
    

    Die Methode KeepWithinBounds der Klasse C_AO_NOA2 dient dazu, den Agenten (boid) innerhalb der angegebenen Grenzen zu halten. Wenn ein Boid zu nahe an den Rand der Region gerät, ändert diese Methode seine Richtung und sorgt für einen gewissen Rückstoß in die Grenzen. Die Methode beginnt mit einer Schleife über alle Koordinaten, was das Arbeiten im mehrdimensionalen Raum ermöglicht.

    Für jede Koordinate wird geprüft, ob die Boid-Position (boid.x[c]) unterhalb der Mindestgrenze (rangeMin[c]) liegt. Ist dies der Fall, wird die Geschwindigkeitsrichtung (boid.dx[c]) mit „boid.dx[c] *= -1.0“ invertiert. Das bedeutet, dass sich der Boid in die entgegengesetzte Richtung bewegt. Dann wird ein kleiner Schub von der Begrenzung hinzugefügt: (rangeMax[c] – rangeMin[c]) * 0,001, was dazu beiträgt, das Boid zurück in den Bereich zu schieben.

    Eine ähnliche Prüfung wird für die Maximalgrenze (rangeMax[c]) durchgeführt: Wenn die Position des Boids über die Maximalgrenze hinausgeht, wird seine Geschwindigkeit ebenfalls invertiert und angepasst, allerdings durch Abzug eines Wertes, der dem vorherigen Fall ähnelt.

    Die Methode KeepWithinBounds schränkt die Bewegungen von Boids innerhalb eines bestimmten Gebiets wirksam ein und verhindert, dass sie aus dem Gebiet herausfliegen, und stellt sicher, dass sie innerhalb des Gebiets zurückkehren. 

    //——————————————————————————————————————————————————————————————————————————————
    // Keep the boid within the boundaries. If it gets too close to the edge,
    // push it back and change direction.
    void C_AO_NOA2::KeepWithinBounds (S_NeuroBoids_Agent &boid)
    {
      for (int c = 0; c < coords; c++)
      {
        if (boid.x [c] < rangeMin [c])
        {
          boid.dx [c] *= -1.0;
          // Add a small push from the border
          boid.dx [c] += (rangeMax [c] - rangeMin [c]) * 0.001;
        }
        if (boid.x [c] > rangeMax [c])
        {
          boid.dx [c] *= -1.0;
          // Add a small push from the border
          boid.dx [c] -= (rangeMax [c] - rangeMin [c]) * 0.001;
        }
      }
    }
    //——————————————————————————————————————————————————————————————————————————————
    

    Die Methode Distance der Klasse C_AO_NOA2 dient der Berechnung des Abstands zwischen zwei Agenten (Boids) in einem mehrdimensionalen Raum. Es verwendet eine Gleichung zur Berechnung des euklidischen Abstands, „dist“ ist eine Variable, die die Summe der quadrierten Differenzen zwischen den Koordinaten zweier Boids speichert.

    Die Methode startet eine Schleife, die alle Koordinaten durchläuft, was die Berechnung der Entfernung in einem Raum beliebiger Dimension ermöglicht. Für jede Koordinate c wird das Quadrat der Differenz zwischen den entsprechenden Koordinaten der beiden Boids berechnet: boid1.x[c] – boid2.x[c].

    Die Ergebnisse ((boid1.x[c] – boid2.x[c])^2) werden der Variablen „dist“ hinzugefügt. Nach Beendigung der Schleife enthält die Variable „dist“ die Summe der Quadrate der Koordinatendifferenzen. Um den tatsächlichen Abstand zu ermitteln, verwendet die Methode MathSqrt, um die Quadratwurzel der Summe der Quadrate zu berechnen, was der Gleichung für den euklidischen Abstand entspricht.

    //——————————————————————————————————————————————————————————————————————————————
    // Calculate the distance between two boids
    double C_AO_NOA2::Distance (S_NeuroBoids_Agent &boid1, S_NeuroBoids_Agent &boid2)
    {
      double dist = 0;
    
      for (int c = 0; c < coords; c++)
      {
        dist += MathPow (boid1.x [c] - boid2.x [c], 2);
      }
    
      return MathSqrt (dist);
    }
    //——————————————————————————————————————————————————————————————————————————————
    

    Die Methode Revision der Klasse C_AO_NOA2 ist für die Aktualisierung der Informationen über die beste Lösung zuständig, die während der Optimierung gefunden wurde. Bei diesem Prozess werden sowohl der Wert der Fitnessfunktion als auch die diesem Wert entsprechenden Koordinaten aktualisiert. Die Methode überwacht auch den Fortschritt und passt die Parameter des Algorithmus an, wenn es zu signifikanten Verbesserungen kommt. Die Methode durchläuft die gesamte Population, dargestellt durch das Array a, das popSize (die Anzahl der Agenten) enthält.

    Innerhalb der Schleife wird jeder Agent daraufhin überprüft, ob sein Fitnesswert (a[i].f) größer ist als der aktuelle Bestwert (fB). Wenn der aktuelle Agent den besten Fitnesswert aufweist, wird der global beste Wert der Fitnessfunktion aktualisiert, wobei fB ein neuer Wert von a[i].f zugewiesen wird, und dann werden die Koordinaten der besten Lösung aktualisiert. Für jede c-Koordinate wird das cB-Array (das die Koordinaten der besten Lösung enthält) mit den Werten des aktuellen Agenten aktualisiert. Der Stagnationszähler (m_stagnationCounter) wird auf Null zurückgesetzt, da eine Verbesserung der Lösung gefunden wurde.

    Die Methode verwendet die Variable hasProgress, um festzustellen, ob ein Fortschritt erzielt wurde. Dazu wird die absolute Differenz zwischen dem aktuellen und dem vorherigen besten Wert der Fitnessfunktion berechnet (MathAbs(fB – m_prevBestFitness)), und wenn diese Differenz größer als 0,000001 ist, wird der Fortschritt als offensichtlich angesehen. Wenn es Fortschritte gibt, wird m_prevBestFitness auf den aktuell besten fB-Wert aktualisiert.
    Die Erkundungsgeschwindigkeit explorationRate wird ebenfalls angepasst: Sie wird reduziert, wenn eine Verbesserung gefunden wird, wobei einige Parameter aus dem Array params und der aktuelle Wert von explorationRate berücksichtigt werden.

    //——————————————————————————————————————————————————————————————————————————————
    // Update the best solution found
    void C_AO_NOA2::Revision ()
    {
      // Update the best coordinates and fitness function value
      for (int i = 0; i < popSize; i++)
      {
        // Update the global best solution
        if (a [i].f > fB)
        {
          fB = a [i].f;
          for (int c = 0; c < coords; c++)
          {
            cB [c] = a [i].c [c];
          }
    
          // Reset the stagnation counter when a better solution is found
          m_stagnationCounter = 0;
        }
      }
    
      // Check for progress to adapt the algorithm parameters
      bool hasProgress = MathAbs (fB - m_prevBestFitness) > 0.000001;
      if (hasProgress)
      {
        m_prevBestFitness = fB;
        explorationRate = MathMax (params [11].val * 0.5, explorationRate * 0.9);
      }
    }
    //——————————————————————————————————————————————————————————————————————————————
    


    Testergebnisse

    Die Testergebnisse sind recht schwach.

    NOA2|Neuroboids Optimization Algorithm 2 (joo)|50.0|0.6|0.001|0.005|0.03|0.1|0.1|0.1|0.01|0.01|0.3|0.1|
    =============================
    5 Hilly's; Func runs: 10000; result: 0.47680799582735267
    25 Hilly's; Func runs: 10000; result: 0.30763714006051013
    500 Hilly's; Func runs: 10000; result: 0.2544737238936433
    =============================
    5 Forest's; Func runs: 10000; result: 0.3238017030688524
    25 Forest's; Func runs: 10000; result: 0.20976876473929068
    500 Forest's; Func runs: 10000; result: 0.15740101965732595
    =============================
    5 Megacity's; Func runs: 10000; result: 0.27076923076923076
    25 Megacity's; Func runs: 10000; result: 0.14676923076923082
    500 Megacity's; Func runs: 10000; result: 0.09729230769230844
    =============================
    All score: 2.24472 (24.94%)

    Die Visualisierung zeigt bescheidene Suchmöglichkeiten. Die Möglichkeit, externe Parameter des Algorithmus mit Hilfe globaler Variablen zu ändern, erlaubt es, mit dem Verhalten von Boids zu experimentieren und interessante Verhaltensmuster zu entdecken. Nachfolgend finden sich die Visualisierungen einiger der vielen möglichen Verhaltensmuster.

    Hilly

    NOA2 mit der Testfunktion Hilly

    Forest

    NOA2 mit der Testfunktion Forest-

    Megacity

    NOA2 mit der Testfunktion Megacity

    Auf der Grundlage der Testergebnisse wird der NOA2-Algorithmus in seiner Basisversion nur zur Information in unsere Rangliste der Algorithmen zur Populationsoptimierung aufgenommen.

    # AO Beschreibung Hilly Hilly final Forest Forest 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)
    1 ANS Suche über die gesamte Nachbarschaft 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-Sperr-Algorithmus (joo) 0.95345 0.87107 0.37590 2.20042 0.98942 0.91709 0.31642 2.22294 0.79692 mit der Testfunktion 0.19303 1.68380 6.107 67.86
    3 AMOm Optimierung der Tiermigration 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) Entwicklungsstrategien 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 Kometenschweif-Algorithmus (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 Zeit-Evolutions-Reise-Algorithmus (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 stochastische Diffusionssuche 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 BOAm Billard-Optimierungsalgorithmus M 0.95757 0.82599 0.25235 2.03590 1.00000 0.90036 0.30502 2.20538 0.73538 0.52523 0.09563 1.35625 5.598 62.19
    9 AAm Algorithmus für das Bogenschießen 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
    10 ESG Entwicklung sozialer Gruppen (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
    11 SIA Simuliertes isotropes Glühen (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
    12 ACS künstliche, kooperative Suche 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
    13 DA dialektischer Algorithmus 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
    14 BHAm Algorithmus für schwarze Löcher 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
    15 ASO Anarchische Gesellschaftsoptimierung 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
    16 RFO Optimierung des Royal Flush (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
    17 AOSm Suche nach atomaren Orbitalen 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
    18 TSEA Schildkrötenpanzer-Evolutionsalgorithmus (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
    19 DE differentielle 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
    20 SRA Algorithmus für erfolgreiche Gastronomen (joo) 0.96883 0.63455 0.29217 1.89555 0.94637 0.55506 0.19124 1.69267 0.74923 0.44031 0.12526 1.31480 4.903 54.48
    21 CRO Optimierung chemischer Reaktionen 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
    22 BIO Optimierung der Blutvererbung (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
    23 BSA Vogelschwarm-Algorithmus 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
    24 HS Harmoniesuche 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
    25 SSG Setzen, Säen und Wachsen 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
    26 BCOm Optimierung mit der bakteriellen Chemotaxis 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
    27 ABO Optimierung des afrikanischen Büffels 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
    28 (PO)ES (PO) Entwicklungsstrategien 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
    29 TSm Tabu-Suche 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
    30 BSO Brainstorming-Optimierung 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
    31 WOAm Wal-Optimierungsalgorithmus 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
    32 AEFA Algorithmus für künstliche elektrische Felder 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
    33 AEO Algorithmus zur Optimierung auf der Grundlage künstlicher Ökosysteme 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
    34 ACOm Ameisen-Kolonie-Optimierung 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
    35 BFO-GA Optimierung der bakteriellen Futtersuche — 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
    36 SOA einfacher Optimierungsalgorithmus 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
    37 ABHA Algorithmus für künstliche Bienenstöcke 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
    38 ACMO Optimierung atmosphärischer Wolkenmodelle 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
    39 ADAMm adaptive Momentabschätzung 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
    40 CGO Chaos Game Optimization 0.57256 0.37158 0.32018 1.26432 0.61176 0.61931 0.62161 1.85267 0.37538 0.21923 0.19028 0.78490 3.902 43.35
    41 ATAm Algorithmus für künstliche Stämme 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
    42 CFO Schwerkraftoptimierung 0.60961 0.54958 0.27831 1.43750 0.63418 0.46833 0.22541 1.32792 0.57231 0.23477 0.09586 0.90294 3.668 40.76
    43 ASHA Algorithmus für künstliches Duschen 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
    44 ASBO Optimierung des adaptiven Sozialverhaltens 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
    45 MEC Evolutionäre Berechnung des Geistes 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
    NOA2 Neuroboids Optimierungsalgorithmus 2(joo) 0.47681 0.30764 0.25447 1.03892 0.32380 0.20977 0.15740 0.69097 0.27077 0.14677 0.09729 0.51483 2.245 24.94


    Zusammenfassung

    Der von mir entwickelte Neuroboid-Optimierungsalgorithmus (NOA2) ist ein hybrider Ansatz, der die Prinzipien der Schwarmintelligenz (der Boid-Algorithmus) mit der neuronalen Steuerung kombiniert. Die Kernidee ist die Verwendung eines neuronalen Netzes zur adaptiven Steuerung der Verhaltensparameter von Boid-Agenten. Die derzeitige Implementierung verwendet ein einfaches einschichtiges neuronales Netz ohne versteckte Schichten. Es besteht aus einer Eingabeschicht, die aktuelle Koordinaten, Geschwindigkeiten, die Entfernung zur besten Lösung und den Wert der Fitnessfunktion empfängt, sowie einer Ausgabeschicht, die Geschwindigkeitskorrekturen und adaptive Parameter für die Verhaltensregeln des Boid-Schwarms definiert.

    Bei dieser Version gibt es keine Zwischenschichten. Die Anzahl der Eingaben ist definiert als (coords * 2 + 2), wobei „coords“ die Anzahl der Dimensionen des Suchraums ist. Die Ergebnisse umfassen eine Korrektur für jede Koordinate und drei zusätzliche Parameter zur Anpassung der Herdenregeln. Der Algorithmus hat eine große Anzahl von konfigurierbaren Parametern, was es schwierig macht, die besten zu finden. Ich habe verschiedene Kombinationen ausprobiert, aber ich habe immer noch nicht die ideale Kombination gefunden, die die besten Ergebnisse bei den Testfunktionen zeigt.

    In seiner jetzigen Form dient der Algorithmus eindeutig als Proof-of-Concept und ist insgesamt ein interessantes Experiment im Bereich der hybriden Optimierungsverfahren, das jedoch keine ausreichende Leistung aufweist. Er schneidet bei den durchgeführten Tests nur minimal ab und kann nur als Beispiel für den Ansatz der Algorithmenhybridisierung betrachtet werden. Interessierten Forschern und Entwicklern kann der NOA2-Code als Ausgangspunkt dienen, um mit verschiedenen Konfigurationen und Parametern zu experimentieren und fortgeschrittenere hybride Optimierungsalgorithmen zu entwickeln, die die Vorteile von populationsbasierten und maschinellen Lernmethoden nutzen.

    Tabelle

    Abbildung 3. Farbabstufung der Algorithmen nach den entsprechenden Tests

    Histogramm

    Abbildung 4. Histogramm der Algorithmus-Testergebnisse (Skala von 0 bis 100, je höher, desto besser, wobei 100 das maximal mögliche theoretische Ergebnis ist; im Archiv befindet sich ein Skript zur Berechnung der Bewertungstabelle)

    Vor- und Nachteile von NOA2:

    Vorteile:

    1. Interessante Idee.

    Nachteile

    1. Schwache Ergebnisse.

    Der Artikel wird von einem Archiv mit den aktuellen Versionen der Codes der Algorithmen begleitet. Der Autor des Artikels übernimmt keine Verantwortung für die absolute Richtigkeit der Beschreibung der kanonischen Algorithmen. An vielen von ihnen wurden Änderungen vorgenommen, um die Suchmöglichkeiten zu verbessern. Die in den Artikeln dargelegten Schlussfolgerungen und Urteile beruhen auf den Ergebnissen der Versuche.


    Programme, die im diesem Artikel verwendet werden

    # Name Typ Beschreibung
    1 #C_AO.mqh
    Include
    Übergeordnete Klasse von Populationsoptimierungsalgorithmen
    2 #C_AO_enum.mqh
    Include
    Enumeration der Algorithmen zur Populationsoptimierung
    3 TestFunctions.mqh
    Include
    Bibliothek mit Testfunktionen
    4 TestStandFunctions.mqh
    Include
    Bibliothek mit Funktionen für den Prüfstand
    5 Utilities.mqh
    Include
    Bibliothek mit Hilfsfunktionen
    6 CalculationTestResults.mqh
    Include
    Skript zur Berechnung der Ergebnisse in der Vergleichstabelle
    7 Testing AOs.mq5
    Skript Der einheitliche Prüfstand für alle Algorithmen zur Populationsoptimierung
    8 Simple use of population optimization algorithms.mq5
    Skript
    Ein einfaches Beispiel für die Verwendung von Algorithmen zur Populationsoptimierung ohne Visualisierung
    9 Test_AO_NOA2.mq5
    Skript NOA2-Prüfstand

    Übersetzt aus dem Russischen von MetaQuotes Ltd.
    Originalartikel: https://www.mql5.com/ru/articles/17497

    Beigefügte Dateien |
    NOA2.zip (186.45 KB)
    Einführung in MQL5 (Teil 28): Beherrschung der API- und WebRequest-Funktion in MQL5 (II) Einführung in MQL5 (Teil 28): Beherrschung der API- und WebRequest-Funktion in MQL5 (II)
    In diesem Artikel erfahren Sie, wie Sie mit Hilfe von APIs und der Funktion WebRequest in MQL5 Preisdaten von externen Plattformen abrufen und extrahieren können. Sie lernen, wie URLs strukturiert sind, wie API-Antworten formatiert werden, wie man Serverdaten in lesbare Strings umwandelt und wie man bestimmte Werte aus JSON-Antworten identifiziert und extrahiert.
    Von der Grundstufe bis zur Mittelstufe: Indikator (IV) Von der Grundstufe bis zur Mittelstufe: Indikator (IV)
    In diesem Artikel erfahren Sie, wie Sie auf einfache Weise einen operativen Ansatz für das Einfärben von Kerzen erstellen und umsetzen können. Dieses Konzept wird von den Händlern sehr geschätzt. Bei der Umsetzung muss darauf geachtet werden, dass die Balken oder Kerzen ihr ursprüngliches Aussehen behalten und das Lesen von Kerze zu Kerze nicht behindern.
    Von der Grundstufe bis zur Mittelstufe: Struktur (V) Von der Grundstufe bis zur Mittelstufe: Struktur (V)
    In diesem Artikel werden wir untersuchen, wie man strukturellen Code überladen kann. Ich weiß, dass es anfangs schwierig sein kann, das zu verstehen, vor allem, wenn man es zum ersten Mal sieht. Es ist sehr wichtig, dass Sie diese Konzepte erfassen und gut verstehen, bevor Sie versuchen, sich in komplexere und umfangreichere Themen zu vertiefen.
    Algorithmische Handelsstrategien: KI und ihr Weg zu den goldenen Zinnen Algorithmische Handelsstrategien: KI und ihr Weg zu den goldenen Zinnen
    In diesem Artikel wird ein Ansatz zur Erstellung von Handelsstrategien für Gold mithilfe von maschinellem Lernen vorgestellt. Betrachtet man den vorgeschlagenen Ansatz zur Analyse und Prognose von Zeitreihen aus verschiedenen Blickwinkeln, so lassen sich seine Vor- und Nachteile im Vergleich zu anderen Methoden zur Erstellung von Handelssystemen, die ausschließlich auf der Analyse und Prognose von Finanzzeitreihen beruhen, feststellen.