神经网络变得轻松(第四十六部分):条件导向目标强化学习(GCRL)·综合运用
📘

神经网络变得轻松(第四十六部分):条件导向目标强化学习(GCRL)·综合运用

第 3/3 篇

「回放缓冲里怎么喂样本给策略网络」

这段逻辑干的是强化学习里的回放训练:先把所有轨迹的最大步数扫出来,再按随机偏移抽取状态-账户快照喂给 Actor 网络。外汇与贵金属杠杆高,这类离线回放若样本偏差大,训练出的决策倾向可能严重偏离实盘。 随机游标用了两次 MathRand() 相乘再除以 32767 的平方,把均匀分布压成左偏分布,使早期步数被抽中的概率更高。若 total_steps 小于 3,i 的计算可能越界或恒为 0,需要在 MT5 里先打印 total_steps 确认缓冲长度。 账户特征做了相对化:余额变化率用 (cur-prev)/prev,权益也除以 prev_balance,把绝对金额压成比率,避免不同本金规模导致网络尺度失衡。这一处理直接决定训练收敛速度,可复制下方代码段逐行核对你的字段索引是否对齐。

MQL5 / C++
  class="type">class="kw">float reward = Account[class="num">0];
  if((buy_value+sell_value)>class="num">0)
    reward+=(class="type">class="kw">float)position_discount;
  else
    reward-=atr;
  if(!Base.Add(sState, act, reward))
      ExpertRemove();
class=class="str">"cmt">//---
  }
class="type">void Train(class="type">void)
  {
   class="type">int total_tr = ArraySize(Buffer);
   class="type">int total_steps = class="num">0;
   for(class="type">int tr = class="num">0; tr < total_tr; tr++)
     {
      if(Buffer[tr].Total > total_steps)
        total_steps = Buffer[tr].Total;
     }
   class="type">uint ticks = GetTickCount();
class=class="str">"cmt">//---
   for(class="type">int iter = class="num">0; (iter < Iterations && !IsStopped()); iter ++)
     {
      class="type">int i = (class="type">int)((MathRand() * MathRand() / MathPow(class="num">32767, class="num">2)) * (total_steps - class="num">2));
      for(class="type">int tr = class="num">0; tr < total_tr; tr++)
       {
        if(i >= (Buffer[tr].Total - class="num">1))
          class="kw">continue;
        State.AssignArray(Buffer[tr].States[i].state);
        class="type">class="kw">float PrevBalance = Buffer[tr].States[MathMax(i - class="num">1, class="num">0)].account[class="num">0];
        class="type">class="kw">float PrevEquity = Buffer[tr].States[MathMax(i - class="num">1, class="num">0)].account[class="num">1];
        Account.Clear();
        Account.Add((Buffer[tr].States[i].account[class="num">0] - PrevBalance) / PrevBalance);
        Account.Add(Buffer[tr].States[i].account[class="num">1] / PrevBalance);
        Account.Add((Buffer[tr].States[i].account[class="num">1] - PrevEquity) / PrevEquity);
        Account.Add(Buffer[tr].States[i].account[class="num">2]);
        Account.Add(Buffer[tr].States[i].account[class="num">3]);
        Account.Add(Buffer[tr].States[i].account[class="num">4] / PrevBalance);
        Account.Add(Buffer[tr].States[i].account[class="num">5] / PrevBalance);
        Account.Add(Buffer[tr].States[i].account[class="num">6] / PrevBalance);
        class=class="str">"cmt">//---
        if(Account.GetIndex()>=class="num">0)
          Account.BufferWrite();
        if(!Actor.feedForward(GetPointer(State), class="num">1, class="kw">false,GetPointer(Account)))
          {
           PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
           ExpertRemove();
           break;
          }
        class=class="str">"cmt">//---
       ActorResult = vector<class="type">class="kw">float>::Zeros(NActions);
       ActorResult[Buffer[tr].Actions[i]] = Buffer[tr].Revards[i];
       Result.AssignArray(ActorResult);

反向传播里的超时打印与终止逻辑

这段训练循环把神经网络的反向传播和实时日志绑在一起:每轮调用 Actor.backProp 做梯度回传,若返回失败就打印函数名加行号、移除 EA 并 break 跳出。 为了避免刷屏,代码用 GetTickCount 做了节流——只有距上次打印超过 500 毫秒才更新一次 Comment。此时会把当前迭代进度(iter / Iterations * 100)和 Actor.getRecentAverageError 输出的平均误差(精度 15.8f)拼成字符串显示在图表左上。 你在 MT5 里跑这类自研训练 EA 时,可以把 500 这个阈值调小看更细的收敛曲线,或调大降低主线程开销;外汇与贵金属市场波动剧烈,这类在线学习策略实盘前务必用历史数据充分回测,过拟合概率不低。

MQL5 / C++
if(!Actor.backProp(Result, class="num">0, NULL, class="num">1, class="kw">false,GetPointer(Account),GetPointer(Gradient)))
      {
       PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
       ExpertRemove();
       break;
      }
       if(GetTickCount() - ticks > class="num">500)
         {
          class="type">class="kw">string str = StringFormat("%-15s %class="num">5.2f%% -> Error %class="num">15.8f\n", "Actor",
iter * class="num">100.0 / (class="type">class="kw">double)(Iterations),
Actor.getRecentAverageError());
          Comment(str);
          ticks = GetTickCount();
         }
}

◍ EURUSD H1 上的 GCRL 代理实测

EA 定型后直接进验证,模型训练参数原样不动,仍吃 EURUSD H1 的历史样本,指标走默认。代理者用 2023 年 4 个月数据训完,挑 6 月 1 日到 18 日这段没见过的数据测泛化。

  • 个交易日里跑了 220 笔,盈利率 53% 出头,盈利因子 2.2、恢复因子 1.47。平均盈利单规模接近亏损单的 2 倍,余额曲线有冲高也有横盘,没出现崩。外汇与贵金属属高风险品种,这类短窗回测不代表样本外稳定。

一个硬伤是代理只开多头,之前方法没解这个偏置。GCRL 的好处是持仓耗时压得低:最长 21 小时 15 分,均值 5 小时 49 分。惩罚项按每小时扣掉潜在盈利的 1/10 计,持仓满 10 小时罚款就盖过该仓收入,逼着模型快点平仓。

「GCRL 实盘测试留下的尾巴」

我们用 MQL5 把这套条件导向目标强化学习(GCRL)跑通了,模型在训练集之外的数据上做验证。结果不算干净:EA 只在单一方向开仓,但测试期内依然录得盈利,说明局部子任务拆解没崩。 持仓时间较训练阶段明显缩短,反向印证代理者确实把‘开仓’和‘平仓’两个本地子任务都学会了,而不是靠长期扛单磨利润。外汇与贵金属杠杆高,这类单方向偏好在趋势反转时可能快速回吐,须先在 MT5 策略测试器里复现再上实盘。 未解决的问题留了个口子——方向偏食。这反而给后续改奖励函数、加反向子任务提供了落点,方法本身值得继续挖。

延伸阅读:技能习得与表征学习的三条线索

想把 MT5 里的智能体从「被动预测」推向「主动探索」,下面三篇线索值得顺着读。它们共同指向一个事实:在缺少显式奖励函数的环境里,Agent 仍可能通过变分授权和目标导向的表征学习掌握技能。 第一条线索关注变分授权框架,它把基于目标的强化学习做成表征学习问题,用潜变量刻画行为意图,省去手工设计奖励的麻烦。 后两条线索是同一系列的连续章节:第四十三部分讲无奖励函数下的技能精通,第四十四部分引入动态学习机制,第四十五部分专门训练状态探索技能。三者递进,构成了从「会做」到「会找状态去练」的闭环。 外汇与贵金属市场高波动、高杠杆,这类方法若接实盘须先用历史 tick 在策略测试器做离线验证,再考虑是否接 EA。

◍ 随包附带的八个文件分别干什么

这套 GCRL 示例不是单文件 EA,而是把数据采集、训练、测试拆成了三个 mq5:Research 负责收集样本,StudyActor 跑代理者训练,Test 做模型验证。三者共用四个 mqh 类库——Trajectory 定义系统状态结构,FQF 管完全参数化模型的工作排程,NeuroNet 搭神经网络,VAE 管变分自动编码器的潜伏层;外加一个 NeuroNet.cl 的 OpenCL 代码库做异构计算加速。 下载包 MQL5.zip 体积 615.73 KB,解压后直接进 MT5 策略测试器就能跑。有读者在讨论区反馈无法复现原文结果,作者 Dmitriy Gizlyk 的答复很关键:三个 EA 所有参数保持默认,只在慢速优化模式下把 Agent 编号拿来设测试器迭代次数,不需要动别的输入项。 另一个被点出的坑是测试 EA 会先开一串买单、再开一串卖单,而卖单常拖到测试期结束才平,怀疑出在策略共用类里。你拉代码自查时优先看 Trajectory.mqh 和 FQF.mqh 里的初始状态与平仓判定,比盲目调参更省时间。外汇与贵金属测试属高风险环境,回测表现不代表实盘倾向。

常见问题

不是。应按近期优先并剔除终止状态后的无效后续,避免把已结束回合的噪声教给网络,否则策略容易学歪。
先确认单步耗时是否超预设阈值,超时即打印当前层状态并中断该轮;终止逻辑看奖励是否为终态标记,命中就跳出避免空转。
可以。小布能加载你的代理配置做离线回测,把未收敛的回合和异常终止自动列成清单,你只管看结论。
原文未给具体耗时与胜率数字;实测仅说明代理可在此周期学习条件策略,实盘仍存未闭合问题,外汇高风险勿盲信。
回放缓冲通常是带 replay/buffer 字样的脚本,技能表征多在 encoder/representation 文件;看函数名含 Store 与 Encode 即可快速分辨。