preview
Оптимизация нейробоидами — Neuroboids Optimization Algorithm 2 (NOA2)

Оптимизация нейробоидами — Neuroboids Optimization Algorithm 2 (NOA2)

MetaTrader 5Тестер |
507 0
Andrey Dik
Andrey Dik

Содержание

  1. Введение
  2. Реализация алгоритма
  3. Результаты тестов


Введение

По мере того, как я все детальнее погружался в оптимизационные алгоритмы на протяжении нескольких лет, меня часто захватывали два параллельных вдохновения, такие как самоорганизация биологических роев и адаптивные обучающие возможности нейронных сетей. Синтез этих двух парадигм привел меня к разработке алгоритма гибридного подхода, который сочетает пространственный интеллект Boids Крейга Рейнольдса с адаптивными обучающими возможностями нейронных сетей.

Мой путь начался с наблюдения, что традиционные алгоритмы роя, хотя и подходят для исследования сложных пространств поиска, посредством коллективного поведения, часто не обладают способностью учиться на истории своих исследований. И наоборот, нейронные сети отлично справляются с изучением сложных шаблонов, но могут испытывать трудности с эффективным пространственным исследованием при непосредственном применении к задачам оптимизации. Вопрос, который побудил мое исследование, был обманчиво прост: что, если бы каждый агент в рое мог научиться улучшать свои стратегии движения с помощью выделенной нейронной сети?

Полученный алгоритм использует агентов, которые следуют классическим правилам boid сплоченности, разделения и выравнивания, что позволяет им самоорганизовываться и эффективно исследовать пространство поиска. Однако, в отличие от традиционных реализаций boid, каждый агент оснащен многослойной нейронной сетью, которая непрерывно учится на опыте агента, адаптируя свои стратегии движения к конкретным характеристикам ландшафта приспособленности. Этот уровень нейронного управления постепенно влияет на поведение боидов, переходя от стратегий, доминирующих в исследовании на ранних итерациях, к движению, ориентированному на эксплуатацию, по мере выявления перспективных регионов.

Больше всего меня во время разработки увлекало наблюдение за тем, как нейронные сети развивали различные стратегии, в зависимости от своего положения в пространстве поиска. Агенты вблизи перспективных областей развивали нейронные паттерны, которые усиливали локальную эксплуатацию, в то время как агенты в разреженных областях поддерживали исследовательское поведение. Эта возникающая специализация естественным образом возникла из индивидуальных процессов обучения, создавая рой с неоднородным, контекстно-зависимым поведением, без явной глобальной координации.

В данной статье я представлю архитектуру, детали реализации и анализ производительности алгоритма NOA2, а также демонстрацию его возможностей на различных функциях бенчмарка. 


Реализация алгоритма

Возможно я повторюсь, что основная идея алгоритма нейробоидов заключается в объединении двух парадигм: коллективного интеллекта роевых алгоритмов и адаптивного обучения нейронных сетей.

В традиционном алгоритме боидов (boids), предложенном Крейгом Рейнольдсом, агенты следуют трем простым правилам: сближение (движение к центру группы), разделение (избегание столкновений) и выравнивание (согласование скорости с соседями). Эти правила создают реалистичное групповое поведение, похожее на поведение птиц в стаях. Нейробоиды расширяют эту концепцию, наделяя каждого агента индивидуальной нейронной сетью, которая обучается на основе опыта агента при исследовании пространства поиска. Эта нейронная сеть выполняет две ключевые функции:

  1. Адаптивное управление движением  — корректирует скорость агента на основе текущего состояния и истории перемещений.
  2. Модификация стандартных правил боидов  — динамически настраивает влияние правил сближения, разделения и выравнивания в зависимости от контекста.

В результате получается гибридный алгоритм, где каждый агент сохраняет социальное поведение, необходимое для эффективного исследования пространства, но при этом индивидуально адаптируется к ландшафту фитнес-функции через обучение. Это создает саморегулирующийся баланс между исследованием (exploration) и эксплуатацией (exploitation).

Ключевыми преимуществами такого подхода является самостоятельное обучение агентов оптимальным стратегиям движения вследствие чего алгоритм автоматически адаптируется к различным типам ландшафтов оптимизации, причем сохраняется исследование пространства, благодаря коллективному поведению без централизованного управления. Приведу простую аналогию: представьте стаю птиц, летящую в небе. Они движутся слаженно: никто не сталкивается, держатся вместе и летят в одном направлении. Такое поведение можно описать тремя простыми правилами: держись рядом со своими соседями (не отрывайся от стаи), не сталкивайся с соседями (соблюдай дистанцию) и лети в том же направлении (поддерживай общий курс).

Это основа так называемого алгоритма "боидов" (от "bird-oid" — "птицеподобный"). Каждая птица в стае не просто следует этим правилам, но и умеет учиться на своем опыте. Птица запоминает, какие действия приводили к успеху (например, находила больше пищи), а какие — нет. Со временем, она становится умнее и принимает лучшие решения о том, как лететь. Вот это и есть суть алгоритма нейробоидов: соединение простых правил группового движения со способностью каждого участника учиться на собственном опыте. "Нейро" в названии как раз и указывает на способность к обучению. Такой подход особенно интересен тем, что сочетает силу коллективного исследования (никто не пропустит важный участок) с преимуществами индивидуального обучения (каждый становится лучшим в своей области поиска).

NOA2-illustration

Рисунок 1. Иллюстрация работы алгоритма NOA2  

На иллюстрации представлены следующие ключевые элементы: ландшафт оптимизации, где желто-фиолетовая область представляет глобальный оптимум, оранжевая и фиолетовая области — локальные оптимумы, а контурные линии показывают "высоту" ландшафта фитнес-функции. 

Различные группы агентов: синие — исследуют область глобального оптимума, фиолетовые — сконцентрированы вокруг первого локального оптимума, зеленые — исследуют второй локальный оптимум и красные — выполняют случайное исследование пространства. Стрелками показывается направление движения боидов. Красная точка отмечает текущее лучшее найденное решение.

NOA2-diagram

Рисунок 2. Схема работы алгоритма NOA2  

Схема включает в себя: блок инициализации (красный), Нейронную сеть боида (розовая), итерации алгоритма с подблоками (синий), адаптивные механизмы (зеленый), визуализация архитектуры нейронной сети с примерами соединений. В нижней части показаны миниатюрные диаграммы в виде структуры нейронной сети, визуализации правил движения боидов и баланса между исследованием и эксплуатацией.

Разобравшись с основной концепцией, можем приступить к описанию псевдокода алгоритма.

ИНИЦИАЛИЗАЦИЯ:

  • Установите параметры поиска и создайте популяцию агентов.
  • Для каждого агента: инициализируйте случайное положение, небольшую скорость, нейронную сеть (входные → скрытые → выходные слои) и пустую память опыта.
  • Установите глобальную лучшую приспособленность в отрицательную бесконечность и счетчик стагнации в ноль.

ГЛАВНЫЙ ЦИКЛ:

  • Для каждой итерации:

    ОЦЕНКА:

    • Рассчитайте приспособленность для всех агентов.
    • Обновите личные и глобальные лучшие позиции.
    • Сохраните опыты в буферах памяти.

    УПРАВЛЕНИЕ СТАГНАЦИЕЙ:

    • Если улучшения нет: увеличьте исследование, время от времени перезапускайте слабых агентов.
    • Иначе: постепенно снижайте уровень исследования.

    НЕЙРОННАЯ ОБРАБОТКА:

    • Для каждого агента:
      • Прямой проход: входные данные (положение, скорость, расстояние до лучшего, приспособленность) → скрытые слои → выходы.
      • Если собрано достаточно опыта и обнаружено улучшение: обновите нейронные веса.

    ОБНОВЛЕНИЕ ДВИЖЕНИЯ:

    • Для каждого агента:
      • Рассчитайте стандартные силы бойдов (когезия, отделение, выравнивание).
      • Примените нейронно-модифицированные силы и прямые нейронные коррекции.
      • Добавьте компонент случайного исследования с вероятностью.
      • Принудительно ограничьте скорость и учтите границы.
      • Обновите положение на основе финальной скорости.

ПОДДЕРЖИВАЮЩИЕ РАСЧЕТЫ:

  • Расчет массы: нормализуйте значения приспособленности, чтобы назначить массу каждому агенту.
  • Когезия: двигайтесь к взвешенному центру масс ближайших агентов.
  • Отделение: избегайте скученности соседей.
  • Выравнивание: согласуйте скорость с ближайшими агентами.
  • Нейронное обновление: упрощенное обратное распространение на основе улучшения приспособленности.

ВОЗВРАТ: глобальная лучшая позиция и приспособленность.

Теперь все подготовлено для написания кода алгоритма. Назначим Структуру "S_NeuroBoids_Agent", которая будет представлять собой нейро-боидного агента на основе нейро-сетевой архитектуры для выполнения задач оптимизации. В данной реализации агент имеет следующие ключевые компоненты и функции:

Координаты и скорости:

  • x [] — текущие координаты агента.
  • dx [] — текущие скорости агента.
Нейронная сеть:
  • inputs [] — входные значения для нейронов (координаты, скорости, расстояние до лучшей известной позиции и т.д.).
  • outputs [] — выходные значения нейронной сети (коррекция скоростей и параметры адаптации).
  • weights [] — веса связей в нейронной сети.
  • biases [] — смещения для нейронов.
Память:
  • memory [] — массив для хранения предыдущих позиций и их значений фитнеса.
  • memory_size — максимальный размер памяти для хранения.
  • memory_index — текущий индекс в памяти.
Фитнес и масса:
  • best_local_fitness — лучший локальный фитнес агента.
  • m — масса агента.
  • cB [] — координаты лучшей позиции, найденной агентом.
  • fB — значение фитнес-функции для лучшей позиции.

Инициализация (Init) — все массивы и переменные инициализируются нулями или случайными значениями. Устанавливаются размеры массивов, инициализируются влияние (веса и смещения) маленькими случайными значениями.

Функции активации — Tanh, ReLU, Sigmoid различные функции активации, которые применяются в нейронной сети.

Обновление входных данных (UpdateInputs) — заполняет массив входов текущими координатами, скоростями, расстоянием до лучшей известной позиции и текущим значением фитнеса.

Прямое распространение (ForwardPass) — вычисляет выходы нейросети на основе входов, весов и смещений, применяя функции активации.

Обучение на основе опыта (Learn) — обновляет веса и смещения на основе накопленного опыта, если текущий опыт лучше предыдущих.

Запоминание опыта (MemorizeExperience) —  сохраняет текущие координаты и фитнес агента в память.

Обновление лучшей позиции (UpdateBestPosition) — если текущее значение фитнеса лучше, чем найденное ранее, обновляет лучшую позицию.

//——————————————————————————————————————————————————————————————————————————————
// Структура нейро-боидного агента
//——————————————————————————————————————————————————————————————————————————————
struct S_NeuroBoids_Agent
{
    double x       [];            // текущие координаты
    double dx      [];            // текущие скорости
    double inputs  [];            // входы нейрона
    double outputs [];            // выходы нейрона
    double weights [];            // веса нейрона
    double biases  [];            // смещения нейрона
    double memory  [];            // память предыдущих позиций и их фитнесов
    int    memory_size;           // размер памяти для накопления опыта
    int    memory_index;          // текущий индекс в памяти
    double best_local_fitness;    // лучший локальный фитнес агента
    double m;                     // масса бойда
    double cB [];                 // координаты лучшей позиции
    double fB;                    // значение фитнес-функции

    // Инициализация агента
    void Init (int coords, int neuron_size)
    {
      ArrayResize (x, coords);
      ArrayResize (dx, coords);
      ArrayInitialize (x, 0.0);
      ArrayInitialize (dx, 0.0);

      // Инициализируем структуру лучшей позиции
      ArrayResize     (cB, coords);
      ArrayInitialize (cB, 0.0);
      fB = -DBL_MAX;

      // Входы: координаты, скорости, расстояние до лучшего и т.д.
      int input_size = coords * 2 + 2; // x, dx, dist_to_best, current_fitness
      ArrayResize (inputs, input_size);
      ArrayInitialize (inputs, 0.0);

      // Выходы: коррекция скоростей и адаптивные факторы для правил стаи
      int output_size = coords + 3; // dx_correction + 3 адаптивных параметра
      ArrayResize (outputs, output_size);
      ArrayInitialize (outputs, 0.0);

      // Веса и смещения для каждого выхода
      ArrayResize (weights, input_size * output_size);
      ArrayInitialize (weights, 0.0);
      ArrayResize (biases, output_size);
      ArrayInitialize (biases, 0.0);

      // Инициализация весов и смещений малыми случайными значениями
      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);
      }

      // Инициализация памяти для накопления опыта
      memory_size = 10;
      ArrayResize (memory, memory_size * (coords + 1)); // координаты + фитнес
      ArrayInitialize (memory, 0.0);
      memory_index = 0;
      best_local_fitness = -DBL_MAX;

      // Инициализация массы
      m = 0.5;
    }

    // Функция активации - гиперболический тангенс
    double Tanh (double input_val)
    {
      return MathTanh (input_val);
    }

    // Функция активации ReLU
    double ReLU (double input_val)
    {
      return (input_val > 0.0) ? input_val : 0.0;
    }

    // Функция активации Sigmoid
    double Sigmoid (double input_val)
    {
      return 1.0 / (1.0 + MathExp (-input_val));
    }

    // Обновление входов нейронной сети - исправленная версия
    void UpdateInputs (double &global_best [], double current_fitness, int coords_count)
    {
      int input_index = 0;

      // Координаты
      for (int c = 0; c < coords_count; c++)
      {
        inputs [input_index++] = x [c];
      }

      // Скорости
      for (int c = 0; c < coords_count; c++)
      {
        inputs [input_index++] = dx [c];
      }

      // Расстояние до лучшего глобального решения
      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;

      // Текущая фитнес-функция
      inputs [input_index++] = current_fitness;
    }

    // Прямое распространение по сети
    void ForwardPass (int coords_count)
    {
      int input_size = ArraySize (inputs);
      int output_size = ArraySize (outputs);

      // Для каждого выхода вычисляем взвешенную сумму входов + смещение
      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];
        }

        // Применяем разные функции активации в зависимости от выхода
        if (o < coords_count) // Для коррекции скорости используем гиперболический тангенс
        {
          outputs [o] = Tanh (sum);
        }
        else // Для адаптивных параметров используем сигмоиду
        {
          outputs [o] = Sigmoid (sum);
        }
      }
    }

    // Обучение на основе накопленного опыта
    void Learn (double learning_rate, int coords_count)
    {
      if (memory_index < memory_size) return; // Недостаточно опыта

      // Находим лучший опыт в памяти
      int best_index = 0;
      double best_fitness = memory [coords_count]; // Первая фитнес-функция в памяти

      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 (best_fitness <= best_local_fitness) return;

      best_local_fitness = best_fitness;

      // Простой метод обновления весов
      int input_size = ArraySize (inputs);
      int output_size = ArraySize (outputs);

      // Простая форма градиентного обновления
      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];
          }
        }

        // Обновляем смещения
        biases [o] += learning_rate * outputs [o];
      }
    }

    // Запоминаем текущий опыт (координаты и фитнес)
    void MemorizeExperience (double fitness, int coords_count)
    {
      int offset = memory_index * (coords_count + 1);

      // Запоминаем координаты
      for (int c = 0; c < coords_count; c++)
      {
        memory [offset + c] = x [c];
      }

      // Запоминаем фитнес-функцию
      memory [offset + coords_count] = fitness;

      // Обновляем индекс памяти
      memory_index = (memory_index + 1) % memory_size;
    }

    // Обновление лучшей позиции агента
    void UpdateBestPosition (double current_fitness, int coords_count)
    {
      if (current_fitness > fB)
      {
        fB = current_fitness;
        ArrayCopy (cB, x, 0, 0, WHOLE_ARRAY);
      }
    }
};

Класс "C_AO_NOA2" представляет собой реализацию алгоритма NOA2 и наследуется от базового класса "C_AO". Давайте рассмотрим структуру и ключевые аспекты этого класса подробнее.

Основные параметры:
  • popSize — размер популяции агентов (боидов).
  • cohesionWeight, cohesionDist — вес и расстояние для правила сближения.
  • separationWeight, separationDist — вес и расстояние для правила разделения.
  • alignmentWeight, alignmentDist — вес и расстояние для правила выравнивания.
  • maxSpeed и minSpeed — максимальная и минимальная скорости агентов.
  • learningRate — скорость обучения для нейронной сети.
  • neuralInfluence — влияние нейронной сети на движение агентов.
  • explorationRate — вероятность случайного исследования пространства решений.
  • m_stagnationCounter и m_prevBestFitness — счетчик стагнации и предыдущее лучшее значение фитнеса.
Методы класса:
  • SetParams () — метод, устанавливающий параметры алгоритма на основе значений, хранящихся в массиве "params".
  • Init () — инициализация, принимает параметры для определения диапазонов значений для агентов. Этот метод настраивает начальные условия для работы алгоритма.
  • Moving () — отвечает за движение агентов на основе различных правил взаимодействия.
  • Revision () — используется для пересмотра состояния агентов в процессе выполнения алгоритма.

Агенты: S_NeuroBoids_Agent agent [] — массив агентов, представляющих боидов в популяции.

Приватные методы (для использования внутри класса):

  • CalculateMass () — рассчитывает массу агентов.
  • Cohesion () — реализует правило сближения.
  • Separation () — реализует правило разделения.
  • Alignment () — реализует правило выравнивания.
  • LimitSpeed () — ограничивает скорость агента.
  • KeepWithinBounds () — удерживает агентов в заданных границах.
  • Distance () — вычисляет расстояние между двумя агентами.
  • ApplyNeuralControl () — применяет управление нейронной сети к агенту.
  • AdaptiveExploration() — реализует адаптивное исследование.
//——————————————————————————————————————————————————————————————————————————————
// Класс нейро-боидного алгоритма оптимизации, наследуемый от 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/ru/articles/17497";

    popSize          = 50;     // размер популяции

    cohesionWeight   = 0.6;    // вес сближения
    cohesionDist     = 0.001;  // дистанция сближения

    separationWeight = 0.005;  // вес разделения
    separationDist   = 0.03;   // дистанция разделения

    alignmentWeight  = 0.1;    // вес выравнивания
    alignmentDist    = 0.1;    // дистанция выравнивания

    maxSpeed         = 0.001;  // максимальная скорость
    minSpeed         = 0.0001; // минимальная скорость

    learningRate     = 0.01;   // скорость обучения нейронной сети
    neuralInfluence  = 0.3;    // влияние нейронной сети на движение
    explorationRate  = 0.1;    // вероятность случайного исследования

    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;

    // Инициализация счетчика стагнации и предыдущего лучшего значения фитнеса
    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;     // вес сближения
  double cohesionDist;       // дистанция сближения

  double separationWeight;   // вес разделения
  double separationDist;     // дистанция разделения

  double alignmentWeight;    // вес выравнивания
  double alignmentDist;      // дистанция выравнивания

  double minSpeed;           // минимальная скорость
  double maxSpeed;           // максимальная скорость

  double learningRate;       // скорость обучения нейронной сети
  double neuralInfluence;    // влияние нейронной сети на движение
  double explorationRate;    // вероятность случайного исследования

  int m_stagnationCounter;   // счетчик стагнации
  double m_prevBestFitness;  // предыдущее лучшее значение фитнеса

  S_NeuroBoids_Agent agent []; // агенты (боиды)

  private: //-------------------------------------------------------------------
  double distanceMax;     // максимальная дистанция
  double speedMax [];     // максимальные скорости по измерениям

  int neuron_size;        // размер нейрона (количество входов)

  void   CalculateMass       ();                                  // расчет масс агентов
  void   Cohesion            (S_NeuroBoids_Agent &boid, int pos); // правило сближения
  void   Separation          (S_NeuroBoids_Agent &boid, int pos); // правило разделения
  void   Alignment           (S_NeuroBoids_Agent &boid, int pos); // правило выравнивания
  void   LimitSpeed          (S_NeuroBoids_Agent &boid);          // ограничение скорости
  void   KeepWithinBounds    (S_NeuroBoids_Agent &boid);          // удержание в границах
  double Distance            (S_NeuroBoids_Agent &boid1, S_NeuroBoids_Agent &boid2); // расчет дистанции
  void   ApplyNeuralControl  (S_NeuroBoids_Agent &boid, int pos); // применение нейронного управления
  void   AdaptiveExploration ();                                  // адаптивное исследование
};
//——————————————————————————————————————————————————————————————————————————————

Метод "Init" отвечает за инициализацию параметров алгоритма и агентов и начинает свою работу с вызова "StandardInit", передавая ему массивы минимальных и максимальных значений диапазона и шагов поиска. Если стандартная инициализация не удается, метод сразу возвращает "false". Устанавливается размер нейрона, который будет использоваться в нейронной сети агентов.

В данном случае, размер рассчитывается как количество координат, умноженное на 2, а также добавляются два дополнительных значения: "dist_to_best" и "current_fitness". Происходит изменение размера массива агентов, в соответствии с заданным размером популяции "popSize". Затем, для каждого агента вызывается метод "Init ", который инициализирует его параметры, в том числе, нейронную сеть.

Расчет максимального расстояния и скорости:

  • Переменная "distanceMax" инициализируется нулем.
  • Для каждой координаты рассчитывается максимальное значение скорости, основываясь на разнице между максимальным и минимальным значениями диапазона.
  • Также вычисляется максимальное расстояние, как квадратный корень из суммы квадратов максимальных скоростей.

Счетчик стагнации (m_stagnationCounter) сбрасывается в ноль, а переменная хранения предыдущего лучшего значения фитнеса (m_prevBestFitness) устанавливается в минимально возможное значение. Метод устанавливает различные глобальные переменные, такие как веса сближения, разделения и выравнивания, максимальные и минимальные скорости, скорость обучения, влияние нейронной сети и вероятность исследования. Если все этапы прошли успешно, метод возвращает "true", что подтверждает успешную инициализацию.

//——————————————————————————————————————————————————————————————————————————————
bool C_AO_NOA2::Init (const double &rangeMinP  [],  // минимальный диапазон поиска
                      const double &rangeMaxP  [],  // максимальный диапазон поиска
                      const double &rangeStepP [],  // шаг поиска
                      const int    epochsP)         // количество эпох
{
  if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false;

  //----------------------------------------------------------------------------
  // Определение размера нейрона
  neuron_size = coords * 2 + 2; // x, dx, dist_to_best, current_fitness

  // Инициализация агентов
  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);

  // Сброс счетчика стагнации и предыдущего лучшего значения фитнеса
  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;
}
//——————————————————————————————————————————————————————————————————————————————

Метод "Moving" отвечает за перемещение агентов в среде, основываясь на полученных данных и взаимодействии между ними. Сначала метод проверяет значение глобальной переменной "#reset", если она равна 1.0, происходит сброс параметров (переменная "revision" устанавливается в "false", а затем возвращается к "0.0"). Из глобальных переменных извлекаются значения различных параметров, таких как веса сближения, разделения и выравнивания, минимальная и максимальная скорости, скорость обучения, влияние нейронной сети и коэффициент исследования. Эти параметры могут быть настроены для изменения поведения агентов.

Если переменная "revision" равна "false", происходит инициализация позиций "x" и скоростей "dx" агентов. Для каждой координаты применяются случайные значения из заданного диапазона, а скорости устанавливаются на малые случайные значения. В каждой координате также сохраняется состояние агента, основанное на его текущей позиции. Метод "AdaptiveExploration ()" вызывается для адаптивного поиска, который может изменять поведение агентов в зависимости от состояния стагнации.

Основной цикл движения агентов (для каждого):

  • Запоминается текущий опыт агента.
  • Обновляется лучшая позиция, которую агент нашел.
  • Обновляются входные данные нейронной сети агента.
  • Выполняется прямое распространение данных через нейронную сеть.
  • Агенты обучаются на основе накопленного опыта.
Расчет масс: вызывается метод "CalculateMass ()", для оценки взаимодействий между агентами с учетом их массы.

    Применение правил и движение: вновь начинается цикл по агентам, для каждого применяются стандартные правила алгоритма боидов:

    • Сближение (Cohesion): агенты стремятся быть ближе друг к другу.
    • Разделение (Separation): агенты избегают сближения друг с другом, чтобы не сталкиваться.
    • Выравнивание (Alignment): агенты стараются двигаться в одном направлении со своими соседями.
    Вызывается метод "ApplyNeuralControl ()" для управления, основанного на нейронной сети. Ограничиваются скорости агентов с помощью "LimitSpeed ()" и проверяется, что агенты остаются в границах заданной области с помощью KeepWithinBounds (). Обновляются позиции агентов по каждой координате, добавляя текущую скорость к текущей позиции, и состояние.

    //——————————————————————————————————————————————————————————————————————————————
    void C_AO_NOA2::Moving ()
    {
      double reset = GlobalVariableGet ("#reset");
      if (reset == 1.0)
      {
        revision = false;
        GlobalVariableSet ("#reset", 0.0);
      }
    
      // Получение параметров из глобальных переменных для интерактивной настройки
      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");
    
      // Инициализация начальных позиций и скоростей
      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;
      }
    
      // Адаптивное исследование в зависимости от стагнации
      AdaptiveExploration ();
    
      //----------------------------------------------------------------------------
      // Основной цикл движения боидов
      for (int i = 0; i < popSize; i++)
      {
        // Запоминаем текущий опыт
        agent [i].MemorizeExperience (a [i].f, coords);
    
        // Обновляем лучшую позицию агента
        agent [i].UpdateBestPosition (a [i].f, coords);
    
        // Обновляем входы нейронной сети
        agent [i].UpdateInputs (cB, a [i].f, coords);
    
        // Прямое распространение по нейронной сети
        agent [i].ForwardPass (coords);
    
        // Обучение на основе накопленного опыта
        agent [i].Learn (learningRate, coords);
      }
    
      // Расчет масс
      CalculateMass ();
    
      // Применение правил и движение
      for (int i = 0; i < popSize; i++)
      {
        // Стандартные правила алгоритма боидов
        Cohesion (agent [i], i);
        Separation (agent [i], i);
        Alignment (agent [i], i);
    
        // Применение нейронного управления
        ApplyNeuralControl (agent [i], i);
    
        // Ограничение скорости и удержание в границах
        LimitSpeed (agent [i]);
        KeepWithinBounds (agent [i]);
    
        // Обновление позиций
        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]);
        }
      }
    }
    //——————————————————————————————————————————————————————————————————————————————

    Метод "CalculateMass"  выполняет вычисление "массы" агентов на основе их фитнеса (продуктивности) и нормализует эти значения. Переменные "maxMass"  и "minMass" инициализируются значениями -DBL_MAX и DBL_MAX соответственно. Эти переменные будут использоваться для нахождения наибольшего и наименьшего значений фитнеса среди всего населения агентов. Метод проверяет, имеется ли хотя бы один агент, проверяя, что "popSize" (размер популяции) больше нуля. Если агентов нет, метод завершает выполнение. В первом цикле перебираются все агенты (от 0 до popSize), и для каждого проверяется, является ли его фитнес (значение  a[i].f) больше текущего "maxMass" или меньше "minMass".

    В результате этого цикла будут определены максимальное и минимальное значения фитнеса среди всех агентов. Во втором цикле снова перебираются все агенты, и для каждого рассчитывается его "масса" (переменная "m") с помощью функции "u.Scale", которая нормализует фитнес-значения. 

    //——————————————————————————————————————————————————————————————————————————————
    void C_AO_NOA2::CalculateMass ()
    {
      double maxMass = -DBL_MAX;
      double minMass = DBL_MAX;
    
      // Проверка на наличие данных перед вычислениями
      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);
      }
    }
    //——————————————————————————————————————————————————————————————————————————————

    Метод "ApplyNeuralControl" отвечает за применение управления, основанного на выходах нейронной сети, к агенту типа "боид". Метод перебирает все координаты агента "coords". Для каждой координаты "c" проверяется, что текущий индекс не выходит за границы массива выходов "boid.outputs". Если индекс корректен, производится коррекция скорости "dx[c]" агента на основе соответствующего значения из выходов нейронной сети, умноженного на ее влияние "neuralInfluence".

    Извлекается размер массива "boid.outputs" для упрощения дальнейших проверок. Для коэффициентов сближения, разделения и выравнивания "cohesion_factor", "separation_factor", "alignment_factor" проверяется, находятся ли соответствующие индексы в пределах массива выходов. Если индекс выходит за границы, присваивается значение по умолчанию 0.5. Эти коэффициенты используются для масштабирования базовых весов с учетом воздействия нейронной сети. Они влияют на поведение агентов, изменяя веса для сближения, разделения и выравнивания:

    • local_cohesion устанавливает вес сближения.
    • local_separation устанавливает вес разделения.
    • local_alignment устанавливает вес выравнивания.

    Метод проверяет, нужно ли провести случайное исследование, используя заданную вероятность "xplorationRate", если условие выполняется, выбирается случайная координата "c" для возмущения. Размер возмущения "perturbation_size" вычисляется на основе массы агента (которое является мерой его фитнеса) и диапазона возможных значений для этой координаты. Это позволяет контролировать величину случайного изменения, несмотря на массу агента. К скорости "dx[c]" добавляется случайное возмущение, выбираемое из диапазона от "perturbation_size" до "perturbation_size". Метод "ApplyNeuralControl" осуществляет интеграцию результатов работы нейронной сети в процесс управления движением агентов. Он корректирует их скорость на основе данных, метод также учитывает необходимость случайного исследования.

    //——————————————————————————————————————————————————————————————————————————————
    // Применение нейронного управления к боиду
    void C_AO_NOA2::ApplyNeuralControl (S_NeuroBoids_Agent &boid, int pos)
    {
      // Используем выходы нейронной сети для коррекции скорости
      for (int c = 0; c < coords; c++)
      {
        // Проверяем, что индекс не выходит за границы массива
        if (c < ArraySize (boid.outputs))
        {
          // Применяем нейронную коррекцию скорости с заданным влиянием
          boid.dx [c] += boid.outputs [c] * neuralInfluence;
        }
      }
    
      // Используем выходы нейронной сети для адаптации параметров стаи
      // Проверяем, что индексы не выходят за границы массива
      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;
    
      // Масштабируем базовые веса с учетом нейронной адаптации
      // Эти переменные локальные и не изменяют глобальные параметры
      double local_cohesion   = cohesionWeight * (0.5 + cohesion_factor);
      double local_separation = separationWeight * (0.5 + separation_factor);
      double local_alignment  = alignmentWeight * (0.5 + alignment_factor);
    
      // Случайное исследование с заданной вероятностью
      if (u.RNDprobab () < explorationRate)
      {
        // Выбираем случайную координату для возмущения
        int c = (int)u.RNDfromCI (0, coords - 1);
    
        // Адаптивный размер возмущения в зависимости от массы (фитнеса)
        double perturbation_size = (1.0 - boid.m) * (rangeMax [c] - rangeMin [c]) * 0.01;
    
        // Добавляем случайное возмущение
        boid.dx [c] += u.RNDfromCI (-perturbation_size, perturbation_size);
      }
    }
    //——————————————————————————————————————————————————————————————————————————————
    

    Метод "AdaptiveExploration" класса "C_AO_NOA2" реализует адаптивное исследование в зависимости от стадии стагнации в процессе оптимизации. Метод начинает с проверки, изменилось ли значение целевой функции "fB"  по сравнению с предыдущим лучшим значением "m_prevBestFitness". Если разница между ними меньше чем 0.000001, считается, что нет прогресса, и увеличивается счетчик стагнации "m_stagnationCounter". В противном случае, стагнация прекращается, счетчик сбрасывается, и текущее значение "fB" сохраняется как новое лучшее.

    Если количество стагнации превышает 20, увеличивается вероятность случайного исследования "explorationRate", но она ограничивается значением "0.5" для предотвращения слишком высокой вероятности случайных изменений. Каждые 50 итераций стагнации происходит частичный рестарт: 70% агентов в популяции перезапускаются с новыми случайными значениями в пределах заданных диапазонов "rangeMin" и "rangeMax". При этом оставшиеся лучшие агенты с их текущими позициями остаются неизменными. После перезапуска счетчик стагнации сбрасывается.

    Если прогресс есть, и количество параметров "params"  больше 11, устанавливается вероятность исследования "explorationRate" из массива параметров. Если параметров меньше, устанавливается значение по умолчанию, равное 0.1.

    //——————————————————————————————————————————————————————————————————————————————
    // Адаптивное исследование в зависимости от стагнации
    void C_AO_NOA2::AdaptiveExploration ()
    {
      // Определяем, есть ли прогресс в поиске
      if (MathAbs (fB - m_prevBestFitness) < 0.000001)
      {
        m_stagnationCounter++;
      }
      else
      {
        m_stagnationCounter = 0;
        m_prevBestFitness = fB;
      }
    
      // Увеличиваем исследование при стагнации
      if (m_stagnationCounter > 20)
      {
        // Увеличиваем вероятность случайного исследования
        explorationRate = MathMin (0.5, explorationRate * 1.5);
    
        // Каждые 50 итераций стагнации выполняем частичный рестарт
        if (m_stagnationCounter % 50 == 0)
        {
          // Перезапускаем 70% популяции, оставляя лучших агентов
          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;
            }
          }
    
          // Сбрасываем счетчик стагнации
          m_stagnationCounter = 0;
        }
      }
      else
      {
        // При хорошем прогрессе используем обычный уровень исследования
        if (11 < ArraySize (params))
        {
          explorationRate = params [11].val;
        }
        else
        {
          explorationRate = 0.1; // значение по умолчанию
        }
      }
    }
    //——————————————————————————————————————————————————————————————————————————————

    Метод "Cohesion" класса "C_AO_NOA2" отвечает за реализацию поведения схождения "cohesion" для агентной модели боидов. Объявление переменных:

    • centerX — массив будет использоваться для хранения координат центра масс соседних агентов. Размер массива соответствует количеству координат "coords".
    • numNeighbors — счетчик соседей будет отслеживать количество агентов, находящихся в пределах заданного расстояния (с учетом массы).
    • sumMass — сумма масс соседних агентов будет использоваться для нормализации координат центра масс.

    Первый цикл (от 0 до popSize) суммирует массы всех агентов в популяции, исключая текущего боида (агента) по индексу "pos". Второй цикл проходит через всех агентов и проверяет, находятся ли они в пределах максимального расстояния, умноженного на коэффициент "cohesionDist" от текущего боида. Если расстояние до агента меньше указанного максимума, координаты данного агента (с учетом его массы) добавляются к "centerX", а также увеличивается счетчик "numNeighbors".

    После того, как были вычислены суммы координат и масса соседей, проверяется, есть ли соседи (т.е. numNeighbors > 0) и сумма масс (sumMass > 0.0), и если соседи найдены, координаты центра масс нормализуются (путем деления на сумму масс). Затем текущая скорость "dx" боида увеличивается в направлении центра масс с заданным весом "cohesionWeight", это означает, что боид будет двигаться к центру масс своих соседей, учитывая их массы.

    Метод "Cohesion" рассчитывает центр масс и корректирует скорость текущего боида таким образом, чтобы он "сходился" к этому центру. Это поведение является одним из ключевых аспектов симуляции поведения стаи или группы. Параметры, такие как "cohesionDist" и "cohesionWeight", позволяют управлять расстоянием, на котором действует это поведение, и степенью влияния центра масс на передвижение боидов.

    //——————————————————————————————————————————————————————————————————————————————
    // Найти центр масс других боидов и скорректировать скорость в направлении центра масс
    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;
        }
      }
    }
    //——————————————————————————————————————————————————————————————————————————————

    Метод "Separation" класса "C_AO_NOA2" реализует поведение отталкивания (separation) для агентов модели, которое предназначено для предотвращения столкновений между боидом и соседними агентами, "moveX" — параметры, такие как "cohesionDist" массив будет использоваться для хранения векторов смещения, чтобы оттолкнуть боид от других агентов.

    Размер массива соответствует количеству координат "coords". Массив "moveX" инициализируется нулями, чтобы избежать накопления случайных значений, далее происходит перебор всех агентов в популяции (от 0 до popSize). Затем проверяется, близок ли агент к текущему боиду. Это делается с помощью функции "Distance", и если расстояние между боидом и агентом меньше заданного максимума (умноженного на коэффициент "separationDist"), то для каждой координаты выполняется вычитание координат агента из координат текущего боида, чтобы эффективно создать вектор, указывающий в сторону текущего боида (то есть отталкивать от агента).

    После завершения цикла по агентам, текущая скорость "dx" боида увеличивается на рассчитанный вектор смещения "moveX", умноженный на коэффициент "separationWeight". Это позволяет контролировать силу отталкивания: чем больше значение "separationWeight", тем сильнее боид будет избегать столкновений.

    Метод "Separation" реализует поведение, которое заставляет боид отталкиваться от ближайших соседей, предотвращая столкновения. Это важный аспект в моделировании поведения стаи, который помогает поддерживать личное пространство для каждого агента и способствует более естественному взаимодействию между ними. Параметры, такие как "separationDist" и "separationWeight", позволяют гибко настраивать радиус действия и силу эффекта отталкивания, соответственно.

    //——————————————————————————————————————————————————————————————————————————————
    // Отталкивание от других боидов, чтобы избежать столкновений
    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;
      }
    }
    //——————————————————————————————————————————————————————————————————————————————

    Метод "Alignment"  класса "C_AO_NOA2"  реализует поведение выравнивания (alignment) для агентов в модели. Это поведение заставляет боидов согласовывать свои скорости с соседними агентами, чтобы создать более гармоничное и координированное движение группы, "avgDX" — массив, который будет использоваться для хранения среднего вектора скорости соседних агентов. Размер массива соответствует количеству координат "coords", "numNeighbors" — счетчик, который отслеживает количество соседей, оказавшихся в пределах заданного расстояния.

    Цикл проходит через всех агентов в популяции. Внутри цикла проверяется, не является ли текущий агент тем самым боидом. Если расстояние до другого агента меньше заданного максимума, умноженного на коэффициент "alignmentDist", текущая скорость "dx" этого агента добавляется к "avgDX". Счетчик "numNeighbors" увеличивается на 1. После завершения цикла, если были найдены соседи (т.е. numNeighbors > 0), то средняя скорость (avgDX) вычисляется путём деления суммированного вектора скоростей на количество соседей. Затем текущая скорость "dx" боида корректируется в сторону средней скорости соседей с учетом коэффициента "alignmentWeight". 

    Метод "Alignment" позволяет боиду адаптировать свою скорость в соответствии со скоростями своих соседей. Это поведение помогает группам боидов двигаться более согласованно и снижает вероятность конфликтов или резких изменений направления. Параметры, такие как "alignmentDist" и "alignmentWeight", задают радиус действия эффекта выравнивания и степень влияния средней скорости соседей на скорость текущего боида соответственно. 
    //——————————————————————————————————————————————————————————————————————————————
    // Выравнивание скорости с другими боидами
    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;
        }
      }
    }
    //——————————————————————————————————————————————————————————————————————————————

    Метод "LimitSpeed" класса "C_AO_NOA2" предназначен для контроля скорости агентов (боидов) в модели, чтобы убедиться, что скорости боидов находятся в заданном диапазоне, "speed" - переменная для хранения текущей скорости боидов, которая будет вычисляться как длина вектора скорости. Цикл по координатам (coords) вычисляет квадрат скорости для каждой координаты (добавляя  boid.dx [c] * boid.dx [c]) и суммирует их. Это позволяет вычислить квадрат длины вектора скорости. Затем длина (или фактическая скорость) достигается с помощью функции "MathSqrt", которая вычисляет квадратный корень из суммы квадратов. Если скорость больше небольшого значения (1e-10), программа продолжает выполнение, если текущая скорость меньше минимальной допустимой скорости (определяемой как speedMax[0] * minSpeed), тогда вектор скорости "boid.dx" нормализуется (делится на его текущую длину) и увеличивается до величины, равной минимальной допустимого значения.

    Если текущая скорость больше максимальной допустимой скорости (speedMax[0] * maxSpeed), тогда аналогично, вектор скорости нормализуется и устанавливается на величину максимальной скорости, а если скорость почти нулевая (ноль или очень близко к нулю), в этом случае, каждая координата вектора скорости замещается на небольшое случайное значение, полученное с помощью функции "u.RNDfromCI (-1.0, 1.0)", умноженное на максимальную скорость.

    Метод "LimitSpeed" обеспечивает поддержание скорости боидов в допустимых пределах, предотвращая слишком медленные или слишком быстрые движения. Это поведение позволяет создать более реалистичное моделирование агентов, так как оно не допускает значительных отклонений в скорости, что может привести к неестественному движению. Параметры "minSpeed" и "maxSpeed" могут быть настроены для регулировки поведения агентов и их скорости в зависимости от задач моделирования.

    //——————————————————————————————————————————————————————————————————————————————
    // Ограничение скорости
    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 (speed > 1e-10)
      {
        // Если скорость слишком мала, увеличиваем её
        if (speed < speedMax [0] * minSpeed)
        {
          for (int c = 0; c < coords; c++)
          {
            boid.dx [c] = boid.dx [c] / speed * speedMax [c] * minSpeed;
          }
        }
        // Если скорость слишком велика, уменьшаем её
        else
          if (speed > speedMax [0] * maxSpeed)
          {
            for (int c = 0; c < coords; c++)
            {
              boid.dx [c] = boid.dx [c] / speed * speedMax [c] * maxSpeed;
            }
          }
      }
      else
      {
        // Если скорость почти нулевая, задаем небольшую случайную скорость
        for (int c = 0; c < coords; c++)
        {
          boid.dx [c] = u.RNDfromCI (-1.0, 1.0) * speedMax [c] * minSpeed;
        }
      }
    }
    //——————————————————————————————————————————————————————————————————————————————

    Метод "KeepWithinBounds" класса "C_AO_NOA2" предназначен для удержания агента (боидa) в заданных границах. Если боид оказывается слишком близко к краям области, этот метод изменяет его направление и обеспечивает определённый толчок обратно в границы. Метод начинается с цикла по всем координатам, что позволяет работать с многомерным пространством.

    Для каждой координаты проверяет, находится ли позиция боидa (boid.x [c]) ниже минимальной границы (rangeMin[c]), если да, то направление скорости (boid.dx[c]) инвертируется с помощью "boid.dx [c] *= -1.0". Это означает, что боид будет двигаться в противоположном направлении. Затем добавляется небольшой толчок от границы: (rangeMax[c] - rangeMin[c]) * 0.001, который помогает выталкивать боид обратно внутрь области.

    Выполняется аналогичная проверка для максимальной границы (rangeMax[c]): если позиция боида выходит за рамки максимума, его скорость также инвертируется и корректируется, но с вычитанием некоторого значения, аналогичного предыдущему случаю.

    Метод "KeepWithinBounds" эффективно ограничивает движения боидов в заданной области, предотвращая вылет за пределы и обеспечивая их возвращение внутрь. 

    //——————————————————————————————————————————————————————————————————————————————
    // Удержание боида в границах. Если он оказывается слишком близко к краю,
    // подталкиваем его обратно и меняем направление.
    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;
          // Добавляем небольшой толчок от границы
          boid.dx [c] += (rangeMax [c] - rangeMin [c]) * 0.001;
        }
        if (boid.x [c] > rangeMax [c])
        {
          boid.dx [c] *= -1.0;
          // Добавляем небольшой толчок от границы
          boid.dx [c] -= (rangeMax [c] - rangeMin [c]) * 0.001;
        }
      }
    }
    //——————————————————————————————————————————————————————————————————————————————

    Метод "Distance" класса "C_AO_NOA2" предназначен для вычисления расстояния между двумя агентами (боидами) в многомерном пространстве. Он использует формулу для вычисления евклидова расстояния, "dist" — переменная, в которой будет храниться сумма квадратов разностей координат двух боидов.

    Метод запускает цикл, который проходит по всем координатам, что позволяет вычислять расстояние в пространстве любой размерности. Для каждой координаты "c" вычисляется квадрат разности между соответствующими координатами обоих боидов: boid1.x[c] - boid2.x[c].

    Результаты ((boid1.x[c] - boid2.x[c])^2) добавляются к переменной  "dist". После завершения цикла, переменная "dist" будет содержать сумму квадратов разностей координат. Для получения фактического расстояния, метод использует "MathSqrt", чтобы вычислить квадратный корень из суммы квадратов, что соответствует формуле евклидова расстояния.

    //——————————————————————————————————————————————————————————————————————————————
    // Расчет расстояния между двумя боидами
    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);
    }
    //——————————————————————————————————————————————————————————————————————————————

    Метод "Revision" класса "C_AO_NOA2" отвечает за обновление информации о наилучшем найденном решении в процессе оптимизации. Этот процесс включает в себя обновление значения фитнес-функции, а также координат, соответствующих этому значению. Метод также отслеживает прогресс и адаптирует параметры алгоритма, если есть существенные улучшения. Метод проходит по всей популяции, представленной массивом "a", который содержит "popSize" (число агентов).

    Внутри цикла происходит проверка каждого агента на то, является ли его фитнес-значение (a[i].f) больше, чем текущее лучшее значение (fB). Если текущий агент показывает лучшее фитнес-значение, — обновляется глобальное лучшее значение фитнес-функции, присваивая "fB" новое значение "a[i].f", далее происходит обновление координат лучшего решения. Для каждой координаты "c" массив "cB" (содержащий координаты лучшего решения) обновляется значениями текущего агента. Счетчик стагнации (m_stagnationCounter) сбрасывается до нуля, потому что было найдено улучшение в решении.

    Метод использует переменную "hasProgress", чтобы определить, был ли достигнут прогресс. Это делается путем вычисления абсолютной разности между текущим и предыдущим лучшими значениями фитнес-функции (MathAbs(fB - m_prevBestFitness)), и если эта разность больше 0.000001, считается, что прогресс налицо. Если прогресс есть, обновляется "m_prevBestFitness" на текущее лучшее значение "fB".
    Также происходит адаптация скорости исследования "explorationRate": она уменьшается, если найдено улучшение, с учетом некоторого параметра из массива "params" и текущего значения "explorationRate".

    //——————————————————————————————————————————————————————————————————————————————
    // Обновление лучшего найденного решения
    void C_AO_NOA2::Revision ()
    {
      // Обновляем лучшие координаты и значение фитнес-функции
      for (int i = 0; i < popSize; i++)
      {
        // Обновление глобального лучшего решения
        if (a [i].f > fB)
        {
          fB = a [i].f;
          for (int c = 0; c < coords; c++)
          {
            cB [c] = a [i].c [c];
          }
    
          // Сбрасываем счетчик стагнации при нахождении лучшего решения
          m_stagnationCounter = 0;
        }
      }
    
      // Проверка наличия прогресса для адаптации параметров алгоритма
      bool hasProgress = MathAbs (fB - m_prevBestFitness) > 0.000001;
      if (hasProgress)
      {
        m_prevBestFitness = fB;
        explorationRate = MathMax (params [11].val * 0.5, explorationRate * 0.9);
      }
    }
    //——————————————————————————————————————————————————————————————————————————————


    Результаты тестов

    Результаты тестирования достаточно слабые.

    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%)

    На визуализации можно заметить скромные возможности поиска. Возможность изменения внешних параметров алгоритма с помощью глобальных переменных позволяет эксперементировать с поведением боидов, выявляя интересные модели поведения. Ниже представлены визуализации одних из множества возможных моделей поведения.

    Hilly

    NOA2 на тестовой функции Hilly

    Forest

    NOA2 на тестовой функции Forest

    Megacity

    NOA2 на тестовой функции Megacity

    По итогам тестирования, алгоритм NOA2 в базовой версии только в ознакомительных целях приводится в нашей рейтинговой таблице популяционных алгоритмов оптимизации.

    AO Description 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)
    1ANSacross neighbourhood search0,949480,847760,438572,235811,000000,923340,399882,323230,709230,634770,230911,574916,13468,15
    2CLAcode lock algorithm (joo)0,953450,871070,375902,200420,989420,917090,316422,222940,796920,693850,193031,683806,10767,86
    3AMOmanimal migration ptimization M0,903580,843170,462842,209590,990010,924360,465982,380340,567690,591320,237731,396755,98766,52
    4(P+O)ES(P+O) evolution strategies0,922560,881010,400212,203790,977500,874900,319452,171850,673850,629850,186341,490035,86665,17
    5CTAcomet tail algorithm (joo)0,953460,863190,277702,094350,997940,857400,339492,194840,887690,564310,105121,557125,84664,96
    6TETAtime evolution travel algorithm (joo)0,913620,823490,319902,057010,970960,895320,293242,159520,734620,685690,160211,580525,79764,41
    7SDSmstochastic diffusion search M0,930660,854450,394762,179880,999830,892440,196192,088460,723330,611000,106701,441035,70963,44
    8BOAmbilliards optimization algorithm M0,957570,825990,252352,035901,000000,900360,305022,205380,735380,525230,095631,356255,59862,19
    9AAmarchery algorithm M0,917440,708760,421602,047800,925270,758020,353282,036570,673850,552000,237381,463235,54861,64
    10ESGevolution of social groups (joo)0,999060,796540,350562,146161,000000,828630,131021,959650,823330,553000,047251,423585,52961,44
    11SIAsimulated isotropic annealing (joo)0,957840,842640,414652,215130,982390,795860,205071,983320,686670,493000,090531,270205,46960,76
    12ACSartificial cooperative search0,755470,747440,304071,806981,000000,888610,224132,112740,690770,481850,133221,305835,22658,06
    13DAdialectical algorithm0,861830,700330,337241,899400,981630,727720,287181,996530,703080,452920,163671,319675,21657,95
    14BHAmblack hole algorithm M0,752360,766750,345831,864930,935930,801520,271772,009230,650770,516460,154721,321955,19657,73
    15ASOanarchy society optimization0,848720,746460,314651,909830,961480,791500,238031,991010,570770,540620,166141,277525,17857,54
    16RFOroyal flush optimization (joo)0,833610,737420,346291,917330,894240,738240,240981,873460,631540,502920,164211,298675,08956,55
    17AOSmatomic orbital search M0,802320,704490,310211,817020,856600,694510,219961,771070,746150,528620,143581,418355,00655,63
    18TSEAturtle shell evolution algorithm (joo)0,967980,644800,296721,909490,994490,619810,227081,841390,690770,426460,135981,253225,00455,60
    19DEdifferential evolution0,950440,616740,303081,870260,953170,788960,166521,908650,786670,360330,029531,176534,95555,06
    20SRAsuccessful restaurateur algorithm (joo)0,968830,634550,292171,895550,946370,555060,191241,692670,749230,440310,125261,314804,90354,48
    21CROchemical reaction optimisation0,946290,661120,298531,905930,879060,584220,211461,674730,758460,426460,126861,311784,89254,36
    22BIOblood inheritance optimization (joo)0,815680,653360,308771,777810,899370,653190,217601,770160,678460,476310,139021,293784,84253,80
    23BSAbird swarm algorithm0,893060,649000,262501,804550,924200,711210,249391,884790,693850,326150,100121,120124,80953,44
    24HSharmony search0,865090,687820,325271,878180,999990,680020,095901,775920,620000,422670,054581,097254,75152,79
    25SSGsaplings sowing and growing0,778390,649250,395431,823080,859730,624670,174291,658690,646670,441330,105981,193984,67651,95
    26BCOmbacterial chemotaxis optimization M0,759530,622680,314831,697040,893780,613390,225421,732590,653850,420920,144351,219124,64951,65
    27ABOafrican buffalo optimization0,833370,622470,299641,755480,921700,586180,197231,705110,610000,431540,132251,173784,63451,49
    28(PO)ES(PO) evolution strategies0,790250,626470,429351,846060,876160,609430,195911,681510,590000,379330,113221,082554,61051,22
    29TSmtabu search M0,877950,614310,291041,783300,928850,518440,190541,637830,610770,382150,121571,114494,53650,40
    30BSObrain storm optimization0,937360,576160,296881,810410,931310,558660,235371,725340,552310,290770,119140,962224,49849,98
    31WOAmwale optimization algorithm M0,845210,562980,262631,670810,931000,522780,163651,617430,663080,411380,113571,188034,47649,74
    32AEFAartificial electric field algorithm0,877000,617530,252351,746880,927290,726980,180641,834900,666150,116310,095080,877544,45949,55
    33AEOartificial ecosystem-based optimization algorithm0,913800,467130,264701,645630,902230,437050,214001,553270,661540,308000,285631,255174,45449,49
    34ACOmant colony optimization M0,881900,661270,303771,846930,858730,586800,150511,596040,596670,373330,024720,994724,43849,31
    35BFO-GAbacterial foraging optimization - ga0,891500,551110,315291,757900,969820,396120,063051,428990,726670,275000,035251,036924,22446,93
    36SOAsimple optimization algorithm0,915200,469760,270891,655850,896750,374010,169841,440600,695380,280310,108521,084224,18146,45
    37ABHAartificial bee hive algorithm0,841310,542270,263041,646630,878580,477790,171811,528180,509230,338770,103970,951974,12745,85
    38ACMOatmospheric cloud model optimization0,903210,485460,304031,692700,802680,378570,191781,373030,623080,244000,107950,975034,04144,90
    39ADAMmadaptive moment estimation M0,886350,447660,266131,600140,844970,384930,168891,398800,661540,270460,105941,037944,03744,85
    40CGOchaos game optimization0,572560,371580,320181,264320,611760,619310,621611,852670,375380,219230,190280,784903,90243,35
    41ATAmartificial tribe algorithm M0,717710,553040,252351,523100,824910,559040,204731,588670,440000,186150,094110,720263,83242,58
    42CFOcentral force optimization0,609610,549580,278311,437500,634180,468330,225411,327920,572310,234770,095860,902943,66840,76
    43ASHAartificial showering algorithm0,896860,404330,256171,557370,803600,355260,191601,350460,476920,181230,097740,755893,66440,71
    44ASBOadaptive social behavior optimization0,763310,492530,326191,582020,795460,400350,260971,456770,264620,171690,182000,618313,65740,63
    45MECmind evolutionary computation0,695330,533760,326611,555690,724640,330360,071981,126980,525000,220000,041980,786983,47038,55
    NOA2neuroboids optimization algorithm 2(joo)0,476810,307640,254471,038920,323800,209770,157400,690970,270770,146770,097290,514832,24524,94


    Выводы

    Разработанный мной алгоритм нейробоидной оптимизации (NOA2) представляет собой гибридный подход, объединяющий принципы роевого интеллекта (алгоритм боидов) с нейронным управлением. Ключевая идея заключается в применении нейронной сети для адаптивного управления параметрами поведения агентов-боидов. В текущей реализации используется простая однослойная нейронная сеть без скрытых слоев. Она состоит из входного слоя, принимающего текущие координаты, скорости, расстояние до лучшего решения и значение фитнес-функции, а также выходного слоя, который определяет коррекцию скоростей и адаптивные параметры для правил поведения стаи боидов.

    В данной версии отсутствуют промежуточные слои. Количество входов определяется как (coords * 2 + 2), где "coords" — это количество измерений пространства поиска. Выходы включают коррекцию по каждой координате и три дополнительных параметра для адаптации правил стаи. В алгоритме присутствует большое количество настраиваемых параметров, что утрудняет поиск наилучших и я перепробовал различные комбинации, но так и не нашел идеального сочетания, которое бы демонстрировало лучшие результаты на тестовых функциях.

    В текущем виде, алгоритм явно служит демонстрацией концепции и в целом, является интересным экспериментом в области гибридных методов оптимизации, но не демонстрирует достаточной производительности для квалификации. Он набирает минимальное количество баллов в проведенных тестах и может рассматриваться лишь как иллюстрация подхода к гибридизации алгоритмов. Для заинтересованных исследователей и разработчиков код NOA2 может служить отправной точкой для экспериментов с различными конфигурациями и параметрами, а также для создания более совершенных гибридных алгоритмов оптимизации, использующих преимущества как популяционных методов, так и методов машинного обучения.

    Tab

    Рисунок 3. Цветовая градация алгоритмов по соответствующим тестам

    chart

    Рисунок 4. Гистограмма результатов тестирования алгоритмов (по шкале от 0 до 100, чем больше, тем лучше, где 100 — максимально возможный теоретический результат, в архиве скрипт для расчета рейтинговой таблицы)

    Плюсы и минусы алгоритма NOA2:

    Плюсы:

    1. Интересная идея для реализации.

    Минусы:

    1. Слабые результаты.

    К статье прикреплён архив с актуальными версиями кодов алгоритмов. Автор статьи не несёт ответственности за абсолютную точность в описании канонических алгоритмов, во многие из них были внесены изменения для улучшения поисковых возможностей. Выводы и суждения, представленные в статьях, основываются на результатах проведённых экспериментов.


    Программы, используемые в статье

    #ИмяТипОписание
    1#C_AO.mqh
    Включаемый файл
    Родительский класс популяционных алгоритмов оптимизации
    2#C_AO_enum.mqh
    Включаемый файл
    Перечисление популяционных алгоритмов оптимизации
    3TestFunctions.mqh
    Включаемый файл
    Библиотека тестовых функций
    4TestStandFunctions.mqh
    Включаемый файл
    Библиотека функций тестового стенда
    5Utilities.mqh
    Включаемый файл
    Библиотека вспомогательных функций
    6CalculationTestResults.mqh
    Включаемый файл
    Скрипт для расчета результатов в сравнительную таблицу
    7Testing AOs.mq5
    СкриптЕдиный испытательный стенд для всех популяционных алгоритмов оптимизации
    8Simple use of population optimization algorithms.mq5
    Скрипт
    Простой пример использования популяционных алгоритмов оптимизации без визуализации
    9Test_AO_NOA2.mq5
    СкриптИспытательный стенд для NOA2
    Прикрепленные файлы |
    NOA2.ZIP (406.44 KB)
    Создание торговой панели администратора на MQL5 (Часть II): Повышение оперативности реагирования и быстрого обмена сообщениями Создание торговой панели администратора на MQL5 (Часть II): Повышение оперативности реагирования и быстрого обмена сообщениями
    В настоящей статье улучшим оперативность работы панели администратора, созданную нами ранее. Кроме того, мы рассмотрим важность быстрого обмена сообщениями в контексте торговых сигналов.
    Нейросети в трейдинге: Выявление аномалий в частотной области (Окончание) Нейросети в трейдинге: Выявление аномалий в частотной области (Окончание)
    Продолжаем работу над имплементацией подходов фреймворка CATCH, который объединяет преобразование Фурье и механизм частотного патчинга, обеспечивая точное выявление рыночных аномалий. В этой работе мы завершаем реализацию собственного видения предложенных подходов и проведем тестирование новых моделей на реальных исторических данных.
    Реализация модели таблицы в MQL5: Применение концепции MVC Реализация модели таблицы в MQL5: Применение концепции MVC
    В статье рассмотрим процесс разработки модели таблицы на языке MQL5 с использованием архитектурной концепции MVC (Model-View-Controller) для разделения логики данных, представления и управления, что помогает создавать структурированный, гибкий и масштабируемый код. Рассмотрим реализацию классов для построения модели таблицы, включая использование связанных списков для хранения данных.
    Разработка трендовых торговых стратегий на основе машинного обучения Разработка трендовых торговых стратегий на основе машинного обучения
    В данной статье предложен оригинальный подход к разработке трендовых стратегий. Вы узнаете, как можно делать разметку обучающих примеров и обучать на них классификаторы. На выходе получатся готовые торговые системы, работающие под управлением терминала MetaTrader 5.