神经网络变得简单(第 76 部分):配合多未来变换器探索不同的交互形态·进阶篇
🧠

神经网络变得简单(第 76 部分):配合多未来变换器探索不同的交互形态·进阶篇

(2/3)· 当多名交易个体互动让预测组合爆炸,MFT 如何用一次前馈算出整体场景

偏理论 第 2/3 篇
很多人以为把每个品种独立预测再拼起来就是多资产策略,结果组合数量指数级膨胀且互相打架。MFT 的思路是先估整体场景再分解模态,少走很多弯路。

多头注意力模块的张量落盘顺序

在 MT5 里用 OpenCL 跑 Transformer 类模型,第一步是把各路中间张量挨个塞进显存缓冲。下面这段初始化逻辑覆盖了 scores、mh_out、out 以及 QKV 四组缓冲,任何一步失败直接 return false,保证后续算子不会读到空指针。 scores 缓冲先以 0 初始化长度,再调 BufferCreate(OpenCL) 推到设备端,最后挂进 S_Tensors 动态数组;mh_out 走同样的路径进 AO_Tensors,out 则进 FF_Tensors。这三组对应多头注意力的打分、头输出与前馈输出,缺一个卷积层都跑不起来。 QKV 张量单独处理:先按 num_q 长度建缓冲并加入 QKV_Tensors,再补一个 out 长度的缓冲交给 cTranspose。外汇与贵金属行情的高波动下,这类 GPU 缓冲若初始化不完整,策略回测可能概率性崩在第一次矩阵乘,开 MT5 接好 OpenCL 设备后务必单步跟一遍 Add 调用。

MQL5 / C++
      class="kw">return class="kw">false;
      if(!temp.BufferInit(scores, class="num">0))
         class="kw">return class="kw">false;
      if(!temp.BufferCreate(OpenCL))
         class="kw">return class="kw">false;
      if(!S_Tensors.Add(temp))
         class="kw">return class="kw">false;
      class=class="str">"cmt">//--- Initialize multi-heads attention out
      temp = new CBufferFloat();
      if(CheckPointer(temp) == POINTER_INVALID)
         class="kw">return class="kw">false;
      if(!temp.BufferInit(mh_out, class="num">0))
         class="kw">return class="kw">false;
      if(!temp.BufferCreate(OpenCL))
         class="kw">return class="kw">false;
      if(!AO_Tensors.Add(temp))
         class="kw">return class="kw">false;
      class=class="str">"cmt">//--- Initialize attention out
      temp = new CBufferFloat();
      if(CheckPointer(temp) == POINTER_INVALID)
         class="kw">return class="kw">false;
      if(!temp.BufferInit(out, class="num">0))
         class="kw">return class="kw">false;
      if(!temp.BufferCreate(OpenCL))
         class="kw">return class="kw">false;
      if(!FF_Tensors.Add(temp))
         class="kw">return class="kw">false;
      class=class="str">"cmt">//--- MHCA
      class=class="str">"cmt">//--- Initilize QKV tensor
      temp = new CBufferFloat();
      if(CheckPointer(temp) == POINTER_INVALID)
         class="kw">return class="kw">false;
      if(!temp.BufferInit(num_q, class="num">0))
         class="kw">return class="kw">false;
      if(!temp.BufferCreate(OpenCL))
         class="kw">return class="kw">false;
      if(!QKV_Tensors.Add(temp))
         class="kw">return class="kw">false;
      temp = new CBufferFloat();
      if(CheckPointer(temp) == POINTER_INVALID)
         class="kw">return class="kw">false;
      if(!temp.BufferInit(out, class="num">0))
         class="kw">return class="kw">false;
      if(!temp.BufferCreate(OpenCL))
         class="kw">return class="kw">false;
      if(!cTranspose.Add(temp))

「多头交叉注意力张量的初始化链路」

在把 Transformer 模块搬进 MT5 的 OpenCL 管线时,QKV、score、多头输出和前馈缓冲必须按顺序在设备端建好,否则后续核函数直接报空指针。下面这段初始化逻辑连续创建了 5 类浮点缓冲,并逐一挂到对应的张量数组里。 每一块都先 new CBufferFloat() 拿对象,立刻用 CheckPointerPOINTER_INVALID,失效就 return false 中断——这一步漏掉,后面 BufferInit 会在非法指针上崩。 QKV 缓冲长度为 num_kv,score 用 scores_ca,多头注意力输出用 mh_out,注意力汇总用 out,前馈第一层则扩到 4 * out。其中前馈层维度是注意力输出的 4 倍,这是典型 Transformer FFN 升维比,回测里升维过小会让黄金 15M 的注意力收敛偏慢。 所有缓冲建完都要走 BufferCreate(OpenCL) 把内存推到显卡,再 Add 进托管数组;任一步返回 false 整体初始化即告失败,调用方应检查返回并释放已建缓冲避免显存泄漏。外汇与贵金属杠杆高,这类 GPU 初始化失败在实盘 EA 里可能触发连环重算,增加滑点概率。

MQL5 / C++
      class="kw">return class="kw">false;
      temp = new CBufferFloat();
      if(CheckPointer(temp) == POINTER_INVALID)
         class="kw">return class="kw">false;
      if(!temp.BufferInit(num_kv, class="num">0))
         class="kw">return class="kw">false;
      if(!temp.BufferCreate(OpenCL))
         class="kw">return class="kw">false;
      if(!QKV_Tensors.Add(temp))
         class="kw">return class="kw">false;
      class=class="str">"cmt">//--- Initialize scores
      temp = new CBufferFloat();
      if(CheckPointer(temp) == POINTER_INVALID)
         class="kw">return class="kw">false;
      if(!temp.BufferInit(scores_ca, class="num">0))
         class="kw">return class="kw">false;
      if(!temp.BufferCreate(OpenCL))
         class="kw">return class="kw">false;
      if(!S_Tensors.Add(temp))
         class="kw">return class="kw">false;
      class=class="str">"cmt">//--- Initialize multi-heads cross attention out
      temp = new CBufferFloat();
      if(CheckPointer(temp) == POINTER_INVALID)
         class="kw">return class="kw">false;
      if(!temp.BufferInit(mh_out, class="num">0))
         class="kw">return class="kw">false;
      if(!temp.BufferCreate(OpenCL))
         class="kw">return class="kw">false;
      if(!AO_Tensors.Add(temp))
         class="kw">return class="kw">false;
      class=class="str">"cmt">//--- Initialize attention out
      temp = new CBufferFloat();
      if(CheckPointer(temp) == POINTER_INVALID)
         class="kw">return class="kw">false;
      if(!temp.BufferInit(out, class="num">0))
         class="kw">return class="kw">false;
      if(!temp.BufferCreate(OpenCL))
         class="kw">return class="kw">false;
      if(!FF_Tensors.Add(temp))
         class="kw">return class="kw">false;
      class=class="str">"cmt">//--- Initialize Feed Forward class="num">1
      temp = new CBufferFloat();
      if(CheckPointer(temp) == POINTER_INVALID)
         class="kw">return class="kw">false;
      if(!temp.BufferInit(class="num">4 * out, class="num">0))

◍ Transformer 缓冲区与权重的初始化落点

这段逻辑出现在基于 MT5 OpenCL 的 Transformer 类构建过程里,负责把前馈层(Feed Forward)和注意力层(MHSA / MHCA)的浮点缓冲区逐一建好并塞进容器。只要任意一步 BufferCreate 或 Add 失败,函数直接 return false,整个模型对象处于半初始化状态,后续推理调用会崩。 前馈部分连续建了两个 CBufferFloat:第一个用 BufferInit(in, 0) 预分配输入侧,第二个用 BufferInit(out, 0) 预分配输出侧,两者都走 FF_Tensors.Add 收集。注意第二段的 out 参数来自外部传入,若调用方给的 out 维度不对,这里不会报错,只会在更后面的矩阵乘里越界。 注意力权重初始化有个关键缩放系数:k = 1 / sqrt(iWindow + 1),其中 iWindow 是回顾窗口长度。比如窗口取 59,k 约为 0.129,所有权重写入前都先 (GenerateWeight() - 0.5) * k,把随机值压到约 ±0.0645 区间,避免多头注意力初始梯度爆炸。 QKV_Weights 和 FF_Weights 分别 Reserve 了 qkv_weights 与 w0 长度再循环填值,Reserve 失败也会 return false。MHCA 的 Q 权重段在原文截断处刚 new 完并过了指针校验,还没开始 Reserve,实盘复制时容易漏掉后半段导致容器为空。

MQL5 / C++
      class="kw">return class="kw">false;
      if(!temp.BufferCreate(OpenCL))
         class="kw">return class="kw">false;
      if(!FF_Tensors.Add(temp))
         class="kw">return class="kw">false;
      class=class="str">"cmt">//--- Initialize Feed Forward class="num">2
      temp = new CBufferFloat();
      if(CheckPointer(temp) == POINTER_INVALID)
         class="kw">return class="kw">false;
      if(!temp.BufferInit(out, class="num">0))
         class="kw">return class="kw">false;
      if(!temp.BufferCreate(OpenCL))
         class="kw">return class="kw">false;
      if(!FF_Tensors.Add(temp))
         class="kw">return class="kw">false;
      }
      class=class="str">"cmt">//--- MHSA
      class=class="str">"cmt">//--- Initilize QKV weights
      temp = new CBufferFloat();
      if(CheckPointer(temp) == POINTER_INVALID)
         class="kw">return class="kw">false;
      if(!temp.Reserve(qkv_weights))
         class="kw">return class="kw">false;
      class="type">class="kw">float k = (class="type">class="kw">float)(class="num">1 / sqrt(iWindow + class="num">1));
      for(class="type">uint w = class="num">0; w < qkv_weights; w++)
         {
         if(!temp.Add((GenerateWeight() - class="num">0.5f)* k))
            class="kw">return class="kw">false;
         }
      if(!temp.BufferCreate(OpenCL))
         class="kw">return class="kw">false;
      if(!QKV_Weights.Add(temp))
         class="kw">return class="kw">false;
      class=class="str">"cmt">//--- Initilize Weights0
      temp = new CBufferFloat();
      if(CheckPointer(temp) == POINTER_INVALID)
         class="kw">return class="kw">false;
      if(!temp.Reserve(w0))
         class="kw">return class="kw">false;
      for(class="type">uint w = class="num">0; w < w0; w++)
         {
         if(!temp.Add((GenerateWeight() - class="num">0.5f)* k))
            class="kw">return class="kw">false;
         }
      if(!temp.BufferCreate(OpenCL))
         class="kw">return class="kw">false;
      if(!FF_Weights.Add(temp))
         class="kw">return class="kw">false;
      class=class="str">"cmt">//--- MHCA
      class=class="str">"cmt">//--- Initilize Q weights
      temp = new CBufferFloat();
      if(CheckPointer(temp) == POINTER_INVALID)

注意力层权重初始化的内存与缩放细节

这段逻辑在 MT5 里给 Transformer 类指标分配三组缓冲区:QKV 权重、前馈权重0、以及 FF 权重1。每一组都先 new 一个 CBufferFloat,立刻用 CheckPointer 判空,指针无效直接 return false,避免后面 Reserve 崩在 OpenCL 上下文。 QKV 部分分两块:query/value 权重按 q_weights 预留,循环里填 (GenerateWeight()-0.5f)*k;KV 权重单独按 kv_weights 预留,缩放系数换成 kv = 1/sqrt(iUnits+1),比前一块的 k 更保守,维度越高初始幅度压得越扁。 前馈侧 Weights0 用 w0 长度、FF 权重用 ff_1 长度,填充公式仍是 (GenerateWeight()-0.5f)*k。所有 temp 在 Add 完之后必须调 BufferCreate(OpenCL) 把数据推到显存,再塞进 QKV_Weights 或 FF_Weights 数组;任一步返回 false 就整体初始化失败。 开 MT5 把 iUnits 设成 64 跑一遍,能看到 kv 系数约为 0.124,比常见 k=0.1~0.5 的随机范围更克制,小布盯盘加载这类模型时显存占用倾向更平稳。外汇与贵金属行情高波动,此类 AI 辅助信号仅作概率参考,实盘请自担风险。

MQL5 / C++
      class="kw">return class="kw">false;
      if(!temp.Reserve(q_weights))
         class="kw">return class="kw">false;
      for(class="type">uint w = class="num">0; w < q_weights; w++)
         {
          if(!temp.Add((GenerateWeight() - class="num">0.5f)* k))
             class="kw">return class="kw">false;
         }
      if(!temp.BufferCreate(OpenCL))
         class="kw">return class="kw">false;
      if(!QKV_Weights.Add(temp))
         class="kw">return class="kw">false;
      class=class="str">"cmt">//--- Initilize KV weights
      temp = new CBufferFloat();
      if(CheckPointer(temp) == POINTER_INVALID)
         class="kw">return class="kw">false;
      if(!temp.Reserve(kv_weights))
         class="kw">return class="kw">false;
      class="type">class="kw">float kv = (class="type">class="kw">float)(class="num">1 / sqrt(iUnits + class="num">1));
      for(class="type">uint w = class="num">0; w < kv_weights; w++)
         {
          if(!temp.Add((GenerateWeight() - class="num">0.5f)* kv))
             class="kw">return class="kw">false;
         }
      if(!temp.BufferCreate(OpenCL))
         class="kw">return class="kw">false;
      if(!QKV_Weights.Add(temp))
         class="kw">return class="kw">false;
      class=class="str">"cmt">//--- Initilize Weights0
      temp = new CBufferFloat();
      if(CheckPointer(temp) == POINTER_INVALID)
         class="kw">return class="kw">false;
      if(!temp.Reserve(w0))
         class="kw">return class="kw">false;
      for(class="type">uint w = class="num">0; w < w0; w++)
         {
          if(!temp.Add((GenerateWeight() - class="num">0.5f)* k))
             class="kw">return class="kw">false;
         }
      if(!temp.BufferCreate(OpenCL))
         class="kw">return class="kw">false;
      if(!FF_Weights.Add(temp))
         class="kw">return class="kw">false;
      class=class="str">"cmt">//--- Initilize FF Weights
      temp = new CBufferFloat();
      if(CheckPointer(temp) == POINTER_INVALID)
         class="kw">return class="kw">false;
      if(!temp.Reserve(ff_1))
         class="kw">return class="kw">false;
      for(class="type">uint w = class="num">0; w < ff_1; w++)
         {
          if(!temp.Add((GenerateWeight() - class="num">0.5f)* k))
             class="kw">return class="kw">false;

「前馈与注意力权重的显存落地」

这段初始化逻辑把 Transformer 类模型的权重分两路塞进 OpenCL 缓冲:一路是前馈层(FF),一路是多头自注意力(MHSA)与交叉注意力(MHCA)。任何一步 BufferCreate 或 Add 失败都直接 return false,意味着 GPU 显存不足或指针无效时模型构建会整体中断。 前馈第二块权重做了缩放:k = 1 / sqrt(4 * iWindow + 1),再让每个权重 = (随机值 - 0.5) * k。若 iWindow 取 50,缩放系数约为 1 / sqrt(201) ≈ 0.0705,随机初始化幅度被压到 ±0.035 左右,能缓解早期梯度爆炸。 MHSA 与 MHCA 的循环次数由优化器决定:SGD 只跑 1 次,其他优化器跑 2 次。QKV_Weights 每次装入 qkv_weights 个零值浮点,FF_Weights 再补一块 w0 大小的零缓冲,交叉注意力则装入 q_weights 大小的零缓冲,全部建完 OpenCL 缓冲才允许后续训练。 在 MT5 里跑这套,先确认你显卡的 OpenCL 可用内存大于 qkv_weights + w0 + q_weights + ff_2 的总浮点字节数(每 float 占 4 字节),否则初始化必然失败。

MQL5 / C++
   }
      if(!temp.BufferCreate(OpenCL))
         class="kw">return class="kw">false;
      if(!FF_Weights.Add(temp))
         class="kw">return class="kw">false;
      class=class="str">"cmt">//---
      temp = new CBufferFloat();
      if(CheckPointer(temp) == POINTER_INVALID)
         class="kw">return class="kw">false;
      if(!temp.Reserve(ff_2))
         class="kw">return class="kw">false;
      k = (class="type">class="kw">float)(class="num">1 / sqrt(class="num">4 * iWindow + class="num">1));
      for(class="type">uint w = class="num">0; w < ff_2; w++)
         {
          if(!temp.Add((GenerateWeight() - class="num">0.5f)* k))
             class="kw">return class="kw">false;
         }
      if(!temp.BufferCreate(OpenCL))
         class="kw">return class="kw">false;
      if(!FF_Weights.Add(temp))
         class="kw">return class="kw">false;
      for(class="type">int d = class="num">0; d < (optimization == SGD ? class="num">1 : class="num">2); d++)
         {
          class=class="str">"cmt">//--- MHSA
          temp = new CBufferFloat();
          if(CheckPointer(temp) == POINTER_INVALID)
             class="kw">return class="kw">false;
          if(!temp.BufferInit(qkv_weights, class="num">0))
             class="kw">return class="kw">false;
          if(!temp.BufferCreate(OpenCL))
             class="kw">return class="kw">false;
          if(!QKV_Weights.Add(temp))
             class="kw">return class="kw">false;
          temp = new CBufferFloat();
          if(CheckPointer(temp) == POINTER_INVALID)
             class="kw">return class="kw">false;
          if(!temp.BufferInit(w0, class="num">0))
             class="kw">return class="kw">false;
          if(!temp.BufferCreate(OpenCL))
             class="kw">return class="kw">false;
          if(!FF_Weights.Add(temp))
             class="kw">return class="kw">false;
          class=class="str">"cmt">//--- MHCA
          temp = new CBufferFloat();
          if(CheckPointer(temp) == POINTER_INVALID)
             class="kw">return class="kw">false;
          if(!temp.BufferInit(q_weights, class="num">0))
             class="kw">return class="kw">false;
          if(!temp.BufferCreate(OpenCL))

◍ 权重张量落进 OpenCL 缓冲的收尾

这段承接前面的初始化流程,把注意力层与前馈层的权重逐个 new 成 CBufferFloat,再推进对应的权重容器。任何一步 BufferInit 或 BufferCreate 失败都直接 return false,保证 GPU 侧内存没就绪时不会带着空指针往下跑。 QKV 权重走的是 kv_weights 与 w0 两组初始化,前者进 QKV_Weights、后者进 FF_Weights 的接法容易看错——w0 实际塞进了 FF_Weights 而不是 QKV 容器。前馈部分另有 ff_1、ff_2 两组矩陈,注释里写的 momentus 就是这类补充权重。 最后 Transpose 方法只做了入参空判断就留了注释截断,说明转置内核调用放在别处。你在 MT5 里接这段代码时,重点核对每个 temp 的 BufferCreate(OpenCL) 返回值,外汇和贵金属行情跳空多,GPU 缓冲申请失败会让推理直接哑火,属于高风险环境下的隐性坑。

MQL5 / C++
      class="kw">return class="kw">false;
      if(!QKV_Weights.Add(temp))
         class="kw">return class="kw">false;
      temp = new CBufferFloat();
      if(CheckPointer(temp) == POINTER_INVALID)
         class="kw">return class="kw">false;
      if(!temp.BufferInit(kv_weights, class="num">0))
         class="kw">return class="kw">false;
      if(!temp.BufferCreate(OpenCL))
         class="kw">return class="kw">false;
      if(!QKV_Weights.Add(temp))
         class="kw">return class="kw">false;
      temp = new CBufferFloat();
      if(CheckPointer(temp) == POINTER_INVALID)
         class="kw">return class="kw">false;
      if(!temp.BufferInit(w0, class="num">0))
         class="kw">return class="kw">false;
      if(!temp.BufferCreate(OpenCL))
         class="kw">return class="kw">false;
      if(!FF_Weights.Add(temp))
         class="kw">return class="kw">false;
      class=class="str">"cmt">//--- FF Weights momentus
      temp = new CBufferFloat();
      if(CheckPointer(temp) == POINTER_INVALID)
         class="kw">return class="kw">false;
      if(!temp.BufferInit(ff_1, class="num">0))
         class="kw">return class="kw">false;
      if(!temp.BufferCreate(OpenCL))
         class="kw">return class="kw">false;
      if(!FF_Weights.Add(temp))
         class="kw">return class="kw">false;
      temp = new CBufferFloat();
      if(CheckPointer(temp) == POINTER_INVALID)
         class="kw">return class="kw">false;
      if(!temp.BufferInit(ff_2, class="num">0))
         class="kw">return class="kw">false;
      if(!temp.BufferCreate(OpenCL))
         class="kw">return class="kw">false;
      if(!FF_Weights.Add(temp))
         class="kw">return class="kw">false;
      }
   }
class=class="str">"cmt">//---
   class="kw">return true;
   }
class="type">bool CNeuronMFTOCL::Transpose(CBufferFloat *in, CBufferFloat *out, class="type">int rows, class="type">int cols)
   {
   if(!in || !out)
      class="kw">return class="kw">false;
class=class="str">"cmt">//---
交给小布盯盘看盘口
这些多体互动的诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到个体与上下文的关联强度,你专注决策。

常见问题

意图不确定性来自个体未观测到的目标端点,与上下文强相关;互动不确定性来自个体之间不同互动模式,MFT 分别建模这两部分来降低未来模糊度。
场景演变导致分布高度多峰且维度随个体数上升,直接拟合混合参数易不稳定,MFT 先分解为单模态分量再各自处理更可控。
目前小布内置的是基于同样思路的轻量关联诊断,完整 MFT 训练仍需自行在 MT5 环境部署,但盘口互动强度可视化已可用。
论文架构含动态个体编码器与上下文编码器,前者抽取个体轨迹特征,后者编码场景静态与半静态信息,供并联互动模块使用。
预测头部依据各单模态分布及对应权重,输出整体场景的未来轨迹集合,无需后处理迭代即可单次前馈生成。