神经网络变得简单(第 93 部分):频域和时域中的自适应预测(终篇)·综合运用
📘

神经网络变得简单(第 93 部分):频域和时域中的自适应预测(终篇)·综合运用

第 3/3 篇

◍ 反向传播里的频域与时序权重回传

这段 CNeuronATFNetOCL 的反向更新逻辑,把梯度先送回频域注意力块,再沿时间序列块逐层回传。注意 SumAndNormilize 末尾的 0.5 不是学习率,而是梯度归一化的缩放系数,改它直接影响权重震荡幅度。 F-Block 调用 cFreqAtteention.UpdateInputWeights,把 cNormFreqComplex 作为上游对象;T-Block 则从 cPositionEncoder 拿梯度源。这两步一旦返回 false,整个 updateInputWeights 立即中断,不继续跑后面的注意力与投影层。 后面两个 for 循环分别遍历 caAttention 和 caProjection 容器,用 prev 指针串联前一层输出。total 由 Total() 动态取,意味着增减注意力头数不用改这段循环代码,但每层 UpdateInputWeights 失败都会直接 return false,训练时某层显存不足会静默停在这一级。 在 MT5 里把这段贴进自定义神经网络类的 cpp,把 0.5 调到 0.3 对比回测,可能看到欧元兑美元 1H 上过拟合速度放缓,但外汇与贵金属自带高杠杆风险,参数改动仅作技术验证。

MQL5 / C++
   class="kw">return false;
  if(!FFT(GetPointer(cInputFreqRe), GetPointer(cInputFreqIm),
GetPointer(cOutputTimeSeriasRe), GetPointer(cOutputTimeSeriasIm), false))
     class="kw">return false;
  if(!DeConcat(GetPointer(cInputFreqRe), GetPointer(cOutputTimeSeriasIm), GetPointer(cOutputTimeSeriasRe),
iHistory, iFFT-iHistory, iVariables))
     class="kw">return false;
  if(!CNeuronBaseOCL::SumAndNormilize(NeuronOCL.getGradient(), GetPointer(cInputFreqRe),
NeuronOCL.getGradient(), iHistory*iVariables, false, class="num">0, class="num">0, class="num">0, class="num">0.5))
     class="kw">return false;
class=class="str">"cmt">//---
   class="kw">return true;
  }
class="type">bool CNeuronATFNetOCL::updateInputWeights(CNeuronBaseOCL *NeuronOCL)
  {
class=class="str">"cmt">//--- F-Block
  if(!cFreqAtteention.UpdateInputWeights(cNormFreqComplex.AsObject()))
     class="kw">return false;
class=class="str">"cmt">//--- T-Block
  if(!cPatching.UpdateInputWeights(cPositionEncoder.AsObject()))
     class="kw">return false;
  class="type">int total = caAttention.Total();
  CNeuronBaseOCL *prev = cPatching.AsObject();
  for(class="type">int i = class="num">0; i < total; i++)
    {
      CNeuronBaseOCL *att = caAttention.At(i);
      if(!att.UpdateInputWeights(prev))
         class="kw">return false;
      prev = att;
    }
  total = caProjection.Total();
  for(class="type">int i = class="num">0; i < total; i++)
    {
      CNeuronBaseOCL *proj = caProjection.At(i);
      if(!proj.UpdateInputWeights(prev))
         class="kw">return false;
      prev = proj;
    }
class=class="str">"cmt">//---
   class="kw">return true;
  }

编码器里塞进 ATFNet 层

把 ATFNet 直接当神经层用并不现实,这个类本身已经是一个完整模型骨架。实际做法是在环境状态编码器内部新增一层,把原始输入(按状态向量拼好的序列)直接喂进去,不再做额外归一化——T 模块和 F 模块内部已经各自带了归一化逻辑,外头重复做只会引入偏差。 喂之前先转置输入,目的是让幺正时间序列能被正确解析。转置后接入新类,这一层基本就承载了整个预测模型,输出即是计划深度内所需的全部预测值,最后再转置回目标维度即可。 频谱一致性靠 FreDF 方法兜底,Actor-Critic 两套模型保持原样不动,训练与测试流程也沿用前文附件里的代码。外汇与贵金属杠杆高,模型预测仅提供概率倾向,实盘前务必在 MT5 用历史数据回测验证。 下面这段 CreateEncoderDescriptions 是编码器搭建的核心,逐行看: bool CreateEncoderDescriptions(CArrayObj *encoder) — 函数返回布尔,入参是编码器层描述对象数组指针。 if(!encoder) 判断指针为空则 new 一个 CArrayObj,再失败就 return false。 encoder.Clear() 清空旧描述,开始叠层。 第一层 defNeuronBaseOCL:输入层,节点数 = HistoryBars * BarDescr,无激活,优化器 ADAM。 第二层 defNeuronTransposeOCL:转置层,count=HistoryBars、window=BarDescr,把输入矩阵转置。 第三层 defNeuronATFNetOCL:核心层,count=BarDescr、window=HistoryBars、window_out=NForecast,step=8、layers=4,内部窗口数组 {5,1,16},batch=10000。 第四层 defNeuronTransposeOCL:再次转置,count=BarDescr、window=NForecast。 第五层 defNeuronFreDFOCL:频谱校准,window=BarDescr、count=NForecast、probability=0.8f,优化器 ADAM。每层 Add 失败均 delete 并返回 false,最终 return true。

MQL5 / C++
class="type">bool CreateEncoderDescriptions(CArrayObj *encoder)
  {
class=class="str">"cmt">//---
   CLayerDescription *descr;
class=class="str">"cmt">//---
   if(!encoder)
     {
       encoder = new CArrayObj();
       if(!encoder)
         class="kw">return false;
     }
class=class="str">"cmt">//--- Encoder
   encoder.Clear();
class=class="str">"cmt">//--- Input layer
   if(!(descr = new CLayerDescription()))
      class="kw">return false;
   descr.type = defNeuronBaseOCL;
   class="type">int prev_count = descr.count = (HistoryBars * BarDescr);
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.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 = defNeuronTransposeOCL;
   descr.count = HistoryBars;
   descr.window = BarDescr;
   if(!encoder.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 = defNeuronATFNetOCL;
   descr.count = BarDescr;
   descr.window = HistoryBars;
   descr.window_out = NForecast;
   descr.step = class="num">8;
   descr.layers = class="num">4;
     {
       class="type">int temp[] = {class="num">5, class="num">1, class="num">16};
       ArrayCopy(descr.windows, temp);
     }
   descr.activation = None;
   descr.batch = class="num">10000;
   if(!encoder.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 = defNeuronTransposeOCL;
   descr.count = BarDescr;
   descr.window = NForecast;
   descr.activation = None;
   if(!encoder.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 = defNeuronFreDFOCL;
   descr.window = BarDescr;
   descr.count =  NForecast;
   descr.step = class="type">int(false);
   descr.probability = class="num">0.8f;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
     {
       class="kw">delete descr;
       class="kw">return false;
     }
class=class="str">"cmt">//---
   class="kw">return true;
  }

「EURUSD 上跑通的训练与测试」

模型训练直接复用早前 EA 采集的数据,不再重新拉样本。训练集锁定 2023 年 H1 的 EURUSD 历史,先把编码器在 NForecast 规定的横向范围内训好,只吃价格走势,这一阶段训练集不动。 第二阶段才进演员-评论者交替迭代,每轮刷新数据集,让环境奖励贴合演员当下策略,再反推更优动作政策。外汇与贵金属杠杆高,这类强化学习策略过拟合风险不容忽视。 最终演员政策在训练集和测试集都跑了正收益。测试期共成交 31 笔,19 笔盈利,胜率 61% 出头;多空开仓 15 比 16,基本对称,说明不是靠单边暴露刷出来的。 想复现的话,开 MT5 用同周期 EURUSD 数据跑一遍两阶段训练,重点看测试集多空比是否仍接近 1:1,偏离过大往往预示样本外退化。

◍ ATFNet 在 MT5 上的落地与潜力边界

前面两篇把 ATFNet 的构造讲透了:它用 T-模块抓时域局部依赖,F-模块在频域拆周期,靠主谐波序列能量加权、扩展傅里叶变换和复数频谱关注来扛频率偏移。这套组合不是纯论文玩具。 我们在 MQL5 里用真实行情做了训练与测试,模型跑通了端到端流程。回测样本显示,它所生成的信号结构具备构造可盈利策略的倾向,但外汇与贵金属属高风险品种,实盘前必须自验。 值得提醒的是,论文里的「长期预测」和盯盘用的短周期信号不是一回事,直接把权重丢进 EA 可能过拟合。 真要验证,开 MT5 用你自己的品种重训一遍,比盯论文结论有用。

随包附带的八个文件

这套 LSTM 预测方案不是只给思路,作者把整套工程文件都打进了 MQL5.zip(约 1298.75 KB)随文发布。里面真正跑起来的核心是 5 个 EA:Research.mq5 负责采集样本,ResearchRealORL.mq5 用 Real-ORL 方法补样本,Study.mq5 训模型,StudyEncoder.mq5 训编码器,Test.mq5 做模型测试。 剩下三个是底层依赖:Trajectory.mqh 定义系统状态结构,NeuroNet.mqh 是建神经网络的类库,NeuroNet.cl 则是 OpenCL 端的算子代码。想在 MT5 里复现,直接把 zip 解到 MQL5 目录就能编译,不用自己拼框架。 不过评论区有个坑值得留意:有用户反馈 bd 数据文件涨到 2 GB 附近就开始写坏,18 条轨迹就接近 500 MB、30 条约 700 MB,作者本人也没给出根治办法,只说自己在用更大的文件。外汇和贵金属行情高波动、高杠杆,这类内存边界问题可能让你训练中断,上实盘前务必先在小额历史段验证文件读写稳定性。

常见问题

不用硬拆。终篇把频域与时序分支在编码器层融合后再统一回传,能减少梯度冲突,实盘更容易跑稳。
放在特征提取之后、预测头之前。原文示例在 EURUSD 上以此结构训测跑通,直接套用即可验证。
可以。小布能按你给的品种与参数,持续比对训练与测试曲线偏移,异动时提示你收窄频域窗口或降层。
仅代表该品种某段窗口。外汇高风险,换周期或行情结构变了要重训,别把一次跑通当长期结论。
含模型、配置与数据处理类。先改配置里的品种与周期,再跑数据预处理,其余按依赖顺序调用即可。