科学家群体优化(CoSO):理论
目录
概述
在本文中,我们将继续探讨优化方法,并考虑另一种解决优化问题的方法 — 基于模拟科学界机制的 CoSO(科学家群体优化)算法。与传统的仿生算法不同,CoSO 复制了科学活动的独特特征:在期刊上发表成果、竞争资助、组建研究团队,以及在深入研究已知领域与寻找根本性新解决方案之间取得平衡。CoSO 算法由两位科学家 A. Milani 和 V. Santucci 于 2012 年开发并发表。
这种方法的一个有趣特点是搜索过程的自然自组织性。如同真正的科学界一样,该算法将资源集中在有前景的领域,通过期刊机制存储和传播最佳解决方案,并通过资助“外来研究者”来保持必要的多样性。动态改变种群规模使算法能够适应特定问题的具体情况,而无需对参数进行微调,这是另一项创新,因为我们通常使用固定种群。在本文中,我们将详细探讨该算法的数学基础、关键组成部分以及它们之间的交互机制。
算法的实现
CoSO 群体算法基于这样一种理念:模拟研究人员在多维空间中寻找最优解的行为,他们通过在期刊上发表论文来交流知识,并竞争有限的资源(如资助)。每位研究人员都对应搜索空间中的一个点,具有以下属性:一个搜索位置、一个移动方向以及用于研究的资金。如果资金耗尽,研究人员就会变得不活跃。
每个研究人员都由一组参数表示:搜索空间中的位置( x )、移动方向( v )、个人最佳结果( b )、资金量( m )、资金管理策略( s )以及选择不同期刊的概率( ρ )。
研究人员的运动由方向更新公式决定,该公式包含三个组成部分:惯性(继续朝同一方向移动的倾向)、认知成分(追求个人最佳成果的愿望)和社会成分(期刊最佳成果的影响):
v {i, t} = ω · v {i, t-1} + φ₁ · β₁ · (b {i, t-1} - x {i, t-1}) + φ₂ · β₂ · Σⱼ [ρᵢⱼ · (J {j,c} - x {i, t-1})],
其中 ω = 0.7298 — 惯性系数,φ₁ = φ₂ = 1.49618 — 加速度系数,β₁ 和 β₂ — [0,1] 中的随机数,ρᵢⱼ — 研究员 i 选择期刊 j 的概率,J {j,c} — 期刊 j 中的随机文章。
更新方向后,研究人员的位置根据一个简单的公式发生变化: x {i, t} = x {i, t-1} + v {i, t} 。
期刊收录了找到的最佳解决方案。研究人员阅读期刊,并朝着优良解的方向移动,只有最佳成果才会被发表在期刊上。算法中的期刊作为集体记忆,存储所有研究人员找到的 k 个最佳解决方案。当研究人员获得新结果时,他们会根据概率分布 ρ 将其提交给其中一家期刊,该期刊仅接受优于已发表结果中最差结果的新结果。这确保了社区可获取信息的质量能够持续提高。
成功的研究人员会获得更多的资助。部分资金流向了“外来研究者” — 即随机分布的新研究人员。这有助于避免陷入局部最优解。资金分配机制效仿了科学领域的竞争性资助模式。在每次迭代中,研究人员会花费一个单位的资金,然后根据排名轮盘选择重新分配所花费的总金额。全局排名中第 i 位的研究人员获得资助的概率由以下公式确定:
P(i) = (N - i + 1) / (N · (N + 1) / 2),
其中 N 为研究人员的数量。
Ω 基金的一部分被预留用于培养“外来研究者” — 位于随机位置的新研究人员。Ω 参数会自适应地改变,以保持探索和开发之间的平衡。当种群适应度值的标准差 σ 小于初始值 σ₀ 时,这表明种群趋于收敛,外来研究者的比例增加:
对于 σ < σ₀ :Ω {t} = Ω {t-1} + (Ω {max} - Ω {min}) / 2 · ε⁺,否则:Ω {t} = Ω {t-1} - (Ω {max} - Ω {min}) / 2 · ε⁻,
其中,Ω {min} = 0.2,Ω {max} = 0.5,ε⁺ = 0.2,ε⁻ = 0.1。
成功的科学家可以雇佣助手,这些助手会在其导师附近开始搜索。这有助于详细探索有前景的领域。资金 m > 1 的成功研究人员可以聘请新的研究人员。在这种情况下,研究人员根据自己的策略,将一部分资金留给自己,其余的资金用于招聘。
新研究人员继承了导师的特征,并略作调整:职位初始化为x {new} = x {supervisor} + N (0, σv) ,策略初始化为s {new} = N (s {supervisor}, σs) ,期刊概率初始化为ρ {new, j} = N (ρ {supervisor, j}, σρ),随后进行归一化。
该算法中的搜索过程可以比作一群人在森林中采摘蘑菇:每个人都在自己的地方搜索,当有人发现蘑菇点时,他们会向其他人喊话,然后部分人前往该地点,其他人则继续寻找新的地方。那些长时间一无所获的人会选择回家,但最成功的采蘑菇人会带着朋友一起去蘑菇点。
该算法的性能由多种机制共同驱动:期刊促进信息共享并将搜索引向有前景的领域,竞争性资助通过淘汰无效的研究人员来创造选择压力,聘用助理确保在优秀解决方案周围进行局部搜索,而外来研究者则防止过早收敛。
动态调整种群规模使算法能够适应问题的复杂性,在有前景的领域增加研究人员数量,在无前景的领域减少研究人员数量。所有这些机制共同作用,形成了一个能够在复杂多维空间中解决问题的自组织系统。
图1.CoSO 算法运行中
图中展示了以下内容:
- 搜索空间中,研究人员(绿色圆圈)朝着最优解移动,他们的运动矢量已显示。
- 科学期刊收录了发现的最佳解决方案,研究人员阅读这些期刊并发表他们的研究成果。
- 资金分配 — 一种在多维空间中的融资机制,具有面向外部参与者的 Ω 自适应参数。
- 更新方向的公式 — 研究人员行动的数学基础。
- 关键机制 — 研究人员类型图例:活跃(有资金)、不活跃(没有资金)、新聘人员和随机位置的外来研究者。
箭头表示信息(灰色)、人员流动(蓝色)和资金(橙色)的流动。现在让我们用伪代码来概括该算法。
1.初始化:
- 随机创建 10 名研究人员
- 给每个人15个单位的资金(150 / 10)
- 每人获得:
* 随机储蓄策略(0 到 1 之间)
* 随机的期刊偏好
* 初始移动方向(小幅随机)
- 创建 3 个空白期刊
- 记住最初的种群分布情况
2.主循环(重复指定次数):
2.1.结果评估:
对于每一位活跃的研究人员:
- 计算当前位置 f(x) 的质量
- 如果结果优于个人纪录:
* 更新个人记录
2.2.在期刊上发表文章:
对于每一位活跃的研究人员:
- 根据个人喜好选择期刊
- 尝试发布结果:
* 如果结果位列前10,该期刊将予以接受。
* 最差的文章被淘汰
2.3.成本核算与报告:
- 收集所有已使用的资金(每种收集1份)
- 编制一份在世研究人员排名
- 将那些没钱的人标记为“非活跃”
2.4.资金分配:
- 确定外来研究者的份额(20-50%)
- 将剩余部分分配给现有研究人员:
* 排名越高,机会越大
* 使用加权轮盘选择
2.5.创建外来研究者:
- 将分配的资金用于引进 1-5 名新人
- 将它们随机放置
- 将分配的预算均分。
2.6.招聘助理:
对于每位富裕的研究人员(资金 > 1):
- 根据策略为自己保留一部分
- 其余资金可雇佣 1-3 名助理:
* 放在自己旁边
* 将自身经验以轻微扰动的形式传递给助理
2.7.移动方向选择:
对每位活跃的研究人员,计算:
新方向 =
0.7 × 旧方向 + // 惯性
1.5 × 随机数 × (个人记录 - 位置) + // 个人最佳
1.5 × 随机性 × 按期刊求和 // 最佳期刊
(期刊权重 × (期刊文章 - 位置))
2.8.移动:
对于每一位活跃的研究人员:
- 迈出一步:新位置 = 旧位置 + 方向
- 如果超出边界,就将其移回。
2.9.多样性控制:
- 测量当前种群离散程度
- 如果种群离散度减小(趋于收敛):
* 将外来研究者比例提高10%
- 否则:
* 将外来研究者比例降低 5%
2.10.种群规模管理:
- 如果非活跃人数超过 25% 或总数超过 200 人:
* 从数组中移除已失活的研究人员
- 如果剩余数量超过150:
* 只保留前150名
3.结果:
- 返回找到的最佳解决方案
现在我们可以继续讨论 CoSO 算法的实际实现了。让我们编写三个数据结构。第一个结构体 S_Journal_Entry 表示一条包含以下内容的期刊条目:
- fitness — 一个反映给定条目“质量”或“适应度”的数值。
- decision[] — 动态数组,用于存储与此条目关联的参数。
- entries [] — S_Journal_Entry 条目的动态数组。
- length — 期刊中当前的条目数。
- maxLength — 期刊可以存储的最大记录数。
Init 方法通过设置 maxLength、为 entries 分配内存以及使用最小适应度值初始化每个条目来初始化期刊。Add 方法用于向期刊中添加新条目。该方法的特点是,它按照适应度值对期刊进行排序。如果新记录比已填写的期刊中最差的记录还要差,则不会将其添加进去。否则,使用二分查找找到新条目的空间,然后将现有条目移出以腾出空间,并插入新条目;如果期刊尚未满,则增加其 “length”。
第三个结构 S_Researcher 描述的是“研究人员”或“代理”。它包含一组参数:
- x[] — 研究人员的当前位置(坐标数组)
- v [] — 研究人员运动的方向或速度矢量
- b [] — 给定研究人员单独找到的最佳位置
- rho [] — 不同期刊的发表概率数组。研究人员与多个“期刊”或信息来源进行互动。
- s — 资金管理策略
- m — 资金数额(整数)
- f — 当前研究人员职位的适应度
- fb — 研究人员发现的最佳位置的适应度
- alive — 指示研究人员活动的标志
//———————————————————————————————————————————————————————————————————— // Structure for the journal struct S_Journal_Entry { double fitness; double decision []; void Init (int coords) { ArrayResize (decision, coords); fitness = -DBL_MAX; } }; // Journal structure struct S_Journal { S_Journal_Entry entries []; int length; int maxLength; void Init (int maxLen, int coords) { maxLength = maxLen; length = 0; ArrayResize (entries, maxLen); for (int i = 0; i < maxLen; i++) { entries [i].Init (coords); entries [i].fitness = -DBL_MAX; // Initialize with the minimum value } } void Add (double fit, const double &coord []) { // Quick check - if it is the worst and the journal is full, do not add if (length >= maxLength && fit <= entries [length - 1].fitness) return; int insertPos = length; // Find the insertion position (binary search) if (length > 0) { int left = 0; int right = length - 1; while (left <= right) { int mid = (left + right) / 2; if (entries [mid].fitness < fit) right = mid - 1; else left = mid + 1; } insertPos = left; } // If we insert at the end and the journal is full if (insertPos >= maxLength) return; // Shift the elements if (length < maxLength) length++; for (int i = length - 1; i > insertPos; i--) { entries [i].fitness = entries [i - 1].fitness; ArrayCopy (entries [i].decision, entries [i - 1].decision, 0, 0, WHOLE_ARRAY); } // Insert a new element entries [insertPos].fitness = fit; ArrayCopy (entries [insertPos].decision, coord, 0, 0, WHOLE_ARRAY); } }; // Extended researcher structure struct S_Researcher { double x []; // current position double v []; // movement direction double b []; // personal best result double rho []; // probabilities of publication in journals double s; // funds management strategy int m; // amount of funds double f; // fitness of the current position double fb; // fitness of the best position bool alive; // activity flag void Init (int coords, int journalsNum) { if (ArraySize (x) != coords) { ArrayResize (x, coords); ArrayResize (v, coords); ArrayResize (b, coords); } if (ArraySize (rho) != journalsNum) { ArrayResize (rho, 0); ArrayResize (rho, journalsNum); } f = -DBL_MAX; fb = -DBL_MAX; m = 0; s = 0.5; alive = true; } };
让我们编写 C_AO_CoSO 类,它是“科学家群体优化”(CoSO)优化算法的实现。C_AO_CoSO 类继承自 C_AO 基类,并假定存在各种优化算法的通用属性和方法。
已设置各种 CoSO 控制参数的默认值:
- “研究人员”群体的初始规模;
- 可分配的“资金”(资源)总额(用科学家的比喻来说,这可以是他们用于“工作”(寻找解决方案)的拨款);
- 研究人员可以发表其“发现”的“期刊”数量(最佳解决方案);
- 一份“期刊”可以包含的文章/发现的最大数量;
- “惯性”参数,用于控制先前运动方向的影响。
- phi1 = 1.5 — 认知参数反映了个人对研究者行为的影响(渴望取得自己最好的发现);
- φ2 = 1.5 — 社会因素决定了研究人员对期刊论文发表的关注程度;
- omegaMin = 0.2 — “外来研究者”的最低百分比;
- omegaMax = 0.5 — “外来研究者”的最大百分比;
- epsilonPlus = 0.2 — 多样性增加步长;
- epsilonMinus = 0.1 — 多样性减少步长。
init() — 算法初始化方法。它接受每个参数的搜索范围(最小值、最大值)和步长,以及 epochsP 的数量。
Moving() — 方法负责根据研究人员的“速度”和“方向”在搜索空间中“移动”(更新位置)。
Revision() — 方法负责更新所有研究人员截至目前为止找到的最佳全局解决方案。
私有字段:S_Researcher 结构的动态数组。该结构的每个元素代表一个“研究人员”,并包含有关其当前位置、速度、找到的最佳解决方案、“存活”状态、“适应度”(f)等信息。S_Journal 结构的动态数组,其中每个结构存储一组由研究人员在给定的“期刊”中发表的最佳解决方案。目前“外来研究者”所占比例。初始标准偏差。当前实际种群规模。它在算法执行过程中可能会发生变化。最大允许种群规模。
struct S_GlobalReport — 一个嵌套结构,用于存储有关最佳全局解决方案的信息:“fitness”(目标函数的值)和 “index”(找到此解决方案的研究人员的索引)。
S_GlobalReport globalReport [] —用于存储全局报表的数组。
私有算法方法:
- UpdateDirection () — 更新索引为 idx 的特定研究人员的移动方向(或“速度”)。
- SubmitToJournal () — 允许拥有索引的研究人员在“期刊”之一中“发表”他们当前的最佳解决方案。
- AssignFunds () — 用于在研究人员之间分配“资金”的方法。在CoSO中,资助可能会影响研究人员的搜索行为或能力。
- HireResearchers() — 培养新的研究人员的方法。
- CreateOutsiders () — 创建“外来研究者”,即特殊的研究人员,他们可以进行更积极或随机的搜索,以避免陷入局部最优解。
- ComputeStdDev () — 计算总体的标准差,作为衡量总体“多样性”或“趋同性”的指标。
- UpdateOmega () — 更新当前的 omegaCurrent 参数。
- SelectJournal() — 根据给定的概率选择要发表的期刊。
- NormalizeProbabilities () — 将概率数组归一化,使其总和等于 1。
- CompactPopulation () — 用于“压缩”种群的方法,移除效率最低的研究人员,即控制种群规模。
//———————————————————————————————————————————————————————————————————— class C_AO_CoSO : public C_AO { public: //---------------------------------------------------------- ~C_AO_CoSO () { } C_AO_CoSO () { ao_name = "CoSO"; ao_desc = "Community of Scientist Optimization"; ao_link = "https://www.mql5.com/en/articles/18886"; popSize = 30; // initial population size totalFunds = 150; // total amount of funds journalsNum = 3; // number of journals journalLen = 10; // journal length omega = 0.7; // inertia parameter ArrayResize (params, 5); params [0].name = "popSize"; params [0].val = popSize; params [1].name = "totalFunds"; params [1].val = totalFunds; params [2].name = "journalsNum"; params [2].val = journalsNum; params [3].name = "journalLen"; params [3].val = journalLen; params [4].name = "omega"; params [4].val = omega; //---------------------------------------------------------------- phi1 = 1.5; // cognitive parameter phi2 = 1.5; // social parameter omegaMin = 0.2; // minimum percentage of outsiders omegaMax = 0.5; // maximum percentage of outsiders epsilonPlus = 0.2; // diversity increase step epsilonMinus = 0.1; // diversity reduction step } void SetParams () { popSize = (int)params [0].val; totalFunds = (int)params [1].val; journalsNum = (int)params [2].val; journalLen = (int)params [3].val; omega = params [4].val; } bool Init (const double &rangeMinP [], const double &rangeMaxP [], const double &rangeStepP [], const int epochsP = 0); void Moving (); void Revision (); //------------------------------------------------------------------ int totalFunds; // total amount of funds int journalsNum; // number of journals int journalLen; // journal length double omega; // inertia parameter double phi1; // cognitive parameter double phi2; // social parameter double omegaMin; // minimum percentage of outsiders double omegaMax; // maximum percentage of outsiders double epsilonPlus; // diversity increase step double epsilonMinus; // diversity reduction step private: //--------------------------------------------------------- S_Researcher researchers []; // array of researchers S_Journal journals []; // array of journals double omegaCurrent; // current percentage of outsiders double sigma0; // initial standard deviation int actualPopSize; // current population size int maxPopSize; // maximum population size double socialComponent []; // cache for the social component struct S_GlobalReport { double fitness; int index; }; S_GlobalReport globalReport []; // Algorithm methods void UpdateDirection (int idx); void SubmitToJournal (int idx); void AssignFunds (int availableFunds); void HireResearchers (int idx); void CreateOutsiders (int outsiderFunds); double ComputeStdDev (); void UpdateOmega (); int SelectJournal (const double &probs []); void NormalizeProbabilities (double &probs []); void CompactPopulation (); }; //————————————————————————————————————————————————————————————————————
Init 方法是 C_AO_CoSO 类的一部分,负责在算法开始运行之前准备所有必要的结构和参数。首先,进行标准初始化,包括搜索参数的范围(最小值、最大值和步长),并设置迭代次数(epochsP)。如果此标准初始化失败,则该函数返回 “false”。然后初始化算法的内部状态变量:
- actualPopSize — 当前研究人群的规模,
- maxPopSize — 最大允许种群规模(或分配给研究人员的内存),
- sigma0 — 标准差的初始值,稍后将进行计算。
- omegaCurrent — 当前 “omega” 参数,初始化为 omegaMin 和 omegaMax 之间的算术平均值。
接下来是检查输入参数的有效性:
- totalFunds — “资金”总额不应少于种群规模,每个研究人员应分配一定数量的资金或拨款;
- journalsNum — “期刊”的数量至少应为 1;
- journalLen — 每部“期刊”的长度也至少应为1;
- omega(全局参数)— 应在 0.0 到 1.0 的范围内。
在检查参数后,会进行“清除之前运行的所有内容”,即将动态数组 “researchers”、“journals”、“globalReport” 和 “socialComponent” 的大小重置为零;算法总是从空白状态开始。前面提到的状态变量(actualPopSize、maxPopSize、sigma0)再次重置。
然后初始化 “journals” 数组:根据 journalsNum 调整 “journals” 数组的大小。该数组中的每个期刊都初始化为给定的 journalLen 长度和给定数量的 “coords” 坐标。
研究人员初始化。maxPopSize 设置为至少三倍 popSize(种群规模)。这样就为种群规模可能的变化(算法会创建新的研究人员)创建了一个“储备”。研究人员人均资金数额(fundsPerResearcher)的计算方法是:将分配给每位研究人员的资金数额(资金总额除以人口规模)。接下来是所有研究人员轮流参与的环节。
每个研究人员都初始化有若干个 “coords”(坐标)和若干个journalsNum(期刊数量)。只有前几个 popSize 研究人员的 “alive” 标志被设置为 “true”,其余研究人员仍处于非活跃状态。活跃研究人员将获得按计算出的每位研究人员的资金数额分配的资金。他们的“基金管理策略”是随机初始化的。他们的位置 “x”、最佳个人位置 “b” 和运动矢量 “v” 已初始化。'x' 和 'b' 的位置在给定的范围内随机设置(rangeMin - rangeMax,考虑到 rangeStep)。“v” 运动矢量被初始化为服从正态分布的一个小的随机值。每个期刊的 “rho” 概率均初始化为随机值。
调用 NormalizeProbabilities 函数,对这些概率进行归一化。初始化所有研究人员后,计算初始标准差 sigma0,该标准差表征研究人员位置在所有坐标上的分布情况。如果 sigma0 恰好为 0(例如,如果所有研究人员都处于同一点),则将其设置为 1.0 以防止除以 0。omegaCurrent 参数再次设置为 omegaMin 和 omegaMax 的平均值。
最后,将所有已初始化的研究人员(popSize)的位置复制到 “a” 的“标准代理数组”中。因此,Init 函数完全为优化算法的运行准备好环境,初始化所有“研究者”代理、它们的参数、用于存储结果的“日志”以及用于控制算法的内部参数。
//———————————————————————————————————————————————————————————————————— bool C_AO_CoSO::Init (const double &rangeMinP [], const double &rangeMaxP [], const double &rangeStepP [], const int epochsP = 0) { if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false; //------------------------------------------------------------------ // Initialize state variables actualPopSize = 0; maxPopSize = 0; sigma0 = 0; omegaCurrent = omegaMin + (omegaMax - omegaMin) / 2.0; // Check the parameters validity if (totalFunds < popSize) totalFunds = popSize; if (journalsNum < 1) journalsNum = 1; if (journalLen < 1) journalLen = 1; if (omega < 0.0) omega = 0.0; if (omega > 1.0) omega = 1.0; // Complete reset of data from previous runs ArrayResize (researchers, 0); ArrayResize (journals, 0); ArrayResize (globalReport, 0); ArrayResize (socialComponent, 0); // Reset parameters actualPopSize = 0; maxPopSize = 0; sigma0 = 0; // Initialize the journals ArrayResize (journals, journalsNum); for (int i = 0; i < journalsNum; i++) { journals [i].Init (journalLen, coords); } // Initialize the array of researchers with a reserve maxPopSize = MathMin (popSize * 3, 300); // Limit the maximum size ArrayResize (researchers, maxPopSize); ArrayResize (socialComponent, coords); actualPopSize = popSize; int fundsPerResearcher = totalFunds / popSize; for (int i = 0; i < maxPopSize; i++) { researchers [i].Init (coords, journalsNum); researchers [i].alive = (i < popSize); if (i < popSize) { researchers [i].m = fundsPerResearcher; researchers [i].s = u.RNDprobab (); // Initialize a position for (int c = 0; c < coords; c++) { researchers [i].x [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]); researchers [i].x [c] = u.SeInDiSp (researchers [i].x [c], rangeMin [c], rangeMax [c], rangeStep [c]); researchers [i].b [c] = researchers [i].x [c]; researchers [i].v [c] = u.GaussDistribution (0.0, -0.01, 0.01, 1); } // Initialize journal probabilities for (int j = 0; j < journalsNum; j++) { researchers [i].rho [j] = u.RNDprobab (); } NormalizeProbabilities (researchers [i].rho); } } // Calculate the initial standard deviation sigma0 = ComputeStdDev (); if (sigma0 == 0) sigma0 = 1.0; // Protection against zero divide omegaCurrent = omegaMin + (omegaMax - omegaMin) / 2.0; // Copy the researchers to the standard array of agents for (int i = 0; i < popSize; i++) { ArrayCopy (a [i].c, researchers [i].x, 0, 0, WHOLE_ARRAY); } return true; } //————————————————————————————————————————————————————————————————————
移动方法是算法迭代过程的核心部分,它执行一个搜索步骤以找到最优解,模拟“研究人员”的行为及其交互。在函数开始时,如果这是初始化后(revision = false)的第一次调用,则它只是将 “revision” 设置为 “true” 并退出。以下是算法在每次迭代中执行的主要步骤。
研究人员适应度更新。 对于每个活跃的研究者(来自当前群体),其适应度值取自通用的 'a' 数组,该数组存储了所有智能体的适应度评估结果。然后检查研究人员当前的适应度是否优于他们之前记录的最佳个人适应度,如果是,则更新 fb,并将研究人员的最佳位置 b 也存储为他们当前的位置 x。
将研究成果投稿至期刊。 每位活跃的研究人员都会将他们当前的结果(当前的适应度和位置)“提交”给其中一本“期刊”。此过程的细节被封装在 SubmitToJournal 函数中,该函数根据研究人员的 'rho' 概率选择期刊,如果结果足够好,则将其添加到该期刊中。
收集全局报告并计算可用资金。 在此步骤中,将重新评估研究人员的资格,并准备资源进行分配。对于每位活跃的研究人员,他们的“资金”(m)数量减少 1,象征着当前迭代的成本。可用资金总额正在增加。如果研究人员在花费资金后还有剩余资金(m > 0),则认为他们能够继续工作,并将被纳入“全局报告”。如果资金耗尽,研究人员就会变成“非活跃”(alive = false)。生成 globalReport — 一个数组,其中包含仅那些仍然活跃且拥有资金的研究人员的适应度和索引。
对全局报告进行排序。 globalReport 中的项目使用简单的冒泡排序算法,按“适应度”值降序排列(从最好到最差)。这样就能迅速识别出最成功的研究人员。
资金分配。 AssignFunds 函数用于在活跃研究人员之间重新分配可用资金,其中最优秀的研究人员(由 globalReport 确定)将获得更多资金,以激励他们继续活跃,而不太成功的研究人员可能会获得较少的资金或一无所获,这可能会导致他们在未来的迭代中“消亡”。
现有研究人员招募新成员。 现有研究人员如果还有充足的资金(m > 1),就可以“聘用”新的研究人员。这是增加种群或基于现有成功个体创建新个体的机制。详情请参阅 HireResearchers 方法。
更新方向和位置。 对于每个活跃的研究人员,都会调用 UpdateDirection 函数,该函数根据研究人员的个人最佳结果 “b”、期刊中的最佳结果、社会成分或其他算法因素来计算研究人员新的 “v” 移动向量。研究人员的位置 “x” 通过加上运动矢量 “v” 进行更新。检查位置是否存在超出范围的错误,如有必要则进行调整,并且将其“离散化”或“四舍五入”到最近的可接受 rangeStep。
更新多样性参数。 调用 UpdateOmega 函数,该函数调整算法的 omegaCurrent 参数。该参数用于控制探索(寻找新区域)和利用(改进已找到的解决方案)之间的平衡,并在算法运行期间动态改变该参数,以保持种群多样性。
种群压缩。 调用 CompactPopulation 函数从 “researchers” 数组中移除不活跃(alive = false)的研究人员,从而有效地减少其大小并释放内存。
将位置复制到代理数组。 最后,将所有活跃研究人员的当前位置复制到 ‘a’ 数组中。'a' 的大小根据 actualPopSize 进行更新。全局 popSize 变量也更新为新的 actualPopSize。
因此,移动功能概括了 CoSO 算法迭代的完整生命周期,包括评估、社交互动(“期刊”)、资源管理、动态种群变化以及代理在搜索空间中的移动。
//———————————————————————————————————————————————————————————————————— void C_AO_CoSO::Moving () { if (!revision) { revision = true; return; } //--- CoSO basic steps: // 1. Updating 'fitness' of researchers from the agents array int aSize = ArraySize (a); for (int i = 0, j = 0; i < actualPopSize && j < aSize; i++) { if (!researchers [i].alive) continue; researchers [i].f = a [j].f; // Update the personal best if (researchers [i].f > researchers [i].fb) { researchers [i].fb = researchers [i].f; ArrayCopy (researchers [i].b, researchers [i].x, 0, 0, WHOLE_ARRAY); } j++; } // 2. Submit results to journals for (int i = 0; i < actualPopSize; i++) { if (researchers [i].alive) SubmitToJournal (i); } // 3. Collect a global report and calculate available funds int availableFunds = 0; int reportSize = 0; // Preliminary calculation of the report size for (int i = 0; i < actualPopSize; i++) { if (!researchers [i].alive) continue; researchers [i].m--; // Spend 1 unit of funds per iteration availableFunds++; if (researchers [i].m > 0) reportSize++; else researchers [i].alive = false; } // Fill out the global report ArrayResize (globalReport, reportSize); int idx = 0; for (int i = 0; i < actualPopSize && idx < reportSize; i++) { if (researchers [i].alive && researchers [i].m > 0) { globalReport [idx].fitness = researchers [i].f; globalReport [idx].index = i; idx++; } } // 4. Quick sorting of a global report for (int i = 0; i < reportSize - 1; i++) { for (int j = i + 1; j < reportSize; j++) { if (globalReport [i].fitness < globalReport [j].fitness) { S_GlobalReport temp = globalReport [i]; globalReport [i] = globalReport [j]; globalReport [j] = temp; } } } // 5. Distribution of funds AssignFunds (availableFunds); // 6. Hire new researchers by existing ones for (int i = 0; i < actualPopSize; i++) { if (researchers [i].alive && researchers [i].m > 1) HireResearchers (i); } // 7. Update the direction and position for each researcher for (int i = 0; i < actualPopSize; i++) { if (!researchers [i].alive) continue; UpdateDirection (i); // Update position for (int c = 0; c < coords; c++) { researchers [i].x [c] += researchers [i].v [c]; // Boundary control if (researchers [i].x [c] < rangeMin [c]) researchers [i].x [c] = rangeMin [c]; if (researchers [i].x [c] > rangeMax [c]) researchers [i].x [c] = rangeMax [c]; researchers [i].x [c] = u.SeInDiSp (researchers [i].x [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } // 8. Update the diversity parameter UpdateOmega (); // 9. Population compactification CompactPopulation (); // 10. Copy positions to the array of agents to calculate 'fitness' ArrayResize (a, actualPopSize); idx = 0; for (int i = 0; i < maxPopSize && idx < actualPopSize; i++) { if (researchers [i].alive) { a [idx].Init (coords); ArrayCopy (a [idx].c, researchers [i].x, 0, 0, WHOLE_ARRAY); idx++; } } popSize = actualPopSize; // Update the population size } //————————————————————————————————————————————————————————————————————
UpdateDirection 函数负责更新 CoSO 算法中单个研究人员的 “v” 运动矢量。这一过程对于研究人员在搜索空间中的移动至关重要,它使他们能够探索新的领域,并利用自己和其他研究人员获得的信息。该函数以 idx 参数作为输入。这是需要更新研究方向的研究人员索引。
在函数开始时,生成两个介于 0 和 1 之间的随机变量:beta1 用作个人成分的系数,beta2 用作社会成分的系数。接下来,我们计算社会成分,而 socialComponent 数组会被重置,以确保在每次新的计算之前它是干净的。
然后,该循环遍历算法可用的所有“期刊”,并检查每个期刊是否包含条目(length > 0),因为空的期刊无法为社会组件提供信息。如果日志不为空,则从中随机选择一个条目(entryIdx)。
对于每个坐标(搜索空间的维度),社会组件都会更新。选定期刊条目的贡献度计算为研究人员选择该期刊的概率 (rho[j]) * (期刊位置 - 研究人员当前位置)。将所有期刊的贡献汇总起来,形成社会信息对研究人员流动的总体影响。计算社会分量后,研究人员的运动矢量得到更新,新的运动矢量(v[c])= 惯性分量+个人分量+社会分量。
让我们逐一分析:
惯性分量: omega * researchers [idx]. v [c]。这里 “omega”(在 Init 函数中设置的 omegaCurrent 参数,并在 Moving 中更新)是惯性系数。它决定了研究人员在多大程度上会保持他们之前的运动方向。数值越大,意味着研究者将继续朝着同一方向前进;数值越小,意味着在其他因素的影响下,研究者会更快地改变方向。
个人成分: phi1 * beta1 * (researchers [idx]. b [c] — researchers [idx]. x [c]), 其中 phi1 是与个人经验相关的加速因子,beta1 是增加随机性的随机因子。
(researchers [idx]. b [c] — researchers [idx]. x [c]) 是一个向量,从研究人员的当前位置指向他们之前找到的最佳个人位置 (fb)。这一环节将研究人员拉回到他们自己曾经取得成功的地方。
- 保持一定的惯性运动;
- “记住”并回到他们自己最好的发现;
- 研究并借鉴其他研究人员的成功研究成果。
因此,UpdateDirection 会计算出研究人员的新运动矢量,该矢量将在下一步中用于实际更新其在搜索空间中的位置。
//———————————————————————————————————————————————————————————————————— void C_AO_CoSO::UpdateDirection (int idx) { double beta1 = u.RNDprobab (); double beta2 = u.RNDprobab (); // Social component ArrayInitialize (socialComponent, 0); for (int j = 0; j < journalsNum; j++) { if (journals [j].length > 0) { int entryIdx = u.RNDminusOne (journals [j].length); for (int c = 0; c < coords; c++) { socialComponent [c] += researchers [idx].rho [j] * (journals [j].entries [entryIdx].decision [c] - researchers [idx].x [c]); } } } // Update direction for (int c = 0; c < coords; c++) { researchers [idx].v [c] = omega * researchers [idx].v [c] + phi1 * beta1 * (researchers [idx].b [c] - researchers [idx].x [c]) + phi2 * beta2 * socialComponent [c]; } } //————————————————————————————————————————————————————————————————————
SubmitToJournal 函数描述了单个研究人员将其当前研究成果“发表”到可用“期刊”之一的过程。这是 CoSO 算法中信息传播和社会学习的关键机制。该函数接受 idx 参数 — 即希望向期刊提交研究成果的研究人员的索引。该函数操作包括两个主要步骤:
选择期刊。 首先,调用 SelectJournal 辅助函数。该函数以研究人员的 “rho” 数组作为输入。“rho” 数组(代表概率或偏好)决定了给定研究人员将其研究成果提交给特定期刊的可能性。SelectJournal 函数根据这些概率随机选择一个可用的期刊,并返回其索引(journalIdx)。
在期刊中添加条目。 。选定期刊后,将调用该期刊的 Add 方法。该方法需要传递两个参数:
- researchers [idx]. f — 研究人员的当前适应度值。这就是它所找到的解决方案的“质量”或“成功”。
- researchers [idx]. x — 研究人员的当前位置(在搜索空间中的坐标)。这是产生该适应度值的解决方案。
所选期刊(通过 journalIdx 索引选择的期刊)中的 Add 方法负责实际添加此信息。因此,该期刊保存了众多研究人员发现的集体知识或最佳实践。
总的来说,SubmitToJournal 模拟发表科研成果,研究人员可以选择将他们的成果提交到哪里,然后其他人就可以查看这些成果。这样,算法就可以积累成功解决方案,并将其分发给各个代理群体。
//———————————————————————————————————————————————————————————————————— void C_AO_CoSO::SubmitToJournal (int idx) { int journalIdx = SelectJournal (researchers [idx].rho); journals [journalIdx].Add (researchers [idx].f, researchers [idx].x); } //————————————————————————————————————————————————————————————————————
SelectJournal 函数旨在根据指定的概率从一组可用的期刊中选择一个期刊。这是轮盘赌选择或比例选择的一种实现方式,其中每份期刊都有一定的“权重”或“偏好”。该函数接受一个参数:probs[] - 一个浮点数数组,表示选择每种期刊的概率或相对偏好。
该函数的工作原理如下:首先,生成一个随机数 rnd,该随机数均匀分布在 0(含)和 1(不含)之间,该随机数将用于确定要选择哪个期刊。cumSum 变量初始化为零,它将遍历 'probs' 数组,累加概率之和。
处理极端案例。 如果循环终止且没有选择任何期刊(理论上,如果 rnd 为 1 且所有 “probs” 的总和恰好为 1,或者 “probs” 的总和略小于 1,则由于浮点计算误差而可能发生这种情况),则该函数返回最后一个期刊的索引(probsSize - 1)。这样就能确保即使在临界或病态的情况下,期刊也总是被选中。
从本质上讲,该函数在数轴上从 0 到 1 创建一系列“槽位”,其中每个槽位的长度与相应期刊的概率成正比。然后它投掷一个“飞镖”(rnd 随机数),并观察它落入哪个槽位。这样可以确保概率较高的期刊更常被选中。
//———————————————————————————————————————————————————————————————————— int C_AO_CoSO::SelectJournal (const double &probs []) { double rnd = u.RNDprobab (); double cumSum = 0; int probsSize = ArraySize (probs); for (int i = 0; i < probsSize; i++) { cumSum += probs [i]; if (rnd <= cumSum) return i; } return probsSize - 1; } //————————————————————————————————————————————————————————————————————
结论
我们已详细研究了 CoSO 算法的理论,以及部分算法方法在代码中的实现。在写作初期,我发现文章的材料量远超预期,于是我决定将文章分为两部分。该算法本身极具趣味性和多面性,其深层且多层次的逻辑值得深入思考。
在第二部分中,我将继续描述具体方法,并对算法进行测试,最后就其性能得出结论。
本文中用到的程序
| # | 名称 | 类型 | 描述 |
|---|---|---|---|
| 1 | #C_AO.mqh | 包含文件 | 种群优化算法的父类 |
| 2 | #C_AO_enum.mqh | 包含文件 | 种群优化算法的枚举 |
| 3 | TestFunctions.mqh | 包含文件 | 测试函数库 |
| 4 | TestStandFunctions.mqh | 包含文件 | 测试平台函数库 |
| 5 | Utilities.mqh | 包含文件 | 辅助函数库 |
| 6 | CalculationTestResults.mqh | 包含文件 | 用于计算比较表中结果的脚本 |
| 7 | Testing AOs.mq5 | 脚本 | 所有种群优化算法的统一测试平台 |
| 8 | Simple use of population optimization algorithms.mq5 | 脚本 | 使用不带可视化的种群优化算法的简单示例 |
| 9 | Test_AO_CoSO.mq5 | 脚本 | CoSO 测试台 |
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/18886
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
通过图表同步简化技术分析
新手在交易中的10个基本错误
MetaTrader 5中交易品种及其价差的小时级波动分析