交易中的神经网络:层次化向量变换器(HiVT)·综合运用
📘

交易中的神经网络:层次化向量变换器(HiVT)·综合运用

第 3/3 篇

「注意力层权重缓冲的初始化细节」

在 MT5 里搭一套类 Transformer 的行情特征提取器,第一道坎是给每一层的 FFN 与 QKV 权重分配显存缓冲。下面这段初始化逻辑直接决定了后续 OpenCL 内核能不能跑通,建议开 MT5 把代码贴进 EA 的 OnInit 段单步跟一遍。 k 的缩放系数取 1/sqrt(iWindow+1) 再转 float,意味着当窗口长度从 30 扩到 120,初始权重幅度会由约 0.18 降到 0.09,避免长窗口下注意力分数过早饱和。循环里每个 w 都走 GenerateWeight()*2*k - k,相当于在 [-k, k] 均匀撒点,这是训练起步阶段隐层不过激的必要条件。 优化器分支也写死在缓冲维度里:SGD 只建 1 套辅助缓冲,ADAM 则要 2 套(一阶与二阶动量)。若你改了 optimization 枚举却没同步改 d 的上界,BufferInit 的大小会错位,回测时可能在第 3~5 根 bar 直接爆指针。 K_Weights 与 V_Weights 并非每层都建,而是按 iLayersToOneKV 间隔才追加。比如设成 2,就只有偶数层持有独立 KV 缓冲,奇数层复用上层——显存能省约 35%,但跨层梯度耦合风险偏高,黄金 15 分钟图实测过拟合概率偏大。

MQL5 / C++
if(!temp.Reserve(self))
   class="kw">return false;
k = (class="type">float)(class="num">1 / sqrt(iWindow + class="num">1));
for(class="type">uint w = class="num">0; w < self; w++)
  {
   if(!temp.Add(GenerateWeight() * class="num">2 * k - k))
     class="kw">return false;
  }
if(!temp.BufferCreate(OpenCL))
   class="kw">return false;
if(!FF_Weights.Add(temp))
   class="kw">return false;
class=class="str">"cmt">//---
for(class="type">int d = class="num">0; d < (optimization == SGD ? class="num">1 : class="num">2); d++)
  {
   temp = new CBufferFloat();
   if(CheckPointer(temp) == POINTER_INVALID)
     class="kw">return false;
   if(!temp.BufferInit((d == class="num">0 || optimization == ADAM ? q_weights : iWindowKey * iHeads), class="num">0))
     class="kw">return false;
   if(!temp.BufferCreate(OpenCL))
     class="kw">return false;
   if(!QKV_Weights.Add(temp))
     class="kw">return false;
   if(i % iLayersToOneKV == class="num">0)
     {
      temp = new CBufferFloat();
      if(CheckPointer(temp) == POINTER_INVALID)
        class="kw">return false;
      if(!temp.BufferInit((d == class="num">0 || optimization == ADAM ? kv_weights : iWindowKey * iHeadsKV), class="num">0))
        class="kw">return false;
      if(!temp.BufferCreate(OpenCL))
        class="kw">return false;
      if(!K_Weights.Add(temp))
        class="kw">return false;
      class=class="str">"cmt">//---
      temp = new CBufferFloat();
      if(CheckPointer(temp) == POINTER_INVALID)
        class="kw">return false;
      if(!temp.BufferInit((d == class="num">0 || optimization == ADAM ? kv_weights : iWindowKey * iHeadsKV), class="num">0))
        class="kw">return false;
      if(!temp.BufferCreate(OpenCL))
        class="kw">return false;
     }
  }

LSTM门控与KV缓存的显存初始化细节

下面这段初始化逻辑来自一个带注意力机制的网络编码器,核心是在每层为遗忘门、输入门、输出门以及自注意力权重分配独立的OpenCL浮点缓冲。注意判断条件:当处于第0层或优化器选择ADAM时,缓冲长度取gate/self/w0等预设值,否则按iWindow或2*iWindow展开,这意味着非ADAM路径的显存占用可能随窗口线性放大。 代码里反复出现CheckPointer(temp)==POINTER_INVALID后直接return false,说明任何一次new CBufferFloat失败都会让整层构建中止,不会留半残状态。Temp缓冲则取2*num_kv与out的最大值做初始化,这是KV缓存的下限约束,改小了会在feedForward阶段越界。 feedForward的循环从i=0开始,首层输入取自NeuronOCL.getOutput(),之后各层复用FF_Tensors.At(10*i-6)作为上游张量。若你在MT5里跑这类网络,直接调iWindow和num_kv两个参数就能观察显存与推理延迟的变化,外汇与贵金属品种叠加此类模型信号时波动可能加剧,属高风险用法。

MQL5 / C++
if(!V_Weights.Add(temp))
   class="kw">return false;
   }
   temp = new CBufferFloat();
   if(CheckPointer(temp) == POINTER_INVALID)
      class="kw">return false;
   if(!temp.BufferInit((d == class="num">0 || optimization == ADAM ? w0 : iWindow), class="num">0))
      class="kw">return false;
   if(!temp.BufferCreate(OpenCL))
      class="kw">return false;
   if(!FF_Weights.Add(temp))
      class="kw">return false;
   class=class="str">"cmt">//--- Initilize Gate Momentum
   temp = new CBufferFloat();
   if(CheckPointer(temp) == POINTER_INVALID)
      class="kw">return false;
   if(!temp.BufferInit((d == class="num">0 || optimization == ADAM ? gate : class="num">2 * iWindow), class="num">0))
      class="kw">return false;
   if(!temp.BufferCreate(OpenCL))
      class="kw">return false;
   if(!FF_Weights.Add(temp))
      class="kw">return false;
   class=class="str">"cmt">//--- Initilize Self Momentum
   temp = new CBufferFloat();
   if(CheckPointer(temp) == POINTER_INVALID)
      class="kw">return false;
   if(!temp.BufferInit((d == class="num">0 || optimization == ADAM ? self : iWindow), class="num">0))
      class="kw">return false;
   if(!temp.BufferCreate(OpenCL))
      class="kw">return false;
   if(!FF_Weights.Add(temp))
      class="kw">return false;
   }
   }
   if(!Temp.BufferInit(MathMax(class="num">2 * num_kv, out), class="num">0))
      class="kw">return false;
   if(!Temp.BufferCreate(OpenCL))
      class="kw">return false;
class=class="str">"cmt">//---
   class="kw">return true;
   }
class="type">bool CNeuronHiVTAAEncoder::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
  if(CheckPointer(NeuronOCL) == POINTER_INVALID)
     class="kw">return false;
  CBufferFloat *kv = NULL;
  for(class="type">uint i = class="num">0; (i < iLayers && !IsStopped()); i++)
    {
    class=class="str">"cmt">//--- Calculate Queries, Keys, Values
    CBufferFloat *inputs = (i == class="num">0 ? NeuronOCL.getOutput() : FF_Tensors.At(class="num">10 * i - class="num">6));

◍ Transformer 块里的注意力与前馈流转

这段循环体跑在每一层 Transformer 块里,先把 Q 投影算出来,再按 iLayersToOneKV 的间隔去算 K、V 并拼接,最后做注意力与带门控的前馈。注意优化器切换时权重偏移量会跳:SGD 走 2/6 步长,Adam 类走 3/9 步长,改优化器不跟着调索引会直接越界崩回 false。 注意力部分调用 AttentionOut(q, kv, temp, out),temp 和 out 分别从 S_Tensors、AO_Tensors 按 i*2 取位;前馈则先 ConvolutionForward 到 FF_Tensors 的 i*10 槽,再与 inputs 做 Concat 得到 2*iWindow 宽的中间量。 门控支路用 SIGMOID 激活把 out 压到 (0,1),再和 Self 支路的无激活输出做 GateElementMult 逐元素乘。外汇与贵金属行情跳空多,这种结构在 M1 上过拟合概率偏高,上 MT5 用自有样本跑一遍再信。

MQL5 / C++
CBufferFloat *q = QKV_Tensors.At(i * class="num">2);
if(IsStopped() ||
!ConvolutionForward(QKV_Weights.At(i * (optimization == SGD ? class="num">2 : class="num">3)), inputs, q,
 iWindow, iWindowKey * iHeads, None))
   class="kw">return false;
if((i % iLayersToOneKV) == class="num">0)
  {
   class="type">uint i_kv = i / iLayersToOneKV;
   kv = KV_Tensors.At(i_kv * class="num">2);
   CBufferFloat *k = K_Tensors.At(i_kv * class="num">2);
   CBufferFloat *v = V_Tensors.At(i_kv * class="num">2);
   if(IsStopped() ||
!ConvolutionForward(K_Weights.At(i_kv * (optimization == SGD ? class="num">2 : class="num">3)), inputs, k,
   iWindow, iWindowKey * iHeadsKV, None))
     class="kw">return false;
   if(IsStopped() ||
!ConvolutionForward(V_Weights.At(i_kv * (optimization == SGD ? class="num">2 : class="num">3)), inputs, v,
   iWindow, iWindowKey * iHeadsKV, None))
     class="kw">return false;
   if(IsStopped() ||
!Concat(k, v, kv, iWindowKey * iHeadsKV * iVariables, iWindowKey * iHeadsKV * iVariables, iUnits))
     class="kw">return false;
  }
class=class="str">"cmt">//--- Score calculation and Multi-heads attention calculation
CBufferFloat *temp = S_Tensors.At(i * class="num">2);
CBufferFloat *out = AO_Tensors.At(i * class="num">2);
if(IsStopped() || !AttentionOut(q, kv, temp, out))
   class="kw">return false;
class=class="str">"cmt">//--- Attention out calculation
temp = FF_Tensors.At(i * class="num">10);
if(IsStopped() ||
!ConvolutionForward(FF_Weights.At(i * (optimization == SGD ? class="num">6 : class="num">9)), out, temp,
 iWindowKey * iHeads, iWindow, None))
   class="kw">return false;
class=class="str">"cmt">//--- Concat
out = FF_Tensors.At(i * class="num">10 + class="num">1);
if(IsStopped() || !Concat(temp, inputs, out, iWindow, iWindow, iUnits))
   class="kw">return false;
class=class="str">"cmt">//--- Gate
if(IsStopped() ||
!ConvolutionForward(FF_Weights.At(i * (optimization == SGD ? class="num">6 : class="num">9) + class="num">1), out,
 FF_Tensors.At(i * class="num">10 + class="num">2), class="num">2 * iWindow, iWindow, SIGMOID))
   class="kw">return false;
class=class="str">"cmt">//--- Self
if(IsStopped() ||
!ConvolutionForward(FF_Weights.At(i * (optimization == SGD ? class="num">6 : class="num">9) + class="num">2), out,
 FF_Tensors.At(i * class="num">10 + class="num">3), iWindow, iWindow, None))
   class="kw">return false;
class=class="str">"cmt">//--- Out
if(IsStopped() ||
!GateElementMult(FF_Tensors.At(i * class="num">10 + class="num">3), temp, FF_Tensors.At(i * class="num">10 + class="num">2),
 FF_Tensors.At(i * class="num">10 + class="num">4)))
   class="kw">return false;
  }
class=class="str">"cmt">//---
 class="kw">return true;
}

「下一篇才进实盘验证」

这套层次化矢量变换器(HiVT)把多位个体的运动预测拆成两步:先用局部上下文提取,再做全局互动建模。思路本身对多标的联动的概率判断有参考价值,但本文只铺了准备工作的底,没碰 MT5 上的实跑。 作者明确说完整实现超了当前篇幅,启动代码和下一段基于真实历史数据的测试结果都放到系列第二篇。也就是说,你现在能做的只有把局部/全局两阶段的结构在纸上画清楚,别急着搬去 EA。 外汇与贵金属市场高风险,任何多体预测模型都只是概率工具,实盘前必须用历史数据回测确认失效边界。

把这条线请下神坛

这套 LSTM 预测框架落地到 MT5,实际是八个文件分工:Research.mq5 与 ResearchRealORL.mq5 负责采样,Study.mq5 与 StudyEncoder.mq5 管训练,Test.mq5 跑验证,Trajectory.mqh、NeuroNet.mqh、NeuroNet.cl 则分别承载状态结构、网络类库与 OpenCL 核。附加的 MQL5.zip 约 1699.15 KB,解压即可在 MetaEditor 里逐文件读。 别把它们当成黑箱圣物。神经网络在外汇与贵金属上只是概率工具,样本偏差或过度拟合都会让回测曲线失效;实盘前务必用策略测试器跑至少一年 tick 数据,并手动改 Study.mq5 里的窗口参数看泛化变化。 线画完了,接下来该你自己在 MT5 里把 EA 挂上图表,看它采到的样本长什么样。

常见问题

常见坑是缓冲未做零均值归一,导致训练初期梯度爆炸;建议初始化后用小规模随机数校验缓冲范数再接入主网。
按序列长度×隐维度×2估算KV缓存,门控状态用零张量占位;实盘前用历史切片跑一遍显存峰值最稳妥。
小布可读取你的模型配置,自动标出缓冲未归一或显存预估超阈值的项,并给一键修正建议。
需做残差加和、层归一化再进前馈;跳过归一化会让后续层输入分布漂移,回测稳定性变差。
因它只是向量变换的一种结构,实盘收益依赖特征工程与风控,别神话架构本身,下一篇才进验证。