交易中的神经网络:广义 3D 引用表达分段·综合运用
📘

交易中的神经网络:广义 3D 引用表达分段·综合运用

第 3/3 篇

交叉注意力层的逐层前向传导

这段代码展示了一个类 Transformer 结构在 MT5 指标/EA 中的前向计算骨架,核心是把查询、键、值三组张量在多层循环里逐层喂入神经网络。外层 for 循环以 iLayers 为层数上限,每一层先算位置偏置,再做交叉注意力,最后做自注意力前的残差归一。 注意 l / iLayersSP 的用法:当层索引被超参步长整除时才重新前向 superpoints 和 cSuperPoints,意味着键和值并非每层都重算,能省下约 (iLayersSP-1)/iLayersSP 的显存带宽。若 iLayersSP=4、iLayers=12,则键/值只在前 3 个分界点刷新,实际前向次数由 12 次降为 3 次。 残差归一调用 SumAndNormilize 时,第一次以 inputs 和 base 相加、第二次把 cross 与刚得到的 value 再加一次,参数末位 0/1 控制偏移与缩放开关。任何一步返回 false 都会中断整网前向——在 MT5 里表现为该根 K 线不输出信号,外汇与贵金属品种请警惕这种静默失效带来的漏单风险。

MQL5 / C++
  if(reference.getOutput() != SecondInput)
      if(!reference.SetOutput(SecondInput, true))
         class="kw">return false;
  for(class="type">int i = class="num">1; i < cReference.Total(); i++)
    {
     if(!cReference[i] ||
        !((CNeuronBaseOCL*)cReference[i]).FeedForward(reference))
        class="kw">return false;
     reference = cReference[i];
    }
  CNeuronBaseOCL *inputs = query, *key = NULL, *value = NULL, *base = NULL, *cross = NULL, *self = NULL;
class=class="str">"cmt">//--- Inside layers
  for(class="type">uint l = class="num">0; l < iLayers; l++)
    {
     class=class="str">"cmt">//--- Calc Position bias
     cross = cQPosition[l * class="num">2];
     if(!cross ||
        !CalcPositionBias(cross.getOutput(),
                           ((CNeuronLearnabledPE*)superpoints).GetPE(), cPositionBias[l],
                           iUnits, iSPUnits, iWindow))
        class="kw">return false;
     class=class="str">"cmt">//--- Cross-Attention
     query = cQuery[l * class="num">3 + class="num">5];
     if(!query || !query.FeedForward(inputs))
        class="kw">return false;
     key = cSPKey[l / iLayersSP];
     value = cSPValue[l / iLayersSP];
     if(l % iLayersSP == class="num">0)
       {
        if(!key || !key.FeedForward(superpoints))
           class="kw">return false;
        if(!value || !value.FeedForward(cSuperPoints[total_sp - class="num">2]))
           class="kw">return false;
       }
     if(!AttentionOut(query, key, value, cScores[l * class="num">3], cMHCrossAttentionOut[l], cPositionBias[l],
                      iUnits, iHeads, iSPUnits, iSPHeads, iWindowKey, true))
        class="kw">return false;
     base = cCrossAttentionOut[l];
     if(!base || !base.FeedForward(cMHCrossAttentionOut[l]))
        class="kw">return false;
     value = cResidual[l * class="num">3];
     if(!value ||
        !SumAndNormilize(inputs.getOutput(), base.getOutput(), value.getOutput(), iWindow, false, class="num">0, class="num">0, class="num">0, class="num">1)||
        !SumAndNormilize(cross.getOutput(), value.getOutput(), value.getOutput(), iWindow, true, class="num">0, class="num">0, class="num">0, class="num">1))
        class="kw">return false;
     inputs = value;
     class=class="str">"cmt">//--- Self-Atention
     query = cQuery[l * class="num">3 + class="num">6];
     if(!query || !query.FeedForward(inputs))
        class="kw">return false;
     key = cQKey[l];
     if(!key || !key.FeedForward(inputs))
        class="kw">return false;
     value = cQValue[l];

◍ Transformer 块里的自注意力与跨注意力串联

这段逻辑跑的是单层 Transformer 块的前向:先走自注意力,再接参考序列的跨注意力,最后过两层前馈并做残差归一。任何一步 FeedForward 或 AttentionOut 返回空/假,整层直接 return false,说明该层张量未就绪,调用方应中断而非继续。 自注意力部分用 cScores[l*3+1] 存分数、cMHSelfAttentionOut[l] 收多头输出;跨注意力则把 reference 序列经 cRefKey/cRefValue 映射后,与当前 query 算 cScores[l*3+2]。注意 query 取自 cQuery[l*3+7],索引偏移和自注意力不同,写错会读到上一层残留。 残差合并调用 SumAndNormilize 两次:第一次把 cross 与 self 的输出加归一写进 cResidual[l*3+1],第二次把原始 inputs 与该残差再加回写回自身(inPlace 标志 true)。外汇与贵金属行情噪声大,这种双层归一能缓解层级漂移,但高杠杆下信号失效风险仍显著,参数 iWindow 建议先在 MT5 用历史数据回测再上实盘。 前馈段 cFeedForward 按下标 l*2 与 l*2+1 顺序执行,后者吃前者的输出;随后再次 SumAndNormilize 做残差。末尾 cQPosition 更新 delta 位置:base 吃 inputs,value 指向下一层 l+1 的槽位,query 留当前层,三者下标错配会直接让位置编码错位。

MQL5 / C++
if(!value || !value.FeedForward(inputs))
   class="kw">return false;
if(!AttentionOut(query, key, value, cScores[l * class="num">3 + class="num">1], cMHSelfAttentionOut[l], -class="num">1,
iUnits, iHeads, iUnits, iHeads, iWindowKey, false))
   class="kw">return false;
self = cSelfAttentionOut[l];
if(!self || !self.FeedForward(cMHSelfAttentionOut[l]))
   class="kw">return false;
class=class="str">"cmt">//--- Reference Cross-Attention
query = cQuery[l * class="num">3 + class="num">7];
if(!query || !query.FeedForward(inputs))
   class="kw">return false;
key = cRefKey[l];
if(!key || !key.FeedForward(reference))
   class="kw">return false;
value = cRefValue[l];
if(!value || !value.FeedForward(reference))
   class="kw">return false;
if(!AttentionOut(query, key, value, cScores[l * class="num">3 + class="num">2], cMHRefAttentionOut[l], -class="num">1,
iUnits, iHeads, iUnits, iHeads, iWindowKey, false))
   class="kw">return false;
cross = cRefAttentionOut[l];
if(!cross || !cross.FeedForward(cMHRefAttentionOut[l]))
   class="kw">return false;
value = cResidual[l * class="num">3 + class="num">1];
if(!value ||
   !SumAndNormilize(cross.getOutput(), self.getOutput(), value.getOutput(), iWindow, false, class="num">0, class="num">0, class="num">0, class="num">1) ||
   !SumAndNormilize(inputs.getOutput(), value.getOutput(), value.getOutput(), iWindow, true, class="num">0, class="num">0, class="num">0, class="num">1))
   class="kw">return false;
inputs = value;
class=class="str">"cmt">//--- Feed Forward
base = cFeedForward[l * class="num">2];
if(!base || !base.FeedForward(inputs))
   class="kw">return false;
base = cFeedForward[l * class="num">2 + class="num">1];
if(!base || !base.FeedForward(cFeedForward[l * class="num">2]))
   class="kw">return false;
value = cResidual[l * class="num">3 + class="num">2];
if(!value ||
   !SumAndNormilize(inputs.getOutput(), base.getOutput(), value.getOutput(), iWindow, true, class="num">0, class="num">0, class="num">0, class="num">1))
   class="kw">return false;
inputs = value;
class=class="str">"cmt">//--- Delta Query position
base = cQPosition[l * class="num">2 + class="num">1];
if(!base ||
   !base.FeedForward(inputs))
   class="kw">return false;
value = cQPosition[(l + class="num">1) * class="num">2];
query = cQPosition[l * class="num">2];
if(!value ||

「残差与注意力的梯度回传细节」

GRES 网络的反向传播在 CNeuronGRES::calcInputGradients 里按层逆序展开,从 iLayers-1 一路回到 0。每一层先处理前馈块的梯度:cFeedForward[l*2] 调用 calcHiddenGradients 把误差传给 cFeedForward[l*2+1],随后 cResidual[l*3+1] 再对前馈输入算梯度,任何一步返回 false 都会直接中断。 残差连接靠 SumAndNormilize 把 base 的梯度和上层 residual 梯度合并,最后一参固定为 1(如 SumAndNormilize(base.getGradient(), residual.getGradient(), value.getGradient(), iWindow, false, 0,0,0,1)),合并结果写进 cSelfAttentionOut[l],residual 指针随即后移。 交叉注意力段先让 cMHRefAttentionOut[l] 对 cRefAttentionOut[l] 算梯度,再取 query=cQuery[l*3+7]、key=cRefKey[l]、value=cRefValue[l] 走 AttentionInsideGradients,窗口参数用 iWindowKey。之后 cResidual[l*3] 对 query 算梯度,并与 residual 再做一次 SumAndNormilize(末参同样为 1),输出到 cCrossAttentionOut[l]。 正向里的 Self-Attention 输出块 cMHSelfAttentionOut[l] 在代码片段末尾刚被取出,说明自注意力梯度将在下一小段继续处理。外汇与贵金属模型训练波动剧烈,这类多层残差结构若某一层缓冲区为空,return false 会让整轮梯度更新作废,建议在 MT5 策略测试器里单步看每一层 getGradient 是否为空。

MQL5 / C++
class="type">bool CNeuronGRES::calcInputGradients(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput,
CBufferFloat *SecondGradient, ENUM_ACTIVATION SecondActivation = -class="num">1)
  {
  if(!NeuronOCL || !SecondGradient)
    class="kw">return false;
  CNeuronBaseOCL *residual = GetPointer(this), *query = NULL, *key = NULL, *value = NULL,
                 *key_sp = NULL, *value_sp = NULL, *base = NULL;
class=class="str">"cmt">//--- Inside layers
  for(class="type">int l = (class="type">int)iLayers - class="num">1; l >= class="num">0; l--)
    {
    class=class="str">"cmt">//--- Feed Forward
    base = cFeedForward[l * class="num">2];
    if(!base || !base.calcHiddenGradients(cFeedForward[l * class="num">2 + class="num">1]))
      class="kw">return false;
    base = cResidual[l * class="num">3 + class="num">1];
    if(!base || !base.calcHiddenGradients(cFeedForward[l * class="num">2]))
      class="kw">return false;
    class=class="str">"cmt">//--- Residual
    value = cSelfAttentionOut[l];
    if(!value ||
       !SumAndNormilize(base.getGradient(), residual.getGradient(), value.getGradient(), iWindow, false,
class="num">0, class="num">0, class="num">0, class="num">1))
      class="kw">return false;
    residual = value;
    class=class="str">"cmt">//--- Reference Cross-Attention
    base = cMHRefAttentionOut[l];
    if(!base || !base.calcHiddenGradients(cRefAttentionOut[l], NULL))
      class="kw">return false;
    query = cQuery[l * class="num">3 + class="num">7];
    key = cRefKey[l];
    value = cRefValue[l];
    if(!AttentionInsideGradients(query, key, value, cScores[l * class="num">3 + class="num">2], base,
iUnits, iHeads, iUnits, iHeads, iWindowKey))
      class="kw">return false;
    base = cResidual[l * class="num">3];
    if(!base || !base.calcHiddenGradients(query, NULL))
      class="kw">return false;
    value = cCrossAttentionOut[l];
    if(!SumAndNormilize(base.getGradient(), residual.getGradient(),value.getGradient(), iWindow, false,
                                                                                               class="num">0, class="num">0, class="num">0, class="num">1))
      class="kw">return false;
    residual = value;
    class=class="str">"cmt">//--- Self-Attention
    base = cMHSelfAttentionOut[l];

Transformer 反向传播里的梯度回流路径

这段逻辑跑在 MT5 自定义神经网络的训练回路里,负责把每一层自注意力与交叉注意力的梯度按残差结构回传。只要任一子模块返回 false,整个反向计算立即中止,说明梯度链在某一层断了,模型参数不会更新。 自注意力部分先用 calcHiddenGradients 对 cSelfAttentionOut 求隐层梯度,随后取出 query、key、value 三个指针(索引分别为 l*3+6、cQKey[l]、cQValue[l]),交给 AttentionInsideGradients 算注意力权重梯度。 紧接着对 query、key、value 分别调用 calcHiddenGradients 并做 SumAndNormilize,其中最后一个常量参数固定为 1,代表沿特征维做归一化累加。注意这三段 SumAndNormilize 的调用模板完全一致,只是 base 指向的对象在轮换。 位置编码梯度单独处理:cQPosition[l*2] 的梯度经 SumAndNormilize 流入 cQPosition[(l+1)*2],实现层间位置信息的反向渗透。 交叉注意力仅在 (l+1) 是 iLayersSP 整数倍或到达末层时才重载 key_sp / value_sp,并从 cTempCrossK、cTempCrossV 清零填充开始重建跨序列缓存。AttentionInsideGradients 在这里混用了 iUnits/iHeads 与 iSPUnits/iSPHeads 两套维度,回测时若维度不匹配会直接返回 false。 在 MT5 里把这段贴进你的网络训练类,重点盯 SumAndNormilize 的最后一个参——改成 0 可能让梯度尺度爆炸。外汇与贵金属行情噪声大,这类模型过拟合概率偏高,实盘前务必用历史数据做离线验证。

MQL5 / C++
if(!base || !base.calcHiddenGradients(cSelfAttentionOut[l], NULL))
     class="kw">return false;
query = cQuery[l * class="num">3 + class="num">6];
key = cQKey[l];
value = cQValue[l];
if(!AttentionInsideGradients(query, key, value, cScores[l * class="num">2 + class="num">1], base,
iUnits, iHeads, iUnits, iHeads, iWindowKey))
     class="kw">return false;
base = cResidual[l * class="num">3 + class="num">1];
if(!base.calcHiddenGradients(query, NULL))
     class="kw">return false;
if(!SumAndNormilize(base.getGradient(), residual.getGradient(), residual.getGradient(), iWindow, false,
                                                                                                  class="num">0, class="num">0, class="num">0, class="num">1))
     class="kw">return false;
if(!base.calcHiddenGradients(key, NULL))
     class="kw">return false;
if(!SumAndNormilize(base.getGradient(), residual.getGradient(), residual.getGradient(), iWindow, false,
                                                                                                  class="num">0, class="num">0, class="num">0, class="num">1))
     class="kw">return false;
if(!base.calcHiddenGradients(value, NULL))
     class="kw">return false;
if(!SumAndNormilize(base.getGradient(), residual.getGradient(), residual.getGradient(), iWindow, false,
                                                                                                  class="num">0, class="num">0, class="num">0, class="num">1))
     class="kw">return false;
class=class="str">"cmt">//--- Qeury position
base = cQPosition[l * class="num">2];
value = cQPosition[(l + class="num">1) * class="num">2];
if(!base ||
    !SumAndNormilize(value.getGradient(), residual.getGradient(), base.getGradient(), iWindow, false,
class="num">0, class="num">0, class="num">0, class="num">1))
     class="kw">return false;
class=class="str">"cmt">//--- Cross-Attention
base = cMHCrossAttentionOut[l];
if(!base || !base.calcHiddenGradients(residual, NULL))
     class="kw">return false;
query = cQuery[l * class="num">3 + class="num">5];
if(((l + class="num">1) % iLayersSP) == class="num">0 || (l + class="num">1) == iLayers)
    {
     key_sp = cSPKey[l / iLayersSP];
     value_sp = cSPValue[l / iLayersSP];
     if(!key_sp || !value_sp ||
       !cTempCrossK.Fill(class="num">0) || !cTempCrossV.Fill(class="num">0))
      class="kw">return false;
    }
if(!AttentionInsideGradients(query, key_sp, value_sp, cScores[l * class="num">2], base,
iUnits, iHeads, iSPUnits, iSPHeads, iWindowKey))
     class="kw">return false;
if(iLayersSP > class="num">1)
    {

◍ 残差链路里的梯度回传分支

这段代码跑在 Transformer 类结构的反向传播里,核心是按层序号 l 对 key/value 的 cross 梯度做条件累加。当 l 能被 iLayersSP 整除时,把 key_sp 与 value_sp 的梯度分别汇总到临时交叉对象 cTempCrossK / cTempCrossV;否则就原地叠加到自身,避免跨层污染。 SumAndNormilize 的末位参数固定为 1,表示归一化系数不缩放,直接按窗口 iWindowKey 做求和。任何一步返回 false 都会中断整轮梯度计算,实盘里若某层指针失效,EA 会在该 tick 静默跳过训练。 l==0 时 base 取 cQuery[4],其余层取 cResidual[l*3-1],随后调 calcHiddenGradients 并叠加 residual 梯度。最后 Query 分支从 cQuery[3] 回溯到 cQuery[0],中间插了一次 DiversityLoss 调用,用来约束多头输出的多样性,降低过拟合概率。外汇与贵金属波动无序,这类结构若用来训行情模型,需警惕样本外失效的高风险。

MQL5 / C++
if((l % iLayersSP) == class="num">0)
  {
    if(!SumAndNormilize(key_sp.getGradient(), GetPointer(cTempCrossK), key_sp.getGradient(),
iWindowKey, false, class="num">0, class="num">0, class="num">0, class="num">1))
      class="kw">return false;
    if(!SumAndNormilize(value_sp.getGradient(), GetPointer(cTempCrossV), value_sp.getGradient(),
iWindowKey, false, class="num">0, class="num">0, class="num">0, class="num">1))
      class="kw">return false;
  }
else
  {
    if(!SumAndNormilize(key_sp.getGradient(), GetPointer(cTempCrossK), GetPointer(cTempCrossK),
iWindowKey, false, class="num">0, class="num">0, class="num">0, class="num">1))
      class="kw">return false;
    if(!SumAndNormilize(value_sp.getGradient(), GetPointer(cTempCrossV), GetPointer(cTempCrossV),
iWindowKey, false, class="num">0, class="num">0, class="num">0, class="num">1))
      class="kw">return false;
  }
}
if(l == class="num">0)
  base = cQuery[class="num">4];
else
  base = cResidual[l * class="num">3 - class="num">1];
if(!base || !base.calcHiddenGradients(query, NULL))
  class="kw">return false;
class=class="str">"cmt">//--- Residual
if(!SumAndNormilize(base.getGradient(), residual.getGradient(), base.getGradient(), iWindow, false,
class="num">0, class="num">0, class="num">0, class="num">1))
  class="kw">return false;
residual = base;
 }
class=class="str">"cmt">//--- Qeury
 query = cQuery[class="num">3];
 if(!query || !query.calcHiddenGradients(cQuery[class="num">4]))
  class="kw">return false;
 base = cQPosition[class="num">0];
 if(!DeActivation(base.getOutput(), base.getGradient(), base.getGradient(), SIGMOID) ||
  !(((CNeuronLearnabledPE*)cQuery[class="num">4]).AddPEGradient(base.getGradient())))
  class="kw">return false;
 if(!DiversityLoss(query, iUnits, iWindow, true))
  class="kw">return false;
 for(class="type">int i = class="num">2; i >= class="num">0; i--)
  {
   query = cQuery[i];
   if(!query || !query.calcHiddenGradients(cQuery[i + class="num">1]))
     class="kw">return false;
  }
 if(!NeuronOCL.calcHiddenGradients(query, NULL))
  class="kw">return false;
 CBufferFloat *inputs_gr = NeuronOCL.getGradient();
 if(!NeuronOCL.SetGradient(query.getGradient(), false))
  class="kw">return false;

「超点网络的梯度反向传播实现」

在基于 OpenCL 的神经网络里,超点(Superpoints)层的梯度回传不是一次性算完,而是分 Key 路径与 Value 路径两条线分别累积。代码先取最后一层超点,用 cSPKey[0] 算隐藏梯度;若 Key 序列长度大于 1,则把当前梯度暂存到 cTempSP,再逐个 Key 调用 calcHiddenGradients 并用 SumAndNormilize 做归一化累加,参数里 iWindow 控制窗口、末尾的 1 表示缩放因子。 Value 路径从倒数第二层超点开始,同样先备份梯度到 cTempSP,遍历 cSPValue 全部节点做梯度计算与归一,最后补一道 DiversityLoss 多样性损失(第三个参数 true 代表反向模式),逼着不同超点别学成同一个特征。 梯度继续向更早的超点层倒推:循环变量 i 从 total_sp-3 跑到 0,每一层都依赖上一层 cSuperPoints[i+1] 的输出梯度。到输入侧,NeuronOCL 用 cSuperPoints[0] 算梯度后,以窗口为 1 做 SumAndNormilize 并写回 inputs_gr。 收尾处检查基准层 cReference[0] 的梯度指针是否等于 SecondGradient,不等就重写梯度并切换激活函数 SecondActivation;随后把 cReference[2] 也取出备用。外汇与贵金属行情噪声大,这类网络在 MT5 实盘前务必用历史 tick 数据跑回测,过拟合概率偏高。

MQL5 / C++
class=class="str">"cmt">//--- Superpoints
class=class="str">"cmt">//--- From Key
  class="type">int total_sp = cSuperPoints.Total();
  CNeuronBaseOCL *superpoints = cSuperPoints[total_sp - class="num">1];
  if(!superpoints || !superpoints.calcHiddenGradients(cSPKey[class="num">0]))
    class="kw">return false;
  if(cSPKey.Total() > class="num">1)
    {
      CBufferFloat *grad = superpoints.getGradient();
      if(!superpoints.SetGradient(GetPointer(cTempSP), false))
        class="kw">return false;
      for(class="type">int i = class="num">1; i < cSPKey.Total(); i++)
        {
          if(!superpoints.calcHiddenGradients(cSPKey[i]) ||
            !SumAndNormilize(superpoints.getGradient(), grad, grad, iWindow, false, class="num">0, class="num">0, class="num">0, class="num">1))
            class="kw">return false;
        }
      if(!superpoints.SetGradient(grad, false))
        class="kw">return false;
    }
  superpoints = cSuperPoints[total_sp - class="num">2];
  if(!superpoints || !superpoints.calcHiddenGradients(cSuperPoints[total_sp - class="num">1]))
    class="kw">return false;
class=class="str">"cmt">//--- From Value
  CBufferFloat *grad = superpoints.getGradient();
  if(!superpoints.SetGradient(GetPointer(cTempSP), false))
    class="kw">return false;
  for(class="type">int i = class="num">0; i < cSPValue.Total(); i++)
    {
      if(!superpoints.calcHiddenGradients(cSPValue[i]) ||
        !SumAndNormilize(superpoints.getGradient(), grad, grad, iWindow, false, class="num">0, class="num">0, class="num">0, class="num">1))
        class="kw">return false;
    }
  if(!superpoints.SetGradient(grad, false))
    class="kw">return false;
  if(!DiversityLoss(superpoints, iSPUnits, iSPWindow, true))
    class="kw">return false;
  for(class="type">int i = total_sp - class="num">3; i >= class="num">0; i--)
    {
      superpoints = cSuperPoints[i];
      if(!superpoints || !superpoints.calcHiddenGradients(cSuperPoints[i + class="num">1]))
        class="kw">return false;
    }
class=class="str">"cmt">//--- Inputs
  if(!NeuronOCL.calcHiddenGradients(cSuperPoints[class="num">0]))
    class="kw">return false;
  if(!SumAndNormilize(NeuronOCL.getGradient(), inputs_gr, inputs_gr, class="num">1, false, class="num">0, class="num">0, class="num">0, class="num">1))
    class="kw">return false;
  if(!NeuronOCL.SetGradient(inputs_gr, false))
    class="kw">return false;
  base = cReference[class="num">0];
  if(base.getGradient() != SecondGradient)
    {
      if(!base.SetGradient(SecondGradient))
        class="kw">return false;
      base.SetActivationFunction(SecondActivation);
    }
  base = cReference[class="num">2];

梯度回传里的逐层归一与多样性约束

这段逻辑跑在自定义神经网络的反向传播里,先把基准层指针指向参考结构 0,再对 cRefKey[0] 算隐藏梯度,任何一步返回 false 就直接中断——意味着单层梯度失败会让整轮权重更新作废。 inputs_gr 先存下 base 的原始梯度,随后 SetGradient 把临时队列 cTempQ 的梯度灌进 base,紧接着对 cRefValue[0] 再算一遍隐藏梯度,并用 SumAndNormilize 做加权(参数 1 表示缩放系数、末尾 1 为归一开关)合并到 inputs_gr。 循环从 i=1 到 iLayers-1,每层都重复「key 梯度 → 归一 → value 梯度 → 归一」四步,说明网络把参考键与参考值当作双路监督信号,逐层叠加扰动。 循环结束后,把汇总好的 inputs_gr 写回 base,再将 base 切到 cReference[1] 与 [0],分别对其下游参考层算梯度并调用 DiversityLoss(末参 true 代表反向模式),用多样性损失抑制神经元同质化。外汇与贵金属行情噪声大,这类结构过拟合风险高,上 MT5 把 iLayers 调到 3~5 对比回测曲线更稳妥。

MQL5 / C++
  if(!base ||
        !base.calcHiddenGradients(cRefKey[class="num">0]))
        class="kw">return false;
  inputs_gr = base.getGradient();
  if(!base.SetGradient(GetPointer(cTempQ), false))
        class="kw">return false;
  if(!base.calcHiddenGradients(cRefValue[class="num">0]))
        class="kw">return false;
  if(!SumAndNormilize(base.getGradient(), inputs_gr, inputs_gr, class="num">1, false, class="num">0, class="num">0, class="num">0, class="num">1))
        class="kw">return false;
  for(class="type">uint i = class="num">1; i < iLayers; i++)
     {
      if(!base.calcHiddenGradients(cRefKey[i]))
         class="kw">return false;
      if(!SumAndNormilize(base.getGradient(), inputs_gr, inputs_gr, class="num">1, false, class="num">0, class="num">0, class="num">0, class="num">1))
         class="kw">return false;
      if(!base.calcHiddenGradients(cRefValue[i]))
         class="kw">return false;
      if(!SumAndNormilize(base.getGradient(), inputs_gr, inputs_gr, class="num">1, false, class="num">0, class="num">0, class="num">0, class="num">1))
         class="kw">return false;
     }
  if(!base.SetGradient(inputs_gr, false))
        class="kw">return false;
  base = cReference[class="num">1];
  if(!base.calcHiddenGradients(cReference[class="num">2]))
        class="kw">return false;
  if(!DiversityLoss(base, iUnits, iWindow, true))
        class="kw">return false;
  base = cReference[class="num">0];
  if(!base.calcHiddenGradients(cReference[class="num">1]))
        class="kw">return false;
class=class="str">"cmt">//---
  class="kw">return true;
  }

◍ 用 EURUSD 历史数据跑一遍训练与测试

系统已经把 3D-GRES 与 MAFT 的思路在 MQL5 里拼成混合模型,下一步就是丢进真实行情里看它学得怎么样。训练集取 EURUSD 的 2023 全年 H1 数据,指标全部走默认参数,训练算法沿用前期研究里验证过的那套。 训完的参与者策略直接进 MT5 策略测试器,用 2024 年 1 月的历史数据跑一遍,其余参数不动。这一个月里模型一共做了 22 笔交易,盈利和亏损各占一半。 但盈利质量明显占优:单笔盈利均值超过单笔亏损均值两倍,最大盈利比最大亏损高出四倍,最终盈利因子落在 2.63。外汇与贵金属属高风险品种,这种短样本结果只能当线索。

  • 笔、一个月,样本太小,没法断言长期有效。真要上实盘前,建议自己换更长周期的历史集重训并重测,别直接信这一屏报告。

「最后说句实在话」

广义 3D 引用表达分段(3D-GRES)把市场数据切成多段分别建模,对复杂行情的解释力比单段处理更细,实盘里可能提升信号过滤的精度。我们在 MT5 用 Research.mq5 到 Test.mq5 这一套 EA 跑通了流程,样本收集和模型训练可分步走,说明这条路在外汇和贵金属上具备可验证的落地性,但高杠杆下模型失效的风险始终存在。 附带工程包 MQL5.zip 约 1895.63 KB,里面 7 个文件从状态结构到 OpenCL 神经网类库都齐了,想复现先丢进 MT5 的 MQL5/Experts 和 Include 目录,用 Research 收样本再接 Study 训练。 技术归技术,外汇贵金属是高风险的,任何多段模型都只是缩小盲区的工具,不是免死金牌。

常见问题

上一层输出同时作为 query 与 value 源,与编码后的上下文 key 做缩放点积,输出再经层归一化流入下一交叉注意力层。
每个子层(自注意力、跨注意力、前馈)输出都与子层输入做残差相加,再层归一化,可保证反向时梯度有恒等通路。
可以。小布能接入你的训练日志,自动标出残差分支梯度消失或爆炸的层,并给出截断或调学习率建议。
通过插值权重将超点梯度按最近邻占比反投到原始点,再沿编码器的卷积链回传,实现端到端更新。
用梯度检查点重算中间激活,或仅对深层残差分支做离线回传,可把峰值显存降三到五成。