
Algoritmo de optimización del comportamiento social adaptativo — Adaptive Social Behavior Optimization (ASBO): Método de Schwefel, método de Box-Muller
1. Introducción
Existe una sabiduría increíble en el comportamiento colectivo de los organismos vivos. Desde los enjambres de peces hasta las sociedades humanas, la cooperación y la colaboración desempeñan un papel esencial en la supervivencia y la prosperidad. Pero, ¿y si pudiéramos usar estos principios de estructura social para crear nuevos algoritmos de optimización capaces de resolver problemas complejos con eficacia y precisión?
Tenemos muchos ejemplos de comportamiento grupal en la naturaleza donde los organismos vivos se unen para formar sociedades y aumentar sus posibilidades de supervivencia e innovación. Este fenómeno, observado en animales, en la sociedad humana y en otras formas de vida, ha sido objeto de interesantes investigaciones por parte de biólogos evolutivos y filósofos sociales. El estudio de estas sociedades ha dado lugar al desarrollo de un modelo informático que imita su funcionamiento exitoso con respecto a determinados objetivos. Estos modelos, como la optimización por enjambre de partículas y la optimización por colonia de hormigas, demuestran la eficacia del trabajo en grupo para resolver problemas de optimización.
El presente artículo examina el concepto de estructura social y su impacto en los procesos de toma de decisiones en grupo. También presentamos un modelo matemático basado en los principios del comportamiento social y la interacción en las sociedades que puede aplicarse para conseguir una optimización global. Este modelo, llamado ASBO (Adaptive Social Behavior Optimisation), tiene en cuenta la influencia del entorno en la toma de decisiones del grupo, incluidos el liderazgo, la vecindad y la autoorganización. El algoritmo Adaptive Social Behaviour Optimisation (ASBO) fue propuesto por Manojo Kumar Singh y publicado en las actas de la conferencia de 2013 en Proceedings of ICAdC, AISC 174, editado por Aswatha Kumar M. et al.
Estructura social y modelo de influencia:
- Una sociedad es un grupo de seres vivos interrelacionados, unidos por un comportamiento o unas características comunes.
- Beneficios de vivir en comunidad: mayores oportunidades de éxito en la caza, la reproducción, la defensa contra los depredadores, la innovación.
- Factores clave que afectan al desarrollo del individuo en la sociedad: liderazgo, vecindad, autoestima.
- El modelo ASBO propuesto se basa en el liderazgo dinámico, la vecindad lógica dinámica y la autoestima.
Principios básicos del algoritmo ASBO:
- Existe una población de soluciones, cada una de las cuales supone un vector del espacio de dimensión D.
- Para cada solución, se calculan tres vectores: mejor solución global, mejor solución personal y centro de vecindad.
- La nueva posición de la solución se calcula usando coeficientes de influencia adaptativos.
- Los coeficientes de influencia se adaptan con la ayuda de una estrategia de mutación autoadaptativa.
2. Implementación de los métodos del algoritmo
Y antes de sumergirnos en el aprendizaje del algoritmo propiamente dicho, deberemos entender el concepto básico que lo sustenta. Este concepto se relaciona con el uso del método de Schwefel, que es un enfoque de la mutación autoadaptativa y se utiliza en algunos algoritmos de optimización, como los algoritmos evolutivos. Sus principales características son:
1. Autoadaptación de los parámetros de mutación:
- Cada individuo (solución) tiene sus propios parámetros de mutación (por ejemplo, el tamaño del paso de mutación).
- Dichos parámetros de mutación también evolucionan con las propias soluciones.
- Así, el tamaño del paso de mutación se adapta al paisaje funcional de cada individuo.
2. Mutación gaussiana:
- Se usa una distribución gaussiana (normal) para generar nuevas soluciones.
- La media de mutación es igual a la solución anterior, mientras que la desviación típica viene determinada por los parámetros de mutación.
3. Relación entre la solución y los parámetros de mutación:
- Los parámetros de mutación (tamaño del paso) dependen del valor de la función objetivo (aptitud) de la solución.
- Cuanto mejor sea la solución, menor será el tamaño del paso de mutación, y viceversa.
La idea que subyace al concepto de Schwefel es que la adaptación de los parámetros de mutación permite al algoritmo explorar el espacio de soluciones de manera más eficiente, especialmente en las últimas fases de la búsqueda, cuando se requiere un ajuste más fino de las soluciones.
El siguiente ejemplo muestra la aplicación del concepto de Schwefel al optimizar los parámetros de una estrategia comercial. A continuación le explicaremos con detalle cómo funciona el método.
Por ejemplo, vamos a tomar el asesor experto hipotético más simple, en el que la población inicial de soluciones aleatorias se crea durante la inicialización de "OnInit". En "OnTick", se ejecuta un paso del proceso evolutivo:
a. Se evalúa la aptitud de cada individuo de una población.
b. La población se clasifica según la aptitud.
c. Se aplica la mutación a todos menos al mejor individuo.
d. El número de generaciones aumenta.
Este proceso se repite hasta alcanzar un determinado número de generaciones. Una vez finalizada la optimización, se emitirá la mejor solución encontrada.
// Inputs input int PopulationSize = 50; // Population size input int Generations = 100; // Number of generations input double InitialStepSize = 1.0; // Initial step size // Structure for storing an individual struct Individual { double genes [3]; // Strategy parameters double stepSizes [3]; // Step sizes for each parameter double fitness; // Fitness }; // Global variables Individual population []; int generation = 0; // Initialization function int OnInit () { ArrayResize (population, PopulationSize); InitializePopulation (); return (INIT_SUCCEEDED); } // Main loop function datetime lastOptimizationTime = 0; void OnTick () { datetime currentTime = TimeCurrent (); // Check if a day has passed since the last optimization if (currentTime - lastOptimizationTime >= 86400) // 86400 seconds in a day { Optimize (); lastOptimizationTime = currentTime; } // Here is the code for trading with the current optimal parameters TradingLogic (); } void Optimize () { // Optimization code (current OnTick content) } void TradingLogic () { // Implementing trading logic using optimized parameters } // Initialize the population void InitializePopulation () { for (int i = 0; i < PopulationSize; i++) { for (int j = 0; j < 3; j++) { population [i].genes [j] = MathRand () / 32767.0 * 100; // Random values from 0 to 100 population [i].stepSizes [j] = InitialStepSize; } } } // Population fitness assessment void EvaluatePopulation () { for (int i = 0; i < PopulationSize; i++) { population [i].fitness = CalculateFitness (population [i]); } } // Calculate the fitness of an individual (this is where you need to implement your objective function) double CalculateFitness (Individual &ind) { // Example of a simple objective function return -(MathPow (ind.genes [0] - 50, 2) + MathPow (ind.genes [1] - 50, 2) + MathPow (ind.genes [2] - 50, 2)); } // Sort the population in descending order of fitness void SortPopulation () { ArraySort (population, WHOLE_ARRAY, 0, MODE_DESCEND); } // Population mutation according to Schwefel's concept void Mutate () { for (int i = 1; i < PopulationSize; i++) // Start from 1 to keep the best solution { for (int j = 0; j < 3; j++) { // Step size mutation population [i].stepSizes [j] *= MathExp (0.2 * MathRandom () - 0.1); // Gene mutation population [i].genes [j] += population [i].stepSizes [j] * NormalRandom (); // Limiting gene values population [i].genes [j] = MathMax (0, MathMin (100, population [i].genes [j])); } } } // Auxiliary function for displaying information about an individual void PrintIndividual (Individual &ind) { Print ("Genes: ", ind.genes [0], ", ", ind.genes [1], ", ", ind.genes [2]); Print ("Step sizes: ", ind.stepSizes [0], ", ", ind.stepSizes [1], ", ", ind.stepSizes [2]); Print ("Fitness: ", ind.fitness); }
Veamos el método por partes:
1. Estructura y parámetros de entrada.
Primero definiremos los parámetros de entrada del algoritmo y la estructura "Individual", que representará una solución individual en la población. Cada individuo posee genes (parámetros de estrategia), tamaños de paso para la mutación y un valor de aptitud.
input int PopulationSize = 50; // Population size input int Generations = 100; // Number of generations input double InitialStepSize = 1.0; // Initial step size struct Individual { double genes[3]; // Strategy parameters double stepSizes[3]; // Step sizes for each parameter double fitness; // Fitness };
2. Inicialización.
En la función "OnInit()", crearemos una población y la inicializaremos. La función "InitialisePopulation()" rellenará la población con valores genéticos aleatorios y establecerá los tamaños de paso iniciales.
int OnInit () { ArrayResize (population, PopulationSize); InitializePopulation (); return (INIT_SUCCEEDED); } void InitializePopulation () { for (int i = 0; i < PopulationSize; i++) { for (int j = 0; j < 3; j++) { population [i].genes [j] = MathRand () / 32767.0 * 100; population [i].stepSizes [j] = InitialStepSize; } } }
3. Ciclo principal.
La función "OnTick ()" controlará el proceso de optimización. Evaluará la población, la clasificará, realizará una mutación y pasará a la siguiente generación.
datetime lastOptimizationTime = 0; void OnTick () { datetime currentTime = TimeCurrent (); // Check if a day has passed since the last optimization if (currentTime - lastOptimizationTime >= 86400) // 86400 seconds in a day { Optimize (); lastOptimizationTime = currentTime; } // Here is the code for trading with the current optimal parameters TradingLogic (); } void Optimize () { // Optimization code (current OnTick content) } void TradingLogic () { // Implementing trading logic using optimized parameters }
4. Evaluación y clasificación de la población.
Estas funciones estimarán la aptitud de cada individuo y clasificarán la población en orden descendente de aptitud. La función "CalculateFitness()" de este ejemplo es sencilla, pero en una aplicación real debería tener una función objetivo para evaluar la estrategia comercial.
void EvaluatePopulation () { for (int i = 0; i < PopulationSize; i++) { population [i].fitness = CalculateFitness (population [i]); } } double CalculateFitness (Individual &ind) { return -(MathPow (ind.genes [0] - 50, 2) + MathPow (ind.genes [1] - 50, 2) + MathPow (ind.genes [2] - 50, 2)); } void SortPopulation () { ArraySort (population, WHOLE_ARRAY, 0, MODE_DESCEND); }
5. Mutación.
Esta será la parte clave para hacer realidad el concepto de Schwefel. Para cada individuo (salvo los mejores), nosotros:
- Mutaremos el tamaño del paso multiplicándolo por el exponente de un número aleatorio.
- Mutaremos el gen añadiendo un número aleatorio distribuido normalmente multiplicado por el tamaño del paso.
- Restringiremos los valores de los genes en el intervalo [0, 100].
Aplicación básica del concepto de Schwefel para la optimización de parámetros. En una aplicación real, deberemos adaptar la función objetivo a una estrategia comercial específica.
void Mutate () { for (int i = 1; i < PopulationSize; i++) { for (int j = 0; j < 3; j++) { population [i].stepSizes [j] *= MathExp (0.2 * MathRandom () - 0.1); population [i].genes [j] += population [i].stepSizes [j] * NormalRandom (); population [i].genes [j] = MathMax (0, MathMin (100, population [i].genes [j])); } } }
Por otra parte, cabe destacar la implementación de la función "NormalRandom()", que forma parte del concepto de Schwefel para la mutación adaptativa e implementará el método Box-Muller para generar números aleatorios con una distribución normal (gaussiana). Vamos a desglosar esta función:
1. Generación de números uniformemente distribuidos. Generaremos dos números aleatorios independientes "u1" y "u2" distribuidos uniformemente en el intervalo [0, 1].
double u1 = u.RNDfromCI(0, 1); double u2 = u.RNDfromCI(0, 1);
2. Conversión a una distribución normal. La fórmula de transformación de Box-Muller convertirá números uniformemente distribuidos en números normalmente distribuidos.
return MathSqrt(-2 * MathLog(u1)) * MathCos(2 * M_PI * u2);
Debemos señalar que se trata de la mitad de la implementación de la transformada de Box-Muller, y generará un único número distribuido normalmente. La transformación completa generará dos números distribuidos normalmente:
z0 = MathSqrt(-2 * MathLog(u1)) * MathCos(2 * M_PI * u2); z1 = MathSqrt(-2 * MathLog(u1)) * MathSin(2 * M_PI * u2);
Nuestra aplicación utilizará solo el coseno, que producirá un número distribuido normalmente. Esto resulta perfectamente aceptable si solo se necesita un número por llamada. Si se necesitan ambos números, podremos añadir un cálculo con seno.
Esta implementación resulta eficiente y se utiliza ampliamente para generar números aleatorios distribuidos normalmente en diversas aplicaciones, incluidos los algoritmos evolutivos y la optimización estocástica.
Características de los números generados:
1. Distribución: Distribución normal (gaussiana)
2. Valor medio: 0
3. Desviación estándar: 1
Rango de números generados: teóricamente, la distribución normal puede generar números que van de menos infinito a más infinito. En la práctica:
- Aproximadamente el 68% de los números generados estarán en el intervalo [-1, 1].
- Aproximadamente el 95% de los números estarán en el intervalo [-2, 2].
- Aproximadamente el 99,7% de los números estarán en el intervalo [-3, 3].
Muy raramente se pueden generar números más allá de [-4, 4], la probabilidad de que esto ocurra es extremadamente pequeña.
El método Box-Muller se usa para generar números aleatorios con distribución normal, lo cual es importante para realizar la mutación autoadaptativa en el algoritmo basado en el concepto de Schwefel. Esta distribución permitirá generar cambios pequeños con más frecuencia, pero a veces permitirá mutaciones mayores, lo cual facilitará una exploración eficaz del espacio de soluciones. Vamos a probar y evaluar el método Box-Muller en la práctica.
Primero escribiremos un script para probar la función "NormalRandom()":
#property version "1.00" #property script_show_inputs input int NumSamples = 10000; // Amount of generated numbers double NormalRandom () { double u1 = (double)MathRand () / 32767.0; double u2 = (double)MathRand () / 32767.0; return MathSqrt (-2 * MathLog (u1)) * MathCos (2 * M_PI * u2); } void OnStart () { double sum = 0; double sumSquared = 0; double min = DBL_MAX; double max = DBL_MIN; int histogram []; ArrayResize (histogram, 20); ArrayInitialize (histogram, 0); // Random number generation and analysis for (int i = 0; i < NumSamples; i++) { double value = NormalRandom (); sum += value; sumSquared += value * value; if (value < min) min = value; if (value > max) max = value; // Filling the histogram int index = (int)((value + 4) / 0.4); // Split the range [-4, 4] into 20 intervals if (index >= 0 && index < 20) histogram [index]++; } // Calculate statistics double mean = sum / NumSamples; double variance = (sumSquared - sum * sum / NumSamples) / (NumSamples - 1); double stdDev = MathSqrt (variance); // Display results Print ("Statistics for ", NumSamples, " generated numbers:"); Print ("Average value: ", mean); Print ("Standard deviation: ", stdDev); Print ("Minimum value: ", min); Print ("Maximum value: ", max); // Display the histogram Print ("Distribution histogram:"); for (int i = 0; i < 20; i++) { string bar = ""; for (int j = 0; j < histogram [i] * 50 / NumSamples; j++) bar += "*"; PrintFormat ("[%.1f, %.1f): %s", -4 + i * 0.4, -3.6 + i * 0.4, bar); } }
El script de verificación hará lo siguiente:
1. Definirá la función "NormalRandom()".
2. Generará el número especificado de números aleatorios (por defecto 10 000).
3. Calculará las características estadísticas básicas: media, desviación típica, valores mínimos y máximos.
4. Creará un histograma de distribución dividiendo el rango [-4, 4] en 20 intervalos.
5. Enviará los resultados al diario de registro del terminal MetaTrader.
Ahora probaremos el script, tomando 20 000 valores. Impresión del funcionamiento de script para verificar el método de transformación Box-Muller:
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) Estadísticas para 20000 números generados:
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) Valor medio: -0.003037802901958332
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) Desviación estándar: 0.9977477093538349
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) Valor mínimo: -3.865371560675546
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) Valor máximo: 3.4797509297243994
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) Histograma de distribución:
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) [-4.0, -3.6):
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) [-3.6, -3.2):
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) [-3.2, -2.8):
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) [-2.8, -2.4):
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) [-2.4, -2.0):
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) [-2.0, -1.6): *
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) [-1.6, -1.2): **
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) [-1.2, -0.8): ****
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) [-0.8, -0.4): ******
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) [-0.4, 0.0): *******
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) [0.0, 0.4): *******
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) [0.4, 0.8): ******
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) [0.8, 1.2): ****
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) [1.2, 1.6): ***
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) [1.6, 2.0): *
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) [2.0, 2.4):
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) [2.4, 2.8):
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) [2.8, 3.2):
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) [3.2, 3.6):
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) [3.6, 4.0):
Por la impresión podemos ver que el método funciona correctamente, la desviación estándar es casi igual a 1, el valor medio es 0, la dispersión se corresponde con el intervalo [-4;4].
A continuación, calcularemos los parámetros de mutación adaptativa y escribiremos la función:
//—————————————————————————————————————————————————————————————————————————————— void AdaptiveMutation (double &Cg, double &Cs, double &Cn) { Cg *= MathExp (tau_prime * NormalRandom() + tau * NormalRandom()); Cs *= MathExp (tau_prime * NormalRandom() + tau * NormalRandom()); Cn *= MathExp (tau_prime * NormalRandom() + tau * NormalRandom()); } //——————————————————————————————————————————————————————————————————————————————
Los parámetros adaptativos Cg, Cs y Cn se calcularán usando una estrategia de mutación autoadaptativa basada en el concepto propuesto por Schwefel. Fórmulas para calcular estos parámetros:
1. Inicialización:
- Tenemos una población de N soluciones donde cada solución se representará como un par de vectores (pi, σi), donde i ∈ {0, 1, 2} se corresponderá con tres parámetros Cg, Cs y Cn.
- Los valores iniciales de los componentes pi se elegirán aleatoriamente según una distribución uniforme en el supuesto espacio de soluciones.
- Los valores iniciales de σi se establecerán en algún valor fijo.
2. Generación de descendientes:
- Para cada progenitor (pi, σi), se generará un descendiente (pi', σi') usando las fórmulas siguientes:
σ'i (j) = σi (j) * exp (τ' * N (0,1) + τ * Nj (0,1))
p'i (j) = pi (j) + σi (j) * N (0,1)
Donde pi (j), p'i (j), σi (j), σ'i (j) serán el j-ésimo componente de los vextores pi, p'i, σi, σ'i, respectivamente.
- N (0,1) será un número aleatorio extraído de una distribución normal con media 0 y desviación típica 1.
- Nj (0,1) será un número aleatorio extraído de una distribución normal con media 0 y desviación típica 1, donde j será el contador.
- τ y τ' serán factores de escala fijados en (√(2√n))^-1 y (√(2n))^-1 respectivamente, donde n será la dimensionalidad del problema.
Así, los parámetros adaptativos Cg, Cs y Cnmutarán según esta estrategia autoadaptativa, lo cual les permitirá ajustarse dinámicamente durante el proceso de optimización.
Como podemos ver a continuación en la impresión de la obtención de los valores de los coeficientes Cg, Cs y Cn, en algunos casos individuales estos coeficientes tomarán valores demasiado elevados. Esto se debe a que en la ecuación de actualización de los parámetros estratégicos σ, el nuevo valor se obtiene multiplicando por el valor anterior. Esto permitirá adaptar los parámetros a la superficie compleja de la función objetivo, pero también podría provocar inestabilidad y saltos bruscos en los valores.
Veamos qué valores toman Cg, Cs y Cn:
1.3300705071425474 0.0019262948596068497 0.00015329292900983978
1.9508542383574192 0.00014860860614036015 7007.656113084095
52.13323602205895 1167.5425054524449 0.0008421503214593158
1.0183156975753507 0.13486291437434697 0.18290674582014257
0.00003239513683361894 61.366694225534175 45.11710564761292
0.0004785111900713668 0.4798661298436033 0.007932035893070274
2712198854.6325 0.00003936758705232012 325.9282730206205
0.0016658142882911 22123.863582276706 1.6844067196638965
2.0422888701555126 0.007999762224016285 0.02460292446531715
7192.66545492709 0.000007671729921045711 0.3705939923291289
0.0073279981653727785 3237957.2544339723 1.6750241266497745e-07
550.7433921368721 13.512466529311943 223762.44571145615
34.571961515974785 0.000008292503593679501 0.008122937723317175
0.000002128739177639208 63.17654973794633 128927.83801094144
866.7293481660888 1260.0820389718326 1.8496629497788273
0.000008459817609364248 25.623751292511788 0.0013478840638847347
27.956286711833616 0.0006967869388129299 0.0006885039945210606
66.6928872126239 47449.76869262452 8934.079392419668
0.15058617433681198 0.003114981958516487 7.703748428996011e-07
0.22147618633450808 265.4903003920267 315.20318731505455
0.0000015873778483580056 1134.6304274682934 0.7883024873065534
Cuando los parámetros Cg, Cs y Cn adquieren valores muy grandes como resultado de la mutación Schwefel autoadaptativa, podría ser necesario tomar medidas para controlar y limitar estos valores. Esto será importante para mantener la estabilidad y eficacia del algoritmo. Existen varios enfoques posibles para limitar los valores numéricos de los coeficientes:
1. Limitación de valores.
Establecer los límites superior e inferior de Cg, Cs y Cn. Por ejemplo:
void LimitParameters (double ¶m, double minValue, double maxValue) { param = MathMax (minValue, MathMin (param, maxValue)); } // Usage: LimitParameters (Cg, 0.0, 2.0); LimitParameters (Cs, 0.0, 2.0); LimitParameters (Cn, 0.0, 2.0);
2. Normalización.
Normalizar los valores de los parámetros después de la mutación para que su suma sea siempre igual a 1:
void NormalizeParameters (double &Cg, double &Cs, double &Cn) { double sum = Cg + Cs + Cn; if (sum > 0) { Cg /= sum; Cs /= sum; Cn /= sum; } else { // If sum is 0, set equal values Cg = Cs = Cn = 1.0 / 3.0; } }
3. Escala logarítmica.
Aplicar una escala logarítmica para suavizar los valores grandes:
double ScaleParameter (double param) { if (param == 0) return 0; double sign = (param > 0) ? 1 : -1; return sign * MathLog (1 + MathAbs (param)); }
4. Escala adaptativa del paso de mutación.
Reducir el tamaño del paso de mutación si los parámetros son demasiado grandes:
void AdaptMutationStep(double &stepSize, double paramValue) { if(MathAbs(paramValue) > threshold) { stepSize *= 0.9; // Reduce the step size } }
5. Reseteo periódico.
Restablecer periódicamente los parámetros a sus valores iniciales o a la media de la población:
void ResetParameters(int generationCount) { if(generationCount % resetInterval == 0) { Cg = initialCg; Cs = initialCs; Cn = initialCn; } }
6. Utilizar la función exponencial.
Aplicar la función exponencial para limitar el crecimiento de los parámetros:
double LimitGrowth(double param) { return 2.0 / (1.0 + MathExp(-param)) - 1.0; // Limit values in [-1, 1] }
7. Monitoreo y adaptación.
Supervisar los valores de los parámetros y adaptar la estrategia de mutación si se salen con frecuencia de la tolerancia:
void MonitorAndAdapt(double ¶m, int &outOfBoundsCount) { if(MathAbs(param) > maxAllowedValue) { outOfBoundsCount++; if(outOfBoundsCount > threshold) { // Adaptation of the mutation strategy AdjustMutationStrategy(); } } }
También deberemos recordar que restringir demasiado los parámetros podría reducir la capacidad del algoritmo para explorar el espacio de soluciones, por lo que será necesario encontrar el equilibrio adecuado entre control y flexibilidad. Los entusiastas de la optimización podrán aplicar estos métodos a investigaciones posteriores, aunque yo he utilizado mi función "GaussDistribution" descrita en este artículo.
//—————————————————————————————————————————————————————————————————————————————— void C_AO_ASBO::AdaptiveMutation (S_ASBO_Agent &ag) { ag.Cg *= MathExp (tau_prime * u.GaussDistribution (0, -1, 1, 1) + tau * u.GaussDistribution (0, -1, 1, 8)); ag.Cs *= MathExp (tau_prime * u.GaussDistribution (0, -1, 1, 1) + tau * u.GaussDistribution (0, -1, 1, 8)); ag.Cn *= MathExp (tau_prime * u.GaussDistribution (0, -1, 1, 1) + tau * u.GaussDistribution (0, -1, 1, 8)); } //——————————————————————————————————————————————————————————————————————————————
Como podemos ver a continuación en la impresión de la obtención de los valores de los coeficientes Cg, Cs y Cn, los valores elevados son mucho menos comunes tras aplicar mi función, en comparación con el método Box-Muller.
0.025582051880112085 0.6207719272290446 0.005335225840354781
0.9810075068811726 0.16583946164135704 0.01016969794039794
0.006133031813953609 17.700790930206647 0.3745475117676483
1.4547663270710334 0.3537259667123157 0.08834618264409702
0.11125695415944291 0.28183794982955684 0.09051405673590024
0.06340035225180855 0.16270375413207716 0.36885953030567936
0.008575136469231127 2.5881627332149053 0.11237602809318312
0.00001436227841400286 0.02323530434501054 10.360403964016376
0.936476760121053 0.017321731852758693 0.40372788912091845
0.009288586536835293 0.0000072639468670123115 15.463139841665908
0.15092489031689466 0.02160456749606 0.011008504295160867
0.0100807047494077 0.4592091893288436 0.0343285901385665
0.010014652012224212 0.0014577046664934706 0.006484475820059919
0.0002654495048564554 0.0005018788250576451 1.8639207859646574
5.972802450172414 0.10070170017416721 0.9226557559293936
0.011441827382547332 14.599641192191408 0.00007257778906744059
0.7249805357484554 0.000004301248511125035 0.2718776654314797
5.019113547774523 0.11351424171113386 0.02129848352762841
0.023978285994614518 0.041738711812672386 1.0247944259605422
0.0036842456260203237 12.869472963773408 1.5167205157941646
0.4529181577133935 0.0000625576761842319 30.751931508050227
0.5555092369559338 0.9606330180338433 0.39426099685543164
0.036106836251057275 2.57811344513881 0.042016638784243526
3.502119772985753 128.0263928713568 0.9925745499516576
279.2236061102195 0.6837013166327449 0.01615639677602729
0.09687457825904996 0.3812813151133578 0.5272720937749686
Ahora que hemos analizado el concepto de Schwefel, así como los valores adaptativos de los coeficientes, pasaremos al método de determinación de los vecinos más próximos en el algoritmo. Para determinar las coordenadas de los vecinos más próximos de "Nc" se utilizará el método siguiente:
1. Para cada individuo de una población, determinaremos sus vecinos más próximos.
2. Los vecinos más próximos se definirán como los tres individuos con los valores de la función objetivo (aptitud) más próximos a un individuo dado.
3. Las coordenadas del centro del grupo formado por este individuo y sus vecinos más próximos se calcularán como la media aritmética de las coordenadas de estos tres vecinos.
Así, las coordenadas de "Nc" no se tomarán simplemente como tres coordenadas arbitrarias, sino que se calcularán como el centro del grupo de vecinos más próximo según el valor de la función objetivo. Esto permitirá utilizar la información sobre el entorno inmediato de un individuo para determinar su próxima posición.
Lo más importante es que los vecinos más próximos no estén determinados por la proximidad geográfica, sino por la proximidad de los valores de la función objetivo. Esto se corresponderá con una proximidad lógica, no geográfica, en la estructura social.
//—————————————————————————————————————————————————————————————————————————————— void C_AO_ASBO::FindNeighborCenter (const S_ASBO_Agent &ag, double ¢er []) { // Create arrays for indices and fitness differences int indices []; double differences []; ArrayResize (indices, popSize - 1); ArrayResize (differences, popSize - 1); // Fill the arrays int count = 0; for (int i = 0; i < popSize; i++) { if (&a [i] != &ag) // Exclude the current agent { indices [count] = i; differences [count] = MathAbs (a [i].fitness - ag.fitness); count++; } } // Sort arrays by fitness difference (bubble sort) for (int i = 0; i < count - 1; i++) { for (int j = 0; j < count - i - 1; j++) { if (differences [j] > differences [j + 1]) { // Exchange of differences double tempDiff = differences [j]; differences [j] = differences [j + 1]; differences [j + 1] = tempDiff; // Exchange of indices int tempIndex = indices [j]; indices [j] = indices [j + 1]; indices [j + 1] = tempIndex; } } } // Initialize the center ArrayInitialize (center, 0.0); // Calculate the center based on the three nearest neighbors for (int j = 0; j < coords; j++) { for (int k = 0; k < 3; k++) { int nearestIndex = indices [k]; center [j] += a [nearestIndex].c [j]; } center [j] /= 3; } } //——————————————————————————————————————————————————————————————————————————————
Explicación del método:
- Determina los vecinos más cercanos basándose en la proximidad de los valores de la función objetivo (aptitud).
- Usa los tres vecinos más próximos.
- Calcula el centro del grupo como la media aritmética de las coordenadas de estos tres vecinos.
Vamos a analizar esta función:
1. Dirección de la clasificación:
La clasificación se realizará según la diferencia de aptitud creciente.
Si el elemento actual es mayor que el siguiente, se intercambiarán. Así, tras la clasificación, el array "differences" se ordenará de menor a mayor valor, mientras que los índices correspondientes del array "indices" reflejarán esta clasificación.
2. La función realiza los siguientes pasos:
- Excluye al agente actual de la consideración.
- Calcula la diferencia de aptitud entre el agente actual y todos los demás.
- Clasifica a los agentes según esa diferencia.
- Selecciona los tres vecinos más próximos (con la menor diferencia de aptitud)
- Calcula el centro del grupo partiendo de las coordenadas de estos tres vecinos
void C_AO_ASBO::FindNeighborCenter(int ind, S_ASBO_Agent &ag[], double ¢er[]) { int indices[]; double differences[]; ArrayResize(indices, popSize - 1); ArrayResize(differences, popSize - 1); int count = 0; for (int i = 0; i < popSize; i++) { if (i != ind) { indices[count] = i; differences[count] = MathAbs(ag[ind].f - ag[i].f); count++; } } // Sort by fitness difference ascending for (int i = 0; i < count - 1; i++) { for (int j = 0; j < count - i - 1; j++) { if (differences[j] > differences[j + 1]) { double tempDiff = differences[j]; differences[j] = differences[j + 1]; differences[j + 1] = tempDiff; int tempIndex = indices[j]; indices[j] = indices[j + 1]; indices[j + 1] = tempIndex; } } } ArrayInitialize(center, 0.0); int neighborsCount = MathMin(3, count); // Protection against the case when there are less than 3 agents for (int j = 0; j < coords; j++) { for (int k = 0; k < neighborsCount; k++) { int nearestIndex = indices[k]; center[j] += ag[nearestIndex].c[j]; } center[j] /= neighborsCount; } }
Esta versión de la función será más robusta a los errores y procesará correctamente los casos con un número pequeño (menos de 3) de agentes en la población. Ya nos hemos familiarizado con los métodos lógicos básicos de un algoritmo, ahora podemos pasar a ver la estructura del propio algoritmo. El algoritmo ASBO consta de los siguientes pasos básicos:
1. Inicialización:
- Se define una población inicial de soluciones, donde cada solución se representa explícitamente (no en formato codificado).
- Para cada solución, se calcula un valor de función objetivo que determina su idoneidad.
- La solución con el máximo valor de aptitud se declarará líder global en ese momento.
- Para cada solución, se determina el grupo de vecinos más próximos con los siguientes valores de aptitud más altos.
2. Mutación adaptativa de los parámetros:
- Para cada solución, se define un vector de tres parámetros adaptativos: Cg, Cs y Cn serán responsables de la influencia del líder global, la mejor decisión personal y el centro del grupo de vecinos, respectivamente.
- Se aplica una estrategia de mutación Schweffel autoadaptativa para actualizar los valores de estos parámetros.
3. Actualización de la posición de las decisiones:
- Para cada solución, se calcula el cambio de su posición usando los valores actuales de los parámetros Cg, Cs y Cn.
- La nueva posición de la solución se calcula sumando el cambio a la posición actual.
4. Proceso en dos fases:
- Fase 1: Se crean M poblaciones independientes, cada una de ellas es procesada por el algoritmo ASBO durante un número fijo de iteraciones. Se conservan los valores de aptitud y de los parámetros de cada solución de las poblaciones finales.
- Fase 2: De todas las poblaciones finales, se seleccionan las soluciones más aptas y sus parámetros se usan para formar una nueva población. La lógica básica del algoritmo ASBO se aplica sobre la nueva población resultante para obtener la solución final.
Así pues, vamos a destacar una vez más las características clave del algoritmo ASBO:
- Aplicación de una estrategia de mutación autoadaptativa para la actualización dinámica de parámetros.
- Uso de los conceptos de liderazgo e influencia vecinal para modelar el comportamiento social.
- Proceso en dos fases para mantener la diversidad de soluciones y acelerar la convergencia.
En el presente artículo, hemos analizado un ejemplo del concepto de Schwefel, el método Box-Muller que incluye una distribución normal, la aplicación de coeficientes de mutación autoadaptativos y la función de determinación de los vecinos más próximos según su valor de aptitud. Hoy hemos abordado la estructura de ASBO; en el próximo artículo profundizaremos en el proceso de evolución artificial en dos fases, finalizaremos el algoritmo, lo probaremos con funciones de prueba y sacaremos conclusiones sobre su eficacia.
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/15283
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.





- 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