神经网络变得轻松(第四十部分):在大数据上运用 Go-Explore(基础篇)
「用 Go-Explore 啃下大数据训练」
在 MT5 里跑神经网络,最头疼的不是写模型,而是样本量一大就陷入重复探索、收敛缓慢。Go-Explore 的思路是先把高回报的状态节点「存档」,再针对性回访,避免在巨大状态空间里盲目随机游走。 这套方法对历史行情回放特别有用。以 2023.11.23 发布的示例为例,在 MQL5 社区原帖下 1,342 次浏览、4 条讨论的规模下,作者 Dmitriy Gizlyk 把 Go-Explore 套到 EA 训练流程,让智能体在数年 Tick 数据上更快锁定关键形态。 外汇与贵金属市场高杠杆、高波动,回测顺不代表实盘稳;用大数据训出来的网络,也要防过拟合到某段特殊行情。开 MT5 把该示例的回放模块单独跑一遍 EURUSD H1,看存档命中率是否随数据量线性提升,比直接挂 EA 更划算。
◍ 长周期训练为何让随机探索失效
上一篇文章里我们跑过 Go-Explore 的两阶段流程:先随机动作探索收集环境全景,再拿样本库训练策略。用随机动作在单月历史数据上能训出可盈利模型,但把训练周期拉长到三个月,同样的方法一次盈利回测都出不来。 这不是玄学。按概率论,总事件概率等于各步概率的乘积;单步盈利概率本就小于 1,步数一多,整体盈利验算的概率就被乘下去了。一个月能成,三个月崩掉,在数学上完全可预期。 更麻烦的是环境漂移。周期拉长后市场状态可能切换,代理者学到的中间表征会跟着变质。所以长周期任务里,得在中间阶段就盯住代理性能,而不是训完才看结果。 改进方向是放弃纯随机动作选择,让代理者带着更宽的任务背景做决策。后面我们会拆几种 Go-Explore 的优化动作选择法,专门补长周期覆盖的短板。外汇与贵金属市场高杠杆、高波动,任何训练结论都只是概率倾向,实盘前务必在 MT5 用历史数据复验。
训练周期拉长后 Go-Explore 的四个坑
把 Go-Explore 跑在长周期任务上,麻烦会随训练步数累积。最直观的是状态数随维度指数膨胀——假设状态维度从 5 维涨到 10 维,可能状态数不是翻倍而是 2^5 倍级跳跃,代理者大量时间耗在无关格子。 环境漂移是实盘类任务特有的暗雷。用过去 6 个月历史数据训好的策略,若中间游戏规则或标的结构变了,原策略可能直接失效。固定游戏模拟里没有这问题,但真实行情环境不会静止。 动作选择的背景变宽也拖慢效率:周期越长,代理者越要顾全任务全局,贪心选动作容易卡在局部最优。训练时长同步拉长,数据采集与模型收敛成本上升。 降维(PCA、特征选择)和基于进化/遗传的搜索能压住维度诅咒;置信度驱动探索可避免过早收敛。环境变化要靠训练期持续监测,发现大变就重训,或切到基于模型的 RL 用环境模型预测未来状态。外汇与贵金属这类高波动市场用此法风险偏高,任何策略失效都可能放大回撤。
「用拆分训练集和改奖励函数突破样本瓶颈」
把训练周期拉长不能只在策略测试器里改个日期就完事。想在 MT5 里拿 Go-Explore 训出能用的模型,得先把两个瓶颈掐掉:Cell 结构里的静态数组太小,以及 OnTesterDeinit 里保存样本前的全量排序太慢。 数组尺寸靠简单算术定。H1 周期下,一个月按 22 个工作日、每天 24 根柱算,4 个月要 2112 个元素缓冲区(88*24)。这个值是向上取整的,比实际柱数略大,能避免越界崩错;若训加密货币这种周末也报价的品种,就得按日历日重算。 排序直接砍掉。实践里排序比遍历历史捞状态还费时,周期一长更夸张。Faza1.mq5 的 OnTesterDeinit 改成只统计、存盘、打印,不再排。 [CODE] #define Buffer_Size 2112 //+------------------------------------------------------------------+
| // | TesterDeinit function |
|---|
//+------------------------------------------------------------------+ void OnTesterDeinit() { //--- int total = ArraySize(Total); printf("total %d", total); Print("Saving..."); SaveTotalBase(); Print("Saved"); } Base[action_count - 1].value = ( Base[action_count - 1].state[241] - state[241] + Base[action_count - 1].state[240] - state[240] ) / 2.0f; input double MaxPosition = 0.1; switch(act) { case 0: [/CODE] 上面这段里,Buffer_Size 2112 就是前面算出的缓冲区;OnTesterDeinit 只做 ArraySize 取总数、printf 打日志、SaveTotalBase 落盘;奖励改成净值差和余额差的算术平均除以 2.0f(state[241]、state[240] 分别存净值和余额相关量),持仓时只吃一半奖惩,平仓了结才给另一半,逼代理者别捂仓;MaxPosition=0.1 把最大开仓手数外置,多空分开限,减少锁仓堆积。 再加两个外部变量控风险:最长持仓时限(整数,按柱数计),到边界强制全平;以及把大训练集拆小段、顺序遍历,每段随机采样,挑最盈利的验算当下段起点。配 MinStartSteps、MaxSteps、MinProfit 三个输入:最小起步步数避中间态、最大子集长度截采样、最低利润门槛替排序筛样本。 拆片训练要考虑时间顺序别重叠,否则预测会偏;每段数据少了精度可能降,但模型抗外部扰动的能力会上来。外汇和贵金属是高风险的,这类机制只帮你在不确定行情里缩亏,不保证概率外的盈利。
class="macro">#define Buffer_Size class="num">2112 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| TesterDeinit function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTesterDeinit() { class=class="str">"cmt">//--- class="type">int total = ArraySize(Total); printf("total %d", total); Print("Saving..."); SaveTotalBase(); Print("Saved"); } Base[action_count - class="num">1].value = ( Base[action_count - class="num">1].state[class="num">241] - state[class="num">241] + Base[action_count - class="num">1].state[class="num">240] - state[class="num">240] ) / class="num">2.0f; class="kw">input class="type">class="kw">double MaxPosition = class="num">0.1; class="kw">switch(act) { case class="num">0: