您应当知道的 MQL5 向导技术(第 32 部分):正则化·进阶篇
(2/2)· 接上篇激活函数与损失函数配对,本篇拆解正则化在回归与分类网络中的具体落点与实测差异
接上篇,我们继续深挖 MQL5 向导里正则化这一层。很多交易者把网络跑出样本内高分就直接上实盘,却没意识到权重早已被少数参数绑架,样本外一测就塌。本篇把 L1、L2 和舍弃三种机制拆开看,搞清楚什么时候该用哪一种。
原节未提供有效内容
本次传入的小节原文仅含占位省略号与空代码标记,未携带任何可提炼的技术点、数据或 MQL5 代码片段。 按保真不编造红线,不能补写价格行为结论或回测数字;读者若需本系列第2篇第1节实质内容,应补回原文后再交由蒸馏。 外汇与贵金属品种波动受杠杆与流动性影响,任何未经验证的描述都不具备开 MT5 复现的价值。
◍ 回归网络里用 L2 配弗罗贝纽斯更稳
L2 正则化和 L1 长得像,但惩罚项用的是权重的平方和,不是绝对值。公式里 λ 管惩罚强度,w_i 是各层权重,n 是带权重矩阵的层数,N 是样本数,y_j 与 y^_j 分别是真实值和预测值。平方项的引入让权重更平滑,这对回归类神经网络尤其友好。
MQL5 里矩阵范数函数同时提供 P2 和弗罗贝纽斯两种选项,实测两者返回值略有差异:弗罗贝纽斯常用于矩阵、P2 常用于向量,但从接口看并非完全等价。下面这段 Cmlp::RegularizeTerm 的实现,L2 分支直接把权重范数平方乘 λ 再加到 MSE 上。
//+------------------------------------------------------------------+
| // | Regularize Term Function |
|---|
//+------------------------------------------------------------------+
vector Cmlp::RegularizeTerm(vector &Label, double Lambda)
{ vector _regularized;
_regularized.Init(Label.Size());
double _term = 0.0;
double _mse = output.Loss(Label, LOSS_MSE);
double _weights_norm = WeightsNorm();
...
else if(THIS.regularization == REGULARIZE_L2)
{ _term = _mse + (Lambda * _weights_norm * _weights_norm);
}
...
_regularized.Fill(_term);
return(_regularized);
}
逐行拆一下:_regularized.Init 按标签尺寸初始化返回向量;_mse 用 LOSS_MSE 算当前输出和标签的均方误差;_weights_norm 取网络权重范数;L2 分支里 _term 就是 MSE 加上 λ 乘权重范数平方,平方操作对应 L2 的平滑惩罚;最后 Fill 把标量损失填进向量返回。
做回归预测时,把正则化设成 L2、范数函数选弗罗贝纽斯,倾向比随便用 P2 更贴合矩阵权重结构。外汇与贵金属模型训练波动大、过拟合风险高,上 MT5 跑这段前先把 Lambda 从 1e-3 量级试起,看验证集 MSE 是否不再乱跳。
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(); ... else if(THIS.regularization == REGULARIZE_L2) { _term = _mse + (Lambda * _weights_norm * _weights_norm); } ... _regularized.Fill(_term); class="kw">return(_regularized); }
「随机归零神经元:Dropout 在 MLP 里的落地逻辑」
舍弃(Dropout)正则化和 L1、L2 不是一类东西。它不在损失函数里加惩罚项,而是在前向传播训练时按随机概率把某些神经元输出直接置零,强迫网络别死赖某几个节点。 原文给出的 MLP 实现里,训练阶段若 regularization 标记为 REGULARIZE_DROPOUT,就随机挑一个输出向量下标置零。代码里是单层随机丢一个,但实际更深的网通常用 for 循环按保留概率逐神经元乘掩码,那样可控性更好,只是要多写几行。 从机制上看,Dropout 等于每次迭代从原网络里采样出一个子网络。多个子网络在训练时共享权重、测试时隐式融合,能降低神经元共适(co-adaptation),让分类器对噪声和高方差数据更健壮。作者指出,这种收益通常在非常深或 Transformer 堆叠的网络才明显,浅网加了可能反而拖慢收敛。 对外汇、贵金属这种高噪声时序数据做深度学习特征提取时,若你打算堆深 MLP 或 CNN 分类走势,Dropout 值得开;但 MT5 上小样本回测里它未必比 L2 强,属于高风险实验,结论仅倾向性。 下面这段是原文里 MLP 前向传播中 Dropout 的骨架代码,逐行看逻辑: // 类方法 Forward,Training 默认 false,只有训练传 true 才触发舍弃 void Cmlp::Forward(bool Training = false) { // 架构没通过校验直接报错退出,避免脏跑 if(!validated) { printf(__FUNCSIG__ + " invalid network arch! "); return; } // 逐隐藏层前向 for(int h = 0; h <= hidden_layers; h++) { vector _output; _output.Init(output.Size()); ... // 训练且启用 Dropout 时,随机选一个输出神经元置零 if(Training && THIS.regularization == REGULARIZE_DROPOUT) { int _drop = MathRand() % int(_output.Size()); _output[_drop] = 0.0; } _output += biases[h]; ... } } 在 MT5 里把 REGULARIZE_DROPOUT 接进你自己的网络类,改 MathRand 取模范围就能调舍弃比例,先拿 EURUSD 的 H1 特征试深网。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| FORWARD PROPAGATION THROUGH 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">//| Chceking 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">//| -Input data is normalized if normalization type was selected at | class=class="str">"cmt">//| class instance initialisation. | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void Cmlp::Forward(class="type">bool Training = false) { if(!validated) { printf(__FUNCSIG__ + " invalid network arch! "); class="kw">return; } class=class="str">"cmt">// for(class="type">int h = class="num">0; h <= hidden_layers; h++) { vector _output; _output.Init(output.Size()); ... if(Training && THIS.regularization == REGULARIZE_DROPOUT) { class="type">int _drop = MathRand() % class="type">int(_output.Size()); _output[_drop] = class="num">0.0; } _output += biases[h]; ... } }
EURUSD 日线年化回测对照
按前两篇指南把附码拼成 EA 后,在 EURUSD 的 2023 年日线帧上跑了回归器与分类器两套网络。分类器沿用 soft-max / Sigmoid 激活、交叉熵或二元交叉熵损失、仅 P1 范数的 L1 正则,无止损挂单;回归器用 soft-sign 激活、L2 山脊正则与 Huber 损失。 关掉正则或改用反向正则、其余设置不变再跑一遍,净值曲线与前两组重合。这说明在本例 1 年样本里,正则化权重很可能低于损失函数、激活函数乃至 EA 的入场平仓阈值。 但别把结论推太满:分类器里的正则收益,或许要放到超过 1 年、且输出类更宽更深的网络上才显形。外汇与贵金属高杠杆,回测吻合不等于实盘概率同向,开 MT5 用 2024 年数据复跑一套再下注。
◍ EURUSD 日线测试为何对正则化不敏感
我们在 EURUSD 2023 年日线周期上跑了一遍分类器与回归器网络,结果是系统表现对正则化项几乎没反应。这组样本只有一年的日线窗口,且网络结构刻意压得很小,层数和宽度都不足以让过拟合显形,正则化的抑制效应自然测不出来。 分类器网络常见配置是层数少但单层很宽,回归器则相反,层偏窄却堆得多,用层数补足深度。两种结构在微型网络上都没暴露出正则化收益,不代表它在更大规模训练里无效。 外汇与贵金属属高杠杆高风险品种,任何模型结论都只是概率倾向。想验证正则化是否真有用,得在 MT5 里把隐藏层扩到 3 层以上、拉长至 5 年日线再对比开/关 dropout 的回撤曲线。
「把这条线请下神坛」
弹性网络不是什么黑盒魔法,它只是把 L1 套索和 L2 山脊两种惩罚按一个 alpha 权重做了加权平均。代码里 REGULARIZE_ELASTIC = 4 进了枚举,RegularizeTerm 里那段 else if 就是照公式落地:alpha 乘 Lambda 乘权范数,再加 (1-alpha) 乘 Lambda 乘权范数平方。 当前实现用单个 _weights_norm 变量走矩阵范数,注定抓不到 L1、L2 各自的独立属性。想彻底分开,得在类里放两个 _weight_norm、各自挂一种范数函数,构造函数也得跟着改——这是实打实的重构成本。 折衷办法是用无穷大范数一并近似两种正则,回测里 alpha 在 0.0~1.0 间网格寻优通常就够用。外汇与贵金属杠杆高、滑点跳空频繁,这类正则化只降低过拟合概率,不保证样本外稳健,上 MT5 用 SignalWZ_32_regR.mqh 改两行枚举自己跑一遍最实在。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Regularization Type Enumerator | class=class="str">"cmt">//+------------------------------------------------------------------+ enum Eregularize { REGULARIZE_NONE = -class="num">1, REGULARIZE_L1 = class="num">1, REGULARIZE_L2 = class="num">2, REGULARIZE_DROPOUT = class="num">3, REGULARIZE_ELASTIC = class="num">4 }; 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(); ... else if(THIS.regularization == REGULARIZE_ELASTIC) { _term = _mse + (THIS.regularization_alpha * (Lambda * _weights_norm)) + ((class="num">1.0 - THIS.regularization_alpha) * (Lambda * _weights_norm * _weights_norm)); } _regularized.Fill(_term); class="kw">return(_regularized); }