您应当知道的 MQL5 向导技术(第 30 部分):聚焦机器学习中的批量归一化(基础篇)
📘

您应当知道的 MQL5 向导技术(第 30 部分):聚焦机器学习中的批量归一化(基础篇)

第 1/2 篇

批量归一化在 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 就能带着自定义枚举跑批量归一化对照。外汇贵金属行情跳空频繁,这类网络信号仅作辅助参考,仓位须自担风险。

MQL5 / C++
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 个匹配函数。外汇与贵金属模型训练波动大、过拟合风险高,缩放和激活不匹配会放大回测失真。

MQL5 / C++
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());
      }
   }
}

常见问题

通常放在全连接层或卷积层输出之后、激活函数之前,这样能把输入分布拉回稳定区间,避免后面层被尺度带偏。
最易出错的是推理阶段仍用训练时的动态均值,应切换为固化后的滑动均值与方差,否则实盘信号会漂移。
可以,小布能读取你的策略结构并标出归一化层位置与推理参数,提示是否误用了训练态统计量。
它对每层输入做标准化并学两个缩放参数,削弱内部协变量偏移,使梯度更新路径更平滑、学习率更耐受。
若归一化后数值范围偏离激活函数敏感区,可能出现大面积饱和或死神经元,表现为回测曲线平坦、实盘无触发。