MQL5 中的矩阵和向量:激活函数·综合运用
希格玛函数在神经网络层里的实际写法
希格玛(Sigmoid)把任意实数压进 (0,1) 区间,公式就是 f = 1 / (1 + e^{-x})。它的导数可以改写成 d = e^{x} / (e^{x} + 1)^2,反向传播时不用重算前向值,直接复用激活后的输出就能拿到梯度。 这个激活函数本身没有需要调的超参,对 MT5 上的神经层来说意味着少一个坑。下面这段是 MQL5 里直接调用的写法,注意 vector_a 激活结果写进 vector_c,类型宏用 AF_SIGMOID。 外汇与贵金属行情噪声大,用希格玛做概率输出层时,极端行情下梯度易饱和,信号可能倾向钝化,属高风险建模环节,建议开 MT5 用历史 tick 跑一遍观察饱和区间。
f = class="num">1 / (class="num">1 + exp(-x)); d = exp(x) / pow(exp(x) + class="num">1, class="num">2); vector_a.Activation(vector_c,AF_SIGMOID);
「Softplus 在向量层怎么落地」
Softplus 把任意实数映射到正值区间,曲线在负轴平滑趋近于 0、正轴趋近线性,相比 ReLU 少了硬拐点。它不带任何可训练额外参数,这对做轻量特征变换的网络很实用。 导数形式为 exp(x)/(exp(x)+1),恰好是 Logistic 形态,反向传播时梯度不会在负区直接归零,缓解了死神经元问题。外汇与贵金属行情里负向冲击频繁,这种平滑梯度在高频特征提取中可能更稳,但杠杆品种高风险依旧存在。 下面这段 MT5 代码直接演示了函数与导数写法,以及用 vector 类的激活接口一键计算: f = log(exp(x) + 1); // Softplus 原函数:先取 exp(x) 加 1 再取自然对数 d = exp(x) / (exp(x) + 1); // 对应导数,等价于 sigmoid vector_a.Activation(vector_c, AF_SOFTPLUS); // 将 vector_a 经 Softplus 激活结果写入 vector_c 开 MT5 新建脚本粘入即可验证:换不同 x 向量,观察负区输出是否平滑贴 0。
f = log(exp(x) + class="num">1); d = exp(x) / (exp(x) + class="num">1); vector_a.Activation(vector_c, AF_SOFTPLUS);
◍ Softsign 在矩阵库里的调用与导数特性
| Softsign 的映射公式是 f(x)=x/( | x | +1),相比 Tanh 它的尾部更平缓,梯度衰减更慢,适合需要长尾响应的特征变换。导数 d(x)=1/( | x | +1)^2,没有需要调的超参,调用时只认激活类型即可。 |
|---|
在 MQL5 的 vector 体系里,直接走 Activation 方法并传 AF_SOFTSIGN 宏就能把结果写进目标向量,不用自己写循环。下面这段是最小可用形态,复制到 MT5 脚本里改 vector_c 的来源就能跑。 代码逐行看:第一行是函数值定义,x 除以绝对值 x 加 1;第二行是导数,分母平方后取倒数;第三行调用库内激活,把 vector_a 经 Softsign 算完塞进 vector_c。外汇与贵金属行情噪声大,用这类平滑激活做特征压缩时仍属高风险实验,回测不过别硬上。
f = x / (|x| + class="num">1) d = class="num">1 / (|x| + class="num">1)^class="num">2 vector_a.Activation(vector_c,AF_SOFTSIGN);
Swish 在向量激活里的实际写法
Swish 把输入 x 乘以 sigmoid 分支,形状在负区不像 ReLU 那样硬截断,而是带一点平滑负值通过。默认 beta = 1 时,它退化为 x/(1+e^-x);beta 越大越接近 ReLU 的拐点形态。 导数项不能只记公式,回测里若自己写梯度下降,必须配套 d = e^(beta*x)*(beta*x + e^(beta*x) + 1) / (e^(beta*x)+1)^2,否则反向传播数值会对不上。 下面这段可直接丢进 MT5 的向量类里跑,beta 换 2.0 或 0.5 能肉眼看到负区被压扁或放开的程度差异,外汇与贵金属样本上属于高风险实验,仅作函数验证用。
f = x / (class="num">1 + exp(-x*beta)); class="type">class="kw">double tmp = exp(beta*x); d = tmp*(beta*x + tmp + class="num">1) / pow(tmp+class="num">1, class="num">2); vector_a.Activation(vector_c,AF_SWISH); class=class="str">"cmt">// call with the class="kw">default parameter vector_a.Activation(vector_c,AF_SWISH,class="num">2.0); class=class="str">"cmt">// call with beta = class="num">2.0 vector_a.Activation(vector_c,AF_SWISH,class="num">0.5); class=class="str">"cmt">// call with beta=class="num">0.5
「双曲正切在矩阵层的落地写法」
TanH 把任意实数压进 (-1, 1) 区间,和 Sigmoid 不同,它输出以 0 为中心,梯度消失比后者轻一些,但深网里仍会衰减。导数可直接写成 1/cosh²(x),没有可训练参数,调用时只认激活枚举。 MT5 的 vector 类已经封装好接口,不用手撸循环。下面三行是实际能贴进 EA 或指标工程的片段,x 为输入标量,vector_a 与 vector_c 为同维向量容器。 别把正态当圣经:TanH 对输入尺度仍敏感,x 绝对值超过 3 以后梯度已接近 0,喂数据前做标准化比调函数本身更管用。外汇与贵金属行情异方差强,直接丢裸价差进网络容易让层输出饱和,高风险且常失效。
f = tanh(x); d = class="num">1 / pow(cosh(x),class="num">2); vector_a.Activation(vector_c,AF_TANH);
◍ TReLU 的阈值截断逻辑与导数断点
阈值修整线性单元(TReLU)在输入大于阈值 theta 时原样输出,否则直接归零。默认 theta 取 1,这意味着喂进去的净值序列里,低于 1 的部分全部被抹成 0,只有突破阈值的片段才参与后续梯度传播。 它的导数同样硬切换:x 大于 theta 时导数为 1,其余为 0。这种非零即一的梯度在反向传播里不会像 sigmoid 那样出现饱和衰减,但也带来一个现实问题——当一批样本全落在阈值左侧,整层梯度直接死掉,权重不再更新。 下面这段 MT5 向量激活调用演示了三种 theta 设定。默认调用走 theta=1;传 0.0 等于把 ReLU 退化成标准 ReLU;传 2.0 则把截断线抬高,更多样本会被压成 0,网络在该层变得更稀疏。外汇与贵金属特征工程里用 TReLU,需意识到阈值偏移会直接改变信号通断比例,属高风险调参。
if(x > theta) f = x; else f = class="num">0; if(x > theta) d = class="num">1; else d = class="num">0; vector_a.Activation(vector_c,AF_TRELU); class=class="str">"cmt">// call with class="kw">default parameter vector_a.Activation(vector_c,AF_TRELU,class="num">0.0); class=class="str">"cmt">// call with theta = class="num">0.0 vector_a.Activation(vector_c,AF_TRELU,class="num">2.0); class=class="str">"cmt">// call with theta = class="num">2.0
给 ReLU 留一道负轴活口
标准 ReLU 在输入为负时直接归零,梯度也断掉,深层网络里容易有神经元‘死透’。PReLU 的做法是负轴不强行压零,而是乘一个可学习的 alpha 系数,让负区间也保留一点响应和梯度。 上面这段 MT5 代码就是 PReLU 的前向与导数核心:x[i] 大于等于 0 时输出等于输入、导数取 1;小于 0 时输出变成 alpha[i]*x[i]、导数退回 alpha[i] 本身。alpha 这里初始化为全 0.1 的向量,意味着负轴默认只保留 10% 的斜率,你可以进 MT5 把 0.1 调成 0.01 或 0.25 看激活分布怎么变。 外汇与贵金属行情序列噪声大,负轴留活口可能让网络对反向波动更敏感,属于高风险建模选择,实盘前务必用历史数据回测确认过拟合程度。
if(x[i] >= class="num">0) f[i] = x[i]; else f[i] = alpha[i]*x[i]; if(x[i] >= class="num">0) d[i] = class="num">1; else d[i] = alpha[i]; vector alpha=vector::Full(vector_a.Size(),class="num">0.1); vector_a.Activation(vector_c,AF_PRELU,alpha);
「Softmax 把向量压成概率分布」
Softmax 的输出不是只看单个输入值,而是把整个向量的值都拉进来一起算。它的结果满足所有分量之和恒为 1,所以常被放在分类模型最后一层,把原始得分转成可读的概率倾向。 下面这段 MQL5 风格代码展示了核心计算:先对向量每个元素取指数并求和,再用单点指数除以总和得到激活值 f,导数直接写成 f*(1-f)。这种写法在反向传播里能省掉 Jacobian 矩阵的展开。 sum_exp = Sum(exp(vector_a)) f = exp(x) / sum_exp d = f*(1 - f) 如果把激活函数拿掉,输入向量会原样进输出向量,这等价于斜率 alpha=1、偏移 beta=0 的线性激活。做 MT5 上的分类信号实验时,对比带 Softmax 与纯线性输出,你会看到前者在多类互斥判断上更不容易给出模糊权重。外汇与贵金属行情受宏观事件扰动大,这类模型信号仅作概率参考,实盘高风险。
sum_exp = Sum(exp(vector_a)) f = exp(x) / sum_exp d = f*(class="num">1 - f)
◍ 把这几个激活函数拖进 MT5 跑一遍
附带的 ActivationFunction.mqh 里藏着可直接复用的激活函数与导数实现,不用自己从头写。下面这段是从文件里摘出的核心片段,建议直接存成 .mqh 挂到你的 EA 或指标工程里调用。 [CODE] double activationSigmoid(double x){ return(1/(1+MathExp(-1.0*x))); } double derivativeSigmoid(double activation_value){ return(activation_value*(1-activation_value)); } double activationReLuShiSig(double x,double shift=-6.0){ if(x>0.0){ return(activationSigmoid(x+shift)); } return(0.0); } double derivativeReLuShiSig(double output){ if(output>0.0){ return(derivativeSigmoid(output)); } return(0.0); } double activationLimitedExponential(double x){ return(MathExp(x)/149.00); } //而导数本身就是 double derivativeLimitedExponential(double x){ return(x); } [/CODE] 逐行拆解:activationSigmoid 用 1/(1+e^-x) 把任意实数压到 (0,1),是标准 sigmoid;derivativeSigmoid 接收已算出的激活值,用 a*(1-a) 直接得导数,省一次指数运算。activationReLuShiSig 对 x>0 才走 sigmoid 并左移 6.0(默认 shift),否则返回 0,相当于带偏移的 ReLU-Sigmoid 混合。其导数 derivativeReLuShiSig 仅在输出大于 0 时回传 sigmoid 导数,其余为 0。activationLimitedExponential 用 MathExp(x)/149.0 限制幅度,x=5.0 时约得 149/149=1.0,把输入域 (-5,5) 映射到接近 (0,1);它的导数实现直接返回入参 x,属于示例性的简化写法,实盘接梯度下降前要自己核对数学正确性。 外汇与贵金属杠杆高、滑点随机,这类函数仅作特征变换或信号压缩用,不暗示任何方向。开 MT5 新建脚本把上面代码贴进去,用 PlotActivationFunction.mq5 把曲线画出来,重点看 shift=-6.0 时 ReLuShiSig 在 x∈[-12,12] 的过渡带是不是你要的形状。
class="type">class="kw">double activationSigmoid(class="type">class="kw">double x){ class="kw">return(class="num">1/(class="num">1+MathExp(-class="num">1.0*x))); } class="type">class="kw">double derivativeSigmoid(class="type">class="kw">double activation_value){ class="kw">return(activation_value*(class="num">1-activation_value)); } class="type">class="kw">double activationReLuShiSig(class="type">class="kw">double x,class="type">class="kw">double shift=-class="num">6.0){ if(x>class="num">0.0){ class="kw">return(activationSigmoid(x+shift)); } class="kw">return(class="num">0.0); } class="type">class="kw">double derivativeReLuShiSig(class="type">class="kw">double output){ if(output>class="num">0.0){ class="kw">return(derivativeSigmoid(output)); } class="kw">return(class="num">0.0); } class="type">class="kw">double activationLimitedExponential(class="type">class="kw">double x){ class="kw">return(MathExp(x)/class="num">149.00); } class=class="str">"cmt">//而导数本身就是 class="type">class="kw">double derivativeLimitedExponential(class="type">class="kw">double x){ class="kw">return(x); }