
您应当知道的 MQL5 向导技术(第 30 部分):聚焦机器学习中的批量归一化
概述
批量归一化,是将数据投喂给神经网络层之前,对其标准化的形式,它正在提高网络的性能,但至于为何要这样做,存在一些分歧。而就该主题的原始论文中,所述原因是内部协变量偏移,其可以理解为减轻“上游层”输入数据不平衡对下游层产生级联效应的影响,但最近的这项研究声称经验性能的改进归结为平滑,因为在归一化的情况下,内层梯度的可变性较小。
从交易者的方位,兴趣在于为何批量归一化令神经网络的性能产生差异,这是用其开发和测试系统的充分理由,如此成为我们手头的任务。我们将验证三种主要形式的批量归一化,即 标准伸缩、特征伸缩 和 稳健伸缩。其中每个都是一个非常简单的算法,我们将运行搭配每个算法的 EA 进行测试,作为比照,我们还将运行不用批量归一化的 EA 进行测试。故此,我们的文章将回逆到我们在最后两篇文章(有关学习率)之前所用的格式,我们现在将讲述所有三种归一化,然后测试报告同样对照,最后得出结论。
与本系列的所有内容一样,本文强调使用向导组装的 EA 来测试我们的新思路。对于新读者,可以从此处和此处获得有关如何行事的概述,这 2 篇文章提供了一些如何使用本文末尾所附代码的指导。至于本文,我们正采用相当多的自定义数据枚举作为可优化输入。MQL5 内置枚举可在自定义信号文件头部声明,它们将自动示意作为输入,并作为信号过滤器的一部分进行初始化。然而,当枚举是自定义的时,将它们放在头部会阻止文件在 MQL5 向导中可见(或可识别),这意味着您无法进行向导组装。目前,我们的折中方法是从自定义信号类头部里省略它们,但在信号类内仍声明参数及其赋值函数,就如同所有输入参数一样。一旦向导组装完成,我们就手动更改输入参数的名册,并初始化信号类,以便加上自定义枚举参数。
我发现了该问题的一个变通技巧,我认为它涉及在信号类本身当中声明枚举,但我不记得具体细节,故我们只得手工编辑进行安置。我们的测试 EA 含有学习率类型、自适应学习率类型的自定义枚举,对于本文来说,最显要的是归一化类型的枚举。后者的代码分享如下:
enum Enormalize { NORMALIZE_NONE = -1, NORMALIZE_ROBUST = 0, NORMALIZE_FEATURE = 1, NORMALIZE_STANDARD = 2, };
为了在 EA 内使用和优化该枚举,我们需要手工添加枚举参数作为输入,如下所示:
.... 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'。完成如下:
... filter0.ActivationType(Signal_CMLP_ActivationType); filter0.NormalizeType(Signal_CMLP_NormalizeType); filter0.LearningType(Signal_CMLP_LearningType); filter0.AdaptiveType(Signal_CMLP_AdaptiveType); ...
一旦操作完成,由向导组装的 EA 就能够正常地协同我们的自定义信号类。如上讲述了我们即将研究的自定义枚举的 3 种归一化格式,在更深入地查看每种批量枚举类型之前,我们现在简要概览这个主题,之后我们将研究在自定义信号类中的整体实现。
神经网络中批量归一化的参数
官方来讲,批量归一化的大部分益处仍然围绕着 2015 年令其曝光的论文中提出的论点而发展。鉴于目前存在一些它如何能够达成其实证结果的争论,故这些原始论点或许不具备初期的相同分量。无论如何,我们至少于此讲述它们作为该主题的介绍。批量归一化标榜可通过降低上述内部协变量偏移来稳定训练过程。它提升了收敛速度,因为在每层的激活都“更标准”。此外,它还降低了网络训练前所用初始化权重的敏感度。初始权重的影响往往不成比例,不仅在于最终达到的权重,而且对于训练过程需多久亦然。
甚至,通过令网络对某些神经元的权重不那么敏感,批量归一化就可充当正则化器。它还允许通过降低梯度消失问题来训练深度神经网络。它解耦了层依赖关系,令训练过程不受输入特征尺度变化的影响,强化了模型普适性,有助于训练不同的批量大小,并准备好验证各种学习率规程。这些就是批量归一化标榜的一些优势。现在我们来研究每种类型。
特征缩放
这种形式的归一化在一层的向量中的所有数据归一化为 0.0 到 +1.0 的范围。向量中每个数值的公式为:

其中
- x' 是归一化值
- x 是原始的非归一化数值
- min(x) 是返回向量最小值的函数
- max(x) 返回向量最大值
这个非常简单的算法,其 MQL5 的实现直截了当,如下所示:
//+------------------------------------------------------------------+ //| Function to apply Feature-Scaling //+------------------------------------------------------------------+ void Cnormalize::FeatureScaling(vector &Data) { vector _copy; _copy.Copy(Data); if(_copy.Max() - _copy.Min() > 0.0) { for (int i = 0; i < int(Data.Size()); i++) { Data[i] = (_copy[i] - _copy.Min()) / (_copy.Max() - _copy.Min()); } } }
无论何时选择一种归一化算法,在每层所用的激活类型都是一个重要的参考因素。这是因为并非所有激活函数的输出结果都在同一范围内。以下是各种激活函数的表格,在 MQL5 中作为枚举提供,以及它们各自的输出范围:
激活函数 | 输出范围 | |
指数线性单元(ELU) | (-1, ∞) | |
指数(Exp) | (0, ∞) | |
高斯误差线性单位(GELU) | (-∞, ∞) | |
硬 Sigmoid | [0, 1] | |
线性 | (-∞, ∞) | |
泄漏整流线性单元(泄漏 ReLU) | (-∞, ∞) | |
整流线性单元(ReLU) | [0, ∞) | |
缩放指数线性单元(SELU) | (-∞, ∞) | |
Sigmoid | (0, 1) | |
Softmax | (0, 1), sums to 1 | |
Softplus | (0, ∞) | |
Softsign | (-1, 1) | |
Swish | (-∞, ∞) | |
双曲正切(Tanh) | (-1, 1) | |
阈值校正线性单元(阈值 ReLU) | {0} ∪ [θ, ∞), 其中 θ > 0 | |
如是所显,我们仅有 3 个激活函数,提供的输出在一个范围内类似于特征缩放的批量归一化算法。它们是 硬-Sigmoid、Sigmoid 和 Softmax。其它一切都超出了边界。如果网络要避免训练期间经常出现的梯度消失/膨胀问题,那么这种输出范围的匹配至关重要。
硬-Sigmoid 激活是 sigmoid 函数的简单近似值,由以下公式定义:

故此,无论 x 的输入是什么,输出都会受限在 0.0 到 +1.0 之间。这种与特征缩放的匹配确保了一致且可解释的激活。此外,硬-Sigmoid 的计算效率很高,因此适用于像变换器这样的大型、和非常深的网络。
Sigmoid 激活函数可以说是所有激活函数中最受欢迎的,因为它通过控制输出范围最大限度地减少了梯度膨胀的问题,并且仍能设法维护一定程度的可变性,这与上面的硬-Sigmoid 激活不同。其方程式为:

其中
- e 是欧拉(Euler)常数
- x 是归一化值
其映射中的平滑度与归一化特征尺度范围非常一致。此外,它避免了梯度饱和度问题,因为输出 0.0 或 1.0 数值极罕见。
一个类(或数据向量)运用 Softmax 激活进行归一化,不仅令其所有值在 0.0 到 1.0 范围内,而且这些归一化数值的总计为 1.0。由此衍生出的如下所示:

其中
- z i 是输入向量 Z 的第 i 个元素
- K 是 Z 的向量大小
- e 是欧拉(Euler)常数
Softmax 通常用于多类分类、或具有多个维度的数据点比较。输出意味着一种概率分布,其中没有单个输出的支配权与其它不成比例,从而创造了一种公平的竞赛区域。此外,合计为 1 可确保不存在大数字,其在非常深的网络、或变换器中可能会产生下游计算问题。
故此,硬-Sigmoid、Sigmoid 和 Softmax 是三种适合特征缩放归一化的激活函数算法,因为这三种算法的输出都倾向于与此该批量归一化的输出匹配。利用 MQL5 实现这些算法非常简单,因为所有激活算法及其各自的衍生品都可从向量数据类型的内置函数中访问。这方面的例子是在 'Forward()' 和 'Backward()' 函数中,它们形成了本文附带的 'Cmlp' 类的一部分。再者,官方指南也均可用于激活和派生函数。
上面已强调了特征缩放算法的实现。这是一个直截了当的函数,即检查零除,确保向量中的最大值和最小值不同。缩放函数非常简单,这对于计算最小化非常重要,因为许多网络可能非常深,并且采用变换器格式。
稳健伸缩
这种形式的归一化在其输出中提供了更多的回旋余地,在某些条件下,它给出的数值略微超出 -1.0 和 +1.0,但在大多数情况下,所有数据往往介于 -1.0 和 +1.0 之间。例外情况很常见,它们发生的实例其中存在异常值,如此这般它们与中位数的距离如此之大,以至于超过了四分位范围。对此的管控方程如下:

其中:
- x 是原始值
- median(x) 是向量或类的中位数
- IQR(x) 是该类的第75 个百分位数减去第 25个百分位数。
虽然稳健伸缩对异常值不如特征伸缩敏感,但不会忽略它们。事实上,远离中位数的异常值倾向提供量级超过 1 的归一化值。还有,通常下,如果与四分位范围相比,数据(最大到最小范围)的可变性很高;换言之,状况是其中数据主要以中位数为中心围绕,但数字朝向极值递减,如此这般极值不一定是异常值。在这些实例中,更接近极值的数值归一化后量级会大于 1。
上面的激活输出范围表格,很清楚,与稳健缩放配对的激活函数的理想选择是 softsign 激活、或 TANH 激活。这是因为,尽管我们上面提到的稳健缩放异常值输出限制在 -1.0 到 +1.0 范围内,但它们与所有其它激活是最佳匹配。比其更重要的是,这些激活输出不会产生任何无穷值,故此梯度消失和膨胀的风险大大降低。
Softsign 由一个非常简单的方程定义:

其中
- x 是类或向量中的数据点
- |x| 是该数据的量级
从其公式来看,很清楚 x 或归一化向量中的数据理想情况下应该是浮点类型,且量级在 0.0 到 2.0 之间。当 x 值从 2.0 放大时,那么变异性、以及归一化数据的解释将受到阻碍。这点表示归一化数值的饱和度远离零,但在零值附近平滑过渡。数值的对称性、及围绕零中心的数值,与稳健缩放非常一致。此外,已经观察到 softsign 的梯度不会像其它激活函数(例如流行的 sigmoid 激活)那样容易消失。在依据大型数据集执行扩展训练时,这可能非常有益。
TANH 激活是另一种绑定在 -1.0 到 +1.0 范围的算法,应可工作良好,且稳健缩放。其方程式给出如下:

其中
- x 是类或向量中的数据点
- e 是欧拉(Euler)常数
TANH 算法在形式上与 sigmoid 非常相似,明显的区别在于它以 0.0 为中心围绕,而 sigmoid 以 0.5 为中心围绕。据报道,它还提供了比 sigmoid 函数更平滑的梯度。Sign 范围与 softsign 一样,令其成为另一个搭配稳健缩放的理想候选者,正如我 们上面看到的。嘈杂的数据、或者含有大量异常值的数据集,从交易者的角度来看,可能包括极具波动证券的价格缓冲,这都是与稳健伸缩和 TANH 激活搭配使用的理想数据集。这源于它能够处理不同规模的数据集。
MQL5 就像我们所说的一样无缝实现上面 3 个用于特征缩放的激活函数。向量数据类型同时具有内置的激活函数和衍生函数,即输出结果到向量中当作输入。正如上面已经分享的那样,这里和这里分别概述了这一点。
在归一化类中实现的 MQL5 稳健缩放算法,如下所示:
//+------------------------------------------------------------------+ //| Function to apply Robust-Scaling //+------------------------------------------------------------------+ void Cnormalize::RobustScaling(vector &Data) { vector _copy; _copy.Copy(Data); if(_copy.Percentile(75) - _copy.Percentile(25) > 0.0) { for (int i = 0; i < int(Data.Size()); i++) { Data[i] = (_copy[i] - _copy.Median()) / (_copy.Percentile(75) - _copy.Percentile(25)); } } }
与上面的特征缩放算法一样,对四分位范围执行零除检查。出于上面已经强调的效率原因,这次又是一个简单的算法。
标准分数
标准分数可能更常见,因为 Z-分数可以说是最流行的归一化算法。在维基百科上的批量归一化页面中,它是唯一用到的归一化函数。它涉及将数据类、或向量转换为均值为 0、且标准差为 1 的数值。这与我们上面讨论的特征缩放和稳健缩放明显不同,其中输出数据的范围是重点。其方程式如下:

其中
- Z 是归一化数值
- μ 是均值
- σ 是标准差
因此,从这个方程我们可以看到,输出具有一个未绑定的范围,如此可能的输出可从负无穷到正无穷。这应该是一个危险标志,因为梯度膨胀很快就会成为一个问题,特别是如果与此一起使用的激活函数也具有未绑定的输出。不过,其做到了输出以零为中心,这能良好地稳定使用梯度下降进行网络训练过程。甚至,它在应对正态分布的数据时非常有效,因其确保输入数据点对于学习过程做出均匀的贡献。
它也有缺点,最主要的是其对异常值的敏感性,假设数据是高斯分布,且如前所述,输出是未绑定的。该标准分数的 MQL5 实现可达成如下:
//+------------------------------------------------------------------+ //| Function to apply Standard-Score //+------------------------------------------------------------------+ void Cnormalize::StandardScore(vector &Data) { vector _copy; _copy.Copy(Data); if(_copy.Std() != 0.0) { for (int i = 0; i < int(Data.Size()); i++) { Data[i] = (_copy[i] - _copy.Mean()) / _copy.Std(); } } }
有趣的是,这并非 MQL5 函数中的标准功能。为避免所有这三个归一化函数的零除,与其检查零分母,不如在底部添加一个很小的非零双精度值,这通常称为 ε。然而,当配以特征缩放或稳健缩放行事时,这样实施可能会导致分母为零时,产生非常大的归一化数值。
归一化变换器
一旦层输入数据经由特征缩放、稳健缩放、或标准分数进行了归一化,来自归一化的输出将被投喂到批量归一化变换器之中。它也是一个相对简单的函数,由以下公式定义:

其中
- y 表示索引 i 处向量中的输出
- x 是索引 i 处先前归一化的数据点
- γ 和 β 是可优化的浮点参数
代码也不需要太多,正如我们的 MQL5 如下所示:
//+------------------------------------------------------------------+ //| Batch Normalizing Transform //+------------------------------------------------------------------+ void Cnormalize::Transform(Enormalize Type, vector &Output, vector &BatchTransform, vector &Beta, vector &Gamma) { if(Type != NORMALIZE_NONE) { if(Type == NORMALIZE_STANDARD) { StandardScore(Output); } else if(Type == NORMALIZE_FEATURE) { FeatureScaling(Output); } else if(Type == NORMALIZE_ROBUST) { RobustScaling(Output); } //Transformer BatchTransform.Init(Output.Size()); BatchTransform.Fill(0.0); for(int i = 0; i < int(Output.Size()); i++) { BatchTransform[i] = (Gamma[i] * Output[i]) + Beta[i]; } } }
经由该变换器获取归一化输出的参数主要有 5 个。首先,它引入灵活性和重新校准,来恢复原始非归一化数据的代表性。灵活性,因为变换之后,输出数据不一定处于最优状态,以便促进网络的学习。举例,当使用标准分数归一化时,每个向量的输出数据始终具有均值 0,和标准差 1。这和其它类似情况或许太极端了,网络无法有效训练。这就是为什么引入可学习/可优化参数 γ 和 β,可帮助将这些数据集重新校准为最优训练形式。
其次,在初始归一化函数可能潜在未绑定输出时,就像标准分数,该归一化变换器能帮助避免梯度消失和膨胀。这应通过优调 γ 和 β 来达成。第三,有论调说变换器维护方差,可通过方差缩放来有效学习。γ 参数能调整归一化输出的方差,因为它是直接倍数。配以这种调整差异的能力,学习过程可加以优调,从而适应网络架构。
此外,变换器能通过网络适配数据,来强化网络的学习动态,这通常会导致收敛性改善。可优化的 γ 和 β 参数再次指导该过程,如此数据缩放来更好地使用网络层数和大小设置,最终获得理想结果所需的训练局次更少。最后,正则化归一化数据的输出缩放和偏移,论调认为工作类似于舍弃正则化。正则化旨在推动网络学习数据集中更多的底层形态,并减少噪声,故此 γ 和 β 参数,其为矢量参数,对于每层都是唯一的。这种基于参数的定制除了对网络进行正则化之外,好在 γ 和 β 的参数特定作用,我们在概述中讨论的协变量偏移也可以更好地利用。
策略测试结果
为了测试,我们取用 EURJPY 品种的 2023 年日线时间帧。标准分数归一化,使用未绑定的数据,不一定拥有合适的激活函数。可以使用我们在本文中提到的绑定激活函数,不过其测试留待读者。出于我们的目的,我们仅测试稳健伸缩归一化,和特征伸缩归一化。我们分别将它们与 softsign 和 sigmoid 激活配对。
对于特征缩放,我们得到了以下结果:
对于稳健伸缩,我们有:
作为对照,我们在用 EA 执行测试时,运行雷同的设置,但无归一化。以下是我们的结果:
结束语
故此,汇总我们的主要发现,批量归一化是参数密集型的,且由此计算成本昂贵。在选择所用归一化算法时要格外精心,尽管标准分数非常流行和普遍,但它的无绑定输出能导致非常大的梯度,从而阻碍收敛,并减慢整个训练过程。不过,如果使用具有绑定输出,像是特征缩放或稳健缩放的备选归一化算法,且这些算法与拥有相似绑定输出的激活函数配对,则可加速训练过程。不幸的是,速度是这里的一个关键考虑因素,因为如前所述,批量归一化涉及很多参数,且是计算密集型的,因此配对归一化算法与激活函数、以及最优网络层和大小时,需要格外小心。
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/15466

