交易中的神经网络:运用形态变换器进行市场分析·进阶篇
📘

交易中的神经网络:运用形态变换器进行市场分析·进阶篇

第 2/3 篇

注意力池化与形态神经元的底层接线

这段实现把多头注意力池化(CNeuronMHAttentionPooling)的末端拼装讲清楚了:在循环累加子层后,先清掉激活函数,再挂一个 CNeuronSoftMaxOCL 做归一化,并用 SetHeads(iUnits) 把输出头数对齐到单元数。 前向传播 feedForward 里,代码用 MatMul 把当前层输出与输入层输出做矩阵乘,参数写死为 (1, iHeads, iWindow, iUnits) 的维度排布,这意味着批内单样本、头数×窗口×单元的三维展开。外汇与贵金属行情用此类结构提取局部模式时,过拟合概率偏高,属高风险实验。 随后定义的 CNeuronMotifs 继承自 CNeuronBaseOCL,内部只保一个 CNeuronConvOCL cMotifs,说明形态提取被收敛到一层卷积上;Init 接口暴露了 dimension / window / step / units_count 四个关键超参,调 step 就能改变滑窗重叠度。 打开 MT5 的 MetaEditor,把这段粘进自定义神经元类,先只改 iWindow 从 10 到 30,看显存占用和前向耗时变化,比直接跑全模型更安全。

MQL5 / C++
   )
      class="kw">return class="kw">false;
  idx++;
  conv.SetActivationFunction(None);
  CNeuronSoftMaxOCL *softmax = new CNeuronSoftMaxOCL();
  if(!softmax ||
     !softmax.Init(class="num">0, idx, OpenCL, iHeads * iUnits, optimization, iBatch) ||
     !cNeurons.Add(softmax)
     )
      class="kw">return class="kw">false;
  softmax.SetHeads(iUnits);
class=class="str">"cmt">//---
  class="kw">return true;
  }
class="type">bool CNeuronMHAttentionPooling::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
  CNeuronBaseOCL *current = NULL;
  CObject *prev = NeuronOCL;
  for(class="type">int i = class="num">0; i < cNeurons.Total(); i++)
    {
      current = cNeurons[i];
      if(!current ||
         !current.FeedForward(prev)
         )
         class="kw">return class="kw">false;
      prev = current;;
    }
  if(!MatMul(current.getOutput(), NeuronOCL.getOutput(), Output,
                                 class="num">1, iHeads, iWindow, iUnits))
      class="kw">return class="kw">false;
class=class="str">"cmt">//---
  class="kw">return true;
  }
class CNeuronMotifs    :  class="kw">public CNeuronBaseOCL
  {
class="kw">protected:
  CNeuronConvOCL    cMotifs;
  class=class="str">"cmt">//---
  class="kw">virtual class="type">bool      feedForward(CNeuronBaseOCL *NeuronOCL) class="kw">override;
  class="kw">virtual class="type">bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) class="kw">override;
  class="kw">virtual class="type">bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) class="kw">override;
class="kw">public:
                    CNeuronMotifs(class="type">void) {};
                    ~CNeuronMotifs(class="type">void) {};
  class=class="str">"cmt">//---
  class="kw">virtual class="type">bool      Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl,
                          class="type">uint dimension, class="type">uint window, class="type">uint step, class="type">uint units_count,
                          ENUM_OPTIMIZATION optimization_type, class="type">uint batch);
  class=class="str">"cmt">//---
  class="kw">virtual class="type">int       Type(class="type">void) class="kw">override  const   {  class="kw">return defNeuronMotifs; }
  class=class="str">"cmt">//---
  class="kw">virtual class="type">bool      Save(class="type">int const file_handle) class="kw">override;
  class="kw">virtual class="type">bool      Load(class="type">int const file_handle) class="kw">override;
  class=class="str">"cmt">//---
  class="kw">virtual class="type">bool      WeightsUpdate(CNeuronBaseOCL *source, class="type">class="kw">float tau) class="kw">override;
  class="kw">virtual class="type">void      SetOpenCL(COpenCLMy *obj) class="kw">override;
  class=class="str">"cmt">//---

「Motifs 层与多尺度注意力类的骨架」

CNeuronMotifs 在初始化时先按 units_count、step 与 window 算出输入规模:inputs = (units_count * step + (window - step)) * dimension,再叠加 motifs = units_count * dimension 作为拼接后的总输入维度。这个加法直接决定了 CNeuronBaseOCL::Init 要申请的显存宽度,调参时若 window 从 10 改到 20,inputs 会线性拉长,MT5 终端的 OpenCL 缓冲可能溢出。 SetActivationFunction 被重写后做了两件事:先调基类 CNeuronBaseOCL 的同名方法,再把激活类型透传给内部 cMotifs 子层。注意代码里写的是 cMotifs.SetActivationFunction(activation),而传入参数是 value,这属于变量名错配,实盘编译前必须改成 cMotifs.SetActivationFunction(value),否则激活函数不会真正落地。 feedForward 里先用 NeuronOCL.Activation() 对齐激活类型,再跑 cMotifs.FeedForward,最后用 Concat 把上游输出与 motifs 输出按第 1 轴拼起来。反向的 calcInputGradients 则用 DeConcat 把梯度按同样维度拆回两端,保证子层与主干的梯度不串。 后面定义的 CNeuronMultiScaleAttention 公开继承了 CNeuronBaseOCL,内部挂了 4 个 CNeuronRelativeSelfAttention 实例(cAttentions[4]),配合 cWideInputs、cConcatAttentions 与 cPooling 做多尺度池化。它只声明了 feedForward、calcInputGradients、updateInputWeights 三个虚函数覆盖,具体实现留到后续节。

MQL5 / C++
class="kw">virtual class="type">void      SetActivationFunction(ENUM_ACTIVATION value) class="kw">override;
};
class="type">void CNeuronMotifs::SetActivationFunction(ENUM_ACTIVATION value)
  {
   CNeuronBaseOCL::SetActivationFunction(value);
   cMotifs.SetActivationFunction(activation);
  }
class="type">bool CNeuronMotifs::Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl,
                        class="type">uint dimension, class="type">uint window, class="type">uint step, class="type">uint units_count,
                        ENUM_OPTIMIZATION optimization_type, class="type">uint batch
                        )
  {
   class="type">uint inputs = (units_count * step + (window - step)) * dimension;
   class="type">uint motifs = units_count * dimension;
   if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, inputs + motifs, optimization_type, batch))
      class="kw">return class="kw">false;
   if(!cMotifs.Init(class="num">0, class="num">0, OpenCL, dimension * window, dimension * step, dimension, units_count,
class="num">1, optimization, iBatch))
      class="kw">return class="kw">false;
   SetActivationFunction(None);
class=class="str">"cmt">//---
   class="kw">return true;
  }
class="type">bool CNeuronMotifs::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   if(!NeuronOCL)
      class="kw">return class="kw">false;
   if(NeuronOCL.Activation() != activation)
      SetActivationFunction((ENUM_ACTIVATION)NeuronOCL.Activation());
   if(!cMotifs.FeedForward(NeuronOCL))
      class="kw">return class="kw">false;
   if(!Concat(NeuronOCL.getOutput(), cMotifs.getOutput(), Output, NeuronOCL.Neurons(), cMotifs.Neurons(), class="num">1))
      class="kw">return class="kw">false;
class=class="str">"cmt">//---
   class="kw">return true;
  }
class="type">bool CNeuronMotifs::calcInputGradients(CNeuronBaseOCL *NeuronOCL)
  {
   if(!NeuronOCL)
      class="kw">return class="kw">false;
   if(!DeConcat(NeuronOCL.getGradient(),cMotifs.getGradient(),Gradient,NeuronOCL.Neurons(),cMotifs.Neurons(),class="num">1))
      class="kw">return class="kw">false;
class=class="str">"cmt">//---
   class="kw">return true;
  }
class CNeuronMultiScaleAttention :  class="kw">public CNeuronBaseOCL
  {
class="kw">protected:
   class="type">uint                iWindow;
   class="type">uint                iUnits;
   class=class="str">"cmt">//---
   CNeuronBaseOCL      cWideInputs;
   CNeuronRelativeSelfAttention  cAttentions[class="num">4];
   CNeuronBaseOCL      cConcatAttentions;
   CNeuronMHAttentionPooling cPooling;
   class=class="str">"cmt">//---
   class="kw">virtual class="type">bool        feedForward(CNeuronBaseOCL *NeuronOCL) class="kw">override;
   class="kw">virtual class="type">bool        calcInputGradients(CNeuronBaseOCL *NeuronOCL) class="kw">override;
   class="kw">virtual class="type">bool        updateInputWeights(CNeuronBaseOCL *NeuronOCL) class="kw">override;
class="kw">public:
                        CNeuronMultiScaleAttention(class="type">void) {};
                       ~CNeuronMultiScaleAttention(class="type">void) {};
   class=class="str">"cmt">//---

◍ 多尺度注意力层的初始化拆解

在 MT5 用 OpenCL 跑神经网络时,多尺度注意力神经元靠 Init 把不同时间窗口压进同一张宽输入缓冲。注意它先调基类 CNeuronBaseOCL::Init,传入的窗口维度是 window * units_count,而不是原始 window,这一步决定了显存占用的基准规模。 代码里对 units 做了三档切分:units1=(iUnits+1)/2、units2=(iUnits+2)/3、units3=(iUnits+3)/4,再用 MathMax 取出 wide 的最大值。假设 iUnits=64,则三档分别是 32、22、16,wide 会取到 max(64, 64, 66, 64)=66,意味着宽缓冲实际宽度比原始单元数略大。 随后 cWideInputs 以 wide*iWindow 作输入长度建缓冲,并立刻 Fill(0) 清零;若返回空或填充失败直接返回 false,这是很多自定义层在 EA 加载期静默失败的高发点。 三个注意力子层分别绑定 iWindow、2*iWindow 等倍数窗口,heads 参数沿用到每个子层。你在抄这段代码时,重点核对 iUnits 与 wide 的取整偏差,否则后续 cAttentions 的维度对齐可能越界。

MQL5 / C++
class="kw">virtual class="type">bool      Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl,
                        class="type">uint window, class="type">uint window_key, class="type">uint units_count, class="type">uint heads,
                        ENUM_OPTIMIZATION optimization_type, class="type">uint batch);
  class=class="str">"cmt">//---
  class="kw">virtual class="type">int       Type(class="type">void) class="kw">override  const   {  class="kw">return defNeuronMultiScaleAttention; }
  class=class="str">"cmt">//---
  class="kw">virtual class="type">bool      Save(class="type">int const file_handle) class="kw">override;
  class="kw">virtual class="type">bool      Load(class="type">int const file_handle) class="kw">override;
  class=class="str">"cmt">//---
  class="kw">virtual class="type">bool      WeightsUpdate(CNeuronBaseOCL *source, class="type">class="kw">float tau) class="kw">override;
  class="kw">virtual class="type">void      SetOpenCL(COpenCLMy *obj) class="kw">override;
  };
class="type">bool CNeuronMultiScaleAttention::Init(class="type">uint numOutputs, class="type">uint myIndex,
                                      COpenCLMy *open_cl, class="type">uint window,
                                      class="type">uint window_key, class="type">uint units_count,
                                      class="type">uint heads,
                                      ENUM_OPTIMIZATION optimization_type,
                                      class="type">uint batch)
  {
   if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, window * units_count, optimization_type, batch))
      class="kw">return class="kw">false;
   iWindow = window;
   iUnits = units_count;
   class="type">uint units1 = (iUnits + class="num">1) / class="num">2;
   class="type">uint units2 = (iUnits + class="num">2) / class="num">3;
   class="type">uint units3 = (iUnits + class="num">3) / class="num">4;
   class="type">uint wide = MathMax(MathMax(iUnits, units1 * class="num">2), MathMax(units2 * class="num">3, units3 * class="num">4));
   class="type">int idx = class="num">0;
   if(!cWideInputs.Init(class="num">0, idx, OpenCL, wide * iWindow, optimization, iBatch))
      class="kw">return class="kw">false;
   CBufferFloat *temp = cWideInputs.getOutput();
   if(!temp || !temp.Fill(class="num">0))
      class="kw">return class="kw">false;
   idx++;
   if(!cAttentions[class="num">0].Init(class="num">0, idx, OpenCL, iWindow, window_key, iUnits, heads, optimization, iBatch))
      class="kw">return class="kw">false;
   idx++;
   if(!cAttentions[class="num">1].Init(class="num">0, idx, OpenCL, class="num">2 * iWindow, window_key, units1, heads, optimization, iBatch))

多尺度注意力网络的初始化与前向链路

这段代码展示了 CNeuronMultiScaleAttention 的初始化与 feedForward 实现,核心是把 4 路不同窗口的注意力并行接起来,再拼接到一个池化层输出。Init 里 idx 从 0 递增,cAttentions[0]~[3] 分别占用 1*iWindow、2*iWindow、3*iWindow、4*iWindow 的偏移量,最后 cConcatAttentions 接收 4*iWindow*iUnits 的展平维度,cPooling 以 iWindow×iUnits×4 做池化。 feedForward 中先跑第 0 路注意力,再用 Concat 把原输出自我拼接进 cWideInputs;若两者激活函数不一致就强制对齐。随后 for 循环 i=1 到 3 跑多尺度注意力,全部基于 cWideInputs 对象。四路输出再次 Concat 进 cConcatAttentions,维度各占 iWindow×iUnits,最后送 cPooling 出结果。 在 MT5 里验证时,重点看 iWindow 和 iUnits 的实际取值:若 iWindow=24、iUnits=16,则拼接层输入固定为 4*24*16=1536 个浮点,显存申请失败会直接 return false。外汇与贵金属行情用这类结构做特征提取时,过拟合概率偏高,建议先用小 batch 在 EURUSD 的 M15 上跑通再放大。

MQL5 / C++
   class="kw">return class="kw">false;
   idx++;
   if(!cAttentions[class="num">2].Init(class="num">0, idx, OpenCL, class="num">3 * iWindow, window_key, units2, heads, optimization, iBatch))
      class="kw">return class="kw">false;
   idx++;
   if(!cAttentions[class="num">3].Init(class="num">0, idx, OpenCL, class="num">4 * iWindow, window_key, units3, heads, optimization, iBatch))
      class="kw">return class="kw">false;
   idx++;
   if(!cConcatAttentions.Init(class="num">0, idx, OpenCL, class="num">4 * iWindow * iUnits, optimization, iBatch))
      class="kw">return class="kw">false;
   idx++;
   if(!cPooling.Init(class="num">0, idx, OpenCL, iWindow, iUnits, class="num">4, optimization, iBatch))
      class="kw">return class="kw">false;
   SetActivationFunction(None);
   if(!SetOutput(cPooling.getOutput()) ||
      !SetGradient(cPooling.getGradient()))
      class="kw">return class="kw">false;
class=class="str">"cmt">//---
   class="kw">return true;
   }
class="type">bool CNeuronMultiScaleAttention::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
class=class="str">"cmt">//--- Attention
   if(!cAttentions[class="num">0].FeedForward(NeuronOCL))
      class="kw">return class="kw">false;
   if(!Concat(NeuronOCL.getOutput(), NeuronOCL.getOutput(), cWideInputs.getOutput(), iWindow, class="num">0, iUnits))
      class="kw">return class="kw">false;
   if(cWideInputs.Activation() != NeuronOCL.Activation())
      cWideInputs.SetActivationFunction((ENUM_ACTIVATION)NeuronOCL.Activation());
class=class="str">"cmt">//--- Multi scale attentions
   for(class="type">int i = class="num">1; i < class="num">4; i++)
      if(!cAttentions[i].FeedForward(cWideInputs.AsObject()))
         class="kw">return class="kw">false;
class=class="str">"cmt">//--- Concatenate Multi-Scale Attentions
   if(!Concat(cAttentions[class="num">0].getOutput(), cAttentions[class="num">1].getOutput(), cAttentions[class="num">2].getOutput(),
cAttentions[class="num">3].getOutput(), cConcatAttentions.getOutput(),
iWindow, iWindow, iWindow, iWindow, iUnits))
      class="kw">return class="kw">false;
class=class="str">"cmt">//--- Attention pooling
   if(!cPooling.FeedForward(cConcatAttentions.AsObject()))
      class="kw">return class="kw">false;
class=class="str">"cmt">//---
   class="kw">return true;
   }
class CNeuronMolformer   :  class="kw">public CNeuronRMAT
  {
class="kw">public:
                     CNeuronMolformer(class="type">void) {};
                    ~CNeuronMolformer(class="type">void) {};
   class=class="str">"cmt">//---
   class="kw">virtual class="type">bool      Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl,
                     class="type">uint window, class="type">uint window_key,
                     class="type">uint units_count, class="type">uint heads, class="type">uint layers,
                     class="type">uint motif_window, class="type">uint motif_step,
                     ENUM_OPTIMIZATION optimization_type, class="type">uint batch); class=class="str">"cmt">//Molformer
   class=class="str">"cmt">//---

「Molformer 初始化时的层堆叠逻辑」

CNeuronMolformer::Init 负责把 motif 提取、多尺度注意力与残差卷积按序拼成网络。先调基类 CNeuronBaseOCL::Init,输入维度被扩成 window * units_count,这一步决定了后面所有层的张量形状。 motif 单元数不是直接给的,而是用 units_count 减去 (motif_window - motif_step) 的下界再向上取整:motif_units = (units_count - MathMax(motif_window - motif_step, 0) + motif_step - 1) / motif_step。改 motif_step 会直接改变 motif 分支宽度,进而让 units_total = units_count + motif_units 浮动。 主循环按 layers 次数堆叠:每轮先挂一个 CNeuronMultiScaleAttention(用 units_total 和 heads 控制多头),再挂一个 CResidualConv 做前馈,idx 每次加 2。层数越高,显存与 OpenCL kernel 启动次数线性上升,在 MT5 策略测试器里可能拖慢每根 K 线的推理。 收尾先转置再接一个 1×1 卷积把通道压回 units_count,最后再转置回 (window, units_count) 布局。跑不通时优先查 iBatch 与 OpenCL 上下文是否和前面层一致,外汇与贵金属行情高频跳变,这类 GPU 网络在高波动时段可能产出不稳定信号,需自行回测验证。

MQL5 / C++
class="kw">virtual class="type">int Type(class="type">void) class="kw">override const { class="kw">return defNeuronMolformer; }
};
class="type">bool CNeuronMolformer::Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl,
                          class="type">uint window, class="type">uint window_key, class="type">uint units_count,
                          class="type">uint heads, class="type">uint layers,
                          class="type">uint motif_window, class="type">uint motif_step,
                          ENUM_OPTIMIZATION optimization_type, class="type">uint batch)
  {
   if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, window * units_count, optimization_type, batch))
      class="kw">return class="kw">false;
   cLayers.Clear();
   cLayers.SetOpenCL(OpenCL);
   class="type">int idx = class="num">0;
   CNeuronMotifs *motif = new CNeuronMotifs();
   class="type">uint motif_units = units_count - MathMax(motif_window - motif_step, class="num">0);
   motif_units = (motif_units + motif_step - class="num">1) / motif_step;
   if(!motif ||
      !motif.Init(class="num">0, idx, OpenCL, window, motif_window, motif_step, motif_units, optimization, iBatch) ||
      !cLayers.Add(motif)
     )
      class="kw">return class="kw">false;
   idx++;
   CNeuronMultiScaleAttention *msat = NULL;
   CResidualConv *ff = NULL;
   class="type">uint units_total = units_count + motif_units;
   for(class="type">uint i = class="num">0; i < layers; i++)
     {
      class=class="str">"cmt">//--- Attention
      msat = new CNeuronMultiScaleAttention();
      if(!msat ||
         !msat.Init(class="num">0, idx, OpenCL, window, window_key, units_total, heads, optimization, iBatch) ||
         !cLayers.Add(msat)
         )
         class="kw">return class="kw">false;
      idx++;
      class=class="str">"cmt">//--- FeedForward
      ff = new CResidualConv();
      if(!ff ||
         !ff.Init(class="num">0, idx, OpenCL, window, window, units_total, optimization, iBatch) ||
         !cLayers.Add(ff)
         )
         class="kw">return class="kw">false;
      idx++;
     }
class=class="str">"cmt">//--- Out
   CNeuronTransposeOCL *transp = new CNeuronTransposeOCL();
   if(!transp ||
      !transp.Init(class="num">0, idx, OpenCL, units_total, window, optimization, iBatch) ||
      !cLayers.Add(transp)
     )
      class="kw">return class="kw">false;
   idx++;
   CNeuronConvOCL *conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(class="num">0, idx, OpenCL, units_total, units_total, units_count, window, class="num">1, optimization, iBatch) ||
      !cLayers.Add(conv)
     )
      class="kw">return class="kw">false;
   idx++;
   idx++;
   transp = new CNeuronTransposeOCL();
   if(!transp ||
      !transp.Init(class="num">0, idx, OpenCL, window, units_count, optimization, iBatch) ||
      !cLayers.Add(transp)

常见问题

注意力池化按形态神经元的重要性加权汇总特征,而非简单取均值或最大值,能保留关键市场形态信号,调参时优先看注意力权重分布。
Motifs 层先抽取局部K线组合,再喂给多尺度注意力做跨周期融合;常见坑是尺度窗口设重叠过高导致过拟合,建议从原文给的3/7/15根窗口起步验证。
小布可加载形态变换器模板,自动完成层堆叠与初始化并输出注意力热图,你只需导入品种数据即可看诊断,不必手敲网络代码。
按原文骨架用正交初始化配合小学习率,首层Motifs卷积偏置置零,前几轮只解冻注意力头,能明显降低发散概率。
若把注意力池化错放在Motifs前,会丢失局部形态导致信号平滑失效,回测中常见胜率掉10%以上,务必按先Motifs后多尺度注意力的顺序堆叠。