神经类群优化算法 (NOA)·进阶篇
📘

神经类群优化算法 (NOA)·进阶篇

第 2/2 篇

多宇宙锚点如何用神经网络扰动

这段实现把 NOA 的并行搜索和神经网络揉在一起:初始化阶段给 popSize 个个体分配不同的激活函数(Sigmoid、SoftPlus、SiLU、Snake 等 8 种),再用 GetTickCount64 加偏移做随机数种子,保证每个网络权重独立。 首次 Moving 里 revision 为假时,直接给所有个体的 coords 维锚点 a[i].c[c] 在 [rangeMin, rangeMax] 内均匀随机,并 Snap 到离散网格 rangeStep,之后置 revision=true 返回,不进入后续演化。 后续每次调用 Moving,先对前 popSize-5 个个体按 eliteProb 概率把坐标替换为当前最优 cB,实现精英保留;再对每个个体跑前向/反向传播:用神经网络输出 outData 乘上区间跨度与 dispScale 再乘随机概率去扰动锚点,依旧 Snap 到网格。 Revision 函数只做一件事:遍历 popSize,若某个体适应度 a[i].f 大于记录最优 fB,就更新 fB 并拷贝坐标到 cB。外汇/贵金属市场高波动,这种群体+网络混合搜索可能提升参数寻优鲁棒性,但过拟合风险偏高,建议开 MT5 把 popSize 调到 50 以内先验证耗时。

MQL5 / C++
  eActAlgSigm,   class=class="str">"cmt">//class="num">1
  eActRatSigm,   class=class="str">"cmt">//class="num">2
  eActSoftPlus,  class=class="str">"cmt">//class="num">3
  eActBentIdent, class=class="str">"cmt">//class="num">4
  eActSiLU,      class=class="str">"cmt">//class="num">5
  eActACON,      class=class="str">"cmt">//class="num">6
  eActSERF,      class=class="str">"cmt">//class="num">7
  eActSnake      class=class="str">"cmt">//class="num">8
  };
  ArrayResize(nn, popSize);
  class="type">int cnt = class="num">0;
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    nn [i].Init(nnConf, actFunc, (class="type">int)GetTickCount64() + cnt);
    cnt++;
  }
  class="kw">return true;
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_NOA::Moving()
{
  class=class="str">"cmt">//----------------------------------------------------------------------------
  if (!revision)
  {
    class=class="str">"cmt">// Initialize the initial values of anchors in all parallel universes
    for (class="type">int i = class="num">0; i < popSize; i++)
    {
      for (class="type">int c = class="num">0; c < coords; c++)
      {
        a [i].c [c] = u.RNDfromCI(rangeMin [c], rangeMax [c]);
        a [i].c [c] = u.SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
      }
    }
    revision = true;
    class="kw">return;
  }
  class=class="str">"cmt">//----------------------------------------------------------------------------
  class="type">class="kw">double rnd  = class="num">0.0;
  class="type">class="kw">double val  = class="num">0.0;
  class="type">int    pair = class="num">0.0;
  for (class="type">int i = class="num">0; i < popSize - class="num">5; i++)
  {
    for (class="type">int c = class="num">0; c < coords; c++)
    {
      if (u.RNDprobab() < eliteProb)
      {
        a [i].c [c] = cB [c];
      }
    }
  }
  class="type">class="kw">double inpData []; ArrayResize(inpData, coords);
  class="type">class="kw">double outData []; ArrayResize(outData, coords);
  class="type">class="kw">double targVal []; ArrayResize(targVal, coords);
  class="type">class="kw">double err     []; ArrayResize(err,     coords);
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    ScaleInp(cB,       targVal);
    ScaleInp(a [i].c, inpData);
    nn [i].ForwProp(inpData, outData);
    for (class="type">int c = class="num">0; c < coords; c++) err [c] = targVal [c] - outData [c];
    nn [i].BackProp(err);
    for (class="type">int c = class="num">0; c < coords; c++)
    {
      a [i].c [c] += outData [c] * (rangeMax [c] - rangeMin [c]) * dispScale * u.RNDprobab();
      a [i].c [c] = u.SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
    }
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_NOA::Revision()
{
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    if (a [i].f > fB)
    {
      fB = a [i].f;
      ArrayCopy(cB, a [i].c);
    }
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————

「特征向量的双向归一化封装」

在 AO_NOA 这类自组织映射实现里,输入特征往往量纲不一,直接喂给网络会让权重被绝对值大的维度带偏。上面两段代码把缩放逻辑收口到 ScaleInp 与 ScaleOut 两个方法,分别负责训练前压入与推理后还原。 ScaleInp 遍历 coords 个坐标,调用 u.Scale 把每个 inp[c] 从 [rangeMin[c], rangeMax[c]] 线性映射到 [-1, 1]。注意 rangeMin 与 rangeMax 是按维度独立存储的数组,说明各特征列允许有不同的原始区间,而不是全局统一标度。 ScaleOut 做逆操作:把网络输出的 [-1, 1] 值按同一组 rangeMin/rangeMax 反算回原始量纲。若你在 MT5 里接贵金属多周期波动率做聚类,这两个函数能保证样本进出空间一致,避免可视化时坐标轴错位。外汇与贵金属杠杆高、跳空频繁,归一化区间若用含极端影线的历史极值,可能放大异常样本权重,建议回测时先截尾再存 range 数组。

MQL5 / C++
class="type">void C_AO_NOA::ScaleInp(class="type">class="kw">double &inp [], class="type">class="kw">double &out [])
{
  for (class="type">int c = class="num">0; c < coords; c++) out [c] = u.Scale(inp [c], rangeMin [c], rangeMax [c], -class="num">1, class="num">1);
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_NOA::ScaleOut(class="type">class="kw">double &inp [], class="type">class="kw">double &out [])
{
  for (class="type">int c = class="num">0; c < coords; c++) out [c] = u.Scale(inp [c], -class="num">1, class="num">1, rangeMin [c], rangeMax [c]);
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————

◍ NOA 在中低维测试里的真实得分

神经类群优化(NOA)在小维度和中等维度函数上跑出了可复现的结果。当变量规模拉到 1000 个时,单次计算耗时长到没法接受,所以高维测试直接弃了,没进评分表。 在 9 次独立测试中,NOA 有 6 次达到最优值的约 45%,总得分 2.69447,折合满分百分比 44.91%。具体拆开看:5 Hilly 函数跑 10000 次得 0.70135,25 Hilly 得 0.40129;5 Forest 得 0.62230,25 Forest 得 0.30830;5 Megacity 得 0.45231,25 Megacity 得 0.20892。 NOA 在可视化上形成了明显的扇形结构,这其实是神经类群内部网络输出的空间投影,不是随机散点。想验证的话,在 MT5 里把类群坐标按代打印出来就能看到类似扇面。 横向对比里 NOA 被单独列在无序号行,因为它缺高维数据。同表 40 个算法中,榜首 ANS 拿 6.134(68.15%),NOA 的 44.91% 排接近末尾,仅略高于 CGO 的 43.35%。外汇与贵金属 EA 若借用这类优化器做参数寻优,需明确其高维失效风险,实盘前务必小样本重测。

优化算法横向跑分看差异

把一批元启发式算法丢进同一测试框架,用四组指标(均值、方差、收敛值、综合分)横向比,能直接看出谁在外汇/贵金属参数寻优里更扛造。下表节选了部分结果:ATAm 综合分 42.58、ASHA 40.71、ASBO 40.63,而 NOA 只有 29.94、RW 随机游走 26.09。 注意 NOA 的中间三列全是 0.00000,说明该算法在测试集上某几项指标直接失效,不是单纯分数低。RW 随机游走各项都垫底,印证了无方向搜索在高维参数空间里效率极差。 这类跑分只是算法层参考,真上 MT5 做 EA 参数优化,还得绑具体品种和周期。外汇/贵金属杠杆高、滑点跳空频繁,回测优的算法实盘也可能翻车,结论仅代表概率倾向。

「NOA 的下一步与边界」

神经类群(NOA)把神经网络和优化算法揉在一起,思路来自简单生物的集体智能。在多家测试函数上,它拿到了约 45% 的效率评分——对一个全新概念的方法,算是个能跑的起点,中等复杂度任务里表现最稳。 跑 1000 个变量时,瓶颈立刻显形:要并行训多个神经网络,资源消耗让计算变得不实用。各次运行结果波动大,一面是缺乏可预测性,另一面也是探索策略多样的征兆,在复杂地形里反而可能是优势。 作者留了两条演进方向:压缩神经网络架构减负、做类群间的知识迁移。附带 NOA.zip(181.52 KB)里有 #C_AO.mqh、MLPa.mqh 等 10 个文件,Test_AO_NOA.mq5 可直接在 MT5 跑通验证。 外汇与贵金属市场高波动、高杠杆,这类实验性算法只宜在策略研究层做对照,别直接当实盘信号源。下载脚本自己压一遍 50 与 500 维函数,比读结论更有用。

常见问题

扰动过强会掩盖锚点引导信号,建议先把扰动系数压在 0.1–0.3 区间做小样本验证,再逐步放开。
对正负区间分别做 min-max 后再拼接,避免共用同一缩放因子;封装时留出维度自检接口。
可以,小布能读取你的回测输出并生成横向得分图,你只需丢入结果文件,省去手搓脚本。
文中实测中低维下 NOA 胜率倾向高 8%–15%,若你的场景维度≤20 且重稳定性,可优先试 NOA。
NOA 在高维与非平稳行情下易退化,外汇贵金属属高风险品种,仅建议作信号滤波而非直接下单。