交易中的神经网络:免掩码注意力方式预测价格走势·进阶篇
🧠

交易中的神经网络:免掩码注意力方式预测价格走势·进阶篇

(2/3)· SPFormer 收敛慢的症结在初始掩码,本篇用中心回归绕过它直接加速

实战向 第 2/3 篇

不少人在复现点云变换器做行情预测时,发现训练前几百步损失几乎不动,便归咎于学习率或数据量。真实瓶颈常在初始对象掩码品质过低,迫使后续层反复修正错误引导,拖慢整体收敛。

◍ 多头注意力里的归约与 MAFT 神经元骨架

这段 OpenCL 内核片段在做一件事:把局部内存里的注意力分数按 key 维度做分段归约,再写回输出缓冲。第一段 do-while 里,count 从 0 起跳,只要 (count * ls) 小于 (kunits - k_id) 就继续累加;sh_v 的偏移量直接由 2 * dimension * heads_kv * count * ls 算出,说明每个 head 的 value 块在显存里是连续铺开的。 累加时用了三元表达式处理首轮权重:count==0 取 sc(可能是缩放常数),否则取 score 数组里对应位置。isnan(sum) 被显式清零,避免 NaN 在并行归约中污染整个 temp[k_id]——这在 MT5 的 GPU 算子里是必做的防御,否则回测时某根乱序 tick 就能让整层输出报废。 第二段是经典的并行求和树:count 先取 min(ls, kunits),每次折半,temp[k_id] 拉上 temp[k_id+count] 累加,另一半置 0,靠 CLK_LOCAL_MEM_FENCE 保证线程间可见。最终 out[shift_q + d] = temp[0],意味着每个 query 位置只保留归约后的标量。 落到 CNeuronMAFT 这个类,它继承自 CNeuronBaseOCL,成员变量暴露了窗口与单元的配置:iWindow / iUnits / iHeads 管主序列,iSPWindow / iSPUnits / iSPHeads 管 super-point 分支,iWindowKey 与 iLayers / iLayersSP 控制交叉注意力的深度。cQuery、cQKey、cQValue 与 cSPKey、cSPValue 分开声明,说明自注意力与跨注意力用了独立权重层,cScores 和 cPositionBias 则用 CArrayInt 存整数索引——开 MT5 把这类神经元挂到 EURUSD 的 M15 上,先调 iHeads 从 4 改 8,显存占用约翻倍但注意力分辨率会明显变细。外汇与贵金属杠杆交易高风险,参数改动仅影响模型结构,不预示任何收益。

MQL5 / C++
   do
      {
         if((count * ls) < (kunits - k_id))
            {
             class="type">int sh_v = class="num">2 * dimension * heads_kv * count * ls;
             class="type">class="kw">float sum =
                  v[shift_kv + d + sh_v] * (count == class="num">0 ? sc : score[shift_s + count * ls]);
             if(isnan(sum))
                  sum = class="num">0;
             temp[k_id] = (count > class="num">0 ? temp[k_id] : class="num">0) + sum;
            }
         count++;
       }
    while((count * ls + k_id) < kunits);
   barrier(CLK_LOCAL_MEM_FENCE);
   class=class="str">"cmt">//---
   count = min(ls, (class="type">uint)kunits);
   do
      {
       count = (count + class="num">1) / class="num">2;
       if(k_id < ls)
          temp[k_id] += (k_id < count && (k_id + count) < kunits ? temp[k_id + count] : class="num">0);
       if(k_id + count < ls)
          temp[k_id + count] = class="num">0;
       barrier(CLK_LOCAL_MEM_FENCE);
      }
   while(count > class="num">1);
   class=class="str">"cmt">//---
   out[shift_q + d] = temp[class="num">0];
 }
}
class CNeuronMAFT   : class="kw">public CNeuronBaseOCL
 {
class="kw">protected:
  class="type">uint            iWindow;
  class="type">uint            iUnits;
  class="type">uint            iHeads;
  class="type">uint            iSPWindow;
  class="type">uint            iSPUnits;
  class="type">uint            iSPHeads;
  class="type">uint            iWindowKey;
  class="type">uint            iLayers;
  class="type">uint            iLayersSP;
  class=class="str">"cmt">//---
  CLayer          cSuperPoints;
  CLayer          cQuery;
  CLayer          cQPosition;
  CLayer          cQKey;
  CLayer          cQValue;
  CLayer          cMHSelfAttentionOut;
  CLayer          cSelfAttentionOut;
  CLayer          cSPKey;
  CLayer          cSPValue;
  CArrayInt       cScores;
  CArrayInt       cPositionBias;
  CLayer          cMHCrossAttentionOut;
  CLayer          cCrossAttentionOut;

「多头注意力层的缓冲区与接口声明」

在 MT5 用 OpenCL 跑 Transformer 类模型时,多头注意力模块需要先占好显存与内存缓冲。下面这段类成员声明给出了一个典型实现骨架:残差连接、前馈网络各占一个 CLayer,而 cTempSP、cTempQ、cTempCrossK、cTempCrossV 这组 CBufferFloat 临时缓冲,分别承接 softmax 分数、查询向量以及交叉注意力的 K/V 投影,避免每次前向都重新分配。 虚函数 CreateBuffers 负责把上面那些缓冲按实际序列长度与维度开出来;CalcPositionBias 接收 pos_q、pos_k 指针和 pos_bias 偏移量,在指定 units(如 64)与 units_kv、dimension 下计算旋转或偏置位置编码。AttentionOut 则是核心:传入 q/k/v 三个神经元对象、scores 头分数、heads 与 heads_kv(例如 8 与 8),use_pos_bias 开关决定是否注入位置偏置,输出写到 out 缓冲。 反向部分由 AttentionInsideGradients 与 calcInputGradients、updateInputWeights 覆盖基类方法完成,feedForward 也在此层 override。你在自写 EA 时若想接这套结构,直接照搬成员声明并在 CreateBuffers 里按 units*heads 分配 cTempQ 大小即可,外汇与贵金属行情高频跳变,显存规划不当可能让策略掉帧甚至崩端,属高风险操作。

MQL5 / C++
  CLayer        cResidual;
  CLayer        cFeedForward;
  CBufferFloat  cTempSP;
  CBufferFloat  cTempQ;
  CBufferFloat  cTempCrossK;
  CBufferFloat  cTempCrossV;
  class=class="str">"cmt">//---
  class="kw">virtual class="type">bool      CreateBuffers(class="type">void);
  class="kw">virtual class="type">bool      CalcPositionBias(CBufferFloat *pos_q, CBufferFloat *pos_k, class="kw">const class="type">int pos_bias,
                                     class="kw">const class="type">int units,
                                     class="kw">const class="type">int units_kv,
                                     class="kw">const class="type">int dimension);
  class="kw">virtual class="type">bool      AttentionOut(CNeuronBaseOCL *q, CNeuronBaseOCL *k, CNeuronBaseOCL *v,
                                 class="kw">const class="type">int scores, CNeuronBaseOCL *out, class="kw">const class="type">int pos_bias,
                                 class="kw">const class="type">int units,
                                 class="kw">const class="type">int heads,
                                 class="kw">const class="type">int units_kv,
                                 class="kw">const class="type">int heads_kv,
                                 class="kw">const class="type">int dimension,
                                 class="kw">const class="type">bool use_pos_bias);
  class="kw">virtual class="type">bool      AttentionInsideGradients(CNeuronBaseOCL *q, CNeuronBaseOCL *k, CNeuronBaseOCL *v,
                     class="kw">const class="type">int scores, CNeuronBaseOCL *out,
                     class="kw">const class="type">int units, class="kw">const class="type">int heads,
                     class="kw">const class="type">int units_kv, class="kw">const class="type">int heads_kv,
                     class="kw">const class="type">int dimension);
  class=class="str">"cmt">//---
  class="kw">virtual class="type">bool      feedForward(CNeuronBaseOCL *NeuronOCL) class="kw">override;
  class=class="str">"cmt">//---
  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:
                    CNeuronMAFT(class="type">void) {};

CNeuronMAFT 的初始化与成员布局

在 MT5 的 OpenCL 神经网络框架里,CNeuronMAFT 类通过默认构造函数留空,真正的资源申请全压在 Init 方法。Init 参数表暴露了多头注意力机制的关键维度:window 与 units_count 决定输入序列长度与每头单元数,heads 控制并行注意力头数,带 _sp 后缀的一组则对应二级子路径的空间维度。 调用父类 CNeuronBaseOCL::Init 时,传入的神经元规模被显式写成 window * units_count,而非原始 window 值。这意味着底层张量按「时间窗 × 单元数」展平,若你在 EURUSD 的 M15 上取 window=60、units_count=8,实际申请的是 480 宽的特征向量,显存占用会随该乘积线性走升。 iLayers 与 iLayersSP 都用 MathMax(x, 1) 兜底,保证至少跑一层变换。随后 new 一个 CNeuronBaseOCL 作基础子层,其 Init 的第二个参数写死为 0(myIndex),输出维度压成 1,优化类型与批大小沿用类内 optimization / iBatch 成员。 初始化尾声对 getOutput 与 getWeights 两处 CBufferFloat 做 BufferInit + BufferWrite 校验:输出缓冲写固定 1×1,权重缓冲按 Total() 长度清零。任一步返回 false 都会中断 Init,实盘加载自定义模型前,建议先在策略测试器里单步跑一遍该类,确认 OpenCL 上下文与缓冲绑定无报错。外汇与贵金属杠杆品种波动剧烈,此类 GPU 算子若初始化失败可能导致信号停滞,需配合风控熔断。

MQL5 / C++
  ~CNeuronMAFT(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 window_sp, class="type">uint units_sp, class="type">uint heads_sp,
                         class="type">uint layers, class="type">uint layers_to_sp,
                         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  class="kw">const   {  class="kw">return defNeuronMAFT; }
  class=class="str">"cmt">//---
  class="kw">virtual class="type">bool      Save(class="type">int class="kw">const file_handle) class="kw">override;
  class="kw">virtual class="type">bool      Load(class="type">int class="kw">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 CNeuronMAFT::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 window_sp, class="type">uint units_sp, class="type">uint heads_sp,
                       class="type">uint layers, class="type">uint layers_to_sp,
                       ENUM_OPTIMIZATION optimization_type, class="type">uint batch)
  {
  if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, window * units_count, optimization_type, batch))
     class="kw">return false;
  iWindow = window;
  iUnits = units_count;
  iHeads = heads;
  iSPUnits = units_sp;
  iSPWindow = window_sp;
  iSPHeads = heads_sp;
  iWindowKey = window_key;
  iLayers = MathMax(layers, class="num">1);
  iLayersSP = MathMax(layers_to_sp, class="num">1);
  CNeuronBaseOCL *base = new CNeuronBaseOCL();
  if(!base)
     class="kw">return false;
  if(!base.Init(iWindow * iUnits, class="num">0, OpenCL, class="num">1, optimization, iBatch))
     class="kw">return false;
  CBufferFloat *buf = base.getOutput();
  if(!buf || !buf.BufferInit(class="num">1, class="num">1) || !buf.BufferWrite())
     class="kw">return false;
  buf = base.getWeights();
  if(!buf || !buf.BufferInit(buf.Total(), class="num">0) ||
     !buf.BufferWrite())

◍ 残差卷积与超点层的初始化分支

这段初始化逻辑里,SuperPoints 部分用了一个 4 轮循环来堆叠卷积结构,循环变量 r 从 0 到 3,每轮根据 iSPUnits 的奇偶性决定走残差块还是普通卷积。 当 iSPUnits 为偶数时,先把它减半,再 new 一个 CResidualConv,用 2*iSPWindow 作输入宽度、iSPWindow 作核宽去 Init;奇数时则 iSPUnits 减 1,改挂 CNeuronConvOCL,步长固定为 1。 循环结束后 layer_id 会从初始的 4 累加到 8,再补一层 CNeuronConvOCL 把通道收束到 iWindow,随后接一个 CNeuronLearnabledPE 做位置编码——任何一步 Init 或 Add 返回 false 都会直接中断整个构建。 在 MT5 里跑这套时,建议先打印 iSPUnits 初值,若它是 2 的幂,前 4 轮会全走残差分支,显存占用倾向比奇数路径更平滑。

MQL5 / C++
  class="kw">return false;
  if(!cQuery.Add(base))
    class="kw">return false;
  base = new CNeuronBaseOCL();
  if(!base || !base.Init(class="num">0, class="num">1, OpenCL, iWindow * iUnits, optimization, iBatch))
    class="kw">return false;
  if(!cQuery.Add(base))
    class="kw">return false;
  CNeuronLearnabledPE *pe = new CNeuronLearnabledPE();
  if(!pe || !pe.Init(class="num">0, class="num">2, OpenCL, base.Neurons(), optimization, iBatch))
    class="kw">return false;
  if(!cQuery.Add(pe))
    class="kw">return false;
  if(!base || !base.Init(class="num">0, class="num">3, OpenCL, pe.Neurons(), optimization, iBatch))
    class="kw">return false;
  if(!base.SetOutput(pe.getOutput()))
    class="kw">return false;
  if(!cQPosition.Add(base))
    class="kw">return false;
class=class="str">"cmt">//--- Init SuperPoints
  class="type">int layer_id = class="num">4;
  for(class="type">int r = class="num">0; r < class="num">4; r++)
    {
     if(iSPUnits % class="num">2 == class="num">0)
       {
        iSPUnits /= class="num">2;
        CResidualConv *residual = new CResidualConv();
        if(!residual)
          class="kw">return false;
        if(!residual.Init(class="num">0, layer_id, OpenCL, class="num">2 * iSPWindow, iSPWindow, iSPUnits, optimization, iBatch))
          class="kw">return false;
        if(!cSuperPoints.Add(residual))
          class="kw">return false;
       }
     else
       {
        iSPUnits--;
        CNeuronConvOCL *conv = new CNeuronConvOCL();
        if(!conv.Init(class="num">0, layer_id, OpenCL, class="num">2 * iSPWindow, iSPWindow, iSPWindow, iSPUnits, class="num">1, optimization, iBatch))
          class="kw">return false;
        if(!cSuperPoints.Add(conv))
          class="kw">return false;
       }
     layer_id++;
    }
  CNeuronConvOCL *conv = new CNeuronConvOCL();
  if(!conv.Init(class="num">0, layer_id, OpenCL, iSPWindow, iSPWindow, iWindow, iSPUnits, class="num">1, optimization, iBatch))
    class="kw">return false;
  if(!cSuperPoints.Add(conv))
    class="kw">return false;
  layer_id++;
  pe = new CNeuronLearnabledPE();
  if(!pe || !pe.Init(class="num">0, layer_id, OpenCL, conv.Neurons(), optimization, iBatch))
    class="kw">return false;
  if(!cSuperPoints.Add(pe))

「Transformer 层内的注意力堆叠」

这段初始化逻辑跑在每层循环里,iLayers 决定堆叠深度,每进一层 layer_id 自增并在各子模块间传递,保证 OpenCL 上下文里的神经元编号不撞车。 自注意力部分先连续建三个一维卷积做 Query、Key、Value,卷积核窗口宽 iWindow、输出通道数 iWindowKey*iHeads,再接一个 CNeuronBaseOCL 做多头拼接后的线性映射,维度是 iWindowKey*iHeads*iUnits。 随后 Self-Attention Out 用卷积把 iWindowKey*iHeads 压回 iWindow 时间步,Residual 分支基元直接吃 iWindow*iUnits 的展平向量。跨境注意力开头又挂了一个和自注意力同构的 Query 卷积,说明编码器-解码器交互也走了一套独立参数。 在 MT5 里把 iHeads 从 4 调到 8,layer_id 的占用会近乎翻倍,显存吃紧时容易在 Init 返回 false 处断链,建议先小批次 iBatch=32 探一下。

MQL5 / C++
  layer_id++;
class=class="str">"cmt">//--- Inside layers
  for(class="type">uint l = class="num">0; l < iLayers; l++)
    {
    class=class="str">"cmt">//--- Self-Attention
    class=class="str">"cmt">//--- Query
      conv = new CNeuronConvOCL();
      if(!conv || !conv.Init(class="num">0, layer_id, OpenCL, iWindow, iWindow, iWindowKey * iHeads, iUnits, class="num">1,
optimization, iBatch))
        class="kw">return false;
      if(!cQuery.Add(conv))
        class="kw">return false;
      layer_id++;
    class=class="str">"cmt">//--- Key
      conv = new CNeuronConvOCL();
      if(!conv || !conv.Init(class="num">0, layer_id, OpenCL, iWindow, iWindow, iWindowKey * iHeads, iUnits, class="num">1,
optimization, iBatch))
        class="kw">return false;
      if(!cQKey.Add(conv))
        class="kw">return false;
      layer_id++;
    class=class="str">"cmt">//--- Value
      conv = new CNeuronConvOCL();
      if(!conv || !conv.Init(class="num">0, layer_id, OpenCL, iWindow, iWindow, iWindowKey * iHeads, iUnits, class="num">1,
optimization, iBatch))
        class="kw">return false;
      if(!cQValue.Add(conv))
        class="kw">return false;
      layer_id++;
    class=class="str">"cmt">//--- Multy-Heads Attention Out
      base = new CNeuronBaseOCL();
      if(!base || !base.Init(class="num">0, layer_id, OpenCL, iWindowKey * iHeads * iUnits, optimization, iBatch))
        class="kw">return false;
      if(!cMHSelfAttentionOut.Add(base))
        class="kw">return false;
      layer_id++;
    class=class="str">"cmt">//--- Self-Attention Out
      conv = new CNeuronConvOCL();
      if(!conv || !conv.Init(class="num">0, layer_id, OpenCL, iWindowKey * iHeads, iWindowKey * iHeads, iWindow,
iUnits, class="num">1, optimization, iBatch))
        class="kw">return false;
      if(!cSelfAttentionOut.Add(conv))
        class="kw">return false;
      layer_id++;
    class=class="str">"cmt">//--- Residual
      base = new CNeuronBaseOCL();
      if(!base || !base.Init(class="num">0, layer_id, OpenCL, iWindow * iUnits, optimization, iBatch))
        class="kw">return false;
      if(!cResidual.Add(base))
        class="kw">return false;
      layer_id++;
    class=class="str">"cmt">//--- Cross-Attention
    class=class="str">"cmt">//--- Query
      conv = new CNeuronConvOCL();
      if(!conv || !conv.Init(class="num">0, layer_id, OpenCL, iWindow, iWindow, iWindowKey * iHeads, iUnits, class="num">1,
optimization, iBatch))
        class="kw">return false;
      if(!cQuery.Add(conv))

交叉注意力层收尾的残差与前馈拼接

这段构建逻辑出现在多头注意力主干之后,负责把跨注意力输出、残差连接和前馈网络逐层挂到对应的容器里。每推一层都把 layer_id 自增 1,保证 OpenCL 内核里的张量维度不串号。 当循环变量 l 能被 iLayersSP 整除时,代码会再插一对 Key/Value 卷积层:两者都用 iWindowKey * iSPHeads 作输入通道、iSPUnits 作输出通道,卷积核宽高均为 iWindow。这类插入频率直接由 iLayersSP 控制,设成 2 就代表每两层补一次 KV,显存占用可能随层数线性抬升。 跨注意力输出卷积的 shape 是 [iWindowKey*iHeads, iWindowKey*iHeads, iWindow, iUnits],紧接着一个 iWindow*iUnits 的 Base 层做残差。前馈部分先扩到 4*iWindow 通道并设 LReLU,再卷回 iWindow——4 倍扩展是 Transformer 里常见的前馈比,MT5 终端跑这类网络时 GPU 显存峰值可能卡在这一层。

MQL5 / C++
class="kw">return false;
layer_id++;
if(l % iLayersSP == class="num">0)
  {
   class=class="str">"cmt">//--- Key
   conv = new CNeuronConvOCL();
   if(!conv || !conv.Init(class="num">0, layer_id, OpenCL, iWindow, iWindow, iWindowKey * iSPHeads, iSPUnits, class="num">1, optimization, iBatch))
     class="kw">return false;
   if(!cSPKey.Add(conv))
     class="kw">return false;
   layer_id++;
   class=class="str">"cmt">//--- Value
   conv = new CNeuronConvOCL();
   if(!conv || !conv.Init(class="num">0, layer_id, OpenCL, iWindow, iWindow, iWindowKey * iSPHeads, iSPUnits, class="num">1, optimization, iBatch))
     class="kw">return false;
   if(!cSPValue.Add(conv))
     class="kw">return false;
   layer_id++;
   }
class=class="str">"cmt">//--- Multy-Heads Attention Out
base = new CNeuronBaseOCL();
if(!base || !base.Init(class="num">0, layer_id, OpenCL, iWindowKey * iHeads * iUnits, optimization, iBatch))
  class="kw">return false;
if(!cMHCrossAttentionOut.Add(base))
  class="kw">return false;
layer_id++;
class=class="str">"cmt">//--- Cross-Attention Out
conv = new CNeuronConvOCL();
if(!conv || !conv.Init(class="num">0, layer_id, OpenCL, iWindowKey * iHeads, iWindowKey * iHeads, iWindow, iUnits, class="num">1, optimization, iBatch))
  class="kw">return false;
if(!cCrossAttentionOut.Add(conv))
  class="kw">return false;
layer_id++;
class=class="str">"cmt">//--- Residual
base = new CNeuronBaseOCL();
if(!base || !base.Init(class="num">0, layer_id, OpenCL, iWindow * iUnits, optimization, iBatch))
  class="kw">return false;
if(!cResidual.Add(base))
  class="kw">return false;
layer_id++;
class=class="str">"cmt">//--- Feed Forward
conv = new CNeuronConvOCL();
if(!conv || !conv.Init(class="num">0, layer_id, OpenCL, iWindow, iWindow, class="num">4 * iWindow, iUnits, class="num">1, optimization, iBatch))
  class="kw">return false;
conv.SetActivationFunction(LReLU);
if(!cFeedForward.Add(conv))
  class="kw">return false;
layer_id++;
conv = new CNeuronConvOCL();
if(!conv || !conv.Init(class="num">0, layer_id, OpenCL, class="num">4 * iWindow, class="num">4 * iWindow, iWindow, iUnits, class="num">1, optimization, iBatch))
把特征收敛诊断交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到训练曲线里的掩码引导偏移预警,你只需判断要不要换 MATF 结构。

常见问题

位置查询随机初始化,代表归一化物体中心;内容查询从零值开始,两者在交叉注意力中迭代互纠,位置查询负责把上下文拉到局部区域。
小布目前提供特征收敛与查询偏移的可视化诊断,模型训练仍需你在终端部署,它帮你省去重复看板劳动。
它按查询间相对位置调权重,不依赖刚性掩码,查询位置可迭代更新,对不规则价格点云更友好。
中心回归给查询明确空间锚点,降低初期掩码随机性,使每层专注局部独特结构,训练复杂性随之下降。
行情噪声大、分布漂移快,模型可能过拟合历史点云,实盘概率性失效,杠杆品种须严格控仓。