神经网络变得轻松(第三十六部分):关系强化学习·进阶篇
🕸️

神经网络变得轻松(第三十六部分):关系强化学习·进阶篇

(2/3)·卷积网络能认出对象却建不起联系,通道假突破正是被这点坑惨

偏理论进阶 第 2/3 篇
很多人把卷积模型直接搬进实盘,以为认出转折点和趋势就够用了。可真实行情里充满噪点和形变,同一对象换了序列卷积就分不清两种形态。更致命的是它根本不会判断突破和回滚之间的先后依赖,假突破上你容易稀里糊涂进场。

「初始化时把神经网与指标缓冲区绑起来」

EA 在 OnInit 阶段先尝试加载已训练好的网络文件,后缀优先找 .icm,找不到再退回到 .nnw 配合 .fwd / .inv 描述文件,且硬性要求 6 个嵌入维度。若两者都缺失,则现场调用 CreateDescriptions 构造模型结构,任何一步 new 出来的对象若建网失败必须显式 delete 后返回 INIT_FAILED,避免 MT5 终端内存泄漏。 网络就绪后立刻切到训练模式(TrainMode(true)),并用 SetBufferSize(Batch, 10 * Batch) 给在线学习留 10 倍批量的滑动缓冲。随后通过 GetLayerOutput(0, temp) 读取输入层尺寸,用公式 (temp.Total() - 9) / 12 反推 HistoryBars——这意味着输入层每 12 个浮点对应 1 根 K 线,且头部有 9 个非 K 线占位。 RSI、CCI、ATR、MACD 四个指标缓冲区都按 HistoryBars 长度 resize,只要有一个失败就打印函数名与行号并 INIT_FAILED。外汇与贵金属杠杆品种波动剧烈,这类初始化若在生产环境静默失败,后续信号会全部错位,建议开 MT5 把这段直接挂到 EA 头里跑一次看日志。

MQL5 / C++
  class="kw">return INIT_FAILED;
class=class="str">"cmt">//---
  if(!MACD.Create(Symb.Name(), TimeFrame, FastPeriod, SlowPeriod, SignalPeriod, MACDPrice))
    class="kw">return INIT_FAILED;
class=class="str">"cmt">//---
  if(!StudyNet.Load(FileName + ".icm", true))
    if(!StudyNet.Load(FileName + ".nnw", FileName + ".fwd", FileName + ".inv", class="num">6, true))
      {
       CArrayObj *model = new CArrayObj();
       CArrayObj *forward = new CArrayObj();
       CArrayObj *inverse = new CArrayObj();
       if(!CreateDescriptions(model, forward, inverse))
         {
          class="kw">delete model;
          class="kw">delete forward;
          class="kw">delete inverse;
          class="kw">return INIT_FAILED;
         }
       if(!StudyNet.Create(model, forward, inverse))
         {
          class="kw">delete model;
          class="kw">delete forward;
          class="kw">delete inverse;
          class="kw">return INIT_FAILED;
         }
       StudyNet.SetStateEmbedingLayer(class="num">6);
       class="kw">delete model;
       class="kw">delete forward;
       class="kw">delete inverse;
      }
  if(!StudyNet.TrainMode(true))
    class="kw">return INIT_FAILED;
  StudyNet.SetBufferSize(Batch, class="num">10 * Batch);
class=class="str">"cmt">//---
  CBufferFloat* temp;
  if(!StudyNet.GetLayerOutput(class="num">0, temp))
    class="kw">return INIT_FAILED;
  HistoryBars = (temp.Total() - class="num">9) / class="num">12;
  class="kw">delete temp;
  if(!RSI.BufferResize(HistoryBars) || !CCI.BufferResize(HistoryBars) ||
     !ATR.BufferResize(HistoryBars) || !MACD.BufferResize(HistoryBars))
     {
      PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
      class="kw">return INIT_FAILED;
     }
class=class="str">"cmt">//---
  if(!Trade.SetTypeFillingBySymbol(Symb.Name()))
    class="kw">return INIT_FAILED;
class=class="str">"cmt">//---
  class="kw">return(INIT_SUCCEEDED);
  }
class="type">bool CreateDescriptions(CArrayObj *Description, CArrayObj *Forward, CArrayObj *Inverse)
  {
class=class="str">"cmt">//---
  if(!Description)
    {
     Description = new CArrayObj();
     if(!Description)
       class="kw">return class="kw">false;
    }
class=class="str">"cmt">//---
  if(!Forward)
    {
     Forward = new CArrayObj();
     if(!Forward)
       class="kw">return class="kw">false;
    }
class=class="str">"cmt">//---
  if(!Inverse)
    {

用 OCL 神经层堆出行情编码器

在 MT5 里搭一套用于价格行为识别的神经网络,第一步是把层结构塞进 Description 容器。下面这段直接给出从输入层到多头注意力的 7 层定义,复制进 EA 的模型初始化函数即可编译跑通。 输入层把 HistoryBars*12*9 个基元神经元铺开,例如 HistoryBars 取 100 时,首层节点数就是 10800;激活函数设 None,优化器统一用 ADAM。第二层接 BatchNorm(batch=1000)做归一,第三层用窗口 3、步长 1 的卷积把节点压到 prev_count-2,再经 LReLU。 往后是两个 LSTM(300 与 100 单元)夹一层卷积,最后用 4 层、窗口 5、步长 64 的多头注意力收口。外汇与贵金属波动剧烈,这类模型仅作概率参考,实盘前务必在策略测试器用历史数据验证过拟合程度。 别把节点数当越大越好 首层 10800 节点在普通显卡上可能直接爆显存;HistoryBars 先调到 20 试水,节点数随之降到 2160,迭代速度会明显友好。

MQL5 / C++
Inverse = new CArrayObj();
if(!Inverse)
   class="kw">return class="kw">false;
class=class="str">"cmt">//--- Model
  Description.Clear();
  CLayerDescription *descr;
class=class="str">"cmt">//--- Input layer
  if(!(descr = new CLayerDescription()))
    class="kw">return class="kw">false;
  descr.type = defNeuronBaseOCL;
  class="type">int prev_count = descr.count = HistoryBars * class="num">12 * class="num">9;
  descr.window = class="num">0;
  descr.activation = None;
  descr.optimization = ADAM;
  if(!Description.Add(descr))
    {
     class="kw">delete descr;
     class="kw">return class="kw">false;
    }
class=class="str">"cmt">//--- layer class="num">1
  if(!(descr = new CLayerDescription()))
    class="kw">return class="kw">false;
  descr.type = defNeuronBatchNormOCL;
  descr.count = prev_count;
  descr.batch = class="num">1000;
  descr.activation = None;
  descr.optimization = ADAM;
  if(!Description.Add(descr))
    {
     class="kw">delete descr;
     class="kw">return class="kw">false;
    }
class=class="str">"cmt">//--- layer class="num">2
  if(!(descr = new CLayerDescription()))
    class="kw">return class="kw">false;
  descr.type = defNeuronConvOCL;
  descr.count = prev_count - class="num">2;
  descr.window = class="num">3;
  descr.step = class="num">1;
  descr.window_out = class="num">16;
  descr.activation = LReLU;
  descr.optimization = ADAM;
  if(!Description.Add(descr))
    {
     class="kw">delete descr;
     class="kw">return class="kw">false;
    }
class=class="str">"cmt">//--- layer class="num">3
  if(!(descr = new CLayerDescription()))
    class="kw">return class="kw">false;
  descr.type = defNeuronLSTMOCL;
  descr.count = class="num">300;
  descr.optimization = ADAM;
  if(!Description.Add(descr))
    {
     class="kw">delete descr;
     class="kw">return class="kw">false;
    }
class=class="str">"cmt">//--- layer class="num">4
  if(!(descr = new CLayerDescription()))
    class="kw">return class="kw">false;
  descr.type = defNeuronConvOCL;
  descr.count = class="num">100;
  descr.window = class="num">3;
  descr.step = class="num">3;
  descr.window_out = class="num">10;
  descr.activation = LReLU;
  descr.optimization = ADAM;
  if(!Description.Add(descr))
    {
     class="kw">delete descr;
     class="kw">return class="kw">false;
    }
class=class="str">"cmt">//--- layer class="num">5
  if(!(descr = new CLayerDescription()))
    class="kw">return class="kw">false;
  descr.type = defNeuronLSTMOCL;
  descr.count = class="num">100;
  descr.optimization = ADAM;
  if(!Description.Add(descr))
    {
     class="kw">delete descr;
     class="kw">return class="kw">false;
    }
class=class="str">"cmt">//--- layer class="num">6
  if(!(descr = new CLayerDescription()))
    class="kw">return class="kw">false;
  descr.type = defNeuronMLMHAttentionOCL;
  descr.count = class="num">20;
  descr.window = class="num">5;
  descr.step = class="num">64;
  descr.window_out = class="num">16;
  descr.layers = class="num">4;
  descr.optimization = ADAM;

◍ 给自编码网络铺层与通道

这段逻辑在 MT5 里搭的是一个对称式自编码结构,分 Description、Forward、Inverse 三条链。Description 里第 7 层用 defNeuronFQF 类型,count=4、window_out=32、优化器 ADAM,负责把特征压缩成低频量子滤波输出。 Forward 链先 Clear 再建层:输入层 104 节点(defNeuronBaseOCL,激活 None),layer1 扩到 500 节点用 LReLU,layer2 收成 100 节点无激活,全程 ADAM。Inverse 链镜像回来,输入 200 节点、layer1 同是 500+LReLU、layer2 压到 4 节点,用来做重构。 每一层 new 出来若加不进容器就 delete 并 return false,这是防内存泄漏的硬写法。你在 EA 里改 count 数值时,Forward 输入 104 和 Inverse 输出 4 必须和 Description 的窗口/维度对上,否则训练会直接初始化失败。 外汇与贵金属行情用这类网络做特征提取,属于高波动高风险场景,参数过拟合可能导致实盘信号失真,回测顺不代表活盘稳。

MQL5 / C++
  if(!Description.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return class="kw">false;
    }
class=class="str">"cmt">//--- layer class="num">7
  if(!(descr = new CLayerDescription()))
    class="kw">return class="kw">false;
  descr.type = defNeuronFQF;
  descr.count = class="num">4;
  descr.window_out = class="num">32;
  descr.optimization = ADAM;
  if(!Description.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return class="kw">false;
    }
class=class="str">"cmt">//--- Forward
  Forward.Clear();
class=class="str">"cmt">//--- Input layer
  if(!(descr = new CLayerDescription()))
    class="kw">return class="kw">false;
  descr.type = defNeuronBaseOCL;
  descr.count = class="num">104;
  descr.window = class="num">0;
  descr.activation = None;
  descr.optimization = ADAM;
  if(!Forward.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return class="kw">false;
    }
class=class="str">"cmt">//--- layer class="num">1
  if(!(descr = new CLayerDescription()))
    class="kw">return class="kw">false;
  descr.type = defNeuronBaseOCL;
  descr.count = class="num">500;
  descr.activation = LReLU;
  descr.optimization = ADAM;
  if(!Forward.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return class="kw">false;
    }
class=class="str">"cmt">//--- layer class="num">2
  if(!(descr = new CLayerDescription()))
    class="kw">return class="kw">false;
  descr.type = defNeuronBaseOCL;
  descr.count = class="num">100;
  descr.activation = None;
  descr.optimization = ADAM;
  if(!Forward.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return class="kw">false;
    }
class=class="str">"cmt">//--- Inverse
  Inverse.Clear();
class=class="str">"cmt">//--- Input layer
  if(!(descr = new CLayerDescription()))
    class="kw">return class="kw">false;
  descr.type = defNeuronBaseOCL;
  descr.count = class="num">200;
  descr.window = class="num">0;
  descr.activation = None;
  descr.optimization = ADAM;
  if(!Inverse.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return class="kw">false;
    }
class=class="str">"cmt">//--- layer class="num">1
  if(!(descr = new CLayerDescription()))
    class="kw">return class="kw">false;
  descr.type = defNeuronBaseOCL;
  descr.count = class="num">500;
  descr.activation = LReLU;
  descr.optimization = ADAM;
  if(!Inverse.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return class="kw">false;
    }
class=class="str">"cmt">//--- layer class="num">2
  if(!(descr = new CLayerDescription()))
    class="kw">return class="kw">false;
  descr.type = defNeuronBaseOCL;
  descr.count = class="num">4;
  descr.activation = None;
  descr.optimization = ADAM;
  if(!Inverse.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return class="kw">false;
    }
class=class="str">"cmt">//--- layer class="num">3
  if(!(descr = new CLayerDescription()))
    class="kw">return class="kw">false;

「给逆变换层挂上 SoftMax 输出」

上面这段是给神经网络逆变换(Inverse)部分追加一个 SoftMax 输出层的典型写法。descr 先被指定为 defNeuronSoftMaxOCL 类型,意味着该层走 OpenCL 的 SoftMax 实现,适合在 GPU 上跑多分类概率归一化。 descr.count = 4 表示这一层固定输出 4 个节点,step = 1 是层间连接步长,activation = None 说明 SoftMax 本身已含归一化,不再叠额外激活;optimization 选 ADAM,学习率自适应在金融序列里更稳。 Add 失败就 delete descr 并 return false,避免悬空描述符泄漏显存;成功才 return true。开 MT5 把这段塞进你的 CNet 派生类,改 count 就能直接验证不同输出维度对贵金属分类信号的影响,外汇与贵金属杠杆高,回测结论仅代表历史概率。

MQL5 / C++
  descr.type = defNeuronSoftMaxOCL;
  descr.count = class="num">4;
  descr.step = class="num">1;
  descr.activation = None;
  descr.optimization = ADAM;
  if(!Inverse.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return class="kw">false;
    }
class=class="str">"cmt">//---
  class="kw">return true;
  }
把形态依赖判读交给小布
这些关系模型的依赖结构诊断,小布盯盘的 AIGC 已内置,打开对应品种页即可看到通道突破与回滚的节点连线,你只管确认信号质量。

常见问题

卷积侧重从数据里识别独立对象,关系模型用图节点和边表达对象间依赖,能处理形变、噪点及多对象相对位置问题。
卷积可检测边界突破节点,但无法建立突破与价格回滚之间的时序依赖,也就难过滤无回滚确认的假突破。
广义关注度机制从常规数组挑出独立对象,让模型把注意力放在关键节点上,为构建示图依赖提供输入。
小布盯盘内置了基于关系结构的形态诊断,能直接画出通道突破、回滚节点间的连接,省去手动梳理依赖的重复劳动。
这类品种高杠杆高波动,噪点更密,模型依赖识别只提供概率倾向,实盘仍可能因突发事件失真,需严格风控。