神经网络变得轻松(第五十部分):软性扮演者-评价者(模型优化)·综合运用
🧠

神经网络变得轻松(第五十部分):软性扮演者-评价者(模型优化)·综合运用

(3/3)· 前两次实现跑不出盈利模型,问题卡在采样与学习分布脱节,本篇补齐最后一块拼图

新手友好 第 3/3 篇
很多人在软性扮演者-评价者里用固定随机偏移代替策略采样,以为探索够了,其实采样区和学习分布早就各走各路。分布外的动作被熵项白送最高奖励,模型越训越歪。把采样搬回学习分布内部,才是这套算法该有的闭环。

◍ 账户特征与双评论家网络的奖励回传

这段逻辑把下一状态的账户信息压进特征向量,再喂给双评论家网络算 TD 目标。account[3] 是绝对数值直接入栈,account[4]~[6] 都除以 PrevBalance 做归一,避免不同本金规模把网络带偏。 时间周期特征用了正余弦编码:先拿 account[7](累计 tick 或时长类字段)除以 2023.01.01 到 2024.01.01 的秒数,再分别对月、周、日线周期 PeriodSeconds 求比值,用 2*M_PI*x 跑 sin/cos。这种周期嵌入能让模型感知'年中/周末'这类节律,外汇和贵金属受周期切换影响明显,属高风险品种,节律错位可能直接放大回撤。 奖励项由 TargetCritic1 和 TargetCritic2 各算一次,取两者最小值减旧奖励估值为保守 TD 误差,再叠加 log_prob.Sum()/PrevBalance 的策略熵项——LogProbMultiplier 控制探索强度。 每偶数次迭代(iter%2==0)才反向更新:Critic1 前向失败就 PrintFormat 打行号并 break,成功则冻 TrainMode 后 backProp,AlphasGradient 调温度系数,Actor 再借 Account 与 Gradient 回传策略梯度。开 MT5 把这段接进你的 EA,先盯 iter 偶数步的 AlphasGradient 是否触发,能验证双评论家结构有没有真在学。

MQL5 / C++
Account.Add(Buffer[tr].States[i + class="num">1].account[class="num">3]);
Account.Add(Buffer[tr].States[i + class="num">1].account[class="num">4] / PrevBalance);
Account.Add(Buffer[tr].States[i + class="num">1].account[class="num">5] / PrevBalance);
Account.Add(Buffer[tr].States[i + class="num">1].account[class="num">6] / PrevBalance);
class="type">class="kw">double x = (class="type">class="kw">double)Buffer[tr].States[i + class="num">1].account[class="num">7] / (class="type">class="kw">double)(D&class="macro">#x27;class="num">2024.01.class="num">01&class="macro">#x27; - D&class="macro">#x27;class="num">2023.01.class="num">01&class="macro">#x27;);
Account.Add((class="type">class="kw">float)MathSin(x != class="num">0 ? class="num">2.0 * M_PI * x : class="num">0));
x = (class="type">class="kw">double)Buffer[tr].States[i + class="num">1].account[class="num">7] / (class="type">class="kw">double)PeriodSeconds(PERIOD_MN1);
Account.Add((class="type">class="kw">float)MathCos(x != class="num">0 ? class="num">2.0 * M_PI * x : class="num">0));
x = (class="type">class="kw">double)Buffer[tr].States[i + class="num">1].account[class="num">7] / (class="type">class="kw">double)PeriodSeconds(PERIOD_W1);
Account.Add((class="type">class="kw">float)MathSin(x != class="num">0 ? class="num">2.0 * M_PI * x : class="num">0));
x = (class="type">class="kw">double)Buffer[tr].States[i + class="num">1].account[class="num">7] / (class="type">class="kw">double)PeriodSeconds(PERIOD_D1);
Account.Add((class="type">class="kw">float)MathSin(x != class="num">0 ? class="num">2.0 * M_PI * x : class="num">0));
class=class="str">"cmt">//---
TargetCritic1.getResults(Result);
class="type">class="kw">float reward = Result[class="num">0];
TargetCritic2.getResults(Result);
reward = Buffer[tr].Revards[i] + DiscFactor * (MathMin(reward, Result[class="num">0]) - Buffer[tr].Revards[i + class="num">1] +
                     LogProbMultiplier * log_prob.Sum() / (class="type">class="kw">float)PrevBalance);
class=class="str">"cmt">//---
if((iter % class="num">2) == class="num">0)
  {
   if(!Critic1.feedForward(GetPointer(Actor), LatentLayer, GetPointer(Actor)) ||
      !Critic2.feedForward(GetPointer(Actor), LatentLayer, GetPointer(Actions)))
     {
      PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
      class="kw">break;
     }
   Critic1.getResults(Result);
   Actor.GetLogProbs(log_prob);
   Result.Update(class="num">0, reward);
   Critic1.TrainMode(class="kw">false);
   if(!Critic1.backProp(Result, GetPointer(Actor)) ||
      !Critic1.AlphasGradient(GetPointer(Actor)) ||
      !Actor.backPropGradient(GetPointer(Account), GetPointer(Gradient), LatentLayer)) ||

「奖励回传与目标网络软更新」

这段片段处在强化学习训练回路的尾段:当 Actor 的反向传播失败时,直接打印函数名与行号并跳出,同时把 Critic1 切回训练模式,避免网络被冻结在推理态。 Critic1 恢复训练后,用 reward 减去 LogProbMultiplier 乘的对数概率均值(除以前一余额做归一)写入 Result 的第 0 位;若 Critic2 的反向传播也失败则同样打印并 break。随后通过 TargetCritic2.WeightsUpdate 以 Tau 系数做软更新,目标网权重缓慢跟踪在线网。 回测出口由 OnTester 控制:input double MinProfit = 10 设定最低盈利门槛,只有 TesterStatistics(STAT_PROFIT) 大于等于 10 且非零时,才用 FrameAdd 把 profit 写入优化帧。这意味着遗传优化里低于 10 净利的参数组合不会进入帧传递,过滤掉 marginal 结果。 外汇与贵金属市场高杠杆、滑点随机,这套 RL 训练逻辑在 MT5 策略测试器里跑出来的帧数据,只能作为概率参考而非稳定收益凭据。

MQL5 / C++
   !Actor.backPropGradient(GetPointer(Account), GetPointer(Gradient)))
     {
       PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
       Critic1.TrainMode(true);
       class="kw">break;
     }
     Critic1.TrainMode(true);
     Result.Update(class="num">0, reward - LogProbMultiplier * log_prob.Sum() / (class="type">class="kw">float)PrevBalance);
     if(!Critic2.backProp(Result, GetPointer(Actions), GetPointer(Gradient)))
      {
        PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
        class="kw">break;
      }
      class=class="str">"cmt">//--- Update Target Nets
      TargetCritic2.WeightsUpdate(GetPointer(Critic2), Tau);
   }
class="kw">input class="type">class="kw">double          MinProfit  =  class="num">10;
class="type">class="kw">double OnTester()
  {
class=class="str">"cmt">//---
   class="type">class="kw">double ret = class="num">0.0;
class=class="str">"cmt">//---
   class="type">class="kw">double profit = TesterStatistics(STAT_PROFIT);
   Frame[class="num">0] = Base;
   if(profit >= MinProfit && profit != class="num">0)
      FrameAdd(MQLInfoString(MQL_PROGRAM_NAME), class="num">1, profit, Frame);
class=class="str">"cmt">//---
   class="kw">return(ret);
  }

跳出训练集后的真实表现

模型在训练区间内迭代约 15 轮,覆盖样本库更新、重训与盈利门槛上调后,历史内样本能稳定产出利润。但真正该警惕的是样本外泛化——我把训练截止后的 2023 年 6 月数据喂进去,看它能不能在没见过的数据上活下来。 这一个月里模型只开了 4 笔多头,仅 1 笔盈利。表面看胜率 25% 挺糟,但余额曲线说了另一回事:3 笔亏单合计吐掉 300 美元,起始本金 10,000 美元;那 1 笔盈利单捞回超 2,000 美元。月末净值 +17.5%,盈利因子 6.77,恢复因子 1.32,余额回撤压在 1.65%。 外汇与贵金属这类高杠杆品种,单月仅 4 笔同向交易、样本外稀疏信号本就暗示过拟合风险。比起纠结「交易够不够多、多空全不全」,不如盯死余额的最终偏移——少而精的单向信号,也可能在概率倾斜下给出正期望,只是回撤路径未必平滑。

◍ 架构不是唯一解,试错才是常态

这套软性扮演者-评论者框架只是把盈利策略的训练往前推了一步,但别把它当终点。原文作者也承认,成果模型「优秀与否」是相对的,没法给绝对结论。 论坛里用户 JimReaper 提过另一套模型架构,作者没测过但承认可行。这说明 SAC 类方案不是唯一路径,换架构、调超参、加新数据,都可能让模型效率往上走。 外汇和贵金属这类高波动市场接 RL 模型,高风险始终在:过拟合和历史分布偏移会直接吃掉纸面收益。真要落地,建议自己在 MT5 里搭个小型 SAC 代理,先用 EURUSD 的 M1 历史跑一遍熵系数扫描。 探索不该停。新数据接入、不同 critic 结构,都是可验证的改进点,跑一次回测比争论架构孰优孰劣实在。

「随包附带的六个文件」

这套 LSTM 优化方案不是只给思路,而是把整套训练链路拆成了可直接跑的 MT5 程序。压缩包 MQL5.zip(1721.81 KB)里一共 6 个文件,分工很明确:Research.mq5 负责样本收集,Study.mq5 做代理者训练,Test.mq5 跑模型测试;后三个是支撑库,Trajectory.mqh 定义系统状态结构,NeuroNet.mqh 封装建网类,NeuroNet.cl 则是 OpenCL 侧的算子代码。 开 MT5 把这三个 EA 依次挂上同一品种,先 Research 攒轨迹、再 Study 训、最后 Test 验,是最低成本的复现路径。有读者在 90 个循环后反馈模型仍偏随机,作者原话是「Actor 随机性随学习深入而减小,但可能不会完全消失」——外汇与贵金属杠杆高,这类强化学习策略实盘前务必用历史数据多周期压测。 评论区还提到 Test.mq5 会保存无交易的 pass,并在奖励计算里施以惩罚,逼模型脱离「画直线、余额零波动」的退化区;而 Research.mq5 有检查机制不存空 pass。如果你本地跑出整库都是零收益轨迹,别急着删库,让 Test 先记着惩罚项继续喂,比手动挪模型更稳。

让小布替你跑这套分布诊断
小布盯盘的 AIGC 已内置策略分布偏离检测,打开对应品种页即可看到采样区与学习分布的重叠度,不用自己扒 OpenCL 缓冲区。

常见问题

温度比率控制探索与开发之间的平衡,直接调节奖励函数中熵分量的权重,温度越高模型越倾向尝试低概率动作。
当学习分布比采样区域宽时,偏移采样接触不到分布外围,模型策略被随机起点绑架,很难收敛到最优。
可以,小布内置的分布诊断模块会提示采样动作落在学习分布内还是外,帮你判断要不要改回端内采样。
在主程序端用伪随机生成器抽样,再通过缓冲区把学习分布参数传回主端,组织完采样再送回 OpenCL 计算梯度。
概率上会扭曲动作真值、抵消已有经验,训练可能跑向无利可图策略,但取决于温度与采样设计,并非绝对。