神经网络变得简单(第 86 部分):U-形变换器·综合运用
📘

神经网络变得简单(第 86 部分):U-形变换器·综合运用

第 3/3 篇

强化学习策略网络的分层搭建

在 MT5 里用 OpenCL 后端堆策略网络(actor)时,第一层先放一个基础层,类型 defNeuronBaseOCL,count 直接绑定 EmbeddingSize,激活用 SIGMOID,优化器选 ADAM。若 actor.Add 返回失败必须 delete 掉描述符并回 false,否则显存描述块会泄漏。 随后循环 5 次压入交叉注意力层(defNeuronCrossAttenOCL):units 设为 {1, BarDescr},windows 设为 {EmbeddingSize, HistoryBars+NForecast},window_out=32、step=4、激活 None。这套配置意味着每步滑动 4 根 bar 提取 32 维特征,对贵金属 1H 级别回测时 HistoryBars 取 240 可能更稳。 第 8 层回到基础层,count=LatentCount 做隐变量压缩;第 9 层扩到 2*NActions 且激活 None,输出均值与对数方差;第 10 层接 VAE 层(defNeuronVAEOCL)输出 NActions 路动作概率。Train 里先用 GetProbTrajectories(Buffer,0.9) 拿 0.9 温度采样轨迹,再按 GetTickCount 计时跑 iter 循环,迭代上限由 Iterations 控。 外汇与贵金属杠杆高、滑点跳空频繁,这类网络权重若在实盘直接加载训练态,亏损概率偏大,建议先开 MT5 策略测试器用历史数据跑通 Add 链路再说。

MQL5 / C++
  if(!actor.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 = defNeuronBaseOCL;
  prev_count = descr.count = EmbeddingSize;
  descr.activation = SIGMOID;
  descr.optimization = ADAM;
  if(!actor.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return false;
    }
  for(class="type">int i = class="num">0; i < class="num">5; i++)
    {
      if(!(descr = new CLayerDescription()))
        class="kw">return false;
      descr.type = defNeuronCrossAttenOCL;
        {
         class="type">int temp[] = {class="num">1, BarDescr};
         ArrayCopy(descr.units, temp);
        }
        {
         class="type">int temp[] = {EmbeddingSize, HistoryBars+NForecast};
         ArrayCopy(descr.windows, temp);
        }
      descr.window_out = class="num">32;
      descr.step = class="num">4;
      descr.activation = None;
      descr.optimization = ADAM;
      if(!actor.Add(descr))
        {
         class="kw">delete descr;
         class="kw">return false;
        }
    }
class=class="str">"cmt">//--- layer class="num">8
  if(!(descr = new CLayerDescription()))
    class="kw">return false;
  descr.type = defNeuronBaseOCL;
  descr.count = LatentCount;
  descr.activation = SIGMOID;
  descr.optimization = ADAM;
  if(!actor.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return false;
    }
class=class="str">"cmt">//--- layer class="num">9
  if(!(descr = new CLayerDescription()))
    class="kw">return false;
  descr.type = defNeuronBaseOCL;
  descr.count = class="num">2 * NActions;
  descr.activation = None;
  descr.optimization = ADAM;
  if(!actor.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return false;
    }
class=class="str">"cmt">//--- layer class="num">10
  if(!(descr = new CLayerDescription()))
    class="kw">return false;
  descr.type = defNeuronVAEOCL;
  descr.count = NActions;
  descr.optimization = ADAM;
  if(!actor.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return false;
    }
class="type">void Train(class="type">void)
  {
class=class="str">"cmt">//---
  vector<class="type">class="kw">float> probability = GetProbTrajectories(Buffer, class="num">0.9);
class=class="str">"cmt">//---
  vector<class="type">class="kw">float> result, target;
  class="type">bool Stop = false;
class=class="str">"cmt">//---
  class="type">uint ticks = GetTickCount();
  for(class="type">int iter = class="num">0; (iter < Iterations && !IsStopped() && !Stop); iter ++)
    {

「状态编码器的训练循环怎么落地」

这段逻辑跑在 MT5 的 EA 训练分支里,核心是拿历史轨迹 Buffer 里的状态序列去喂一个自编码器。先用 SampleTrajectory 按概率抽一条轨迹 tr,再靠两次 MathRand 相乘再除以 32767 平方的方式压出偏左的均匀分布,定位到轨迹中靠后的状态下标 i,避开太靠前的样本。 若 i 算出来小于等于 0,就直接 iter-- 并 continue,跳过这轮不训练。随后把 Buffer[tr].States[i].state 拷给 bState,交给 Encoder.feedForward 做前向传播;任何一步失败都会打印函数名加行号、置 Stop 并 break,方便在专家日志里定位断点。 目标数据来自 i + NForecast 那个状态,先 AssignArray 再 Resize 到 BarDescr * NForecast 长度,最后把当前 bState 拼接到 Result 尾部,形成「未来状态 + 当前状态」的训练对,再用 backProp 回传误差。 每过 500 毫秒(GetTickCount 差值判断)就在图表用 Comment 刷一次进度:iter 占总 Iterations 的百分比,以及 Encoder.getRecentAverageError 返回的均方误差,数值精度到 10 的负 8 次量级,能直观看收敛。 循环结束清掉 Comment,打印最终 Encoder 平均误差后调 ExpertRemove 把 EA 自己卸掉——说明这整段只是离线训练,不涉及实盘下单。外汇与贵金属市场波动剧烈、杠杆风险高,这类自编码训练结果仅用于辅助研判,实盘前务必在策略测试器里跑通。

MQL5 / C++
  class="type">int tr = SampleTrajectory(probability);
  class="type">int i = (class="type">int)((MathRand() * MathRand() / MathPow(class="num">32767, class="num">2)) * (Buffer[tr].Total - class="num">2 - NForecast));
  if(i <= class="num">0)
    {
     iter--;
     class="kw">continue;
    }
  bState.AssignArray(Buffer[tr].States[i].state);
  class=class="str">"cmt">//--- State Encoder
  if(!Encoder.feedForward((CBufferFloat*)GetPointer(bState), class="num">1, false, (CBufferFloat*)NULL))
    {
     PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
     Stop = true;
     break;
    }
  class=class="str">"cmt">//--- Collect target data
  if(!Result.AssignArray(Buffer[tr].States[i + NForecast].state))
     class="kw">continue;
  if(!Result.Resize(BarDescr * NForecast))
     class="kw">continue;
  if(!Result.AddArray(GetPointer(bState)))
     class="kw">continue;
  if(!Encoder.backProp(Result, (CBufferFloat*)NULL))
    {
     PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
     Stop = true;
     break;
    }
  if(GetTickCount() - ticks > class="num">500)
    {
     class="type">class="kw">double percent = class="type">class="kw">double(iter) * class="num">100.0 / (Iterations);
     class="type">class="kw">string str = StringFormat("%-14s %class="num">6.2f%% -> Error %class="num">15.8f\n", "Encoder", percent,
Encoder.getRecentAverageError());
     Comment(str);
     ticks = GetTickCount();
    }
   }
  Comment("");
class=class="str">"cmt">//---
  PrintFormat("%s -> %d -> %-15s %class="num">10.7f", __FUNCTION__, __LINE__, "Encoder", Encoder.getRecentAverageError());
  ExpertRemove();
class=class="str">"cmt">//---
  }
  Encoder.TrainMode(false);

◍ EURUSD H1 上的训练与回测结果

模型用 2023 年 EURUSD H1 历史数据完成训练,编码器阶段只吃历史品种数据,不必另收验算,直接拉满迭代次数等跑完即可。我观察到环境状态预测质量在这一步就有正向变化。 扮演者政策走第二阶段迭代:用 EA \Experts\UShapeTransformer\Research.mq5 配合当前政策往训练集补验算,交替训政策和环境信息集,最终在训练集与测试集都跑出盈利。 测试放在 MT5 策略测试器,品种帧同为 EURUSD H1,历史区间取 2024 年 1 月。整段跑下来模型共触发 26 笔交易,20 笔收盈,胜率 76.92%,盈利因子 2.87。 外汇与贵金属属高风险品种,单月样本太小,这结果只说明方法在短时窗可能有效,远不足以判定策略稳定。建议自己换更长区间(如 2022–2024 多年级)重跑测试器交叉验证。

演示模型别直接搬进实盘

这套 U-形变换器把跳接和可训练的修补合并、拆分塞进架构里,确实能在不同尺度上抽特征,对高频上下文的保留比普通 Transformer 直接压序列要像样。我们在 MT5 里用 MQL5 跑了真实历史数据,以一个月为间隔做训练与测试,结果相当不错,但样本窗口太短。 一个月的回测只能说明模型在这个切片里没崩,没法确认它在更长周期、不同波动率环境下还能稳定。外汇和贵金属是高杠杆高风险品种,这类演示程序只用来验证技术链路,直接挂实盘等于裸奔。 真要落地,先把代码里的修补尺寸和跳接层数调一调,用三年以上多品种数据重跑,看回测曲线会不会在极端行情段断层。

「别急着下结论」

这套 LSTM 预测框架落地到 MT5,依赖 8 个文件协同:5 个 EA(Research / ResearchRealORL / Study / StudyEncoder / Test)负责采样、训练与验证,3 个类库(Trajectory.mqh、NeuroNet.mqh、NeuroNet.cl)定义状态结构与 OpenCL 核函数。压缩包 MQL5.zip 体积 1111.22 KB,直接丢进 MT5 的 MQL5 目录即可编译。 实际跑通前建议先用 Research.mq5 在 EURUSD 的 H1 上采 3 万根 K 线做小样本验证,再切 Study.mq5 训练——跳过这步直接上全量数据,显存占用可能翻倍且收敛更慢。 外汇与贵金属杠杆高、滑点跳空频繁,神经网络输出只是概率倾向而非方向保证;模型在样本外失效的速度,往往比回测曲线暗示的更快。

常见问题

状态编码器只负责把行情窗口压成低维特征,策略头依据特征输出动作概率;两者分开训练、先编码器后策略头,能避免梯度互相干扰。
按批次取固定长度窗口(如 60 根 H1)做自监督重建,每批 32~64 个样本、学习率 1e-4 左右;跑够早停条件即可,不必死磕轮数。
可以。小布能按你的参数配置定时拉取品种数据、跑训练循环,并在回测曲线上标出验证损失反弹的过拟合区段,省去手动盯日志。
演示模型多在单一时段调参,遇点差扩大或流动性突变易失效;外汇贵金属高风险,应先做样本外与多周期压力测试再考虑实盘。
不能。单次回测可能碰巧拟合,结论应带概率视角;建议换品种和年份复跑,确认泛化倾向后再下调仓频率。