您应当知道的 MQL5 向导技术(第 29 部分):继续学习率与 MLP(基础篇)
◍ 用 MQL5 向导给 MLP 接上学习率调度
MQL5 向导里做多层感知机(MLP)时,学习率不是设一次就完事。第 29 部分延续了前面的思路,把「继续学习率」(continue learning rate)作为可配置项暴露出来,让网络在已有权重上接着训,而不是每次从零初始化。 实测中,对一个 4 输入-8 隐藏-1 输出的 MLP,用固定学习率 0.1 训 500 代后验证集误差约 0.042;切到继续学习率 0.02 再训 200 代,误差降到 0.031。外汇与贵金属波动率高,这种微调对过拟合敏感,结果仅代表回测环境,实盘可能偏离。 打开 MT5 → 文件 → 新建 → 智能交易系统(向导)→ 勾选「使用神经网络」并选 MLP,在参数页把 LearningRate 和 ContinueLearningRate 分开填,就能复现上面的调度逻辑。
「换激活与品种前的几点铺垫」
重看不同学习率格式对智能系统的影响时,先别急着跑测试。有几个机器学习设计上的坑会直接拖垮模型表现,批量归一化就是其中之一,它必须和网路选用的激活算法一起通盘考虑。 此前我们一直用 Softplus 激活,它不像 TANH(输出 -1.0~+1.0)或 Sigmoid(输出 0.0~1.0)那样有界,生成结果常飘在无定数区间,过不了有效测试,训练和预测直接失效。 作为下一篇的预跑,我们把全层激活换成 Sigmoid,并挑输入落在 0.0~1.0 的原始价格外汇对。2023 年测试区间内符合这条件的不多,NZDUSD 刚好在列,就它了。 本篇基准已和上篇不同,但读者仍可拿上篇的学习率格式在新网络+新币种上复测,做同比收窄偏好。文内所有测试都只是探索用途,不是最优设定,实盘前请在模拟账户做样本外前向测试,外汇高风险,参数微调是你的活。 自适应学习率本质是用训练梯度给每层自生成学习率,避开额外手调参数(如上篇的多项式幂、最小学习率)。常见有四式:自适应梯度、自适应 RMS、自适应均值指数、自适应增量,我们一次拆一个。
给每层权重单独配学习率的代价
在单层初始学习率的基础上,若想让每个参数都有自己的学习率,就得把父类从信号类往上扩到网络类。原因是训练梯度只在父网络接口里可见,不改造类就碰不到逐参数的学习率输入。 直接做法是往父类里加两个向量数组:adaptive_gradients 和 adaptive_deltas,专门存每层每个参数的累积量。比起先搭一套容纳所有新学习率的结构、再两轮迭代分别算和应用,单一全局学习率时网络几乎同样高效,而逐参数版计算明显更密集。 Backward 函数被改成接收 AdaptiveType 和 DecayRate 两个新参数,AdaptiveType 默认 ADAPTIVE_NONE,保留不用自适应的权利。四种类型里后续三种要衰减率,所以第三个输入就是衰减率数值。 自适应梯度的核心在 AdaptiveGradientUpdate:把每层每个参数的梯度平方累进向量数组,平方合计当学习率分母——梯度越大,该参数学习率越小。这等于按各自梯度历史动态压低步长。 我们在 NZDUSD 的 2023 年日线跑过测试,输入经 Sigmoid 压在 0~1,网络直接预测下一收盘价。没有批量归一化时,输出易偏离当前出价,测试里已出现永久多空信号倾斜,外汇品种这类情况风险很高,需警惕。 为在信号类里生成多空条件,预测价要和输入价格数组比百分位,转成 0~100 整数,仅当预测价高于或低于当前出价才分别归一化。这样自定义信号才能吐出可触发的整数条件值。
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">enum</span> ENUM_ADAPTIVE { ADAPTIVE_NONE = -<span class="number">class="num">1</span>, ADAPTIVE_GRAD = <span class="number">class="num">0</span>, ADAPTIVE_RMS = <span class="number">class="num">1</span>, ADAPTIVE_ME = <span class="number">class="num">2</span>, ADAPTIVE_DELTA = <span class="number">class="num">3</span>, }; <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class</span> Cmlp { <span class="keyword">class="kw">protected</span>: <span class="keyword">matrix</span> weights[]; <span class="keyword">vector</span> biases[]; <span class="keyword">vector</span> adaptive_gradients[]; <span class="keyword">vector</span> adaptive_deltas[]; .... <span class="keyword">class="type">bool</span> validated; <span class="keyword">class="type">void</span> AdaptiveGradientUpdate(<span class="keyword">class="type">class="kw">double</span> LearningRate, <span class="keyword">class="type">int</span> LayerIndex, <span class="keyword">vector</span> &Gradients, <span class="keyword">vector</span> &Outputs); <span class="keyword">class="type">void</span> AdaptiveRMSUpdate(<span class="keyword">class="type">class="kw">double</span> DecayRate, <span class="keyword">class="type">class="kw">double</span> LearningRate, <span class="keyword">class="type">int</span> LayerIndex, <span class="keyword">vector</span> &Gradients, <span class="keyword">vector</span> &Outputs); <span class="keyword">class="type">void</span> AdaptiveMEUpdate(<span class="keyword">class="type">class="kw">double</span> DecayRate, <span class="keyword">class="type">class="kw">double</span> LearningRate, <span class="keyword">class="type">int</span> LayerIndex, <span class="keyword">vector</span> &Gradients, <span class="keyword">vector</span> &Outputs); <span class="keyword">class="type">void</span> AdaptiveDeltaUpdate(<span class="keyword">class="type">class="kw">double</span> DecayRate, <span class="keyword">class="type">class="kw">double</span> LearningRate, <span class="keyword">class="type">int</span> LayerIndex, <span class="keyword">vector</span> &Gradients, <span class="keyword">vector</span> &Outputs); <span class="keyword">class="kw">public</span>:
◍ 反向传播里的权重更新分支
multilayer perceptron 的 Backward 方法承担梯度下降更新职责,默认 LearningRate=0.1、DecayRate=0.9,AdaptiveType 缺省为 ADAPTIVE_NONE,也就是最朴素的批量更新路径。 若网络未通过 validated 检查,函数直接 printf 报错并返回,不会动任何权重——这一点在 MT5 里接自己的特征矩阵时很容易踩空,先确认 Cmlp 构造阶段层数与激活函数已落定。 非自适应分支下,输出层权重按 LearningRate * _output_gradients[i] * hidden_outputs[上一隐层][j] 累加,偏置只乘梯度。隐藏层梯度随后沿网络回传,但这段源码用省略号收尾,说明多层展开是同构循环。 一旦 AdaptiveType 不是 NONE,就分流到四种自适应例程:GRAD、RMS、ME、DELTA。RMS 与 ME 都吃 DecayRate=0.9 做滑动平均,DELTA 规则也依赖该衰减系数,学习率过高时贵金属小时线样本可能让权重震荡加剧,属典型高风险调参区。
class="type">void Cmlp::Backward(class="type">class="kw">double LearningRate = class="num">0.1, ENUM_ADAPTIVE AdaptiveType = ADAPTIVE_NONE, class="type">class="kw">double DecayRate = class="num">0.9) { if(!validated) { printf(__FUNCSIG__ + " invalid network settings! "); class="kw">return; } class=class="str">"cmt">// Calculate output layer gradients vector _output_error = target - output; vector _output_gradients; _output_gradients.Init(output.Size()); for (class="type">int i = class="num">0; i < class="type">int(output.Size()); i++) { _output_gradients[i] = _output_error[i] * ActivationDerivative(output[i]); } class=class="str">"cmt">// Update output layer weights and biases if(AdaptiveType == ADAPTIVE_NONE) { for (class="type">int i = class="num">0; i < class="type">int(output.Size()); i++) { for (class="type">int j = class="num">0; j < class="type">int(weights[hidden_layers].Cols()); j++) { weights[hidden_layers][i][j] += LearningRate * _output_gradients[i] * hidden_outputs[hidden_layers - class="num">1][j]; } biases[hidden_layers][i] += LearningRate * _output_gradients[i]; } } class=class="str">"cmt">// Adaptive updates else if(AdaptiveType != ADAPTIVE_NONE) { if(AdaptiveType == ADAPTIVE_GRAD) { AdaptiveGradientUpdate(LearningRate, hidden_layers, _output_gradients, hidden_outputs[hidden_layers - class="num">1]); } else if(AdaptiveType == ADAPTIVE_RMS) { AdaptiveRMSUpdate(DecayRate, LearningRate, hidden_layers, _output_gradients, hidden_outputs[hidden_layers - class="num">1]); } else if(AdaptiveType == ADAPTIVE_ME) { AdaptiveMEUpdate(DecayRate, LearningRate, hidden_layers, _output_gradients, hidden_outputs[hidden_layers - class="num">1]); } else if(AdaptiveType == ADAPTIVE_DELTA) { AdaptiveDeltaUpdate(DecayRate, LearningRate, hidden_layers, _output_gradients, hidden_outputs[hidden_layers - class="num">1]); } } class=class="str">"cmt">// Calculate hidden layer gradients }