条所述情况。 算法说明
图 1 示意性地展示狼群的阶层结构。阿尔法阶层起着主导作用。
图例 1. 狼群中的社会阶层
数学模型和算法
社会阶层:
- 优等解是由阿尔法(α)狼形成的。
- 二等解由贝塔(β)狼形成。
- 三等解由德尔塔(δ)狼形成。
- 其它可能的解,由欧米茄伏(ω)狼形成。
包围猎物:当已经有最佳解的阿尔法、贝塔、和德尔塔在场时,进一步的行动取决于欧米茄。
图例 2. 狩猎阶段:搜索、包围、攻击。
算法的所有迭代都表现为三个阶段:搜索、包围、和狩猎。 该算法的规范版本具有引入的a 计算比率,以提高算法的收敛性。 该比率在每次迭代时递减,直至为零。 只要比率超过 1,狼群的初始化就会持续进行。 在这个阶段,猎物的位置是完全未知的,所以狼群应该是随机分布的。
在“搜索”阶段之后,判定适应度函数的值,然后才有可能进入“包围”阶段。 在此阶段,a 比率大于 1。 这意味着阿尔法、贝塔和德尔塔正在远离它们之前的位置,从而可以优调估算猎物的位置。 当 a 比率等于 1 时,“攻击”阶段开始,而比率在迭代结束前趋于 0。 这导致狼群接近猎物,表明已经找到了最佳位置。 虽然,如果在这个阶段,其中某只狼找到了更好的解,那么猎物的位置和狼群的层次结构将会更新,但比例仍然趋于 0。 变化的过程由非线性函数表示。 这些阶段示意如图例 2 所示。
欧米茄阶层的狼,其行为在所有世代都是不变的,包括遵循当前占主导地位的个体位置之间的几何中心。 在图例 3 中,阿尔法、贝塔和德尔塔在随机方向上偏离它们之前的位置,半径由系数给出,欧米茄移动到它们之间的中心,但在半径内有一定程度偏离它的概率。 半径决定了 a比率,正如我们记得的那样,它的变化导致半径成比例地减小。
图例 3. 欧米茄相对阿尔法、贝塔和德尔塔的的运动图
GWO 算法的伪代码如下:
1) 随机初始化灰狼种群。
2) 计算种群每只个体成员的体质状况。
3) 狼群领导者:
-α = 具有最佳体质值的成员
-β = 第二等强大的成员(就体质值而言)
-δ = 第三等强大的成员(就体质值而言)
根据 α、β、δ 方程更新所有欧米茄狼的位置
4) 计算种群中每个成员的体质。
5) 重复步骤 3。
我们继续讨论算法代码。 我对原始版本所做的唯一补充就是能够设置狼群中主导狼的数量。 现在,您可以设置任意数量的主导者,最多可达整个狼群。 对于特定任务这可能很实用。
如往常一样,我们从算法的基本单元 — 狼开始,这是问题的解。 这是一个包含坐标数组和猎物值(适应度函数)的结构。 对于主导者和次等成员,结构是相同的。 这简化了算法,并允许我们在循环操作中使用相同的结构。 甚至,在所有迭代过程中,狼的角色会多次变化。 角色由排序后在数组中的位置唯一确定。 主导者位于数组的开头。
//—————————————————————————————————————————————————————————————————————————————— struct S_Wolf { double c []; //coordinates double p; //prey }; //——————————————————————————————————————————————————————————————————————————————
狼群由一个紧凑且易于理解的类代表。 在此,我们声明要优化的参数范围和步长,最佳生产位置,最佳解的值,和辅助函数。
//—————————————————————————————————————————————————————————————————————————————— class C_AO_GWO //wolfpack { //============================================================================ public: double rangeMax []; //maximum search range public: double rangeMin []; //manimum search range public: double rangeStep []; //step search public: S_Wolf wolves []; //wolves of the pack public: double cB []; //best prey coordinates public: double pB; //best prey public: void InitPack (const int coordinatesP, //number of opt. parameters const int wolvesNumberP, //wolves number const int alphaNumberP, //alpha beta delta number const int epochCountP); //epochs number public: void TasksForWolves (int epochNow); public: void RevisionAlphaStatus (); //============================================================================ private: void ReturnToRange (S_Wolf &wolf); private: void SortingWolves (); private: double SeInDiSp (double In, double InMin, double InMax, double Step); private: double RNDfromCI (double Min, double Max); private: int coordinates; //coordinates number private: int wolvesNumber; //the number of all wolves private: int alphaNumber; //Alpha beta delta number of all wolves private: int epochCount; private: S_Wolf wolvesT []; //temporary, for sorting private: int ind []; //array for indexes when sorting private: double val []; //array for sorting private: bool searching; //searching flag }; //——————————————————————————————————————————————————————————————————————————————
传统上,类声明后跟初始化。 而在此,我们重置为狼群适应度的最小 “double” 值,并分配数组的大小。
//—————————————————————————————————————————————————————————————————————————————— void C_AO_GWO::InitPack (const int coordinatesP, //number of opt. parameters const int wolvesNumberP, //wolves number const int alphaNumberP, //alpha beta delta number const int epochCountP) //epochs number { MathSrand (GetTickCount ()); searching = false; pB = -DBL_MAX; coordinates = coordinatesP; wolvesNumber = wolvesNumberP; alphaNumber = alphaNumberP; epochCount = epochCountP; ArrayResize (rangeMax, coordinates); ArrayResize (rangeMin, coordinates); ArrayResize (rangeStep, coordinates); ArrayResize (cB, coordinates); ArrayResize (ind, wolvesNumber); ArrayResize (val, wolvesNumber); ArrayResize (wolves, wolvesNumber); ArrayResize (wolvesT, wolvesNumber); for (int i = 0; i < wolvesNumber; i++) { ArrayResize (wolves [i].c, coordinates); ArrayResize (wolvesT [i].c, coordinates); wolves [i].p = -DBL_MAX; wolvesT [i].p = -DBL_MAX; } } //——————————————————————————————————————————————————————————————————————————————
每次迭代时调用的第一个公开方法最令人难以理解,也是体量最庞大的。 此处是算法的主要逻辑。 事实上,该算法的性能是严格由等式描述的概率机制提供的。 我们来逐步研究此方法。 在第一次迭代中,当预期猎物的位置未知时,在检查标志后,我们只需依从来自优化参数最大值和最小值的生成值,即可将狼发往随机方向。
//---------------------------------------------------------------------------- //space has not been explored yet, then send the wolf in a random direction if (!searching) { for (int w = 0; w < wolvesNumber; w++) { for (int c = 0; c < coordinates; c++) { wolves [w].c [c] = RNDfromCI (rangeMin [c], rangeMax [c]); wolves [w].c [c] = SeInDiSp (wolves [w].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } searching = true; return; }
在算法描述的规范版本中,存在运算向量的方程。 然而,它们以代码的形式表达更加清晰。 欧米茄狼的计算是在阿尔法、贝塔、和的额尔塔狼之前进行的,因为我们需要使用前面的主导者数值。
提供三个狩猎阶段(搜索、包围、和攻击)的主要组成部分是 a 比率。 它代表对当前迭代和总迭代次数的非线性依赖性,并且趋于 0。 等式的下一个组成部分是 Ai 和 Сi
- Ai = 2.0 * a * r1 - a;
- Ci = 2.0 * r2;
其中 r1 和 r2 是在 [0.0;1.0] 范围内的随机数。
在表达式中
Xn += wolves [abd].c [c] - Ai * (Ci * wolves [abd].c [c] - wolves [w].c [c]);
狼的坐标根据领头狼的平均值进行调整。 由于可以在算法中指定任意数量的主导者,因此坐标求和是在循环中执行的。 之后,所得数额除以主导者的数量。 我们分别针对每个坐标执行此操作,每次生成新的 r1 和 r2 值。 正如我们所见,欧米茄狼的新位置是根据主导狼的位置调整的,同时考虑到它们自己当前的位置。
//---------------------------------------------------------------------------- double a = sqrt (2.0 * (1.0 - (epochNow / epochCount))); double r1 = 0.0; double r2 = 0.0; double Ai = 0.0; double Ci = 0.0; double Xn = 0.0; double min = 0.0; double max = 1.0; //omega----------------------------------------------------------------------- for (int w = alphaNumber; w < wolvesNumber; w++) { Xn = 0.0; for (int c = 0; c < coordinates; c++) { for (int abd = 0; abd < alphaNumber; abd++) { r1 = RNDfromCI (min, max); r2 = RNDfromCI (min, max); Ai = 2.0 * a * r1 - a; Ci = 2.0 * r2; Xn += wolves [abd].c [c] - Ai * (Ci * wolves [abd].c [c] - wolves [w].c [c]); } wolves [w].c [c] = Xn /= (double)alphaNumber; } ReturnToRange (wolves [w]); }
此处计算主导者。 为每个坐标计算每个坐标的 a、Ai 和 Ci 比率。 唯一的区别是主导者的位置会随着当前最佳猎物的坐标和它们自身的位置而变化。 主导者围着猎物转圈,或远或近,并在攻击中控制着次要狼群。
//alpha, beta, delta---------------------------------------------------------- for (int w = 0; w < alphaNumber; w++) { for (int c = 0; c < coordinates; c++) { r1 = RNDfromCI (min, max); r2 = RNDfromCI (min, max); Ai = 2.0 * a * r1 - a; Ci = 2.0 * r2; wolves [w].c [c] = cB [c] - Ai * (Ci * cB [c] - wolves [w].c [c]); } ReturnToRange (wolves [w]); }
这是每次迭代时调用的第二个公开方法。 在此处更新群中主导者的状态。 事实上,狼是按体质值排序的。 如果找到的猎物坐标比整个群体中保存的猎物坐标更好,那么我们就需更新数值。
//—————————————————————————————————————————————————————————————————————————————— void C_AO_GWO::RevisionAlphaStatus () { SortingWolves (); if (wolves [0].p > pB) { pB = wolves [0].p; ArrayCopy (cB, wolves [0].c, 0, 0, WHOLE_ARRAY); } } //——————————————————————————————————————————————————————————————————————————————
3. 测试函数
您已经知道 Skin、Forest 和 Megacity 等函数。 这些测试函数满足测试优化算法的所有复杂性标准。 不过,有一个功能没有被考虑在内。 它理应实现,以便提高测试的客观性。 其需求如下:
- 全局极值不应位于范围的边界上。 如果算法没有超界检查,则可能会出现的状况就是算法展示出完美的结果。 而事实上这是由于内部缺陷,这些数值都位于边界上。
- 全局极值不应位于范围坐标的中心。 在这种情况下,算法将考虑在某个范围内生成平均值。
- 全局最小值应位于坐标中心。 这是必要的,以便排除第 2 条所述情况。
- 测试函数结果的计算应考虑以下情况:在整个函数域(当函数是多变量时)随机生成的数字将给出大约最大值 50% 的平均结果,尽管实际上这些结果是偶然获得的。
考虑到这些需求,修改了测试函数的边界,并将范围的中心偏移到函数值的最小值。 我再次总结一下。 如此做是必要的,以便所获测试优化算法的结果合理性和客观性最大化。 因此,在新测试函数上,基于随机数生成的优化算法很自然地展现出较低的总体结果。 更新后的评级表位于文章末尾。
Skin 函数。 具有多个局部极值的平滑函数,可能会混淆优化算法,因为它可能会在其中一个极值处卡顿。 唯一的全局极值的特征是其附近的数值变化较弱。 这个函数清楚地显示了该算法可被切分到正在研究领域的能力,远不止专注于单个领域。 特别是,蜂群(ABC)算法的行为方式。