交易中的神经网络:将全局信息注入独立通道(InjectTST)·进阶篇
🧠

交易中的神经网络:将全局信息注入独立通道(InjectTST)·进阶篇

(2/3)· 通道独立能降噪却丢了跨市场关联,这篇进阶拆解教你如何隐式混合而不崩盘

案例拆解新手友好 第 2/3 篇
很多交易者直接拿独立通道模型跑多品种联动行情,结果把黄金和美指的隐性共振全滤掉了。以为降噪就是纯隔离,实则丢掉了最有用的跨通道线索。这篇先帮你把误区掰正,再拆 InjectTST 怎么补回来。

「按层批量建 KV 与分数缓冲」

在 Transformer 类模型里,K/V 缓存和注意力分数都要按层落在 GPU 显存。下面这段逻辑每跑满 iLayersToOneKV 层,就一次性 new 出三套 CBufferFloat:一套给 K、一套给 V(均用 num_kv 长度初始化),再一套给拼接后的 KV(长度为 2*num_kv)。 每次 new 完都用 CheckPointer 判 POINTER_INVALID,任一失败直接 return false;随后 BufferInit 填零、BufferCreate 推到 OpenCL 上下文,最后塞进 K_Tensors / V_Tensors / KV_Tensors 动态数组。只要有一环没过,整层初始化就废了。 分数缓冲 S_Tensors 不按层循环,而是在循环外单独建一个长度为 scores 的浮点缓冲,同样走判空、初始化、建显存、入数组四步。你在 MT5 里改 iLayersToOneKV 时,显存峰值会按 num_kv 的整数倍跳变,外汇与贵金属行情推理属高风险,参数乱调可能直接爆显存。

MQL5 / C++
if(i % iLayersToOneKV == class="num">0)
  {
    temp = new CBufferFloat();
    if(CheckPointer(temp) == POINTER_INVALID)
      class="kw">return false;
    if(!temp.BufferInit(num_kv, class="num">0))
      class="kw">return false;
    if(!temp.BufferCreate(OpenCL))
      class="kw">return false;
    if(!K_Tensors.Add(temp))
      class="kw">return false;
    temp = new CBufferFloat();
    if(CheckPointer(temp) == POINTER_INVALID)
      class="kw">return false;
    if(!temp.BufferInit(num_kv, class="num">0))
      class="kw">return false;
    if(!temp.BufferCreate(OpenCL))
      class="kw">return false;
    if(!V_Tensors.Add(temp))
      class="kw">return false;
    temp = new CBufferFloat();
    if(CheckPointer(temp) == POINTER_INVALID)
      class="kw">return false;
    if(!temp.BufferInit(class="num">2 * num_kv, class="num">0))
      class="kw">return false;
    if(!temp.BufferCreate(OpenCL))
      class="kw">return false;
    if(!KV_Tensors.Add(temp))
      class="kw">return false;
  }
class=class="str">"cmt">//--- Initialize scores
temp = new CBufferFloat();
if(CheckPointer(temp) == POINTER_INVALID)
  class="kw">return false;
if(!temp.BufferInit(scores, class="num">0))
  class="kw">return false;
if(!temp.BufferCreate(OpenCL))
  class="kw">return false;
if(!S_Tensors.Add(temp))
  class="kw">return false;
class=class="str">"cmt">//--- Initialize multi-heads attention out
temp = new CBufferFloat();

◍ Transformer 层里的张量缓冲装配

在 MT5 用 OpenCL 跑神经网络推理时,每一层 transformer 的张量缓冲必须按 attention 与 feed forward 两条线分别挂到容器里,否则后面内核一调就崩。 下面这段是单层初始化的核心:先给 attention 输出建缓冲,尺寸由 mh_out 决定,初始化填 0;再给 FF 输出建缓冲,尺寸用 out 参数。 喂给前馈的隐层缓冲直接拉到 4*out,这是 transformer 里把维度放大再缩回的标准做法,显存占用会随层宽线性跳。 最后一层的 FF 出口不走临时缓冲,而是按 d 值直接接 Output 或 Gradient,用 continue 跳过后续建缓冲逻辑,少一次 new 就少一处泄漏风险。 缓冲区建完别忘 CheckPointer 判空,POINTER_INVALID 就 return false,MT5 的 OpenCL 上下文里野指针比 CPU 端更难排查。外汇与贵金属行情高频跳动,这类 GPU 计算若初始化失败可能导致信号延迟,实盘属高风险操作。

MQL5 / C++
if(CheckPointer(temp) == POINTER_INVALID)
      class="kw">return false;
   if(!temp.BufferInit(mh_out, class="num">0))
      class="kw">return false;
   if(!temp.BufferCreate(OpenCL))
      class="kw">return false;
   if(!AO_Tensors.Add(temp))
      class="kw">return false;
   class=class="str">"cmt">//--- Initialize attention out
   temp = new CBufferFloat();
   if(CheckPointer(temp) == POINTER_INVALID)
      class="kw">return false;
   if(!temp.BufferInit(out, class="num">0))
      class="kw">return false;
   if(!temp.BufferCreate(OpenCL))
      class="kw">return false;
   if(!FF_Tensors.Add(temp))
      class="kw">return false;
   class=class="str">"cmt">//--- Initialize Feed Forward class="num">1
   temp = new CBufferFloat();
   if(CheckPointer(temp) == POINTER_INVALID)
      class="kw">return false;
   if(!temp.BufferInit(class="num">4 * out, class="num">0))
      class="kw">return false;
   if(!temp.BufferCreate(OpenCL))
      class="kw">return false;
   if(!FF_Tensors.Add(temp))
      class="kw">return false;
   class=class="str">"cmt">//--- Initialize Feed Forward class="num">2
   if(i == iLayers - class="num">1)
     {
      if(!FF_Tensors.Add(d == class="num">0 ? Output : Gradient))
         class="kw">return false;
      class="kw">continue;
     }
   temp = new CBufferFloat();
   if(CheckPointer(temp) == POINTER_INVALID)
      class="kw">return false;
   if(!temp.BufferInit(out, class="num">0))
      class="kw">return false;
   if(!temp.BufferCreate(OpenCL))
      class="kw">return false;
   if(!FF_Tensors.Add(temp))
      class="kw">return false;
   }
   class=class="str">"cmt">//--- Initialize Q weights
temp = new CBufferFloat();

QKV 权重在 OpenCL 侧的初始化细节

这段逻辑干的事很直接:给注意力结构里的 Query 权重逐层建缓冲,并按 iLayersToOneKV 的间隔去补 K、V 权重。任何一步 CBufferFloat 分配或 Reserve 失败,函数立刻 return false,模型构建直接中断。 先看 Q 权重部分:先 CheckPointer 确认 temp 不是 POINTER_INVALID,否则退出;Reserve(q_weights) 预留空间后,用 1/sqrt(iWindow+1) 算出缩放系数 k,循环 q_weights 次,每次塞入 GenerateWeight()*2*k - k,把权重约束在 [-k, k] 区间。 K 和 V 的初始化只在 i % iLayersToOneKV == 0 时触发,说明多层可能共享同一组 KV,能省掉不少显存。kv_weights 长度的权重同样走 [-k, k] 截断,且 K、V 各新建一个 temp 缓冲分别入列,互不共用对象。 在 MT5 里跑这套,重点盯 BufferCreate(OpenCL) 的返回值——显卡上下文没起来时它会失败,却容易被外层忽略。外汇与贵金属杠杆高,这类 GPU 加速模块若初始化不完整,后续推理结果可能无声偏离,建议先在策略测试器单步验证每层权重尺寸。

MQL5 / C++
if(CheckPointer(temp) == POINTER_INVALID)
     class="kw">return false;
if(!temp.Reserve(q_weights))
     class="kw">return false;
class="type">float k = (class="type">float)(class="num">1 / sqrt(iWindow + class="num">1));
for(class="type">uint w = class="num">0; w < q_weights; w++)
  {
   if(!temp.Add(GenerateWeight() * class="num">2 * k - k))
        class="kw">return false;
  }
if(!temp.BufferCreate(OpenCL))
     class="kw">return false;
if(!QKV_Weights.Add(temp))
     class="kw">return false;
class=class="str">"cmt">//--- Initialize K weights
if(i % iLayersToOneKV == class="num">0)
  {
   temp = new CBufferFloat();
   if(CheckPointer(temp) == POINTER_INVALID)
        class="kw">return false;
   if(!temp.Reserve(kv_weights))
        class="kw">return false;
   class="type">float k = (class="type">float)(class="num">1 / sqrt(iWindow + class="num">1));
   for(class="type">uint w = class="num">0; w < kv_weights; w++)
     {
      if(!temp.Add(GenerateWeight() * class="num">2 * k - k))
          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.Reserve(kv_weights))
        class="kw">return false;
   for(class="type">uint w = class="num">0; w < kv_weights; w++)
     {
      if(!temp.Add(GenerateWeight() * class="num">2 * k - k))
          class="kw">return false;
     }
   if(!temp.BufferCreate(OpenCL))
        class="kw">return false;
   if(!V_Weights.Add(temp))
        class="kw">return false;
   }
class=class="str">"cmt">//--- Initialize Weights0

「前馈层权重缓冲的初始化细节」

这段逻辑在 MT5 的 OpenCL 环境里给前馈网络(FF)逐层建权重缓冲,分三次循环对应三组维度:w0、ff_1、ff_2。每次都先 new 一个 CBufferFloat,用 CheckPointer 判空,再 Reserve 预留空间,防止后续 Add 频繁重分配。 权重值由 GenerateWeight()*2*k - k 生成,相当于把随机权重从 [0,1] 映射到 [-k, k] 区间。前两组用同一个 k,第三组在循环前把 k 重算成 1/sqrt(4*iWindow+1),意味着窗口越大、初始权重尺度越小,倾向抑制大窗口下的梯度爆炸。 最后一段按优化器类型补缓冲:SGD 只建 1 个,ADAM 或其他建 2 个;BufferInit 的大小在 d==0 或 ADAM 时取 q_weights,否则取 iWindowKey*iHeads。开 MT5 把这段贴进 EA 初始化函数,改 iWindow 从 10 到 100,看第三组 k 值从约 0.154 降到约 0.05,能直接验证尺度收缩。

MQL5 / C++
temp = new CBufferFloat();
if(CheckPointer(temp) == POINTER_INVALID)
   class="kw">return false;
if(!temp.Reserve(w0))
   class="kw">return false;
for(class="type">uint w = class="num">0; w < w0; 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">//--- Initialize FF Weights
temp = new CBufferFloat();
if(CheckPointer(temp) == POINTER_INVALID)
   class="kw">return false;
if(!temp.Reserve(ff_1))
   class="kw">return false;
for(class="type">uint w = class="num">0; w < ff_1; 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">//---
temp = new CBufferFloat();
if(CheckPointer(temp) == POINTER_INVALID)
   class="kw">return false;
if(!temp.Reserve(ff_2))
   class="kw">return false;
k = (class="type">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">2 * k - k))
      class="kw">return false;
  }
if(!temp.BufferCreate(OpenCL))
   class="kw">return false;
if(!FF_Weights.Add(temp))
   class="kw">return false;
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))

◍ 注意力层权重缓冲的创建节奏

在 Transformer 类指标的初始化循环里,QKV 权重和前馈权重的显存缓冲不是每层都全量建,而是按 iLayersToOneKV 的间隔来分配 K、V 缓冲。代码里用 if(i % iLayersToOneKV == 0) 控制:只有整除的层才 new 出 K_Weights 与 V_Weights 的 CBufferFloat,其余层只挂 Q 权重和 FF 权重。 具体看 BufferInit 的尺寸逻辑:当 d==0 或优化器为 ADAM 时,KV 缓冲长度为 kv_weights;否则压缩为 iWindowKey * iHeadsKV。前馈部分同理,d==0 或 ADAM 时取 w0,否则用 iWindow,而第二块 FF 缓冲在非 ADAM 时固定为 4 * iWindow——这个 4 倍扩张是后续逐层非线性容量的来源。 每段 new 出来都要过 CheckPointer 判 POINTER_INVALID,且 BufferCreate(OpenCL) 失败立即 return false。在 MT5 里跑这类 EA,若显卡显存不足,往往就卡在某一层的 BufferCreate 返回 false,开终端的 OpenCL 日志能直接定位到第几层。外汇与贵金属杠杆交易本身高风险,这类自定义指标仅作概率参考,不构成方向判定。

MQL5 / C++
      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;
         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 FF Weights
      temp = new CBufferFloat();
      if(CheckPointer(temp) == POINTER_INVALID)
         class="kw">return false;
      if(!temp.BufferInit((d == class="num">0 || optimization == ADAM ? ff_1 : class="num">4 * iWindow), class="num">0))

注意力层权重缓冲与核函数参数绑定

上面这段截自多头注意力类里两个关键方法的尾部,先看前一段:循环里每建一个 CBufferFloat 就走 BufferInit → BufferCreate → FF_Weights.Add 三步,任何一步返回 false 整个初始化立即中断。当 d==0 或优化器选 ADAM 时,缓冲长度取 ff_2,否则取 iWindow,这个分支直接决定显存占用的峰值。 后一段 AttentionOut 把 q、kv、scores、out 四个浮点缓冲依次塞进 OpenCL 核 def_k_MH2AttentionOut 的参数槽,global_work_size 设成 {iUnits, iUnits, iHeads*iVariables},local_work_size 则是 {1, iUnits, 1}。任何 SetArgumentBuffer 失败都会 printf 出函数名、错误码和行号然后返 false,在 MT5 专家日志里能直接定位是哪一块缓冲没绑上。 跑这类 GPU 注意力网络时,外汇与贵金属行情的高波动可能让回测与实盘偏差放大,任何推断结果都只是概率倾向,开 MT5 把这段代码贴进自定义神经元类,先单步看 BufferCreate 是否每次都返 true 再上量。

MQL5 / C++
      class="kw">return false;
      if(!temp.BufferCreate(OpenCL))
         class="kw">return false;
      if(!FF_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 ? ff_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;
      }
   }
   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 CNeuronMVMHAttentionMLKV::AttentionOut(CBufferFloat *q, CBufferFloat *kv,
CBufferFloat *scores, CBufferFloat *out)
   {
   if(!OpenCL)
      class="kw">return false;
class=class="str">"cmt">//---
   class="type">uint global_work_offset[class="num">3] = {class="num">0};
   class="type">uint global_work_size[class="num">3] = {iUnitsclass=class="str">"cmt">/*Q units*/, iUnitsclass=class="str">"cmt">/*K units*/, iHeads * iVariables};
   class="type">uint local_work_size[class="num">3] = {class="num">1, iUnits, class="num">1};
   ResetLastError();
   if(!OpenCL.SetArgumentBuffer(def_k_MH2AttentionOut, def_k_mh2ao_q, q.GetIndex()))
      {
      printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__);
      class="kw">return false;
      }
   if(!OpenCL.SetArgumentBuffer(def_k_MH2AttentionOut, def_k_mh2ao_kv, kv.GetIndex()))
      {
      printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__);
      class="kw">return false;
      }
   if(!OpenCL.SetArgumentBuffer(def_k_MH2AttentionOut, def_k_mh2ao_score, scores.GetIndex()))
      {
      printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__);
      class="kw">return false;
      }
   if(!OpenCL.SetArgumentBuffer(def_k_MH2AttentionOut, def_k_mh2ao_out, out.GetIndex()))
      {
      printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__);
      class="kw">return false;
      }
   if(!OpenCL.SetArgument(def_k_MH2AttentionOut, def_k_mh2ao_dimension, (class="type">int)iWindowKey))
      {
让小布替你跑这套通道诊断
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到独立通道与全局注入后的形态差异,你专注判断信号而不是调参。

常见问题

通常将每个符号的历史序列单独抽取为高速通道再分别投影,避免跨品种张量拼接,具体代码见技术正文的小节实现。
外汇贵金属波动受宏观事件冲击,分布漂移频繁,保留独立结构作底可概率上降低过拟合,但仍是高风险市场。
可以,小布盯盘的品种页已内置 AIGC 诊断,能呈现全局信息注入前后的通道特异性变化,省去手动跑脚本。
它随通道投影联合优化,更适配多模态不等长序列,细节在本文算法小节展开。
标识符让统一模型区分来源通道,在维持独立底结构同时保留各品种形态偏好,倾向提升预测稳定性。