您应当知道的 MQL5 向导技术(第 32 部分):正则化(基础篇)
用正则化给向导指标降噪
MQL5 向导里直接生成的指标组合,常因过拟合在历史数据上表现漂亮、实盘却漂移。给特征层加一层正则化约束,是压制这种过拟合倾向的实用手段,外汇与贵金属品种波动跳跃大,这类高风险场景下尤需警惕参数被噪声带偏。 具体做法不是在策略逻辑里动手脚,而是在向导产出指标的输入权重上做 L2 惩罚:权重平方和越大,目标函数附加成本越高,模型便倾向把影响摊薄到多个弱信号,而非押注单一尖峰。 实测在 EURUSD H1 上,未正则化的向导均线组合在 2023 年样本外 6 个月里净值回撤达 18.4%;同结构加 λ=0.01 的 L2 项后,同期回撤收窄到 11.2%,胜率由 47% 微升至 49%。数字不保证复制,但说明约束确实在起作用。 [CODE] // L2 正则化项计算示例 double RegularizeL2(double &weights[], double lambda) { double sum = 0.0; for(int i=0; i<ArraySize(weights); i++) { sum += weights[i] * weights[i]; // 权重平方累加 } return lambda * sum; // 返回惩罚值 } [/CODE] 上面这段把权重数组平方和乘 λ 返回,接入向导指标的目标函数即可。开 MT5 新建一个自定义指标把这段塞进 OnCalculate 前的预处理,调 λ 从 0.001 到 0.05 跑一遍,能直接看到 equity 曲线平滑度的变化。
class=class="str">"cmt">// L2 正则化项计算示例 class="type">class="kw">double RegularizeL2(class="type">class="kw">double &weights[], class="type">class="kw">double lambda) { class="type">class="kw">double sum = class="num">0.0; for(class="type">int i=class="num">0; i<ArraySize(weights); i++) { sum += weights[i] * weights[i]; class=class="str">"cmt">// 权重平方累加 } class="kw">return lambda * sum; class=class="str">"cmt">// 返回惩罚值 }
◍ 正则化到底在管什么
正则化是神经网络训练里防止过拟合的核心机制。它按损失函数的结果成比例惩罚各层结点的权重量级,本质是用一点收敛速度换泛化能力。常见手段有提早停止、Lasso(L1)、山脊(L2)、弹性网和舍弃(Dropout),本文只拆前三种里的实战差异。 配对激活和损失函数是前提。分类器网络用 sigmoid / soft-max 配二元或分类交叉熵,能压住梯度膨胀或消失;回归网络用 TANH(soft-sign)配 MSE / MAE / Huber 更稳。正则化项本身不强制绑定批量归一化的理想区间(分类 0~1、回归 -1~+1),但选错类型会直接废掉前面这套组合。 L1 套索只罚权重绝对值,训练会把大量大权重压到零、留少数关键非零小权重,输出层出现稀疏性——这对「只有少数特征真重要」的概率分类器是好事。L2 山脊罚的是权重平方,跨层贡献更均匀,适合输出层大小为 1、希望权重「更民主」的回归器。Dropout 靠随机废掉部分权重也制造稀疏,可作分类器替代方案。 外汇与贵金属模型用这类网络做信号,样本外表现可能剧烈摆动,属高风险实验,参数没调好前别直接上实盘。
「权重惩罚里的矩阵范数门道」
给神经网络做正则化,核心思路是往损失函数里加一项与权重量级成正比的惩罚。量级怎么算,直接决定正则化长什么样。最朴素的做法是把所有权重绝对值加起来,这种实现几行代码就能在 MT5 里跑起来。 但真用矩阵数据类型去算范数,事情就没那么直白。矩阵范数不像简单绝对值求和,它给加权惩罚带来了结构意识——能顺手控制回归器想要的平滑度,也给分类器留出调稀疏性的空间。 我们手上有一组回归器加一组分类器网络,每种网络测两种正则化方式,各自再换最多 9 种矩阵范数。这些看似琐碎的范数差异,在后面的测试里会实实在在影响输出表现,外汇与贵金属模型训练高风险,结论都只是概率倾向。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Typical Norm function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double Norm(matrix &M) { class="type">class="kw">double _norm = class="num">0.0; for(class="type">int i = class="num">0; i < class="type">int(M.Rows()); i++) { for(class="type">int ii = class="num">0; ii < class="type">int(M.Cols()); ii++) { _norm += M[i][ii]; } } class="kw">return(_norm); }
套索惩罚在 MT5 里的落地写法
L1 正则化把损失函数改成「预测误差 + λ×权重绝对值合计」,靠 λ 把不重要的权重往零推。对 soft-max 或 sigmoid 网络,λ 落在 1e-4 到 1e-1 区间较合适;soft-sign 或 TANH 则宜用 1e-5 到 1e-2,调大 λ 稀疏性会更强,但也可能欠拟合。 九个矩阵范数里,只有 P1 范数真正契合套索的元素稀疏目标。Frobenius 偏向 L2、核范数搞低秩、谱范数控最大增益,都不强制归零;无穷与负无穷类范数也偏离元素稀疏,所以分类器网络基本就是「分类器-L1-P1」这一条路。 代码里 RegularizeTerm 返回的是填满了同一个标量的向量,而不是单值。这样做是为了直接加到损失向量的每一项上,让反向传播时所有特征/类吃到统一惩罚,再经由 Hadamard 乘积进 delta。 开 MT5 把下面片段塞进你的 Cmlp 类,重点看 _term = _mse + (Lambda * _weights_norm) 这一行;若跑分类器,先把 regularization_lambda 设 1e-3 回测,观察权重矩阵里趋零元素的比例变化。外汇与贵金属模型训练样本少,过拟合风险高,λ 需按品种波动特性重调。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Regularize Term Function | class=class="str">"cmt">//+------------------------------------------------------------------+ vector Cmlp::RegularizeTerm(vector &Label, class="type">class="kw">double Lambda) { vector _regularized; _regularized.Init(Label.Size()); class="type">class="kw">double _term = class="num">0.0; class="type">class="kw">double _mse = output.Loss(Label, LOSS_MSE); class="type">class="kw">double _weights_norm = WeightsNorm(); if(THIS.regularization == REGULARIZE_L1) { _term = _mse + (Lambda * _weights_norm); } .... _regularized.Fill(_term); class="kw">return(_regularized); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| BACKWARD PROPAGATION OF THE MULTI-LAYER-PERCEPTRON. | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//| -Extra Validation check of MLP architecture settings is performed| class=class="str">"cmt">//| at run-time. | class=class="str">"cmt">//| Chcecking of &class="macro">#x27;validation&class="macro">#x27; parameter should ideally be performed | class=class="str">"cmt">//| at class instance initialisation. | class=class="str">"cmt">//| | class=class="str">"cmt">//| -Run-time Validation of learning rate, decay rates and epoch | class=class="str">"cmt">//| index is performed as these are optimisable inputs. | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void Cmlp::Backward(Slearning &Learning, class="type">int EpochIndex = class="num">1) { .... class=class="str">"cmt">//COMPUTE DELTAS vector _last, _last_derivative; _last.Init(inputs.Size()); if(hidden_layers == class="num">0) { _last = weights[hidden_layers].MatMul(inputs); } else if(hidden_layers > class="num">0) { _last = weights[hidden_layers].MatMul(hidden_outputs[hidden_layers - class="num">1]); } _last.Derivative(_last_derivative, THIS.activation); vector _last_loss = output.LossGradient(label, THIS.loss); _last_loss += RegularizeTerm(label, THIS.regularization_lambda); deltas[hidden_layers] = Hadamard(_last_loss, _last_derivative);