![Clase básica de algoritmos de población como base para una optimización eficaz](https://c.mql5.com/2/71/The_basic_class_of_population_algorithms__600x314.jpg)
Clase básica de algoritmos de población como base para una optimización eficaz
Contenido:
1. Introducción. Perspectivas y oportunidades al heredar de la clase básica de algoritmos de población
2. Escribimos una clase básica de algoritmos de población
3. Código de algoritmos herederos
4. Código de un banco de pruebas único para todos los algoritmos
5. Añadimos funciones de prueba muy conocidas
6. Creamos funciones de prueba en 3D
7. Conclusiones
1. Introducción. Perspectivas y oportunidades al heredar de la clase básica de algoritmos de población
En el mundo de la informática moderna y la inteligencia artificial, una clase básica diseñada para integrar algoritmos de optimización en soluciones de software finitas supone un elemento clave que abre infinitos horizontes de posibilidades técnicas a los desarrolladores. La herencia de esta clase básica en el contexto de los algoritmos poblacionales no solo ofrece comodidad y eficacia en el desarrollo de nuevos métodos de optimización, sino que también amplía las perspectivas de creación de algoritmos híbridos capaces de adaptarse a una gran variedad de problemas.
La combinación de algoritmos de optimización dentro de una clase básica abre la puerta a la creación de soluciones innovadoras que combinen las mejores características de distintos métodos. Los algoritmos híbridos surgidos de este enfoque son capaces de superar eficazmente las limitaciones de los métodos individuales y alcanzar nuevas cotas en la resolución de problemas de optimización complejos.
Además, la clase básica para algoritmos de población garantiza que los algoritmos desarrollados puedan usarse y probarse fácilmente en conjuntos estándar de funciones de prueba. Esto permite tanto a investigadores como desarrolladores evaluar rápidamente la eficacia de nuevos métodos de optimización comparando su rendimiento con las soluciones existentes.
Imaginemos que el mundo de la optimización y la búsqueda de soluciones es como un asombroso mundo culinario en el que cada método de optimización supone un ingrediente único que aporta su propio sabor al plato. En este contexto, la hibridación es como combinar ingeniosamente una variedad de ingredientes para crear platos nuevos, más sabrosos e interesantes.
Así, dispondremos de una amplia gama de métodos de optimización: algoritmos genéticos, estrategias evolutivas, algoritmos de hormigas, optimización por enjambre de partículas y muchos otros. Cada uno tiene sus puntos fuertes y sus propias capacidades, pero también sus limitaciones.
Ahí es donde la hibridación viene al rescate. Podemos tomar lo mejor de cada método, combinándolos en combinaciones únicas como un chef experimentado. De esta forma, los métodos de optimización híbridos pueden combinar los puntos fuertes de distintos enfoques, compensando sus puntos débiles y creando herramientas más eficaces y potentes para encontrar soluciones óptimas.
Imagine combinar un algoritmo genético con la búsqueda local: sería como la combinación perfecta de pimienta picante y miel dulce en un plato, que le confiere un sabor profundo y rico. Del mismo modo, la hibridación de algoritmos basados en poblaciones permite crear métodos innovadores capaces de hallar soluciones óptimas con mayor rapidez y precisión en diversos ámbitos, ya sean problemas de ingeniería, análisis financieros o inteligencia artificial.
Por tanto, la hibridación en la optimización no consiste solo en mezclar métodos, sino también en dominar el arte de crear nuevos enfoques que permitan aprovechar al máximo el potencial de cada método y lograr resultados extraordinarios. Gracias a la hibridación, podremos crear métodos de optimización más eficaces, innovadores y potentes, capaces de resolver los problemas más complejos y propiciar nuevos descubrimientos y avances en diversos campos.
Además, la clase básica unificada permitirá integrar elementos individuales de cada uno de los algoritmos en soluciones personalizadas para utilizar estas en el diseño de métodos de optimización nuevos, únicos y potentes.
2. Escribimos una clase básica de algoritmos de población
En el contexto de un artículo sobre la herencia de una clase básica para algoritmos de población y la creación de métodos de optimización híbridos, podemos considerar algunas combinaciones interesantes en calidad de ejemplos:
- Algoritmo genético con búsqueda local mejorada. En esta combinación, el algoritmo genético se usará para buscar la solución óptima de forma global, mientras que la búsqueda local se utilizará para precisar la solución encontrada en las proximidades. Esto permitirá combinar las ventajas de la búsqueda global y local, mejorando la precisión y la velocidad de convergencia del algoritmo.
- Estrategia evolutiva con el algoritmo de hormigas. En este caso, podremos utilizar la estrategia evolutiva para cambiar los parámetros del modelo y el algoritmo de hormigas para encontrar el camino óptimo en el espacio de parámetros. Esta combinación podría ser eficaz para optimizar problemas complejos en los que sea necesario encontrar la combinación óptima de parámetros.
- Enjambre de partículas con programación genética. En esta combinación, pueden usarse partículas de enjambre para explorar el espacio de soluciones y programación genética para evolucionar estructuras de software que resuelvan el problema de optimización. Esto permitirá una exploración eficaz tanto del espacio de parámetros como de las estructuras de solución.
- Búsqueda por recocido simulado con algoritmo genético. Aquí, el recocido simulado puede usarse para explorar el espacio de soluciones considerando el régimen de temperatura, mientras que el algoritmo genético podrá utilizarse para encontrar soluciones óptimas en el espacio dado. Esta combinación puede ofrecer una exploración más profunda del espacio de soluciones y mejorar la convergencia del algoritmo.
A modo de ejemplo, también podremos considerar las siguientes direcciones en la utilización de las capacidades de los algoritmos de población combinados en una única clase básica:
- Método de enfoque metaheurístico combinado. En este método podremos combinar varios algoritmos metaheurísticos diferentes, como algoritmos genéticos, algoritmos de hormigas, algoritmos de optimización de enjambre de partículas, recocido simulado, etc. Estos algoritmos podrán funcionar en paralelo o secuencialmente, intercambiando información y combinando sus fuerzas para encontrar más eficazmente la solución óptima.
- Método híbrido con control adaptativo de las estrategias de búsqueda. En este enfoque, podremos usar mecanismos de control adaptativo para combinar dinámicamente distintas estrategias de optimización en función de las características del problema. Por ejemplo, tendremos la posibilidad de cambiar las ponderaciones o los parámetros de cada método en función de su rendimiento en la fase actual de optimización.
- Método híbrido con redes neuronales artificiales. En este enfoque, las redes neuronales artificiales podrán utilizarse para controlar de forma adaptativa los parámetros y las estrategias de optimización de los algoritmos poblacionales. Las redes neuronales podrán aprender sobre la marcha, adaptándose a los cambios en el espacio de búsqueda y sugiriendo parámetros óptimos para cada método de optimización.
- Método de optimización conjunta y aprendizaje por refuerzo. En este enfoque, los algoritmos poblacionales podrán combinarse con técnicas de aprendizaje por refuerzo para crear un sistema híbrido capaz de explorar y optimizar eficazmente espacios de decisión complejos. Los agentes de aprendizaje por refuerzo podrán aprender de los resultados de los algoritmos poblacionales y viceversa, creando interacciones entre distintos métodos de optimización.
Un número distinto de parámetros externos en cada algoritmo de optimización podría crear problemas en la herencia y la aplicación unificada. Para resolver este problema, hemos decidido prescribir los parámetros externos por defecto para los algoritmos en el constructor basándonos en los resultados de pruebas exhaustivas. De todas formas, podrá cambiar estos parámetros antes de inicializar los algoritmos. Así, el objeto de cada algoritmo representará una solución final lista para su uso. Antes, los algoritmos y sus parámetros eran entidades separadas.
Empezaremos por los parámetros de los algoritmos. Cada parámetro se describe cómodamente con la ayuda de la estructura "S_AlgoParam", que contiene el nombre y el valor del parámetro. En consecuencia, el array de objetos de esta estructura representará el conjunto de parámetros externos de los algoritmos.
struct S_AlgoParam { double val; string name; };
Cada algoritmo de optimización tendrá un agente de búsqueda, que será una unidad elemental y un participante indispensable de la estrategia de búsqueda, ya sea una criatura frágil, digamos una luciérnaga en el algoritmo de la luciérnaga, o una abeja diligente en el algoritmo de la abeja, una hormiga trabajadora en el algoritmo de la hormiga, etc. Son artistas inimitables en el laberinto de la optimización, que revelan la excelencia del arte de buscar y descubrir caminos óptimos hacia el éxito. Sus esfuerzos y aspiraciones, como por arte de magia, transforman el caos de los datos en una armonía de soluciones, iluminando el camino hacia nuevos horizontes de optimización perfecta.
Mmm, de qué estábamos hablando... ah sí, de los agentes de optimización. :)
Así, cada agente representa una solución particular al problema de optimización y posee dos propiedades mínimas obligatorias: las coordenadas en el espacio de búsqueda (parámetros optimizados) y la calidad de la solución (adaptabilidad o función de aptitud). Para tener la posibilidad de ampliar la funcionalidad y las capacidades e implementar propiedades específicas de los algoritmos, formalizaremos el agente como una clase "C_AO_Agent" que podemos heredar más tarde.
class C_AO_Agent { public: ~C_AO_Agent () { } double c []; //coordinates double f; //fitness };
La mayoría de las operaciones lógicas de los algoritmos de optimización se repiten y pueden formalizarse por separado como un conjunto de funciones de la clase "C_AO_Utilities", cuyo objeto a su vez puede usarse tanto en clases de algoritmos como en agentes.
La clase "C_AO_Utilities" contiene los siguientes métodos:
- Scale: Método sobrecargado que escala el valor de entrada "In" del rango [InMIN, InMAX] al rango [OutMIN, OutMAX]. También podremos realizar un escalado inverso si "reverse" se ha establecido en "true".
- RNDfromCI: Genera un número real aleatorio dentro del rango indicado [min, max].
- RNDintInRange: Genera un número entero aleatorio dentro del rango indicado [min, max].
- RNDbool: Genera un valor lógico aleatorio (true/false).
- RNDprobab: Genera una probabilidad aleatoria (un número real entre 0 y 1).
- SeInDiSp: Calcula el valor considerando el paso "Step" especificado dentro del rango [InMin, InMax].
- Métodos "DecimalToGray", "IntegerToBinary", "GrayToDecimal", "BinaryToInteger", "GetMaxDecimalFromGray": Realizar conversiones entre números decimales, números binarios y códigos de Gray.
- Métodos GaussDistribution y PowerDistribution: Realizan los cálculos para la distribución normal y la distribución de potencia, respectivamente.
- Método "Sorting" (método de plantilla): Clasifica el array "p" de tipo "T" en orden descendente.
- Estructura "S_Roulette": Contiene los campos "start" y "end" para representar el rango.
- Métodos "PreCalcRoulette" y "SpinRoulette": "PreCalcRoulette" calcula los rangos para los objetos de tipo "T" y los almacena en un array "roulette". "SpinRoulette" realiza una operación de "giro" de ruleta según el tamaño de la población "aPopSize".
//—————————————————————————————————————————————————————————————————————————————— class C_AO_Utilities { public: //-------------------------------------------------------------------- double Scale (double In, double InMIN, double InMAX, double OutMIN, double OutMAX); double Scale (double In, double InMIN, double InMAX, double OutMIN, double OutMAX, bool revers); double RNDfromCI (double min, double max); int RNDintInRange (int min, int max); bool RNDbool (); double RNDprobab (); double SeInDiSp (double In, double InMin, double InMax, double Step); void DecimalToGray (ulong decimalNumber, char &array []); void IntegerToBinary (ulong number, char &array []); ulong GrayToDecimal (const char &grayCode [], int startInd, int endInd); ulong BinaryToInteger (const char &binaryStr [], const int startInd, const int endInd); ulong GetMaxDecimalFromGray (int digitsInGrayCode); double GaussDistribution (const double In, const double outMin, const double outMax, const double sigma); double PowerDistribution (const double In, const double outMin, const double outMax, const double p); //---------------------------------------------------------------------------- template<typename T> void Sorting (T &p [], T &pTemp [], int size) { int cnt = 1; int t0 = 0; double t1 = 0.0; int ind []; double val []; ArrayResize (ind, size); ArrayResize (val, size); for (int i = 0; i < size; i++) { ind [i] = i; val [i] = p [i].f; } while (cnt > 0) { cnt = 0; for (int i = 0; i < size - 1; i++) { if (val [i] < val [i + 1]) { t0 = ind [i + 1]; t1 = val [i + 1]; ind [i + 1] = ind [i]; val [i + 1] = val [i]; ind [i] = t0; val [i] = t1; cnt++; } } } for (int u = 0; u < size; u++) pTemp [u] = p [ind [u]]; for (int u = 0; u < size; u++) p [u] = pTemp [u]; } //---------------------------------------------------------------------------- struct S_Roulette { double start; double end; }; S_Roulette roulette []; template<typename T> void PreCalcRoulette (T &agents []) { int aPopSize = ArraySize (agents); roulette [0].start = agents [0].f; roulette [0].end = roulette [0].start + (agents [0].f - agents [aPopSize - 1].f); for (int s = 1; s < aPopSize; s++) { if (s != aPopSize - 1) { roulette [s].start = roulette [s - 1].end; roulette [s].end = roulette [s].start + (agents [s].f - agents [aPopSize - 1].f); } else { roulette [s].start = roulette [s - 1].end; roulette [s].end = roulette [s].start + (agents [s - 1].f - agents [s].f) * 0.1; } } } int SpinRoulette (int aPopSize); }; //——————————————————————————————————————————————————————————————————————————————
Como los algoritmos de optimización estocástica se basan en la generación de números aleatorios, esta operación puede realizarse cientos o incluso miles de veces para obtener cada solución. Por lo tanto, resultará aconsejable optimizar el proceso de generación de números aleatorios separando tareas específicas. Dado que el generador estándar genera números enteros, existe la posibilidad de acelerar este proceso.
Ya nos hemos encontrado antes con el método "RNDfromCI", que genera un número real aleatorio dentro de un rango dado ["min", "max"]:
double C_AO_Utilities ::RNDfromCI (double min, double max) { if (min == max) return min; if (min > max) { double temp = min; min = max; max = temp; } return min + ((max - min) * rand () / 32767.0); }
Con mucha frecuencia es necesario generar un entero aleatorio, por ejemplo para seleccionar aleatoriamente un agente en una población. El método "RNDintInRange" nos ayudará con ello.
int C_AO_Utilities :: RNDintInRange (int min, int max) { if (min == max) return min; if (min > max) { int temp = min; min = max; max = temp; } return min + rand () % (max - min + 1); }
Obtener una magnitud booleana aleatoria usando el método "RNDbool" puede ser muy rápido comparado con los dos métodos anteriores, por lo que tenía sentido dividir las variables aleatorias en métodos separados dependiendo del problema.
bool C_AO_Utilities :: RNDbool () { return rand () % 2 == 0; }
Y un método más "RNDprobab" para obtener un número real aleatorio en el rango [0.0, 1.0]. Resulta muy adecuado para calcular la probabilidad de realizar determinadas operaciones, como la probabilidad de cruce en un algoritmo genético. Dichas transacciones también son bastante habituales.
double C_AO_Utilities :: RNDprobab () { return (double)rand () / 32767; }Vamos a analizar ahora la clase básica "C_AO" de los algoritmos de optimización basados en poblaciones. Esta clase describe los atributos obligatorios de todos los algoritmos de población, tales como:
- Métodos y propiedades de la clase "C_AO":
- SetParams: método virtual para establecer los parámetros del algoritmo.
- Init: método virtual para inicializar el algoritmo transmitiendo el rango de búsqueda mínimo y máximo, el paso y el número de épocas.
-Moving: método virtual para ejecutar un paso del algoritmo.
- Revision: método virtual para realizar la revisión de un algoritmo.
- GetName: método para obtener el nombre del algoritmo.
- GetDesc: método para obtener la descripción del algoritmo.
- GetParams: método para obtener los parámetros del algoritmo en forma de cadena.
- Propiedades protegidas de la clase "C_AO":
- ao_name: nombre del algoritmo.
- " ao_desc: descripción del algoritmo.
- rangeMin, rangeMax, rangeStep: arrays para almacenar el rango de búsqueda mínimo y máximo y el paso.
- coords: número de coordenadas.
- popSize: tamaño de la población.
- revision: bandera de revisión.
- u: objeto de clase "C_AO_Utilities" para ejecutar funciones auxiliares.
#include "#C_AO_Utilities.mqh" //—————————————————————————————————————————————————————————————————————————————— class C_AO { public: //-------------------------------------------------------------------- C_AO () { } ~C_AO () { for (int i = 0; i < ArraySize (a); i++) delete a [i];} double cB []; //best coordinates double fB; //FF of the best coordinates C_AO_Agent *a []; //agents S_AlgoParam params []; //algorithm parameters virtual void SetParams () { } virtual bool Init (const double &rangeMinP [], //minimum search range const double &rangeMaxP [], //maximum search range const double &rangeStepP [], //step search const int epochsP = 0) //number of epochs { return false;} virtual void Moving () { } virtual void Revision () { } string GetName () { return ao_name;} string GetDesc () { return ao_desc;} string GetParams () { string str = ""; for (int i = 0; i < ArraySize (params); i++) { str += (string)params [i].val + "|"; } return str; } protected: //----------------------------------------------------------------- string ao_name; //ao name; string ao_desc; //ao description double rangeMin []; //minimum search range double rangeMax []; //maximum search range double rangeStep []; //step search int coords; //coordinates number int popSize; //population size bool revision; C_AO_Utilities u; //auxiliary functions bool StandardInit (const double &rangeMinP [], //minimum search range const double &rangeMaxP [], //maximum search range const double &rangeStepP []) //step search { MathSrand ((int)GetMicrosecondCount ()); //reset of the generator fB = -DBL_MAX; revision = false; coords = ArraySize (rangeMinP); if (coords == 0 || coords != ArraySize (rangeMaxP) || coords != ArraySize (rangeStepP)) return false; ArrayResize (rangeMin, coords); ArrayResize (rangeMax, coords); ArrayResize (rangeStep, coords); ArrayResize (cB, coords); ArrayCopy (rangeMin, rangeMinP, 0, 0, WHOLE_ARRAY); ArrayCopy (rangeMax, rangeMaxP, 0, 0, WHOLE_ARRAY); ArrayCopy (rangeStep, rangeStepP, 0, 0, WHOLE_ARRAY); return true; } }; //——————————————————————————————————————————————————————————————————————————————
Asimismo, en el propio archivo junto con la clase básica encontraremos la enumeración "E_AO" que contiene los identificadores de los algoritmos de optimización y la función "SelectAO", que permite crear un ejemplar del algoritmo correspondiente y obtener su puntero.
#include "AO_BGA_Binary_Genetic_Algorithm.mqh" #include "AO_(P_O)ES_Evolution_Strategies.mqh" #include "AO_DE_Differential_Evolution.mqh" #include "AO_SDSm_Stochastic_Diffusion_Search.mqh" #include "AO_ESG_Evolution_of_Social_Groups.mqh"; //—————————————————————————————————————————————————————————————————————————————— enum E_AO { AO_BGA, AO_P_O_ES, AO_SDSm, AO_ESG, AO_DE, AO_NONE }; C_AO *SelectAO (E_AO a) { C_AO *ao; switch (a) { case AO_BGA: ao = new C_AO_BGA (); return (GetPointer (ao)); case AO_P_O_ES: ao = new C_AO_P_O_ES (); return (GetPointer (ao)); case AO_SDSm: ao = new C_AO_SDSm (); return (GetPointer (ao)); case AO_ESG: ao = new C_AO_ESG (); return (GetPointer (ao)); case AO_DE: ao = new C_AO_DE (); return (GetPointer (ao)); default: ao = NULL; return NULL; } } //——————————————————————————————————————————————————————————————————————————————
3. Código de algoritmos herederos
Como ejemplo de herencia de una clase básica, analizaremos el algoritmo de búsqueda por difusión estocástica, SDSm. Heredaremos el agente "C_SDS_Agent" de este algoritmo del agente básico "C_AO_Agent". Nótese que las coordenadas "c" y la adaptabilidad "f" están presentes en el método de inicialización del agente, pero no están declaradas en la clase "C_SDS_Agent". Esto tiene sentido, ya que estos atributos son obligatorios para todos los agentes de los algoritmos de optimización y se heredan del algoritmo básico, por lo que no será necesario volver a declararlos.
//—————————————————————————————————————————————————————————————————————————————— class C_SDS_Agent : public C_AO_Agent { public: //-------------------------------------------------------------------- ~C_SDS_Agent () { } int raddr []; //restaurant address int raddrPrev []; //previous restaurant address double cPrev []; //previous coordinates (dishes) double fPrev; //previous fitness void Init (int coords) { ArrayResize (c, coords); ArrayResize (cPrev, coords); ArrayResize (raddr, coords); ArrayResize (raddrPrev, coords); f = -DBL_MAX; fPrev = -DBL_MAX; } }; //——————————————————————————————————————————————————————————————————————————————
La clase "C_AO_SDSm" del algoritmo SDSm heredará de la clase "C_AO". Al declarar el objeto de la clase en el constructor, inicializaremos los parámetros externos del algoritmo, que posteriormente podrán ser modificados por el usuario si lo desea; los parámetros estarán disponibles como array y no tendremos que preocuparnos de la compatibilidad con el banco de pruebas.
//—————————————————————————————————————————————————————————————————————————————— class C_AO_SDSm : public C_AO { public: //-------------------------------------------------------------------- ~C_AO_SDSm () { } C_AO_SDSm () { ao_name = "SDSm"; ao_desc = "Stochastic Diffusion Search"; popSize = 100; //population size restNumb = 100; //restaurants number probabRest = 0.05; //probability restaurant choosing ArrayResize (params, 3); params [0].name = "popSize"; params [0].val = popSize; params [1].name = "restNumb"; params [1].val = restNumb; params [2].name = "probabRest"; params [2].val = probabRest; } void SetParams () { popSize = (int)params [0].val; restNumb = (int)params [1].val; probabRest = params [2].val; } bool Init (const double &rangeMinP [], //minimum search range const double &rangeMaxP [], //maximum search range const double &rangeStepP [], //step search const int epochsP = 0); //number of epochs void Moving (); void Revision (); //---------------------------------------------------------------------------- int restNumb; //restaurants number double probabRest; //probability restaurant choosing C_SDS_Agent *agent []; //candidates private: //------------------------------------------------------------------- struct S_Riverbed //river bed { double coordOnSector []; //coordinate on the sector (number of cells: number of sectors on the coordinate, cell value: specific coordinate on the sector) }; double restSpace []; //restaurants space S_Riverbed rb []; //riverbed void Research (const double ko, const int raddr, const double restSpace, const double rangeMin, const double rangeStep, const double pitOld, double &pitNew); }; //——————————————————————————————————————————————————————————————————————————————Además, deberemos considerar especialmente el método de inicialización "Init" de la clase "C_AO_SDSm". Por pasos, el método hará lo siguiente
1. Desde el principio deberemos llamar al método de la clase básica "StandardInit" y transmitirle "rangeMinP", "rangeMaxP", "rangeStepP". Si el método retorna "false", la función "Init" también devolverá "false", informando de que el algoritmo no se ha podido inicializar.
2. Eliminación de agentes usando "delete". Esto será necesario cuando el objeto algoritmo se utilice repetidamente.
3. A continuación, redimensionaremos con los arrays "agent" del algoritmo SDSm y "a" de la clase básica a "popSize" y realizaremos la conversión de tipos.
4. Los siguientes pasos será similares al algoritmo descrito anteriormente en el artículo sobre SDSm.
//—————————————————————————————————————————————————————————————————————————————— bool C_AO_SDSm::Init (const double &rangeMinP [], //minimum search range const double &rangeMaxP [], //maximum search range const double &rangeStepP [], //step search const int epochsP = 0) //number of epochs { if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false; //---------------------------------------------------------------------------- for (int i = 0; i < ArraySize (agent); i++) delete agent [i]; ArrayResize (agent, popSize); ArrayResize (a, popSize); for (int i = 0; i < popSize; i++) { a [i] = new C_SDS_Agent (); agent [i] = (C_SDS_Agent *)a [i]; agent [i].Init (coords); } ArrayResize (restSpace, coords); ArrayResize (rb, coords); for (int i = 0; i < coords; i++) { ArrayResize (rb [i].coordOnSector, restNumb); ArrayInitialize (rb [i].coordOnSector, -DBL_MAX); } for (int i = 0; i < coords; i++) { restSpace [i] = (rangeMax [i] - rangeMin [i]) / restNumb; } return true; } //——————————————————————————————————————————————————————————————————————————————
4. Código de un banco de pruebas único para todos los algoritmos
Aunque en este momento no será necesario "multiplicar" los bancos de pruebas, trasladaremos todas las funciones de los bancos de pruebas a la clase "C_TestStand". Esto permitirá encapsular cómodamente la funcionalidad del banco. Como no hay cambios significativos en las funciones del banco, no las describiremos en detalle. No hay más que ver el aspecto que tiene ahora esta "granja de pruebas":
#include <Canvas\Canvas.mqh> #include <\Math\Functions.mqh> //—————————————————————————————————————————————————————————————————————————————— class C_TestStand { public: void Init (int width, int height) { W = width; //750; H = height; //375; WscrFunc = H - 2; HscrFunc = H - 2; //creating a table --------------------------------------------------------- string canvasName = "AO_Test_Func_Canvas"; if (!Canvas.CreateBitmapLabel (canvasName, 5, 30, W, H, COLOR_FORMAT_ARGB_RAW)) { Print ("Error creating Canvas: ", GetLastError ()); return; } ObjectSetInteger (0, canvasName, OBJPROP_HIDDEN, false); ObjectSetInteger (0, canvasName, OBJPROP_SELECTABLE, true); ArrayResize (FunctScrin, HscrFunc); for (int i = 0; i < HscrFunc; i++) ArrayResize (FunctScrin [i].clr, HscrFunc); } struct S_CLR { color clr []; }; //---------------------------------------------------------------------------- public: void CanvasErase () { Canvas.Erase (XRGB (0, 0, 0)); Canvas.FillRectangle (1, 1, H - 2, H - 2, COLOR2RGB (clrWhite)); Canvas.FillRectangle (H + 1, 1, W - 2, H - 2, COLOR2RGB (clrWhite)); } //---------------------------------------------------------------------------- public: void MaxMinDr (C_Function & f) { //draw Max global------------------------------------------------------------- int x = (int)Scale(f.GetMaxFuncX(), f.GetMinRangeX(), f.GetMaxRangeX(), 1, W/2 - 1, false); int y = (int)Scale(f.GetMaxFuncY(), f.GetMinRangeY(), f.GetMaxRangeY(), 1, H - 1, true); Canvas.Circle(x, y, 12, COLOR2RGB(clrBlack)); Canvas.Circle(x, y, 13, COLOR2RGB(clrBlack)); Canvas.Circle(x, y, 14, COLOR2RGB(clrBlack)); Canvas.Circle(x, y, 15, COLOR2RGB(clrBlack)); //draw Min global------------------------------------------------------------- x = (int)Scale(f.GetMinFuncX(), f.GetMinRangeX(), f.GetMaxRangeX(), 0, W/2 - 1, false); y = (int)Scale(f.GetMinFuncY(), f.GetMinRangeY(), f.GetMaxRangeY(), 0, H - 1, true); Canvas.Circle(x, y, 12, COLOR2RGB(clrBlack)); Canvas.Circle(x, y, 13, COLOR2RGB(clrBlack)); } //---------------------------------------------------------------------------- public: void PointDr (double &args [], C_Function & f, int shiftX, int shiftY, int count, bool main) { double x = 0.0; double y = 0.0; double xAve = 0.0; double yAve = 0.0; int width = 0; int height = 0; color clrF = clrNONE; for(int i = 0; i < count; i++) { xAve += args [i * 2]; yAve += args [i * 2 + 1]; x = args [i * 2]; y = args [i * 2 + 1]; width = (int)Scale(x, f.GetMinRangeX(), f.GetMaxRangeX(), 0, WscrFunc - 1, false); height = (int)Scale(y, f.GetMinRangeY(), f.GetMaxRangeY(), 0, HscrFunc - 1, true); clrF = DoubleToColor(i, 0, count - 1, 0, 270); Canvas.FillCircle(width + shiftX, height + shiftY, 1, COLOR2RGB(clrF)); } xAve /=(double)count; yAve /=(double)count; width = (int)Scale(xAve, f.GetMinRangeX(), f.GetMaxRangeX(), 0, WscrFunc - 1, false); height = (int)Scale(yAve, f.GetMinRangeY(), f.GetMaxRangeY(), 0, HscrFunc - 1, true); if(!main) { Canvas.FillCircle(width + shiftX, height + shiftY, 3, COLOR2RGB(clrBlack)); Canvas.FillCircle(width + shiftX, height + shiftY, 2, COLOR2RGB(clrWhite)); } else { Canvas.Circle (width + shiftX, height + shiftY, 5, COLOR2RGB (clrBlack)); Canvas.Circle (width + shiftX, height + shiftY, 6, COLOR2RGB (clrBlack)); } } //---------------------------------------------------------------------------- public: void SendGraphToCanvas () { for (int w = 0; w < HscrFunc; w++) { for (int h = 0; h < HscrFunc; h++) { Canvas.PixelSet (w + 1, h + 1, COLOR2RGB (FunctScrin [w].clr [h])); } } } //---------------------------------------------------------------------------- public: void DrawFunctionGraph (C_Function & f) { double ar [2]; double fV; for (int w = 0; w < HscrFunc; w++) { ar [0] = Scale (w, 0, H, f.GetMinRangeX (), f.GetMaxRangeX (), false); for (int h = 0; h < HscrFunc; h++) { ar [1] = Scale (h, 0, H, f.GetMinRangeY (), f.GetMaxRangeY (), true); fV = f.CalcFunc (ar, 1); FunctScrin [w].clr [h] = DoubleToColor (fV, f.GetMinFunValue (), f.GetMaxFunValue (), 0, 270); } } } //---------------------------------------------------------------------------- public: void Update () { Canvas.Update (); } //---------------------------------------------------------------------------- //Scaling a number from a range to a specified range public: double Scale (double In, double InMIN, double InMAX, double OutMIN, double OutMAX, bool Revers = false) { if (OutMIN == OutMAX) return (OutMIN); if (InMIN == InMAX) return ((OutMIN + OutMAX) / 2.0); else { if (Revers) { if (In < InMIN) return (OutMAX); if (In > InMAX) return (OutMIN); return (((InMAX - In) * (OutMAX - OutMIN) / (InMAX - InMIN)) + OutMIN); } else { if (In < InMIN) return (OutMIN); if (In > InMAX) return (OutMAX); return (((In - InMIN) * (OutMAX - OutMIN) / (InMAX - InMIN)) + OutMIN); } } } //---------------------------------------------------------------------------- private: color DoubleToColor (const double In, //input value const double inMin, //minimum of input values const double inMax, //maximum of input values const int loH, //lower bound of HSL range values const int upH) //upper bound of HSL range values { int h = (int) Scale (In, inMin, inMax, loH, upH, true); return HSLtoRGB (h, 1.0, 0.5); } //---------------------------------------------------------------------------- private: color HSLtoRGB (const int h, //0 ... 360 const double s, //0.0 ... 1.0 const double l) //0.0 ... 1.0 { int r; int g; int b; if (s == 0.0) { r = g = b = (unsigned char)(l * 255); return StringToColor ((string) r + "," + (string) g + "," + (string) b); } else { double v1, v2; double hue = (double) h / 360.0; v2 = (l < 0.5) ? (l * (1.0 + s)) : ((l + s) - (l * s)); v1 = 2.0 * l - v2; r = (unsigned char)(255 * HueToRGB (v1, v2, hue + (1.0 / 3.0))); g = (unsigned char)(255 * HueToRGB (v1, v2, hue)); b = (unsigned char)(255 * HueToRGB (v1, v2, hue - (1.0 / 3.0))); return StringToColor ((string) r + "," + (string) g + "," + (string) b); } } //---------------------------------------------------------------------------- private: double HueToRGB (double v1, double v2, double vH) { if (vH < 0) vH += 1; if (vH > 1) vH -= 1; if ((6 * vH) < 1) return (v1 + (v2 - v1) * 6 * vH); if ((2 * vH) < 1) return v2; if ((3 * vH) < 2) return (v1 + (v2 - v1) * ((2.0f / 3) - vH) * 6); return v1; } //---------------------------------------------------------------------------- public: int W; //monitor screen width public: int H; //monitor screen height private: int WscrFunc; //test function screen width private: int HscrFunc; //test function screen height public: CCanvas Canvas; //drawing table private: S_CLR FunctScrin []; //two-dimensional matrix of colors }; //——————————————————————————————————————————————————————————————————————————————Veamos ahora el código del banco de pruebas. Al examinar los parámetros de entrada del banco, queda claro que ahora tendremos la posibilidad de seleccionar el algoritmo de optimización y las funciones de prueba en los ajustes. Esto permitirá crear conjuntos únicos de funciones de prueba para validar el algoritmo. Ahora podremos probar solo funciones suaves o solo funciones discretas. Además, también podremos seleccionar cualquier combinación de funciones de prueba que se adapte mejor a las necesidades del usuario.
#include "PAO\#C_TestStandFunctions.mqh" #include "PAO\#C_AO.mqh" //—————————————————————————————————————————————————————————————————————————————— input string AOparam = "----------------"; //AO parameters----------- input E_AO AOexactly_P = AO_NONE; input string TestStand_1 = "----------------"; //Test stand-------------- input double ArgumentStep_P = 0.0; //Argument Step input string TestStand_2 = "----------------"; //------------------------ input int Test1FuncRuns_P = 5; //Test #1: Number of functions in the test input int Test2FuncRuns_P = 25; //Test #2: Number of functions in the test input int Test3FuncRuns_P = 500; //Test #3: Number of functions in the test input string TestStand_3 = "----------------"; //------------------------ input EFunc Function1 = Hilly; input EFunc Function2 = Forest; input EFunc Function3 = Megacity; input string TestStand_4 = "----------------"; //------------------------ input int NumbTestFuncRuns_P = 10000; //Number of test function runs input int NumberRepetTest_P = 10; //Test repets number input string TestStand_5 = "----------------"; //------------------------ input bool Video_P = true; //Show video //—————————————————————————————————————————————————————————————————————————————— //—————————————————————————————————————————————————————————————————————————————— void OnStart () { C_AO *AO = SelectAO (AOexactly_P); if (AO == NULL) { Print ("AO is not selected..."); return; } Print (AO.GetName (), "|", AO.GetDesc (), "|", AO.GetParams ()); //============================================================================ C_TestStand ST; //stand ST.Init (750, 375); double allScore = 0.0; double allTests = 0.0; C_Function *F1 = SelectFunction (Function1); C_Function *F2 = SelectFunction (Function2); C_Function *F3 = SelectFunction (Function3); if (F1 != NULL) { Print ("============================="); ST.CanvasErase (); FuncTests (AO, ST, F1, Test1FuncRuns_P, clrLime, allScore, allTests); FuncTests (AO, ST, F1, Test2FuncRuns_P, clrAqua, allScore, allTests); FuncTests (AO, ST, F1, Test3FuncRuns_P, clrOrangeRed, allScore, allTests); delete F1; } if (F2 != NULL) { Print ("============================="); ST.CanvasErase (); FuncTests (AO, ST, F2, Test1FuncRuns_P, clrLime, allScore, allTests); FuncTests (AO, ST, F2, Test2FuncRuns_P, clrAqua, allScore, allTests); FuncTests (AO, ST, F2, Test3FuncRuns_P, clrOrangeRed, allScore, allTests); delete F2; } if (F3 != NULL) { Print ("============================="); ST.CanvasErase (); FuncTests (AO, ST, F3, Test1FuncRuns_P, clrLime, allScore, allTests); FuncTests (AO, ST, F3, Test2FuncRuns_P, clrAqua, allScore, allTests); FuncTests (AO, ST, F3, Test3FuncRuns_P, clrOrangeRed, allScore, allTests); delete F3; } Print ("============================="); if (allTests > 0.0) Print ("All score: ", DoubleToString (allScore, 5), " (", DoubleToString (allScore * 100 / allTests, 2), "%)"); delete AO; } //—————————————————————————————————————————————————————————————————————————————— //—————————————————————————————————————————————————————————————————————————————— void FuncTests (C_AO &ao, C_TestStand &st, C_Function &f, const int funcCount, const color clrConv, double &allScore, double &allTests) { if (funcCount <= 0) return; allTests++; if (Video_P) { st.DrawFunctionGraph (f); st.SendGraphToCanvas (); st.MaxMinDr (f); st.Update (); } int xConv = 0.0; int yConv = 0.0; double aveResult = 0.0; int params = funcCount * 2; int epochCount = NumbTestFuncRuns_P / (int)ao.params [0].val; //---------------------------------------------------------------------------- double rangeMin [], rangeMax [], rangeStep []; ArrayResize (rangeMin, params); ArrayResize (rangeMax, params); ArrayResize (rangeStep, params); for (int i = 0; i < funcCount; i++) { rangeMax [i * 2] = f.GetMaxRangeX (); rangeMin [i * 2] = f.GetMinRangeX (); rangeStep [i * 2] = ArgumentStep_P; rangeMax [i * 2 + 1] = f.GetMaxRangeY (); rangeMin [i * 2 + 1] = f.GetMinRangeY (); rangeStep [i * 2 + 1] = ArgumentStep_P; } for (int test = 0; test < NumberRepetTest_P; test++) { //-------------------------------------------------------------------------- if (!ao.Init (rangeMin, rangeMax, rangeStep)) break; // Optimization------------------------------------------------------------- for (int epochCNT = 1; epochCNT <= epochCount && !IsStopped (); epochCNT++) { ao.Moving (); for (int set = 0; set < ArraySize (ao.a); set++) { ao.a [set].f = f.CalcFunc (ao.a [set].c, funcCount); } ao.Revision (); if (Video_P) { //drawing a population-------------------------------------------------- st.SendGraphToCanvas (); for (int i = 0; i < ArraySize (ao.a); i++) { st.PointDr (ao.a [i].c, f, 1, 1, funcCount, false); } st.PointDr (ao.cB, f, 1, 1, funcCount, true); st.MaxMinDr (f); //drawing a convergence graph--------------------------------------------- xConv = (int)st.Scale (epochCNT, 1, epochCount, st.H + 2, st.W - 3, false); yConv = (int)st.Scale (ao.fB, f.GetMinFunValue (), f.GetMaxFunValue (), 2, st.H - 2, true); st.Canvas.FillCircle (xConv, yConv, 1, COLOR2RGB (clrConv)); st.Update (); } } aveResult += ao.fB; } aveResult /= (double)NumberRepetTest_P; double score = aveResult; Print (funcCount, " ", f.GetFuncName (), "'s; Func runs: ", NumbTestFuncRuns_P, "; result: ", aveResult); allScore += score; } //——————————————————————————————————————————————————————————————————————————————
5. Añadimos funciones de prueba muy conocidas
A veces me preguntan por qué no he incluido en el conjunto de funciones de prueba algoritmos tan conocidos y usados en la investigación y el desarrollo de algoritmos de optimización. La razón es que todas ellas entran en la categoría de funciones de prueba "simples", según mi clasificación. Más de la mitad de sus superficies se concentran cerca del extremo global, lo cual las hace demasiado "previsibles" para realizar pruebas adecuadas. Aun así, la gente está acostumbrada a confiar en ellas y tiende a probar sus algoritmos precisamente con esas características. Así que he decidido incluir en el conjunto de características Ackley, Goldstein-Price, y Shaffer nº 2. Esto ayudará a equilibrar la elección de funciones de prueba para los usuarios y posibilitará la realización de pruebas más completas y sólidas para los algoritmos de optimización, abriendo nuevos horizontes a los investigadores y contribuyendo a una comprensión más profunda de su rendimiento.
La fórmula de Ackley:
f(x, y) = -(-20 * exp(-0.2 * sqrt(0.5 * (x^2 + y^2))) - exp(0.5 * (cos(2 * pi * x) + cos(2 * pi * y))) + e + 20)
donde:
- x, y - parámetros de entrada de la función,
- e - número de Euler (aproximadamente 2,71828),
- π - número pi (aproximadamente 3,14159).
Código de la función:
double Core (double x, double y) { double res1 = -20.0 * MathExp (-0.2 * MathSqrt (0.5 * (x * x + y * y))); double res2 = -MathExp (0.5 * (MathCos (2.0 * M_PI * x) + MathCos (2.0 * M_PI * y))); double res3 = -(res1 + res2 + M_E + 20.0); return Scale (res3, -14.302667500265278, 0.0, 0.0, 1.0); }
Fórmula de Goldstein-Price :
f(x, y) = -([1 + (x + y + 1)^2 * (19 - 14x + 3x^2 - 14y + 6xy + 3y^2)] * [30 + (2x - 3y)^2 * (18 - 32x + 12x^2 + 48y - 36xy + 27y^2)])
Código de la función:
double Core (double x, double y) { double part1 = 1 + MathPow ((x + y + 1), 2) * (19 - 14 * x + 3 * x * x - 14 * y + 6 * x * y + 3 * y * y); double part2 = 30 + MathPow ((2 * x - 3 * y), 2) * (18 - 32 * x + 12 * x * x + 48 * y - 36 * x * y + 27 * y * y); return Scale (-part1 * part2, -1015690.2717980597, -3.0, 0.0, 1.0); }
Fórmula de Shaffer nº 2:
f(x, y) = -(0.5 + ((sin(x^2 - y^2)^2 - 0.5) / (1 + 0.001 * (x^2 + y^2))^2))
Código de la función:
double Core (double x, double y) { double numerator = MathPow (MathSin (x * x - y * y), 2) - 0.5; double denominator = MathPow (1 + 0.001 * (x * x + y * y), 2); return Scale (-(0.5 + numerator / denominator), -0.9984331449753265, 0, 0, 1.0); }
Nota: los valores de la función de prueba se darán en el intervalo [0,0, 1,0].
6. Creamos funciones de prueba en 3D
A veces, para comprender mejor las funciones de prueba y su topografía, deberemos no solo abstraer números y fórmulas, sino verlos visualmente. Así que hemos decidido aprovechar la capacidad de construir escenas 3D utilizando DirectX en la plataforma MetaTrader 5. Me he inspirado en el archivo "...\MQL5\Experts\Examples\Math 3D Morpher\Math 3D Morpher.mq5" de los desarrolladores, que se incluye en el paquete estándar y representa las funciones que hemos desarrollado anteriormente (pienso añadirlas a la lista de funciones del banco de pruebas). Así que hemos decidido crear un visualizador para las funciones de prueba.
Para ello,hemos tenido que ampliar el archivo "Functions.mqh", que almacena la clase de funciones de prueba utilizadas para probar algoritmos de optimización. Las funciones adicionales que hemos añadido permitirán no solo estudiar visualmente los cambios de las funciones al modificar estas (si surge esa necesidad), sino también estudiar más a fondo sus propiedades y características.
Este proceso no solo hace que mi investigación resulte más atractiva y visual, sino que también me ayuda a comprender mejor el comportamiento de las funciones de prueba. En última instancia, la visualización en 3D ayuda no solo a ver números en la pantalla, sino a interactuar directamente con la forma y la estructura de los elementos, lo cual resulta importante a la hora de modificarlos y analizar sus propiedades.
Código de funciones adicionales para construir un modelo para una escena 3D:
//—————————————————————————————————————————————————————————————————————————————— //GenerateFunctionDataFixedSize bool GenerateFunctionDataFixedSize (int x_size, int y_size, double &data [], double x_min, double x_max, double y_min, double y_max, C_Function &function) { if (x_size < 2 || y_size < 2) { PrintFormat ("Error in data sizes: x_size=%d,y_size=%d", x_size, y_size); return (false); } double dx = (x_max - x_min) / (x_size - 1); double dy = (y_max - y_min) / (y_size - 1); ArrayResize (data, x_size * y_size); //--- for (int j = 0; j < y_size; j++) { for (int i = 0; i < x_size; i++) { double x = x_min + i * dx; double y = y_min + j * dy; data [j * x_size + i] = function.Core (x, y); } } return (true); } //—————————————————————————————————————————————————————————————————————————————— //—————————————————————————————————————————————————————————————————————————————— //GenerateDataFixedSize bool GenerateDataFixedSize (int x_size, int y_size, C_Function &function, double &data []) { if (x_size < 2 || y_size < 2) { PrintFormat ("Error in data sizes: x_size=%d,y_size=%d", x_size, y_size); return (false); } return GenerateFunctionDataFixedSize (x_size, y_size, data, function.GetMinRangeX (), function.GetMaxRangeX (), function.GetMinRangeY (), function.GetMaxRangeY (), function); } //——————————————————————————————————————————————————————————————————————————————
Además, hemos añadido variantes discretas de funciones que ya conocemos: SkinDiscrete y HillyDiscrete.
Función HillyDiscrete.
Función SkinDiscrete.
Función Shaffer nº 2.
7. Conclusiones
Vamos a continuar usando las funciones de prueba Hilly, Forest y Megacity, que ya se han ganado su reputación como pruebas reales para algoritmos de optimización, ayudando a elaborar una tabla de clasificación sólida. Sin embargo, no nos hemos limitado a estas funciones; de hecho, ¡la lista de funciones de prueba se ha ampliado con éxito! Así que tendrá libertad de elección: experimente, explore, descubra nuevos horizontes, porque eso es lo bonito de la investigación científica.
Para concluir nuestra exploración de "libro de recetas" respecto a los métodos de optimización híbridos con herencia de una clase básica, podemos ver que la creación de un punto de partida de este tipo abre la puerta a infinitas posibilidades de investigación. Combinando distintos algoritmos de población en una sola clase, no solo podremos mejorar la precisión y la velocidad de búsqueda de soluciones óptimas, sino también crear una herramienta de investigación versátil.
La creación de un único banco de pruebas que combine diversas funciones de prueba -discretas, suaves, agudas, no diferenciables- abrirá nuevas oportunidades para probar y comparar distintos métodos de optimización. Este planteamiento permitirá a los investigadores no solo utilizar conjuntos de características normalizadas, sino también crear sus propios bancos de pruebas adaptados a tareas y requisitos específicos.
Así, la combinación de algoritmos poblacionales en una única clase y la creación de un banco de pruebas universal nos abrirá una vía fascinante para crear nuevas combinaciones "gastronómicas" de métodos de optimización, permitiéndonos descubrir nuevas notas de sabor en el mundo de la búsqueda de soluciones óptimas. Le invito a continuar este experimento "culinario" y crear nuevas obras maestras en el camino hacia la excelencia y la innovación en la optimización.
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/14331
![Superar los retos de integración de ONNX](https://c.mql5.com/2/75/Overcoming_ONNX_Integration_Challenges____LOGO.png)
![Formulación Genérica de Optimización (GOF, Generic Optimization Formulation) utilizando el `Criterio máximos del usuario` (Custom Max) con múltiples restricciones en el Probador de Estrategias](https://c.mql5.com/2/76/A_Generic_Optimization_Formulation_2GOFt_to_Implement_Custom_Max_with_Constraints____LOGO.png)
![Algoritmos de optimización de la población: Resiliencia ante el estancamiento en los extremos locales (Parte I)](https://c.mql5.com/2/72/Population_optimization_algorithms__Resistance_to_getting_stuck_in_local_extrema__LOGO.png)
![Redes neuronales: así de sencillo (Parte 78): Detector de objetos basado en el Transformer (DFFT)](https://c.mql5.com/2/70/Neural_networks_made_easy_Part_78____LOGO.png)
![MQL5 - Lenguaje de estrategias comerciales para el terminal de cliente MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso