交易中的神经网络:受控分段(终章)·进阶篇
🧠

交易中的神经网络:受控分段(终章)·进阶篇

(2/3)·从OCM模块自交叉注意力结构到完整框架落地,啃下提升1.57%性能的那块硬骨头

偏理论进阶 第 2/3 篇

不少人在复现RefMask3D时把对象聚类模块当成标准变换器解码器直接套用,忽略了交叉注意力被改写后的信息流顺序,导致嵌入聚合偏差。上篇停在了OCM算法开发前夜,这一篇接着把模块实现和整体框架拼起来。

「Transformer 模块在 OpenCL 下的逐层装配」

这段初始化逻辑把价格序列的上下文编码拆成了多头自注意力、交叉注意力与残差三条链路,全部走 OpenCL 后端。每一层神经元或卷积对象都用 Init 绑定层号、窗口长度、头数与单元数,任一层返回 false 就直接中断整个模型构建。 自注意力部分从层号 4 起:先建一个卷积层做 Primary 序列的 Q/K/V 投影,窗口参数由 iPrimHeads * iWindowKey 决定;随后接 CNeuronBaseOCL 做残差(层号 5),再进交叉注意力,Query 来自 Context 窗口(层号 6),Key/Value 来自 Primary 窗口(层号 7、8)。 交叉注意力输出先经多头合并层(层号 9,神经元数 = iContHeads * iWindowKey * iContUnits),再卷积回 iContHeads * iWindowKey 通道(层号 10),并补一层残差(层号 11)。Context 端的自注意力复用同样结构,Query/Key/Value 均为层号 12–14,窗口统一取 iContWindow。 在 MT5 里跑这套,最易踩的坑是 iWindowKeyiBatch 不匹配导致显存分配失败;建议先写个 print 把每层 Neurons() 打出来,确认 4 到 13 层参数连续后再接训练。外汇与贵金属行情跳空频繁,这类大模型在实盘高杠杆下误差可能被放大,验证时先用模拟盘小批量跑。

MQL5 / C++
   !conv.Init(class="num">0, class="num">4, OpenCL, iPrimHeads * iWindowKey, iPrimHeads * iWindowKey, iPrimWindow, iPrimUnits, class="num">1,
optimization, iBatch) ||
   !cAttentionOut.Add(conv)
   )
      class="kw">return class="kw">false;
class=class="str">"cmt">//--- Residual
   neuron = new CNeuronBaseOCL();
   if(!neuron ||
      !neuron.Init(class="num">0, class="num">5, OpenCL, conv.Neurons(), optimization, iBatch) ||
      !cResidual.Add(neuron)
   )
      class="kw">return class="kw">false;
class=class="str">"cmt">//--- Cross-Attention
class=class="str">"cmt">//--- Query
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(class="num">0, class="num">6, OpenCL, iContWindow, iContWindow, iContHeads * iWindowKey, iContUnits, class="num">1,
optimization, iBatch) ||
      !cQuery.Add(conv)
   )
      class="kw">return class="kw">false;
class=class="str">"cmt">//--- Key
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(class="num">0, class="num">7, OpenCL, iPrimWindow, iPrimWindow, iPrimHeads * iWindowKey, iPrimUnits, class="num">1,
optimization, iBatch) ||
      !cKey.Add(conv)
   )
      class="kw">return class="kw">false;
class=class="str">"cmt">//--- Value
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(class="num">0, class="num">8, OpenCL, iPrimWindow, iPrimWindow, iPrimHeads * iWindowKey, iPrimUnits, class="num">1,
optimization, iBatch) ||
      !cValue.Add(conv)
   )
      class="kw">return class="kw">false;
class=class="str">"cmt">//--- Multi-Heads Cross-Attention Out
   neuron = new CNeuronBaseOCL();
   if(!neuron ||
      !neuron.Init(class="num">0, class="num">9, OpenCL, iContHeads * iWindowKey * iContUnits, optimization, iBatch) ||
      !cMHAttentionOut.Add(neuron)
   )
      class="kw">return class="kw">false;
class=class="str">"cmt">//--- Cross-Attention Out
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(class="num">0, class="num">10, OpenCL, iContHeads * iWindowKey, iContHeads * iWindowKey, iContWindow, iContUnits, class="num">1,
optimization, iBatch) ||
      !cAttentionOut.Add(conv)
   )
      class="kw">return class="kw">false;
class=class="str">"cmt">//--- Residual
   neuron = new CNeuronBaseOCL();
   if(!neuron ||
      !neuron.Init(class="num">0, class="num">11, OpenCL, conv.Neurons(), optimization, iBatch) ||
      !cResidual.Add(neuron)
   )
      class="kw">return class="kw">false;
class=class="str">"cmt">//--- Context Self-Attention
class=class="str">"cmt">//--- Query
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(class="num">0, class="num">12, OpenCL, iContWindow, iContWindow, iContHeads * iWindowKey, iContUnits, class="num">1,
optimization, iBatch) ||
      !cQuery.Add(conv)
   )
      class="kw">return class="kw">false;
class=class="str">"cmt">//--- Key
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(class="num">0, class="num">13, OpenCL, iContWindow, iContWindow, iContHeads * iWindowKey, iContUnits, class="num">1,
optimization, iBatch) ||
      !cKey.Add(conv)
   )
      class="kw">return class="kw">false;
class=class="str">"cmt">//--- Value
   conv = new CNeuronConvOCL();
   if(!conv ||

Transformer块里的多头与残差接线

这段初始化把一次完整注意力子层在 OpenCL 神经元容器里逐层挂好:从多头注意力的 Value 卷积(层号14)到 MHAttentionOut 全连接(层号15),再到 AttentionOut 卷积(层号16)与 Residual 基线(层号17),任何一步 Init 失败就直接 return false,说明显存或参数维度不匹配会在这里立刻暴露。 FeedForward 部分用了两组卷积:先以窗口数 4 倍扩维并置 LReLU 激活(层号18),再收敛回原窗口维度(层号19),最后 SetGradient 把梯度指针绑到末层卷积上,这一结构在 EURUSD 的 M15 回测中常见参数是 iContWindow=30、iContUnits=64。 feedForward 方法里先让 Query/Key/Value 三组(索引0)各自前向,再走 AttentionOut 算分;之后 cAttentionOut[0] 的输出会和 Primitives 原输出做 SumAndNormilize 残差归一——这里第4参数 iPrimWindow 若与你上下文窗口不一致,归一会越界报错。外汇与贵金属杠杆高,这类 GPU 算子一旦维度写错,实盘推理延迟可能从毫秒级跳到不可接受区间,开 MT5 用 Experts/Files 里的日志核对层号最直白。

MQL5 / C++
   !conv.Init(class="num">0, class="num">14, OpenCL, iContWindow, iContWindow, iContHeads * iWindowKey, iContUnits, class="num">1,
optimization, iBatch) ||
   !cValue.Add(conv)
   )
      class="kw">return class="kw">false;
class=class="str">"cmt">//--- Multi-Heads Attention Out
   neuron = new CNeuronBaseOCL();
   if(!neuron ||
      !neuron.Init(class="num">0, class="num">15, OpenCL, iContHeads * iWindowKey * iContUnits, optimization, iBatch) ||
      !cMHAttentionOut.Add(neuron)
   )
      class="kw">return class="kw">false;
class=class="str">"cmt">//--- Attention Out
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(class="num">0, class="num">16, OpenCL, iContHeads * iWindowKey, iContHeads * iWindowKey, iContWindow, iContUnits, class="num">1,
optimization, iBatch) ||
      !cAttentionOut.Add(conv)
   )
      class="kw">return class="kw">false;
class=class="str">"cmt">//--- Residual
   neuron = new CNeuronBaseOCL();
   if(!neuron ||
      !neuron.Init(class="num">0, class="num">17, OpenCL, conv.Neurons(), optimization, iBatch) ||
      !cResidual.Add(neuron)
   )
      class="kw">return class="kw">false;
class=class="str">"cmt">//--- Feed Forward
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(class="num">0, class="num">18, OpenCL, iContWindow, iContWindow, class="num">4 * iContWindow, iContUnits, class="num">1, optimization, iBatch) ||
      !cFeedForward.Add(conv)
   )
      class="kw">return class="kw">false;
   conv.SetActivationFunction(LReLU);
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(class="num">0, class="num">19, OpenCL, class="num">4*iContWindow, class="num">4*iContWindow, iContWindow, iContUnits, class="num">1, optimization, iBatch) ||
      !cFeedForward.Add(conv)
   )
      class="kw">return class="kw">false;
   if(!SetGradient(conv.getGradient()))
      class="kw">return class="kw">false;
class=class="str">"cmt">//---
   SetOpenCL(OpenCL);
class=class="str">"cmt">//---
   class="kw">return true;
   }
class="type">bool CNeuronOCM::feedForward(CNeuronBaseOCL *Primitives, CNeuronBaseOCL *Context)
   {
   CNeuronBaseOCL *neuron = NULL, *q = cQuery[class="num">0], *k = cKey[class="num">0], *v = cValue[class="num">0];
   if(!q || !k || !v)
      class="kw">return class="kw">false;
   if(!q.FeedForward(Primitives) ||
      !k.FeedForward(Primitives) ||
      !v.FeedForward(Primitives)
   )
      class="kw">return class="kw">false;
   if(!AttentionOut(q, k, v, cScores[class="num">0], cMHAttentionOut[class="num">0], iPrimUnits, iPrimHeads, iPrimUnits, iPrimHeads,
iWindowKey))
      class="kw">return class="kw">false;
   neuron = cAttentionOut[class="num">0];
   if(!neuron ||
      !neuron.FeedForward(cMHAttentionOut[class="num">0])
   )
      class="kw">return class="kw">false;
   v = cResidual[class="num">0];
   if(!v ||
      !SumAndNormilize(Primitives.getOutput(), neuron.getOutput(), v.getOutput(), iPrimWindow, true, class="num">0, class="num">0, class="num">0, class="num">1)
   )
      class="kw">return class="kw">false;
   neuron = v;
class=class="str">"cmt">//--- Cross-Attention
   q = cQuery[class="num">1];
   k = cKey[class="num">1];

◍ 双层自注意力与残差归一的串联实现

这段逻辑跑的是两层 Context Self-Attention 加一个前馈层的 Transformer 式前向传播,任何一层返回 false 都会直接中断,说明容错边界很硬。 第一层先把 cValue[1] 赋给 v,随后 q/k/v 三个张量各自 FeedForward(Context 或 neuron),只要有一个为空或前向失败就 return false。AttentionOut 调用后结果写入 cMHAttentionOut[1],再用 SumAndNormilize 做残差加归一,参数里末位的 1 表示归一化维度标记。 第二层换用索引 2 的 q/k/v,但 FeedForward 的输入改成了第一层输出的 neuron,形成层间依赖;注意这里 q 被重新赋值为 cAttentionOut[1] 并对接 cMHAttentionOut[2],残差源是 cResidual[2]。两层窗口参数 iContWindow 与 iWindowKey 若设错,注意力分数会塌成近零矩阵。 最后的前馈段用 cFeedForward[0] 和 [1] 做两层线性变换,k.FeedForward(q) 吃的是第一前馈的输出,最终 SumAndNormilize 把 neuron 与 k 的输出求和归一进 Output。外汇与贵金属行情下用这类结构做特征提取,过拟合概率偏高,建议在 MT5 策略测试器里先拿 EURUSD 的 M15 跑一遍看层间梯度是否断流。

MQL5 / C++
  v = cValue[class="num">1];
  if(!q || !k || !v)
      class="kw">return class="kw">false;
  if(!q.FeedForward(Context) ||
     !k.FeedForward(neuron) ||
     !v.FeedForward(neuron)
    )
      class="kw">return class="kw">false;
  if(!AttentionOut(q, k, v, cScores[class="num">1], cMHAttentionOut[class="num">1], iContUnits, iContHeads, iPrimUnits, iPrimHeads,
iWindowKey))
      class="kw">return class="kw">false;
  neuron = cAttentionOut[class="num">1];
  if(!neuron ||
     !neuron.FeedForward(cMHAttentionOut[class="num">1])
    )
      class="kw">return class="kw">false;
  v = cResidual[class="num">1];
  if(!v ||
     !SumAndNormilize(Context.getOutput(), neuron.getOutput(), v.getOutput(), iContWindow, true, class="num">0, class="num">0, class="num">0, class="num">1)
    )
      class="kw">return class="kw">false;
  neuron = v;
class=class="str">"cmt">//--- Context Self-Attention
  q = cQuery[class="num">2];
  k = cKey[class="num">2];
  v = cValue[class="num">2];
  if(!q || !k || !v)
      class="kw">return class="kw">false;
  if(!q.FeedForward(neuron) ||
     !k.FeedForward(neuron) ||
     !v.FeedForward(neuron)
    )
      class="kw">return class="kw">false;
  if(!AttentionOut(q, k, v, cScores[class="num">2], cMHAttentionOut[class="num">2], iContUnits, iContHeads, iPrimUnits, iPrimHeads,
iWindowKey))
      class="kw">return class="kw">false;
  q = cAttentionOut[class="num">1];
  if(!q ||
     !q.FeedForward(cMHAttentionOut[class="num">2])
    )
      class="kw">return class="kw">false;
  v = cResidual[class="num">2];
  if(!v ||
     !SumAndNormilize(q.getOutput(), neuron.getOutput(), v.getOutput(), iContWindow, true, class="num">0, class="num">0, class="num">0, class="num">1)
    )
      class="kw">return class="kw">false;
  neuron = v;
class=class="str">"cmt">//--- Feed Forward
  q = cFeedForward[class="num">0];
  k = cFeedForward[class="num">1];
  if(!q || !k ||
     !q.FeedForward(neuron) ||
     !k.FeedForward(q) ||
     !SumAndNormilize(neuron.getOutput(), k.getOutput(), Output, iContWindow, true, class="num">0, class="num">0, class="num">0, class="num">1)
    )
      class="kw">return class="kw">false;
class=class="str">"cmt">//---
  class="kw">return true;
  }

「把零散模块拼成 CNeuronRefMask 统一架构」

实现 RefMask3D 的核心动作,是新建一个 CNeuronRefMask 类,把几何-强化群-单词注意力、上下文编码器、背景令牌生成、语言原语模块和对象聚集解码器全部静态挂载。所有对象声明为静态,构造/析构留空,真正的初始化全部压进 Init 方法,只暴露输入/输出数据维度这类最少参数,内部对象尽量复用外部参数——比如单个序列窗口大小同时复用作基元嵌入和上下文嵌入尺寸。 点云编码器在每个阶段叠两层注意力,layers 参数只控制 U 形瓶颈里的嵌入数,并开启梯度求和。上下文编码器不单独造模块,而是复用优化 3D-GRES 的编码器:先全连接层存账户状态向量,再生成嵌入,最后拼接上下文与背景令牌。背景令牌多 3 个可学习位,用来吸收账户描述里的噪声和异常值。 解码器没用原版 Transformer 层,而是换成了之前开发的对象聚集模块(OCM),并循环绑定 U 形点编码器各层做依赖分析。前馈时先由上下文嵌入起步,点编码器把它当第二数据源;背景令牌只在点编码器内过滤噪声。最后一步与原文不同:原框架把点编码器输出乘 OCM 输出再过检测头做像素级分割,这里直接省略检测头,因为交易决策只需要形态存在性与参数,不需要视觉分段。 反向传播里 calcInputGradients 完全逆转前馈,但重点在处理动态实体——可训练原语、上下文嵌入、背景令牌。背景令牌和上下文同子空间,所以要在级联张量里加多元化误差,再分别派回;瓶颈层之前开的梯度汇总就是为保留点编码器内层梯度。末尾经 MLP 反向循环把梯度派给背景令牌生成模型,返回成功标志即结束。 附件里给的源码包含完整类与方法,模型架构仅微调了环境描述层,交互与训练程序沿用旧工作。外汇与贵金属市场高波动,这类模型输出仅作概率参考,实盘前务必在 MT5 用历史数据跑通前馈与反向传播。

MQL5 / C++
class CNeuronRefMask     :  class="kw">public CNeuronBaseOCL
  {
class="kw">protected:
   CNeuronGEGWA       cGEGWA;
   CLayer             cContentEncoder;
   CLayer             cBackGround;
   CNeuronLPC         cLPC;
   CLayer             cDecoder;
   CNeuronOCM         cOCM;
   class=class="str">"cmt">//---
   class="kw">virtual class="type">bool      feedForward(CNeuronBaseOCL *NeuronOCL) class="kw">override { class="kw">return class="kw">false; }
   class="kw">virtual class="type">bool      feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) class="kw">override;
   class=class="str">"cmt">//---
   class="kw">virtual class="type">bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) class="kw">override { class="kw">return class="kw">false; }
   class="kw">virtual class="type">bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput,
CBufferFloat *SecondGradient,
ENUM_ACTIVATION SecondActivation = None) class="kw">override;

参考掩码神经元的初始化与权重冻结

在 MT5 的 OpenCL 神经网络扩展里,CNeuronRefMask 这个类把输入权重更新直接 override 返回 false,意味着该层不参与常规反向传播调权,属于结构固定的参考掩码节点。 看 Init 实现,它先调基类 CNeuronBaseOCL::Init,传入的窗口维度被乘上 content_units,这一步决定了基础张量形状。随后初始化 cGEGWA(几何增强组词注意力),其中 content_units+3 作为特征宽度、layers 设为 2,是这套注意力子模块的最小可跑配置。 如果你在自研 EA 里复用该类,注意 AddNeckGradient(true) 会强制梯度回传至颈部连接,但 updateInputWeights 返回 false 仍锁死输入侧权重。外汇与贵金属行情下用此类结构做特征提取,模型过拟合概率偏高,属高风险实验性用法,建议先用历史 Tick 在策略测试器里跑通再上模拟盘。

MQL5 / C++
class="kw">virtual class="type">bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) class="kw">override { class="kw">return class="kw">false; }
class="kw">virtual class="type">bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) class="kw">override;
class="kw">public:
                      CNeuronRefMask(class="type">void) {};
                     ~CNeuronRefMask(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 content_size, class="type">uint content_units,
                         class="type">uint primitive_units, class="type">uint layers,
                         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 defNeuronRefMask; }
  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 CNeuronRefMask::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 content_size, class="type">uint content_units,
                         class="type">uint primitive_units, class="type">uint layers,
                         ENUM_OPTIMIZATION optimization_type, class="type">uint batch)
  {
  if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, window * content_units, optimization_type, batch))
     class="kw">return class="kw">false;
class=class="str">"cmt">//--- Geometry-Enhaced Group-Word Attention
  if(!cGEGWA.Init(class="num">0, class="num">0, OpenCL, window, window_key, heads, units_count, window, heads, (content_units + class="num">3), class="num">2,
layers, optimization, iBatch))
     class="kw">return class="kw">false;
  cGEGWA.AddNeckGradient(true);
class=class="str">"cmt">//--- Content Encoder
  cContentEncoder.Clear();
  cContentEncoder.SetOpenCL(OpenCL);
  CNeuronBaseOCL *neuron = new CNeuronBaseOCL();
  if(!neuron ||
让小布替你跑这套结构校验
这些网络模块的结构校验和嵌入维度诊断,小布盯盘的AIGC已内置,打开对应品种页即可看到异常提示,你只需判断信号有没有脱离价格行为语境。

常见问题

原版解码器交叉注意力在后、自注意力在前且各带FFN,OCM把交叉注意力夹在两个自注意力之间并省略了编码端FFN,属于紧凑改写版,不能直接套用标准实现。
可以,小布盯盘内置了结构维度与输出嵌入的异常检测,能提示聚类偏移,但外汇贵金属属高风险,模型输出仅作概率参考,决策仍靠你自己。
点云多模态定位任务中,语义专注度微调带来的点数级精度提升会连锁影响后续识别召回,作者实验确认该模块为框架不可或缺部分。
本篇通过解码器输出接入OCM初始自注意力,LPC生成的语义属性嵌入作为交叉注意力的一侧输入,与上篇类实现直接复用。
常漏掉对象聚类模块输出端FFN的MLP维度对齐,以及点编码器与解码器之间的令牌结构透传,需对照原可视化逐层核对。