神经网络变得轻松(第三十七部分):分散关注度·综合运用
📘

神经网络变得轻松(第三十七部分):分散关注度·综合运用

第 3/3 篇

用 EURUSD H1 跑通分散关注度层

把新写的分散关注度神经层接进 EA 后,直接在 MT5 策略测试器里用历史数据训模型最省事。这次拿前一篇的关系强化学习 EA 当底子,只把模型里的关注度模块换成新层,前向和逆向两个子模型原封不动。 原架构里的 LSTM 递归模块我删了,改成全连接层,模型输入是每根历史柱 12 个特征加账户状态 9 个特征。压缩后的序列被切成 20 个区块、每块 5 元素,用 4 个关注者各挑 30% 最重要元素,连续两层同参数跑完再进分位数函数决策模块,EA 输出买、卖、全平、不动四种动作。 训练样本取 2023 年 3 月 EURUSD H1,学习期账面盈利,但赢亏单数几乎一样,利润全靠平均盈利单大于平均亏损单撑着。最终盈利因子 1.12、恢复因子 1.01,外汇高风险下这种薄优势随时可能被点差和滑点吃掉。 下面这段是 CreateDescriptions 里模型前 5 层的拼装逻辑,逐行看清楚张维和激活怎么叠的: //--- Model 注释:模型描述开始 Description.Clear(); 清空层描述容器 CLayerDescription *descr; 声明层描述指针 //--- Input layer 注释:输入层 if(!(descr = new CLayerDescription())) 新建层描述失败则 return false; 直接返回失败 descr.type = defNeuronBaseOCL; 设为基础神经元层(OpenCL) descr.count = (int)(HistoryBars * 12 + 9); 输入维度=历史柱数*12+账户9维 prev_count = descr.count; 记录上一层输出数 descr.window = 0; 无窗口 Descr.activation = None; 无激活 descr.optimization = ADAM; 用 ADAM 优化 if(!Description.Add(descr)) 加入容器失败则 { delete descr; return false; } 释放并返回失败 //--- layer 1 批归一化层 if(!(descr = new CLayerDescription())) return false; descr.type = defNeuronBatchNormOCL; 批归一化层 descr.count = prev_count; 维度同输入 descr.batch = 1000; 批大小 1000 descr.activation = None; 无激活 descr.optimization = ADAM; 优化器 ADAM if(!Description.Add(descr)) { delete descr; return false; } //--- layer 2 卷积层 descr.type = defNeuronConvOCL; 卷积层 descr.count = prev_count - 2; 输出数减 2 descr.window = 3; 核宽 3 descr.step = 1; 步长 1 descr.window_out = 6; 输出窗口 6 descr.activation = LReLU; 泄漏 ReLU descr.optimization = ADAM; //--- layer 3 全连接 descr.type = defNeuronBaseOCL; 基础层 descr.count = 100; 压到 100 维 descr.optimization = ADAM; //--- layer 4 卷积 descr.type = defNeuronConvOCL; 卷积层 descr.count = 50; 输出 50 descr.window = 2; 核宽 2 descr.step = 2; 步长 2 descr.window_out = 4; 输出窗口 4 descr.activation = LReLU; //--- layer 5 全连接 descr.type = defNeuronBaseOCL; 基础层 descr.count = 100; 再回到 100 维

MQL5 / C++
class=class="str">"cmt">//--- Model
   Description.Clear();
   CLayerDescription *descr;
class=class="str">"cmt">//--- Input layer
   if(!(descr = new CLayerDescription()))
      class="kw">return false;
   descr.type = defNeuronBaseOCL;
   class="type">int prev_count = descr.count = (class="type">int)(HistoryBars * class="num">12 + class="num">9);
   descr.window = class="num">0;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!Description.Add(descr))
      {
         class="kw">delete descr;
         class="kw">return false;
      }
class=class="str">"cmt">//--- layer class="num">1
   if(!(descr = new CLayerDescription()))
      class="kw">return false;
   descr.type = defNeuronBatchNormOCL;
   descr.count = prev_count;
   descr.batch = class="num">1000;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!Description.Add(descr))
      {
         class="kw">delete descr;
         class="kw">return false;
      }
class=class="str">"cmt">//--- layer class="num">2
   if(!(descr = new CLayerDescription()))
      class="kw">return false;
   descr.type = defNeuronConvOCL;
   descr.count = prev_count - class="num">2;
   descr.window = class="num">3;
   descr.step = class="num">1;
   descr.window_out = class="num">6;
   descr.activation = LReLU;
   descr.optimization = ADAM;
   if(!Description.Add(descr))
      {
         class="kw">delete descr;
         class="kw">return false;
      }
class=class="str">"cmt">//--- layer class="num">3
   if(!(descr = new CLayerDescription()))
      class="kw">return false;
   descr.type = defNeuronBaseOCL;
   descr.count = class="num">100;
   descr.optimization = ADAM;
   if(!Description.Add(descr))
      {
         class="kw">delete descr;
         class="kw">return false;
      }
class=class="str">"cmt">//--- layer class="num">4
   if(!(descr = new CLayerDescription()))
      class="kw">return false;
   descr.type = defNeuronConvOCL;
   descr.count = class="num">50;
   descr.window = class="num">2;
   descr.step = class="num">2;
   descr.window_out = class="num">4;
   descr.activation = LReLU;
   descr.optimization = ADAM;
   if(!Description.Add(descr))
      {
         class="kw">delete descr;
         class="kw">return false;
      }
class=class="str">"cmt">//--- layer class="num">5
   if(!(descr = new CLayerDescription()))
      class="kw">return false;
   descr.type = defNeuronBaseOCL;
   descr.count = class="num">100;
   descr.optimization = ADAM;
   if(!Description.Add(descr))
      {
         class="kw">delete descr;

「稀疏注意力与分位层的具体参数落地」

在 MT5 的神经网络描述对象里,第六层用稀疏注意力结构承接前层输出,代码里把节点数设成 20、输入窗口 5、步长 4、输出窗口 8、堆叠 2 层,丢弃概率 0.3,优化器走 ADAM。这类配置在 EURUSD 的 M15 回测中,倾向让注意力只聚焦局部序列,显存占用比全注意力低一个量级。 第七层直接挂分位回归(FQF),节点数压到 4、输出窗口扩到 32,同样用 ADAM。注意 Description.Add 失败时必须 delete 掉 descr 否则会漏内存,这种细节在实盘跑高频训练时容易把终端拖崩。 把上面两段抄进你自己的 CLayerDescription 初始化函数,改 window 和 step 就能验证不同片段长度对贵金属信号延迟的影响,外汇与贵金属杠杆高,参数随便上实盘属于高风险动作。

MQL5 / C++
   class="kw">return false;
   }
class=class="str">"cmt">//--- layer class="num">6
   if(!(descr = new CLayerDescription()))
      class="kw">return false;
   descr.type = defNeuronMLMHSparseAttentionOCL;
   descr.count = class="num">20;
   descr.window = class="num">5;
   descr.step = class="num">4;
   descr.window_out = class="num">8;
   descr.layers = class="num">2;
   descr.probability = class="num">0.3f;
   descr.optimization = ADAM;
   if(!Description.Add(descr))
      {
       class="kw">delete descr;
       class="kw">return false;
      }
class=class="str">"cmt">//--- layer class="num">7
   if(!(descr = new CLayerDescription()))
      class="kw">return false;
   descr.type = defNeuronFQF;
   descr.count = class="num">4;
   descr.window_out = class="num">32;
   descr.optimization = ADAM;
   if(!Description.Add(descr))
      {
       class="kw">delete descr;
       class="kw">return false;
      }

◍ 实盘前必须补的三件事

回测里这套带分散关注度机制的类库确实跑出了盈利,说明用它搭交易方案不是死路,但样本只覆盖历史数据,没经过点差滑点实盘摩擦。 真要上 MT5 实盘,得先测它对剧烈波动的抵抗力,光看历史 equity 曲线不够,得用不同年份行情重跑。 超参数现在还是粗调,建议把学习率、窗口长度、注意力头数逐个网格搜一遍,才有可能逼近较优解。外汇与贵金属杠杆高,任何模型实盘都可能连续回撤,先搞清原理再谈资金。 这套机制本身能当构件用,但别直接把测试版丢进 live 账户。

顺藤摸瓜的延伸阅读线索

想把 MQL5 里的神经网络真正跑顺,单看某一节代码不够,得顺着同一套知识体系往上补。原文列过一组关联资料:从「使用分散变换器生成长序列」「关注度就是您所需要的全部」,到「神经网络变得轻松」第八、十、十一、三十五、三十六部分,分别覆盖了注意力机制、多关注者结构、自 GPT 表征、内在好奇心模块与关系强化学习。 这些篇目不是装饰,而是同一技术树的枝干。比如第三十五部分的内在好奇心模块,直接决定智能体在外汇分钟级行情里探索未知状态的概率,而不只是复述历史形态。 打开 MT5 的 MQL5 向导新建一个神经网络 EA,先只接第十一部分的自 GPT 特征提取层,用 EURUSD 的 M15 历史跑一轮;若样本外拟合崩了,再回头翻第三十五部分调好奇心权重,比盲目加层数有用。贵金属与外汇杠杆交易高风险,任何模块改动都先用极小手数验证。

「随包附带的四个模块」

这套 LSTM 优化方案落地到 MT5,真正要拷进终端的只有四个文件。SparseRL-learning.mq5 是跑训练的 EA 主体;ICM.mqh 管模型组织的类库;NeuroNet.mqh 负责搭神经网络结构;NeuroNet.cl 则是 OpenCL 侧的实际计算代码,压缩包 MQL5.zip 约 207.29 KB。 读者在 build 3661 环境实测时踩过两个坑:一是英特尔 UHD 730 上跑出 'NeuroNet.mqh' 2913 行 invalid pointer access,根因是核显不支持 fp64;二是 Nvidia 显卡上报 MatrixSum: unknown OpenCL error 132640,作者本人没用过 N 卡暂未复现。换支持双精度浮点的 GPU 或改用 CPU 计算后端,大概率能避开这两类初始化崩溃。 外汇与贵金属行情受杠杆和跳空影响,神经网络预测仅提供概率倾向,实盘前务必在策略测试器用历史数据验证,高风险自担。

常见问题

原文示例用 64 根 H1 K 线做注意力窗口,分位层切 8 段;可先照此跑,再按品种波动微调。
阈值过高会丢掉多数弱信号,只剩极少数关注点,容易滞后;建议从 0.1 起小步试。
可以,小布盯盘的 AIGC 已内置注意力分散诊断,打开 EURUSD 品种页即可直接看信号分布。
补样本外验证、成交滑点估算、以及分位层边界在极端行情的重算,三者缺一会放大风险。
先啃分位层模块,它决定信号切分逻辑,其余三层都挂在它的输出上,外汇贵金属高风险需谨慎。