preview
Экстремальная оптимизация — Extremal Optimization (EO)

Экстремальная оптимизация — Extremal Optimization (EO)

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

Содержание

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


Введение

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

Extremal Optimization (EO)  — это метаэвристический алгоритм оптимизации, вдохновленный моделью Бака-Снеппена (Bak-Sneppen). Алгоритм был разработан Стефаном Бётчером (Stefan Boettcher) и Аллоном Перкусом (Allon Percus) в 1999 году как метод, вдохновленный концепцией самоорганизованной критичности, которые естественным образом эволюционируют к критическому состоянию, где происходят лавинообразные изменения различных масштабов. Population-based EO был разработан для решения задач непрерывной оптимизации, используя популяционные итерационные операции.


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

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

Принципы модели Бака-Снеппена

Экосистема видов:

  • N видов расположены в цепочке (или на решетке)
  • Каждому виду i присвоено значение fitness fi ∈ [0,1]
  • Fitness представляет приспособленность вида к окружающей среде

Динамика эволюции

На каждом шаге времени:
1. Найти вид с минимальным fitness: fmin = min{fi}
2. Заменить fitness этого вида и его соседей новыми случайными значениями
3. Повторять процесс
Самоорганизованная критичность
  • Система самоорганизуется к критическому состоянию
  • Возникает пороговое значение fc ≈ 0.67
  • Виды с fitness < fc вымирают лавинообразно
  • Размеры лавин подчиняются степенному закону

Длительные периоды стагнации, когда мало изменений сменяются внезапными лавинообразными изменениями различных масштабов при отсутствии внешнего управления — система самоорганизуется. Степенные изменения затрагивают размеры лавин: P(s) ~ s^(-τ), где времена жизни видов подчиняются степенному закону при отсутствии характерного масштаба. Изменение одного вида влияет на соседей. Глобальная динамика возникает из локальных взаимодействий.

Бёттчер и Перкус адаптировали принципы модели Бака-Снеппена для создания алгоритма оптимизации. Вместо улучшения лучших, улучшаем худшие. Фокус на худших элементах — это против интуиции, но эффективно для выхода из локальных оптимумов. В модели Бака-Снеппена эволюция происходит через "прерывистое равновесие" — длительные периоды стазиса прерываются внезапными изменениями. EO использует этот принцип: когда изменяются только плохие компоненты, и когда изменение одного компонента драматически улучшает решение, улучшение одного компонента может сделать другие компоненты "плохими".

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

Инициализация:

  1. Создать популяцию из popSize агентов 
  2. Инициализировать параметры
  3. Создать массивы для ранжирования агентов и компонентов

Первый запуск (инициализация популяции):

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

Основной цикл оптимизации:

Для каждого агента в популяции выполнить:

  1. Ранжирование агентов:
    • создать список всех агентов с их fitness
    • отсортировать агентов по fitness (худшие в начале для максимизации)
  2. Выбор целевого агента:
    • использовать степенное распределение P(n) ∝ n^(-τ)
    • выбрать ранг согласно этому распределению
    • определить агента с выбранным рангом как целевого
  3. Ранжирование компонентов целевого агента:
    • Для каждого компонента вычислить его fitness:
      • fitness = 1 - (нормализованное отклонение от лучшего известного значения)
    • Отсортировать компоненты по fitness (худшие в начале)
  4. Выбор и мутация компонента:
    • использовать степенное распределение для выбора ранга компонента
    • заменить выбранный компонент на новое случайное значение
    • проверить границы и применить дискретизацию

Обновление результатов:

  1. Отсортировать всю популяцию по fitness (лучшие в начале)
  2. Обновить глобальное лучшее решение если найдено улучшение

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

  • popSize — размер популяции, количество агентов в работе;
  • tau — параметр степенного распределения, определяющего вероятности выбора элементов;
  • greedyStart — доля агентов с инициализацией по жадному принципу;
  • eliteUpdate — доля агентов, участвующих в обновлении за итерацию.
Массив параметров "params" содержит настройки и копирует значения исходных параметров для интеграции с пользовательским интерфейсом или внешним управлением. Методы:
  • SetParams — извлекает текущие значения параметров из массива и применяет их;
  • Init — инициализация популяции с учетом диапазонов параметров и количества эпох;
  • Moving — выполнение одного шага алгоритма, обновление состояния популяции;
  • Revision — пересчет оценок или подготовка к следующему этапу. 
Внутренние структуры:
  • RankedComponent — структура для хранения ранжирования компонент агента по их качеству (Fitness);
  • RankedAgent — структура для хранения ранжирования самих агентов по их итоговому уровню пригодности.
Внутренние переменные и параметры:
  • compRanks и agentRanks — массивы структур для хранения отсортированных компонентов и агентов;
  • taugreedyStarteliteUpdate позволяют управлять поведением алгоритма.
Внутренние методы:
  • ApplyExtremalOptimization — основной механизм для выбора и обновления элементов на основе их рангов с учетом распределения степенного закона.
  • CalculateComponentFitness — вычисление фитнеса компонента агента.
  • SelectRankByPowerLaw — выбор ранга компонента или агента с использованием распределения со степенным законом, что позволяет сосредоточиться на худших элементах.

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

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

  //------------------------------------------------------------------
  ArrayResize (compRanks, coords);
  ArrayResize (agentRanks, popSize);

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

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

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

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

//————————————————————————————————————————————————————————————————————
//--- Основной цикл алгоритма
void C_AO_EO::Moving ()
{
  // Начальная инициализация популяции
  if (!revision)
  {
    int greedyCount = (int)(popSize * greedyStart);

    for (int i = 0; i < popSize; i++)
    {
      // Случайная инициализация для остальных
      for (int c = 0; c < coords; c++)
      {
        a [i].c [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]);
        a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
      }
    }

    revision = true;
    return;
  }

  // Применяем Extremal Optimization ---------------------------------
  ApplyExtremalOptimization ();
}
//————————————————————————————————————————————————————————————————————

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

Для выбранного агента, производится ранжирование его компонентов. Каждому компоненту присваивается значение пригодности "fitness", чтобы оценивать его вклад в общее качество агента. Компоненты сортируются от худших к лучшим на основе их "fitness". Используется степенное распределение вероятности для выбора одного из компонентов агента для модификации. Это делается для того, чтобы с большей вероятностью выбирать компоненты с худшим рангом.

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

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

    //————————————————————————————————————————————————————————————————————
    //--- Применение Extremal Optimization
    void C_AO_EO::ApplyExtremalOptimization ()
    {
      // Количество агентов для обновления на этой итерации
      int numUpdates = MathMax (1, (int)(popSize * eliteUpdate));
    
      // Обновляем выбранных агентов по принципу EO
      //for (int update = 0; update < numUpdates; update++)
      for (int update = 0; update < popSize; update++)
      {
        // Шаг 1: Выбираем агента для модификации
        // Используем ранжирование по общему fitness
        int targetAgent;
    
        // Ранжируем агентов по fitness (от худшего к лучшему для максимизации)
        for (int i = 0; i < popSize; i++)
        {
          agentRanks [i].idx = i;
          agentRanks [i].fitness = a [i].f;
        }
    
        // Сортировка (худшие в начале для максимизации)
        for (int i = 0; i < popSize - 1; i++)
        {
          for (int j = i + 1; j < popSize; j++)
          {
            if (agentRanks [i].fitness > agentRanks [j].fitness)
            {
              RankedAgent temp = agentRanks [i];
              agentRanks [i] = agentRanks [j];
              agentRanks [j] = temp;
            }
          }
        }
    
        // Выбираем агента согласно степенному распределению
        int rank    = SelectRankByPowerLaw (popSize);
        targetAgent = agentRanks [rank].idx;
    
        // Шаг 2: Ранжируем компоненты выбранного агента
        for (int c = 0; c < coords; c++)
        {
          compRanks [c].agentIdx     = targetAgent;
          compRanks [c].componentIdx = c;
          compRanks [c].fitness      = CalculateComponentFitness (targetAgent, c);
        }
    
        // Сортировка компонентов (худшие в начале)
        for (int i = 0; i < coords - 1; i++)
        {
          for (int j = i + 1; j < coords; j++)
          {
            if (compRanks [i].fitness > compRanks [j].fitness)
            {
              RankedComponent temp = compRanks [i];
              compRanks [i] = compRanks [j];
              compRanks [j] = temp;
            }
          }
        }
    
        // Шаг 3: Выбираем компонент для изменения согласно P(n) ∝ n^(-τ)
        int compRank = SelectRankByPowerLaw (coords);
        int compIdx  = compRanks [compRank].componentIdx;
    
        // Шаг 4: Заменяем выбранный компонент новым случайным значением
        // Это ключевой принцип EO - безусловная замена на случайное
        a [targetAgent].c [compIdx] = u.RNDfromCI (rangeMin [compIdx], rangeMax [compIdx]);
    
        // Проверка границ
        a [targetAgent].c [compIdx] = u.SeInDiSp (a [targetAgent].c [compIdx],
                                                  rangeMin [compIdx],
                                                  rangeMax [compIdx],
                                                  rangeStep [compIdx]);
      }
    }
    //————————————————————————————————————————————————————————————————————

    Метод "CalculateComponentFitness" вычисляет значение пригодности "fitness" для отдельного компонента агента. Эта функция играет важную роль в алгоритме Extremal Optimization (EO), оценивая вклад каждого компонента в общее качество решения. Алгоритм использует простую метрику, основанную на расчёте "fitness", как относительного вклада компонента в общее качество.

    Вначале "fitness" инициализируется нулём. Далее вычисляется диапазон значений компонента (range). Если этот диапазон больше нуля, рассчитывается нормализованное отклонение "deviation". Отклонение вычисляется как абсолютное значение разницы между значением компонента агента и базовым значением "cB", нормированное на диапазон значений компонента. Приспособленность рассчитывается путем вычитания "deviation" из 1.0. Это делает "fitness" обратно пропорциональным отклонению: чем меньше отклонение компонента от целевого значения, тем выше его "fitness".

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

    //————————————————————————————————————————————————————————————————————
    //--- Расчет fitness компонента
    double C_AO_EO::CalculateComponentFitness (int agentIdx, int componentIdx)
    {
      // Для общей задачи оптимизации используем простую метрику
      // λi = относительный вклад компонента в общее качество
    
      double fitness = 0.0;
    
      double range = rangeMax [componentIdx] - rangeMin [componentIdx];
      if (range > 0)
      {
        // Нормализованное отклонение
        double deviation = MathAbs (a [agentIdx].c [componentIdx] - cB [componentIdx]) / range;
        fitness = 1.0 - deviation; // Инвертируем, чтобы больше = лучше
      }
    
      return fitness;
    }
    //————————————————————————————————————————————————————————————————————

    Метод "SelectRankByPowerLaw" реализует выбор позиции (ранга) элемента из списка на основе степенного распределения вероятностей. Этот метод является ключевым для алгоритма EO, так как именно через него производится выбор агентов и компонентов для модификации. Метод принимает на вход "maxRank", который определяет максимальный ранг элемента.

    В основе метода лежит принцип обратного преобразования для получения случайного значения, следуя степенному закону P(n) ∝ n^(-τ), где: n  - ранг элемента; τ (tau) — параметр, определяющий форму степенного распределения.

    В зависимости от значения параметра "tau", реализуются два сценария:

    1. Общий случай (τ ≠ 1.0):

      • вычисляется нормализующий множитель "norm", который обеспечивает правильное масштабирование вероятностей;
      • генерируется случайное число "r" в диапазоне [0, 1) с помощью "u.RNDprobab ()";
      • используется формула обратного преобразования для вычисления ранга "rank" на основе "r", "norm" и "τ";
      • ранг ограничивается в пределах [0,  maxRank  - 1], чтобы избежать выхода за пределы массива.
    2. Специальный случай (τ = 1.0):

      • вычисляется нормализующий множитель "norm" для случая  τ = 1.0;
      • используется специальная формула для вычисления ранга "rank";
      • ранг ограничивается в пределах [0, maxRank  - 1].

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

    //————————————————————————————————————————————————————————————————————
    //--- Выбор ранга согласно степенному распределению
    int C_AO_EO::SelectRankByPowerLaw (int maxRank)
    {
      // P(n) ∝ n^(-τ), где n - ранг от 1 до maxRank
      // Используем метод обратного преобразования
    
      double r = u.RNDprobab ();
    
      if (tau != 1.0)
      {
        // Общий случай: обратное преобразование для P(n) ∝ n^(-τ)
        double norm = (1.0 - MathPow (maxRank + 1.0, 1.0 - tau)) / (1.0 - tau);
        double x = r * norm;
        int rank = (int)MathPow ((1.0 - tau) * x + 1.0, 1.0 / (1.0 - tau)) - 1;
    
        if (rank >= maxRank) rank = maxRank - 1;
        if (rank < 0) rank = 0;
    
        return rank;
      }
      else
      {
        // Специальный случай τ = 1: P(n) ∝ 1/n
        double norm = MathLog (maxRank + 1.0);
        int rank = (int)(MathExp (r * norm) - 1.0);
    
        if (rank >= maxRank) rank = maxRank - 1;
        if (rank < 0) rank = 0;
    
        return rank;
      }
    }
    //————————————————————————————————————————————————————————————————————

    Метод "Revision" предназначен для обновления информации о лучших найденных решениях в процессе работы алгоритма EO. Его задача —определить текущее наилучшее решение и обновить глобальные переменные, хранящие информацию о нём. Создаётся временный массив "aT" для хранения отсортированных агентов. Популяция агентов "a" сортируется, используя встроенную функцию "u.Sorting". Сортировка выполняется для максимизации, то есть лучшие агенты (те, у которых значение функции пригодности "f" больше) оказываются в начале массива "a" (является основной структурой данных), содержащей информацию об агентах, включая значения их компонент "c" и значение функции пригодности "f".

    После сортировки проверяется, является ли значение функции пригодности "f" самого лучшего агента (то есть  a [0]) больше, чем текущее лучшее значение "fB" (хранит значение функции пригодности лучшего решения, найденного на данный момент). Если текущий лучший агент лучше, чем глобальное лучшее решение, то значения компонент лучшего агента (a [0].c) копируются в массив "cB", который вероятно хранит компоненты лучшего решения, а "fB" обновляется до значения функции пригодности лучшего агента (a [0].f).

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

    //————————————————————————————————————————————————————————————————————
    //--- Обновление лучших решений
    void C_AO_EO::Revision ()
    {
      // Сортировка популяции для МАКСИМИЗАЦИИ
      static S_AO_Agent aT [];
      ArrayResize (aT, popSize);
    
      // Используем встроенную функцию сортировки
      u.Sorting (a, aT, popSize);
    
      // Обновление глобального лучшего решения
      if (a [0].f > fB)
      {
        ArrayCopy (cB, a [0].c, 0, 0, WHOLE_ARRAY);
        fB = a [0].f;
      }
    }
    //————————————————————————————————————————————————————————————————————


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

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

    EO|Extremal Optimization (Boettcher-Percus)|50.0|1.4|0.5|0.3|
    =============================
    5 Hilly's; Func runs: 10000; result: 0.5146369337195529
    25 Hilly's; Func runs: 10000; result: 0.29089804555433085
    500 Hilly's; Func runs: 10000; result: 0.25192095557138877
    =============================
    5 Forest's; Func runs: 10000; result: 0.367128650332966
    25 Forest's; Func runs: 10000; result: 0.19477408852361866
    500 Forest's; Func runs: 10000; result: 0.15367465708144543
    =============================
    5 Megacity's; Func runs: 10000; result: 0.2584615384615384
    25 Megacity's; Func runs: 10000; result: 0.12707692307692314
    500 Megacity's; Func runs: 10000; result: 0.09413846153846227
    =============================
    All score: 2.25271 (25.03%)

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

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

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

    //————————————————————————————————————————————————————————————————————
    class C_AO_EOm : public C_AO
    {
      public: //----------------------------------------------------------
      ~C_AO_EOm () { }
      C_AO_EOm ()
      {
        ao_name = "EOm";
        ao_desc = "Extremal Optimization M";
        ao_link = "https://www.mql5.com/ru/articles/18755";
    
        popSize        = 50;      // Размер популяции
        popRaising     = 3;       // Повышение самых худших
        mutationRate   = 0.1;     // Вероятность мутации
        powCh          = 2.0;     // Степень закона распределения отбора
        powMut         = 8.0;     // Степень закона распределения мутации
    
        ArrayResize (params, 5);
    
        params [0].name = "popSize";        params [0].val = popSize;
        params [1].name = "popRaising";     params [1].val = popRaising;
        params [2].name = "mutationRate";   params [2].val = mutationRate;
        params [3].name = "powCh";          params [3].val = powCh;
        params [4].name = "powMut";         params [4].val = powMut;
      }
    
      void SetParams ()
      {
        popSize        = (int)params [0].val;
        popRaising     = (int)params [1].val;
        mutationRate   = params      [2].val;
        powCh          = params      [3].val;
        powMut         = params      [4].val;
      }
    
      bool Init (const double &rangeMinP  [],
                 const double &rangeMaxP  [],
                 const double &rangeStepP [],
                 const int     epochsP = 0);
    
      void Moving   ();
      void Revision ();
    
      //------------------------------------------------------------------
      int    popRaising;       // Повышение самых худших
      double mutationRate;     // Вероятность мутации
      double powCh;            // Степень закона распределения отбора
      double powMut;           // Степень закона распределения мутации
    
      private: //---------------------------------------------------------
      int    currentEpoch;     // текущая эпоха
      int    totalEpochs;      // общее количество эпох
    
      void MutateComponent (int agentIdx, int componentIdx);
    };
    //————————————————————————————————————————————————————————————————————

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

    Внутри метода сначала вызывается функция "StandardInit", из базового класса "C_AO". Эта функция, выполняет общую инициализацию, выделение памяти или установку базовых параметров, связанных с диапазонами и шагами для параметров оптимизации.

    Если "StandardInit" завершается неудачно, то функция "Init" также завершает работу, возвращая "false". Если "StandardInit" прошла успешно, то метод инициализирует переменные "currentEpoch" (текущая эпоха) в "0" и "totalEpochs" (общее количество эпох) значением, полученным в параметре "epochsP".

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

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

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

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

    Применение экстремальной оптимизации (в последующих эпохах): создается временный массив "aT" для хранения новых (мутировавших) решений. Для каждого агента в популяции инициализируется структура "aT [i]". Для каждой координаты решения генерируется случайное число, возводится в степень "powCh". Это используется для отбора "родителя".

    Случайное число масштабируется для выбора индекса родителя в популяции "ind". Определяется тип мутации: если случайное число меньше "mutationRate" (мутация включается с вероятностью "mutationRate"), то применяется мутация по закону степенного распределения "PowerDistribution" к значению координаты родителя. В противном случае, происходит "направленное движение" к лучшему решению с добавлением случайного шума. Новое значение вычисляется как сумма значения координаты родителя, случайного числа, и разницы между "cB [c]"  (лучшая найденная координата) и координатой родителя. Применяется дискретизация "SeInDiSp". После мутации, все координаты из временного массива "aT" копируются в основной массив популяции "a".

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

    //————————————————————————————————————————————————————————————————————
    //--- Основной цикл алгоритма
    void C_AO_EOm::Moving ()
    {
      currentEpoch++;
    
      // Начальная инициализация популяции
      if (!revision)
      {
        for (int i = 0; i < popSize; i++)
        {
          for (int c = 0; c < coords; c++)
          {
            a [i].c [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]);
            a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
          }
        }
    
        revision = true;
        return;
      }
    
      //Apply Extremal Optimization---------------------------------------
      static S_AO_Agent aT []; ArrayResize (aT, popSize);
    
      for (int i = 0; i < popSize; i++)
      {
        aT [i].Init (coords);
    
        for (int c = 0; c < coords; c++)
        {
          double rnd = u.RNDprobab (); rnd = pow (rnd, powCh);
          int ind = (int)u.Scale (rnd, 0.0, 1.0, 0, popSize - 1);
    
          // Выбор типа мутации
          double mutType = u.RNDprobab ();
    
          if (mutType < mutationRate)
          {
            aT [i].c [c] = u.PowerDistribution (a [ind].c [c], rangeMin [c], rangeMax [c], powMut);
          }
          else
          {
            // Направленное движение к лучшему с шумом
            aT [i].c [c] = a [ind].c [c] + u.RNDprobab () * (cB [c] - a [ind].c [c]);
          }
    
          // Проверка границ
          aT [i].c [c] = u.SeInDiSp (aT [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
        }
      }
    
      for (int i = 0; i < popSize; i++) ArrayCopy (a [i].c, aT [i].c);
    }
    //————————————————————————————————————————————————————————————————————

    Этот метод отвечает за обновление информации о лучших решениях в процессе работы алгоритма. 

    Сортировка популяции: создается временный массив "aT" для хранения копии популяции. Популяция "a" сортируется по значению целевой функции. Для сортировки используется метод "u.Sorting". 

    Обновление глобального лучшего решения: проверяется, лучше ли значение целевой функции "f" первого элемента популяции (т.е. самого лучшего решения) текущего глобального лучшего значения "fB". Если текущее лучше, то "cB" копируются из координат первого элемента "a [0].c", а "fB" обновляется значением "f" лучшего решения. 

    Обновление худшего решения: "fW" обновляется значением функции последнего элемента популяции. 

    "Поднятие" (Raising) худших решений: в цикле, выполняющемся "popRaising" раз, происходит "поднятие" худших решений в популяции: для "popRaising" худших агентов (расположенных в конце отсортированного массива "a"), значение целевой функции "f" пересчитывается как случайное значение, расположенное между "fW" (худший текущий результат) и "fB" (лучший текущий результат). Это сделано для внесения разнообразия или смещения худших решений в лучшую сторону. 

    Пересортировка популяции: популяция "a" пересортировывается, используя метод "u.Sorting". Это необходимо, чтобы снова привести популяцию в порядок после внесения изменений в значения "f".

    //————————————————————————————————————————————————————————————————————
    //--- Обновление лучших решений
    void C_AO_EOm::Revision ()
    {
      // Сортировка популяции --------------------------------------------
      static S_AO_Agent aT []; ArrayResize (aT, popSize);
      u.Sorting (a, aT, popSize);
    
      // Обновление глобального лучшего решения
      if (a [0].f > fB)
      {
        ArrayCopy (cB, a [0].c, 0, 0, WHOLE_ARRAY);
        fB = a [0].f;
      }
    
      fW = a [popSize - 1].f;
    
      //------------------------------------------------------------------
      for (int i = 0; i < popRaising; i++)
      {
        a [popSize - 1 - i].f = u.RNDfromCI (fW, fB);
      }
    
      u.Sorting (a, aT, popSize);
    }
    //————————————————————————————————————————————————————————————————————

    Теперь алгоритм, в некотором смысле наследующий идеи EO, но измененный, использует степенное распределение для выбора доноров (не обязательно плохих).

    a [popSize - 1 - i].f = u.RNDfromCI (fW, fB);

    Мною добавлена особенность, не встречающаяся в описаниях EO, алгоритм фактически "воскрешает" худших агентов, присваивая им случайный fitness в диапазоне от самого худшего до самого лучшего по популяции. PowerDistribution для мутации — это интерпретация "мутации на основе степенного распределения". Направленное движение к лучшему (при mutType >= mutationRate ) — дополнение для ускорения сходимости.

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

    EOm|Extremal Optimization Mod|50.0|3.0|0.1|2.0|8.0|
    =============================
    5 Hilly's; Func runs: 10000; result: 0.7616651321732368
    25 Hilly's; Func runs: 10000; result: 0.7724295992586084
    500 Hilly's; Func runs: 10000; result: 0.3174703398632668
    =============================
    5 Forest's; Func runs: 10000; result: 0.9999936711143977
    25 Forest's; Func runs: 10000; result: 0.7675163269928252
    500 Forest's; Func runs: 10000; result: 0.23527203643380376
    =============================
    5 Megacity's; Func runs: 10000; result: 0.7476923076923077
    25 Megacity's; Func runs: 10000; result: 0.5396923076923076
    500 Megacity's; Func runs: 10000; result: 0.14249230769230903
    =============================
    All score: 5.28422 (58.71%)

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

    Hilly

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

    Forest

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

    Megacity

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

    После проведенного тестирования, алгоритм EOm в нашей рейтинговой таблице занимает 12 место.

    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
    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
    26BSAbird swarm algorithm0,893060,649000,262501,804550,924200,711210,249391,884790,693850,326150,100121,120124,80953,44
    27DEAdolphin_echolocation_algorithm0,759950,675720,341711,777380,895820,642230,239411,777460,615380,440310,151151,206844,76252,91
    28HSharmony search0,865090,687820,325271,878180,999990,680020,095901,775920,620000,422670,054581,097254,75152,79
    29SSGsaplings sowing and growing0,778390,649250,395431,823080,859730,624670,174291,658690,646670,441330,105981,193984,67651,95
    30BCOmbacterial chemotaxis optimization M0,759530,622680,314831,697040,893780,613390,225421,732590,653850,420920,144351,219124,64951,65
    31ABOafrican buffalo optimization0,833370,622470,299641,755480,921700,586180,197231,705110,610000,431540,132251,173784,63451,49
    32(PO)ES(PO) evolution strategies0,790250,626470,429351,846060,876160,609430,195911,681510,590000,379330,113221,082554,61051,22
    33FBAfractal-based Algorithm0,790000,651340,289651,730990,871580,568230,188771,628580,610770,460620,123981,195374,55550,61
    34TSmtabu search M0,877950,614310,291041,783300,928850,518440,190541,637830,610770,382150,121571,114494,53650,40
    35BSObrain storm optimization0,937360,576160,296881,810410,931310,558660,235371,725340,552310,290770,119140,962224,49849,98
    36WOAmwale optimization algorithm M0,845210,562980,262631,670810,931000,522780,163651,617430,663080,411380,113571,188034,47649,74
    37AEFAartificial electric field algorithm0,877000,617530,252351,746880,927290,726980,180641,834900,666150,116310,095080,877544,45949,55
    38AEOartificial ecosystem-based optimization algorithm0,913800,467130,264701,645630,902230,437050,214001,553270,661540,308000,285631,255174,45449,49
    39CAmcamel algorithm M0,786840,560420,351331,698590,827720,560410,243361,631490,648460,330920,134181,113564,44449,37
    40ACOmant colony optimization M0,881900,661270,303771,846930,858730,586800,150511,596040,596670,373330,024720,994724,43849,31
    41CMAEScovariance_matrix_adaptation_evolution_strategy0,762580,720890,000001,483470,820560,796160,000001,616720,758460,490770,000001,249234,34948,33
    42BFO-GAbacterial foraging optimization - ga0,891500,551110,315291,757900,969820,396120,063051,428990,726670,275000,035251,036924,22446,93
    43SOAsimple optimization algorithm0,915200,469760,270891,655850,896750,374010,169841,440600,695380,280310,108521,084224,18146,45
    44ABHAartificial bee hive algorithm0,841310,542270,263041,646630,878580,477790,171811,528180,509230,338770,103970,951974,12745,85
    45ACMOatmospheric cloud model optimization0,903210,485460,304031,692700,802680,378570,191781,373030,623080,244000,107950,975034,04144,90
    RWrandom walk0,487540,321590,257811,066940,375540,219440,158770,753750,279690,149170,098470,527342,34826,09


    Выводы

    Представленная компактная модифицированная реализация EOm демонстрирует интересный пример в области метаэвристической оптимизации: значительное отклонение от теоретических принципов может привести к улучшению практических результатов. Алгоритм, занявший 12-е место среди 45 популяционных методов, фактически представляет собой гибрид, сохранивший лишь ключевую идею степенного распределения из оригинального EO. 

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

    Успех модифицированной версии EO подтверждает важный принцип в разработке метаэвристик: эффективность алгоритма определяется не верностью исходной идее, а балансом между исследованием и эксплуатацией пространства поиска. Представленная реализация, несмотря на отход от классических принципов Extremal Optimization, демонстрирует высокую скорость сходимости, конкурентоспособные результаты, простоту реализации и настройки.

    Это делает её полезным инструментом в арсенале методов популяционной оптимизации, особенно для задач, где важен баланс между качеством решения и вычислительными затратами.

    tab

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

    tab

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

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

    Плюсы:

    1. Простая реализация
    2. Быстрый и производительный
    3. Хорошие результаты на дискретных задачах

    Минусы:

    1. Большой разброс результатов на функциях малой размерности
    2. Средние результаты на "гладких" задачах малой размерности

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


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

    #ИмяТипОписание
    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_EOm.mq5
    СкриптИспытательный стенд для EOm

    Прикрепленные файлы |
    EOm.ZIP (314.62 KB)
    Знакомство с кривыми рабочих характеристик приемника (ROC-кривыми) Знакомство с кривыми рабочих характеристик приемника (ROC-кривыми)
    ROC-кривые — графические представления, используемые для оценки эффективности классификаторов. Хотя графики ROC относительно просты, на практике при их использовании существуют распространенные заблуждения и подводные камни. Цель данной статьи — познакомить читателя с графиками ROC как инструментом для практикующих специалистов, стремящихся разобраться в оценке эффективности классификаторов.
    Нейросети в трейдинге: Сквозная многомерная модель прогнозирования временных рядов (GinAR) Нейросети в трейдинге: Сквозная многомерная модель прогнозирования временных рядов (GinAR)
    Предлагаем познакомиться с инновационным подходом к прогнозированию временных рядов с пропущенными данными на базе фреймворка GinAR. В статье показана реализация ключевых компонентов на OpenCL, что обеспечивает высокую производительность. В следующей публикации мы подробно рассмотрим интеграцию этих решений в MQL5. Это позволит понять, как применять метод на практике в трейдинге.
    Автоматизация торговых стратегий на MQL5 (Часть 6): Поиск ордер-блоков для торговли по концепции Smart Money Автоматизация торговых стратегий на MQL5 (Часть 6): Поиск ордер-блоков для торговли по концепции Smart Money
    В настоящей статье мы автоматизируем обнаружение ордер-блоков на MQL5, используя чистый анализ движения цены. Мы определяем ордер-блоки , реализуем их обнаружение и интегрируем автоматическое исполнение сделок. Наконец, для оценки эффективности стратегии, мы проведём её бэк-тестирование.
    Механизмы гейтинга в ансамблевом обучении Механизмы гейтинга в ансамблевом обучении
    В настоящей статье мы продолжаем наше исследование ансамблевых моделей, обсуждая концепцию ворот (gates), в частности, как они могут быть полезны при объединении выходных данных модели для повышения точности прогнозирования или обобщения модели.