Algoritmo de optimización de neuroboides 2 — Neuroboids Optimization Algorithm 2 (NOA2)
Contenido
Introducción
Al sumergirme cada vez más en los algoritmos de optimización a lo largo de los años, con frecuencia me atraían dos inspiraciones paralelas, como la autoorganización de los enjambres biológicos y la capacidad de aprendizaje adaptativo de las redes neuronales. La síntesis de estos dos paradigmas me llevó a desarrollar un algoritmo de enfoque híbrido que combina la inteligencia espacial de los Boids de Craig Reynolds con la capacidad de aprendizaje adaptativo que poseen las redes neuronales.
Mi viaje comenzó con la observación de que los algoritmos de enjambre tradicionales, aunque adecuados para explorar espacios de búsqueda complejos a través de un comportamiento colectivo, a menudo carecen de la capacidad de aprender de la historia de sus exploraciones. Y al contrario, las redes neuronales son excelentes en el aprendizaje de patrones complejos, pero pueden tener dificultades con la exploración espacial eficiente cuando se aplican directamente a problemas de optimización. La pregunta que impulsó mi investigación resultaba aparentemente sencilla: ¿y si cada agente del enjambre pudiera aprender a mejorar sus estrategias de movimiento usando una red neuronal específica?
El algoritmo resultante utiliza agentes que siguen las reglas boid clásicas de cohesión, separación y alineación, lo cual les permite autoorganizarse y explorar el espacio de búsqueda con eficacia. No obstante, a diferencia de las implementaciones boid tradicionales, cada agente está equipado con una red neuronal multicapa que aprende continuamente de la experiencia del agente adaptando sus estrategias de movimiento a las características específicas del paisaje de adaptabilidad. Este nivel de control neuronal influye gradualmente en el comportamiento de los boids, pasando de estrategias dominadas por la exploración en las primeras iteraciones a movimientos impulsados por la explotación a medida que se identifican regiones prometedoras.
Lo que más me fascinó durante el desarrollo fue observar cómo las redes neuronales evolucionaban con estrategias distintas según su posición en el espacio de búsqueda. Los agentes situados cerca de zonas prometedoras desarrollaron patrones neuronales que potenciaron la explotación local, mientras que los situados en zonas poco pobladas favorecieron el comportamiento exploratorio. Dicha especialización emergente surgió de forma natural a partir de procesos de aprendizaje individuales, creando un enjambre con un comportamiento heterogéneo y dependiente del contexto, sin coordinación global aparente.
En este artículo, presentaremos la arquitectura, los detalles de implementación y el análisis de rendimiento del algoritmo NOA2, así como una demostración de sus capacidades en varios parámetros de referencia.
Implementación del algoritmo
Puede que me esté repitiendo, la idea básica del algoritmo de neuroboids consiste en combinar dos paradigmas: la inteligencia colectiva de los algoritmos de enjambre y el aprendizaje adaptativo de las redes neuronales.
En el algoritmo tradicional de boids que propuso Craig Reynolds, los agentes siguen tres reglas sencillas: convergencia (moverse hacia el centro del grupo), separación (evitar colisiones) y alineación (igualar la velocidad con los vecinos). Estas reglas crean un comportamiento de grupo realista, similar al de los pájaros en bandada. Los neuroboids amplían este concepto dotando a cada agente de una red neuronal individual que aprende de la experiencia del agente conforme explora el espacio de búsqueda. Esta red neuronal realiza dos funciones clave:
- Control de movimiento adaptativo: ajusta la velocidad del agente según el estado actual y la historia de movimientos.
- Modificación de las reglas estándar de boids: ajusta dinámicamente el impacto de las reglas de convergencia, separación y alineación en función del contexto.
El resultado es un algoritmo híbrido en el que cada agente mantiene el comportamiento social necesario para explorar el espacio de forma eficiente, pero se adapta individualmente al paisaje de la función de aptitud mediante el aprendizaje. Esto crea un equilibrio autorregulado entre exploración y explotación.
Las principales ventajas de este enfoque son que los agentes aprenden de manera autónoma estrategias de movimiento óptimas, como consecuencia de lo cual el algoritmo se adapta automáticamente a distintos tipos de paisajes de optimización, al tiempo que se preserva la exploración del espacio mediante un comportamiento colectivo sin control centralizado. Por poner una analogía sencilla: imagine una bandada de pájaros volando por el cielo. Se mueven con coherencia: ninguno choca, se mantienen unidos y vuelan en la misma dirección. Este comportamiento puede describirse con tres sencillas reglas: permanecer cerca de los vecinos (no separarse de la bandada), no chocar con ellos (mantener la distancia) y volar en la misma dirección (mantener un rumbo común).
Esta es la base del algoritmo llamado "boids" (de "bird-oid", "semejante a un pájaro"). Cada ave de la bandada no se limita a seguir estas reglas, sino que es capaz de aprender de sus experiencias. El pájaro recuerda qué acciones le llevaron al éxito (por ejemplo, encontrar más comida) y cuáles de ellas no. Con el tiempo, se vuelve más inteligente y toma mejores decisiones sobre cómo volar. Esta es la esencia del algoritmo de neuroboids: combinar las sencillas reglas del movimiento en grupo con la capacidad de cada participante para aprender de su propia experiencia. El "neuro" del nombre se refiere exactamente a la capacidad de aprender. Este enfoque resulta especialmente interesante porque combina el poder de la investigación colaborativa (nadie se pierde un área importante) con los beneficios del aprendizaje individualizado (cada uno se convierte en el mejor en su área de búsqueda).

Figura 1. Ilustración del funcionamiento del algoritmo NOA2
La ilustración muestra los siguientes elementos clave: el paisaje de optimización, en el que la región amarilla y morada representa el óptimo global, las regiones naranja y morada representan los óptimos locales, y las curvas de nivel muestran la "altura" del paisaje de la función de aptitud.
Diferentes grupos de agentes: los agentes azules exploran la región del óptimo global, los agentes morados se concentran en torno al primer óptimo local, los agentes verdes exploran el segundo óptimo local y los agentes rojos exploran el espacio de forma aleatoria. Las flechas muestran la dirección de movimiento de los boids. El punto rojo marca la mejor solución encontrada actualmente.

Figura 2. Esquema de funcionamiento del algoritmo NOA2
El esquema incluye: un bloque de inicialización (rojo), una red neuronal de boid (rosa), iteraciones del algoritmo con subbloques (azul), mecanismos adaptativos (verde) y visualización de la arquitectura de la red neuronal con conexiones de ejemplo. La parte inferior muestra esquemas en miniatura en forma de estructura de red neuronal, la visualización de las reglas de movimiento de los boids y el entre investigación y explotación.
Una vez entendido el concepto básico, podemos empezar a describir el pseudocódigo del algoritmo.
INICIALIZACIÓN:
- Establecemos los parámetros de búsqueda y creamos una población de agentes.
- Para cada agente: inicializamos una posición aleatoria, una velocidad pequeña, una red neuronal (entrada → oculta → capas de salida) y una memoria de experiencia vacía.
- Establecemos la mejor adaptabilidad global en infinito negativo y el contador de estancamiento en cero.
CICLO GENERAL:
Para cada iteración:
EVALUACIÓN:
- Calculamos la adaptabilidad de todos los agentes.
- Actualizamos las primeras posiciones personales y globales.
- Guardamos los experimentos en los búferes de memoria.
GESTIÓN DEL ESTANCAMIENTO:
- Si no hay mejora: aumentamos la investigación, reiniciamos los agentes débiles de vez en cuando.
- En caso contrario: reducimos gradualmente el nivel de investigación.
PROCESAMIENTO NEURONAL:
- Para cada agente:
- Pasada directa: entradas (posición, velocidad, distancia al mejor, adaptabilidad) → capas ocultas → salidas.
- Si se acumula suficiente experiencia y se detecta una mejora: actualizamos los pesos neuronales.
ACTUALIZACIÓN DEL MOVIMIENTO:
- Para cada agente:
- Calculamos las fuerzas estándar de boids (cohesión, separación, nivelación).
- Aplicamos fuerzas modificadas por neuronas y correcciones neuronales directas.
- Añadimos un componente de investigación aleatoria con probabilidad.
- Hacemos cumplir los límites de velocidad y consideramos los límites.
- Actualizamos la posición según la velocidad final.
CÁLCULOS DE SOPORTE:
- Cálculo de la masa: normalizamos los valores de adaptabilidad para asignar una masa a cada agente.
- Cohesión: nos desplazamos hacia el centro de masa ponderado de los agentes más próximos.
- Separación: evitamos la aglomeración de vecinos.
- Alineación: coordinamos la velocidad con los agentes cercanos.
- Actualización neuronal: retropropagación simplificada basada en la mejora de la adaptabilidad.
RETORNO: mejor posición global y adaptabilidad.
Ahora todo está preparado para escribir el código del algoritmo. Asignaremos la estructura "S_NeuroBoids_Agent", que será un agente neuroboid basado en una arquitectura de red neuronal para realizar tareas de optimización. En esta implementación, el agente tendrá los siguientes componentes y funciones clave:
Coordenadas y velocidades:
- x [] — coordenadas actuales del agente.
- dx [] — velocidades actuales del agente.
- inputs [] — valores de entrada para las neuronas (coordenadas, velocidades, distancia a la mejor posición conocida, etc.).
- outputs [] — valores de salida de la red neuronal (corrección de velocidad y parámetros de adaptación).
- steps [] — pesos de los enlaces en la red neuronal.
- biases [] — sesgos para las neuronas.
- memory [] — array para almacenar las posiciones anteriores y sus valores de aptitud.
- memory_size — tamaño máximo de la memoria para el almacenamiento.
- memory_index — índice actual en la memoria.
- best_local_fitness — mejor agente local de aptitud.
- m — masa del agente.
- cB [] — coordenadas de la mejor posición encontrada por el agente.
- fB — valor de la función de aptitud para la mejor posición.
Inicialización: (Init) todas los arrays y variables se inicializan con ceros o valores aleatorios. Se fijan los tamaños de los arrays y se inicializan las influencias (pesos y desplazamientos) con pequeños valores aleatorios.
Funciones de activación — Tanh, ReLU, Sigmoid; diferentes funciones de activación que se aplican en la red neuronal.
Activación de los datos de entrada (UpdateInputs) — rellena un array con las coordenadas actuales, las velocidades, la distancia a la mejor posición conocida y el valor de aptitud actual.
Pasada directa (ForwardPass) — calcula las salidas de la red neuronal basándose en las entradas, los pesos y los desplazamientos aplicando funciones de activación.
Aprendizaje basado en la experiencia (Learn) — actualiza los pesos y desplazamientos según la experiencia si la experiencia actual es mejor que la anterior.
Memorización de la experiencia (MemorizeExperience) — guarda en la memoria las coordenadas actuales y la forma física del agente.
Actualización de la mejor posición (UpdateBestPosition) — si el valor de aptitud actual es mejor que el encontrado anteriormente, actualiza la mejor posición.
//—————————————————————————————————————————————————————————————————————————————— // Структура нейро-боидного агента //—————————————————————————————————————————————————————————————————————————————— struct S_NeuroBoids_Agent { double x []; // текущие координаты double dx []; // текущие скорости double inputs []; // входы нейрона double outputs []; // выходы нейрона double weights []; // веса нейрона double biases []; // смещения нейрона double memory []; // память предыдущих позиций и их фитнесов int memory_size; // размер памяти для накопления опыта int memory_index; // текущий индекс в памяти double best_local_fitness; // лучший локальный фитнес агента double m; // масса бойда double cB []; // координаты лучшей позиции double fB; // значение фитнес-функции // Инициализация агента void Init (int coords, int neuron_size) { ArrayResize (x, coords); ArrayResize (dx, coords); ArrayInitialize (x, 0.0); ArrayInitialize (dx, 0.0); // Инициализируем структуру лучшей позиции ArrayResize (cB, coords); ArrayInitialize (cB, 0.0); fB = -DBL_MAX; // Входы: координаты, скорости, расстояние до лучшего и т.д. int input_size = coords * 2 + 2; // x, dx, dist_to_best, current_fitness ArrayResize (inputs, input_size); ArrayInitialize (inputs, 0.0); // Выходы: коррекция скоростей и адаптивные факторы для правил стаи int output_size = coords + 3; // dx_correction + 3 адаптивных параметра ArrayResize (outputs, output_size); ArrayInitialize (outputs, 0.0); // Веса и смещения для каждого выхода ArrayResize (weights, input_size * output_size); ArrayInitialize (weights, 0.0); ArrayResize (biases, output_size); ArrayInitialize (biases, 0.0); // Инициализация весов и смещений малыми случайными значениями for (int i = 0; i < ArraySize (weights); i++) { weights [i] = 0.01 * (MathRand () / 32767.0 * 2.0 - 1.0); } for (int i = 0; i < ArraySize (biases); i++) { biases [i] = 0.01 * (MathRand () / 32767.0 * 2.0 - 1.0); } // Инициализация памяти для накопления опыта memory_size = 10; ArrayResize (memory, memory_size * (coords + 1)); // координаты + фитнес ArrayInitialize (memory, 0.0); memory_index = 0; best_local_fitness = -DBL_MAX; // Инициализация массы m = 0.5; } // Функция активации - гиперболический тангенс double Tanh (double input_val) { return MathTanh (input_val); } // Функция активации ReLU double ReLU (double input_val) { return (input_val > 0.0) ? input_val : 0.0; } // Функция активации Sigmoid double Sigmoid (double input_val) { return 1.0 / (1.0 + MathExp (-input_val)); } // Обновление входов нейронной сети - исправленная версия void UpdateInputs (double &global_best [], double current_fitness, int coords_count) { int input_index = 0; // Координаты for (int c = 0; c < coords_count; c++) { inputs [input_index++] = x [c]; } // Скорости for (int c = 0; c < coords_count; c++) { inputs [input_index++] = dx [c]; } // Расстояние до лучшего глобального решения double dist_to_best = 0; for (int c = 0; c < coords_count; c++) { dist_to_best += MathPow (x [c] - global_best [c], 2); } dist_to_best = MathSqrt (dist_to_best); inputs [input_index++] = dist_to_best; // Текущая фитнес-функция inputs [input_index++] = current_fitness; } // Прямое распространение по сети void ForwardPass (int coords_count) { int input_size = ArraySize (inputs); int output_size = ArraySize (outputs); // Для каждого выхода вычисляем взвешенную сумму входов + смещение for (int o = 0; o < output_size; o++) { double sum = biases [o]; for (int i = 0; i < input_size; i++) { sum += inputs [i] * weights [o * input_size + i]; } // Применяем разные функции активации в зависимости от выхода if (o < coords_count) // Для коррекции скорости используем гиперболический тангенс { outputs [o] = Tanh (sum); } else // Для адаптивных параметров используем сигмоиду { outputs [o] = Sigmoid (sum); } } } // Обучение на основе накопленного опыта void Learn (double learning_rate, int coords_count) { if (memory_index < memory_size) return; // Недостаточно опыта // Находим лучший опыт в памяти int best_index = 0; double best_fitness = memory [coords_count]; // Первая фитнес-функция в памяти for (int i = 1; i < memory_size; i++) { double fitness = memory [i * (coords_count + 1) + coords_count]; if (fitness > best_fitness) { best_fitness = fitness; best_index = i; } } // Если текущий опыт не лучше предыдущего, не обновляем веса if (best_fitness <= best_local_fitness) return; best_local_fitness = best_fitness; // Простой метод обновления весов int input_size = ArraySize (inputs); int output_size = ArraySize (outputs); // Простая форма градиентного обновления for (int o = 0; o < output_size; o++) { for (int i = 0; i < input_size; i++) { int weight_index = o * input_size + i; if (weight_index < ArraySize (weights)) { weights [weight_index] += learning_rate * outputs [o] * inputs [i]; } } // Обновляем смещения biases [o] += learning_rate * outputs [o]; } } // Запоминаем текущий опыт (координаты и фитнес) void MemorizeExperience (double fitness, int coords_count) { int offset = memory_index * (coords_count + 1); // Запоминаем координаты for (int c = 0; c < coords_count; c++) { memory [offset + c] = x [c]; } // Запоминаем фитнес-функцию memory [offset + coords_count] = fitness; // Обновляем индекс памяти memory_index = (memory_index + 1) % memory_size; } // Обновление лучшей позиции агента void UpdateBestPosition (double current_fitness, int coords_count) { if (current_fitness > fB) { fB = current_fitness; ArrayCopy (cB, x, 0, 0, WHOLE_ARRAY); } } };
La clase "C_AO_NOA2" es una implementación del algoritmo NOA2 y se hereda de la clase básica "C_AO". Veamos más de cerca la estructura y los aspectos clave de esta clase.
Parámetros clave:- popSize — tamaño de la población de agentes (boids).
- cohesionWeight, cohesionDist — peso y distancia de la regla de cohesión.
- separationWeight, separationDist — peso y distancia de la regla de separación.
- alignmentWeight, alignmentDist — peso y distancia de la regla de alineación.
- maxSpeed y minSpeed — velocidades máxima y mínima de los agentes.
- learningRate — velocidad de aprendizaje de la red neuronal.
- neuralInfluence — influencia de la red neuronal en el movimiento de los agentes.
- ExplorationRate — probabilidad de exploración aleatoria del espacio de soluciones.
- m_stagnationCounter y m_prevBestFitness — contador de estancamiento y valor anterior de mejor valor de aptitud.
- SetParams () — método que establece los parámetros del algoritmo basándose en los valores almacenados en el array "params".
- Init () — inicialización, acepta los parámetros para definir los rangos de valores para los agentes. Este método establece las condiciones iniciales para que se ejecute el algoritmo.
- Moving () — se encarga del movimiento de los agentes usando como base varias reglas de interacción.
- Revision () — se utiliza para revisar el estado de los agentes durante la ejecución del algoritmo.
Agentes: S_NeuroBoids_Agent agent [] — array de agentes que representan los boids en la población.
Métodos privados (para el uso dentro de una clase):
- CalculateMass () — calcula la masa de los agentes.
- Cohesión () — implementa la regla de convergencia.
- Separation () — implementa la regla de separación.
- Alignment () — implementa la regla de alineación.
- LimitSpeed () — limita la velocidad del agente.
- KeepWithinBounds () — mantiene a los agentes dentro de los límites dados.
- Distance () — calcula la distancia entre dos agentes.
- ApplyNeuralControl () — aplica el control de la red neuronal al agente.
- AdaptiveExploration() — implementa la exploración adaptativa.
//—————————————————————————————————————————————————————————————————————————————— // Класс нейро-боидного алгоритма оптимизации, наследуемый от C_AO //—————————————————————————————————————————————————————————————————————————————— class C_AO_NOA2 : public C_AO { public: //-------------------------------------------------------------------- ~C_AO_NOA2 () { } C_AO_NOA2 () { ao_name = "NOA2"; ao_desc = "Neuroboids Optimization Algorithm 2 (joo)"; ao_link = "https://www.mql5.com/es/articles/17497"; popSize = 50; // размер популяции cohesionWeight = 0.6; // вес сближения cohesionDist = 0.001; // дистанция сближения separationWeight = 0.005; // вес разделения separationDist = 0.03; // дистанция разделения alignmentWeight = 0.1; // вес выравнивания alignmentDist = 0.1; // дистанция выравнивания maxSpeed = 0.001; // максимальная скорость minSpeed = 0.0001; // минимальная скорость learningRate = 0.01; // скорость обучения нейронной сети neuralInfluence = 0.3; // влияние нейронной сети на движение explorationRate = 0.1; // вероятность случайного исследования ArrayResize (params, 12); params [0].name = "popSize"; params [0].val = popSize; params [1].name = "cohesionWeight"; params [1].val = cohesionWeight; params [2].name = "cohesionDist"; params [2].val = cohesionDist; params [3].name = "separationWeight"; params [3].val = separationWeight; params [4].name = "separationDist"; params [4].val = separationDist; params [5].name = "alignmentWeight"; params [5].val = alignmentWeight; params [6].name = "alignmentDist"; params [6].val = alignmentDist; params [7].name = "maxSpeed"; params [7].val = maxSpeed; params [8].name = "minSpeed"; params [8].val = minSpeed; params [9].name = "learningRate"; params [9].val = learningRate; params [10].name = "neuralInfluence"; params [10].val = neuralInfluence; params [11].name = "explorationRate"; params [11].val = explorationRate; // Инициализация счетчика стагнации и предыдущего лучшего значения фитнеса m_stagnationCounter = 0; m_prevBestFitness = -DBL_MAX; } void SetParams () { popSize = (int)params [0].val; cohesionWeight = params [1].val; cohesionDist = params [2].val; separationWeight = params [3].val; separationDist = params [4].val; alignmentWeight = params [5].val; alignmentDist = params [6].val; maxSpeed = params [7].val; minSpeed = params [8].val; learningRate = params [9].val; neuralInfluence = params [10].val; explorationRate = params [11].val; } bool Init (const double &rangeMinP [], const double &rangeMaxP [], const double &rangeStepP [], const int epochsP = 0); void Moving (); void Revision (); //---------------------------------------------------------------------------- double cohesionWeight; // вес сближения double cohesionDist; // дистанция сближения double separationWeight; // вес разделения double separationDist; // дистанция разделения double alignmentWeight; // вес выравнивания double alignmentDist; // дистанция выравнивания double minSpeed; // минимальная скорость double maxSpeed; // максимальная скорость double learningRate; // скорость обучения нейронной сети double neuralInfluence; // влияние нейронной сети на движение double explorationRate; // вероятность случайного исследования int m_stagnationCounter; // счетчик стагнации double m_prevBestFitness; // предыдущее лучшее значение фитнеса S_NeuroBoids_Agent agent []; // агенты (боиды) private: //------------------------------------------------------------------- double distanceMax; // максимальная дистанция double speedMax []; // максимальные скорости по измерениям int neuron_size; // размер нейрона (количество входов) void CalculateMass (); // расчет масс агентов void Cohesion (S_NeuroBoids_Agent &boid, int pos); // правило сближения void Separation (S_NeuroBoids_Agent &boid, int pos); // правило разделения void Alignment (S_NeuroBoids_Agent &boid, int pos); // правило выравнивания void LimitSpeed (S_NeuroBoids_Agent &boid); // ограничение скорости void KeepWithinBounds (S_NeuroBoids_Agent &boid); // удержание в границах double Distance (S_NeuroBoids_Agent &boid1, S_NeuroBoids_Agent &boid2); // расчет дистанции void ApplyNeuralControl (S_NeuroBoids_Agent &boid, int pos); // применение нейронного управления void AdaptiveExploration (); // адаптивное исследование }; //——————————————————————————————————————————————————————————————————————————————
El método "Init" se encarga de inicializar el algoritmo y los parámetros del agente e inicia su trabajo con la llamada "StandardInit", transmitiéndole los arrays de valores mínimo y máximo del rango y los pasos de búsqueda. Si falla la inicialización estándar, el método retornará inmediatamente "false". Luego se establecerá el tamaño de la neurona que se utilizará en la red neuronal agente.
En este caso, el tamaño se calculará como el número de coordenadas multiplicado por 2, y se añadirán dos valores adicionales: "dist_to_best" y "current_fitness". El tamaño del array de agentes se redimensionará según el tamaño de población especificado "popSize". A continuación, para cada agente, se llamará al método "Init ", que inicializará sus parámetros, incluida la red neuronal.
Cálculo de la distancia y la velocidad máximas:
- La variable "distanceMax" se inicializará con cero.
- Para cada coordenada, el valor máximo de velocidad se calculará partiendo de la diferencia entre los valores máximo y mínimo del intervalo.
- La distancia máxima también se calculará como la raíz cuadrada de la suma de los cuadrados de las velocidades máximas.
El contador de estancamiento (m_stagnationCounter) se pondrá a cero, y la variable que almacenará el valor anterior de mejor forma física (m_prevBestFitness) se establecerá en el valor más bajo posible. El método establecerá diversas variables globales, como la convergencia, los pesos de separación y alineación, las velocidades máxima y mínima, la tasa de aprendizaje, la influencia de la red neuronal y la probabilidad de exploración. Si todos los pasos se han realizado correctamente, el método retornará "true", lo cual confirmará que la inicialización se ha realizado correctamente.
//—————————————————————————————————————————————————————————————————————————————— bool C_AO_NOA2::Init (const double &rangeMinP [], // минимальный диапазон поиска const double &rangeMaxP [], // максимальный диапазон поиска const double &rangeStepP [], // шаг поиска const int epochsP) // количество эпох { if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false; //---------------------------------------------------------------------------- // Определение размера нейрона neuron_size = coords * 2 + 2; // x, dx, dist_to_best, current_fitness // Инициализация агентов ArrayResize (agent, popSize); for (int i = 0; i < popSize; i++) { agent [i].Init (coords, neuron_size); } distanceMax = 0; ArrayResize (speedMax, coords); for (int c = 0; c < coords; c++) { speedMax [c] = rangeMax [c] - rangeMin [c]; distanceMax += MathPow (speedMax [c], 2); } distanceMax = MathSqrt (distanceMax); // Сброс счетчика стагнации и предыдущего лучшего значения фитнеса m_stagnationCounter = 0; m_prevBestFitness = -DBL_MAX; GlobalVariableSet ("#reset", 1.0); GlobalVariableSet ("1cohesionWeight", params [1].val); GlobalVariableSet ("2cohesionDist", params [2].val); GlobalVariableSet ("3separationWeight", params [3].val); GlobalVariableSet ("4separationDist", params [4].val); GlobalVariableSet ("5alignmentWeight", params [5].val); GlobalVariableSet ("6alignmentDist", params [6].val); GlobalVariableSet ("7maxSpeed", params [7].val); GlobalVariableSet ("8minSpeed", params [8].val); GlobalVariableSet ("9learningRate", params [9].val); GlobalVariableSet ("10neuralInfluence", params [10].val); GlobalVariableSet ("11explorationRate", params [11].val); return true; } //——————————————————————————————————————————————————————————————————————————————
El método Moving se encargará de desplazar a los agentes en el entorno según los datos obtenidos y las interacciones entre ellos. En primer lugar, el método comprobará el valor de la variable global "#reset", si es igual a 1,0, se restablecerán los parámetros (la variable "revision" se pondrá en "false" y luego volverá a "0,0"). De las variables globales se extraerán los valores de diversos parámetros, como la convergencia, los pesos de separación y alineación, las velocidades mínima y máxima, la tasa de aprendizaje, la influencia de la red neuronal y la relación de exploración. Estos parámetros pueden configurarse para modificar el comportamiento de los agentes.
Si la variable "revisión" es "false", se inicializarán las posiciones "x" y velocidades "dx" de los agentes. Luego se aplicarán valores aleatorios de un rango determinado a cada coordenada, y las velocidades se fijarán en pequeños valores aleatorios. Cada coordenada almacenará igualmente el estado del agente según su posición actual. El método "AdaptiveExploration ()" se llamará para la exploración adaptativa, que puede cambiar el comportamiento de los agentes según el estado de estancamiento.
Ciclo de movimiento básico de los agentes (para cada uno):
- Primero se guardará la experiencia actual del agente.
- Luego se actualizará la mejor posición que haya encontrado el agente.
- Después se actualizarán los datos de entrada de la red neuronal del agente.
- Y se realizará una propagación directa de los datos a través de la red neuronal.
- Los agentes se entrenarán a través de la experiencia.
Aplicación de reglas y movimiento: el ciclo comenzará de nuevo en los agentes, con las reglas estándar del algoritmo de boids aplicadas a cada uno:
- Cohesión: los agentes buscarán estar más cerca unos de otros.
- Separación: los agentes evitarán acercarse unos a otros para no colisionar.
- Alineación: los agentes intentarán moverse en la misma dirección que sus vecinos.
//—————————————————————————————————————————————————————————————————————————————— void C_AO_NOA2::Moving () { double reset = GlobalVariableGet ("#reset"); if (reset == 1.0) { revision = false; GlobalVariableSet ("#reset", 0.0); } // Получение параметров из глобальных переменных для интерактивной настройки cohesionWeight = GlobalVariableGet ("1cohesionWeight"); cohesionDist = GlobalVariableGet ("2cohesionDist"); separationWeight = GlobalVariableGet ("3separationWeight"); separationDist = GlobalVariableGet ("4separationDist"); alignmentWeight = GlobalVariableGet ("5alignmentWeight"); alignmentDist = GlobalVariableGet ("6alignmentDist"); maxSpeed = GlobalVariableGet ("7maxSpeed"); minSpeed = GlobalVariableGet ("8minSpeed"); learningRate = GlobalVariableGet ("9learningRate"); neuralInfluence = GlobalVariableGet ("10neuralInfluence"); explorationRate = GlobalVariableGet ("11explorationRate"); // Инициализация начальных позиций и скоростей if (!revision) { for (int i = 0; i < popSize; i++) { for (int c = 0; c < coords; c++) { agent [i].x [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]); agent [i].dx [c] = (rangeMax [c] - rangeMin [c]) * u.RNDfromCI (-1.0, 1.0) * 0.001; a [i].c [c] = u.SeInDiSp (agent [i].x [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } revision = true; return; } // Адаптивное исследование в зависимости от стагнации AdaptiveExploration (); //---------------------------------------------------------------------------- // Основной цикл движения боидов for (int i = 0; i < popSize; i++) { // Запоминаем текущий опыт agent [i].MemorizeExperience (a [i].f, coords); // Обновляем лучшую позицию агента agent [i].UpdateBestPosition (a [i].f, coords); // Обновляем входы нейронной сети agent [i].UpdateInputs (cB, a [i].f, coords); // Прямое распространение по нейронной сети agent [i].ForwardPass (coords); // Обучение на основе накопленного опыта agent [i].Learn (learningRate, coords); } // Расчет масс CalculateMass (); // Применение правил и движение for (int i = 0; i < popSize; i++) { // Стандартные правила алгоритма боидов Cohesion (agent [i], i); Separation (agent [i], i); Alignment (agent [i], i); // Применение нейронного управления ApplyNeuralControl (agent [i], i); // Ограничение скорости и удержание в границах LimitSpeed (agent [i]); KeepWithinBounds (agent [i]); // Обновление позиций for (int c = 0; c < coords; c++) { agent [i].x [c] += agent [i].dx [c]; a [i].c [c] = u.SeInDiSp (agent [i].x [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } } //——————————————————————————————————————————————————————————————————————————————
El método "CalculateMass" calculará la "masa" de los agentes según su aptitud (productividad) y normalizará estos valores. Las variables "maxMass" y "minMass" se inicializarán con los valores -DBL_MAX y DBL_MAX respectivamente. Estas variables se utilizarán para encontrar los valores de aptitud más altos y más bajos entre toda la población de agentes. El método comprobará si hay al menos un agente, verificando que el "popSize" (tamaño de la población) sea mayor que cero. Si no hay agentes, el método finalizará la ejecución. El primer ciclo probará todos los agentes (de 0 a popSize), y para cada uno comprobará si su aptitud (el valor de a[i].f) es mayor que "maxMass" actual o menor que "minMass".
Como resultado de este ciclo se determinarán los valores de aptitud máximo y mínimo entre todos los agentes. En el segundo ciclo, se enumerarán de nuevo todos los agentes y, para cada uno de ellos, se calculará su "masa" (variable "m") mediante la función "u.Scale", que normalizará los valores de aptitud.
//—————————————————————————————————————————————————————————————————————————————— void C_AO_NOA2::CalculateMass () { double maxMass = -DBL_MAX; double minMass = DBL_MAX; // Проверка на наличие данных перед вычислениями if (popSize <= 0) return; for (int i = 0; i < popSize; i++) { if (a [i].f > maxMass) maxMass = a [i].f; if (a [i].f < minMass) minMass = a [i].f; } for (int i = 0; i < popSize; i++) { agent [i].m = u.Scale (a [i].f, minMass, maxMass, 0.1, 1.0); } } //——————————————————————————————————————————————————————————————————————————————
El método "ApplyNeuralControl" se encargará de aplicar el control basado en las salidas de la red neuronal a un agente de tipo "boid". El método iterará todas las coordenadas del agente "coords". Para cada coordenada "c", se comprobará que el índice actual no supere los límites del array de salidas "boid.outputs". Si el índice es correcto, se realizará una corrección de la velocidad "dx[c]" del agente basada en el valor correspondiente de las salidas de la red neuronal multiplicado por su influencia "neuralInfluence".
El tamaño del array "boid.outputs" se extraerá para simplificar las comprobaciones posteriores. Para los coeficientes de cohesión, separación y alineación "cohesion_factor", "separation_factor", "alignment_factor", se comprobará si los índices correspondientes están dentro del array de salidas. Si el índice está fuera de los límites, se asignará un valor por defecto de 0,5. Estos coeficientes se utilizarán para escalar los pesos básicos a fin de considerar el impacto de la red neuronal. Estos influirán en el comportamiento de los agentes modificando los pesos de convergencia, separación y alineación:
- local_cohesion establece el peso de convergencia.
- local_separation establece el peso de la separación.
- local_alignment establece el peso de la alineación.
El método comprobará si debe realizarse una exploración aleatoria utilizando la probabilidad dada "xplorationRate", si se cumple la condición, se seleccionará una coordenada aleatoria "c" para la perturbación. El tamaño de la perturbación "perturbation_size" se calculará según la masa del agente (que es una medida de su aptitud) y del rango de valores posibles para esa coordenada. Esto permitirá controlar la magnitud de la variación aleatoria a pesar de la masa del agente. A la velocidad "dx[c]" se le añadirá una perturbación aleatoria seleccionada desde "perturbation_size" hasta "perturbation_size". El método "ApplyNeuralControl" integrará los resultados de la red neuronal en el proceso de control del movimiento del agente. Asimismo, ajustará su tasa según los datos; el método también considerará la necesidad de una investigación aleatorio.
//—————————————————————————————————————————————————————————————————————————————— // Применение нейронного управления к боиду void C_AO_NOA2::ApplyNeuralControl (S_NeuroBoids_Agent &boid, int pos) { // Используем выходы нейронной сети для коррекции скорости for (int c = 0; c < coords; c++) { // Проверяем, что индекс не выходит за границы массива if (c < ArraySize (boid.outputs)) { // Применяем нейронную коррекцию скорости с заданным влиянием boid.dx [c] += boid.outputs [c] * neuralInfluence; } } // Используем выходы нейронной сети для адаптации параметров стаи // Проверяем, что индексы не выходят за границы массива int output_size = ArraySize (boid.outputs); double cohesion_factor = (coords < output_size) ? boid.outputs [coords] : 0.5; double separation_factor = (coords + 1 < output_size) ? boid.outputs [coords + 1] : 0.5; double alignment_factor = (coords + 2 < output_size) ? boid.outputs [coords + 2] : 0.5; // Масштабируем базовые веса с учетом нейронной адаптации // Эти переменные локальные и не изменяют глобальные параметры double local_cohesion = cohesionWeight * (0.5 + cohesion_factor); double local_separation = separationWeight * (0.5 + separation_factor); double local_alignment = alignmentWeight * (0.5 + alignment_factor); // Случайное исследование с заданной вероятностью if (u.RNDprobab () < explorationRate) { // Выбираем случайную координату для возмущения int c = (int)u.RNDfromCI (0, coords - 1); // Адаптивный размер возмущения в зависимости от массы (фитнеса) double perturbation_size = (1.0 - boid.m) * (rangeMax [c] - rangeMin [c]) * 0.01; // Добавляем случайное возмущение boid.dx [c] += u.RNDfromCI (-perturbation_size, perturbation_size); } } //——————————————————————————————————————————————————————————————————————————————
El método "AdaptiveExploration" de la clase "C_AO_NOA2" implementará la exploración adaptativa según la fase de estancamiento en el proceso de optimización. El método comenzará comprobando si el valor de la función objetivo "fB" ha cambiado con respecto al mejor valor anterior "m_prevBestFitness". Si la diferencia entre ellos es inferior a 0,000001, se considerará que no hay progreso y se incrementará el contador de estancamiento "m_stagnationCounter". De lo contrario, el estancamiento se detendrá, el contador se pondrá a cero y el valor actual de "fB" se almacenará como el nuevo mejor valor.
Si el número de estancamientos es superior a 20, aumentará la probabilidad de exploración aleatoria "explorationRate", pero se limitará a un valor de "0,5" para evitar una probabilidad demasiado alta de variación aleatoria. Cada 50 iteraciones de estancamiento, se producirá un reinicio parcial: El 70% de los agentes de la población se reiniciarán con nuevos valores aleatorios dentro de los rangos especificados "rangeMin" y "rangeMax". De este modo, el resto de los agentes de primera línea mantendrán sus posiciones actuales. Tras el reinicio, se reiniciará el contador de estancamiento.
Si hay progreso y el número de parámetros "params" es superior a 11, se establecerá la probabilidad de exploración "explorationRate" del array de parámetros. Si hay menos parámetros, se establecerá un valor por defecto de 0,1.
//—————————————————————————————————————————————————————————————————————————————— // Адаптивное исследование в зависимости от стагнации void C_AO_NOA2::AdaptiveExploration () { // Определяем, есть ли прогресс в поиске if (MathAbs (fB - m_prevBestFitness) < 0.000001) { m_stagnationCounter++; } else { m_stagnationCounter = 0; m_prevBestFitness = fB; } // Увеличиваем исследование при стагнации if (m_stagnationCounter > 20) { // Увеличиваем вероятность случайного исследования explorationRate = MathMin (0.5, explorationRate * 1.5); // Каждые 50 итераций стагнации выполняем частичный рестарт if (m_stagnationCounter % 50 == 0) { // Перезапускаем 70% популяции, оставляя лучших агентов int restart_count = (int)(popSize * 0.7); for (int i = popSize - restart_count; i < popSize; i++) { for (int c = 0; c < coords; c++) { agent [i].x [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]); agent [i].dx [c] = (rangeMax [c] - rangeMin [c]) * u.RNDfromCI (-1.0, 1.0) * 0.001; } } // Сбрасываем счетчик стагнации m_stagnationCounter = 0; } } else { // При хорошем прогрессе используем обычный уровень исследования if (11 < ArraySize (params)) { explorationRate = params [11].val; } else { explorationRate = 0.1; // значение по умолчанию } } } //——————————————————————————————————————————————————————————————————————————————
El método "Cohesion" de la clase "C_AO_NOA2" se encargará de implementar el comportamiento de convergencia "cohesion" para el modelo boid basado en los agentes. Declaración de variables:
- centerX — el array se utilizará para almacenar las coordenadas del centro de masa de los agentes vecinos. El tamaño del array se corresponderá con el número de coordenadas "coords".
- numNeighbors — el contador Neighbours llevará la cuenta del número de agentes dentro de una distancia dada (considerando la masa).
- sumMass — la suma de las masas de los agentes vecinos se utilizará para normalizar las coordenadas del centro de masa.
El primer ciclo (de 0 a popSize) sumará las masas de todos los agentes de la población, excluyendo el boid (agente) actual en el índice "pos". El segundo ciclo iterará todos los agentes y comprobará si se encuentran a una distancia máxima multiplicada por el factor "cohesionDist" del boid actual. Si la distancia a un agente es inferior al máximo especificado, las coordenadas de ese agente (teniendo en cuenta su masa) se añadirán a "centreX" y el contador "numNeighbors" se incrementará.
Una vez calculadas las sumas de coordenadas y las masas de los vecinos, se comprobará si hay vecinos (es decir, numNeighbors > 0) y la suma de las masas (sumMass > 0,0), y si se encuentran vecinos, se normalizarán las coordenadas del centro de masas (dividiendo por la suma de masas). Entonces la velocidad actual "dx" del boid se incrementará hacia el centro de masa con un peso dado "cohesionWeight", esto significará que el boid se desplazará hacia el centro de masa de sus vecinos dadas sus masas.
El método "Cohesion" calculará el centro de masa y ajustará la velocidad del boid actual para que "converja" hacia ese centro. Este comportamiento será uno de los aspectos clave de la simulación del comportamiento de manada o grupo. Parámetros como "cohesionDist" y "cohesionWeight" permitirán controlar la distancia a la que se aplica este comportamiento y el grado en que el centro de masa influirá en el movimiento de los boids.
//—————————————————————————————————————————————————————————————————————————————— // Найти центр масс других боидов и скорректировать скорость в направлении центра масс void C_AO_NOA2::Cohesion (S_NeuroBoids_Agent &boid, int pos) { double centerX []; ArrayResize (centerX, coords); ArrayInitialize (centerX, 0.0); int numNeighbors = 0; double sumMass = 0; for (int i = 0; i < popSize; i++) { if (pos != i) sumMass += agent [i].m; } for (int i = 0; i < popSize; i++) { if (pos != i) { if (Distance (boid, agent [i]) < distanceMax * cohesionDist) { for (int c = 0; c < coords; c++) { centerX [c] += agent [i].x [c] * agent [i].m; } numNeighbors++; } } } if (numNeighbors > 0 && sumMass > 0.0) { for (int c = 0; c < coords; c++) { centerX [c] /= sumMass; boid.dx [c] += (centerX [c] - boid.x [c]) * cohesionWeight; } } } //——————————————————————————————————————————————————————————————————————————————
El método "Separation" de la clase "C_AO_NOA2" implementará un comportamiento de repulsión (separación) para agentes modelo que está diseñado para prevenir colisiones entre un boid y los agentes vecinos. "moveX" — parámetros como el array "cohesionDist" se usarán para almacenar vectores de desplazamiento para alejar al boid de otros agentes.
El tamaño del array se corresponderá con el número de coordenadas "coords". El array "moveX" se inicializará con ceros para evitar la acumulación de valores aleatorios, luego se realizará la iteración sobre todos los agentes de la población (de 0 a popSize). A continuación, se comprobará si el agente está cerca del boid actual. Esto se logrará utilizando la función "Distance", y si la distancia entre el boid y el agente es menor que un máximo dado (multiplicado por el factor "separationDist"), entonces para cada coordenada, las coordenadas del agente se restarán de las coordenadas del boid actual para crear efectivamente un vector que apunte hacia el boid actual (es decir, lejos del agente).
Una vez completado el ciclo del agente, la velocidad actual "dx" del boid se incrementará con el vector de desplazamiento calculado "moveX" multiplicado por el factor "separationWeight". Esto permitirá controlar la fuerza de la repulsión: cuanto mayor sea el valor de "separationWeight", más con mayor intensidad evitará el boid las colisiones.
El método "Separation" implementará un comportamiento que hará que el boid repela a sus vecinos más cercanos, evitando colisiones. Se trata de un aspecto importante en el modelado del comportamiento de la manada, que ayuda a mantener el espacio personal de cada agente y favorece interacciones más naturales entre ellos. Parámetros como "separationDist" y "separationWeight" permiten ajustar con flexibilidad el alcance y la fuerza del efecto de repulsión, respectivamente.
//—————————————————————————————————————————————————————————————————————————————— // Отталкивание от других боидов, чтобы избежать столкновений void C_AO_NOA2::Separation (S_NeuroBoids_Agent &boid, int pos) { double moveX []; ArrayResize (moveX, coords); ArrayInitialize (moveX, 0.0); for (int i = 0; i < popSize; i++) { if (pos != i) { if (Distance (boid, agent [i]) < distanceMax * separationDist) { for (int c = 0; c < coords; c++) { moveX [c] += boid.x [c] - agent [i].x [c]; } } } } for (int c = 0; c < coords; c++) { boid.dx [c] += moveX [c] * separationWeight; } } //——————————————————————————————————————————————————————————————————————————————
El método "Alignment" de la clase "C_AO_NOA2" implementará el comportamiento de alineación para los agentes del modelo. Este comportamiento hace que los boids coordinen sus velocidades con los agentes vecinos para crear un movimiento de grupo más armonioso y coordinado. "avgDX" es un array que se utilizará para almacenar el vector de velocidad media de los agentes vecinos. El tamaño del array se corresponde con el número de coordenadas "coords"; "numNeighbors" es un contador que llevará la cuenta del número de vecinos que se encuentran a una distancia determinada.
El ciclo iterará todos los agentes de la población. Dentro del ciclo se comprobará si el agente actual es el mismo que el boid. Si la distancia a otro agente es menor que el máximo especificado multiplicado por el factor "alignmentDist", la velocidad actual "dx" de ese agente se añadirá a "avgDX". El contador "numNeighbors" se incrementará en 1. Una vez finalizado el ciclo, si se han encontrado vecinos (es decir, numNeighbors > 0), se calculará la velocidad media (avgDX) dividiendo el vector de velocidad sumado por el número de vecinos. La velocidad actual "dx" del boid se ajustará entonces a la velocidad media de sus vecinos teniendo en cuenta el factor "alignmentWeight".
El método de "Alignment" permitirá a un boid adaptar su velocidad a la de sus vecinos. Este comportamiento ayudará a los grupos de boids a moverse de forma más coherente y reducirá la probabilidad de conflictos o cambios bruscos de dirección. Parámetros como "alignmentDist" y "alignmentWeight" establecerán, respectivamente, el radio del efecto de alineación y el grado en que la velocidad media de los vecinos afectará a la velocidad del boid actual.//—————————————————————————————————————————————————————————————————————————————— // Выравнивание скорости с другими боидами void C_AO_NOA2::Alignment (S_NeuroBoids_Agent &boid, int pos) { double avgDX []; ArrayResize (avgDX, coords); ArrayInitialize (avgDX, 0.0); int numNeighbors = 0; for (int i = 0; i < popSize; i++) { if (pos != i) { if (Distance (boid, agent [i]) < distanceMax * alignmentDist) { for (int c = 0; c < coords; c++) { avgDX [c] += agent [i].dx [c]; } numNeighbors++; } } } if (numNeighbors > 0) { for (int c = 0; c < coords; c++) { avgDX [c] /= numNeighbors; boid.dx [c] += (avgDX [c] - boid.dx [c]) * alignmentWeight; } } } //——————————————————————————————————————————————————————————————————————————————
El método "LimitSpeed" de la clase "C_AO_NOA2" está diseñado para controlar la velocidad de los agentes (boids) en el modelo para asegurarse de que las velocidades de los boids estén dentro del rango especificado, y "speed" es una variable para almacenar la velocidad actual de los boids, que será calculada como la longitud del vector de velocidad. El ciclo por coordenadas (coords) calculará el cuadrado de la velocidad para cada coordenada (sumando boid.dx [c] * boid.dx [c]) y las sumará. Esto nos permitirá calcular el cuadrado de la longitud del vector velocidad. La longitud (o velocidad real) se obtendrá entonces mediante la función "MathSqrt", que calculará la raíz cuadrada de la suma de cuadrados. Si la velocidad es mayor que un valor pequeño (1e-10), el programa continuará la ejecución, si la velocidad actual es menor que la velocidad mínima permitida (definida como speedMax[0] * minSpeed), entonces el vector de velocidad "boid.dx" será normalizado (dividido por su longitud actual) e incrementado a un valor igual al valor mínimo permitido.
Si la velocidad actual es mayor que la velocidad máxima permitida (speedMax[0] * maxSpeed), entonces, de forma similar, el vector de velocidad se normalizará y se establecerá en el valor de la velocidad máxima, mientras que si la velocidad es casi cero (cero o muy cercana a cero), en este caso, cada coordenada del vector de velocidad se sustituirá por un pequeño valor aleatorio obtenido mediante la función "u.RNDfromCI (-1.0, 1.0)" multiplicado por la velocidad máxima.
El método "LimitSpeed" garantizará que la velocidad de los boids se mantenga dentro de unos límites aceptables, evitando movimientos demasiado lentos o demasiado rápidos. Este comportamiento permitirá una simulación más realista de los agentes, ya que no permitirá grandes desviaciones de velocidad que podrían dar lugar a movimientos poco naturales. Los parámetros "minSpeed" y "maxSpeed" pueden configurarse para ajustar el comportamiento y la velocidad de los agentes según los objetivos de la simulación.
//—————————————————————————————————————————————————————————————————————————————— // Ограничение скорости void C_AO_NOA2::LimitSpeed (S_NeuroBoids_Agent &boid) { double speed = 0; for (int c = 0; c < coords; c++) { speed += boid.dx [c] * boid.dx [c]; } speed = MathSqrt (speed); // Если скорость не нулевая (предотвращаем деление на ноль) if (speed > 1e-10) { // Если скорость слишком мала, увеличиваем её if (speed < speedMax [0] * minSpeed) { for (int c = 0; c < coords; c++) { boid.dx [c] = boid.dx [c] / speed * speedMax [c] * minSpeed; } } // Если скорость слишком велика, уменьшаем её else if (speed > speedMax [0] * maxSpeed) { for (int c = 0; c < coords; c++) { boid.dx [c] = boid.dx [c] / speed * speedMax [c] * maxSpeed; } } } else { // Если скорость почти нулевая, задаем небольшую случайную скорость for (int c = 0; c < coords; c++) { boid.dx [c] = u.RNDfromCI (-1.0, 1.0) * speedMax [c] * minSpeed; } } } //——————————————————————————————————————————————————————————————————————————————
El método "KeepWithinBounds" de la clase "C_AO_NOA2" está diseñado para mantener al agente (boid) dentro de los límites especificados. Si el boid se acerca demasiado a los bordes de la zona, este método invertirá su dirección y proporcionará un empujón definitivo hacia los límites. El método comenzará con un ciclo a través de todas las coordenadas, lo que permitirá trabajar con un espacio multidimensional.
Para cada coordenada, comprobará si la posición del boid (boid.x[c]) está por debajo del límite mínimo (rangeMin[c]), si es así, la dirección de la velocidad (boid.dx[c]) se invertirá con "boid.dx [c] *= -1.0". Esto significará que el boid se moverá en la dirección opuesta. A continuación, se añadirá un pequeño empujón desde el límite: (rangeMax[c] - rangeMin[c]) * 0,001, que ayudará a empujar el boid hacia el interior de la región.
Luego se realizará una comprobación similar para el límite máximo (rangeMax[c]): si la posición del boid sobrepasa el máximo, su velocidad también se invertirá y se corregirá, pero restándole algún valor, de forma similar al caso anterior.
El método "KeepWithinBounds" restringirá eficazmente los movimientos de los boids en un área determinada, impidiendo que salgan de los límites y garantizando que vuelvan al interior.
//—————————————————————————————————————————————————————————————————————————————— // Удержание боида в границах. Если он оказывается слишком близко к краю, // подталкиваем его обратно и меняем направление. void C_AO_NOA2::KeepWithinBounds (S_NeuroBoids_Agent &boid) { for (int c = 0; c < coords; c++) { if (boid.x [c] < rangeMin [c]) { boid.dx [c] *= -1.0; // Добавляем небольшой толчок от границы boid.dx [c] += (rangeMax [c] - rangeMin [c]) * 0.001; } if (boid.x [c] > rangeMax [c]) { boid.dx [c] *= -1.0; // Добавляем небольшой толчок от границы boid.dx [c] -= (rangeMax [c] - rangeMin [c]) * 0.001; } } } //——————————————————————————————————————————————————————————————————————————————
El método "Distance" de la clase "C_AO_NOA2" está diseñado para calcular la distancia entre dos agentes (boids) en un espacio multidimensional. Este utilizará una fórmula para calcular la distancia euclidiana; "dist" es una variable que almacenará la suma de los cuadrados de las diferencias de las coordenadas de los dos boids.
El método iniciará un ciclo que iterará todas las coordenadas, lo que permitirá calcular la distancia en un espacio de cualquier dimensionalidad. Para cada coordenada "c", se calculará el cuadrado de la diferencia entre las coordenadas correspondientes de ambos boids: boid1.x[c] - boid2.x[c].
Los resultados ((boid1.x[c] - boid2.x[c])^2) se añadirán a la variable "dist". Una vez finalizado el ciclo, la variable "dist" contendrá la suma de cuadrados de las diferencias de coordenadas. Para obtener la distancia real, el método usará "MathSqrt" para calcular la raíz cuadrada de la suma de cuadrados, que se corresponderá con la fórmula de la distancia euclidiana.
//—————————————————————————————————————————————————————————————————————————————— // Расчет расстояния между двумя боидами double C_AO_NOA2::Distance (S_NeuroBoids_Agent &boid1, S_NeuroBoids_Agent &boid2) { double dist = 0; for (int c = 0; c < coords; c++) { dist += MathPow (boid1.x [c] - boid2.x [c], 2); } return MathSqrt (dist); } //——————————————————————————————————————————————————————————————————————————————
El método "Revision" de la clase "C_AO_NOA2" se encargará de actualizar la información sobre la mejor solución encontrada durante el proceso de optimización. Este proceso implicará la actualización del valor de la función de aptitud, así como de las coordenadas correspondientes a dicho valor. El método también supervisará los progresos y adaptará los parámetros del algoritmo si se producen mejoras significativas. El método iterará toda la población representada por el array "a", que contiene "popSize" (número de agentes).
Dentro del ciclo, se comprobará si el valor de aptitud de cada agente (a[i].f) es mayor que el mejor valor actual (fB). Si el agente actual muestra el mejor valor de aptitud, se actualizará el mejor valor de aptitud global asignando a "fB" un nuevo valor "a[i].f", luego se actualizarán las coordenadas de la mejor solución. Para cada coordenada "c", el array "cB" (que contiene las coordenadas de la mejor solución) se actualizará con los valores del agente actual. El contador de estancamiento (m_stagnationCounter) se pondrá a cero porque se ha encontrado una mejora en la solución.
El método utilizará la variable "hasProgress" para determinar si se ha avanzado. Para ello, se calculará la diferencia absoluta entre los valores actual y anterior de la mejor función de aptitud (MathAbs(fB - m_prevBestFitness)), y si esta diferencia es superior a 0,000001, se considerará que se está avanzando. Si hay progreso, "m_prevBestFitness" se actualizará al mejor valor actual "fB".
La tasa de exploración "explorationRate" también se adaptará: esta disminuirá si se encuentra una mejora, teniendo en cuenta algún parámetro del array "params" y el valor actual de "explorationRate".
//—————————————————————————————————————————————————————————————————————————————— // Обновление лучшего найденного решения void C_AO_NOA2::Revision () { // Обновляем лучшие координаты и значение фитнес-функции for (int i = 0; i < popSize; i++) { // Обновление глобального лучшего решения if (a [i].f > fB) { fB = a [i].f; for (int c = 0; c < coords; c++) { cB [c] = a [i].c [c]; } // Сбрасываем счетчик стагнации при нахождении лучшего решения m_stagnationCounter = 0; } } // Проверка наличия прогресса для адаптации параметров алгоритма bool hasProgress = MathAbs (fB - m_prevBestFitness) > 0.000001; if (hasProgress) { m_prevBestFitness = fB; explorationRate = MathMax (params [11].val * 0.5, explorationRate * 0.9); } } //——————————————————————————————————————————————————————————————————————————————
Resultados de las pruebas
Los resultados de las pruebas son bastante flojos.NOA2|Neuroboids Optimization Algorithm 2 (joo)|50.0|0.6|0.001|0.005|0.03|0.1|0.1|0.1|0.01|0.01|0.3|0.1|
=============================
5 Hilly's; Func runs: 10000; result: 0.47680799582735267
25 Hilly's; Func runs: 10000; result: 0.30763714006051013
500 Hilly's; Func runs: 10000; result: 0.2544737238936433
=============================
5 Forest's; Func runs: 10000; result: 0.3238017030688524
25 Forest's; Func runs: 10000; result: 0.20976876473929068
500 Forest's; Func runs: 10000; result: 0.15740101965732595
=============================
5 Megacity's; Func runs: 10000; result: 0.27076923076923076
25 Megacity's; Func runs: 10000; result: 0.14676923076923082
500 Megacity's; Func runs: 10000; result: 0.09729230769230844
=============================
All score: 2.24472 (24.94%)
En la visualización podemos apreciar la modesta capacidad de búsqueda. La posibilidad de modificar los parámetros externos del algoritmo mediante variables globales permite experimentar con el comportamiento de los boids, revelando comportamientos interesantes. A continuación le mostramos algunos de los posibles comportamientos.

NOA2 en la función de prueba Hilly

NOA2 en la función de prueba Forest

NOA2 en la función de prueba Megacity
Basándonos en los resultados de las pruebas, el algoritmo NOA2 en su versión de referencia se incluye en nuestra tabla de clasificación de algoritmos de optimización de poblaciones únicamente con fines ilustrativos.
| № | AO | Description | Hilly | Hilly final | Forest | Forest final | Megacity (discrete) | Megacity final | Final result | % de MAX | ||||||
| 10 p (5 F) | 50 p (25 F) | 1000 p (500 F) | 10 p (5 F) | 50 p (25 F) | 1000 p (500 F) | 10 p (5 F) | 50 p (25 F) | 1000 p (500 F) | ||||||||
| 1 | ANS | across neighbourhood search | 0,94948 | 0,84776 | 0,43857 | 2,23581 | 1,00000 | 0,92334 | 0,39988 | 2,32323 | 0,70923 | 0,63477 | 0,23091 | 1,57491 | 6,134 | 68,15 |
| 2 | CLA | code lock algorithm (joo) | 0,95345 | 0,87107 | 0,37590 | 2,20042 | 0,98942 | 0,91709 | 0,31642 | 2,22294 | 0,79692 | 0,69385 | 0,19303 | 1,68380 | 6,107 | 67,86 |
| 3 | AMOm | animal migration optimization M | 0,90358 | 0,84317 | 0,46284 | 2,20959 | 0,99001 | 0,92436 | 0,46598 | 2,38034 | 0,56769 | 0,59132 | 0,23773 | 1,39675 | 5,987 | 66,52 |
| 4 | (P+O)ES | (P+O) evolution strategies | 0,92256 | 0,88101 | 0,40021 | 2,20379 | 0,97750 | 0,87490 | 0,31945 | 2,17185 | 0,67385 | 0,62985 | 0,18634 | 1,49003 | 5,866 | 65,17 |
| 5 | CTA | comet tail algorithm (joo) | 0,95346 | 0,86319 | 0,27770 | 2,09435 | 0,99794 | 0,85740 | 0,33949 | 2,19484 | 0,88769 | 0,56431 | 0,10512 | 1,55712 | 5,846 | 64,96 |
| 6 | TETA | time evolution travel algorithm (joo) | 0,91362 | 0,82349 | 0,31990 | 2,05701 | 0,97096 | 0,89532 | 0,29324 | 2,15952 | 0,73462 | 0,68569 | 0,16021 | 1,58052 | 5,797 | 64,41 |
| 7 | SDSm | stochastic diffusion search M | 0,93066 | 0,85445 | 0,39476 | 2,17988 | 0,99983 | 0,89244 | 0,19619 | 2,08846 | 0,72333 | 0,61100 | 0,10670 | 1,44103 | 5,709 | 63,44 |
| 8 | BOAm | billiards optimization algorithm M | 0,95757 | 0,82599 | 0,25235 | 2,03590 | 1,00000 | 0,90036 | 0,30502 | 2,20538 | 0,73538 | 0,52523 | 0,09563 | 1,35625 | 5,598 | 62,19 |
| 9 | AAm | archery algorithm M | 0,91744 | 0,70876 | 0,42160 | 2,04780 | 0,92527 | 0,75802 | 0,35328 | 2,03657 | 0,67385 | 0,55200 | 0,23738 | 1,46323 | 5,548 | 61,64 |
| 10 | ESG | evolution of social groups (joo) | 0,99906 | 0,79654 | 0,35056 | 2,14616 | 1,00000 | 0,82863 | 0,13102 | 1,95965 | 0,82333 | 0,55300 | 0,04725 | 1,42358 | 5,529 | 61,44 |
| 11 | SIA | simulated isotropic annealing (joo) | 0,95784 | 0,84264 | 0,41465 | 2,21513 | 0,98239 | 0,79586 | 0,20507 | 1,98332 | 0,68667 | 0,49300 | 0,09053 | 1,27020 | 5,469 | 60,76 |
| 12 | ACS | artificial cooperative search | 0,75547 | 0,74744 | 0,30407 | 1,80698 | 1,00000 | 0,88861 | 0,22413 | 2,11274 | 0,69077 | 0,48185 | 0,13322 | 1,30583 | 5,226 | 58,06 |
| 13 | DA | dialectical algorithm | 0,86183 | 0,70033 | 0,33724 | 1,89940 | 0,98163 | 0,72772 | 0,28718 | 1,99653 | 0,70308 | 0,45292 | 0,16367 | 1,31967 | 5,216 | 57,95 |
| 14 | BHAm | black hole algorithm M | 0,75236 | 0,76675 | 0.34583 | 1,86493 | 0.93593 | 0.80152 | 0,27177 | 2,00923 | 0.65077 | 0.51646 | 0,15472 | 1,32195 | 5.196 | 57.73 |
| 15 | ASO | anarchy society optimization | 0,84872 | 0,74646 | 0,31465 | 1,90983 | 0,96148 | 0,79150 | 0,23803 | 1,99101 | 0,57077 | 0,54062 | 0,16614 | 1,27752 | 5,178 | 57,54 |
| 16 | RFO | royal flush optimization (joo) | 0,83361 | 0,73742 | 0,34629 | 1,91733 | 0,89424 | 0,73824 | 0,24098 | 1,87346 | 0,63154 | 0,50292 | 0,16421 | 1,29867 | 5,089 | 56,55 |
| 17 | AOSm | búsqueda de orbitales atómicos M | 0,80232 | 0,70449 | 0,31021 | 1,81702 | 0,85660 | 0,69451 | 0,21996 | 1,77107 | 0,74615 | 0,52862 | 0,14358 | 1,41835 | 5,006 | 55,63 |
| 18 | TSEA | turtle shell evolution algorithm (joo) | 0,96798 | 0,64480 | 0,29672 | 1,90949 | 0,99449 | 0,61981 | 0,22708 | 1,84139 | 0,69077 | 0,42646 | 0,13598 | 1,25322 | 5,004 | 55,60 |
| 19 | DE | differential evolution | 0,95044 | 0,61674 | 0,30308 | 1,87026 | 0,95317 | 0,78896 | 0,16652 | 1,90865 | 0,78667 | 0,36033 | 0,02953 | 1,17653 | 4,955 | 55,06 |
| 20 | SRA | successful restaurateur algorithm (joo) | 0,96883 | 0,63455 | 0,29217 | 1,89555 | 0,94637 | 0,55506 | 0,19124 | 1,69267 | 0,74923 | 0,44031 | 0,12526 | 1,31480 | 4,903 | 54,48 |
| 21 | CRO | chemical reaction optimisation | 0,94629 | 0,66112 | 0,29853 | 1,90593 | 0,87906 | 0,58422 | 0,21146 | 1,67473 | 0,75846 | 0,42646 | 0,12686 | 1,31178 | 4,892 | 54,36 |
| 22 | BIO | blood inheritance optimization (joo) | 0,81568 | 0,65336 | 0,30877 | 1,77781 | 0,89937 | 0,65319 | 0,21760 | 1,77016 | 0,67846 | 0,47631 | 0,13902 | 1,29378 | 4,842 | 53,80 |
| 23 | BSA | bird swarm algorithm | 0,89306 | 0,64900 | 0,26250 | 1,80455 | 0,92420 | 0,71121 | 0,24939 | 1,88479 | 0,69385 | 0,32615 | 0,10012 | 1,12012 | 4,809 | 53,44 |
| 24 | HS | harmony search | 0,86509 | 0,68782 | 0,32527 | 1,87818 | 0,99999 | 0,68002 | 0,09590 | 1,77592 | 0,62000 | 0,42267 | 0,05458 | 1,09725 | 4,751 | 52,79 |
| 25 | SSG | saplings sowing and growing | 0,77839 | 0,64925 | 0,39543 | 1,82308 | 0,85973 | 0,62467 | 0,17429 | 1,65869 | 0,64667 | 0,44133 | 0,10598 | 1,19398 | 4,676 | 51,95 |
| 26 | BCOm | bacterial chemotaxis optimization M | 0,75953 | 0,62268 | 0,31483 | 1,69704 | 0,89378 | 0,61339 | 0,22542 | 1,73259 | 0,65385 | 0,42092 | 0,14435 | 1,21912 | 4,649 | 51,65 |
| 27 | ABO | african buffalo optimization | 0,83337 | 0,62247 | 0,29964 | 1,75548 | 0,92170 | 0,58618 | 0,19723 | 1,70511 | 0,61000 | 0,43154 | 0,13225 | 1,17378 | 4,634 | 51,49 |
| 28 | (PO)ES | (PO) evolution strategies | 0,79025 | 0,62647 | 0,42935 | 1,84606 | 0,87616 | 0,60943 | 0,19591 | 1,68151 | 0,59000 | 0,37933 | 0,11322 | 1,08255 | 4,610 | 51,22 |
| 29 | TSm | tabu search M | 0,87795 | 0,61431 | 0,29104 | 1,78330 | 0,92885 | 0,51844 | 0,19054 | 1,63783 | 0,61077 | 0,38215 | 0,12157 | 1,11449 | 4,536 | 50,40 |
| 30 | BSO | brain storm optimization | 0,93736 | 0,57616 | 0,29688 | 1,81041 | 0,93131 | 0,55866 | 0,23537 | 1,72534 | 0,55231 | 0,29077 | 0,11914 | 0,96222 | 4,498 | 49,98 |
| 31 | WOAm | wale optimization algorithm M | 0,84521 | 0,56298 | 0,26263 | 1,67081 | 0,93100 | 0,52278 | 0,16365 | 1,61743 | 0,66308 | 0,41138 | 0,11357 | 1,18803 | 4,476 | 49,74 |
| 32 | AEFA | artificial electric field algorithm | 0,87700 | 0,61753 | 0,25235 | 1,74688 | 0,92729 | 0,72698 | 0,18064 | 1,83490 | 0,66615 | 0,11631 | 0,09508 | 0,87754 | 4,459 | 49,55 |
| 33 | AEO | artificial ecosystem-based optimization algorithm | 0,91380 | 0,46713 | 0,26470 | 1,64563 | 0,90223 | 0,43705 | 0,21400 | 1,55327 | 0,66154 | 0,30800 | 0,28563 | 1,25517 | 4,454 | 49,49 |
| 34 | ACOm | ant colony optimization M | 0,88190 | 0,66127 | 0,30377 | 1,84693 | 0,85873 | 0,58680 | 0,15051 | 1,59604 | 0,59667 | 0,37333 | 0,02472 | 0,99472 | 4,438 | 49,31 |
| 35 | BFO-GA | bacterial foraging optimization — ga | 0,89150 | 0,55111 | 0,31529 | 1,75790 | 0,96982 | 0,39612 | 0,06305 | 1,42899 | 0,72667 | 0,27500 | 0,03525 | 1,03692 | 4,224 | 46,93 |
| 36 | SOA | simple optimization algorithm | 0.91520 | 0.46976 | 0.27089 | 1,65585 | 0.89675 | 0.37401 | 0.16984 | 1,44060 | 0.69538 | 0.28031 | 0.10852 | 1,08422 | 4.181 | 46,45 |
| 37 | ABH | artificial bee hive algorithm | 0,84131 | 0,54227 | 0,26304 | 1,64663 | 0,87858 | 0,47779 | 0,17181 | 1,52818 | 0,50923 | 0,33877 | 0,10397 | 0,95197 | 4.127 | 45,85 |
| 38 | ACMO | atmospheric cloud model optimization | 0,90321 | 0,48546 | 0,30403 | 1,69270 | 0,80268 | 0,37857 | 0,19178 | 1,37303 | 0,62308 | 0,24400 | 0,10795 | 0,97503 | 4,041 | 44,90 |
| 39 | ADAMm | adaptive moment estimation M | 0.88635 | 0.44766 | 0,26613 | 1.60014 | 0.84497 | 0.38493 | 0.16889 | 1,39880 | 0,66154 | 0.27046 | 0.10594 | 1,03794 | 4.037 | 44.85 |
| 40 | CGO | chaos game optimization | 0,57256 | 0,37158 | 0,32018 | 1,26432 | 0,61176 | 0,61931 | 0,62161 | 1,85267 | 0,37538 | 0,21923 | 0,19028 | 0,78490 | 3,902 | 43,35 |
| 41 | ATAm | artificial tribe algorithm M | 0.71771 | 0.55304 | 0,25235 | 1,52310 | 0.82491 | 0.55904 | 0,20473 | 1,58867 | 0,44000 | 0,18615 | 0.09411 | 0.72026 | 3.832 | 42.58 |
| 42 | CFO | central force optimization | 0,60961 | 0,54958 | 0,27831 | 1,43750 | 0,63418 | 0,46833 | 0,22541 | 1,32792 | 0,57231 | 0,23477 | 0,09586 | 0,90294 | 3,668 | 40,76 |
| 43 | ASHA | artificial showering algorithm | 0,89686 | 0,40433 | 0,25617 | 1,55737 | 0,80360 | 0,35526 | 0,19160 | 1,35046 | 0,47692 | 0,18123 | 0,09774 | 0,75589 | 3,664 | 40,71 |
| 44 | ASBO | adaptive social behavior optimization | 0,76331 | 0,49253 | 0,32619 | 1,58202 | 0,79546 | 0,40035 | 0,26097 | 1,45677 | 0,26462 | 0,17169 | 0,18200 | 0,61831 | 3.657 | 40,63 |
| 45 | MEC | mind evolutionary computation | 0,69533 | 0,53376 | 0,32661 | 1,55569 | 0,72464 | 0,33036 | 0,07198 | 1,12698 | 0,52500 | 0,22000 | 0,04198 | 0,78698 | 3,470 | 38,55 |
| NOA2 | neuroboids optimization algorithm 2(joo) | 0,47681 | 0,30764 | 0,25447 | 1,03892 | 0,32380 | 0,20977 | 0,15740 | 0,69097 | 0,27077 | 0,14677 | 0,09729 | 0,51483 | 2,245 | 24,94 | |
Conclusiones
El Algoritmo de Optimización Neuroboidal (NOA2) que hemos desarrollado es un enfoque híbrido que combina los principios de la inteligencia de enjambre (algoritmo boid) con el control neuronal. La idea clave consiste en aplicar una red neuronal para controlar de forma adaptativa los parámetros de comportamiento de los agentes boid. La aplicación actual usa una red neuronal simple de una sola capa sin capas ocultas. Consta de una capa de entrada que admite las coordenadas actuales, las velocidades, la distancia a la mejor solución y el valor de la función de aptitud, y una capa de salida que determina las correcciones de velocidad y los parámetros adaptativos de las reglas de comportamiento del enjambre boid.
En esta versión no hay capas intermedias. El número de entradas se define como (coords * 2 + 2), donde "coords" será el número de dimensiones del espacio de búsqueda. Los resultados incluyen una corrección para cada coordenada y tres parámetros adicionales para adaptar las reglas del enjambre. El algoritmo tiene un gran número de parámetros ajustables, lo cual hace difícil encontrar el mejor; hemos probado varias combinaciones, pero no hemos encontrado la combinación ideal que muestre los mejores resultados en las funciones de prueba.
En su forma actual, el algoritmo sirve claramente para demostrar el concepto y, en general, supone un experimento interesante en métodos de optimización híbridos, pero no demuestra un rendimiento suficiente para ser calificado. Ha obtenido el número mínimo de puntos en las pruebas realizadas y solo puede considerarse como una ilustración del enfoque de hibridación de algoritmos. Para los investigadores y desarrolladores interesados, el código NOA2 puede servir como punto de partida para experimentar con diferentes configuraciones y parámetros, así como para crear mejores algoritmos de optimización híbridos que aprovechen tanto los métodos de población como los de aprendizaje automático.

Figura 3. Clasificación por colores de los algoritmos según las pruebas respectivas

Figura 4. Histograma de los resultados de las pruebas de algoritmos (en una escala de 0 a 100, cuanto más mejor, donde 100 es el máximo resultado teórico posible, el script para calcular la tabla de puntuación está en el archivo)
Ventajas y desventajas del algoritmo NOA2:
Ventajas:
- Supone una idea interesante para poner en práctica.
Desventajas:
- Resultados muy flojos.
Adjuntamos al artículo un archivo con las versiones actuales de los códigos de los algoritmos. El autor de este artículo no se responsabiliza de la exactitud absoluta de la descripción de los algoritmos canónicos, muchos de ellos han sido modificados para mejorar las capacidades de búsqueda. Las conclusiones y juicios presentados en los artículos se basan en los resultados de los experimentos realizados.
Programas usados en el artículo
| # | Nombre | Tipo | Descripción |
|---|---|---|---|
| 1 | #C_AO.mqh | Archivo de inclusión | Clase padre de algoritmos de optimización basados en la población |
| 2 | #C_AO_enum.mqh | Archivo de inclusión | Enumeración de los algoritmos de optimización basados en la población |
| 3 | TestFunctions.mqh | Archivo de inclusión | Biblioteca de funciones de prueba |
| 4 | TestStandFunctions.mqh | Archivo de inclusión | Biblioteca de funciones del banco de pruebas |
| 5 | Utilities.mqh | Archivo de inclusión | Biblioteca de funciones auxiliares |
| 6 | CalculationTestResults.mqh | Archivo de inclusión | Script para calcular los resultados en una tabla comparativa |
| 7 | Testing AOs.mq5 | Script | Banco de pruebas único para todos los algoritmos de optimización basados en la población |
| 8 | Simple use of population optimization algorithms.mq5 | Script | Ejemplo sencillo de utilización de algoritmos de optimización basados en la población sin visualización |
| 9 | Test_AO_NOA2.mq5 | Script | Banco de pruebas para NOA2 |
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/17497
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.
Explorando técnicas avanzadas de aprendizaje automático en la estrategia Darvas Box Breakout
Automatización de estrategias de trading en MQL5 (Parte 12): Implementación de la estrategia Mitigation Order Blocks (MOB)
Particularidades del trabajo con números del tipo double en MQL4
Desarrollo de estrategias comerciales de tendencia basadas en el aprendizaje automático
- 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