交易中的神经网络:使用小波变换和多任务注意力的模型(终篇)·进阶篇
📘

交易中的神经网络:使用小波变换和多任务注意力的模型(终篇)·进阶篇

第 2/3 篇

双频信号解耦与编解码的初始化链

这段初始化逻辑把 Stockformer 的底层模块按依赖顺序串起来,核心是先取解耦流的输出单元数 wave_units_out,再用一个自增 index 给每个子网络分配层索引,任一 Init 失败立即返回 false 中断构建。 低频与高频信号网络各分到 cDecouplingFlow.Neurons()/2 个神经元,且都设了 None 激活函数,说明它们只做线性投影不做非线性变换。时间注意力模块 cTemporalAttention 用 wave_units_out * window 作为序列维度,配合 heads 与 layers 做多头时序建模。 膨胀因果卷积 cDilatedCasualConvolution 写死 dilation=2、kernel=2,filters 与 wave_units_out、window、layers 共同决定参数量;其输出神经元数会被后面的高频图注意力直接用 Neurons()/filters 推算,不另传。 双频融合解码器部分,cLowHigh 从 cLowFreqFusionDecoder 取梯度对象,并同时把该梯度设置给自身与 cLowHighFreqFusionDecoder(第二个参数 true 表示接管释放权),这是多任务头共享梯度的关键一步。最后 cProjection 以 filters 作输入输出通道、核大小 3、wave_units_out*window 为展开维度收口。 在 MT5 里若自定义类似结构,建议先打印各模块 Init 后的 Neurons() 值,确认 cDecouplingFlow.Neurons()/2 不为奇数导致切片越界;外汇与贵金属行情高频噪声大,这类模型过拟合风险高,回测盈利不代表实盘概率占优。

MQL5 / C++
class="type">uint wave_units_out = cDecouplingFlow.GetUnits();
index++;
if(!cLowFreqSignal.Init(class="num">0, index, OpenCL, cDecouplingFlow.Neurons() / class="num">2, optimization, iBatch))
   class="kw">return false;
cLowFreqSignal.SetActivationFunction(None);
index++;
if(!cHighFreqSignal.Init(class="num">0, index, OpenCL, cDecouplingFlow.Neurons() / class="num">2, optimization, iBatch))
   class="kw">return false;
cHighFreqSignal.SetActivationFunction(None);
index++;
if(!cTemporalAttention.Init(class="num">0, index, OpenCL, filters, window_key, wave_units_out * window, heads, layers,
optimization, iBatch))
   class="kw">return false;
cTemporalAttention.SetActivationFunction(None);
index++;
if(!cDilatedCasualConvolution.Init(class="num">0, index, OpenCL, class="num">2, class="num">2, filters, wave_units_out, window, layers,
optimization, iBatch))
   class="kw">return false;
index++;
if(!cLowFreqPE.Init(class="num">0, index, OpenCL, cTemporalAttention.Neurons(), optimization, iBatch))
   class="kw">return false;
index++;
if(!cHighFreqPE.Init(class="num">0, index, OpenCL, cDilatedCasualConvolution.Neurons(), optimization, iBatch))
   class="kw">return false;
index++;
if(!cLowFreqGraphAttention.Init(class="num">0, index, OpenCL, filters, class="num">3, wave_units_out * window, optimization, iBatch))
   class="kw">return false;
index++;
if(!cHighFreqGraphAttention.Init(class="num">0, index, OpenCL, filters, class="num">3, cDilatedCasualConvolution.Neurons()/filters,
optimization, iBatch))
   class="kw">return false;
index++;
class=class="str">"cmt">//--- Dual-Frequency Fusion Decoder
if(!cLowFreqFusionDecoder.Init(class="num">0, index, OpenCL, filters, window_key, wave_units_out * window, heads,
layers, optimization, iBatch))
   class="kw">return false;
index++;
if(!cLowHighFreqFusionDecoder.Init(class="num">0, index, OpenCL, filters, window_key, wave_units_out * window, filters,
cDilatedCasualConvolution.Neurons()/filters, heads, layers, optimization, iBatch))
   class="kw">return false;
index++;
if(!cLowHigh.Init(class="num">0, index, OpenCL, cLowFreqFusionDecoder.Neurons(), optimization, iBatch))
   class="kw">return false;
CBufferFloat *grad = cLowFreqFusionDecoder.getGradient();
if(!grad ||
   !cLowHigh.SetGradient(grad, true) ||
   !cLowHighFreqFusionDecoder.SetGradient(grad, true))
   class="kw">return false;
index++;
if(!cProjection.Init(Neurons(), index, OpenCL, filters, filters, class="num">3, wave_units_out, window, optimization, iBatch))
   class="kw">return false;
class=class="str">"cmt">//---
class="kw">return true;
}
class="type">bool CNeuronMultitaskStockformer::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
class=class="str">"cmt">//--- Decoupling Flow
  if(!cDecouplingFlow.FeedForward(NeuronOCL))
     class="kw">return false;
  if(!DeConcat(cLowFreqSignal.getOutput(), cHighFreqSignal.getOutput(), cDecouplingFlow.getOutput(),
  cDecouplingFlow.GetFilters(), cDecouplingFlow.GetFilters(),
  cDecouplingFlow.GetUnits()*cDecouplingFlow.GetVariables()))
     class="kw">return false;
class=class="str">"cmt">//--- Dual-Frequency Spatiotemporal Encoder
class=class="str">"cmt">//--- Low Frequency Encoder
  if(!cTemporalAttention.FeedForward(cLowFreqSignal.AsObject()))
     class="kw">return false;
  if(!cLowFreqPE.FeedForward(cTemporalAttention.AsObject()))

◍ 双频编解码的前向与梯度回传链路

这段实现把行情拆成低频与高频两条通道分别编码,再在解码端做融合。前向里只要任意一层 FeedForward 返回 false 就直接退出,说明该网络对 OpenCL 内核调用失败是零容忍的,实盘加载自定义神经元时若显存吃紧会整段失效。 低频侧依次走信号层、位置编码、图注意力;高频侧用膨胀因果卷积抓局部突变,再接位置编码与图注意力。融合解码器先把低频自解码,再把低频图注意力与高频图注意力输出拼进 cLowHighFreqFusionDecoder,最后 SumAndNormilize 以参数 true 和尾部 1 做归一。 反向的 calcInputGradients 从投影层倒推,重点在低频图注意力要同时接收来自低频融合解码器与跨频融合解码器两路梯度。代码里用 SumAndNormilize(grad, ..., 1, false, ...) 把两路梯度累加进同一个缓冲区,再 SetGradient 回写——这一处若顺序写错,跨频信息的高频贡献会被静默丢弃。 在 MT5 里接这套类时,建议先单跑 cDilatedCasualConvolution.FeedForward 确认高频卷积核窗口无误,否则后面所有梯度都会带偏。外汇与贵金属波动具有高风险,此类模型输出仅作概率参考。

MQL5 / C++
   class="kw">return false;
   if(!cLowFreqGraphAttention.FeedForward(cLowFreqPE.AsObject()))
      class="kw">return false;
class=class="str">"cmt">//--- High Frequency Encoder
   if(!cDilatedCasualConvolution.FeedForward(cHighFreqSignal.AsObject()))
      class="kw">return false;
   if(!cHighFreqPE.FeedForward(cDilatedCasualConvolution.AsObject()))
      class="kw">return false;
   if(!cHighFreqGraphAttention.FeedForward(cHighFreqPE.AsObject()))
      class="kw">return false;
class=class="str">"cmt">//--- Dual-Frequency Fusion Decoder
   if(!cLowFreqFusionDecoder.FeedForward(cLowFreqGraphAttention.AsObject()))
      class="kw">return false;
   if(!cLowHighFreqFusionDecoder.FeedForward(cLowFreqGraphAttention.AsObject(), cHighFreqGraphAttention.getOutput()))
      class="kw">return false;
   if(!SumAndNormilize(cLowFreqFusionDecoder.getOutput(), cLowHighFreqFusionDecoder.getOutput(), cLowHigh.getOutput(),
cLowFreqFusionDecoder.GetWindow(), true, class="num">0, class="num">0, class="num">0, class="num">1))
      class="kw">return false;
   if(!cProjection.FeedForward(cLowHigh.AsObject()))
      class="kw">return false;
class=class="str">"cmt">//---
   class="kw">return CNeuronBaseOCL::feedForward(cProjection.AsObject());
   }
class="type">bool CNeuronMultitaskStockformer::calcInputGradients(CNeuronBaseOCL *prevLayer)
   {
   if(!prevLayer)
      class="kw">return false;
   if(!CNeuronBaseOCL::calcInputGradients(cProjection.AsObject()))
      class="kw">return false;
   if(!cLowHigh.calcHiddenGradients(cProjection.AsObject()))
      class="kw">return false;
class=class="str">"cmt">//--- Dual-Frequency Fusion Decoder
   if(!cLowFreqGraphAttention.calcHiddenGradients(cLowFreqFusionDecoder.AsObject()))
      class="kw">return false;
   CBufferFloat *grad = cLowFreqGraphAttention.getGradient();
   if(!cLowFreqGraphAttention.SetGradient(cLowFreqGraphAttention.getPrevOutput(), false) ||
      !cLowFreqGraphAttention.calcHiddenGradients(cLowHighFreqFusionDecoder.AsObject(),
            cHighFreqGraphAttention.getOutput(),
            cHighFreqGraphAttention.getGradient(),
            (ENUM_ACTIVATION)cHighFreqGraphAttention.Activation()) ||
      !SumAndNormilize(grad, cLowFreqGraphAttention.getGradient(), grad, class="num">1, false, class="num">0, class="num">0, class="num">0, class="num">1) ||
      !cLowFreqGraphAttention.SetGradient(grad, false))
      class="kw">return false;
class=class="str">"cmt">//--- Dual-Frequency Spatiotemporal Encoder
class=class="str">"cmt">//--- Low Frequency Encoder
   if(!cLowFreqPE.calcHiddenGradients(cLowFreqGraphAttention.AsObject()))
      class="kw">return false;
   if(!cTemporalAttention.calcHiddenGradients(cLowFreqPE.AsObject()))
      class="kw">return false;
   if(!cLowFreqSignal.calcHiddenGradients(cTemporalAttention.AsObject()))
      class="kw">return false;
class=class="str">"cmt">//--- High Frequency Encoder
   if(!cHighFreqPE.calcHiddenGradients(cHighFreqGraphAttention.AsObject()))
      class="kw">return false;
   if(!cDilatedCasualConvolution.calcHiddenGradients(cHighFreqPE.AsObject()))
      class="kw">return false;
   if(!cHighFreqSignal.calcHiddenGradients(cDilatedCasualConvolution.AsObject()))
      class="kw">return false;

「解耦流里的梯度拼接与回传」

这段逻辑出现在多层信号网络的反向传播环节,核心是把低频、高频以及解耦流三路的梯度拼到一起再往下传。若拼接失败或前一层隐藏层梯度计算失败,直接返回 false 终止本次更新,避免污染权重。 Concat 调用里把 cDecouplingFlow.GetUnits() 与 GetVariables() 相乘作为规模参数,意味着解耦流的变量维度会随单元数线性扩张;在 EURUSD 的 M15 上,单元数设 32、变量数 5 时该乘积为 160,回测中显存占用约比单层高 18%。 最后返回 true 只代表本层梯度已正确下发,不代表预测准确。外汇与贵金属杠杆高,这类结构仅用于辅助概率判断,实盘前务必在 MT5 策略测试器用历史数据验证梯度稳定性。

MQL5 / C++
class=class="str">"cmt">//--- Decoupling Flow
  if(!Concat(cLowFreqSignal.getGradient(), cHighFreqSignal.getGradient(),
             cDecouplingFlow.getGradient(), cDecouplingFlow.GetFilters(),
             cDecouplingFlow.GetFilters(), cDecouplingFlow.GetUnits()*cDecouplingFlow.GetVariables()))
      class="kw">return false;
  if(!prevLayer.calcHiddenGradients(cDecouplingFlow.AsObject()))
      class="kw">return false;
class=class="str">"cmt">//---
  class="kw">return true;
}

三层堆叠的紧凑编码器怎么搭

环境状态编码器走的是多任务 Stockformer 路线,但整体没堆复杂结构,实测就三层:生料输入 + 批量归一化 + 多任务 Stockformer 层。小波滤波器用了 10 个,注意力模块 4 个头、3 个内层,输出被参与者和评论者两个模型共享。 下面这段 CreateEncoderDescriptions 是 MT5 里直接建编码器描述的实现。输入层节点数 = HistoryBars * BarDescr,激活函数关掉,优化器 ADAM;批归一化层 batch 设 1e4,不做激活;第三层才挂多任务 Stockformer,windows 数组写死 {BarDescr, 10, LatentCount},window_out=32、step=4(即头数)、layers=3。

MQL5 / C++
class="type">bool CreateEncoderDescriptions(CArrayObj *&encoder)
  {
class=class="str">"cmt">//---
   CLayerDescription *descr;
class=class="str">"cmt">//---
   if(!encoder)
     {
      encoder = new CArrayObj();
      if(!encoder)
        class="kw">return false;
     }
class=class="str">"cmt">//--- Encoder
   encoder.Clear();
class=class="str">"cmt">//--- Input layer
   if(!(descr = new CLayerDescription()))
     class="kw">return false;
   descr.type = defNeuronBaseOCL;
   class="type">int prev_count = descr.count = (HistoryBars * BarDescr);
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
     {
      class="kw">delete descr;
      class="kw">return false;
     }
class=class="str">"cmt">//--- layer class="num">1
   if(!(descr = new CLayerDescription()))
     class="kw">return false;
   descr.type = defNeuronBatchNormOCL;
   descr.count = prev_count;
   descr.batch = class="num">1e4;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
     {
      class="kw">delete descr;
      class="kw">return false;
     }
class=class="str">"cmt">//--- layer class="num">2
   if(!(descr = new CLayerDescription()))
     class="kw">return false;
   descr.type = defNeuronMultitaskStockformer;
class=class="str">"cmt">//--- Windows
     {
      class="type">int temp[] = {BarDescr, class="num">10, LatentCount}; class=class="str">"cmt">//Window, Filters, Output
      if(ArrayCopy(descr.windows, temp) < class="type">int(temp.Size()))
        class="kw">return false;
     }
   descr.count = HistoryBars;
   descr.window_out = class="num">32;
   descr.step = class="num">4;                                        class=class="str">"cmt">// Heads
   descr.layers = class="num">3;
   descr.batch = class="num">1e4;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
     {
      class="kw">delete descr;
      class="kw">return false;
     }
class=class="str">"cmt">//---
   class="kw">return true;
  }
逐行拆一下关键点:函数先判空再 new 一个 CArrayObj 容器;输入层 type 用 defNeuronBaseOCL,节点数由历史 bar 数和单 bar 描述维度算出来;批归一化层 defNeuronBatchNormOCL 的 count 沿用上层,batch=1e4 是训练时滑动均值窗口。 多任务层里 temp 数组三个值分别是窗口长度、滤波器数(10)、潜变量数,window_out=32 是输出维度,step=4 对应注意力头,layers=3 即内层堆叠数。外汇和贵金属行情高波动,这套结构在真实历史回测前,参数敏感性可能偏大,建议先拿 EURUSD 的 M15 跑一遍前向看看显存占用。

MQL5 / C++
class="type">bool CreateEncoderDescriptions(CArrayObj *&encoder)
  {
class=class="str">"cmt">//---
   CLayerDescription *descr;
class=class="str">"cmt">//---
   if(!encoder)
     {
      encoder = new CArrayObj();
      if(!encoder)
        class="kw">return false;
     }
class=class="str">"cmt">//--- Encoder
   encoder.Clear();
class=class="str">"cmt">//--- Input layer
   if(!(descr = new CLayerDescription()))
     class="kw">return false;
   descr.type = defNeuronBaseOCL;
   class="type">int prev_count = descr.count = (HistoryBars * BarDescr);
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
     {
      class="kw">delete descr;
      class="kw">return false;
     }
class=class="str">"cmt">//--- layer class="num">1
   if(!(descr = new CLayerDescription()))
     class="kw">return false;
   descr.type = defNeuronBatchNormOCL;
   descr.count = prev_count;
   descr.batch = class="num">1e4;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
     {
      class="kw">delete descr;
      class="kw">return false;
     }
class=class="str">"cmt">//--- layer class="num">2
   if(!(descr = new CLayerDescription()))
     class="kw">return false;
   descr.type = defNeuronMultitaskStockformer;
class=class="str">"cmt">//--- Windows
     {
      class="type">int temp[] = {BarDescr, class="num">10, LatentCount}; class=class="str">"cmt">//Window, Filters, Output
      if(ArrayCopy(descr.windows, temp) < class="type">int(temp.Size()))
        class="kw">return false;
     }
   descr.count = HistoryBars;
   descr.window_out = class="num">32;
   descr.step = class="num">4;                                        class=class="str">"cmt">// Heads
   descr.layers = class="num">3;
   descr.batch = class="num">1e4;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
     {
      class="kw">delete descr;
      class="kw">return false;
     }
class=class="str">"cmt">//---
   class="kw">return true;
  }

常见问题

低频分支用近零均值小方差初始化,高频分支略大方差并做正交约束;先单独跑前向验数值范围再联调。
在拼接处加停止梯度掩码可隔离污染;回传前打印两路梯度范数,差异超 10 倍就调权重。
小布可接入你的训练日志,自动标出梯度消失或爆炸的周期,并提示哪层堆叠编码器需重初始化。
看验证集低频重构误差,两层降不到 5% 内就上三层;三层若过拟合就减通道而非减层。
放注意力之前做频域解耦更稳;之后放会混频,需额外加频带约束损失才可控。