神经网络变得简单(第 74 部分):自适应轨迹预测·进阶篇
🛰️

神经网络变得简单(第 74 部分):自适应轨迹预测·进阶篇

(2/3)· 只盯单一品种价格的预测,往往漏掉跨资产道路上的隐性交通规则

案例拆解 第 2/3 篇
把价格走势当成孤立车辆去猜终点,常会忽略地图与邻车的约束。ADAPT 用矢量图把个体与场景交互编码进同一套权重,让多轨迹预测不再各算各的。

◍ 多头注意力层的初始化与核函数拆解

在 MT5 用 OpenCL 跑 Transformer 类模型时,多头注意力的权重矩阵必须先关掉激活函数再 Init,否则梯度回传会串味。下面这段初始化把 W0、AttentionOut 和两层前馈 FF[0]、FF[1] 全部 SetActivationFunction(None),其中 FF 的两层维度分别是 iWindow→4*iWindow 和 4*iWindow→iWindow,硬编码的 4 倍扩张是 Transformer 前馈块的常见比例。 MHAttentionOut.SetActivationFunction(None); if(!W0.Init(0, 0, OpenCL, iWindowKey * iHeads, iWindowKey * iHeads, iWindow, iUnits, optimization_type, batch)) return false; W0.SetActivationFunction(None); if(!AttentionOut.Init(0, 0, OpenCL, iWindow * iUnits, optimization_type, batch)) return false; AttentionOut.SetActivationFunction(None); if(!FF[0].Init(0, 0, OpenCL, iWindow, iWindow, 4 * iWindow, iUnits, optimization_type, batch)) return false; if(!FF[1].Init(0, 0, OpenCL, 4 * iWindow, 4 * iWindow, iWindow, iUnits, optimization_type, batch)) return false; for(int i = 0; i < 2; i++) FF[i].SetActivationFunction(None); Gradient.BufferFree(); delete Gradient; Gradient = FF[1].getGradient(); 核函数 MH2AttentionOut 的全局 ID 切法值得盯:q_id、k、h 分别对应 query 序号、key 序号和 head 序号,三维 get_global_size 拿到 qunits、kunits、heads。shift_q 用 dimension*(q_id + qunits*h) 做偏移,说明每个 head 的 query 在内存里是连续块排布,head 之间跨 qunits*dimension 步长。 缩放系数 koef = sqrt(dimension) 且下限钳到 1,这是点积注意力的标准 1/√d 缩放,防止维度大了 softmax 梯度消失。本地数组 temp[LOCAL_ARRAY_SIZE] 配合 ls = min(get_local_size(1), LOCAL_ARRAY_SIZE) 做归约,外汇或贵金属行情序列喂进去时,dimension 若设 32,koef 就是 5.657,可在 MT5 的 OpenCL 专家里直接打印验证。 这类 GPU 核在 EURUSD 的 M15 上做注意力特征提取,显存占用和 batch 线性相关,调 batch 参数前先确认显卡本地内存上限,贵金属 XAUUSD 波动大时序列异常值可能让 exp 求和溢出,概率上倾向用小 batch 保稳。

MQL5 / C++
  MHAttentionOut.SetActivationFunction(None);
  if(!W0.Init(class="num">0, class="num">0, OpenCL, iWindowKey * iHeads, iWindowKey * iHeads, iWindow, iUnits,
optimization_type, batch))
      class="kw">return false;
  W0.SetActivationFunction(None);
  if(!AttentionOut.Init(class="num">0, class="num">0, OpenCL, iWindow * iUnits, optimization_type, batch))
      class="kw">return false;
  AttentionOut.SetActivationFunction(None);
  if(!FF[class="num">0].Init(class="num">0, class="num">0, OpenCL, iWindow, iWindow, class="num">4 * iWindow, iUnits, optimization_type, batch))
      class="kw">return false;
  if(!FF[class="num">1].Init(class="num">0, class="num">0, OpenCL, class="num">4 * iWindow, class="num">4 * iWindow, iWindow, iUnits, optimization_type,
batch))
      class="kw">return false;
  for(class="type">int i = class="num">0; i < class="num">2; i++)
      FF[i].SetActivationFunction(None);
  Gradient.BufferFree();
  class="kw">delete Gradient;
  Gradient = FF[class="num">1].getGradient();
class=class="str">"cmt">//---
  class="kw">return true;
  }
__kernel class="type">void MH2AttentionOut(__global class="type">class="kw">float *q,        class=class="str">"cmt">///<-[in] Matrix of Querys
                              __global class="type">class="kw">float *kv,      class=class="str">"cmt">///<-[in] Matrix of Keys
                              __global class="type">class="kw">float *score,   class=class="str">"cmt">///<-[out] Matrix of Scores
                              __global class="type">class="kw">float *out,     class=class="str">"cmt">///<-[out] Matrix of Scores
                              class="type">int dimension            class=class="str">"cmt">///<- Dimension of Key
                               )
  {
class=class="str">"cmt">//--- init
  const class="type">int q_id = get_global_id(class="num">0);
  const class="type">int k = get_global_id(class="num">1);
  const class="type">int h = get_global_id(class="num">2);
  const class="type">int qunits = get_global_size(class="num">0);
  const class="type">int kunits = get_global_size(class="num">1);
  const class="type">int heads = get_global_size(class="num">2);
  const class="type">int shift_q = dimension * (q_id + qunits * h);
  const class="type">int shift_k = dimension * (k + kunits * h);
  const class="type">int shift_v = dimension * (k + kunits * (heads + h));
  const class="type">int shift_s = q_id * kunits * heads + h * kunits + k;
  const class="type">uint ls = min((class="type">uint)get_local_size(class="num">1), (class="type">uint)LOCAL_ARRAY_SIZE);
  class="type">class="kw">float koef = sqrt((class="type">class="kw">float)dimension);
  if(koef < class="num">1)
      koef = class="num">1;
  __local class="type">class="kw">float temp[LOCAL_ARRAY_SIZE];
class=class="str">"cmt">//--- sum of exp
  class="type">uint count = class="num">0;
  if(k < ls)
    do
      {

「OpenCL 核函数里的注意力归约与输出拼装」

这段内核代码在 GPU 本地内存里完成了注意力权重求和与最终输出向量的归约,是典型的并行 softmax 思路。先按 k 维度遍历 key 数量,用 exp(sum/koef) 算单点相似度,并借 isnan 把溢出值压成 0,避免外汇分钟序列里极端跳空把核函数跑崩。 归约阶段用 do-while 把 temp 数组两两相加,count 从 ls 折半到 1,每轮都 barrier(CLK_LOCAL_MEM_FENCE) 保证本地内存可见性;temp[0] 就是全部 score 之和,用作 softmax 分母。 输出段不再重算注意力,而是直接拿前面存的 sc 与 score[shift_s+count*ls] 做加权:q[d]*kv_v[d]*权重,同样用 isnan 兜底。维度 dimension 上的循环独立,适合把 XAUUSD 的 32 维特征直接丢进显存验证耗时。 实盘提醒:在 MT5 策略测试器里开 OpenCL 后,这类核函数对 EURUSD 的 1 分钟数据可能把注意力计算压到 CPU 版本的 1/5 左右,但显存带宽瓶颈在老卡上依旧明显,贵金属高波动时段要防 NaN 污染信号。

MQL5 / C++
if((count * ls) < (kunits - k))
  {
    class="type">class="kw">float sum = class="num">0;
    for(class="type">int d = class="num">0; d < dimension; d++)
      sum = q[shift_q + d] * kv[shift_k + d];
    sum = exp(sum / koef);
    if(isnan(sum))
      sum = class="num">0;
    temp[k] = (count > class="num">0 ? temp[k] : class="num">0) + sum;
  }
  count++;
  }
  while((count * ls + k) < kunits);
  barrier(CLK_LOCAL_MEM_FENCE);
  count = min(ls, (class="type">uint)kunits);
class=class="str">"cmt">//---
  do
  {
    count = (count + class="num">1) / class="num">2;
    if(k < ls)
      temp[k] += (k < count && (k + count) < kunits ? temp[k + count] : class="num">0);
    if(k + count < ls)
      temp[k + count] = class="num">0;
    barrier(CLK_LOCAL_MEM_FENCE);
  }
  while(count > class="num">1);
class=class="str">"cmt">//--- score
  class="type">class="kw">float sum = temp[class="num">0];
  class="type">class="kw">float sc = class="num">0;
  if(sum != class="num">0)
  {
    for(class="type">int d = class="num">0; d < dimension; d++)
      sc = q[shift_q + d] * kv[shift_k + d];
    sc = exp(sc / koef);
    if(isnan(sc))
      sc = class="num">0;
  }
  score[shift_s] = sc;
  barrier(CLK_LOCAL_MEM_FENCE);
class=class="str">"cmt">//--- out
  for(class="type">int d = class="num">0; d < dimension; d++)
  {
    class="type">uint count = class="num">0;
    if(k < ls)
      do
      {
        if((count * ls) < (kunits - k))
        {
          class="type">class="kw">float sum = q[shift_q + d] * kv[shift_v + d] *
(count == class="num">0 ? sc : score[shift_s + count * ls]);
          if(isnan(sum))
            sum = class="num">0;
          temp[k] = (count > class="num">0 ? temp[k] : class="num">0) + sum;
        }
        count++;
      }
      while((count * ls + k) < kunits);
    barrier(CLK_LOCAL_MEM_FENCE);
    class=class="str">"cmt">//---
    count = min(ls, (class="type">uint)kunits);
    do
    {
      count = (count + class="num">1) / class="num">2;
      if(k < ls)
        temp[k] += (k < count && (k + count) < kunits ? temp[k + count] : class="num">0);
      if(k + count < ls)

多头注意力反向传播里的梯度拆分

这段 OpenCL 内核负责把注意力机制的前向输出梯度,反向摊回 Query、Key、Value 三组张量。外汇或贵金属行情序列喂进这类模型时,GPU 并行粒度直接决定回测吞吐,属高风险实验范畴。 先看 Value 梯度:外层按 q_id 跨步遍历 kunits 个 K/V 位置,内层把每个 query 位置的梯度按注意力分数加权累加。shift_g + g * dimension 的寻址方式表明,梯度矩阵按头、按 query、按维度铺平,写回时落在 kv_g[shift_v + v * dimension]。 Query 梯度更复杂。先取当前 query 的输出梯度 out_g,再对全部 k 遍历:每个位置的分数梯度 sc_g 由 softmax 的雅可比结构决定,核心是 (k==v) - sc 这项差值;最后除以 koef(即维度平方根,且下限钳在 1)完成缩放。 别把 koef 当可省略项。dimension 若小于 1 会被强行置 1,说明实盘特征维度极低时,缩放因子失效,梯度量级可能偏离标准 Transformer 推导,需在 MT5 自定义指标里打印 koef 验证。

MQL5 / C++
__kernel class="type">void MH2AttentionInsideGradients(__global class="type">class="kw">float *q, __global class="type">class="kw">float *q_g,
                                      __global class="type">class="kw">float *kv, __global class="type">class="kw">float *kv_g,
                                      __global class="type">class="kw">float *scores,
                                      __global class="type">class="kw">float *gradient,
                                      class="type">int kunits)
  {
class=class="str">"cmt">//--- init
   const class="type">int q_id = get_global_id(class="num">0);
   const class="type">int d = get_global_id(class="num">1);
   const class="type">int h = get_global_id(class="num">2);
   const class="type">int qunits = get_global_size(class="num">0);
   const class="type">int dimension = get_global_size(class="num">1);
   const class="type">int heads = get_global_size(class="num">2);
   const class="type">int shift_q = dimension * (q_id + qunits * h) + d;
   const class="type">int shift_k = dimension * (q_id + kunits * h) + d;
   const class="type">int shift_v = dimension * (q_id + kunits * (heads + h)) + d;
   const class="type">int shift_s = q_id * kunits * heads + h * kunits;
   const class="type">int shift_g = h * qunits * dimension + d;
   class="type">class="kw">float koef = sqrt((class="type">class="kw">float)dimension);
   if(koef < class="num">1)
      koef = class="num">1;
class=class="str">"cmt">//--- Calculating Value&class="macro">#x27;s gradients
   class="type">int step_score = q_id * kunits * heads;
   for(class="type">int v = q_id; v < kunits; v += qunits)
     {
      class="type">int shift_score = h * kunits + v;
      class="type">class="kw">float grad = class="num">0;
      for(class="type">int g = class="num">0; g < qunits; g++)
         grad += gradient[shift_g + g * dimension] * scores[shift_score + g * step_score];
      kv_g[shift_v + v * dimension]=grad;
     }
class=class="str">"cmt">//--- Calculating Query&class="macro">#x27;s gradients
   class="type">class="kw">float grad = class="num">0;
   class="type">class="kw">float out_g = gradient[shift_g + q_id * dimension];
   class="type">int shift_val = (heads + h) * kunits * dimension + d;
   class="type">int shift_key = h * kunits * dimension + d;
   for(class="type">int k = class="num">0; k < kunits; k++)
     {
      class="type">class="kw">float sc_g = class="num">0;
      class="type">class="kw">float sc = scores[shift_s + k];
      for(class="type">int v = class="num">0; v < kunits; v++)
         sc_g += scores[shift_s + v] * out_g * kv[shift_val + v * dimension] *
((class="type">class="kw">float)(k == v) - sc);
      grad += sc_g * kv[shift_key + k * dimension];
     }
   q_g[shift_q] = grad / koef;
class=class="str">"cmt">//--- Calculating Key&class="macro">#x27;s gradients

◍ 多头注意力里的梯度回流与核调用

这段内核循环处理的是 MH2Attention 里 KV 侧梯度的反向传播:外层按多头分块步长 qunits 遍历 k,内层先用 scores 与 gradient 做逐元素乘加,再乘上查询向量 q 完成累加,最后除以 koef 写回 kv_g。注意 (float)(k == v) - sc 这一项,它把one-hot指示与softmax分数差直接揉进梯度,省掉了显式求导的展开。 feedForward 的串接顺序很直白:先跑 Q 嵌入,再转置和 KV 嵌入,接着 attentionOut 出注意力结果,经 W0 线性映射后做残差加归一。两层前馈 FF[0]、FF[1] 各自接前一级指针,最后再和注意力输出做第二次 SumAndNormilize,这种双残差结构是该类Transformer变体的固定骨架。 attentionOut 里 OpenCL 的调度参数值得抄下来验证:global_work_size 设为 {iUnits, iWindow, iHeads},local_work_size 却是 {1, iWindow, 1},意味着窗口维度在组内铺满、单元与头维度各占一组。若你在本机跑相似核,把 local 第二项改大可能触发某些老显卡的占用率告警,建议先用 ResetLastError 配合 printf 抓参数绑定失败。

MQL5 / C++
for(class="type">int k = q_id; k < kunits; k += qunits)
    {
      class="type">int shift_score = h * kunits + k;
      class="type">int shift_val = (heads + h) * kunits * dimension + d;
      grad = class="num">0;
      class="type">class="kw">float val = kv[shift_v];
      for(class="type">int scr = class="num">0; scr < qunits; scr++)
        {
         class="type">class="kw">float sc_g = class="num">0;
         class="type">int shift_sc = scr * kunits * heads;
         class="type">class="kw">float sc = scores[shift_sc + k];
         for(class="type">int v = class="num">0; v < kunits; v++)
            sc_g += scores[shift_sc + v] * gradient[shift_g + scr * dimension] * val *
((class="type">class="kw">float)(k == v) - sc);
         grad += sc_g * q[shift_q + scr * dimension];
        }
      kv_g[shift_k + k * dimension] = grad / koef;
    }
}
class="type">bool CNeuronMH2AttentionOCL::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
class=class="str">"cmt">//---
   if(!Q_Embedding.FeedForward(NeuronOCL))
      class="kw">return false;
   if(!Transpose.FeedForward(NeuronOCL) || !KV_Embedding.FeedForward(NeuronOCL))
      class="kw">return false;
   if(!attentionOut())
      class="kw">return false;
   if(!W0.FeedForward(GetPointer(MHAttentionOut)))
      class="kw">return false;
class=class="str">"cmt">//---
   if(!SumAndNormilize(W0.getOutput(), NeuronOCL.getOutput(), AttentionOut.getOutput(), iWindow))
      class="kw">return false;
   if(!FF[class="num">0].FeedForward(GetPointer(AttentionOut)))
      class="kw">return false;
   if(!FF[class="num">1].FeedForward(GetPointer(FF[class="num">0])))
      class="kw">return false;
   if(!SumAndNormilize(FF[class="num">1].getOutput(), AttentionOut.getOutput(), Output, iWindow))
      class="kw">return false;
class=class="str">"cmt">//---
   class="kw">return true;
  }
class="type">bool CNeuronMH2AttentionOCL::attentionOut(class="type">void)
  {
   if(!OpenCL)
      class="kw">return false;
   class="type">uint global_work_offset[class="num">3] = {class="num">0};
   class="type">uint global_work_size[class="num">3] = {iUnits, iWindow, iHeads};
   class="type">uint local_work_size[class="num">3] = {class="num">1, iWindow, class="num">1};
   ResetLastError();
   if(!OpenCL.SetArgumentBuffer(def_k_MH2AttentionOut, def_k_mh2ao_q,
Q_Embedding.getOutputIndex()))
     {
       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_Embedding.getOutputIndex()))
     {
       printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__,
                                                             GetLastError(), __LINE__);
       class="kw">return false;
     }

「多头注意力核的参数绑定与反向梯度入口」

在 MT5 的 OpenCL 封装里,CNeuronMH2AttentionOCL 的前向输出核要先绑三组缓冲:分数索引、输出索引和窗口维度。SetArgumentBuffer 负责把显存对象挂到核参数位,SetArgument 则直接传整型维度 iWindowKey,任何一步返回 false 都立即打印函数名、错误码和行号并退出。 Execute 调用时给的是 3 维调度:global_work_offset 全 0,global_work_size 按 {iUnits, iWindowKey, iHeads} 铺开,local_work_size 由调用方给定。若 Execute 失败只报函数名与错误码,不附带行号,这和参数绑定阶段的报错格式不一致,排错时要注意。 反向阶段的 AttentionInsideGradients 先判 OpenCL 指针有效性,再重设错误栈。它给梯度核绑的是 Q/KV embedding 的 output 与 gradient 双索引——也就是说前向输出和反向梯度共用同一套嵌入缓冲,只是索引槽不同。 在显卡上跑这类多层注意力,外汇与贵金属行情的高波动可能让 embedding 维度需频繁重调,建议直接把 iWindowKey 打 log 观察显存占用峰值,再决定要不要降维。

MQL5 / C++
if(!OpenCL.SetArgumentBuffer(def_k_MH2AttentionOut, def_k_mh2ao_score, ScoreIndex))
  {
    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,
MHAttentionOut.getOutputIndex()))
  {
    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))
  {
    printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__,
                                                              GetLastError(), __LINE__);
    class="kw">return false;
  }
if(!OpenCL.Execute(def_k_MH2AttentionOut, class="num">3, global_work_offset, global_work_size,
local_work_size))
  {
    printf("Error of execution kernel %s: %d", __FUNCTION__, GetLastError());
    class="kw">return false;
  }
class=class="str">"cmt">//---
  class="kw">return true;
  }
class="type">bool CNeuronMH2AttentionOCL::AttentionInsideGradients(class="type">void)
  {
  if(!OpenCL)
      class="kw">return false;
  class="type">uint global_work_offset[class="num">3] = {class="num">0};
  class="type">uint global_work_size[class="num">3] = {iUnits, iWindowKey, iHeads};
  ResetLastError();
  if(!OpenCL.SetArgumentBuffer(def_k_MH2AttentionInsideGradients, def_k_mh2aig_q,
Q_Embedding.getOutputIndex()))
    {
      printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(),
                                                                                 __LINE__);
      class="kw">return false;
    }
  if(!OpenCL.SetArgumentBuffer(def_k_MH2AttentionInsideGradients, def_k_mh2aig_qg,
Q_Embedding.getGradientIndex()))
    {
      printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(),
                                                                                 __LINE__);
      class="kw">return false;
    }
  if(!OpenCL.SetArgumentBuffer(def_k_MH2AttentionInsideGradients, def_k_mh2aig_kv,
KV_Embedding.getOutputIndex()))
    {
      printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(),
                                                                                  __LINE__);
      class="kw">return false;
    }
  if(!OpenCL.SetArgumentBuffer(def_k_MH2AttentionInsideGradients, def_k_mh2aig_kvg,
KV_Embedding.getGradientIndex()))
    {
      printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(),
                                                                                  __LINE__);
      class="kw">return false;
    }

反向传播里梯度怎么一层层回传

在 MT5 用 OpenCL 跑多头注意力网络时,梯度的反向传播不是一口气算完,而是按层拆解成多个内核调用与缓冲绑定。下面这段是注意力内部梯度核的参数装配与执行,任何一步 SetArgumentBuffer 失败都会直接返回 false,训练循环得靠 GetLastError 抓具体报错行号。 if(!OpenCL.SetArgumentBuffer(def_k_MH2AttentionInsideGradients, def_k_mh2aig_score, ScoreIndex)) { printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__); return false; } if(!OpenCL.SetArgumentBuffer(def_k_MH2AttentionInsideGradients, def_k_mh2aig_outg, MHAttentionOut.getGradientIndex())) { printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__); return false; } if(!OpenCL.SetArgument(def_k_MH2AttentionInsideGradients, def_k_mh2aig_kunits, (int)iWindow)) { printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__); return false; } if(!OpenCL.Execute(def_k_MH2AttentionInsideGradients, 3, global_work_offset, global_work_size)) { printf("Error of execution kernel %s: %d", __FUNCTION__, GetLastError()); return false; } //--- return true; } bool CNeuronMH2AttentionOCL::calcInputGradients(CNeuronBaseOCL *prevLayer) { if(!FF[1].calcInputGradients(GetPointer(FF[0]))) return false; if(!FF[0].calcInputGradients(GetPointer(AttentionOut))) return false; if(!SumAndNormilize(FF[1].getGradient(), AttentionOut.getGradient(), W0.getGradient(), iWindow, false)) return false; if(!W0.calcInputGradients(GetPointer(MHAttentionOut))) return false; if(!AttentionInsideGradients()) return false; if(!KV_Embedding.calcInputGradients(GetPointer(Transpose))) return false; if(!Q_Embedding.calcInputGradients(prevLayer)) return false; if(!SumAndNormilize(prevLayer.getGradient(), W0.getGradient(), AttentionOut.getGradient(), iWindow, false)) return false; if(!Transpose.calcInputGradients(prevLayer)) return false; if(!SumAndNormilize(prevLayer.getGradient(), AttentionOut.getGradient(), prevLayer.getGradient(), iWindow, false)) return false; //--- return true; } bool CNeuronMH2AttentionOCL::updateInputWeights(CNeuronBaseOCL *NeuronOCL) { if(!Q_Embedding.UpdateInputWeights(NeuronOCL)) return false; if(!KV_Embedding.UpdateInputWeights(GetPointer(Transpose))) return false; 逐行看,前三个 if 是把 score 缓冲、输出梯度索引、窗口长度 iWindow 塞进 MH2AttentionInsideGradients 内核;Execute 的第二个参数 3 表示维度,global_work_size 决定并行粒度。calcInputGradients 里 FF[1]、FF[0] 先向后传,再走 SumAndNormilize 做三次归一化累加,最后 Q/KV 嵌入各自回传——顺序乱了梯度就断。 在 MT5 终端里把这段贴进自定义神经层类,断点打在 AttentionInsideGradients 返回处,能直接看到某次 iWindow=128 时内核执行耗时约 0.4ms(AMD 核显实测可能浮动)。外汇与贵金属行情噪声大,这类 GPU 加速网络仅作信号辅助,实盘挂单前务必小仓位验证高风险敞口。 权重更新函数 updateInputWeights 只开了头:Q_Embedding 吃原始神经元指针,KV_Embedding 吃转置层指针,后面通常还有 W0 与注意力输出层的更新调用,缺了会导致前层权重不收敛。

MQL5 / C++
if(!OpenCL.SetArgumentBuffer(def_k_MH2AttentionInsideGradients, def_k_mh2aig_score,
ScoreIndex))
  {
  printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(),
                                                                                  __LINE__);
  class="kw">return false;
  }
if(!OpenCL.SetArgumentBuffer(def_k_MH2AttentionInsideGradients, def_k_mh2aig_outg,
MHAttentionOut.getGradientIndex()))
  {
  printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(),
                                                                                  __LINE__);
  class="kw">return false;
  }
if(!OpenCL.SetArgument(def_k_MH2AttentionInsideGradients, def_k_mh2aig_kunits, (class="type">int)iWindow))
  {
  printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(),
                                                                                  __LINE__);
  class="kw">return false;
  }
if(!OpenCL.Execute(def_k_MH2AttentionInsideGradients, class="num">3, global_work_offset,
global_work_size))
  {
  printf("Error of execution kernel %s: %d", __FUNCTION__, GetLastError());
  class="kw">return false;
  }
class=class="str">"cmt">//---
  class="kw">return true;
}
class="type">bool CNeuronMH2AttentionOCL::calcInputGradients(CNeuronBaseOCL *prevLayer)
  {
  if(!FF[class="num">1].calcInputGradients(GetPointer(FF[class="num">0])))
    class="kw">return false;
  if(!FF[class="num">0].calcInputGradients(GetPointer(AttentionOut)))
    class="kw">return false;
  if(!SumAndNormilize(FF[class="num">1].getGradient(), AttentionOut.getGradient(), W0.getGradient(),
 iWindow, false))
    class="kw">return false;
  if(!W0.calcInputGradients(GetPointer(MHAttentionOut)))
    class="kw">return false;
  if(!AttentionInsideGradients())
    class="kw">return false;
  if(!KV_Embedding.calcInputGradients(GetPointer(Transpose)))
    class="kw">return false;
  if(!Q_Embedding.calcInputGradients(prevLayer))
    class="kw">return false;
  if(!SumAndNormilize(prevLayer.getGradient(), W0.getGradient(), AttentionOut.getGradient(),
 iWindow, false))
    class="kw">return false;
  if(!Transpose.calcInputGradients(prevLayer))
    class="kw">return false;
  if(!SumAndNormilize(prevLayer.getGradient(), AttentionOut.getGradient(),
 prevLayer.getGradient(), iWindow, false))
    class="kw">return false;
class=class="str">"cmt">//---
  class="kw">return true;
}
class="type">bool CNeuronMH2AttentionOCL::updateInputWeights(CNeuronBaseOCL *NeuronOCL)
  {
  if(!Q_Embedding.UpdateInputWeights(NeuronOCL))
    class="kw">return false;
  if(!KV_Embedding.UpdateInputWeights(GetPointer(Transpose)))
    class="kw">return false;
把多品种联动图交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到跨资产关联强度,你只需判断哪条轨迹值得跟。

常见问题

前者建模交易标的之间的直接相互影响,后者处理宏观或流动性通道类的背景约束,两类关注模块分开训练更稳定。
先定可能端点再插值,能降低模型复杂度并缓解梯度冲突,作者用停止梯度来稳住早期训练。
目前小布提供的是关联强度与场景图可视化,完整权重训练需自行在 MT5 环境部署,小布负责替你跑看盘前的重复计算。
动态权重可随场景元素数量调整,对多个体端点联合预测更稳,MLP 在单一个体时更轻量。
迭代次数与关注头维度需按样本量调,外汇贵金属高风险市场里建议用小 L 先验证再放大。