自适应社会行为优化(ASBO):Schwefel函数与Box-Muller方法
1. 引言
生命体的集体行为展现出惊人的智慧。从鱼群到人类社会,合作与协作是生存与繁荣的关键。但如果我们能够利用这些社会结构的原理来创建能够高效且准确地解决复杂问题的新型优化算法呢?
自然界中有很多群体行为的例子,生命体通过组成社会来增加生存和创新的机会。这种在动物界、人类社会以及其他生命形式中观察到的现象,已经成为进化生物学家和社会哲学家研究的迷人主题。通过研究这些社会,已经开发出了一种计算模型,该模型模拟了它们在某些目标方面的成功运作。这些模型,如粒子群优化和蚁群优化,展示了群体工作在解决优化问题中的效率。
本文探讨了社会结构的概念及其对群体决策过程的影响。我们还提出了一种基于社会行为和交互原则的数学模型,该模型可用于实现全局优化。该模型被称为自适应社会行为优化(ASBO),它考虑了环境对群体决策的影响,包括领导力、邻里关系和自组织。该算法由Manojo Kumar Singh提出,并于2013年在Aswatha Kumar M.等人编辑的《ICAdC论文集,AISC 174》中发表。
社会结构与影响模型:
- 社会是一群由共同行为或特征相联结的生命体。
- 生活在社会中的好处:增加了捕食、繁殖、抵御捕食者以及创新的机会。
- 影响个体在社会中发展的关键因素:领导力、邻里关系、自尊心。
- 所提出的ASBO模型基于动态领导力、动态逻辑邻里和自我评价。
ASBO算法的主要原则:
- 一个解决方案的群体,其中每个解决方案都是D维空间中的一个向量。
- 对于每个解决方案,计算三个向量:全局最优、个体最优和邻里中心。
- 使用自适应影响比率计算新解决方案的位置。
- 影响比率使用自适应变异策略进行调整。
2. 实现算法的方法
在我们深入探讨算法本身之前,了解它背后的基本概念至关重要。这个概念与施韦费尔(Schwefel)方法的使用有关,它是自适应变异的方法之一,并被用于一些优化算法中,如进化算法。其主要特征包括:
1. 变异参数的自适应:
- 每个个体(解决方案)都有自己的变异参数(例如,变异步长)。
- 这些变异参数也会随着解决方案本身的进化而进化。
- 因此,变异步长会根据每个个体的函数景观进行适应。
2. 高斯变异:
- 使用高斯(正态)分布来生成新的解决方案。
- 平均变异值等于前一个解决方案,而标准差由变异参数确定。
3. 解决方案与变异参数的关系:
- 变异参数(步长)取决于解决方案的目标函数值(适应度)。
- 解决方案越好,变异步长越小,反之亦然。
施韦费尔概念背后的思想是,通过调整变异参数,算法能够更有效地探索解空间,特别是在搜索的后期阶段,当需要对解决方案进行更精确的调整时。
下面的示例展示了施韦费尔概念在优化交易策略参数中的应用。下面将介绍该方法本身的操作。
以最简单的假设进化算法(Evolutionary Algorithm, EA)为例,在OnInit初始化期间创建一个随机的初始解决方案群体。进化过程的一个步骤在OnTick中完成:
a. 种群中每个个体的适应度被评估。b. 种群按适应度排序。c. 除了最优个体外,所有个体都进行变异操作。d. 增加代数计数器。
这个过程一直重复,直到达到指定的代数。优化完成后,会显示找到的最优解决方案。
// 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); }
让我们分段来看这个方法:
1. 代码结构和输入参数。
首先,我们为算法和Individual结构定义输入,Individual结构代表种群中的一个单独解决方案。每个个体都有基因(策略参数)、变异步长以及适应度值。
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. 初始化。
在OnInit()函数中,创建并初始化种群。InitializePopulation()函数用随机的基因值填充种群,并设置初始的变异步长。
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. 主循环。
优化过程在OnTick()函数中管理。它评估种群,对其进行排序,执行变异,然后进入到下一代。
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. 种群评估与排序。
这些函数评估每个个体的适应度,并按适应度降序对种群进行排序。在这个例子中,CalculateFitness()函数很简单,但在实际使用中,它应该包含一个用于评估交易策略的目标函数。
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. 变异。
这是实现施韦费尔概念的关键部分。对于每个个体(除了最优个体),我们:
- 通过将一个随机数作为指数与步长相乘来变异步长。
- 通过将正态分布随机数乘以步长并加到基因上来变异一个基因。
- 将基因的值限制在[0, 100]范围内。
参数优化的施韦费尔概念的基本实现。在实际应用中,需要将目标函数适应于特定的交易策略。
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])); } } }
我们还应该注意NormalRandom()函数的实现,它是施韦费尔自适应变异概念的一部分,实现了Box-Muller方法,用于生成具有正态分布(高斯分布)的随机数。让我们分解这个函数:
1. 生成均匀分布的数。生成两个在区间[0, 1]内均匀分布的独立随机数u1和u2。
double u1 = u.RNDfromCI(0, 1); double u2 = u.RNDfromCI(0, 1);
2. 转换为正态分布。Box-Muller变换方程将均匀分布的数转换为正态分布的数。
return MathSqrt(-2 * MathLog(u1)) * MathCos(2 * M_PI * u2);
重要的是要注意,这是Box-Muller变换实现的一半,它生成一个正态分布的数。完整的变换生成两个正态分布的数:
z0 = MathSqrt(-2 * MathLog(u1)) * MathCos(2 * M_PI * u2); z1 = MathSqrt(-2 * MathLog(u1)) * MathSin(2 * M_PI * u2);
我们的实现只使用了余弦,这给出了一个正态分布的数。如果你每次调用只需要一个数,这是完全可以接受的。如果需要两个数,可以添加一个正弦计算。
这种实现在各种应用中(包括进化算法和随机优化)用于生成正态分布的随机数,既高效又广泛。
生成数的特征:
1. 分布:正态分布(高斯分布)
2. 平均值: 0
3. 标准差:1
生成数的范围:理论上,正态分布可以生成从负无穷大到正无穷大的数。在实践中:
- 大约68%的生成数将在范围[-1, 1]内。
- 大约95%的数将在范围[-2, 2]内。
- 大约99.7%的数将在范围[-3, 3]内。
在一些非常罕见的情况下,可能会有数在[-4, 4]之外 。
Box-Muller方法用于生成具有正态分布的随机数,这对于基于施韦费尔概念的算法中实现自适应变异非常重要。这种分布允许更频繁地生成较小的变化,但有时允许较大的变异,这有助于有效地探索解空间。让我们在实践中测试和评估Box-Muller方法。
让我们实现一个脚本来测试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); } }
测试脚本执行以下操作:
1. 定义NormalRandom()函数。
2. 生成指定数量的随机数(默认为10,000个)。
3. 计算基本统计特征:平均值、标准差、最小值和最大值。
4. 通过将范围[-4, 4]划分为20个区间来创建分布直方图。
5. 将结果显示在MetaTrader终端日志中。
现在让我们测试这个脚本。我们将取20,000个值。以下是运行脚本以测试Box-Muller变换方法的打印输出:
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) Statistics for 20,000 generated numbers:
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) Average value: -0.003037802901958332
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) Standard deviation: 0.9977477093538349
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) Minimum value: -3.865371560675546
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) Maximum value: 3.4797509297243994
2024.07.12 13:11:05.437 checkRND (.US500Cash,M5) Distribution histogram:
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):
从打印输出中可以清楚地看出,该方法工作正常,标准差几乎等于1,平均值是0,而分布范围对应于区间[-4;4]。
接下来,我们将继续计算变异的自适应参数,并编写相应的函数:
//—————————————————————————————————————————————————————————————————————————————— 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()); } //——————————————————————————————————————————————————————————————————————————————
基于Schwefel提出的概念,我们使用自适应变异策略来计算Cg、Cs和Cn这三个自适应参数。这些参数的计算公式如下:
1. 初始化:
- 我们有一个包含N个解的种群,其中每个解由一对向量(pi, σi)表示,其中i ∈ {0, 1, 2}分别对应三个参数Cg、Cs和Cn。
- pi分量的初始值在假定的解空间中根据均匀分布随机选择。
- σi的初始值被设置为一个固定值。
2. 后代生成:
- 对于每个父代(pi, σi),根据以下公式生成一个后代(pi', σi'):
σ'i (j) = σi (j) * exp (τ' * N (0,1) + τ * Nj (0,1))
p'i (j) = pi (j) + σi (j) * N (0,1)
其中pi (j)、p'i (j)、σi (j)、σ'i (j)分别是向量pi、p'i、σi和σ'i的第j个分量。
- N (0,1)是从均值为0、标准差为1的正态分布中随机抽取的一个数。
- Nj (0,1)是从均值为0、标准差为1的正态分布中随机抽取的一个数,其中j是一个计数器。
- τ和τ'是缩放因子,分别设置为(√(2√n))^-1和(√(2n))^-1,其中n是问题的维度。
因此,Cg、Cs和Cn这三个自适应参数会根据这种自适应策略进行变异,使它们能够在优化过程中动态调整。
从下面获得的Cg、Cs和Cn比值的打印输出中,我们可以看到在某些个别情况下,这些比值会变得过大。这是因为在更新策略参数σ的方程中,新值是通过将其与之前的值相乘得到的。这允许参数适应目标函数的复杂表面,但同时也可能导致不稳定和值的急剧跳跃。
让我们看看Cg、Cs和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
当根据Schwefel方法的自适应变异导致Cg、Cs和Cn取非常大的值时,可能需要采取措施来控制和限制这些值。这对于保持算法的稳定性和效率至关重要。有几种可能的方法可以用来限制这些比值的数值:
1. 限制值。
为Cg、Cs和Cn设置上限和下限。例如:
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. 规范化。
将变异后的参数值进行规范化,使其总和始终等于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. 对数缩放。
应用对数缩放来平滑较大的值:
double ScaleParameter (double param) { if (param == 0) return 0; double sign = (param > 0) ? 1 : -1; return sign * MathLog (1 + MathAbs (param)); }
4. 自适应变异步长缩放。
如果参数变得过大,则减小变异步长:
void AdaptMutationStep(double &stepSize, double paramValue) { if(MathAbs(paramValue) > threshold) { stepSize *= 0.9; // Reduce the step size } }
5. 周期性重置。
定期将参数重置为其初始值或种群平均值:
void ResetParameters(int generationCount) { if(generationCount % resetInterval == 0) { Cg = initialCg; Cs = initialCs; Cn = initialCn; } }
6. 使用指数函数。
应用指数函数来限制参数的增长:
double LimitGrowth(double param) { return 2.0 / (1.0 + MathExp(-param)) - 1.0; // Limit values in [-1, 1] }
7. 监控与自适应。
监控参数值并根据它们是否频繁超过可接受范围来调整变异策略:
void MonitorAndAdapt(double ¶m, int &outOfBoundsCount) { if(MathAbs(param) > maxAllowedValue) { outOfBoundsCount++; if(outOfBoundsCount > threshold) { // Adaptation of the mutation strategy AdjustMutationStrategy(); } } }
还需要记住的是,对参数施加过多的约束可能会降低算法探索解空间的能力,因此有必要在控制和灵活性之间找到适当的平衡。这些方法可以被优化爱好者们用于进一步的研究,但我在文章中描述了自己使用的GaussDistribution(高斯分布)函数。
//—————————————————————————————————————————————————————————————————————————————— 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)); } //——————————————————————————————————————————————————————————————————————————————
从下面打印出的Cg、Cs和Cn比值的获得值可以看出,在应用了我自己的函数之后,大值出现的频率远低于应用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
既然我们已经理解了Schwefel概念以及比值的自适应值,接下来让我们探讨一下算法中确定最近邻的方法。以下方法用于确定最近邻Nc的坐标:
1. 对于种群中的每个个体,都会确定其最近邻。
2. 最近邻被认为是目标函数(适应度)值与给定个体最接近的三个个体。
3. 由给定个体及其最近邻组成的群体的中心坐标,计算为这三个近邻坐标的算术平均值。
因此,Nc坐标并不是简单地取三个任意坐标,而是根据目标函数值计算出的最近邻群体的中心。这使得可以利用个体周围环境的信息来确定其下一个位置。
关键是,最近邻的确定不是基于地理上的接近性,而是基于目标函数值的接近性。这对应于社会结构中的逻辑接近性,而非地理接近性。
//—————————————————————————————————————————————————————————————————————————————— 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; } } //——————————————————————————————————————————————————————————————————————————————
方法的阐释:
- 基于目标函数(适应度)值的接近性来确定最近邻。
- 使用三个最近邻。
- 计算群体中心为这三个近邻坐标的算术平均值。
让我们来分析这个函数:
1. 排序方向:
按适应度差异的升序进行排序。
如果当前元素大于下一个元素,则它们交换位置。因此,排序后,differences数组将按从小到大的值排序,而indices数组中的相应索引将反映这种排序。
2. 函数执行以下步骤:
- 排除当前个体不考虑。
- 计算当前个体与所有其他个体之间的适应度差异。
- 按此差异对个体进行排序。
- 选择三个最近邻(适应度差异最小的)。
- 根据这三个近邻的坐标计算群体中心。
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; } }
这个版本的函数对错误具有更强的鲁棒性,并且能够正确处理种群中个体数量较少(少于3个)的情况。我们已经熟悉了算法中的基本逻辑方法,现在可以继续研究算法本身的结构。ASBO算法主要包括以下主要步骤:
1. 初始化:
- 首先定义了一个初始解集,其中每个解都以显式形式(非编码格式)表示。
- 对于每个解,都会计算其目标函数值,以确定其适应性。
- 在当前时间点,将适应性最高的解声明为全局领导者。
- 对于每个解,都会确定一个由适应性次高的解组成的最近邻群体。
2. 参数的自适应变异:
- 对于每个解,都会确定一个包含三个自适应参数的向量:Cg、Cs和Cn,它们分别负责全局领导者、个人最佳解和近邻群体中心的影响。
- 使用自适应Schwefel变异策略来更新这些参数的值。
3. 更新决策状态:
- 对于每个解,都会使用当前Cg、Cs和Cn参数的值来计算其位置的变化。
- 通过将变化量加到当前位置上,来计算新解的位置。
4. 两阶段过程
- 阶段1:创建M个独立种群,每个种群都使用ASBO算法进行固定次数的迭代处理。存储每个最终种群中每个解的适应性和参数值。
- 阶段2:从所有最终种群中选择适应性最高的解,并使用它们的参数来形成新的种群。对由此产生的新种群应用ASBO算法的基本逻辑,以获得最终解。
因此,让我们突出ASBO算法的关键特征:
- 应用自适应变异策略来动态更新参数。
- 使用领导力和近邻影响的概念来模拟社会行为。
- 采用两阶段过程来维持解的多样性并加速收敛。
在本文中,我们考虑了Schwefel概念的一个例子,即Box-Muller方法,其中涉及正态分布、自适应变异率的使用以及通过适应性值确定最近邻的函数。我们简要介绍了ASBO的结构。在下一篇文章中,我们将详细探讨人工进化的两阶段过程,完成算法的形成,对测试函数进行测试,并得出关于其效率的结论。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/15283