您应当知道的 MQL5 向导技术(第 30 部分):聚焦机器学习中的批量归一化(基础篇)
批量归一化在 MQL5 机器学习管线里的位置
做 MT5 上的机器学习策略时,特征量纲不一致会让梯度下降在训练初期剧烈抖动。批量归一化(Batch Normalization)把每一小批数据的输入做零均值单位方差处理,缓解内部协变量偏移,使网络能用更大的学习率收敛。 在 MQL5 向导生成的模型代码里,批量归一化层通常出现在隐藏层之前。它不预测行情,只改变前向传播的数值稳定性,外汇与贵金属杠杆高、跳空频繁,归一化后的输入对异常 tick 更鲁棒,但模型仍可能过拟合历史。 下面这段摘出的 MQL5 片段演示了如何对一个批量输入做归一化计算,读者可直接粘进 MT5 的 CS 神经网络类里验证数值变化:
◍ EA 里接住三种批量归一化
批量归一化在神经网络层前先做标准化,原始论文把效果归因于「内部协变量偏移」,即削弱上游输入分布漂移对下游的级联干扰;但较新的实证研究认为,真正起作用的是梯度平滑——归一化后内层梯度方差更小,训练更稳。对外汇与贵金属交易者而言,这类网络在 MT5 里属于高风险实验,结论只代表回测概率倾向,不等于实盘必然占优。
我们这次只验证三种主要形式:标准伸缩(NORMALIZE_STANDARD)、特征伸缩(NORMALIZE_FEATURE)、稳健伸缩(NORMALIZE_ROBUST),并拿不归一化(NORMALIZE_NONE)作对照。思路延续之前学习率文章的格式——先讲清每种算法,再跑对应 EA 出对照报告。
用 MQL5 向导拼 EA 时有个坑:自定义枚举若写在信号类头文件,向导就识别不出这个文件,没法可视化组装。折中是头里不声明枚举,只在信号类内部留参数和赋值函数;向导组装完再手动改输入名册、初始化时补上枚举。下面这段就是归一化类型枚举和手工接入的写法。
[CODE]
enum Enormalize
{ NORMALIZE_NONE = -1,
NORMALIZE_ROBUST = 0,
NORMALIZE_FEATURE = 1,
NORMALIZE_STANDARD = 2,
};
....
input ENUM_ACTIVATION_FUNCTION Signal_CMLP_ActivationType = AF_SIGMOID; // CMLP(30,6,0.05,10,0.1,2,...) Activation Type
input Enormalize Signal_CMLP_NormalizeType = NORMALIZE_FEATURE; // CMLP(30,6,0.05,10,0.1,2,...) Batch Normalisation Type
input Elearning Signal_CMLP_LearningType = LEARNING_ADAPTIVE; // CMLP(30,6,0.05,10,0.1,2,...) Learning Type
input Eadaptive Signal_CMLP_AdaptiveType = ADAPTIVE_GRADIENT; // CMLP(30,6,0.05,10,0.1,2,...) Adaptive Type
...
...
filter0.ActivationType(Signal_CMLP_ActivationType);
filter0.NormalizeType(Signal_CMLP_NormalizeType);
filter0.LearningType(Signal_CMLP_LearningType);
filter0.AdaptiveType(Signal_CMLP_AdaptiveType);
...[/CODE]
代码逐行拆解:
· enum Enormalize 定义归一化类型,-1 为不用,0/1/2 分别对应稳健、特征、标准伸缩;
· input Enormalize Signal_CMLP_NormalizeType = NORMALIZE_FEATURE 把枚举暴露成 EA 输入,默认特征伸缩,方便策略测试器里优化切换;
· 后面几个 input 是激活函数、学习率类型、自适应类型的枚举,同样手工挂接;
· filter0.NormalizeType(...) 等四行在初始化时把输入值灌进自定义信号实例 filter0,向导组装后才补这几行就能正常跑。
手动改完输入名册再编译,由向导生成的 EA 就能带着自定义枚举跑批量归一化对照。外汇贵金属行情跳空频繁,这类网络信号仅作辅助参考,仓位须自担风险。
enum Enormalize { NORMALIZE_NONE = -class="num">1, NORMALIZE_ROBUST = class="num">0, NORMALIZE_FEATURE = class="num">1, NORMALIZE_STANDARD = class="num">2, }; .... class="kw">input ENUM_ACTIVATION_FUNCTION Signal_CMLP_ActivationType = AF_SIGMOID; class=class="str">"cmt">// CMLP(class="num">30,class="num">6,class="num">0.05,class="num">10,class="num">0.1,class="num">2,...) Activation Type class="kw">input Enormalize Signal_CMLP_NormalizeType = NORMALIZE_FEATURE; class=class="str">"cmt">// CMLP(class="num">30,class="num">6,class="num">0.05,class="num">10,class="num">0.1,class="num">2,...) Batch Normalisation Type class="kw">input Elearning Signal_CMLP_LearningType = LEARNING_ADAPTIVE; class=class="str">"cmt">// CMLP(class="num">30,class="num">6,class="num">0.05,class="num">10,class="num">0.1,class="num">2,...) Learning Type class="kw">input Eadaptive Signal_CMLP_AdaptiveType = ADAPTIVE_GRADIENT; class=class="str">"cmt">// CMLP(class="num">30,class="num">6,class="num">0.05,class="num">10,class="num">0.1,class="num">2,...) Adaptive Type ... ... filter0.ActivationType(Signal_CMLP_ActivationType); filter0.NormalizeType(Signal_CMLP_NormalizeType); filter0.LearningType(Signal_CMLP_LearningType); filter0.AdaptiveType(Signal_CMLP_AdaptiveType); ...
「批量归一化到底改了什么」
批量归一化最初在 2015 年那篇论文里被提出,核心卖点是降低内部协变量偏移,顺带让每层激活分布更接近标准态。官方论点后来有人质疑,但它至少把训练稳定性这件事说清楚了:收敛更快,对初始权重的敏感度也降下来。 实测里有个容易被忽略的点——初始权重不仅影响最终收敛到的参数,还直接拉长或压缩训练所需步数,比例经常不成线性。批量归一化把这种不成比例的影响压薄了。 它还能当正则化器用,原理是让网络对某些神经元权重不那么敏感;同时缓解梯度消失,使深层网络可训。层间依赖被解耦后,输入特征尺度怎么变都不太拖累过程,批量大小切换、学习率规程验证都更从容。 外汇与贵金属模型训练属高风险实验,上述收益均为概率性倾向,需在 MT5 用自有数据复验,勿直接当实盘结论。
归一化范围与激活函数的匹配门槛
特征缩放把一层向量压进 0.0~+1.0。公式 x'=(x-min(x))/(max(x)-min(x)) 看起来简单,但真正麻烦的是:MQL5 里激活函数枚举的输出范围并不统一。 翻一遍内置枚举,只有硬 Sigmoid、Sigmoid、Softmax 三者输出落在 [0,1] 或 (0,1) 且 Softmax 合计为 1。其余如 Tanh 是 (-1,1)、ReLU 是 [0,∞)、ELU 是 (-1,∞),都越过边界。若网络每层激活范围和输入缩放范围错位,训练时梯度消失或膨胀的概率会明显上升。 硬 Sigmoid 是 Sigmoid 的廉价近似,输出恒限 0.0~+1.0,计算量小,适合深网和 transformer。Sigmoid 用欧拉常数做平滑映射,极难吐出精确的 0.0 或 1.0,因此既控梯度膨胀又保留可变性。Softmax 把向量归一成概率分布、总和为 1,多类分类或高维比较时常选它,能压住深网下游的大数计算问题。 MQL5 里这三者的前向反向都能从 vector 类型内置函数直接调,典型入口在 Cmlp 类的 Forward() 与 Backward()。下面这段特征缩放实现先拷数据、判最大最小差是否大于 0.0 防零除,再逐元素算比例,逻辑直给。 //+------------------------------------------------------------------+ //| Function to apply Feature-Scaling //+------------------------------------------------------------------+ void Cnormalize::FeatureScaling(vector &Data) { vector _copy; _copy.Copy(Data); // 拷贝原向量,避免改原数据 if(_copy.Max() - _copy.Min() > 0.0) // 最大减最小大于0才做,防除零 { for (int i = 0; i < int(Data.Size()); i++) { Data[i] = (_copy[i] - _copy.Min()) / (_copy.Max() - _copy.Min()); // 逐元素缩放至0~1 } } } 开 MT5 自建个 vector 丢进这个函数,改两行把范围打印出来,就能确认你的激活选型有没有踩中那 3 个匹配函数。外汇与贵金属模型训练波动大、过拟合风险高,缩放和激活不匹配会放大回测失真。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Function to apply Feature-Scaling class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void Cnormalize::FeatureScaling(vector &Data) { vector _copy; _copy.Copy(Data); if(_copy.Max() - _copy.Min() > class="num">0.0) { for (class="type">int i = class="num">0; i < class="type">int(Data.Size()); i++) { Data[i] = (_copy[i] - _copy.Min()) / (_copy.Max() - _copy.Min()); } } }