交易中的神经网络:层次化向量变换器(终章)·进阶篇
🧠

交易中的神经网络:层次化向量变换器(终章)·进阶篇

(2/3)· 局部特征、全局互动、解码预测三段式已拆开讲过,本篇只解决「怎么把它们焊成一个能跑前向的复杂网络」

含代码示例偏理论 第 2/3 篇
很多人把各神经网络模块写完就以为模型能用,结果前向传播里张量维度对不上、静态对象没初始化就调用,训练直接崩。HiVT 这种多阶段结构若不在汇编期统一定义局部与全局表示的流转,后面调参全是瞎子摸象。

◍ Transformer 各层在 MT5 里的初始化串接

下面这段初始化链把行情特征提取到多轨迹预测的网络骨架一次性搭起来,任何一个子模块 Init 返回 false 都会直接中断,实盘加载 EA 时若卡在 OnInit 多半是这里某层参数越界。 cDataTAD 用 2*iVariables*iVariables*iHistory 的缓冲规模做数据张量预处理,cEmbeddingTAD 则把维度压到 2*iVariables 并吃进 window_key 长度的历史窗;两者设备号都填 0 表示走同一 OpenCL 上下文。 注意力部分分了三支:cAAEncoder 处理变量间自注意力(heads 头数、缩放因子 (heads+1)/2),cTemporalEncoder 走时间轴注意力,cALEncoder 额外引入 8 维线嵌入做局部对齐,三层均依赖 iHistory 与 iVariables 的乘积关系,改窗口长度必须同步核对显存申请量。 解码侧 cDecoder[0~2] 分别挂 SIGMOID、LReLU、TANH 三种激活,其中 cDecoder[2] 输出维度直连 iForecast*iNumTraj,意味着单根 K 线可能吐出多条候选轨迹——外汇与贵金属波动剧烈,这种多轨迹仅作概率分布参考,杠杆风险极高。

MQL5 / C++
if(!cDataTAD.Init(class="num">0, class="num">0, OpenCL, class="num">2 * iVariables * iVariables * iHistory, optimization, iBatch))
   class="kw">return false;
if(!cEmbeddingTAD.Init(class="num">0, class="num">1, OpenCL, class="num">2 * iVariables, class="num">2 * iVariables, window_key, iVariables * iHistory,
class="num">1, optimization, iBatch))
   class="kw">return false;
if(!cTransposeATD.Init(class="num">0, class="num">2, OpenCL, iHistory, iVariables, window_key, optimization, iBatch))
   class="kw">return false;
if(!cAAEncoder.Init(class="num">0, class="num">3, OpenCL, window_key, window_key, heads, (heads + class="num">1) / class="num">2, iVariables, class="num">2, class="num">1,
iHistory, optimization, iBatch))
   class="kw">return false;
if(!cTransposeTAD.Init(class="num">0, class="num">4, OpenCL, iVariables, iHistory, window_key, optimization, iBatch))
   class="kw">return false;
if(!cPosEmbeddingTAD.Init(class="num">0, class="num">5, OpenCL, iVariables * iHistory * window_key, optimization, iBatch))
   class="kw">return false;
if(!cTemporalEncoder.Init(class="num">0, class="num">6, OpenCL, window_key, window_key, heads, (heads + class="num">1) / class="num">2, iHistory, class="num">2, class="num">1,
iVariables, optimization, iBatch))
   class="kw">return false;
if(!cLineEmbeddibg.Init(class="num">0, class="num">7, OpenCL, class="num">3, class="num">1, class="num">8, iHistory - class="num">1, iVariables, optimization, iBatch))
   class="kw">return false;
if(!cPosLineEmbeddingTAD.Init(class="num">0, class="num">8, OpenCL, cLineEmbeddibg.Neurons(), optimization, iBatch))
   class="kw">return false;
if(!cALEncoder.Init(class="num">0, class="num">9, OpenCL, window_key, window_key, heads, class="num">8, (heads + class="num">1) / class="num">2,
iHistory, iHistory - class="num">1, class="num">2, class="num">1, iVariables, iVariables, optimization, iBatch))
   class="kw">return false;
if(!cGlobalEncoder.Init(class="num">0, class="num">10, OpenCL, window_key*iVariables, window_key*iVariables, heads, (heads+class="num">1)/class="num">2,
iHistory, class="num">4, class="num">2, optimization, iBatch))
   class="kw">return false;
if(!cTransposeADT.Init(class="num">0, class="num">11, OpenCL, iHistory, window_key * iVariables, optimization, iBatch))
   class="kw">return false;
if(!cDecoder[class="num">0].Init(class="num">0, class="num">12, OpenCL, iHistory, iHistory, iForecast, window_key * iVariables,
optimization, iBatch))
   class="kw">return false;
cDecoder[class="num">0].SetActivationFunction(SIGMOID);
if(!cDecoder[class="num">1].Init(class="num">0, class="num">13, OpenCL, iForecast * window_key, iForecast * window_key, iForecast * window_key,
iVariables, optimization, iBatch))
   class="kw">return false;
cDecoder[class="num">1].SetActivationFunction(LReLU);
if(!cDecoder[class="num">2].Init(class="num">0, class="num">14, OpenCL, iForecast * window_key, iForecast * window_key, iForecast * iNumTraj,
iVariables, optimization, iBatch))
   class="kw">return false;
cDecoder[class="num">2].SetActivationFunction(TANH);

HiVT 网络在 OpenCL 上的前向装配

这段 CNeuronHiVTOCL 的初始化把多个子模块挂到不同 GPU 内核编号上:投影内核用 15,概率内核用 16,预测内核用 17,转置内核用 18。每个 Init 失败都会直接 return false,意味着任一层显存或参数异常都会让整网罢工,排查时先看是哪个编号的内核报错。 cProbability.SetHeads(iVariables) 注释写的是 Agent * Traj,实际是把变量数当作多头数,后续 MatMul 的维度顺序是 (iForecast, iNumTraj, 1, iVariables),少配一个就会在矩阵乘时越界。 feedForward 里链路很长:从 cEmbeddingTAD 一路过 cAAEncoder、cTemporalEncoder 到 cGlobalEncoder,再经三层 cDecoder 和 cProbProj 出概率,最后用 MatMul 把解码输出和概率乘出 cForecast。中间插了 IsStopped() 判断,EA 在 MT5 里点停止会立刻断流,避免显卡空转。 想验证这条链路,把 iForecast 设小(比如 10)、iNumTraj 设 8,在 MT5 策略测试器开 OpenCL 日志,看内核 15–18 是否依次分配成功;外汇与贵金属杠杆高,此类 GPU 推理延迟可能在滑点敏感行情放大亏损概率。

MQL5 / C++
  if(!cProbProj.Init(class="num">0, class="num">15, OpenCL, iForecast * iNumTraj, iForecast * iNumTraj, iNumTraj, iVariables,
optimization, iBatch))
        class="kw">return false;
  if(!cProbability.Init(class="num">0, class="num">16, OpenCL, iForecast * iNumTraj * iVariables, optimization, iBatch))
        class="kw">return false;
  cProbability.SetHeads(iVariables); class=class="str">"cmt">// Agent * Traj
  if(!cForecast.Init(class="num">0, class="num">17, OpenCL, iForecast * iVariables, optimization, iBatch))
        class="kw">return false;
  if(!cTransposeTA.Init(class="num">0, class="num">18, OpenCL, iVariables, iForecast, optimization, iBatch))
        class="kw">return false;
  SetOutput(cTransposeTA.getOutput(),true);
  SetGradient(cTransposeTA.getGradient(),true);
class=class="str">"cmt">//---
  class="kw">return true;
  }
class="type">bool CNeuronHiVTOCL::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
  if(!Prepare(NeuronOCL))
        class="kw">return false;
  if(!cEmbeddingTAD.FeedForward(cDataTAD.AsObject()))
        class="kw">return false;
  if(!cTransposeATD.FeedForward(cEmbeddingTAD.AsObject()))
        class="kw">return false;
  if(!cAAEncoder.FeedForward(cTransposeATD.AsObject()))
        class="kw">return false;
  if(!cTransposeTAD.FeedForward(cAAEncoder.AsObject()))
        class="kw">return false;
  if(!cPosEmbeddingTAD.FeedForward(cTransposeTAD.AsObject()))
        class="kw">return false;
  if(!cTemporalEncoder.FeedForward(cPosEmbeddingTAD.AsObject()))
        class="kw">return false;
  if(!cLineEmbeddibg.FeedForward(NeuronOCL))
        class="kw">return false;
  if(!cPosLineEmbeddingTAD.FeedForward(cLineEmbeddibg.AsObject()))
        class="kw">return false;
  if(!cALEncoder.FeedForward(cTemporalEncoder.AsObject(), cPosLineEmbeddingTAD.getOutput()))
        class="kw">return false;
  if(!cGlobalEncoder.FeedForward(cALEncoder.AsObject()))
        class="kw">return false;
  if(!cTransposeADT.FeedForward(cGlobalEncoder.AsObject()))
        class="kw">return false;
  if(!cDecoder[class="num">0].FeedForward(cTransposeADT.AsObject()))
        class="kw">return false;
  if(!cDecoder[class="num">1].FeedForward(cDecoder[class="num">0].AsObject()))
        class="kw">return false;
  if(!cDecoder[class="num">2].FeedForward(cDecoder[class="num">1].AsObject()))
        class="kw">return false;
  if(!cProbProj.FeedForward(cDecoder[class="num">2].AsObject()))
        class="kw">return false;
  if(!cProbability.FeedForward(cProbProj.AsObject()))
        class="kw">return false;
  if(IsStopped() ||
      !MatMul(cDecoder[class="num">2].getOutput(), cProbability.getOutput(), cForecast.getOutput(), iForecast,
iNumTraj, class="num">1, iVariables))
        class="kw">return false;
  if(!cTransposeTA.FeedForward(cForecast.AsObject()))
   class="kw">return false;
class=class="str">"cmt">//---

「反向传播里的梯度回流顺序」

这段 CNeuronHiVTOCL::calcInputGradients 做的是 Transformer 类模型在 OpenCL 端的输入梯度回传,从输出层一路反推到原始行情数据嵌入层。 函数开头先判空 NeuronOCL,随后调用 cForecast.calcHiddenGradients 拿到隐藏层梯度,再用 MatMulGrad 处理解码器第二层(索引 2)输出与梯度的矩阵乘梯度,参数里 iForecast、iNumTraj、1、iVariables 直接决定了梯度张量的维度排布。 之后是链式回传:cProbProj → cDecoder[1] → cDecoder[0] → cTransposeADT → cGlobalEncoder → cALEncoder → cTemporalEncoder,其中 cTemporalEncoder 还额外吃了位置行嵌入 cPosLineEmbeddingTAD 的输出与梯度,并传入其激活枚举。 末尾继续 cLineEmbeddibg → NeuronOCL,以及另一条 cPosEmbeddingTAD → cTransposeTAD → cAAEncoder → cTransposeATD → cEmbeddingTAD → cDataTAD 的路径,任一步 IsStopped 或计算失败立即返回 false。 在 MT5 里跑这类模型时,若某次训练中途报梯度为空,优先查 cDecoder[2] 的 MatMulGrad 维度参数——iVariables 与实际特征数不一致是常见坑,外汇与贵金属数据噪声大,梯度异常往往意味着输入标准化没到位,高风险下别盲目加层数。

MQL5 / C++
class="type">bool CNeuronHiVTOCL::calcInputGradients(CNeuronBaseOCL *NeuronOCL)
  {
  if(!NeuronOCL)
     class="kw">return false;
  if(!cForecast.calcHiddenGradients(cTransposeTA.AsObject()))
     class="kw">return false;
  if(IsStopped() ||
     !MatMulGrad(cDecoder[class="num">2].getOutput(), cDecoder[class="num">2].getGradient(), cProbability.getOutput(),
cProbability.getGradient(), cForecast.getGradient(), iForecast, iNumTraj, class="num">1, iVariables))
     class="kw">return false;
  if(!cProbProj.calcHiddenGradients(cProbability.AsObject()))
     class="kw">return false;
  if(!cDecoder[class="num">1].calcHiddenGradients(cDecoder[class="num">2].AsObject()))
     class="kw">return false;
  if(!cDecoder[class="num">0].calcHiddenGradients(cDecoder[class="num">1].AsObject()))
     class="kw">return false;
  if(!cTransposeADT.calcHiddenGradients(cDecoder[class="num">0].AsObject()))
     class="kw">return false;
  if(!cGlobalEncoder.calcHiddenGradients(cTransposeADT.AsObject()))
     class="kw">return false;
  if(!cALEncoder.calcHiddenGradients(cGlobalEncoder.AsObject()))
     class="kw">return false;
  if(!cTemporalEncoder.calcHiddenGradients(cALEncoder.AsObject(), cPosLineEmbeddingTAD.getOutput(),
cPosLineEmbeddingTAD.getGradient(),
(ENUM_ACTIVATION)cPosLineEmbeddingTAD.Activation()))
     class="kw">return false;
  if(!cLineEmbeddibg.calcHiddenGradients(cPosLineEmbeddingTAD.AsObject()))
     class="kw">return false;
  if(!NeuronOCL.calcHiddenGradients(cLineEmbeddibg.AsObject()))
     class="kw">return false;
  if(!cPosEmbeddingTAD.calcHiddenGradients(cTemporalEncoder.AsObject()))
     class="kw">return false;
  if(!cTransposeTAD.calcHiddenGradients(cPosEmbeddingTAD.AsObject()))
     class="kw">return false;
  if(!cAAEncoder.calcHiddenGradients(cTransposeTAD.AsObject()))
     class="kw">return false;
  if(!cTransposeATD.calcHiddenGradients(cAAEncoder.AsObject()))
     class="kw">return false;
  if(!cEmbeddingTAD.calcHiddenGradients(cTransposeATD.AsObject()))
     class="kw">return false;
  if(!cDataTAD.calcHiddenGradients(cEmbeddingTAD.AsObject()))
     class="kw">return false;
class=class="str">"cmt">//---
   class="kw">return true;
  }

◍ 把 HiVT 模块接进编码器

CNeuronHiVTOCL 类已经写完了,它落地了我们自己解释的 HiVT 思路。下一步是把它塞进环境状态编码器,具体架构在 CreateEncoderDescriptions 里用动态数组逐层描述。 方法先判指针是否有效,空了就 new 一个 CArrayObj,随后按层往里 push 描述对象。基层用全连接把原始多模态序列吃进来,节点数等于 HistoryBars * BarDescr,不做激活,优化器走 ADAM。 原始数据直接进模型前先过批量归一化(batch=1e4),把不同量纲压到可比较空间。紧接着就送进 HiVT 模块,参数基本沿用旧工作,只多了一个轨迹变体数,这里写死 6,意味着输出层会给出 6 条可能的未来轨迹。 HiVT 吐出来的预测值仍是归一化尺度,必须接一个反归一化层(defNeuronRevInDenormOCL)把分布统计加回去,才能和行情原始数值对齐。最后叠一层频域校正(defNeuronFreDFOCL),窗口=BarDescr、概率阈值 0.7f,把时序里的噪声分量滤一遍。 参与者与评论者网络、训练流程都没动,本文不重复。全套源码在附件里,想跑通就照着把 NumTraj 从 6 改成别的数试试推理差异——外汇和贵金属波动大,多轨迹预测也只是概率分布,实盘前务必在 MT5 用历史数据回测。

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 = defNeuronHiVTOCL;
     {
       class="type">int temp[] = {BarDescr, NForecast, class="num">6};                    class=class="str">"cmt">// {Variables, Forecast, NumTraj}
       ArrayCopy(descr.windows, temp);
     }
   descr.window_out = EmbeddingSize;                            class=class="str">"cmt">// Inside Dimension
   descr.count = HistoryBars;                                  class=class="str">"cmt">// Units
   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">3
   if(!(descr = new CLayerDescription()))
      class="kw">return false;
   descr.type = defNeuronRevInDenormOCL;
   descr.count = BarDescr * NForecast;
   descr.activation = None;
   descr.optimization = ADAM;
   descr.layers = class="num">1;
   if(!encoder.Add(descr))
     {
       class="kw">delete descr;
       class="kw">return false;
     }
class=class="str">"cmt">//--- layer class="num">4
   if(!(descr = new CLayerDescription()))
      class="kw">return false;
   descr.type = defNeuronFreDFOCL;
   descr.window = BarDescr;
   descr.count =  NForecast;
   descr.step = class="type">int(true);
   descr.probability = class="num">0.7f;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
     {
让小布替你跑这套结构检查
把重复劳动交给小布盯盘,你专注决策:这类多模块网络的维度校验和前向流程诊断,小布盯盘的 AIGC 已内置,打开对应品种页即可看到结构健康度提示。

常见问题

声明为静态后构造函数和析构函数可留空,所有嵌套对象和变量统一在 Init 方法里初始化,避免多次实例化带来的资源重复和状态不一致。
因为初始状态矢量化过程操作的是动态值,需要当前和前一个时间步两个引用才能计算数值变化,少于 2 步无法求得差分特征。
能把场景拆成多个以被分析元素为核心的子空间,降低全局计算量,同时保留该个体周边其他对象的相对位置变化描述,利于后续全局互动建模。
目前小布盯盘内置的是结构健康与流程诊断的 AIGC 视图,模型权重需你自行训练;它可辅助你发现维度断裂或模块未连通等问题,外汇贵金属属高风险,信号仅作概率参考。
组合后的局部与全局表示作为解码器输入,在单次前向通验中预测所有个体未来轨迹,具体融合方式在模型架构小节展开。