您应当知道的 MQL5 向导技术(第 28 部分):据入门学习率重新审视 GAN(基础篇)
◍ 用学习率曲线判断 GAN 训练是否走偏
在 MT5 里跑 GAN 类策略模型,入门阶段最常用的观察项就是学习率(learning rate)随迭代步数的变化。很多新手直接套用默认 0.001 的固定步长,结果前 200 步损失下降很快,之后就长期横在 0.4 附近不动,这种扁平段往往意味着生成器已经陷入局部均衡。 把学习率从固定改成按步数衰减(比如每 50 步乘 0.9),在同样 500 步训练中,损失曲线更容易在 300 步后继续下探到 0.2 以下,说明模型还在吸收价格分布特征。外汇与贵金属行情高波动、高杠杆,用这类未充分收敛的 GAN 直接下单风险极高,仅建议作特征生成辅助。 小布盯盘里可以接 MT5 的 OnTester 回传,把每轮学习率打印成折线,肉眼看衰减拐点比看最终准确率更实在。
把学习率单独拎出来拷问 GAN
这次不铺开讲整套生成式对抗网络,只盯一个超参数:学习率。GAN 是成对跑的神经网络,一个生成器按真实行情样本学着装真,一个判别器专门挑刺;两者表面互骗,实际同步训练,最终生成器吐出的信号才对交易者有用。 我们只在一个极简 GAN 上做文章:3 层结构(5-8-1,输入到输出),1 个隐藏层 8 节点,品种锁 EURJPY、日线、回测区间 2023 全年。架构故意保持朴素,就是为了排除层数干扰,单纯看学习率调度有没有戏。 性能衡量不玩虚的,就盯总盈利加恢复因子。每种学习率格式都附策略测试报告,不再像以前堆到文末才给,边讲边贴,方便你直接对照。所有变体共用同一品种、周期、区间,保证可比性。 下面这段代码是信号类的多空触发核心:生成器输出乘 100 后,判别器背书且值大于 50 才投多,小于 50 才投空。改架构或阈值,直接动这几行就能在 MT5 向导里重编译验证。外汇与贵金属杠杆高,回测盈利不代表实盘概率占优,动手前先想清楚风险。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| "Voting" that price will grow. | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CSignalCGAN::LongCondition(class="type">void) { class="type">int result = class="num">0; class="type">class="kw">double _gen_out = class="num">0.0; class="type">bool _dis_out = false; GetOutput(_gen_out, _dis_out); _gen_out *= class="num">100.0; if(_dis_out && _gen_out > class="num">50.0) { result = class="type">int(_gen_out); } class=class="str">"cmt">//printf(__FUNCSIG__ + " generator output is: %.5f, which is backed by discriminator as: %s", _gen_out, class="type">class="kw">string(_dis_out));class="kw">return(class="num">0); class="kw">return(result); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| "Voting" that price will fall. | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CSignalCGAN::ShortCondition(class="type">void) { class="type">int result = class="num">0; class="type">class="kw">double _gen_out = class="num">0.0; class="type">bool _dis_out = false; GetOutput(_gen_out, _dis_out); _gen_out *= class="num">100.0; if(_dis_out && _gen_out < class="num">50.0) { result = class="type">int(fabs(_gen_out)); } class=class="str">"cmt">//printf(__FUNCSIG__ + " generator output is: %.5f, which is backed by discriminator as: %s", _gen_out, class="type">class="kw">string(_dis_out));class="kw">return(class="num">0); class="kw">return(result); }
「固定学习率在训练里的取舍」
固定学习率常被刚接触机器学习的人直接拿来用,原因只有一个:够简单。每次迭代里权重和偏差的修正幅度都按同一个浮点值走,跨训练局次也不会变,你用一个值就能贯穿全部过程。 这种写法让训练动态更好预测,也更容易调试和复现。我们在测试里统一把初始权重设为 0.1、初始偏差设为 0.01,靠这组固定值,多次测试跑出来的结果能稳定对齐,不像随机初始化神经网络那样每次都飘。 早期训练阶段它还有个隐性好处:学习率不会像别的策略那样后期猛降或暴跌,新旧数据被一视同仁。当你拿初始权重这类非学习率超参数做基准对比时,固定学习率能让你更快摸到有意义的优化区间。 代价是次优收敛和适应性差。梯度下降可能卡在局部最小值而非全局最优,尤其在你没调好那个固定值之前;而且“学习需求随局次递减”几乎是共识,固定写法显然违背了这点。 即便优缺点并存,在 EURJPY 货币对、2023 年日线周期上做试跑,仍得到一组可观察的训练轨迹,外汇与贵金属这类品种波动剧烈,实盘验证前须认清高风险。
◍ 步进衰减怎么压住学习率
步进衰减本质是个带闸门的固定学习率:只多两个参数,管着初始学习率随训练局次往下掉。MQL5 里判定每局实际学习率分两步,先拿局次索引,再乘衰减系数。 局次索引怎么来?for 循环是倒着数的,用总局次 m_epochs 减当前 i,再除以 m_decay_epoch_steps,MathFloor 向下取整。步长设 5,就每 5 局降一次;设 10 就每 10 局降,不会中途乱降。 [CODE] <span class="keyword">if</span>(m_learning_type == LEARNING_STEP_DECAY) { <span class="keyword">int</span> _epoch_index = <span class="keyword">int</span>(<span class="functions">MathFloor</span>((m_epochs - i) / m_decay_epoch_steps)); _learning_rate = m_learning_rate * <span class="functions">pow</span>(m_decay_rate, _epoch_index); } [/CODE] 逐行看:第一行进分支,仅当学习类型被选为步进衰减;第二行算局次索引,倒计数保证越靠近训练尾段索引越大;第三行用 pow 把初始率乘衰减率的指数幂,指数就是刚算出的索引,完成逐步降压。 渐次下调能避免一把冲过最小值,在前期快学、后期细调之间取平衡。相比死盯固定率,它更可能绕开损失面的局部最小和鞍点,泛化倾向更好,过度拟合的概率更低。拿 EURJPY 2023 年日线跑这套,会得到一组随局次阶梯式收缩的学习率曲线,开 MT5 把 m_decay_epoch_steps 从 5 改成 10 对照训练日志就能验证节奏差异。外汇与贵金属波动剧烈,任何训练策略都属高风险验证,实盘前务必隔离测试。
<span class="keyword">if</span>(m_learning_type == LEARNING_STEP_DECAY) { <span class="keyword">class="type">int</span> _epoch_index = <span class="keyword">class="type">int</span>(<span class="functions">MathFloor</span>((m_epochs - i) / m_decay_epoch_steps)); _learning_rate = m_learning_rate * <span class="functions">pow</span>(m_decay_rate, _epoch_index); }