神经网络变得简单(第 93 部分):频域和时域中的自适应预测(终篇)·综合运用
◍ 反向传播里的频域与时序权重回传
这段 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 上过拟合速度放缓,但外汇与贵金属自带高杠杆风险,参数改动仅作技术验证。
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。
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,作者本人也没给出根治办法,只说自己在用更大的文件。外汇和贵金属行情高波动、高杠杆,这类内存边界问题可能让你训练中断,上实盘前务必先在小额历史段验证文件读写稳定性。