交易中的神经网络:超点变换器(SPFormer)·进阶篇
🛰️

交易中的神经网络:超点变换器(SPFormer)·进阶篇

(2/3)· 跳过语义分支与后期处理,用超点+查询解码器直接出实例掩码

实战向 第 2/3 篇
很多人在点云实例分段里硬套基于假设或聚类的旧管线,结果被稀疏点和间接标签拖垮训练。SPFormer 的两阶段设计不是更花哨,而是把分组和提案解耦,少走聚合弯路。先弄清超点怎么来,再谈上线。

SPFormer 神经元的掩码梯度与结构骨架

在基于 OpenCL 的 Transformer 变体里,掩码梯度不能直接反向透传,必须按阈值截断。下面这段内核逻辑遍历 key 维度,当掩码值低于 mask_level 时梯度置 0,否则回写 1 减原掩码值,等于把软门控的饱和区掐掉。 for(int k = q_id; k < kunits; k += qunits) { float m = mask[shift_s + k]; if(m < mask_level) mask_g[shift_s + k] = 0; else mask_g[shift_s + k] = 1 - m; } CNeuronSPFormer 这个类把整套稀疏点交叉注意力打包进一个神经元层。它显式持有 iWindow / iUnits / iHeads 与对应的 SP(SuperPoints)分支窗口、单元数、头数,以及 iLayers、iLayersSP 控制堆叠深度。 内部缓冲区分两类:cSuperPoints、cQuery、cSPKeyValue、cMask 等 CLayer 负责参数与激活;cTempSP、cTempQ、cTempSelfKV、cTempCrossKV 这些 CBufferFloat 则是前向和梯度计算时的临时张量,避免每次推理重复申请显存。 AttentionOut 虚函数把 query 分支、key-value 分支、scores 索引、输出层和 mask 层全串起来,units 与 heads 作为参数传入,说明多头拆分在调用点动态决定,而不是写死在类里。外汇与贵金属行情用这类结构做序列建模时波动剧烈,属高风险场景,回测结论仅代表历史样本倾向。

MQL5 / C++
                                       __global class="kw">const class="type">float *mask, __global class="type">float *mask_g,
                                       __global class="kw">const class="type">float *scores, __global class="kw">const class="type">float *gradient,
                                       class="kw">const class="type">int kunits, class="kw">const class="type">int heads_kv, class="kw">const class="type">float mask_level
)
  {
class=class="str">"cmt">//--- Mask&class="macro">#x27;s gradient
   for(class="type">int k = q_id; k < kunits; k += qunits)
     {
      class="type">float m = mask[shift_s + k];
      if(m < mask_level)
        mask_g[shift_s + k] = class="num">0;
      else
        mask_g[shift_s + k] = class="num">1 - m;
     }
  }
class CNeuronSPFormer   : 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              cSPKeyValue;
   CLayer              cMask;
   CArrayInt           cScores;
   CLayer              cMHCrossAttentionOut;
   CLayer              cCrossAttentionOut;
   CLayer              cResidual;
   CLayer              cQKeyValue;
   CLayer              cMHSelfAttentionOut;
   CLayer              cSelfAttentionOut;
   CLayer              cFeedForward;
   CBufferFloat        cTempSP;
   CBufferFloat        cTempQ;
   CBufferFloat        cTempSelfKV;
   CBufferFloat        cTempCrossKV;
   class=class="str">"cmt">//---
   class="kw">virtual class="type">bool        CreateBuffers(class="type">void);
   class="kw">virtual class="type">bool        AttentionOut(CNeuronBaseOCL *q, CNeuronBaseOCL *kv, class="kw">const class="type">int scores,
                                    CNeuronBaseOCL *out, CNeuronBaseOCL *mask,
                                    class="kw">const class="type">int units,
                                    class="kw">const class="type">int heads,

◍ SPFormer 神经元的接口与重载方法

在 MT5 的 OpenCL 神经网络框架里,CNeuronSPFormer 类把标准注意力与前馈逻辑做成了一套可序列化的算子。它对外暴露的虚函数几乎全部 override 了基类,意味着你替换网络层时不需要改训练循环。 注意力部分给了两个入口:AttentionInside 做前向打分,AttentionInsideGradients 回传梯度。两个方法都接收 q、kv 张量指针,以及 units_kv、heads_kv、dimension 等形状参数,mask_level 默认 0.5f,用来控制注意力遮罩的阈值。 Init 函数的参数表值得细看:window 与 window_key 分别控制查询和键的回溯窗口,units_count 与 heads 定义多头维度,window_sp、units_sp、heads_sp 则是稀疏(SP)分支的独立配置。layers 与 layers_to_sp 决定总层数和多少层走稀疏路径,最后 optimization_type 与 batch 接优化器与批大小。 类还重载了 Save / Load,文件句柄由调用方传入,方便你把训好的 SPFormer 权重直接落盘到 .nnw 文件。外汇与贵金属行情噪声大,用这类结构做特征提取时务必用小样本先验证过拟合风险。

MQL5 / C++
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">float mask_level = class="num">0.5f);
  class="kw">virtual class="type">bool      AttentionInsideGradients(CNeuronBaseOCL *q, CNeuronBaseOCL *kv, class="kw">const class="type">int scores,
                                      CNeuronBaseOCL *out, CNeuronBaseOCL *mask,
                                      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,
                                     &nbsp;&nbsp; class="kw">const class="type">int dimension,
                                      class="kw">const class="type">float mask_level = class="num">0.5f);
  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:
              CNeuronSPFormer(class="type">void) {};
              ~CNeuronSPFormer(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 defNeuronSPFormer; }
  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">//---

「SPFormer 神经元的初始化链路」

在 MT5 的 OpenCL 神经网络扩展里,CNeuronSPFormer 的 Init 方法负责把时序窗口、多头维度与超点(SuperPoint)分支一次性铺开。它先调用基类 CNeuronBaseOCL::Init,输入维度被强制设为 window * units_count,这一步若返回 false,整个神经元直接废掉。 初始化里最值得盯的是两段 Query 缓冲:第一段 base 以 iWindow*iUnits 作输入、输出维度 1,第二段反过来用 1 作输入、iWindow*iUnits 作输出,两者都塞进 cQuery 数组。任何一次 BufferInit(1,1) 或 BufferWrite() 失败,函数都会提前 return false,开 MT5 跑自定义 EA 时若报空指针,八成卡在这两块缓冲没写通。 超点部分用 for(r=0; r<4; r++) 循环最多叠 4 层 CResidualConv。当 iSPUnits 为偶数时折半并接一个卷积残差块,窗口参数传 2*iSPWindow 与 iSPWindow;奇数则 iSPUnits-- 后跳过当前层。这种硬上限 4 层的写法意味着,layers_to_sp 参数实际生效层数不会超过 4,调参时别指望堆更多。 外汇与贵金属行情受杠杆与跳空影响,这类 GPU 加速模型仅作概率侧辅助,实盘前务必在策略测试器用历史数据回测验证。

MQL5 / C++
class="kw">virtual class="type">bool      WeightsUpdate(CNeuronBaseOCL *source, class="type">float tau) class="kw">override;
 class="kw">virtual class="type">void      SetOpenCL(COpenCLMy *obj) class="kw">override;
};
class="type">bool CNeuronSPFormer::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);
class=class="str">"cmt">//--- Init Querys
  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;
  if(!cQuery.Add(base))
     class="kw">return false;
  base = new CNeuronBaseOCL();
  if(!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;
class=class="str">"cmt">//--- Init SuperPoints
  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, r+class="num">2, OpenCL, class="num">2*iSPWindow, iSPWindow, iSPUnits, optimization, iBatch))
           class="kw">return false;
        if(!cSuperPoints.Add(residual))
           class="kw">return false;
       }
     else
       {
        iSPUnits--;

交叉注意力层的卷积初始化细节

在 MT5 的 OpenCL 神经网络封装里,跨注意力模块不是直接堆全连接,而是先用一维卷积把 Query、Key-Value 和 Mask 各自投影到不同头空间。上面这段就是逐层循环里对第 l 层做初始化的核心代码,每层偏移量用 l * 14 + 6 起步,说明单层占了至少 14 个内部索引槽位。 Query 分支的卷积窗口设为 iWindow,输出通道是 iWindowKey * iHeads,意味着多头数会直接放大显存占用;若 iHeads=8、iWindowKey=16,单这路就产出 128 个特征图。 Key-Value 只在 l % iLayersSP == 0 时新建,相当于每 iLayersSP 层才做一次超分辨率分支的下采样投影,能省掉重复计算。Mask 分支最后挂了 SIGMOID 激活,并用转置卷积把 iSPUnits 投回 iUnits * iHeads,这一步决定了注意力权重是否会被压到 0~1 区间。 开 MT5 把 iHeads 从 4 调到 8,若显卡是入门级核显,Init 返回 false 的概率会明显上升——显存不够时别硬撑。外汇与贵金属行情受杠杆影响,这类模型仅作辅助,实盘高风险。

MQL5 / C++
CNeuronConvOCL *conv = new CNeuronConvOCL();
if(!conv.Init(class="num">0, r+class="num">2, 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;
   }
   }
CNeuronConvOCL *conv = NULL;
CNeuronTransposeOCL *transp = NULL;
for(class="type">uint l = class="num">0; l < iLayers; l++)
{
class=class="str">"cmt">//--- Cross Attention
class=class="str">"cmt">//--- Query
conv = new CNeuronConvOCL();
if(!conv)
   class="kw">return false;
if(!conv.Init(class="num">0, l * class="num">14 + class="num">6, OpenCL, iWindow, iWindow, iWindowKey * iHeads, iUnits,
class="num">1, optimization, iBatch))
   class="kw">return false;
if(!cQuery.Add(conv))
   class="kw">return false;
class=class="str">"cmt">//--- Key-Value
if(l % iLayersSP == class="num">0)
   {
   conv = new CNeuronConvOCL();
   if(!conv)
      class="kw">return false;
   if(!conv.Init(class="num">0, l * class="num">14 + class="num">7, OpenCL, iSPWindow, iSPWindow, iWindowKey * iSPHeads,
iSPUnits, class="num">1, optimization, iBatch))
      class="kw">return false;
   if(!cSPKeyValue.Add(conv))
      class="kw">return false;
   }
class=class="str">"cmt">//--- Mask
conv = new CNeuronConvOCL();
if(!conv)
   class="kw">return false;
if(!conv.Init(class="num">0, l * class="num">14 + class="num">8, OpenCL, iSPWindow, iSPWindow, iUnits * iHeads, iSPUnits,
class="num">1, optimization, iBatch))
   class="kw">return false;
conv.SetActivationFunction(SIGMOID);
if(!cMask.Add(conv))
   class="kw">return false;
transp = new CNeuronTransposeOCL();
if(!transp)
   class="kw">return false;
if(!transp.Init(class="num">0, l * class="num">14 + class="num">9, OpenCL, iSPUnits, iUnits * iHeads, optimization, iBatch))
   class="kw">return false;
if(!cMask.Add(transp))
   class="kw">return false;
class=class="str">"cmt">//--- MH Cross Attention out
base = new CNeuronBaseOCL();
if(!base)
   class="kw">return false;

◍ Transformer 块里多头注意力的层叠组装

这段初始化逻辑跑在循环体里,每一层 transformer 都用固定的 14 个索引步长来排布子模块,例如 Cross Attention 输出层占用 l*14+11,残差层占用 l*14+12,自注意力 Query 卷积落在 l*14+13。读懂这个偏移规律,你就能在 MT5 的神经网络调试器里直接定位某一层的张量维度异常。 Key-Value 分支不是每层都建:代码里用 l % iLayersSP == 0 做门控,只有当层号能被稀疏层间隔整除时才新增 cQKeyValue 卷积,这意味着稀疏多头注意力是隔层插入的,显存占用会呈现阶梯式跳变。 所有神经元都走 OpenCL 后端初始化,iBatch 作为批维度贯穿 Conv 与 Base 层。若你在贵金属 EA 里复用这套结构,注意 iWindow*iUnits*iHeads 的乘积一旦超过显卡缓冲区,Init 会静默返回 false,外汇与贵金属杠杆交易本身高风险,模型加载失败不等于策略失效,先排查显存再谈信号。

MQL5 / C++
if(!base.Init(class="num">0, l * class="num">14 + class="num">10, OpenCL, iWindow * iUnits * iHeads, optimization, iBatch))
   class="kw">return false;
if(!cMHCrossAttentionOut.Add(base))
   class="kw">return false;
class=class="str">"cmt">//--- Cross Attention out
conv = new CNeuronConvOCL();
if(!conv)
   class="kw">return false;
if(!conv.Init(class="num">0, l * class="num">14 + class="num">11, OpenCL, iWindow * iHeads, iWindow * iHeads, iWindow, iUnits,
class="num">1, optimization, iBatch))
   class="kw">return false;
if(!cCrossAttentionOut.Add(conv))
   class="kw">return false;
class=class="str">"cmt">//--- Residual
base = new CNeuronBaseOCL();
if(!base)
   class="kw">return false;
if(!base.Init(class="num">0, l * class="num">14 + class="num">12, OpenCL, iWindow * iUnits, optimization, iBatch))
   class="kw">return false;
if(!cResidual.Add(base))
   class="kw">return false;
class=class="str">"cmt">//--- Self-Attention
class=class="str">"cmt">//--- Query
conv = new CNeuronConvOCL();
if(!conv)
   class="kw">return false;
if(!conv.Init(class="num">0, l*class="num">14+class="num">13, OpenCL, iWindow, iWindow, iWindowKey * iHeads, iUnits,
                                                               class="num">1, optimization, iBatch))
   class="kw">return false;
if(!cQuery.Add(conv))
   class="kw">return false;
class=class="str">"cmt">//--- Key-Value
if(l % iLayersSP == class="num">0)
   {
   conv = new CNeuronConvOCL();
   if(!conv)
      class="kw">return false;
   if(!conv.Init(class="num">0, l*class="num">14+class="num">14, OpenCL, iWindow, iWindow, iWindowKey * iSPHeads, iUnits,
class="num">1, optimization, iBatch))
      class="kw">return false;
   if(!cQKeyValue.Add(conv))
      class="kw">return false;
   }
class=class="str">"cmt">//--- MH Attention out
base = new CNeuronBaseOCL();
if(!base)
   class="kw">return false;
if(!base.Init(class="num">0, l * class="num">14 + class="num">15, OpenCL, iWindow * iUnits * iHeads, optimization, iBatch))
   class="kw">return false;

「自注意力层后的残差与前馈拼接」

在多层自注意力结构里,每一层除了注意力输出,还要接残差连接和前馈网络,否则深层梯度容易在反向传播时衰减到接近 0。下面这段初始化逻辑把第 l 层的输出维度算成 l*14+16 起步,每层多占 5 个索引号,写死在 Init 的参数里。 注意力输出用一个 1 维卷积把维度从 iWindow*iHeads 压回 iWindow,再进残差容器;紧接着的前馈部分先用卷积扩到 iWindow*4 并挂 LReLU 激活,再用第二个卷积收回 iWindow,这两步的索引分别是 l*14+18 和 l*14+19。 残差节点 CNeuronBaseOCL 在每层出现两次,第二次会调用 SetGradient(conv.getGradient()) 把前馈末层的梯度直接接过来。最后一层(l == iLayers-1)额外把梯度设给整体网络,这一步漏掉的话回测时损失可能卡住不降。外汇与贵金属行情噪声大,这类深度结构过拟合概率偏高,上 MT5 用历史数据跑前先小规模验证梯度通路。

MQL5 / C++
if(!cMHSelfAttentionOut.Add(base))
     class="kw">return false;
class=class="str">"cmt">//--- Attention out
conv = new CNeuronConvOCL();
if(!conv)
     class="kw">return false;
if(!conv.Init(class="num">0, l * class="num">14 + class="num">16, OpenCL, iWindow * iHeads, iWindow * iHeads, iWindow, iUnits,
class="num">1, optimization, iBatch))
     class="kw">return false;
if(!cSelfAttentionOut.Add(conv))
     class="kw">return false;
class=class="str">"cmt">//--- Residual
base = new CNeuronBaseOCL();
if(!base)
     class="kw">return false;
if(!base.Init(class="num">0, l * class="num">14 + class="num">17, OpenCL, iWindow * iUnits, optimization, iBatch))
     class="kw">return false;
if(!cResidual.Add(base))
     class="kw">return false;
class=class="str">"cmt">//--- FeedForward
conv = new CNeuronConvOCL();
if(!conv)
     class="kw">return false;
if(!conv.Init(class="num">0, l * class="num">14 + class="num">18, OpenCL, iWindow, iWindow, iWindow * class="num">4, iUnits, class="num">1, optimization, iBatch))
     class="kw">return false;
conv.SetActivationFunction(LReLU);
if(!cFeedForward.Add(conv))
     class="kw">return false;
conv = new CNeuronConvOCL();
if(!conv)
     class="kw">return false;
if(!conv.Init(class="num">0, l * class="num">14 + class="num">19, OpenCL, iWindow * class="num">4, iWindow * class="num">4, iWindow, 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)
     class="kw">return false;
if(!base.Init(class="num">0, l * class="num">14 + class="num">20, OpenCL, iWindow * iUnits, optimization, iBatch))
     class="kw">return false;
if(!cResidual.Add(base))
     class="kw">return false;
if(!base.SetGradient(conv.getGradient()))
     class="kw">return false;
if(l == (iLayers - class="num">1))
     {
      if(!SetGradient(conv.getGradient()))
把点云诊断交给小布盯盘
这些超点分组与查询解码的推理耗时、掩码置信度分布,小布盯盘的 AIGC 已内置可视化,打开对应品种页即可对照看盘口异动。

常见问题

它用稀疏 3D U-Net 提点级特征后直接点池化成超点,靠实例标签监督,不依赖语义分段输出,因此去掉了语义分支和中间聚合。
目前小布内置的是推理耗时与置信度分布的可视化对照,原始掩码需自行导出,但可借其页面对比品种波动结构。
在自上而下提案阶段,基于超点掩码做双分匹配,无需劳力密集型聚合,端到端训练时直接匹配查询与实例。
此类资产高频跳空与低流动性会造成点云极稀疏,模型可能倾向过拟合表面片段,实盘概率性失效,属高风险尝试。