交易中的神经网络:探索局部数据结构·进阶篇
📘

交易中的神经网络:探索局部数据结构·进阶篇

第 2/3 篇

「Transformer 模块的逐层 OpenCL 初始化」

在 MT5 的 OpenCL 神经网络封装里,一个 Transformer 单元靠连续 12 个索引偏移把各子层挂到计算图。上面这段初始化循环里,i 代表层序号,每层占用 i*12+5 到 i*12+10 共 6 个关键节点,外加卷积与全连接对象。 第一个卷积 conv 用 Init(0, i*12+5, OpenCL, center_window, center_window, 32, 1, iPoints*2, optimization, iBatch) 建立,窗口尺寸等于 center_window,输出通道 32,步长 1,输入展开长度为 iPoints*2;若返回 false 直接中断建网。 Key-Value 的全连接层 base 节点号为 i*12+6,输入维度被拉到 iPoints*2*32*2,也就是把前面卷积的时空与通道维直接拍平送进矩阵乘。Score 缓冲区则用 OpenCL.AddBuffer 按 float 大小申请 iUnits*iPoints*4 个元素的读写显存,索引写入 cScores 容器。 注意力输出部分分两路:MHAttentionOut 的 base 节点 i*12+7 接收 iUnits*64 维,AttentionOut 的卷积节点 i*12+8 用 64→64→64 的三参数卷积核、宽度 iUnits 做逐位融合。残差 base 在 i*12+9 同样吃 iUnits*64 维,Feed Forward 卷积在 i*12+10 把 64 通道扩到 256 并显式调 SetActivationFunction(LReLU),漏 ReLU 能缓解深层梯度消失。 把这些索引规律记下来,你改网络深度时只要调 iUnits 和 iPoints,层内偏移不用动;外汇与贵金属行情跳空频繁,这类 GPU 网络过拟合风险高,上实盘前务必用历史样本外数据验证。

MQL5 / C++
  !conv.Init(class="num">0, i * class="num">12 + class="num">5, OpenCL, center_window, center_window, class="num">32, class="num">1, iPoints * class="num">2,
optimization, iBatch))
  class="kw">return false;
  if(!cValue.Add(conv))
    class="kw">return false;
  class=class="str">"cmt">//--- Key-Value
  base = new CNeuronBaseOCL();
  if(!base ||
    !base.Init(class="num">0, i * class="num">12 + class="num">6, OpenCL, iPoints * class="num">2 * class="num">32 * class="num">2, optimization, iBatch))
    class="kw">return false;
  if(!cKeyValue.Add(base))
    class="kw">return false;
  class=class="str">"cmt">//--- Score
  class="type">int s = class="type">int(iUnits * iPoints * class="num">4);
  s = OpenCL.AddBuffer(class="kw">sizeof(class="type">float) * s, CL_MEM_READ_WRITE);
  if(s < class="num">0 || !cScores.Add(s))
    class="kw">return false;
  class=class="str">"cmt">//--- MH Attention Out
  base = new CNeuronBaseOCL();
  if(!base ||
    !base.Init(class="num">0, i * class="num">12 + class="num">7, OpenCL, iUnits * class="num">64, optimization, iBatch))
    class="kw">return false;
  if(!cMHAttentionOut.Add(base))
    class="kw">return false;
  class=class="str">"cmt">//--- Attention Out
  conv = new CNeuronConvOCL();
  if(!conv ||
    !conv.Init(class="num">0, i * class="num">12 + class="num">8, OpenCL, class="num">64, class="num">64, class="num">64, iUnits, class="num">1, optimization, iBatch))
    class="kw">return false;
  if(!cAttentionOut.Add(conv))
    class="kw">return false;
  class=class="str">"cmt">//--- Residual
  base = new CNeuronBaseOCL();
  if(!base ||
    !base.Init(class="num">0, i * class="num">12 + class="num">9, OpenCL, iUnits * class="num">64, optimization, iBatch))
    class="kw">return false;
  if(!cResidual.Add(base))
    class="kw">return false;
  class=class="str">"cmt">//--- Feed Forward
  conv = new CNeuronConvOCL();
  if(!conv ||
    !conv.Init(class="num">0, i * class="num">12 + class="num">10, OpenCL, class="num">64, class="num">64, class="num">256, iUnits, class="num">1, optimization, iBatch))
    class="kw">return false;
  conv.SetActivationFunction(LReLU);
  if(!cFeedForward.Add(conv))

SEFormer 残差与注意力层的初始化落点

这段片段展示的是 CNeuronSEFormer 类里卷积、残差和最终注意力模块的逐层挂接逻辑。循环变量 i 每推进一次,就按 i*12+11、i*12+12、i*12+13 的偏移量向网络结构里塞入对应层,说明每层参数索引是硬编码错位排布的,改结构时容易踩坑。 卷积层 Init 接收 256 输入通道、64 卷积核、64 步长,残差基层则把 iUnits*64 个神经元直接接在卷积梯度上(SetGradient 第二个参数置 true 表示共享梯度)。这种接法在外汇或贵金属行情的 GPU 训练里,可能让显存占用随 iUnits 线性膨胀,实操前建议在 MT5 策略测试器里先跑小 iUnits 验证。 最终注意力层 CNeuronMLMHSceneConditionAttention 的 Init 里写死了头数 16、层数 4、缩放 2,外部只暴露 iUnits 和批次参数。feedForward 里对 bTrain 为真时才先推中心点点云,且内部循环只跑 2 层(l<2),说明推理与训练的前向路径不一致,复制代码时别漏掉这个分支。

MQL5 / C++
    class="kw">return false;
    conv = new CNeuronConvOCL();
    if(!conv ||
       !conv.Init(class="num">0, i * class="num">12 + class="num">11, OpenCL, class="num">256, class="num">64, class="num">64, iUnits, class="num">1, optimization, iBatch))
       class="kw">return false;
    if(!cFeedForward.Add(conv))
       class="kw">return false;
    class=class="str">"cmt">//--- Residual
    base = new CNeuronBaseOCL();
    if(!base ||
       !base.Init(class="num">0, i * class="num">12 + class="num">12, OpenCL, iUnits * class="num">64, optimization, iBatch))
       class="kw">return false;
    if(!base.SetGradient(conv.getGradient(), true))
       class="kw">return false;
    if(!cResidual.Add(base))
       class="kw">return false;
    class=class="str">"cmt">//--- Final Attention
    CNeuronMLMHSceneConditionAttention *att = new CNeuronMLMHSceneConditionAttention();
    if(!att ||
       !att.Init(class="num">0, i * class="num">12 + class="num">13, OpenCL, class="num">64, class="num">16, class="num">4, class="num">2, iUnits, class="num">2, class="num">1, optimization, iBatch))
       class="kw">return false;
    if(!cFinalAttention.Add(att))
       class="kw">return false;
    }
 if(!SEOut.Init(class="num">0, class="num">26, OpenCL, class="num">64, class="num">64, class="num">4, class="num">16, class="num">4, iUnits, iUnits, class="num">4, class="num">1, optimization, iBatch))
    class="kw">return false;
 if(!cbTemp.BufferInit(buf_size, class="num">0) ||
    !cbTemp.BufferCreate(OpenCL))
    class="kw">return false;
class=class="str">"cmt">//---
  class="kw">return true;
  }
class="type">bool CNeuronSEFormer::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
class=class="str">"cmt">//---
  CNeuronBaseOCL *neuron = NULL, *q = NULL, *k = NULL, *v = NULL, *kv = NULL;
class=class="str">"cmt">//--- Init Points
  if(bTrain)
    {
     neuron = cCenterPoints[class="num">1];
     if(!neuron ||
        !neuron.FeedForward(cCenterPoints[class="num">0]))
        class="kw">return false;
    }
class=class="str">"cmt">//--- Inside Layers
  for(class="type">int l = class="num">0; l < class="num">2; l++)
    {
     class=class="str">"cmt">//--- Segmentation Inputs
     if(l > class="num">0 || !cTNetG)
       {
        if(!caLocalPointNet[l].FeedForward((l == class="num">0 ? NeuronOCL : GetPointer(caLocalPointNet[l - class="num">1]))))
           class="kw">return false;
       }

◍ 结构嵌入注意力里的残差与多头计算

这段代码处在局部点网络向上一层传递之后,负责把中心点做插值,再跑结构嵌入注意力。任何一步 FeedForward 或矩阵运算返回失败就直接 return false,说明该层推理链条是严格串行的,断一处整层作废。 先看窗口尺寸:window 用神经元总数的平方根取整,例如神经元数为 1024 时 window=32,后面 MatMul 的分块就按 NeuronOCL.Neurons()/window 与 window 对齐。若 IsStopped() 触发(EA 被停止或卸载),矩阵乘直接放弃,避免后台空转。 注意力部分把 Query 来自局部点网络、Key/Value 来自插值后的中心点,Concat 时写死 32*2 的维度,即单头 32 维、拼接后 64 维。多头输出再经 Scale 与 Residual:SumAndNormilize 里 64 是向量长度,true 表示做归一化,后面四个 0/1 是均值方差相关开关参数。 残差加归一化之后进两层 FeedForward(索引 l*2 与 l*2+1),这就是典型 Transformer 块的后半段。复制这段代码到 MT5 的自定义网络类里,把 32、64 这些维度打印出来,能确认你的显存占用是否和原设计一致;外汇与贵金属杠杆高,此类模型仅作辅助信号,实盘误判概率不低。

MQL5 / C++
else
      {
       if(!cTurnedG)
         class="kw">return false;
       if(!cTNetG.FeedForward(NeuronOCL))
         class="kw">return false;
       class="type">int window = (class="type">int)MathSqrt(cTNetG.Neurons());
       if(IsStopped() ||
         !MatMul(NeuronOCL.getOutput(), cTNetG.getOutput(), cTurnedG.getOutput(),
NeuronOCL.Neurons() / window, window, window))
         class="kw">return false;
       if(!caLocalPointNet[class="num">0].FeedForward(cTurnedG.AsObject()))
         class="kw">return false;
      }
      class=class="str">"cmt">//--- Interpolate center points
      neuron = cCenterPoints[l + class="num">2];
      if(!neuron ||
         !neuron.FeedForward(cCenterPoints[l + class="num">1], caLocalPointNet[l].getOutput()))
         class="kw">return false;
      class=class="str">"cmt">//--- Structure-Embedding Attention
      q = cQuery[l];
      k = cKey[l];
      v = cValue[l];
      kv = cKeyValue[l];
      class=class="str">"cmt">//--- Query
      if(!q || !q.FeedForward(GetPointer(caLocalPointNet[l])))
         class="kw">return false;
      class=class="str">"cmt">//--- Key
      if(!k || !k.FeedForward(cCenterPoints[l + class="num">2]))
         class="kw">return false;
      class=class="str">"cmt">//--- Value
      if(!v || !v.FeedForward(cCenterPoints[l + class="num">2]))
         class="kw">return false;
      if(!kv ||
         !Concat(k.getOutput(), v.getOutput(), kv.getOutput(), class="num">32 * class="num">2, class="num">32 * class="num">2, iPoints))
         class="kw">return false;
      class=class="str">"cmt">//--- Multi-Head Attention
      neuron = cMHAttentionOut[l];
      if(!neuron ||
         !AttentionOut(q.getOutput(), kv.getOutput(), cScores[l], neuron.getOutput()))
         class="kw">return false;
      class=class="str">"cmt">//--- Scale
      neuron = cAttentionOut[l];
      if(!neuron || !neuron.FeedForward(cMHAttentionOut[l]))
         class="kw">return false;
      class=class="str">"cmt">//--- Residual
      q = cResidual[l * class="num">2];
      if(!q ||
         !SumAndNormilize(caLocalPointNet[l].getOutput(), neuron.getOutput(), q.getOutput(), class="num">64, true,
class="num">0, class="num">0, class="num">0, class="num">1))
         class="kw">return false;
      class=class="str">"cmt">//--- Feed Forward
      neuron = cFeedForward[l * class="num">2];
      if(!neuron || !neuron.FeedForward(q))
         class="kw">return false;
      neuron = cFeedForward[l * class="num">2 + class="num">1];

「残差与跨尺度注意力的前向和反向实现」

SEFormer 这类模型在 MT5 里跑,前向传播并不是一口气卷积到底,而是逐层走残差 + 注意力。代码里用 cResidual[l*2+1] 取出第 l 层的残差连接节点,再调 SumAndNormilize 把查询输出和神经元输出做归一化求和,维度参数写死为 64,最后一个布尔位 true 代表这是前向阶段的标准化。 反向传播 calcInputGradients 里同一段逻辑翻转:归一化函数最后一个布尔变成 false,表示此刻在算梯度而非前向激活。循环从 l=1 倒序到 0,先把 cFinalAttention[0] 的隐藏梯度算清,再逐层拆残差、前馈、缩放注意力,任何一步指针为空或计算返回 false 就直接退出,避免脏梯度污染 PointNet 嵌入层。 实盘接这种网络做贵金属信号判别,要清楚 OCL 缓冲一旦某层 getOutput() 返回空,整个 feedForward 会静默失败返回 false。外汇与贵金属杠杆高,模型推理失败若不被交易逻辑捕获,可能发出错误订单,务必在 EA 里对返回值做硬判断。

MQL5 / C++
if(!neuron || !neuron.FeedForward(cFeedForward[l * class="num">2]))
     class="kw">return false;
   class=class="str">"cmt">//--- Residual
   k = cResidual[l * class="num">2 + class="num">1];
   if(!k ||
      !SumAndNormilize(q.getOutput(), neuron.getOutput(), k.getOutput(), class="num">64, true, class="num">0, class="num">0, class="num">0, class="num">1))
      class="kw">return false;
   class=class="str">"cmt">//--- Final Attention
   neuron = cFinalAttention[l];
   if(!neuron || !neuron.FeedForward(k))
      class="kw">return false;
   }
class=class="str">"cmt">//--- Cross scale attention
   if(!SEOut.FeedForward(cFinalAttention[class="num">0], neuron.getOutput()))
      class="kw">return false;
class=class="str">"cmt">//--- Global Point Cloud Embedding
   if(!CNeuronPointNetOCL::feedForward(SEOut.AsObject()))
      class="kw">return false;
class=class="str">"cmt">//--- result
   class="kw">return true;
   }
class="type">bool CNeuronSEFormer::calcInputGradients(CNeuronBaseOCL *NeuronOCL)
   {
    if(!NeuronOCL)
      class="kw">return false;
    CNeuronBaseOCL *neuron = NULL, *q = NULL, *k = NULL, *v = NULL, *kv = NULL;
    CBufferFloat *buf = NULL;
class=class="str">"cmt">//--- Global Point Cloud Embedding
    if(!CNeuronPointNetOCL::calcInputGradients(SEOut.AsObject()))
      class="kw">return false;
class=class="str">"cmt">//--- Cross scale attention
    neuron = cFinalAttention[class="num">0];
    q = cFinalAttention[class="num">1];
    if(!neuron.calcHiddenGradients(SEOut.AsObject(), q.getOutput(), q.getGradient(), (ENUM_ACTIVATION)q.Activation()))
      class="kw">return false;
    for(class="type">int l = class="num">1; l >= class="num">0; l--)
      {
      class=class="str">"cmt">//--- Final Attention
      neuron = cResidual[l * class="num">2 + class="num">1];
      if(!neuron || !neuron.calcHiddenGradients(cFinalAttention[l]))
         class="kw">return false;
      class=class="str">"cmt">//--- Feed Forward
      neuron = cFeedForward[l * class="num">2];
      if(!neuron || !neuron.calcHiddenGradients(cFeedForward[l * class="num">2 + class="num">1]))
         class="kw">return false;
      neuron = cResidual[l * class="num">2];
      if(!neuron || !neuron.calcHiddenGradients(cFeedForward[l * class="num">2]))
         class="kw">return false;
      class=class="str">"cmt">//--- Residual
      q = cResidual[l * class="num">2 + class="num">1];
      k = neuron;
      neuron = cAttentionOut[l];
      if(!neuron ||
         !SumAndNormilize(q.getGradient(), k.getGradient(), neuron.getGradient(), class="num">64, false, class="num">0, class="num">0, class="num">0, class="num">1))
         class="kw">return false;
      class=class="str">"cmt">//--- Scale
      neuron = cMHAttentionOut[l];
      if(!neuron || !neuron.calcHiddenGradients(cAttentionOut[l]))

注意力层反向传播的梯度回流细节

这段逻辑处在多层注意力结构里 l 层的反向传播阶段,核心是把输出梯度按 MH Attention 的路径拆回 q、k、v 各自支路。任何一步计算返回 false 就直接中断,说明梯度链不容许静默失败,调试时若某层不更新,优先查这里的返回值。 DeConcat 调用把 kv 的梯度按 32*2 的维度切回 k 与 v,两个 64 宽度的张量在 l==0 时还要走 LocalPointNet 的临时缓冲 cbTemp,再经 SumAndNormilize 以 64 长度做归一,系数 1 表示直接覆盖而非缩放。 残差部分把 LocalPointNet 的梯度与 AttentionOut 的梯度相加,同样走 64 长度归一;CenterPoints 则按 l+2 索引取神经元,l==0 时系数 1 全量回写,非 0 层用 0.5f 做半量插值——这意味着深层中心点的梯度贡献被刻意压低,可能在 MT5 里表现为浅层权重调整更剧烈。 开 MT5 把这段塞进你自己的神经网络 EA 反向函数,改一下 0.5f 和 64 这两个量,观察不同层学习率差异是否如预期收敛。外汇与贵金属行情高波动,这类模型仅作概率参考,实盘需严控仓位。

MQL5 / C++
class="kw">return false;
class=class="str">"cmt">//--- MH Attention
q = cQuery[l];
kv = cKeyValue[l];
k = cKey[l];
v = cValue[l];
if(!AttentionInsideGradients(q.getOutput(), q.getGradient(), kv.getOutput(), kv.getGradient(),
cScores[l], neuron.getGradient()))
  class="kw">return false;
if(!DeConcat(k.getGradient(), v.getGradient(), kv.getGradient(), class="num">32 * class="num">2, class="num">32 * class="num">2, iPoints))
  class="kw">return false;
if(l == class="num">0)
  {
   buf = caLocalPointNet[l].getGradient();
   if(!caLocalPointNet[l].SetGradient(GetPointer(cbTemp), false))
     class="kw">return false;
   }
if(!caLocalPointNet[l].calcHiddenGradients(q, NULL))
  class="kw">return false;
if(l == class="num">0)
  {
   if(!SumAndNormilize(buf, GetPointer(cbTemp), buf, class="num">64, false, class="num">0, class="num">0, class="num">0, class="num">1))
     class="kw">return false;
   if(!caLocalPointNet[l].SetGradient(buf, false))
     class="kw">return false;
   }
neuron = cAttentionOut[l];
class=class="str">"cmt">//--- Residual
if(!SumAndNormilize(caLocalPointNet[l].getGradient(), neuron.getGradient(),
caLocalPointNet[l].getGradient(), class="num">64, false, class="num">0, class="num">0, class="num">0, class="num">1))
  class="kw">return false;
class=class="str">"cmt">//--- Interpolate Center points
neuron = cCenterPoints[l + class="num">2];
if(!neuron)
  class="kw">return false;
buf = neuron.getGradient();
if(!neuron.SetGradient(GetPointer(cbTemp), false))
  class="kw">return false;
if(!neuron.calcHiddenGradients(k, NULL))
  class="kw">return false;
if(l == class="num">0)
  {
   if(!SumAndNormilize(buf, GetPointer(cbTemp), buf, class="num">1, false, class="num">0, class="num">0, class="num">0, class="num">1))
     class="kw">return false;
   }
else
  {
   if(!SumAndNormilize(GetPointer(cbTemp), GetPointer(cbTemp), buf, class="num">1, false, class="num">0, class="num">0, class="num">0, class="num">0.5f))
     class="kw">return false;
   }

◍ 反向传播里梯度在多层间的交接

这段逻辑处在网络训练的反向阶段,核心是把隐藏层梯度从后向前逐层算清并归一。任何一步返回 false 都会中断整轮训练,因此调用链上的 calcHiddenGradients 与 SumAndNormilize 必须全部成功。 对本地子网梯度做归一时,SumAndNormilize 的第四个参数用了 64,而中心点与输出缓冲的同类调用用的是 1,说明本地子网梯度聚合的维度明显更大,跑 MT5 时若改这个常数,显存与收敛速度会直接变。 当 l==0 且存在转置网 cTurnedG 时,window 按 MathSqrt(cTNetG.Neurons()) 取整,MatMulGrad 的分块尺寸就是 neuron.Neurons()/window 配 window 配 window。外汇与贵金属行情噪声大,这类张量乘梯度在高杠杆下容易过拟合,调参须控制风险。 最后几行把 neuron 与 cTurnedG 的梯度指针互换再补一次 calcHiddenGradients,等于在转置分支上做了一次梯度回流;想验证可实现性,直接把这段贴进 MT5 的神经网络类反向函数里单步跟一遍最直观。

MQL5 / C++
if(!neuron.calcHiddenGradients(v, NULL))
   class="kw">return false;
if(!SumAndNormilize(buf, GetPointer(cbTemp), buf, class="num">1, false, class="num">0, class="num">0, class="num">0, class="num">1))
   class="kw">return false;
if(!neuron.SetGradient(buf, false))
   class="kw">return false;
neuron = cCenterPoints[l + class="num">1];
if(!neuron.calcHiddenGradients(cCenterPoints[l + class="num">2], caLocalPointNet[l].getOutput(),
                                     GetPointer(cbTemp), (ENUM_ACTIVATION)caLocalPointNet[l].Activation()))
   class="kw">return false;
if(!SumAndNormilize(caLocalPointNet[l].getGradient(), GetPointer(cbTemp),
caLocalPointNet[l].getGradient(), class="num">64, false, class="num">0, class="num">0, class="num">0, class="num">1))
   class="kw">return false;
class=class="str">"cmt">//--- Local Net
neuron = (l > class="num">0 ? GetPointer(caLocalPointNet[l - class="num">1]) : NeuronOCL);
if(l > class="num">0 || !cTNetG)
  {
   if(!neuron.calcHiddenGradients(caLocalPointNet[l].AsObject()))
     class="kw">return false;
  }
else
  {
   if(!cTurnedG)
     class="kw">return false;
   if(!cTurnedG.calcHiddenGradients(caLocalPointNet[l].AsObject()))
     class="kw">return false;
   class="type">int window = (class="type">int)MathSqrt(cTNetG.Neurons());
   if(IsStopped() ||
      !MatMulGrad(neuron.getOutput(), neuron.getGradient(), cTNetG.getOutput(), cTNetG.getGradient(),
cTurnedG.getGradient(), neuron.Neurons() / window, window, window))
     class="kw">return false;
   if(!OrthoganalLoss(cTNetG, true))
     class="kw">return false;
   class=class="str">"cmt">//---
   CBufferFloat *temp = neuron.getGradient();
   neuron.SetGradient(cTurnedG.getGradient(), false);
   cTurnedG.SetGradient(temp, false);
   class=class="str">"cmt">//---
   if(!neuron.calcHiddenGradients(cTNetG.AsObject()))
     class="kw">return false;
   if(!SumAndNormilize(neuron.getGradient(), cTurnedG.getGradient(), neuron.getGradient(), class="num">1, false,
                                                                                               class="num">0, class="num">0, class="num">0, class="num">1))
     class="kw">return false;
  }
 }
class=class="str">"cmt">//---
 class="kw">return true;
 }
class=class="str">"cmt">//--- layer class="num">2

常见问题

检查每层内核参数与本地内存大小是否匹配,先单独跑单层初始化日志,确认无越界再串起整个模块。
把残差分支和多头注意力的权重偏移打印出来比对,确保残差加法节点的指针指向同一特征图尺寸。
可以,小布能直接读你的结构嵌入注意力代码,标出残差相加维度不一致或多头 split 错误的位置。
多在尺度对齐的上采样层,检查反向是否为各尺度梯度做了归一,漏掉会引发数值溢出。
在每层反向入口和出口打梯度范数,逐层缩窄范围,定位到交接处未累加的那一层再改。