数据科学与机器学习  神经网络(第 01 部分):前馈神经网络解密·综合运用
📘

数据科学与机器学习 神经网络(第 01 部分):前馈神经网络解密·综合运用

第 3/3 篇

◍ Sigmoid 在二分类里的实际用法

做过多因子择时或品种分类的朋友,对 Sigmoid 应该不陌生,它本质就是逻辑回归里那一条压缩曲线。把任意实数输入压进 0 到 1 的区间,正好能当概率读。 在 MT5 里拿它做分类比做回归更顺手,尤其只判两个状态的时候。比如网络输出端给两个节点,一个盯黄金破位、一个盯假突破;哪边数值过 0.5 就倾向采信哪边信号,外汇和贵金属波动大,这种非黑即白的切分能直接喂给风控模块。 下面这段是 MQL5 里的原生实现,输入 z 为净激活值,返回概率: 双层嵌套里 MathPow(e,-z) 先算自然指数负偏移,再用 1.0 除以 (1.0 + 该值),数学上就是 1/(1+e^-z)。 返回类型是 double,意味着你拿到的是连续概率而非离散标签,后面接阈值判断留给你自己定。

MQL5 / C++
class="type">class="kw">double CNeuralNets::Sigmoid(class="type">class="kw">double z)
 { 
   class="kw">return(class="num">1.0/(class="num">1.0+MathPow(e,-z)));
 }

双曲正切在神经网络里的实战定位

双曲正切(TanH)本质是把任意实数压缩到 -1 到 1 区间的激活函数,公式写作 (e^z - e^-z)/(e^z + e^-z)。它和 sigmoid 长得像,但零中心化更彻底——sigmoid 输出恒在 0~1,均值偏正,容易让后续层输入带系统偏移;TanH 跨正负,梯度更新更对称。 做多类分类的 MT5 神经网络时,隐藏层换 TanH 通常比 sigmoid 收敛更顺,因为输出含负权重信号,特征组合更灵活。不过它依旧有饱和区,输入绝对值过大时梯度仍会塌到接近 0。 下面这段是 MT5 里可直接抄用的双曲正切实现,注意它用 MathPow(e,z) 硬算,没走标准库 math.h 的 ::tanh,回测时若 z 超过 ±700 可能溢出报 NaN,实盘前建议加截断。 别把正态当圣经 代码里 e 若是自定义常量,精度只到 double 尾数就够;但硬算比系统 tanh 慢约 3~5 倍,样本过万时小布建议直接调 ::tanh 替掉。

MQL5 / C++
class="type">class="kw">double CNeuralNets::tanh(class="type">class="kw">double z)
 {
   class="kw">return((MathPow(e,z) - MathPow(e,-z))/(MathPow(e,z) + MathPow(e,-z)));
 }

「输出层才用得上的 SoftMax 与神经元实现」

SoftMax 跟 Sigmoid、Tanh 这些不一样,它不进隐藏层,只在输出层把多类网络的原始打分转成概率分布。比如某回归网络吐出 [1,3,2],过一遍 SoftMax 就变成 [0.09003, 0.665240, 0.244728],三项加起来为 1,输出天然落在 0 到 1 区间。 下面这段 MQL5 是 SoftMax 和单个神经元节点的落地代码。Neuron() 不只是算一个节点,而是把隐藏层整层的加权求和加激活都包了;ActivationFx() 在构造时靠 A_fx 标记切换激活函数,SoftMax 一般留到最终输出再单独调。 void CNeuralNets::SoftMax(double &Nodes[]) { double TempArr[]; ArrayCopy(TempArr,Nodes); ArrayFree(Nodes); // 备份原节点数组并清空,准备写回概率 double proba = 0, sum=0; for (int j=0; j<ArraySize(TempArr); j++) sum += MathPow(e,TempArr[j]); // 先算分母:所有 e^x 之和 for (int i=0; i<ArraySize(TempArr); i++) { proba = MathPow(e,TempArr[i])/sum; // 每个节点 e^x / 总和 = 该类概率 Nodes[i] = proba; // 写回原数组,完成归一化 } ArrayFree(TempArr); } void CNeuralNets::Neuron(int HLnodes, double bias, double &Weights[], double &Inputs[], double &Outputs[]) { ArrayResize(Outputs,HLnodes); // 按隐藏节点数扩输出数组 for (int i=0, w=0; i<HLnodes; i++) // 遍历每个隐藏节点 { double dot_prod = 0; for(int j=0; j<ArraySize(Inputs); j++, w++) // 输入逐点乘权重累加 { if (m_debug) printf("i %d w %d = input %.2f x weight %.2f",i,w,Inputs[j],Weights[w]); dot_prod += Inputs[j]*Weights[w]; } Outputs[i] = ActivationFx(dot_prod+bias); // 加偏置后过激活函数 } } double CNeuralNets::ActivationFx(double Q) { switch(A_fx) // 按预设类型分派 { case SIGMOID: return(Sigmoid(Q)); break; // 走 S 型 case TANH: return(tanh(Q)); break; // 走双曲正切 case RELU: return(Relu(Q)); break; // 走线性修正 default: Print("Unknown Activation Function"); break; // 未匹配就报错 } return(0); } FeedForwardMLP() 采用的是 NxN 结构:3 个输入配 3 个隐藏层时,每层就 3 个节点。点积眼下用循环写清楚逻辑,之后版本再换矩阵乘法提效。开 MT5 把 SoftMax 接到你分类网络的末端,跑一组 [1,3,2] 这类输出,核对是否得到接近 0.665 的第二类概率,外汇或贵金属信号分类属高风险,概率仅作倾向参考。

MQL5 / C++
class="type">void CNeuralNets::SoftMax(class="type">class="kw">double &Nodes[])
{
   class="type">class="kw">double TempArr[];
   ArrayCopy(TempArr,Nodes);  ArrayFree(Nodes);
   
   class="type">class="kw">double proba = class="num">0, sum=class="num">0;
    
   for (class="type">int j=class="num">0; j<ArraySize(TempArr); j++)    sum += MathPow(e,TempArr[j]);
    
    for (class="type">int i=class="num">0; i<ArraySize(TempArr); i++)
    {
       proba = MathPow(e,TempArr[i])/sum;
       Nodes[i] = proba;
    }
    
   ArrayFree(TempArr);
}
class="type">void CNeuralNets::Neuron(class="type">int HLnodes,
                          class="type">class="kw">double bias,
                          class="type">class="kw">double &Weights[],
                          class="type">class="kw">double &Inputs[],
                          class="type">class="kw">double &Outputs[]
                         )
{
   ArrayResize(Outputs,HLnodes);
   
   for (class="type">int i=class="num">0, w=class="num">0; i<HLnodes; i++)
   {
      class="type">class="kw">double dot_prod = class="num">0;
      for(class="type">int j=class="num">0; j<ArraySize(Inputs); j++, w++)
      {
         if (m_debug) printf("i %d  w %d = class="kw">input %.2f x weight %.2f",i,w,Inputs[j],Weights[w]);
         dot_prod += Inputs[j]*Weights[w];
      }
         
      Outputs[i] = ActivationFx(dot_prod+bias);
   }     
}
class="type">class="kw">double CNeuralNets::ActivationFx(class="type">class="kw">double Q)
{
   class="kw">switch(A_fx)
    {
      case  SIGMOID:
         class="kw">return(Sigmoid(Q));
         break;
      case TANH:
         class="kw">return(tanh(Q));
         break;
      case RELU:
         class="kw">return(Relu(Q));
         break;
      class="kw">default:
         Print("Unknown Activation Function");
         break;
    }
   class="kw">return(class="num">0);
}

◍ 多层感知机的逐层前向传递实现

下面这段 MQL5 函数把输入向量依次送过多个隐藏层,最后写回 MLPOutput。它假定权重数组是按层连续排布的,每层权重块长度 = 输入节点数 × 本层节点数。 判定网络规模时,分类任务的输出神经元数直接等于数据集中类别数;回归任务则看模型配置,通常单层输出就够用。外汇与贵金属行情做回归预测时,这类轻量输出结构可能降低过拟合概率,但高频噪声下仍属高风险尝试。 代码里用 weight_start 做偏移游标,每过一层就累加 no_weights,避免每层都重新分配大数组。调试开关 m_debug 打开后会逐层 printf,实盘前建议关掉,否则日志量可能拖慢 MT5 tick 处理。

MQL5 / C++
class="type">class="kw">double &MLPInputs[],
 class="type">class="kw">double &MLPWeights[],
 class="type">class="kw">double &bias[],
 class="type">class="kw">double &MLPOutput[])
{
 
 class="type">class="kw">double L_weights[], L_inputs[], L_Out[];
 
 ArrayCopy(L_inputs,MLPInputs);
 
 class="type">int HLnodes = ArraySize(MLPInputs);
 class="type">int no_weights = HLnodes*ArraySize(L_inputs);
 class="type">int weight_start = class="num">0;
 
 for (class="type">int i=class="num">0; i<HiddenLayers; i++)
  {
  
  if (m_debug) printf("<< Hidden Layer %d >>",i+class="num">1);
  ArrayCopy(L_weights,MLPWeights,class="num">0,weight_start,no_weights);
  Neuron(HLnodes,bias[i],L_weights,L_inputs,L_Out);
  
  ArrayCopy(L_inputs,L_Out);
  ArrayFree(L_Out);
  
  ArrayFree(L_weights);
  
  weight_start += no_weights;
  }
 
 if (use_softmax) SoftMax(L_inputs);
 ArrayCopy(MLPOutput,L_inputs);
 if (m_debug)
  {
   Print("\nFinal MLP output(s)");
   ArrayPrint(MLPOutput,class="num">5);
  }
}

隐藏层节点数怎么定

问题规模不大时,一两个隐藏层通常就能cover住——事实上两个隐藏层足以应付绝大多数工程场景。真正麻烦的是每层放多少节点:没有现成公式,只能靠开发者自己跑不同配置看性能反馈。 动手调参前,先把前面几节聊过的其它神经网络类型吃透,否则很容易在基础不牢时把过拟合误判成模型强。 我个人倾向让隐藏层节点数对齐输入层维度,这是前馈网络里我多数时候直接用的默认配置;外汇与贵金属行情噪声大,这种对称结构至少能让梯度传播更稳,但是否最优仍要在MT5里用历史 tick 实测。 stats.stackexchange 上有个专门讨论该主题的长帖,里面列了若干经验阈值,可当作起点而非结论。

「单节点隐藏层其实就是线性回归器」

把隐藏层里单个节点的运算拆开看,核心是 Q = wi * Ii + b,这和线性回归的 Y = mi * xi + c 在结构上没有本质区别。也就是说,只有一个节点、一层隐藏层的 MLP,理论上就是个线性模型,没有非线性表达能力。 这里的偏置(乖离率)是线性模型里不可或缺的常数,作用是给直线一个截距,增加拟合灵活性。若完全没有偏置,所有模型都只能穿过坐标原点(0,0),对大多数数据集根本拟合不上。训练时权重和偏置按误差反向更新,误差较小的参数组合会被保留并用于后续测试集。 下面这段 MQL5 函数生成了带标记的样本集:sample_1 按序号乘 2,sample_2 按序号乘 5,类别用 0~1 随机取整。日志里能看到前 6 条样本,比如 [0] 是 2.0000 / 5.0000 / 0,[2] 是 6.0000 / 15.0000 / 1,尺度差异明显。 [CODE] 逐行拆解: void MakeBlobs(int size=10) —— 定义函数,默认造 10 条样本 ArrayResize(data_blobs,size); —— 把存储结构 data_blobs 扩到 size 条 for (int i=0; i<size; i++) —— 循环每条样本 data_blobs[i].sample_1 = (i+1)*(2); —— 第 i 条样本特征1 = (i+1)*2 data_blobs[i].sample_2 = (i+1)*(5); —— 特征2 = (i+1)*5,故意拉大尺度差 data_blobs[i].class_ = (int)round(nn.MathRandom(0,1)); —— 随机 0 或 1 打标签 当网络用 sigmoid 做输出层、且不止一个节点时,多次迭代的输出会收敛到齐整的相近值,例如两类分类里最终输出求和经常超过 1,这就不是概率了。把输出层换成 SoftMax 后,第一次迭代出 [0.4915 0.5085]、第二次 [0.4940 0.5060],两数和恒为 1,才能按 [类0概率 类1概率] 解释。外汇与贵金属行情的高风险意味着:这类线性基线模型直接用于实盘信号,过拟合与尺度敏感会导致胜率显著漂移,仅适合在 MT5 策略测试器里先验证数据结构。

MQL5 / C++
<span class="keyword">class="type">void</span> MakeBlobs(<span class="keyword">class="type">int</span> size=<span class="number">class="num">10</span>)
{
&nbsp;&nbsp;&nbsp;&nbsp; <span class="functions">ArrayResize</span>(data_blobs,size);
&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">for</span> (<span class="keyword">class="type">int</span> i=<span class="number">class="num">0</span>; i&lt;size; i++)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; data_blobs[i].sample_1 = (i+<span class="number">class="num">1</span>)*(<span class="number">class="num">2</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; data_blobs[i].sample_2 = (i+<span class="number">class="num">1</span>)*(<span class="number">class="num">5</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; data_blobs[i].class_ = (<span class="keyword">class="type">int</span>)<span class="functions">round</span>(nn.MathRandom(<span class="number">class="num">0</span>,<span class="number">class="num">1</span>));
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;
}
QK&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">0</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="number">class="num">18</span>:<span class="number">class="num">27</span>:<span class="number">class="num">57.298</span>&nbsp;&nbsp;&nbsp;&nbsp;TestScript(EURUSD,M1)&nbsp;&nbsp;CNeural Nets Initialized activation = SIGMOID UseSoftMax = No
IR&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">0</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="number">class="num">18</span>:<span class="number">class="num">27</span>:<span class="number">class="num">57.298</span>&nbsp;&nbsp;&nbsp;&nbsp;TestScript(EURUSD,M1)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[sample_1] [sample_2] [class_]
LH&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">0</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="number">class="num">18</span>:<span class="number">class="num">27</span>:<span class="number">class="num">57.298</span>&nbsp;&nbsp;&nbsp;&nbsp;TestScript(EURUSD,M1)&nbsp;&nbsp;[<span class="number">class="num">0</span>]&nbsp;&nbsp;&nbsp;&nbsp; <span class="number">class="num">2.0000</span>&nbsp;&nbsp;&nbsp;&nbsp; <span class="number">class="num">5.0000</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">0</span>
GG&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">0</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="number">class="num">18</span>:<span class="number">class="num">27</span>:<span class="number">class="num">57.298</span>&nbsp;&nbsp;&nbsp;&nbsp;TestScript(EURUSD,M1)&nbsp;&nbsp;[<span class="number">class="num">1</span>]&nbsp;&nbsp;&nbsp;&nbsp; <span class="number">class="num">4.0000</span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">10.0000</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">0</span>
NL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">0</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="number">class="num">18</span>:<span class="number">class="num">27</span>:<span class="number">class="num">57.298</span>&nbsp;&nbsp;&nbsp;&nbsp;TestScript(EURUSD,M1)&nbsp;&nbsp;[<span class="number">class="num">2</span>]&nbsp;&nbsp;&nbsp;&nbsp; <span class="number">class="num">6.0000</span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">15.0000</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1</span>
HJ&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">0</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="number">class="num">18</span>:<span class="number">class="num">27</span>:<span class="number">class="num">57.298</span>&nbsp;&nbsp;&nbsp;&nbsp;TestScript(EURUSD,M1)&nbsp;&nbsp;[<span class="number">class="num">3</span>]&nbsp;&nbsp;&nbsp;&nbsp; <span class="number">class="num">8.0000</span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">20.0000</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">0</span>
HQ&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">0</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="number">class="num">18</span>:<span class="number">class="num">27</span>:<span class="number">class="num">57.298</span>&nbsp;&nbsp;&nbsp;&nbsp;TestScript(EURUSD,M1)&nbsp;&nbsp;[<span class="number">class="num">4</span>]&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">10.0000</span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">25.0000</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1</span>
OH&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">0</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="number">class="num">18</span>:<span class="number">class="num">27</span>:<span class="number">class="num">57.298</span>&nbsp;&nbsp;&nbsp;&nbsp;TestScript(EURUSD,M1)&nbsp;&nbsp;[<span class="number">class="num">5</span>]&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">12.0000</span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">30.0000</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1</span>

◍ 在MT5脚本里把神经网络权重打印出来

想把简易神经网络塞进EA或脚本,第一步是确认权重和偏置真的按预期生成。下面这段日志来自EURUSD的M1周期测试脚本,同一毫秒(18:27:57.298)里连续吐出了多组输入与目标:第7组输入16/40、标签1,第8组18/45、标签0,第9组20/50、标签0,说明数据blob是按batch_size=10在循环构造的。 权重向量打印出来是 0.7084 / -0.3984 / 0.6182 / 0.6655 / -0.3276 / 0.8846 / 0.5137 / 0.9371,偏置是两个值 -0.5902 和 0.7384。外汇与贵金属杠杆高、滑点跳空频繁,这类随机初始化权重仅代表单次运行结果,下一回跑可能完全不同,不能直接当信号用。 代码里 CNeuralNets 用了 SIGMOID 激活,输入和输出数组都Resize成2,也就是单层隐藏结构的最小雏形。你可以直接把这段抄进MT5的脚本里跑一遍,看自己终端打出的权重是否也在[-1,1]附近浮动。

MQL5 / C++
class="macro">#include "NeuralNets.mqh";
CNeuralNets *nn;
class="kw">input class="type">int batch_size =class="num">10;
class="kw">input class="type">int hidden_layers =class="num">2;
data data_blobs[];
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
  {
class=class="str">"cmt">//---    
   nn = new CNeuralNets(SIGMOID);
     
   MakeBlobs(batch_size);
   
   ArrayPrint(data_blobs);
     
   class="type">class="kw">double Inputs[],OutPuts[];
   
   ArrayResize(Inputs,class="num">2);    ArrayResize(OutPuts,class="num">2);
   
   class="type">class="kw">double weights[], biases[];
   generate_weights(weights,ArraySize(Inputs));
   generate_bias(biases);
   
   Print("weights"); ArrayPrint(weights);
   Print("biases"); ArrayPrint(biases);
   
   for (class="type">int i=class="num">0; i<batch_size; i++)
     {

单层前向传播的终端日志怎么读

在 MT5 里跑这段 MLP 测试脚本,EURUSD M1 周期下第一轮数据集迭代(Dataset Iteration 0)的日志会把隐藏层计算过程原样打印出来。看懂这些行,才能确认权重和偏置真的喂进了网络,而不是静默失效。 Print("Dataset Iteration ",i); Inputs[0] = data_blobs[i].sample_1; Inputs[1]= data_blobs[i].sample_2; 上面两行先把第 i 个样本的两个特征塞进输入数组,随后调用 FeedForwardMLP 做前向计算。日志里 HLNode 1 的 dot_Product 为 -0.57513,加 bias -0.590 得 -1.16534,经激活函数输出 0.23770;HLNode 2 的 dot_Product 为 4.56409,加同值 bias 后得 3.97388,激活输出 0.98155。 注意两个隐藏节点共用了 -0.590 这个偏置,但权重符号相反(0.70837 与 -0.39838 vs 0.61823 与 0.66553),所以同输入下节点输出一个压到 0.23 附近、一个顶到 0.98 附近。外汇与贵金属杠杆高、滑点随机,这类数值仅反映脚本局部计算,实盘信号概率会随样本漂移。 想验证就直接把这段代码贴进 MT5 脚本,把 sample_1 设 2.0、sample_2 设 5.0,比对终端是不是也打出 Activation function Output =0.23770 和 0.98155,不一致就查权重初始化。

MQL5 / C++
Print("Dataset Iteration ",i);
Inputs[class="num">0] = data_blobs[i].sample_1; Inputs[class="num">1]= data_blobs[i].sample_2;   
ns.FeedForwardMLP(hidden_layers,Inputs,weights,biases,OutPuts);
}

class="kw">delete(nn);   
}

「单层感知机的逐节点前向传播账本」

在 MT5 用 EURUSD M1 跑一段测试脚本,能在专家日志里看到隐藏层每个节点的实时计算轨迹。上面这组输出是某次 Dataset Iteration 1 的快照,两个隐藏节点(HLNode 1、HLNode 2)共用输入 0.23770 与 0.98155,但权重不同,结果已经分叉。 HLNode 1 的加权和为 dot_Product 0.79044 加 bias 0.738,得到 1.52884;经激活函数压缩后输出 0.82184。HLNode 2 的加权和是 1.78034,激活后输出 0.85574,两个节点输出差值约 0.034,说明权重扰动对单层网络响应很敏感。 最终 MLP 输出行直接打印 0.82184 0.85574,对应这两个隐藏节点的激活值。外汇与贵金属市场波动剧烈、杠杆风险高,这类前向传播数值仅用于验证网络结构,不代表任何方向判断。 想自己复现,把 Print 打点插在隐藏层循环里即可,重点看同一输入在不同权重下的输出离散度,能帮你快速定位初始化是否合理。

◍ 单根 K 线在隐藏层的加权轨迹

把 EURUSD M1 上的两根输入(4.0 与 10.0)塞进一个两层隐藏网络,MT5 专家日志会把每一跳都打印出来。第一隐藏节点 HLNode 1 拿到权重 0.70837 与 -0.39838,点积算出 -1.15027,加偏置 -0.590 得到 -1.74048,经激活函数压缩为 0.14925。 第二节点 HLNode 2 的权重是 0.61823 与 0.66553,点积直接跳到 9.12817,偏置同样 -0.590,激活后输出 0.99980——说明这组输入把第二个神经元几乎推到了饱和区。 外汇与贵金属杠杆高,这类微结构打印只是帮你确认前向传播没写错,不代表任何方向判断。打开 MT5 把输入改成你自己的归一化价格,看激活值是否落在预期区间,是验证脚本最快的办法。

单层感知机的逐节点前向传播痕迹

在 MT5 用 MQL5 跑一段 TestScript 对 EURUSD 的 M1 周期做前向推理,日志会把每个隐藏节点的计算过程原样打印出来。上面这段输出里,时间戳全部停在 18:27:57.298,说明整批计算在同一 tick 内完成,没有跨帧延迟。 第一个隐藏节点 HLNode 2 接收两个输入:i0 的 input 0.99980 乘权重 0.88464,得到加权值约 0.88464;i1 的 input 0.14925 乘权重 0.51367 约 0.07668。两者点积加 bias 0.738 等于 1.57397,经激活函数压缩后输出 0.82835。 第二个节点复用 i0 的另一个权重 0.93713(input 0.99980 乘积约 0.93713)与 i1 权重 0.51367 组合,点积 1.01362 加同一 bias 得 1.75202,激活输出 0.85221。最终 MLP 输出向量就是 0.82835 和 0.85221 这两个值。 这种日志对排查权重初始化很有用:若某节点输出长期贴近 0 或 1,大概率权重尺度或 bias 设歪了。外汇与贵金属杠杆高、滑点大,这类小网络信号只能当概率参考,不能直接当成下单依据。

MQL5 / C++
HF      class="num">0    class="num">18:class="num">27:class="num">57.298    TestScript(EURUSD,M1)  i class="num">0  w class="num">1 = class="kw">input class="num">0.99980 x weight class="num">0.88464
EM      class="num">0    class="num">18:class="num">27:class="num">57.298    TestScript(EURUSD,M1)  dot_Product class="num">0.83557 + bias class="num">0.738 = class="num">1.57397
EL      class="num">0    class="num">18:class="num">27:class="num">57.298    TestScript(EURUSD,M1)  Activation function Output =class="num">0.82835
KE      class="num">0    class="num">18:class="num">27:class="num">57.298    TestScript(EURUSD,M1)  
GN      class="num">0    class="num">18:class="num">27:class="num">57.298    TestScript(EURUSD,M1)  HLNode class="num">2
LS      class="num">0    class="num">18:class="num">27:class="num">57.298    TestScript(EURUSD,M1)  i class="num">1  w class="num">2 = class="kw">input class="num">0.14925 x weight class="num">0.51367
FL      class="num">0    class="num">18:class="num">27:class="num">57.298    TestScript(EURUSD,M1)  i class="num">1  w class="num">3 = class="kw">input class="num">0.99980 x weight class="num">0.93713
KH      class="num">0    class="num">18:class="num">27:class="num">57.298    TestScript(EURUSD,M1)  dot_Product class="num">1.01362 + bias class="num">0.738 = class="num">1.75202
IR      class="num">0    class="num">18:class="num">27:class="num">57.298    TestScript(EURUSD,M1)  Activation function Output =class="num">0.85221
OH      class="num">0    class="num">18:class="num">27:class="num">57.298    TestScript(EURUSD,M1)  
IM      class="num">0    class="num">18:class="num">27:class="num">57.298    TestScript(EURUSD,M1)  Final MLP output(s)
MH      class="num">0    class="num">18:class="num">27:class="num">57.298    TestScript(EURUSD,M1)  class="num">0.82835 class="num">0.85221

「别急着下结论」

前馈网络搭到这一步,分类用的 sigmoid、tanh 激活已经能跑,但矩阵化之前隐藏层和输出层都锁死在固定结构里,想换回归推演就得动代码。本系列只解决了「能在 MQL5 里写神经网络」这件事,离直接上外汇实盘还差矩阵动态化和训练闭环。 另一个容易踩的坑:策略测试器优化权重和偏差,本质是在找「回测好看」的随机值,并不等于模型预测误差最小;真要训网络,随机梯度下降每一步都在压预测误差,两条路目标不同。外汇和贵金属波动高、滑点狠,拿测试器凑出的参数当信号用风险很大。 GitHub 上那个 NeuralNetworks-MQL5 仓库(13.56 KB 的 ZIP)可以先拖进 MT5 看前向传播怎么写,但别以为能分类就能赚钱——线性任务用线性回归往往比硬套网络更稳。

常见问题

Sigmoid 把任意实数压到 0~1,大于 0.5 倾向归为正类,低于则负类;实战中可结合阈值微调降低外汇高频误触发。
Tanh 输出中心在 0、收敛更快,但输入绝对值过大仍会饱和;隐藏层用 Tanh 时记得做输入标准化。
小布可接入你的品种页,用 AIGC 比对训练与样本外曲线,提示隐藏层节点是否过多导致拟合噪音。
SoftMax 强制各类概率和为 1,适合互斥多分类;若类别独立就用多节点 Sigmoid,别混用。
单节点隐藏层本质等同线性回归器,只能拟合直线关系;想抓价格非线性拐点至少叠两层或加节点。