多层感知机与反向传播算法·综合运用
◍ 误差如何顺着网络往回改权重
反向传播是现代神经网络能训练起来的核心机制。它的任务很直接:拿到输出层的误差,从最后一层往前逐层重算权重向量 W,让预测值尽量贴近真实值。 整个过程分两步。前向传递把输入向量灌进网络,得到预测输出;反向传递在输出层算损失函数的梯度,再用链式法则递归地把梯度传回每一层去更新权重。 以带一层隐藏神经元和一个输出神经元的网络为例,监督训练要调整权重来缩小 Pred(y) 与 Req(y) 的差距。定义网络误差 E = Pred - Req,算法目标是最小化 E²。反向传播本质就是用梯度下降最小化 E²,关键在算清每个权重对 E² 的灵敏度 ∂E²/∂W。 对连接神经元 A 到 B 的权重 W_AB,更新遵循 delta 法则:ΔW_AB = -η · ∂E²/∂W_AB,η 是学习率。若 B 是输出神经元且用逻辑激活函数,可推得 ΔW_AB = η·O_A·O_B·(1-O_B)·(Req-O_B);若 B 是隐藏神经元,则灵敏度要再乘以后层传回的 δ 并乘 f'(I_B),即 ΔW_AB = η·O_A·O_B·(1-O_B)·Σ(W_BO·δ_O)。 在 MT5 里写神经网络 EA 时,这两套公式必须分开判断:输出层用目标减预测,隐藏层靠后层误差加权回传。外汇与贵金属波动大、过拟合风险高,实盘前务必用历史数据验证梯度推导是否正确。
反向传播权重更新的数学骨架
单输出神经网络的权重调整,核心就两条等式。隐藏层输出 HID 先由输入 I 与权重 WI 做乘加再经 tanh 激活;输出层误差 ERROR 直接取网络输出与目标的差。 输出层权重 WO 按 WO = WO - (LR × ERROR × HID) 更新,隐藏层权重 WI 则要多乘一项 (1 - HID²) 并左乘输出误差链 ERROR×WO,再转置回传。等式 17、18 说明更新方向由输入乘局部梯度决定,幅度同时受误差大小与学习率 LR 牵制;若人为抹掉幅度只留方向,每次变动就只剩 LR 一个变量。 原版算法可挂多个输出神经元,梯度下降目标是让所有输出的总平方误差最小。后续许多加速变种都从这棵母树分叉,麦格理大学计算系的技术报告 C/TR96-05 把它们归进“反向传播家庭相册”。 如今 CNN 做图像分类、RNN 处理自然语言,底层依旧跑反向传播。它能挖出人眼难辨的特征耦合,但外汇与贵金属行情里直接套这类黑箱,过拟合与样本外失效的高风险必须先掂量清楚。
「用纯MQL5手搓一个MLP原型」
把多层感知机搬进 MT5,不依赖外部库也能跑通。教程拆成网络初始化、前馈、反向传播、训练、预测五步;演示代码刻意不用 OOP,只为把前面推导的算式摊开给你看,真实项目里还是类封装更利于扩展。 权重初始化建议用小的随机数。下面这段 random() 把 rand() 映射到 0~1 区间,作为权重初值种子,避免对称权重导致神经元同步更新。 前馈部分,隐藏层用 tanh 做激活,输出层是线性的。forward_propagate() 先算隐藏神经元加权和再 tanh,再乘隐藏-输出权重得到预测值 outPred,最后拿预测减真实标签 y[patNum] 存进 errThisPat。 反向传播按随机梯度下降更新。backward_propagate_error() 里输出权重改量是 LR_HO * errThisPat * hiddenVal[k],输入-隐藏权重则乘上 (1-tanh²) 与输出权重的链式法则项;regularisationWeights() 把权重夹在 -5 到 5 之间,抑制极端值。 训练循环 train() 跑 numEpochs 轮,每轮随机抽 patNum = rand()%numPatterns 做前馈+回传。完整脚本见 MLP_Script.mq5,外汇与贵金属行情噪声大,这类原型直接上实盘信号概率偏低,仅适合在策略测试器里验证数学链路。
class="type">class="kw">double random(class="type">void) { class="kw">return ((class="type">class="kw">double)rand())/(class="type">class="kw">double)SHORT_MAX; } class=class="str">"cmt">// Forward propagate class="kw">input to a network output class="type">void forward_propagate(class="type">void) { class=class="str">"cmt">//calculate the outputs of the hidden neurons class=class="str">"cmt">//the hidden neurons are tanh class="type">int i = class="num">0; for(i = class="num">0; i<numHidden; i++) { hiddenVal[i] = class="num">0.0; for(class="type">int j = class="num">0; j<numInputs; j++) { hiddenVal[i] += (X[patNum][j] * weightsIH[j][i]); } hiddenVal[i] = tanh(hiddenVal[i]); } class=class="str">"cmt">//calculate the output of the network class=class="str">"cmt">//the output neuron is linear outPred = class="num">0.0; for(i = class="num">0; i<numHidden; i++) { outPred += hiddenVal[i] * weightsHO[i]; } class=class="str">"cmt">//calculate the error errThisPat = outPred - y[patNum]; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Backpropagate error and change network weights | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void backward_propagate_error(class="type">void) { class=class="str">"cmt">//adjust the weights hidden-output for(class="type">int k = class="num">0; k<numHidden; k++) { class="type">class="kw">double weightChange = LR_HO * errThisPat * hiddenVal[k]; weightsHO[k] -= weightChange; class=class="str">"cmt">//regularisation on the output weights regularisationWeights(weightsHO[k]); } class=class="str">"cmt">// adjust the weights class="kw">input-hidden for(class="type">int i = class="num">0; i<numHidden; i++) { for(class="type">int k = class="num">0; k<numInputs; k++) { class="type">class="kw">double x = class="num">1 - pow(hiddenVal[i],class="num">2); x = x * weightsHO[i] * errThisPat * LR_IH; x = x * X[patNum][k]; class="type">class="kw">double weightChange = x; weightsIH[k][i] -= weightChange; } } } class=class="str">"cmt">//regularisation on the output weights class="type">void regularisationWeights(class="type">class="kw">double &weight) { weight<-class="num">5?weight=-class="num">5:weight>class="num">5?weight=class="num">5:weight=weight; } class=class="str">"cmt">//# Train a network for a fixed number of epochs class="type">void train(class="type">void) { for(class="type">int j = class="num">0; j <= numEpochs; j++) { for(class="type">int i = class="num">0; i<numPatterns; i++) { class=class="str">"cmt">//select a pattern at random patNum = rand()%numPatterns;
◍ 单轮训练与预测的输出落点
这段代码片段把神经网络在一轮 epoch 里的动作收了尾:先对当前样本做前向传播算输出,再反传误差,循环结束用 calcOverallError 汇总,并用 printf 打出 epoch 序号和 RMS Error。RMS Error 是回归类模型常用的均方根误差,数值越小代表拟合越紧,盯盘时可用它判断训练是否还在收敛。 预测函数 predict 则遍历全部 numPatterns 个样本,逐个前向传播后打印真实标签 y[patNum] 与模型输出 outPred。你在 MT5 里跑这套,直接对比这两列数字就能看出哪类行情样本被网络判歪了。 外汇与贵金属波动受杠杆与跳空影响,模型误差不等于实盘胜率,任何信号都只是概率倾向,需用小资金验证。
class=class="str">"cmt">//calculate the current network output class=class="str">"cmt">//and error for this pattern forward_propagate(); backward_propagate_error(); } class=class="str">"cmt">//display the overall network error class=class="str">"cmt">//after each epoch calcOverallError(); printf("epoch = %d RMS Error = %f",j,RMSerror); } } class=class="str">"cmt">// # Make a prediction with a network class="type">void predict(class="type">void) { for(class="type">int i = class="num">0; i<numPatterns; i++) { patNum = i; forward_propagate(); printf("real = %d predict = %f",y[patNum],outPred); } }
记住这一条就够了
前面几节把感知器到多层感知机(MLP)的演进、反向传播与梯度下降的训练机制都拆开了,核心落点其实就一句:想在 MT5 里跑通这类网络,权重初始化和 forward_propagate 的链路必须自己验证一遍。 附带的两个脚本 PerceptronScript.mq5(3.34 KB)和 MLP_Script.mq5(6.98 KB)是直接可编译的入口,外汇与贵金属市场高波动、高杠杆,任何神经网络信号都只是概率倾向,实盘前务必用策略测试器跑历史样本。 下面这段转发函数虽只是片段,却是网络推演的起点——把输入真正送进输出层,才能谈误差回传。 外汇贵金属属高风险领域,模型在历史表现不代表未来,参数调优请控制在可承受回撤内。
class=class="str">"cmt">// 将输入转发到网络输出 class="type">void forward_propagate(class="type">void) ... ...