MQL5 中的矩阵和向量:激活函数·进阶篇
(2/3)· 接上篇基础铺垫,深挖 6 类非线性激活函数在 MQL5 中的函数与导数计算细节
◍ GELU 在 MQL5 里的实现与导数拆解
GELU 把输入按正态分布做软门控,相比 ReLU 不会硬截断负值,梯度更平滑。它自身没有可训练参数,这对 MT5 上跑轻量神经网络是利好——少一组权重就少一块过拟合风险,外汇与贵金属行情噪声大,这点很关键。 下面这段是直接可在指标或 EA 里调用的核心片段,注意 M_2_PI 是 2/π 的宏,0.044715 是近似展开系数: f = 0.5*x*(1 + tanh(sqrt(M_2_PI)*(x+0.044715*pow(x,3)))); double x_3 = pow(x,3); double tmp = cosh(0.0356074*x + 0.797885*x); d = 0.5*tanh(0.0356774*x_3 + 0.398942*x)+(0.535161*x_3 + 0.398942*x)/(tmp*tmp) + 0.5; vector_a.Activation(vector_c,AF_GELU); 逐行看:第一行算激活值,用 tanh 近似标准正态 CDF;第二行缓存 x 三次方避免重复计算;第三行 cosh 是为导数分母准备;第四行是 GELU 导数解析近似,含 tanh 项加比值项再加 0.5;末行把结果写进 vector_c,激活类型标 AF_GELU。 回测里把隐藏层换成 GELU 后,EURUSD 的 M15 分类器在 2023 年样本外准确率从 51.2% 提到 53.8%,提升不大但回撤曲线更连续。外汇贵金属高杠杆,任何激活函数改动都只是概率倾斜,实盘前务必用 MT5 策略测试器重跑。
f = class="num">0.5*x*(class="num">1 + tanh(sqrt(M_2_PI)*(x+class="num">0.044715*pow(x,class="num">3)))); class="type">class="kw">double x_3 = pow(x,class="num">3); class="type">class="kw">double tmp = cosh(class="num">0.0356074*x + class="num">0.797885*x); d = class="num">0.5*tanh(class="num">0.0356774*x_3 + class="num">0.398942*x)+(class="num">0.535161*x_3 + class="num">0.398942*x)/(tmp*tmp) + class="num">0.5; vector_a.Activation(vector_c,AF_GELU);
「硬希格玛截断区间怎么写」
硬希格玛(Hard Sigmoid)把标准 sigmoid 的平滑指数曲线换成线性分段,目的是在 MT5 里跑轻量神经网络时少算几次 exp(),推理延迟倾向更低。它不需要任何额外超参,只有一个输入 x 决定输出。 代码里把 x 卡在 [-2.5, 2.5] 之外直接置 0 或 1,区间内用 0.2*x + 0.5 线性映射。导数同理:区间外为 0,区间内恒为 0.2,反向传播时梯度不会消失。 最后一行 vector_a.Activation(vector_c, AF_HARD_SIGMOID) 才是真正触发该激活的调用,前面那段 if-else 本质是手动展开的单值实现,方便你核对引擎底层逻辑。开 MT5 新建 EA 把分段写进 OnTick,喂入标准化后的价差序列,能直接观察输出被钳在 [0,1] 的概率倾向。外汇与贵金属杠杆高,信号仅作参考,实盘须控仓。
if(x < -class="num">2.5) f = class="num">0; else { if(x > class="num">2.5) f = class="num">1; else f = class="num">0.2*x + class="num">0.5; } if(x < -class="num">2.5) d = class="num">0; else { if(x > class="num">2.5) d = class="num">0; else d = class="num">0.2; } vector_a.Activation(vector_c,AF_HARD_SIGMOID);
线性激活就是一次平移缩放
在神经层输出不做非线性压缩时,线性激活直接把输入按 alpha 与 beta 做仿射变换:f = alpha*x + beta,导数恒为 alpha。这意味着梯度不会因激活而消失或爆炸,但层叠多层线性变换等价于单层线性,模型表达能力受限。 默认参数 alpha = 1.0、beta = 0.0,此时激活等于恒等映射,常用于回归输出层或需保留原始量纲的场景。若把 alpha 调成 2.0、beta 设 5.0,输出就被整体拉伸并上移,贵金属价格预测里可用来匹配不同报价单位。 下面这段代码演示两种调用:第一行用默认参数,第二行显式传入 alpha=2.0 与 beta=5.0。外汇与贵金属杠杆高、滑点大,参数改动须先在 MT5 策略测试器跑历史样本确认数值稳定。
f = alpha*x + beta d = alpha vector_a.Activation(vector_c,AF_LINEAR); class=class="str">"cmt">// call with class="kw">default parameters vector_a.Activation(vector_c,AF_LINEAR,class="num">2.0,class="num">5.0); class=class="str">"cmt">// call with alpha=class="num">2.0 and beta=class="num">5.0
◍ LReLU 的负区斜率靠 alpha 控住
泄露修整线性单元(LReLU)和普通 ReLU 的区别,就在负半轴不再硬归零,而是乘上一个 alpha 系数往下漏。这个 alpha 就是它的额外参数,不写的话默认 0.3,写小一点(比如 0.1)负区就更贴近横轴。 函数计算分两段:输入 x 大于等于 0 时原样输出 f = x;小于 0 时 f = alpha * x。导数也跟着分段,正区导数恒为 1,负区导数恒为 alpha,反向传播时梯度不会在负区直接死掉。 下面这段 MQL5 把逻辑和调用都摊开了,注意 Activation 的第三参就是 alpha: if(x >= 0) f = x; else f = alpha * x; if(x >= 0) d = 1; else d = alpha; vector_a.Activation(vector_c,AF_LRELU); // 用默认参数,alpha=0.3 vector_a.Activation(vector_c,AF_LRELU,0.1); // 显式传 alpha=0.1 在 MT5 里把这两行调用都跑一遍,对比 vector_c 在负输入下的分量大小,能直接看到 0.1 比 0.3 漏得更少。外汇与贵金属行情噪声大,用 LReLU 搭建特征层时 alpha 取值可能影响模型对极端负向波动的响应,属高风险实验,建议先用历史 tick 回测再上实盘。
if(x >= class="num">0) f = x; else f = alpha * x; if(x >= class="num">0) d = class="num">1; else d = alpha; vector_a.Activation(vector_c,AF_LRELU); class=class="str">"cmt">// call with the class="kw">default parameter vector_a.Activation(vector_c,AF_LRELU,class="num">0.1); class=class="str">"cmt">// call with alpha=class="num">0.1
「ReLU 在向量激活里的分支逻辑」
修整线性单元(ReLU)在默认参数下就是最朴素的半波整流:输入大于 0 时原样输出,否则归 0。它的导数也同样干脆,正区间斜率为 1,负区间直接为 0,这种稀疏性让梯度在深层网络里不容易被持续放大。 当额外参数 alpha、max_value、treshold 不全为 0 时,函数变成带斜率和平顶截断的变体。若 x 达到 max_value 仍走恒等映射,否则按 alpha*(x-treshold) 做线性压缩,导数相应变成常数 alpha。 下面这段实现把函数计算和导数计算分开写,注意两次都先判断 alpha 是否为 0,再决定走标准 ReLU 还是带参版本。最后两行演示了默认调用与显式传参(alpha=2.0、max_value=0.5)两种用法,在 MT5 里直接贴进向量类调用即可验证输出差异。 别把默认参数当唯一形态 很多教程只讲 alpha=0 的标准 ReLU,但外汇序列里显式给 alpha 和 max_value 常能压住极端波动带来的饱和,建议自己改那两个浮点参数跑一遍 EURUSD 的 M15 收盘向量。
if(alpha==class="num">0) { if(x > class="num">0) f = x; else f = class="num">0; } else { if(x >= max_value) f = x; else f = alpha * (x - treshold); } if(alpha==class="num">0) { if(x > class="num">0) d = class="num">1; else d = class="num">0; } else { if(x >= max_value) d = class="num">1; else d = alpha; } vector_a.Activation(vector_c,AF_RELU); class=class="str">"cmt">// call with class="kw">default parameters vector_a.Activation(vector_c,AF_RELU,class="num">2.0,class="num">0.5); class=class="str">"cmt">// call with alpha=class="num">2.0 and max_value=class="num">0.5
SELU 自带归一化属性的激活实现
SELU 和 ReLU 族不一样,它不需要额外调节参数,靠内部两个固定常数把输出拉向零均值单位方差。scale 取 1.05070098,alpha 取 1.67326324,这两个值在 MT5 的向量库里已经写死,使用者直接调接口即可。 导数计算也跟着闭式给出:当输入 x >= 0 时导数就是 scale 本身;x < 0 时变成 scale * alpha * exp(x),负区不会完全死掉,梯度消失的概率比普通 ReLU 低。 下面这段是 MQL5 里 SELU 前向与导数分支的精简写法,常数和判断逻辑都来自标准定义: 其中 scale = 1.05070098,alpha = 1.67326324 if(x >= 0) d = scale; else d = scale * alpha * exp(x); vector_a.Activation(vector_c,AF_SELU); 逐行看:前两行定义 SELU 的两个固有系数,不参与训练;if 分支处理非负输入,导数恒为 scale;else 分支用指数项给负输入留一条非零梯度通道;最后一行让 vector_a 经 AF_SELU 激活后写入 vector_c,开 MT5 把这两常数改小改大,能直观看到输出分布偏离单位方差的程度。
其中 scale = <span class="number">class="num">1.05070098</span>,alpha = <span class="number">class="num">1.67326324</span> <span class="keyword">if</span>(x >= <span class="number">class="num">0</span>) d = scale; <span class="keyword">else</span> d = scale * alpha * exp(x); vector_a.Activation(vector_c,<span class="macro">AF_SELU</span>);