Files
Population-optimization-alg…/MQL5/Include/Math/AOs/PopulationAO/AO_CMAES_CovarianceMatrixAdaptationEvolutionStrategy.mqh
2026-05-18 20:56:49 +04:00

714 lines
45 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//+——————————————————————————————————————————————————————————————————+
//| C_AO_CMAES |
//| Copyright 2007-2025, Andrey Dik |
//| https://www.mql5.com/ru/users/joo |
//———————————————————————————————————————————————————————————————————+
//Article: https://www.mql5.com/ru/articles/18227
#include "#C_AO.mqh"
//————————————————————————————————————————————————————————————————————
class C_AO_CMAES : public C_AO
{
public: //----------------------------------------------------------
~C_AO_CMAES () { }
C_AO_CMAES ()
{
ao_name = "CMAES";
ao_desc = "Covariance Matrix Adaptation Evolution Strategy";
ao_link = "https://www.mql5.com/ru/articles/18227";
// Параметры по умолчанию
popSize = 50; // Размер популяции по умолчанию (lambda)
mu = 25; // Количество родителей (половина популяции)
learningRateC1 = 0.01; // Скорость обучения для ранг-1 обновления
learningRateCMu = 0.8; // Скорость обучения для ранг-μ обновления
stepSizeDamping = 0.6; // Демпфирование для размера шага
// Создание и инициализация массива параметров
ArrayResize (params, 5);
params [0].name = "popSize"; params [0].val = popSize;
params [1].name = "mu"; params [1].val = mu;
params [2].name = "learningRateC1"; params [2].val = learningRateC1;
params [3].name = "learningRateCMu"; params [3].val = learningRateCMu;
params [4].name = "stepSizeDamping"; params [4].val = stepSizeDamping;
}
void SetParams ()
{
popSize = (int)params [0].val;
mu = (int)params [1].val;
learningRateC1 = params [2].val;
learningRateCMu = params [3].val;
stepSizeDamping = params [4].val;
}
bool Init (const double &rangeMinP [], // минимальные значения
const double &rangeMaxP [], // максимальные значения
const double &rangeStepP [], // размер шага
const int epochsP = 0);
void Moving ();
void Revision ();
//------------------------------------------------------------------
private: //---------------------------------------------------------
// Специфичные параметры CMA-ES
int mu; // Количество родителей (выбранных точек)
double sigma; // Размер шага
double learningRateC1; // Скорость обучения для ранг-1 обновления
double learningRateCMu; // Скорость обучения для ранг-μ обновления
double stepSizeDamping; // Фактор демпфирования для обновления размера шага
// Специфичные структуры данных CMA-ES
double weights []; // Веса рекомбинации
double covMatrix []; // Ковариационная матрица (хранится как одномерный массив)
double B []; // Собственные векторы C
double D []; // Собственные значения (квадратные корни) C
double pc []; // Путь эволюции для C
double ps []; // Сопряженный путь эволюции для sigma
double mu_eff; // Эффективная масса выбора дисперсии
int counteval; // Счетчик вычислений функции с последнего разложения
int eigeneval; // Счетчик генераций, когда выполнялось разложение
double chiN; // Ожидаемая норма N(0,I)
int eigenInterval; // Интервал для разложения на собственные значения
// Переменные для оптимизации производительности
double cs; // Скорость обучения для пути sigma
double cc; // Скорость обучения для пути ранг-1
double damps; // Демпфирование для sigma
double hsig_threshold; // Порог для функции Хевисайда
// Вспомогательные массивы
double y_vec []; // Вектор мутации
double arindex []; // Массив индексов для сортировки
double arfitness []; // Массив фитнеса для сортировки
double temp_vec []; // Временный вектор для матричных операций
double invsqrtC_times_yw []; // Временное хранилище для C^(-1/2) * y_w
// Переменные кэширования для Бокса-Мюллера
double cached_normal;
bool has_cached;
// Вспомогательные методы
void InitDistribution ();
void UpdateDistribution ();
void ComputeEigendecomposition ();
double GetChiN ();
void SortPopulation ();
void ComputeWeights ();
void UpdateMean ();
bool CheckPositiveDefinite ();
void EnforcePositiveDefinite ();
};
//————————————————————————————————————————————————————————————————————
//————————————————————————————————————————————————————————————————————
bool C_AO_CMAES::Init (const double &rangeMinP [], // минимальные значения
const double &rangeMaxP [], // максимальные значения
const double &rangeStepP [], // размер шага
const int epochsP = 0) // количество эпох
{
if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false;
//------------------------------------------------------------------
// Инициализация кэширования Бокса-Мюллера
has_cached = false;
// Инициализация специфичных переменных CMA-ES
sigma = 0.3; // Начальный размер шага (30% от диапазона поиска)
// Вычисление эффективной массы выбора дисперсии
ComputeWeights ();
// Ожидаемая норма N(0,I)
chiN = GetChiN ();
// Вычисление и сохранение параметров стратегии
cs = (mu_eff + 2.0) / (coords + mu_eff + 5.0);
cc = (4.0 + mu_eff / coords) / (coords + 4.0 + 2.0 * mu_eff / coords);
damps = 1.0 + 2.0 * MathMax (0.0, MathSqrt ((mu_eff - 1.0) / (coords + 1.0)) - 1.0) + cs;
hsig_threshold = 1.4 + 2.0 / (coords + 1.0);
// Установка интервала разложения на собственные значения - настройка для производительности
eigenInterval = (int)(coords / (10.0 * MathSqrt (learningRateC1 + learningRateCMu)));
eigenInterval = MathMax (1, eigenInterval);
// Выделение массивов - выделяем только один раз
ArrayResize (covMatrix, coords * coords);
ArrayResize (B, coords * coords);
ArrayResize (D, coords);
ArrayResize (pc, coords);
ArrayResize (ps, coords);
ArrayResize (y_vec, coords);
ArrayResize (arindex, popSize);
ArrayResize (arfitness, popSize);
ArrayResize (temp_vec, coords);
ArrayResize (invsqrtC_times_yw, coords);
// Инициализация путей эволюции нулями
ArrayInitialize (pc, 0);
ArrayInitialize (ps, 0);
// Быстрая инициализация единичной ковариационной матрицы и разложения
ArrayInitialize (covMatrix, 0.0);
ArrayInitialize (B, 0.0);
for (int i = 0; i < coords; i++)
{
covMatrix [i * coords + i] = 1.0;
B [i * coords + i] = 1.0;
D [i] = 1.0;
}
// Инициализация начального распределения
InitDistribution ();
// Сброс счетчиков вычислений
counteval = 0;
eigeneval = 0;
// Принудительный пересчет фитнеса
revision = true;
return true;
}
//————————————————————————————————————————————————————————————————————
//————————————————————————————————————————————————————————————————————
void C_AO_CMAES::Moving ()
{
// Генерация lambda новых потомков
for (int k = 0; k < popSize; k++)
{
// Применение преобразования y = B*D*z
for (int i = 0; i < coords; i++)
{
y_vec [i] = 0.0;
for (int j = 0; j < coords; j++)
{
y_vec [i] += B [i * coords + j] * D [j] * u.PowerDistribution (0.0, -8, 8, 20);
}
// Генерация потомка: x_k = m + σ * y
a [k].c [i] = cB [i] + sigma * y_vec [i];
a [k].c [i] = u.SeInDiSp (a [k].c [i], rangeMin [i], rangeMax [i], rangeStep [i]);
}
}
// Отметка для пересчета фитнеса
revision = true;
}
//————————————————————————————————————————————————————————————————————
//————————————————————————————————————————————————————————————————————
void C_AO_CMAES::Revision ()
{
if (!revision) return;
revision = false;
// Сортировка популяции по фитнесу
SortPopulation ();
// Обновление параметров распределения на основе выбранных особей
UpdateDistribution ();
// Обновление счетчика вычислений
counteval++;
}
//————————————————————————————————————————————————————————————————————
//+------------------------------------------------------------------+
//| Инициализация распределения поиска |
//+------------------------------------------------------------------+
void C_AO_CMAES::InitDistribution ()
{
// Установка начального среднего в центр пространства поиска
for (int i = 0; i < coords; i++)
{
cB [i] = u.RNDfromCI (rangeMin [i], rangeMax [i]);
}
for (int k = 0; k < popSize; k++)
{
for (int i = 0; i < coords; i++)
{
// Генерация равномерно распределенной точки
a [k].c [i] = u.RNDfromCI (rangeMin [i], rangeMax [i]);
a [k].c [i] = u.SeInDiSp (a [k].c [i], rangeMin [i], rangeMax [i], rangeStep [i]);
}
}
}
//————————————————————————————————————————————————————————————————————
//+------------------------------------------------------------------+
//| Вычисление ожидаемой нормы N(0,I) |
//+------------------------------------------------------------------+
double C_AO_CMAES::GetChiN ()
{
double n = (double)coords;
return MathSqrt (n) * (1.0 - 1.0 / (4.0 * n) + 1.0 / (21.0 * n * n));
}
//————————————————————————————————————————————————————————————————————
//+------------------------------------------------------------------+
//| Сортировка популяции по фитнесу |
//+------------------------------------------------------------------+
void C_AO_CMAES::SortPopulation ()
{
// Копирование значений фитнеса и индексов
for (int i = 0; i < popSize; i++)
{
arindex [i] = i;
arfitness [i] = a [i].f;
}
for (int i = 1; i < popSize; i++)
{
double tempFitness = arfitness [i];
double tempIndex = arindex [i];
int j = i - 1;
// Сортировка по убыванию (для максимизации)
while (j >= 0 && arfitness [j] < tempFitness)
{
arfitness [j + 1] = arfitness [j];
arindex [j + 1] = arindex [j];
j--;
}
arfitness [j + 1] = tempFitness;
arindex [j + 1] = tempIndex;
}
// Обновление лучшего решения при необходимости
if (arfitness [0] > fB)
{
fB = arfitness [0];
int bestIdx = (int)arindex [0];
ArrayCopy (cB, a [bestIdx].c, 0, 0, coords);
}
}
//————————————————————————————————————————————————————————————————————
//+------------------------------------------------------------------+
//| Обновление среднего с использованием взвешенной рекомбинации |
//+------------------------------------------------------------------+
void C_AO_CMAES::UpdateMean ()
{
// Взвешенная рекомбинация: m^(g+1) = Σ w_i * x_{i:λ}^(g+1)
for (int j = 0; j < coords; j++)
{
double meanSum = 0.0;
for (int i = 0; i < mu; i++)
{
int idx = (int)arindex [i];
meanSum += weights [i] * a [idx].c [j];
}
cB [j] = meanSum;
}
}
//————————————————————————————————————————————————————————————————————
//+------------------------------------------------------------------+
//| Вычисление весов взвешенной рекомбинации |
//+------------------------------------------------------------------+
void C_AO_CMAES::ComputeWeights ()
{
// Выделение массива весов
ArrayResize (weights, mu);
// Предварительное вычисление log(mu + 0.5)
double log_mu_plus_half = MathLog (mu + 0.5);
// Вычисление положительных весов
double sum = 0.0;
for (int i = 0; i < mu; i++)
{
weights [i] = log_mu_plus_half - MathLog (i + 1);
sum += weights [i];
}
// Нормализация весов
double sum_weights = 0.0;
double sum_squares = 0.0;
for (int i = 0; i < mu; i++)
{
weights [i] /= sum;
sum_weights += weights [i];
sum_squares += weights [i] * weights [i];
}
// Вычисление эффективной массы выбора дисперсии
mu_eff = sum_weights * sum_weights / sum_squares;
}
//————————————————————————————————————————————————————————————————————
//+------------------------------------------------------------------+
//| Обновление параметров распределения |
//+------------------------------------------------------------------+
void C_AO_CMAES::UpdateDistribution ()
{
// Проверка необходимости разложения на собственные значения
if (counteval - eigeneval > eigenInterval)
{
ComputeEigendecomposition ();
eigeneval = counteval;
}
// Сохранение старого среднего
double oldMean [];
ArrayResize (oldMean, coords);
ArrayCopy (oldMean, cB, 0, 0, coords);
// Обновление среднего
UpdateMean ();
// Вычисление смещения среднего
double y_w [];
ArrayResize (y_w, coords);
for (int j = 0; j < coords; j++)
{
y_w [j] = (cB [j] - oldMean [j]) / sigma;
}
// Вычисление C^(-1/2) * y_w
// Шаг 1: B^T * y_w
ArrayInitialize (temp_vec, 0.0);
for (int i = 0; i < coords; i++)
{
for (int j = 0; j < coords; j++)
{
temp_vec [i] += B [j * coords + i] * y_w [j];
}
}
// Шаг 2: D^(-1) * (B^T * y_w)
for (int i = 0; i < coords; i++)
{
temp_vec [i] /= D [i];
}
// Шаг 3: B * D^(-1) * B^T * y_w
ArrayInitialize (invsqrtC_times_yw, 0.0);
for (int i = 0; i < coords; i++)
{
for (int j = 0; j < coords; j++)
{
invsqrtC_times_yw [i] += B [i * coords + j] * temp_vec [j];
}
}
// Обновление пути эволюции для sigma
double norm_ps_squared = 0.0;
for (int i = 0; i < coords; i++)
{
ps [i] = (1.0 - cs) * ps [i] + MathSqrt (cs * (2.0 - cs) * mu_eff) * invsqrtC_times_yw [i];
norm_ps_squared += ps [i] * ps [i];
}
// Функция Хевисайда
double norm_ps = MathSqrt (norm_ps_squared);
double expected_length = MathSqrt (1.0 - MathPow (1.0 - cs, 2.0 * (counteval + 1))) * chiN;
bool hsig = norm_ps / expected_length < hsig_threshold;
// Обновление пути эволюции для C
double delta_hsig = hsig ? 1.0 : 0.0;
for (int i = 0; i < coords; i++)
{
pc [i] = (1.0 - cc) * pc [i] + delta_hsig * MathSqrt (cc * (2.0 - cc) * mu_eff) * y_w [i];
}
// Подготовка ранг-1 обновления
double c1a [];
ArrayResize (c1a, coords * coords);
for (int i = 0; i < coords; i++)
{
for (int j = 0; j <= i; j++)
{
c1a [i * coords + j] = c1a [j * coords + i] = pc [i] * pc [j];
}
}
// Подготовка ранг-μ обновления
double cmu [];
ArrayResize (cmu, coords * coords);
ArrayInitialize (cmu, 0.0);
for (int k = 0; k < mu; k++)
{
int idx = (int)arindex [k];
// Вычисление y_i = (x_i - m_old) / sigma
for (int i = 0; i < coords; i++)
{
temp_vec [i] = (a [idx].c [i] - oldMean [i]) / sigma;
}
// Добавление взвешенного внешнего произведения
for (int i = 0; i < coords; i++)
{
for (int j = 0; j <= i; j++)
{
double update = weights [k] * temp_vec [i] * temp_vec [j];
cmu [i * coords + j] += update;
if (i != j) cmu [j * coords + i] += update;
}
}
}
// Обновление ковариационной матрицы C
double c1 = learningRateC1;
double cmu_rate = learningRateCMu;
// Корректировка c1 если hsig false (застой прогресса)
if (!hsig)
{
c1 *= (1.0 - (1.0 - delta_hsig) * cc * (2.0 - cc));
}
double one_minus_c1_cmu = 1.0 - c1 - cmu_rate;
// Обновление C с ранг-1 и ранг-μ обновлениями
for (int i = 0; i < coords; i++)
{
for (int j = 0; j <= i; j++)
{
covMatrix [i * coords + j] = one_minus_c1_cmu * covMatrix [i * coords + j] +
c1 * c1a [i * coords + j] +
cmu_rate * cmu [i * coords + j];
// Сохранение симметрии
if (i != j)
{
covMatrix [j * coords + i] = covMatrix [i * coords + j];
}
}
}
// Обеспечение положительной определенности
if (counteval % (10 * eigenInterval) == 0)
{
EnforcePositiveDefinite ();
}
// Обновление размера шага sigma
double exponent = (cs / damps) * (norm_ps / chiN - 1.0);
sigma *= MathExp (exponent);
// Ограничение sigma для численной стабильности
double min_sigma = 1e-16;
double max_eigenvalue = D [0]; // D отсортирован по убыванию
double max_sigma = 1e4 * MathMax (1.0, MathSqrt (max_eigenvalue));
if (sigma < min_sigma) sigma = min_sigma;
else
if (sigma > max_sigma) sigma = max_sigma;
}
//————————————————————————————————————————————————————————————————————
//+------------------------------------------------------------------+
//| Вычисление разложения на собственные значения методом Якоби |
//+------------------------------------------------------------------+
void C_AO_CMAES::ComputeEigendecomposition ()
{
// Создание копии ковариационной матрицы для разложения
double C_copy [];
ArrayResize (C_copy, coords * coords);
ArrayCopy (C_copy, covMatrix);
// Инициализация B как единичной матрицы
for (int i = 0; i < coords; i++)
{
for (int j = 0; j < coords; j++)
{
B [i * coords + j] = (i == j) ? 1.0 : 0.0;
}
}
// Улучшенное разложение Якоби на собственные значения
int max_iterations = 10; //50 * coords;
double tolerance = 0.01; //1e-14 * coords * coords;
for (int iter = 0; iter < max_iterations; iter++)
{
// Поиск наибольшего внедиагонального элемента
double max_val = 0.0;
int p = 0, q = 1;
for (int i = 0; i < coords - 1; i++)
{
for (int j = i + 1; j < coords; j++)
{
double val = MathAbs (C_copy [i * coords + j]);
if (val > max_val)
{
max_val = val;
p = i;
q = j;
}
}
}
// Проверка сходимости
if (max_val < tolerance) break;
// Вычисление угла поворота
double app = C_copy [p * coords + p];
double aqq = C_copy [q * coords + q];
double apq = C_copy [p * coords + q];
double phi = 0.5 * MathArctan (2.0 * apq / (aqq - app + 1e-14));
double c = MathCos (phi);
double s = MathSin (phi);
// Обновление элементов матрицы
double app_new = c * c * app - 2 * c * s * apq + s * s * aqq;
double aqq_new = s * s * app + 2 * c * s * apq + c * c * aqq;
C_copy [p * coords + p] = app_new;
C_copy [q * coords + q] = aqq_new;
C_copy [p * coords + q] = C_copy [q * coords + p] = 0.0;
// Обновление других элементов в строках/столбцах p и q
for (int i = 0; i < coords; i++)
{
if (i != p && i != q)
{
double aip = C_copy [i * coords + p];
double aiq = C_copy [i * coords + q];
C_copy [i * coords + p] = C_copy [p * coords + i] = c * aip - s * aiq;
C_copy [i * coords + q] = C_copy [q * coords + i] = s * aip + c * aiq;
}
}
// Обновление собственных векторов
for (int i = 0; i < coords; i++)
{
double bip = B [i * coords + p];
double biq = B [i * coords + q];
B [i * coords + p] = c * bip - s * biq;
B [i * coords + q] = s * bip + c * biq;
}
}
// Извлечение собственных значений и обеспечение положительности
double min_eigenvalue = 1e-14;
for (int i = 0; i < coords; i++)
{
D [i] = MathSqrt (MathMax (min_eigenvalue, C_copy [i * coords + i]));
}
// Сортировка собственных значений и векторов по убыванию
for (int i = 0; i < coords - 1; i++)
{
int max_idx = i;
for (int j = i + 1; j < coords; j++)
{
if (D [j] > D [max_idx]) max_idx = j;
}
if (max_idx != i)
{
// Обмен собственных значений
double temp = D [i];
D [i] = D [max_idx];
D [max_idx] = temp;
// Обмен собственных векторов
for (int k = 0; k < coords; k++)
{
temp = B [k * coords + i];
B [k * coords + i] = B [k * coords + max_idx];
B [k * coords + max_idx] = temp;
}
}
}
}
//————————————————————————————————————————————————————————————————————
//+------------------------------------------------------------------+
//| Проверка положительной определенности ковариационной матрицы |
//+------------------------------------------------------------------+
bool C_AO_CMAES::CheckPositiveDefinite ()
{
// Быстрая проверка: все диагональные элементы должны быть положительными
for (int i = 0; i < coords; i++)
{
if (covMatrix [i * coords + i] <= 0) return false;
}
// Проверка минимального собственного значения на положительность
double min_eigenvalue = D [coords - 1]; // D отсортирован по убыванию
return min_eigenvalue > 1e-14;
}
//————————————————————————————————————————————————————————————————————
//+------------------------------------------------------------------+
//| Обеспечение положительной определенности ковариационной матрицы |
//+------------------------------------------------------------------+
void C_AO_CMAES::EnforcePositiveDefinite ()
{
// Метод 1: Добавление малого значения к диагонали
double min_diag = 1e308; // Очень большое число
for (int i = 0; i < coords; i++)
{
if (covMatrix [i * coords + i] < min_diag)
{
min_diag = covMatrix [i * coords + i];
}
}
if (min_diag < 1e-10)
{
double correction = 1e-10 - min_diag;
for (int i = 0; i < coords; i++)
{
covMatrix [i * coords + i] += correction;
}
}
// Метод 2: Обеспечение симметрии
for (int i = 0; i < coords; i++)
{
for (int j = i + 1; j < coords; j++)
{
double avg = (covMatrix [i * coords + j] + covMatrix [j * coords + i]) * 0.5;
covMatrix [i * coords + j] = covMatrix [j * coords + i] = avg;
}
}
// Если все еще не положительно определена, выполнить разложение и исправить
if (!CheckPositiveDefinite ())
{
ComputeEigendecomposition ();
double min_eigenvalue = 1e-10;
for (int i = 0; i < coords; i++)
{
if (D [i] < MathSqrt (min_eigenvalue))
{
D [i] = MathSqrt (min_eigenvalue);
}
}
// Восстановление C = B * D^2 * B^T
ArrayInitialize (covMatrix, 0.0);
for (int i = 0; i < coords; i++)
{
for (int j = 0; j <= i; j++)
{
double sum = 0.0;
for (int k = 0; k < coords; k++)
{
sum += B [i * coords + k] * D [k] * D [k] * B [j * coords + k];
}
covMatrix [i * coords + j] = covMatrix [j * coords + i] = sum;
}
}
}
}
//————————————————————————————————————————————————————————————————————