preview
Алгоритм Бизона — Bison Algorithm (BIA)

Алгоритм Бизона — Bison Algorithm (BIA)

MetaTrader 5Трейдинг |
112 0
Andrey Dik
Andrey Dik

Содержание

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


Введение

В поисках лучшего оптимизационного метода, в этой статье познакомимся с Алгоритмом Бизона (Bison Algorithm, BIA) — это популяционный алгоритм оптимизации, вдохновлённый поведением роя, предназначенный для непрерывных задач с одной целевой функцией. Ключевыми особенностями BIA являются два основополагающих принципа, заимствованных из поведения бизонов, это способность к динамичному перемещению и оборонительная стратегия. Алгоритм был разработан чешскими исследователями в 2017 году и был опубликован в сборнике Lecture Notes in Computer Science (Springer) в 2019 году. 

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

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


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

Вы наблюдаете за стадом бизонов в прерии. Им нужно найти самое лучшее пастбище с сочной травой и чистой водой. Но прерия огромна! Как же они это делают? Разделим стадо бизонов на две группы.

Основное стадо (80% бизонов) — это осторожные бизоны. Они держатся вместе, медленно передвигаются группой. "Безопасность в числах!" — их девиз.

Исследователи (20% бизонов) — это смелые молодые бизоны. Они бегут вперед в одном направлении, исследуя новые территории. "А что там, за холмом?" — думают они.

Сформированы две группы: 40 бизонов основного стада разбредаются по известной территории, а 10 бегунов собираются возле самого упитанного бизона (он явно знает, где хорошая трава!) и готовятся к забегу. Когда бизон из основного стада идет к новому месту, он сначала запоминает, где стоял. Делает несколько шагов (от 0 до 3.5 метров). Пробует траву. Если новая трава хуже, тогда возвращается обратно. "Лучше синица в руках..." Если бегун-исследователь нашел место лучше, чем у худшего в стаде бизона, то происходит их замена местами. Бегуны каждый день слегка меняют направление (на 10% влево или вправо). Так больше шансов найти что-то новое. Когда стадо собирается вокруг лучших бизонов, они не просто встают посередине. Вожак (самый упитанный) имеет "вес" 100, второй — 90, третий — 80... десятый — 10. 

Теперь представьте, что:

  • Пастбище = пространство поиска решений
  • Качество травы = значение целевой функции
  • Позиция бизона = потенциальное решение
  • Лучшее пастбище = оптимальное решение

Диаграмма ниже показывает фазу инициализации — разделение популяции на роевую группу (80%) и бегущую группу (20%). Основной цикл итераций с четырьмя шагами: определение цели роения (выбор между лучшим бегуном или центром элиты), движение роевой группы к центру (вычисление центра элиты со взвешенным усреднением), движение бегунов по направлению исследования, проверка и обновление результатов.

bison-algorithm

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

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

Приступим к написанию псевдокода алгоритма DIA.

ИНИЦИАЛИЗАЦИЯ:
1. Создать популяцию из N бизонов
2. Разделить на группы:
   - Роевая группа (80%): разместить случайно по всему пространству
   - Бегущая группа (20%): разместить вокруг лучшего бизона роевой группы
3. Задать случайное направление бега для разведчиков

ОСНОВНОЙ ЦИКЛ (повторять до сходимости):
  
  ВЫБОР ЦЕЛИ ДВИЖЕНИЯ:
  - ЕСЛИ лучший из бегущей группы нашел место лучше худшего в стаде:
      Цель = позиция лучшего из бегущей группы
  - ИНАЧЕ:
      Цель = взвешенный центр 10 лучших в стаде
  
  ДВИЖЕНИЕ РОЕВОЙ ГРУППЫ:
  - Для каждого бизона в стаде:
    1. Сохранить текущую позицию
    2. Вычислить направление к цели
    3. Сделать шаг к цели (случайная длина от 0 до 3.5)
    4. Проверить границы области поиска
  
  ДВИЖЕНИЕ БЕГУЩЕЙ ГРУППЫ:
  - Слегка изменить направление бега (×0.9-1.1)
  - Для каждого индивида:
    1. сдвинуться по направлению бега
    2. проверить границы области поиска
  
  ОЦЕНКА И ОБНОВЛЕНИЕ:
  1. Проверка роевой группы:
     - ЕСЛИ новая позиция хуже старой:
         Вернуть старую позицию
  
  2. Обмен между группами:
     - ЕСЛИ лучший индивид из бегущей группы лучше худшего в стаде:
         Заменить худшего в стаде на лучшего индивида
  
  3. Сортировка:
     - Отсортировать роевую группу по качеству
  
  4. Обновление:
     - Запомнить лучшее найденное решение
     - Запомнить худшее решение

КОНЕЦ ЦИКЛА

ВЫЧИСЛЕНИЕ ВЗВЕШЕННОГО ЦЕНТРА:
- Взять 10 лучших решений
- Присвоить веса: 1-й получает вес 100, 2-й - 90, ..., 10-й - 10
- Центр = сумма(позиция × вес) / сумма(весов)

Теперь нам более понятно, как все работает, и мы можем приступить к написанию кода алгоритма. Класс реализует "Bison Algorithm" и указывает наследование от базового класса "C_AO".

Параметры алгоритма:
  • popSize — определяет общее число "индивидов" или "агентов" в популяции, участвующей в работе алгоритма.
  • swarmGroupRate — задает долю (в процентах) от общего числа индивидов, которая будет составлять "роевую группу".
  • eliteGroupSize — определяет количество самых лучших индивидов, которые формируют "элитную группу" для вычисления центра.
  • overstep — устанавливает максимальный шаг, который может сделать "роевая группа" при своем движении.

В конструкторе устанавливаются значения по умолчанию для всех параметров. Метод "SetParams" позволяет обновить значения параметров алгоритма, используя данные из внешнего источника (представленного массивом "params").

Функциональность:

  • Init — метод для начальной инициализации алгоритма, с использованием диапазонов значений для параметров.
  • Moving — метод, отвечающий за движение или обновление состояния индивидов в популяции.
  • Revision — метод используется для пересмотра и корректировки поведения индивидов или параметров.
  • ComputeCenter — приватный метод для вычисления центра элитной группы.
  • CheckIfRunnerBetter — приватный метод, проверяет, является ли какой-либо "бегущий" индивид лучше других.
Внутренние переменные:
  • swarmGroupSize — рассчитывается на основе "popSize" и "swarmGroupRate", определяет фактическое количество особей в роевой группе.
  • runningGroupSize — размер "бегущей" группы (подгруппа в общей популяции).
  • runDirection — массив, хранящий направление движения для каждого индивида.
  • center — массив, хранящий координаты центра элитной группы.

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

class C_AO_BisonAlgorithm : public C_AO
{
  public: //----------------------------------------------------------
  ~C_AO_BisonAlgorithm () { }
  C_AO_BisonAlgorithm ()
  {
    ao_name = "BIA";
    ao_desc = "Bison Algorithm";
    ao_link = "https://www.mql5.com/ru/articles/19444";

    popSize          = 50;    // размер популяции
    swarmGroupRate   = 0.8;   // доля роевой группы от популяции (80%)
    eliteGroupSize   = 10;    // размер элитной группы для вычисления центра (s параметр)
    overstep         = 3.5;   // максимальный шаг роевой группы

    ArrayResize (params, 4);

    params [0].name = "popSize";        params [0].val = popSize;
    params [1].name = "swarmGroupRate"; params [1].val = swarmGroupRate;
    params [2].name = "eliteGroupSize"; params [2].val = eliteGroupSize;
    params [3].name = "overstep";       params [3].val = overstep;
  }

  void SetParams ()
  {
    popSize        = (int)params [0].val;
    swarmGroupRate = params      [1].val;
    eliteGroupSize = (int)params [2].val;
    overstep       = params      [3].val;
  }

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

  void Moving   ();
  void Revision ();
  
  private:
  void ComputeCenter ();
  bool CheckIfRunnerBetter ();

  //------------------------------------------------------------------
  public:
  double swarmGroupRate;     // доля роевой группы
  int    eliteGroupSize;     // размер элитной группы (s параметр)
  double overstep;           // максимальный шаг роевой группы

  private: //---------------------------------------------------------
  int    swarmGroupSize;     // размер роевой группы
  int    runningGroupSize;   // размер бегущей группы
  double runDirection [];    // направление бега
  double center [];          // центр элитной группы
};
//————————————————————————————————————————————————————————————————————

Метод "Init" класса "C_AO_BisonAlgorithm" отвечает за первоначальную настройку алгоритма перед началом его работы. Сначала метод пытается выполнить общую, стандартизированную часть инициализации, используя передаваемые ей диапазоны значений для параметров и количество эпох. Если эта стандартная часть не удается, весь процесс инициализации прерывается. Далее, исходя из общего размера популяции и заданного коэффициента, вычисляется количество особей, которые будут относиться к "роевой группе". Оставшиеся особи формируют "бегущую группу".

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

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

//————————————————————————————————————————————————————————————————————
//--- Инициализация
bool C_AO_BisonAlgorithm::Init (const double &rangeMinP  [],
                                const double &rangeMaxP  [],
                                const double &rangeStepP [],
                                const int     epochsP = 0)
{
  if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false;

  //------------------------------------------------------------------
  // Вычисляем размеры групп
  swarmGroupSize   = (int)MathFloor (popSize * swarmGroupRate);
  runningGroupSize = popSize - swarmGroupSize;
  
  // Корректировка размеров групп
  if (swarmGroupSize < 1) swarmGroupSize = 1;
  if (swarmGroupSize >= popSize) swarmGroupSize = popSize - 1;
  if (eliteGroupSize < 1) eliteGroupSize = 1;
  if (eliteGroupSize > swarmGroupSize) eliteGroupSize = swarmGroupSize;
  
  // Инициализация вспомогательных массивов
  ArrayResize (runDirection, coords);
  ArrayResize (center, coords);

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

Метод "Moving" класса "C_AO_BisonAlgorithm" является ядром алгоритма, отвечающим за одну итерацию или шаг эволюции популяции. Он состоит из двух основных фаз: первоначальной инициализации популяции (если алгоритм запускается впервые) и последующих шагов обновления позиций особей.

Фаза 1: Первоначальная инициализация (при первом запуске): если алгоритм запускается впервые (флаг "revision" установлен в "false"), выполняется следующая последовательность действий:

  • Генерация роевой группы. Особи, входящие в роевую группу, инициализируются случайными значениями в пределах заданных для каждого параметра диапазонов. После генерации эти значения могут быть дополнительно скорректированы, чтобы соответствовать шагу изменения параметров.
  • Поиск лучшего бизона. Среди всех особей роевой группы находится та, которая имеет наилучшую приспособленность (наибольшее значение целевой функции).
  • Генерация бегущей группы. Остальные особи популяции (бегущая группа) инициализируются вокруг найденного лучшего бизона. Их позиции генерируются случайным образом в некоторой окрестности лучшего решения, а затем также корректируются, в соответствии с диапазонами и шагами параметров.
  • Инициализация вектора направления движения. Для каждой размерности пространства поиска генерируется случайный вектор направления движения. Величина этого вектора зависит от общего диапазона значений параметра, и его знак (положительный или отрицательный) также выбирается случайно.
  • Установка флага инициализации. Флаг "revision" устанавливается в "true", начальная инициализация завершена, и в дальнейшем будут выполняться только шаги обновления.

    Фаза 2: Основной цикл итерации (последующие шаги): когда начальная инициализация уже проведена, метод выполняет следующие шаги для каждой итерации:

    Определение цели роения. Алгоритм проверяет, не является ли какое-либо решение из бегущей группы лучше, чем текущая "цель роения".
    • Если решение из бегущей группы оказывается лучше, то эта цель роения (центроид) устанавливается равной этому лучшему решению.
    • В противном случае, вычисляется новый центроид, который представляет собой среднюю позицию лучших решений популяции.
    Движение роевой группы. Каждая особь роевой группы перемещается.
    • Сначала сохраняются текущие позиции и значения приспособленности особи.
    • Затем новая позиция рассчитывается путем движения от текущей позиции в направлении целевой точки роения. Величина этого движения определяется случайным фактором, который регулирует "перешагивание" через цель.
    • Полученная новая позиция затем корректируется, чтобы оставаться в пределах допустимых диапазонов и учитывать шаг параметров.

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

    Движение бегущей группы. Каждая особь бегущей группы перемещается, используя скорректированный вектор направления.

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

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

      /————————————————————————————————————————————————————————————————————
      //--- Основной шаг алгоритма
      void C_AO_BisonAlgorithm::Moving ()
      {
        // Начальная инициализация популяции
        if (!revision)
        {
          // Генерация роевой группы случайным образом
          for (int i = 0; i < swarmGroupSize; i++)
          {
            for (int j = 0; j < coords; j++)
            {
              a [i].c [j] = u.RNDfromCI (rangeMin [j], rangeMax [j]);
              a [i].c [j] = u.SeInDiSp (a [i].c [j], rangeMin [j], rangeMax [j], rangeStep [j]);
            }
          }
          
          // Находим лучшее решение среди роевой группы для инициализации бегунов
          int bestIdx = 0;
          double bestFit = a [0].f;
          for (int i = 1; i < swarmGroupSize; i++)
          {
            if (a [i].f > bestFit)
            {
              bestFit = a [i].f;
              bestIdx = i;
            }
          }
          
          // Генерация бегущей группы вокруг лучшего бизона
          double neighbourhood = (rangeMax [0] - rangeMin [0]) / 15.0;
          for (int i = swarmGroupSize; i < popSize; i++)
          {
            for (int j = 0; j < coords; j++)
            {
              a [i].c [j] = a [bestIdx].c [j] + u.RNDfromCI (-neighbourhood, neighbourhood);
              a [i].c [j] = u.SeInDiSp (a [i].c [j], rangeMin [j], rangeMax [j], rangeStep [j]);
            }
          }
          
          // Генерация run direction = random((ub-lb)/45, (ub-lb)/15)
          for (int j = 0; j < coords; j++)
          {
            double range = rangeMax [j] - rangeMin [j];
            runDirection [j] = u.RNDfromCI (range / 45.0, range / 15.0);
            if (u.RNDprobab () < 0.5) runDirection [j] = -runDirection [j];
          }
          
          revision = true;
          return;
        }
      
        //------------------------------------------------------------------
        // Основной цикл итерации
        
        // 1. Определяем цель роения (swarming target)
        if (CheckIfRunnerBetter ())
        {
          // Если runner лучше swarmer, center = runner
          // center уже установлен в CheckIfRunnerBetter
        }
        else
        {
          // Иначе вычисляем центр сильнейших решений
          ComputeCenter ();
        }
        
        // 2. Движение роевой группы
        for (int i = 0; i < swarmGroupSize; i++)
        {
          // Сохраняем старые позиции в cP и фитнес в fP
          ArrayCopy (a [i].cP, a [i].c, 0, 0, coords);
          a [i].fP = a [i].f;
          
          // Вычисляем нового кандидата позиции
          for (int c = 0; c < coords; c++)
          {
            double direction = center [c] - a [i].c [c];
            a [i].c [c] = a [i].c [c] + direction * u.RNDfromCI (0.0, overstep);
            a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
          }
        }
        
        // 3. Корректировка run direction вектора
        for (int j = 0; j < coords; j++)
        {
          runDirection [j] = runDirection [j] * u.RNDfromCI (0.9, 1.1);
        }
        
        // 4. Движение бегущей группы
        for (int i = swarmGroupSize; i < popSize; i++)
        {
          for (int c = 0; c < coords; c++)
          {
            a [i].c [c] = a [i].c [c] + runDirection [c];
            a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
          }
        }
      }
      //————————————————————————————————————————————————————————————————————
      

      Метод "Revision" класса "C_AO_BisonAlgorithm" предназначен для обновления состояния популяции после выполнения одного шага движения и оценки приспособленности особей, а также для подведения итогов итерации.

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

      Сравнение бегущей группы с роевой:

      • Находится лучшая особь в бегущей группе (с самой высокой приспособленностью) и худшая особь в роевой группе (с самой низкой приспособленностью).
      • Если лучшая особь из бегущей группы оказывается лучше, чем худшая особь из роевой группы, то худшая особь из роевой группы заменяется лучшей особью из бегущей группы. Это позволяет более сильным решениям из бегущей группы проникать в роевую группу.
      Сортировка роевой группы:
      • Создается временная копия всех особей роевой группы.
      • Эта временная копия сортируется по убыванию приспособленности (от лучшего к худшему).
      • После сортировки, особи из отсортированной временной копии копируются обратно в основную популяцию. Это гарантирует, что в роевой группе всегда будут находиться лучшие решения, найденные на данный момент.
      Обновление глобальных решений:
      • Лучшее глобальное решение. Сравнивается приспособленность лучшей особи в роевой группе (которая теперь является лучшим решением после сортировки) с текущим лучшим глобальным решением. Если лучшая особь роевой группы лучше, обновляется глобальное лучшее решение.
      • Худшее глобальное решение. Просматриваются все особи в популяции (и роевой, и бегущей), чтобы найти самую слабую особь. Если обнаружена особь со значением приспособленности ниже текущего худшего глобального решения, то худшее глобальное решение обновляется.

        Таким образом, метод "Revision" играет роль "приемщика результатов", который отбирает лучшие решения, поддерживает порядок и обновление в роевой группе, и отслеживает общий прогресс алгоритма, сохраняя лучшее и худшее найденное решение.

        //————————————————————————————————————————————————————————————————————
        //--- Обновление и проверка результатов
        void C_AO_BisonAlgorithm::Revision ()
        {
          // 1. Для роевой группы: обновляем позиции только если новая позиция лучше
          for (int i = 0; i < swarmGroupSize; i++)
          {
            // Если новая позиция хуже старой, возвращаем старую
            if (a [i].f < a [i].fP)
            {
              ArrayCopy (a [i].c, a [i].cP, 0, 0, coords);
              a [i].f = a [i].fP;
            }
          }
          
          // 2. Проверяем, не нашел ли runner лучшее решение чем swarmer
          double bestRunnerFitness = -DBL_MAX;
          int bestRunnerIdx = -1;
          double worstSwarmerFitness = DBL_MAX;
          int worstSwarmerIdx = -1;
          
          for (int i = swarmGroupSize; i < popSize; i++)
          {
            if (a[i].f > bestRunnerFitness)
            {
              bestRunnerFitness = a[i].f;
              bestRunnerIdx = i;
            }
          }
          
          for (int i = 0; i < swarmGroupSize; i++)
          {
            if (a[i].f < worstSwarmerFitness)
            {
              worstSwarmerFitness = a[i].f;
              worstSwarmerIdx = i;
            }
          }
          
          // Если runner лучше худшего swarmer, копируем runner в swarming group
          if (bestRunnerIdx >= 0 && worstSwarmerIdx >= 0 && bestRunnerFitness > worstSwarmerFitness)
          {
            ArrayCopy (a[worstSwarmerIdx].c, a[bestRunnerIdx].c, 0, 0, coords);
            a[worstSwarmerIdx].f = a[bestRunnerIdx].f;
          }
          
          // 3. Сортировка роевой группы
          // Создаем временный массив для сортировки только роевой группы
          S_AO_Agent swarmTemp [];
          ArrayResize (swarmTemp, swarmGroupSize);
          
          // Копируем роевую группу
          for (int i = 0; i < swarmGroupSize; i++)
          {
            swarmTemp[i].Init(coords);
            ArrayCopy (swarmTemp[i].c, a[i].c, 0, 0, coords);
            swarmTemp[i].f = a[i].f;
            ArrayCopy (swarmTemp[i].cP, a[i].cP, 0, 0, coords);
            swarmTemp[i].fP = a[i].fP;
          }
          
          // Сортируем
          S_AO_Agent tempSorted [];
          ArrayResize (tempSorted, swarmGroupSize);
          u.Sorting (swarmTemp, tempSorted, swarmGroupSize);
          
          // Копируем обратно отсортированную роевую группу
          for (int i = 0; i < swarmGroupSize; i++)
          {
            ArrayCopy (a[i].c, swarmTemp[i].c, 0, 0, coords);
            a[i].f = swarmTemp[i].f;
            ArrayCopy (a[i].cP, swarmTemp[i].cP, 0, 0, coords);
            a[i].fP = swarmTemp[i].fP;
          }
          
          // 4. Обновляем глобальное лучшее и худшее решение
          if (a[0].f > fB)
          {
            fB = a[0].f;
            ArrayCopy (cB, a[0].c, 0, 0, WHOLE_ARRAY);
          }
          
          // Находим худшее во всей популяции
          for (int i = 0; i < popSize; i++)
          {
            if (a[i].f < fW)
            {
              fW = a[i].f;
              ArrayCopy (cW, a[i].c, 0, 0, WHOLE_ARRAY);
            }
          }
        }
        //————————————————————————————————————————————————————————————————————

        Метод "CheckIfRunnerBetter" служит для определения, содержит ли бегущая группа более перспективное решение, чем те, что присутствуют в роевой группе. Если такое решение найдено, оно становится новой "целью" для роения.

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

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

        Сравнение и определение цели. После этого происходит сравнение:

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

        Возврат результата:

        • Если наилучший "бегун" оказался лучше худшего из "роя", метод сообщает об этом, возвращая утвердительное значение.
        • В противном случае (если ни один "бегун" не превзошел худшего из "роя"), метод сообщает об этом, возвращая отрицательное значение, и "центр роения" остается прежним.

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

        //————————————————————————————————————————————————————————————————————
        //--- Проверка, лучше ли runner чем swarmer
        bool C_AO_BisonAlgorithm::CheckIfRunnerBetter ()
        {
          // Находим лучшего runner
          double bestRunnerFitness = -DBL_MAX;
          int bestRunnerIdx = -1;
          
          for (int i = swarmGroupSize; i < popSize; i++)
          {
            if (a[i].f > bestRunnerFitness)
            {
              bestRunnerFitness = a[i].f;
              bestRunnerIdx = i;
            }
          }
          
          // Находим худшего swarmer 
          double worstSwarmerFitness = DBL_MAX;
          
          for (int i = 0; i < swarmGroupSize; i++)
          {
            if (a[i].f < worstSwarmerFitness)
            {
              worstSwarmerFitness = a[i].f;
            }
          }
          
          // Если runner лучше swarmer, устанавливаем его как центр
          if (bestRunnerIdx >= 0 && bestRunnerFitness > worstSwarmerFitness)
          {
            ArrayCopy (center, a[bestRunnerIdx].c, 0, 0, coords);
            return true;
          }
          
          return false;
        }
        //————————————————————————————————————————————————————————————————————

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

        • Обнуление целевой позиции. Сначала все компоненты будущей целевой позиции (центра) обнуляются. Это подготавливает их к накоплению новых значений.
        • Определение количества лучших решений. Метод выбирает, сколько именно лучших решений будет использоваться для расчета центра. Он берет минимальное значение из двух параметров:

          • eliteGroupSize — максимальное количество "элитных" решений, которые могут быть учтены;
          • swarmGroupSize — размер текущей роевой группы;
          • Это гарантирует, что мы используем либо все заданные элитные решения, либо столько, сколько есть в роевой группе, если элитных решений меньше.
        • Назначение весов. Создается набор весов для каждого из выбранных лучших решений. Эти веса увеличиваются линейно: первое решение получает меньший вес, второе — больший, и так далее, до последнего. Конкретно, веса задаются как 10, 20, 30... до  s * 10, где  s  —количество учитываемых лучших решений. Эти веса показывают, насколько значимым является каждое решение при вычислении центра.
        • Накопление общей суммы весов. Все рассчитанные веса суммируются. Эта общая сумма понадобится для нормализации.
        • Взвешенное суммирование позиций. Теперь для каждой размерности (координаты) позиции:
          • берется значение соответствующей координаты из каждого из  s  лучших решений;
          • это значение координаты умножается на вес соответствующего лучшего решения. Важно отметить, что веса применяются в обратном порядке: самое лучшее решение (первое в отсортированном списке) получается самый большой вес, второе — следующий по величине, и так далее.
          • результаты этих умножений для всех  s  лучших решений суммируются для данной координаты.
        • Нормализация. Полученная сумма взвешенных координат для каждой размерности делится на общую сумму всех весов. Это делает расчет "средневзвешенным", гарантируя, что итоговая позиция центра находится в некоторой "средней" области лучших найденных решений, при этом более качественные решения имеют большее влияние.

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

          //————————————————————————————————————————————————————————————————————
          //--- Вычисление центра элитной группы
          void C_AO_BisonAlgorithm::ComputeCenter ()
          {
            // Обнуляем центр
            for (int j = 0; j < coords; j++)
            {
              center [j] = 0.0;
            }
            
            // Вычисляем взвешенный центр из s лучших решений
            double weights [];
            int s = MathMin(eliteGroupSize, swarmGroupSize);
            ArrayResize (weights, s);
            double totalWeight = 0.0;
            
            // weight = (10, 20, 30, ..., 10*s)
            for (int i = 0; i < s; i++)
            {
              weights[i] = (i + 1) * 10.0;
              totalWeight += weights[i];
            }
            
            // center = sum(weight[i] * position[i]) / sum(weights)
            for (int j = 0; j < coords; j++)
            {
              for (int i = 0; i < s; i++)
              {
                center[j] += (weights[s - 1 - i] * a[i].c[j]) / totalWeight;
              }
            }
          }
          //————————————————————————————————————————————————————————————————————


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

          Результаты, как видим, средние, на дискретной функции Megacity несколько ниже, алгоритм труднее справляется с дискретными задачами.

          Bison|Bison Algorithm|50.0|0.8|10|3.5|
          =============================
          5 Hilly's; Func runs: 10000; result: 0.761847128451914
          25 Hilly's; Func runs: 10000; result: 0.4002698460741261
          500 Hilly's; Func runs: 10000; result: 0.25201531651422443
          =============================
          5 Forest's; Func runs: 10000; result: 0.7620992740937584
          25 Forest's; Func runs: 10000; result: 0.45225292646780135
          500 Forest's; Func runs: 10000; result: 0.19295587559803248
          =============================
          5 Megacity's; Func runs: 10000; result: 0.48769230769230765
          25 Megacity's; Func runs: 10000; result: 0.19876923076923075
          500 Megacity's; Func runs: 10000; result: 0.10058461538461608
          =============================
          All score: 3.60849 (40.09%)


          На визуализации работы алгоритма BIA заметен достаточно большой разброс результатов не только на функциях малой размерности (зеленые линии), но и на функциях средней размерности (голубые линии).

          Hilly

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

          Forest

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

          Megacity

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

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

          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)
          1DOAdingomdingo_optimization_algorithm_M0,479680,453670,463691,397040,941450,879090,914542,735080,786150,860610,848052,494816,62773,63
          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
          12EOmextremal_optimization_M0,761660,772420,317471,851550,999990,767510,235272,002770,747690,539690,142491,429875,28458,71
          13BBObiogeography based optimization0,949120,694560,350311,993990,938200,673650,256821,868670,746150,482770,173691,402615,26558,50
          14ACSartificial cooperative search0,755470,747440,304071,806981,000000,888610,224132,112740,690770,481850,133221,305835,22658,06
          15DAdialectical algorithm0,861830,700330,337241,899400,981630,727720,287181,996530,703080,452920,163671,319675,21657,95
          16BHAmblack hole algorithm M0,752360,766750,345831,864930,935930,801520,271772,009230,650770,516460,154721,321955,19657,73
          17ASOanarchy society optimization0,848720,746460,314651,909830,961480,791500,238031,991010,570770,540620,166141,277525,17857,54
          18RFOroyal flush optimization (joo)0,833610,737420,346291,917330,894240,738240,240981,873460,631540,502920,164211,298675,08956,55
          19AOSmatomic orbital search M0,802320,704490,310211,817020,856600,694510,219961,771070,746150,528620,143581,418355,00655,63
          20TSEAturtle shell evolution algorithm (joo)0,967980,644800,296721,909490,994490,619810,227081,841390,690770,426460,135981,253225,00455,60
          21BSAbacktracking_search_algorithm0,973090,545340,290981,809410,999990,585430,217471,802890,847690,369530,129781,347004,95955,10
          22DEdifferential evolution0,950440,616740,303081,870260,953170,788960,166521,908650,786670,360330,029531,176534,95555,06
          23SRAsuccessful restaurateur algorithm (joo)0,968830,634550,292171,895550,946370,555060,191241,692670,749230,440310,125261,314804,90354,48
          24CROchemical reaction optimisation0,946290,661120,298531,905930,879060,584220,211461,674730,758460,426460,126861,311784,89254,36
          25BIOblood inheritance optimization (joo)0,815680,653360,308771,777810,899370,653190,217601,770160,678460,476310,139021,293784,84253,80
          26DOAdream_optimization_algorithm0,855560,700850,372801,929210,734210,489050,241471,464730,772310,473540,185611,431464,82553,62
          27BSAbird swarm algorithm0,893060,649000,262501,804550,924200,711210,249391,884790,693850,326150,100121,120124,80953,44
          28DEAdolphin_echolocation_algorithm0,759950,675720,341711,777380,895820,642230,239411,777460,615380,440310,151151,206844,76252,91
          29HSharmony search0,865090,687820,325271,878180,999990,680020,095901,775920,620000,422670,054581,097254,75152,79
          30SSGsaplings sowing and growing0,778390,649250,395431,823080,859730,624670,174291,658690,646670,441330,105981,193984,67651,95
          31BCOmbacterial chemotaxis optimization M0,759530,622680,314831,697040,893780,613390,225421,732590,653850,420920,144351,219124,64951,65
          32ABOafrican buffalo optimization0,833370,622470,299641,755480,921700,586180,197231,705110,610000,431540,132251,173784,63451,49
          33(PO)ES(PO) evolution strategies0,790250,626470,429351,846060,876160,609430,195911,681510,590000,379330,113221,082554,61051,22
          34FBAfractal-based Algorithm0,790000,651340,289651,730990,871580,568230,188771,628580,610770,460620,123981,195374,55550,61
          35TSmtabu search M0,877950,614310,291041,783300,928850,518440,190541,637830,610770,382150,121571,114494,53650,40
          36BSObrain storm optimization0,937360,576160,296881,810410,931310,558660,235371,725340,552310,290770,119140,962224,49849,98
          37WOAmwale optimization algorithm M0,845210,562980,262631,670810,931000,522780,163651,617430,663080,411380,113571,188034,47649,74
          38AEFAartificial electric field algorithm0,877000,617530,252351,746880,927290,726980,180641,834900,666150,116310,095080,877544,45949,55
          39AEOartificial ecosystem-based optimization algorithm0,913800,467130,264701,645630,902230,437050,214001,553270,661540,308000,285631,255174,45449,49
          40CAmcamel algorithm M0,786840,560420,351331,698590,827720,560410,243361,631490,648460,330920,134181,113564,44449,37
          41ACOmant colony optimization M0,881900,661270,303771,846930,858730,586800,150511,596040,596670,373330,024720,994724,43849,31
          42CMAEScovariance_matrix_adaptation_evolution_strategy0,762580,720890,000001,483470,820560,796160,000001,616720,758460,490770,000001,249234,34948,33
          43DA_duelistduelist_algorithm0,927820,537780,277921,743520,869570,475360,181931,526860,621530,335690,117151,074374,34548,28
          44BFO-GAbacterial foraging optimization - ga0,891500,551110,315291,757900,969820,396120,063051,428990,726670,275000,035251,036924,22446,93
          BIAbison_algorithm0,761850,400270,252011,414130,762100,452250,192961,407310,487690,198770,100580,787043,60840,09
          RWrandom walk0,487540,321590,257811,066940,375540,219440,158770,753750,279690,149170,098470,527342,34826,09


          Выводы

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

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

          Tab

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

          chart

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

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

          Плюсы:

          1. Простая реализация.
          2. Быстрый.

          Минусы:

          1. Разброс значений на функциях малой и средней размерности.

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


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

          #ИмяТипОписание
          1#C_AO.mqh
          Включаемый файл
          Родительский класс популяционных алгоритмов оптимизации
          2#C_AO_enum.mqh
          Включаемый файл
          Перечисление популяционных алгоритмов оптимизации
          3TestFunctions.mqh
          Включаемый файл
          Библиотека тестовых функций
          4
          TestStandFunctions.mqh
          Включаемый файл
          Библиотека функций тестового стенда
          5
          Utilities.mqh
          Включаемый файл
          Библиотека вспомогательных функций
          6
          CalculationTestResults.mqh
          Включаемый файл
          Скрипт для расчета результатов в сравнительную таблицу
          7
          Testing AOs.mq5
          СкриптЕдиный испытательный стенд для всех популяционных алгоритмов оптимизации
          8
          Simple use of population optimization algorithms.mq5
          Скрипт
          Простой пример использования популяционных алгоритмов оптимизации без визуализации
          9
          Test_AO_BIA.mq5
          СкриптИспытательный стенд для BIA
          Искусство ведения логов (Часть 3): Изучение обработчиков для сохранения логов Искусство ведения логов (Часть 3): Изучение обработчиков для сохранения логов
          В этой статье мы разберем концепцию обработчиков в библиотеке логирования, поймем их работу, и создадим три начальные реализации: консоль, база данных и файл. Мы рассмотрим все: от базовой структуры обработчиков до практического тестирования, заложив основу для их дальнейшей полноценной реализации.
          Создание пользовательской системы определения рыночного режима на языке MQL5 (Часть 1): Индикатор Создание пользовательской системы определения рыночного режима на языке MQL5 (Часть 1): Индикатор
          В этой статье подробно описывается создание системы определения рыночного режима на языке MQL5 с использованием статистических методов, таких как автокорреляция и волатильность. Она предоставляет код для классов, чтобы классифицировать трендовые, диапазонные и волатильные условия, а также пользовательский индикатор.
          Разрабатываем мультивалютный советник (Часть 29): Доработка конвейера Разрабатываем мультивалютный советник (Часть 29): Доработка конвейера
          Повышаем удобство работы с конвейером автоматической оптимизации: попробуем пройти путь от создания проекта оптимизации до теста итогового советника.
          Символьное уравнение прогнозирования цены с использованием SymPy Символьное уравнение прогнозирования цены с использованием SymPy
          Статья описывает интересный подход к алготрейдингу, основанный на символьных математических уравнениях вместо традиционных "черных ящиков" машинного обучения. Автор показывает, как преобразовать непрозрачные нейросети в читаемые математические формулы через библиотеку SymPy и полиномиальную регрессию, что позволяет полностью понимать логику принятия торговых решений. Подход сочетает вычислительную мощь ML с прозрачностью классических методов, давая трейдеру возможность анализировать, корректировать и адаптировать модели в реальном времени.