多层感知机与反向传播算法·进阶篇
感知机权重与预测函数的裸实现
下面这段 MQL5 片段给出了一个极简感知机的静态数据集与推理入口。数据集每行是 {x1, x2, label} 结构,前两组标签为 0,后五组标签为 1,分界线并不线性可分,肉眼可见 8.675 那行 x2 为负却仍标 1。 权重数组写死为 {-0.1, 0.20653640140000007, -0.23418117710000003},偏置占第 0 位。predict() 用 z = w0 + Σ w_i*x_i 做线性组合,再丢进 activation() 阶跃函数:z≥0 返回 1,否则 0。 主循环里对 7 行样本逐一打印 Expected 与 Predicted,开 MT5 把代码补完直接跑,能立刻看到哪几行被错分。外汇与贵金属行情用这类硬阈值模型极易过拟合,实盘前务必做样本外验证。 别把阶跃当万能 activation() 返回非 0 即 1,梯度恒为 0,没法直接反向传播;真要在线学习得换 sigmoid 或 ReLU 再接 SGD。
class="type">class="kw">double weights[] = {-class="num">0.1, class="num">0.20653640140000007, -class="num">0.23418117710000003}; for(class="type">int row=class="num">0; row<ArrayRange(dataset, class="num">0); row++) { class="type">class="kw">double predict = predict(dataset, weights, row); printf("Expected=%.1f, Predicted=%.1f", dataset[row][nINPUT-class="num">1], predict); } } class=class="str">"cmt">// Make a prediction with weights class="kw">template <class="kw">typename Array> class="type">class="kw">double predict(class="kw">const Array &X[][nINPUT], class="kw">const Array &weights[], class="kw">const class="type">int row=class="num">0) { class="type">class="kw">double z = weights[class="num">0]; for(class="type">int i=class="num">0; i<ArrayRange(X, class="num">1)-class="num">1; i++) { z+=weights[i+class="num">1]*X[row][i]; } class="kw">return activation(z); } class="type">class="kw">double activation(class="kw">const class="type">class="kw">double activation) { class="kw">return activation>=class="num">0.0?class="num">1.0:class="num">0.0; } class="kw">template <class="kw">typename Array> class="type">void train_weights(Array &weights[], class="kw">const Array &X[][nINPUT], class="type">class="kw">double l_rate=class="num">0.1, class="type">int n_epoch=class="num">5) { ArrayResize(weights, ArrayRange(X, class="num">1)); for(class="type">int i=class="num">0; i<ArrayRange(X, class="num">1); i++) {
◍ 在 MT5 里跑通感知机权重迭代
下面这段 MQL5 脚本用固定随机种子复现了一个二分类感知机的在线学习过程。数据集含 10 行样本,前两列是特征 X1、X2,第三列是标签 Y(0 或 1),典型如区分两类价格形态簇。 训练核心是一个双层循环:外层按 n_epoch 轮迭代,内层遍历每一行样本,用当前 weights 做 predict 得到 y,再以「标签 - 预测」为 error,对偏置 weights[0] 与各特征权重做梯度更新。每轮结束用 printf 打出累计平方误差,方便你直接看误差是否随 epoch 下降。 随机种子设为 42(random.seed(42)),意味着同环境每次重跑权重初值一致,你能稳定对比调 l_rate 的效果。外汇与贵金属行情受突发事件扰动大,这类线性边界模型只适合做辅助过滤,实盘误判概率不低,务必轻仓验证。 把代码贴进 MT5 脚本的 OnStart,改 n_epoch 或 l_rate 各跑一次,观察终端里 error 曲线的收敛速度差异,比看任何文字说明都直观。
class="macro">#define nINPUT class="num">3 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class=class="str">"cmt">//--- random.seed(class="num">42); class="type">class="kw">double dataset[][nINPUT] = { class=class="str">"cmt">//X1 //X2 //Y {class="num">2.7810836,class="num">2.550537003,class="num">0}, {class="num">1.465489372,class="num">2.362125076,class="num">0}, {class="num">3.396561688,class="num">4.400293529,class="num">0}, {class="num">1.38807019,class="num">1.850220317,class="num">0}, {class="num">3.06407232,class="num">3.005305973,class="num">0}, {class="num">7.627531214,class="num">2.759262235,class="num">1}, {class="num">5.332441248,class="num">2.088626775,class="num">1}, {class="num">6.922596716,class="num">1.77106367,class="num">1}, {class="num">8.675418651,-class="num">0.242068655,class="num">1}, {class="num">7.673756466,class="num">3.508563011,class="num">1}};
「感知机权重训练与预测的函数实现」
这段 MT5 代码把感知机最核心的两件事拆开了:用随机梯度下降训权重,再用训好的权重做二分类预测。激活函数走的是硬阈值路线,z≥0 输出 1.0,否则 0.0,适合处理已经二值化的行情标签(如涨/跌)。 train_weights 默认学习率 0.1、迭代 5 轮(n_epoch=5)。每轮先算全样本平方误差和,再逐行更新偏置 weights[0] 与各特征权重,更新量是 l_rate * error * X[row][i]。在 MT5 里把 nINPUT 和 dataset 准备好后直接调,终端会按轮打印 >epoch=0, lrate=0.100, error=... 这样的过程日志。 predict 是纯前向:z 从偏置起算,累加 weights[i+1]*X[row][i] 后过 activation。主流程里用 ArrayPrint(weights,20) 打出 20 位精度权重,再逐行 printf 出 Expected 与 Predicted 对比——你复制进 EA 的 OnStart 跑一遍,就能看到权重收敛到什么数值、预测偏离多大。外汇与贵金属杠杆高,这套只作特征工程验证,实盘信号须自行评估概率与风险。
class="type">class="kw">double weights[]; train_weights(weights, dataset); ArrayPrint(weights, class="num">20); for(class="type">int row=class="num">0; row<ArrayRange(dataset, class="num">0); row++) { class="type">class="kw">double predict = predict(dataset, weights, row); printf("Expected=%.1f, Predicted=%.1f", dataset[row][nINPUT-class="num">1], predict); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">// Make a prediction with weights class="kw">template <class="kw">typename Array> class="type">class="kw">double predict(class="kw">const Array &X[][nINPUT], class="kw">const Array &weights[], class="kw">const class="type">int row=class="num">0) { class="type">class="kw">double z = weights[class="num">0]; for(class="type">int i=class="num">0; i<ArrayRange(X, class="num">1)-class="num">1; i++) { z+=weights[i+class="num">1]*X[row][i]; } class="kw">return activation(z); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Transfer neuron activation | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double activation(class="kw">const class="type">class="kw">double activation) class=class="str">"cmt">//# { class="kw">return activation>=class="num">0.0?class="num">1.0:class="num">0.0; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Estimate Perceptron weights using stochastic gradient descent | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template <class="kw">typename Array> class="type">void train_weights(Array &weights[], class="kw">const Array &X[][nINPUT], class="type">class="kw">double l_rate=class="num">0.1, class="type">int n_epoch=class="num">5) { ArrayResize(weights, ArrayRange(X, class="num">1)); ArrayInitialize(weights, class="num">0); for(class="type">int epoch=class="num">0; epoch<n_epoch; epoch++) { class="type">class="kw">double sum_error = class="num">0.0; for(class="type">int row=class="num">0; row<ArrayRange(X, class="num">0); row++) { class="type">class="kw">double y = predict(X, weights, row); class="type">class="kw">double error = X[row][nINPUT-class="num">1] - y; sum_error += pow(error, class="num">2); weights[class="num">0] = weights[class="num">0] + l_rate * error; for(class="type">int i=class="num">0; i<ArrayRange(X, class="num">1)-class="num">1; i++) { weights[i+class="num">1] = weights[i+class="num">1] + l_rate * error * X[row][i]; } } printf(">epoch=%d, lrate=%.3f, error=%.3f",epoch, l_rate, sum_error); } }
把单神经元叠成多层感知机
单个神经元只能拟合一条线性边界,实战里价格序列的非线性耦合它根本吃不下。把神经元按层堆叠,前一层输出直接喂给下一层输入,就构成了多层感知机(MLP)。 输入向量 X 先进初始层,该层输出作为下一层输入,逐层传递直到末层给出结果。层数加深后,网络有能力逼近越来越复杂的映射关系——这也是为什么 MT5 里的神经网络信号常做成 3 层以上而非单层。 外汇与贵金属波动受多维因子纠缠,MLP 拟合的是概率性关联而非因果,实盘使用前务必在策略测试器用历史数据做样本外验证。
◍ 给 MLP 喂带标签的数据
网络上线前得先训练,不然就是一堆随机权重。监督训练的思路很直接:你手里有输入和已知正确的输出,让模型在两者间建立映射,这和教小孩对照卡片认字是一个道理。 具体到任务类型,监督学习拆成两类。回归盯连续值,比如用房屋面积推房价,面积到价格是一条连续曲线;分类盯离散标签,把同样的房子按「是否贵过某条线」切成两类,输出就不再是数字而是类别。 做 MT5 上的行情模型时,你如果用 MLP 预测下一根收盘价,那是回归;若只判「涨还是跌」两态,就是二分类。外汇与贵金属波动剧烈、杠杆风险高,同一套网络在回测里拟合得好,实盘也可能因样本外漂移而失效,标签口径先定清再谈训练。