神经网络变得轻松(第十二部分):舍弃·综合运用
📘

神经网络变得轻松(第十二部分):舍弃·综合运用

第 3/3 篇

「在 OpenCL 层里挂 Dropout 与多头注意力」

这段代码是神经网络工厂方法里的两个 case 分支,专门在 GPU(OpenCL)上下文里实例化正则化与注意力层。先看 defNeuronMLMHAttentionOCL:它 new 一个 CNeuronMLMHAttentionOCL,马上用 CheckPointer 判 POINTER_INVALID,失败就删掉临时容器 temp 并 return,避免野指针进网络图。 初始化调用 Init 传了 9 个参数,其中 desc.window 与 desc.window_out 决定注意力滑动窗口的入出长度,desc.step 是步长,desc.count 是头数,desc.layers 是堆叠层数;任一项返回 false 都会连续 delete 神经元与 temp 后退出。 被高亮的 defNeuronDropoutOCL 分支逻辑同构:new CNeuronDropoutOCL 后判指针,再 Init(outputs,0,opencl,desc.count,desc.probability,desc.optimization)。这里 desc.probability 是丢弃概率,训练时按该比例置零激活值,对过拟合有概率上的抑制倾向。 两个分支末尾都把局部指针置 NULL 并 break,说明工厂只负责装配不持有所有权,temp 容器接管生命周期。开 MT5 把 desc.probability 从 0.1 调到 0.3,可能明显改变验证集波动,但外汇与贵金属训练属高风险,过拟合缓解不预示实盘收益。

MQL5 / C++
neuron_attention_ocl=NULL;
class="kw">break;
class=class="str">"cmt">//---
case defNeuronMLMHAttentionOCL:
   neuron_mlattention_ocl=new CNeuronMLMHAttentionOCL();
   if(CheckPointer(neuron_mlattention_ocl)==POINTER_INVALID)
     {
      class="kw">delete temp;
      class="kw">return;
     }
   if(!neuron_mlattention_ocl.Init(outputs,class="num">0,opencl,desc.window,desc.window_out,desc.step,desc.count,desc.layers,desc.optimization))
     {
      class="kw">delete neuron_mlattention_ocl;
      class="kw">delete temp;
      class="kw">return;
     }
   neuron_mlattention_ocl.SetActivationFunction(desc.activation);
   if(!temp.Add(neuron_mlattention_ocl))
     {
      class="kw">delete neuron_mlattention_ocl;
      class="kw">delete temp;
      class="kw">return;
     }
   neuron_mlattention_ocl=NULL;
   class="kw">break;
class=class="str">"cmt">//---
case defNeuronDropoutOCL:
   dropout=new CNeuronDropoutOCL();
   if(CheckPointer(dropout)==POINTER_INVALID)
     {
      class="kw">delete temp;
      class="kw">return;
     }
   if(!dropout.Init(outputs,class="num">0,opencl,desc.count,desc.probability,desc.optimization))
     {
      class="kw">delete dropout;
      class="kw">delete temp;
      class="kw">return;
     }
   if(!temp.Add(dropout))
     {
      class="kw">delete dropout;

◍ 神经网络层里的 dropout 与 OpenCL 内核挂载

在 MT5 的 GPU 加速神经网络实现里,dropout 作为一种正则化层被单独封装成 CNeuronDropoutOCL,并在 CLayer::CreateElement 中通过指针 temp_drop_ocl 参与元素构建。当从模型文件读取时,switch(type) 命中 defNeuron 之外的 dropout 分支才会实例化它,否则直接 delete temp 并 return,避免脏指针残留。 内核注册阶段调用 opencl.SetKernelsCount(24) 显式声明了 24 个计算内核,随后用 KernelCreate 逐一绑定名称字符串,例如 FeedForward、UpdateWeightsAdam、AttentionScore 等。其中高亮行 opencl.KernelCreate(def_k_Dropout,"Dropout") 把 dropout 的前向掩码逻辑也推到显卡执行,训练时随机置零部分神经元输出,可能降低外汇时序过拟合倾向,但 GPU 占用与显存带宽压力会随层数线性抬升。 CreateElement 开头先做边界判断 if(index>=m_data_max) return false,再声明一批临时神经元指针(含 temp_drop_ocl=NULL)。若 iFileHandle<=0 走新建分支用 new CNeuron() 初始化;否则读文件按类型还原。这一套在贵金属小时线训练上属于高风险实验,参数没调稳前回测曲线可能剧烈抖动。

MQL5 / C++
class="kw">delete temp;
class="kw">return;
             }
            dropout=NULL;
            class="kw">break;
class=class="str">"cmt">//---
            class="kw">default:
               class="kw">return;
               class="kw">break;
            }
         }
   opencl.SetKernelsCount(class="num">24);
   opencl.KernelCreate(def_k_FeedForward,"FeedForward");
   opencl.KernelCreate(def_k_CalcOutputGradient,"CalcOutputGradient");
   opencl.KernelCreate(def_k_CalcHiddenGradient,"CalcHiddenGradient");
   opencl.KernelCreate(def_k_UpdateWeightsMomentum,"UpdateWeightsMomentum");
   opencl.KernelCreate(def_k_UpdateWeightsAdam,"UpdateWeightsAdam");
   opencl.KernelCreate(def_k_AttentionGradients,"AttentionInsideGradients");
   opencl.KernelCreate(def_k_AttentionOut,"AttentionOut");
   opencl.KernelCreate(def_k_AttentionScore,"AttentionScore");
   opencl.KernelCreate(def_k_CalcHiddenGradientConv,"CalcHiddenGradientConv");
   opencl.KernelCreate(def_k_CalcInputGradientProof,"CalcInputGradientProof");
   opencl.KernelCreate(def_k_FeedForwardConv,"FeedForwardConv");
   opencl.KernelCreate(def_k_FeedForwardProof,"FeedForwardProof");
   opencl.KernelCreate(def_k_MatrixSum,"SumMatrix");
   opencl.KernelCreate(def_k_Matrix5Sum,"Sum5Matrix");
   opencl.KernelCreate(def_k_UpdateWeightsConvAdam,"UpdateWeightsConvAdam");
   opencl.KernelCreate(def_k_UpdateWeightsConvMomentum,"UpdateWeightsConvMomentum");
   opencl.KernelCreate(def_k_Normilize,"Normalize");
   opencl.KernelCreate(def_k_NormilizeWeights,"NormalizeWeights");
   opencl.KernelCreate(def_k_ConcatenateMatrix,"ConcatenateBuffers");
   opencl.KernelCreate(def_k_DeconcatenateMatrix,"DeconcatenateBuffers");
   opencl.KernelCreate(def_k_MHAttentionGradients,"MHAttentionInsideGradients");
   opencl.KernelCreate(def_k_MHAttentionScore,"MHAttentionScore");
   opencl.KernelCreate(def_k_MHAttentionOut,"MHAttentionOut");
   opencl.KernelCreate(def_k_Dropout,"Dropout");
class="type">bool CLayer::CreateElement(class="type">int index)
  {
   if(index>=m_data_max)
      class="kw">return class="kw">false;
class=class="str">"cmt">//---
   class="type">bool result=class="kw">false;
   CNeuronBase *temp=NULL;
   CNeuronProof *temp_p=NULL;
   CNeuronBaseOCL *temp_ocl=NULL;
   CNeuronConvOCL *temp_con_ocl=NULL;
   CNeuronAttentionOCL *temp_at_ocl=NULL;
   CNeuronMLMHAttentionOCL *temp_mlat_ocl=NULL;
   CNeuronDropoutOCL *temp_drop_ocl=NULL;
   if(iFileHandle<=class="num">0)
     {
      temp=new CNeuron();
      if(CheckPointer(temp)==POINTER_INVALID || !temp.Init(iOutputs,index,SGD))
         class="kw">return class="kw">false;
      result=true;
     }
   else
     {
      class="type">int type=FileReadInteger(iFileHandle);
      class="kw">switch(type)
        {
         case  defNeuron:

工厂方法里的神经元分支构造

在 MT5 的神经网络类体系里,通常用工厂分支按类型实例化具体神经元。下面这段 switch 逻辑覆盖了基础神经元、证明层、卷积、LSTM 以及 OpenCL 加速基类的创建路径,可直接拷进你自己的 CNet 派生类做对照。 普通分支先 new 出对象,立刻用 CheckPointer 判 POINTER_INVALID;若失败就把 result 置 false 并 break,避免后续对空指针调用 Init。基础神经元只传 iOutputs、index 与优化器 ADAM,不接管 OpenCL 上下文。 Proof / Conv / LSTM 三个分支结构一致:new 出 temp_p 后判指针,再调 Init(iOutputs,index,1,1,1,ADAM),成功才把 temp_p 赋给 temp 并 result=true。注意这里卷积和 LSTM 的 Init 参数写死为 1,1,1,意味着默认窗口与步长都极小,实盘前大概率要按品种波动重调。 OpenCL 分支多一道守卫:先查全局 OpenCL 指针无效就直接 return false,随后 new CNeuronBaseOCL 并 Init(iOutputs,index,OpenCL,1,ADAM),成功直接写 m_data[index] 并返回 true,不再走外层 result 变量。外汇与贵金属行情跳空频繁,GPU 加速虽降延迟,但模型过拟合风险偏高,参数须逐步验证。

MQL5 / C++
temp=new CNeuron();
if(CheckPointer(temp)==POINTER_INVALID)
   result=class="kw">false;
result=temp.Init(iOutputs,index,ADAM);
class="kw">break;
 case  defNeuronProof:
   temp_p=new CNeuronProof();
   if(CheckPointer(temp_p)==POINTER_INVALID)
      result=class="kw">false;
   if(temp_p.Init(iOutputs,index,class="num">1,class="num">1,class="num">1,ADAM))
     {
      temp=temp_p;
      result=true;
     }
   class="kw">break;
 case  defNeuronConv:
   temp_p=new CNeuronConv();
   if(CheckPointer(temp_p)==POINTER_INVALID)
      result=class="kw">false;
   if(temp_p.Init(iOutputs,index,class="num">1,class="num">1,class="num">1,ADAM))
     {
      temp=temp_p;
      result=true;
     }
   class="kw">break;
 case  defNeuronLSTM:
   temp_p=new CNeuronLSTM();
   if(CheckPointer(temp_p)==POINTER_INVALID)
      result=class="kw">false;
   if(temp_p.Init(iOutputs,index,class="num">1,class="num">1,class="num">1,ADAM))
     {
      temp=temp_p;
      result=true;
     }
   class="kw">break;
 case  defNeuronBaseOCL:
   if(CheckPointer(OpenCL)==POINTER_INVALID)
      class="kw">return class="kw">false;
   temp_ocl=new CNeuronBaseOCL();
   if(CheckPointer(temp_ocl)==POINTER_INVALID)
      result=class="kw">false;
   if(temp_ocl.Init(iOutputs,index,OpenCL,class="num">1,ADAM))
     {
      m_data[index]=temp_ocl;
      class="kw">return true;

「卷积与注意力神经元的 OpenCL 实例化分支」

在 MT5 的神经网络容器里,按类型枚举去 new 对应的 OpenCL 神经元对象,是跑 GPU 加速训练的前置动作。下面这段 switch 分支覆盖了卷积、单头注意力、多头注意力及多层多头注意力四类 OCL 神经元,每一种都先校验 OpenCL 上下文指针,无效直接返回 false。 卷积分支调用 CNeuronConvOCL 的 Init,传入的核尺寸与步长全是 1(1,1,1,1),优化器写死 ADAM;注意力类分支的 Init 参数少一个空间维度,同样是 1×1 窗口加 ADAM。若 new 出来的指针无效,就把外层 result 标为 false,但注意它没立刻 return,而是 break 后由调用方去读 result。 这种写法意味着:你在改写自己的 EA 神经网络层时,若漏了 OpenCL 上下文校验,GPU 端初始化会静默失败,回测里可能表现为某层权重不更新。外汇与贵金属杠杆交易高风险,GPU 加速只解决算力,不解决过拟合。

MQL5 / C++
                     class="kw">break;
                     case  defNeuronConvOCL:
                        if(CheckPointer(OpenCL)==POINTER_INVALID)
                           class="kw">return class="kw">false;
                        temp_con_ocl=new CNeuronConvOCL();
                        if(CheckPointer(temp_con_ocl)==POINTER_INVALID)
                           result=class="kw">false;
                        if(temp_con_ocl.Init(iOutputs,index,OpenCL,class="num">1,class="num">1,class="num">1,class="num">1,ADAM))
                          {
                           m_data[index]=temp_con_ocl;
                           class="kw">return true;
                          }
                        class="kw">break;
                     case  defNeuronAttentionOCL:
                        if(CheckPointer(OpenCL)==POINTER_INVALID)
                           class="kw">return class="kw">false;
                        temp_at_ocl=new CNeuronAttentionOCL();
                        if(CheckPointer(temp_at_ocl)==POINTER_INVALID)
                           result=class="kw">false;
                        if(temp_at_ocl.Init(iOutputs,index,OpenCL,class="num">1,class="num">1,ADAM))
                          {
                           m_data[index]=temp_at_ocl;
                           class="kw">return true;
                          }
                        class="kw">break;
                     case  defNeuronMHAttentionOCL:
                        if(CheckPointer(OpenCL)==POINTER_INVALID)
                           class="kw">return class="kw">false;
                        temp_at_ocl=new CNeuronMHAttentionOCL();
                        if(CheckPointer(temp_at_ocl)==POINTER_INVALID)
                           result=class="kw">false;
                        if(temp_at_ocl.Init(iOutputs,index,OpenCL,class="num">1,class="num">1,ADAM))
                          {
                           m_data[index]=temp_at_ocl;
                           class="kw">return true;
                          }
                        class="kw">break;
                     case  defNeuronMLMHAttentionOCL:
                        if(CheckPointer(OpenCL)==POINTER_INVALID)
                           class="kw">return class="kw">false;
                        temp_mlat_ocl=new CNeuronMLMHAttentionOCL();

◍ Dropout 层在 OpenCL 神经网络里的挂载点

在 MT5 用 OpenCL 跑神经网络时,Dropout 层不是单独存在的黑盒,而是在工厂函数里通过 case 分支动态挂进 m_data 数组。下面这段工厂代码里,defNeuronDropoutOCL 分支先校验 OpenCL 上下文指针有效,再 new 一个 CNeuronDropoutOCL 对象,Init 参数中 0.1 就是丢弃概率——意味着前向传播时约 10% 的神经元输出会被随机置零,这是抑制过拟合的常用比例。 FeedForward 的 switch 同样要把 defNeuronDropoutOCL 列进可接收的上游类型,否则该层收不到前一层的 CNeuronBaseOCL 指针,直接返回 false 导致整图前向中断。calcHiddenGradients 里也声明了 CNeuronDropoutOCL *dropout 变量,说明反向传播时该层需要单独处理梯度掩码。 实际调参时,把 Init 里的 0.1 改成 0.2 或 0.05 能在 MT5 策略测试器里看到训练曲线平滑度变化;外汇与贵金属杠杆高、滑点无常,这类模型仅作概率参考,实盘前务必用历史数据跑通前向与反向链路。

MQL5 / C++
if(CheckPointer(temp_mlat_ocl)==POINTER_INVALID)
   result=class="kw">false;
if(temp_mlat_ocl.Init(iOutputs,index,OpenCL,class="num">1,class="num">1,class="num">1,class="num">1,class="num">0,ADAM))
  {
   m_data[index]=temp_mlat_ocl;
   class="kw">return true;
  }
class="kw">break;
   case  defNeuronDropoutOCL:
      if(CheckPointer(OpenCL)==POINTER_INVALID)
       class="kw">return class="kw">false;
      temp_drop_ocl=new CNeuronDropoutOCL();
      if(CheckPointer(temp_drop_ocl)==POINTER_INVALID)
       result=class="kw">false;
      if(temp_drop_ocl.Init(iOutputs,index,OpenCL,class="num">1,class="num">0.1,ADAM))
        {
         m_data[index]=temp_drop_ocl;
         class="kw">return true;
        }
      class="kw">break;
     class="kw">default:
      result=class="kw">false;
      class="kw">break;
     }
   }
 if(result)
   m_data[index]=temp;
class=class="str">"cmt">//---
  class="kw">return (result);
  }
class="type">bool CNeuronBaseOCL::FeedForward(CObject *SourceObject)
  {
  if(CheckPointer(SourceObject)==POINTER_INVALID)
   class="kw">return class="kw">false;
class=class="str">"cmt">//---
  CNeuronBaseOCL *temp=NULL;
  class="kw">switch(SourceObject.Type())
   {
    case defNeuronBaseOCL:
    case defNeuronConvOCL:
    case defNeuronAttentionOCL:
    case defNeuronMHAttentionOCL:
    case defNeuronMLMHAttentionOCL:
    case defNeuronDropoutOCL:
      temp=SourceObject;
      class="kw">return feedForward(temp);
      class="kw">break;
   }
class=class="str">"cmt">//---
  class="kw">return class="kw">false;
  }
class="type">bool CNeuronBaseOCL::calcHiddenGradients(CObject *TargetObject)
  {
  if(CheckPointer(TargetObject)==POINTER_INVALID)
   class="kw">return class="kw">false;
class=class="str">"cmt">//---
  CNeuronBaseOCL *temp=NULL;
  CNeuronAttentionOCL *at=NULL;
  CNeuronMLMHAttentionOCL *mlat=NULL;
  CNeuronConvOCL *conv=NULL;
  CNeuronDropoutOCL *dropout=NULL;
  class="kw">switch(TargetObject.Type())
   {
    case defNeuronBaseOCL:

反向传播里如何把梯度甩给 Dropout 层

在自定义神经网络基类里,反向传播要靠 SourceObject 的类型分支把梯度计算派发给不同神经元。原代码用 switch 把 defNeuronDropoutOCL 也纳入了派发路径,说明 Dropout 层在训练阶段同样参与梯度回传,而不是只在正向丢神经元。 [CODE] temp=TargetObject; return calcHiddenGradients(temp); break; case defNeuronConvOCL: conv=TargetObject; temp=GetPointer(this); return conv.calcInputGradients(temp); break; case defNeuronAttentionOCL: case defNeuronMHAttentionOCL: at=TargetObject; temp=GetPointer(this); return at.calcInputGradients(temp); break; case defNeuronMLMHAttentionOCL: mlat=TargetObject; temp=GetPointer(this); return mlat.calcInputGradients(temp); break; case defNeuronDropoutOCL: dropout=TargetObject; temp=GetPointer(this); return dropout.calcInputGradients(temp); break; } //--- return false; } bool CNeuronBaseOCL::UpdateInputWeights(CObject *SourceObject) { if(CheckPointer(SourceObject)==POINTER_INVALID) return false; //--- CNeuronBaseOCL *temp=NULL; switch(SourceObject.Type()) { case defNeuronBaseOCL: case defNeuronConvOCL: case defNeuronAttentionOCL: case defNeuronMHAttentionOCL: case defNeuronMLMHAttentionOCL: case defNeuronDropoutOCL: temp=SourceObject; return updateInputWeights(temp); break; } //--- return false; } [/CODE] 逐行看:temp=TargetObject 是把目标层指针接住;GetPointer(this) 则把当前层自身指针传给对方层的 calcInputGradients,让卷积、注意力、Dropout 各自算输入梯度。UpdateInputWeights 里把 Dropout 和基类等并列,统一调 updateInputWeights(temp),少写一套重载。 在 MT5 里跑这套网络时,若你发现自己加的 Dropout 层完全不更新权重,先查 switch 分支有没有把 defNeuronDropoutOCL 列进去——漏了这行,return false 会直接截断回传。外汇与贵金属模型训练波动大,漏梯度可能让过拟合风险升高,建议用 EURUSD 的 M15 样本先验证回传链是否闭合。

MQL5 / C++
   temp=TargetObject;
   class="kw">return calcHiddenGradients(temp);
   class="kw">break;
   case defNeuronConvOCL:
   conv=TargetObject;
   temp=GetPointer(this);
   class="kw">return conv.calcInputGradients(temp);
   class="kw">break;
   case defNeuronAttentionOCL:
   case defNeuronMHAttentionOCL:
   at=TargetObject;
   temp=GetPointer(this);
   class="kw">return at.calcInputGradients(temp);
   class="kw">break;
   case defNeuronMLMHAttentionOCL:
   mlat=TargetObject;
   temp=GetPointer(this);
   class="kw">return mlat.calcInputGradients(temp);
   class="kw">break;
   case defNeuronDropoutOCL:
   dropout=TargetObject;
   temp=GetPointer(this);
   class="kw">return dropout.calcInputGradients(temp);
   class="kw">break;
   }
class=class="str">"cmt">//---
   class="kw">return class="kw">false;
   }
class="type">bool CNeuronBaseOCL::UpdateInputWeights(CObject *SourceObject)
  {
  if(CheckPointer(SourceObject)==POINTER_INVALID)
    class="kw">return class="kw">false;
class=class="str">"cmt">//---
  CNeuronBaseOCL *temp=NULL;
  class="kw">switch(SourceObject.Type())
    {
    case defNeuronBaseOCL:
    case defNeuronConvOCL:
    case defNeuronAttentionOCL:
    case defNeuronMHAttentionOCL:
    case defNeuronMLMHAttentionOCL:
    case defNeuronDropoutOCL:
      temp=SourceObject;
      class="kw">return updateInputWeights(temp);
      class="kw">break;
    }
class=class="str">"cmt">//---
  class="kw">return class="kw">false;
  }

「在 EURUSD H1 上对比 dropout 的实盘回测」

为了压住外部变量,测试直接复用同一套 EA 结构,只在四个位置插了舍弃层:初始数据后、嵌入代码后、关注模块后、全连接层后各一层。所有架构都喂入 EURUSD H1 的最后 20 根烛条,相似数据集下比性能才公平。 前 30 个迭代里,带 dropout 和不带 dropout 的误差曲线几乎平行,此时无舍弃的网反而数值更好看。转折出现在第 33 迭代,带舍弃的 EA 误差开始下探;到第 35 迭代后,它稳定跑出更低误差且仍有下降趋势,而无舍弃网把误差焊死在同一水平。 错失形态图更直白:带 dropout 的 EA 立刻显出差距收窄倾向,无舍弃网却让错失区域慢慢膨胀。预测命中两张图靠得很近,训练 44 迭代后,带舍弃的 EA 只比对手多捞了 0.5% 命中率——提升不大,但泛化倾向更稳。 外汇与贵金属属高风险品种,上述回测仅反映历史样本表现,实盘可能偏离。下面这段是插了两层 dropout 的网结构核心,黄底两段即舍弃层。

MQL5 / C++
      class=class="str">"cmt">//--- class="num">0
      CLayerDescription *desc=new CLayerDescription();
      if(CheckPointer(desc)==POINTER_INVALID)
         class="kw">return INIT_FAILED;
      desc.count=(class="type">int)HistoryBars*class="num">12;
      desc.type=defNeuronBaseOCL;
      desc.optimization=ADAM;
      desc.activation=TANH;
      if(!Topology.Add(desc))
         class="kw">return INIT_FAILED;
      class=class="str">"cmt">//--- class="num">1
      desc=new CLayerDescription();
      if(CheckPointer(desc)==POINTER_INVALID)
         class="kw">return INIT_FAILED;
      desc.count=(class="type">int)HistoryBars*class="num">12;
      desc.type=defNeuronDropoutOCL;
      desc.probability=class="num">0.2;
      desc.optimization=ADAM;
      desc.activation=TANH;
      if(!Topology.Add(desc))
         class="kw">return INIT_FAILED;
      class=class="str">"cmt">//--- class="num">2
      desc=new CLayerDescription();
      if(CheckPointer(desc)==POINTER_INVALID)
         class="kw">return INIT_FAILED;
      desc.count=(class="type">int)HistoryBars;
      desc.type=defNeuronConvOCL;
      desc.window=class="num">12;
      desc.step=class="num">12;
      desc.window_out=class="num">24;
      desc.optimization=ADAM;
      desc.activation=SIGMOID;
      if(!Topology.Add(desc))
         class="kw">return INIT_FAILED;
      class=class="str">"cmt">//--- class="num">3
      desc=new CLayerDescription();
      if(CheckPointer(desc)==POINTER_INVALID)
         class="kw">return INIT_FAILED;
      desc.count=(class="type">int)HistoryBars;
      desc.type=defNeuronDropoutOCL;
      desc.probability=class="num">0.2;
      desc.optimization=ADAM;
      desc.activation=SIGMOID;
      if(!Topology.Add(desc))
         class="kw">return INIT_FAILED;
      class=class="str">"cmt">//--- class="num">4
      desc=new CLayerDescription();
      if(CheckPointer(desc)==POINTER_INVALID)
         class="kw">return INIT_FAILED;
      desc.count=(class="type">int)HistoryBars;
      desc.type=defNeuronMLMHAttentionOCL;
      desc.window=class="num">24;
      desc.window_out=class="num">4;
      desc.step=class="num">8;   class=class="str">"cmt">//heads
      desc.layers=class="num">5;
      desc.optimization=ADAM;
      desc.activation=SIGMOID;

◍ 堆叠带 Dropout 的隐层与输出层

这段初始化逻辑在往 Topology 里连续塞 5 层网络描述,每一层都先 new 一个 CLayerDescription,用 CheckPointer 判空,失败直接 INIT_FAILED,避免悬空指针带崩后续训练。 第 5 层是 Dropout 变体(defNeuronDropoutOCL),神经元数量取 HistoryBars 的整型值,丢弃概率写死 0.2,激活用 SIGMOID、优化用 ADAM;它和第 6 层形成对照——第 6 层是普通 defNeuron,固定 200 个节点、TANH 激活。 第 7 层又回到 Dropout 结构,200 节点、概率 0.2、TANH,和第 8 层普通 200 节点 TANH 层交错。最后第 9 层是输出层,只有 3 个 SIGMOID 节点,适合做三分类信号(如多 / 空 / 观望)。外汇与贵金属波动剧烈,这类网络只是概率输出,实盘前务必在 MT5 策略测试器里跑样本外验证。 调参时先把 0.2 的 dropout 概率改到 0.3 试一轮,若过拟合明显再动隐层节点数;直接改代码里的 desc.probability 即可,不用重构整体拓扑。

MQL5 / C++
   if(!Topology.Add(desc))
      class="kw">return INIT_FAILED;
   class=class="str">"cmt">//--- class="num">5
   desc=new CLayerDescription();
   if(CheckPointer(desc)==POINTER_INVALID)
      class="kw">return INIT_FAILED;
   desc.count=(class="type">int)HistoryBars;
   desc.type=defNeuronDropoutOCL;
   desc.probability=class="num">0.2;
   desc.optimization=ADAM;
   desc.activation=SIGMOID;
   if(!Topology.Add(desc))
      class="kw">return INIT_FAILED;
   class=class="str">"cmt">//--- class="num">6
   desc=new CLayerDescription();
   if(CheckPointer(desc)==POINTER_INVALID)
      class="kw">return INIT_FAILED;
   desc.count=class="num">200;
   desc.type=defNeuron;
   desc.activation=TANH;
   desc.optimization=ADAM;
   if(!Topology.Add(desc))
      class="kw">return INIT_FAILED;
   class=class="str">"cmt">//--- class="num">7
   desc=new CLayerDescription();
   if(CheckPointer(desc)==POINTER_INVALID)
      class="kw">return INIT_FAILED;
   desc.count=class="num">200;
   desc.type=defNeuronDropoutOCL;
   desc.probability=class="num">0.2;
   desc.optimization=ADAM;
   desc.activation=TANH;
   if(!Topology.Add(desc))
      class="kw">return INIT_FAILED;
   class=class="str">"cmt">//--- class="num">8
   desc=new CLayerDescription();
   if(CheckPointer(desc)==POINTER_INVALID)
      class="kw">return INIT_FAILED;
   desc.count=class="num">200;
   desc.type=defNeuron;
   desc.activation=TANH;
   desc.optimization=ADAM;
   if(!Topology.Add(desc))
      class="kw">return INIT_FAILED;
   class=class="str">"cmt">//--- class="num">9
   desc=new CLayerDescription();
   if(CheckPointer(desc)==POINTER_INVALID)
      class="kw">return INIT_FAILED;
   desc.count=class="num">3;
   desc.type=defNeuron;
   desc.activation=SIGMOID;
   desc.optimization=ADAM;

把舍弃法塞进你已有的 EA 里试一轮

前面几节我们把舍弃(dropout)接进了原先的 MQL5 智能交易系统,EA 回测里能直接看到收敛稳定性比不加时更耐造。代价是神经网络训练耗时往上走,不过实测中被最终结果质量提升盖过去了。 外汇和贵金属杠杆高、滑点跳空频繁,任何训练优化都只是概率层面的改善,不是免死金牌。 找你手上跑得最别扭的那个 EA,把 dropout 层参数从 0 调到 0.2 附近,跑一遍 2023 年 XAUUSD 的 H1 样本,对比训练损失曲线就知道值不值。

「顺着这条线继续深挖」

上面列的这些篇目,基本把从基础卷积、循环结构到 OpenCL 多线程、自适应优化乃至自 GPT 获取的一整条 MQL5 神经网络实践链路铺开了。如果你已经在 MT5 里跑过前几篇的示范 EA,建议直接按这个顺序把第十、十一部关于多目击者关注和自 GPT 获取的源码拉下来对照编译。 其中「通过防止特征检测器的协同适应来改善神经网络」这一篇,核心在于 dropout 思路在 MQL5 端的落地——训练时随机屏蔽部分神经元,验证集误差往往比不加约束的版本低 3%~8%(视特征维度而定)。外汇与贵金属行情序列信噪比低,直接套用易过拟合,高风险环境下更该用这套方法压住泛化缺口。 统计评估那节则给出回测样本外的夏普波动对照,数字虽不保证实盘复现,但足以判断哪类网络结构在 XAUUSD 的 H1 上更耐打。打开终端把对应 mqh 丢进 Include 目录,先本地跑一轮再谈调参。

◍ 把这条线请下神坛

这套 GTP 分类网络 + 5 关注层的 EA 在附件里拆成了五个实体:主程序 Fractal_OCL_AttentionMLMH.mq5、带舍弃逻辑的 _d 版本、NeuroNet.mqh 类库、NeuroNet.cl 的 OpenCL 核,以及 NN.chm 离线帮助。ZIP 体积 2459.71 KB,直接丢进 MT5 的 MQL5 目录就能编译,但评论区已有人实测:训练时若算法报未定义信号,遗漏模式占比会冲到 100%,换历时才回落。 OpenCL 依赖显卡或多核 CPU 的运行时驱动,老卡用户跑 _d 版本基本只能当处理器软解,速度惨但能验证逻辑。窗口=3、步长=1 的卷积设定,本质是滑动 3 根 K 线搜分形形态,相关值向量就是这么挤出来的。 外汇和贵金属自带高杠杆高风险,这类神经网络 EA 只是概率模型,回测亮眼不等于实盘能活。下掉神坛的意思很简单:开 MT5 把代码跑一遍,看遗漏率什么时候爆,比信任何「框架很牛」的叙事都实在。

常见问题

通常挂在全连接层或注意力层输出之后,训练时按概率屏蔽神经元,推理时关闭即可避免随机扰动。
在工厂方法里按类型标识走不同分支,卷积和注意力各自实例化对应的 OpenCL 内核,Dropout 单独挂一个轻量内核。
可以,把网络结构和内核代码发给小布,它能对照常见挂载点帮你标出梯度回传遗漏或推理未关闭 Dropout 的问题。
训练阶段按前向掩码把梯度只回传未被丢弃的神经元,推理阶段跳过掩码直接透传,否则权重更新会偏。
主要坑在内存布局和线程索引,卷积用局部共享内存、注意力用全局规约,分支写混会导致内核启动崩溃或结果全零。