神经网络轻松制作·综合运用
◍ 神经网络类的接口与前向传播实现
在 MT5 里用 C++ 风格封装一个多层感知机,核心类 CNet 先把生命周期和资源接口定清楚。析构走空实现 ~CNet(){},对外暴露 feedForward(推前向)、backProp(反传)、getResults(取输出)、getRecentAverageError(读平滑误差)四个方法,另配 Save/Load 把权重和预测元数据落盘,common 参数默认 true 表示存到公共目录。 recentAverageSmoothingFactor 是静态因子,控制滑动误差的平滑强度;私有成员 layers 用 CArrayLayer 管理各层,recentAverageError 记录当前平均误差。建网时构造函数吃一个 topology(各层神经元数数组),循环里给每一层 CreateElement,输出数取下一层规模,最后一层输出数为 0。 feedForward 先做指针和维度校验:输入长度必须等于首层神经元数减 1(减的是偏置位),否则直接 return。随后把输入写进首层每个神经元的 outputVal,再从 layerNum=1 起逐层调用 neuron.feedForward(prevLayer) 把信号往后送;循环里全嵌了 !IsStopped() 判断,EA 被终止时能立刻跳出,避免卡死回测或实盘。 getResults 通过引用回传结果数组,调用方拿到的是最后一层的激活输出。外汇与贵金属波动剧烈、杠杆高风险大,这套网络仅作概率性信号参考,实盘前务必在 MT5 策略测试器用历史数据验证收敛与误差表现。
~CNet(){}; class="type">void feedForward(const CArrayDouble *inputVals); class="type">void backProp(const CArrayDouble *targetVals); class="type">void getResults(CArrayDouble *&resultVals); class="type">class="kw">double getRecentAverageError() const { class="kw">return recentAverageError; } class="type">bool Save(const class="type">class="kw">string file_name, class="type">class="kw">double error, class="type">class="kw">double undefine, class="type">class="kw">double forecast, class="type">class="kw">datetime time, class="type">bool common=true); class="type">bool Load(const class="type">class="kw">string file_name, class="type">class="kw">double &error, class="type">class="kw">double &undefine, class="type">class="kw">double &forecast, class="type">class="kw">datetime &time, class="type">bool common=true); class=class="str">"cmt">//--- class="kw">static class="type">class="kw">double recentAverageSmoothingFactor; class="kw">private: CArrayLayer layers; class="type">class="kw">double recentAverageError; }; CNet::CNet(const CArrayInt *topology) { if(CheckPointer(topology)==POINTER_INVALID) class="kw">return; class=class="str">"cmt">//--- class="type">int numLayers=topology.Total(); for(class="type">int layerNum=class="num">0; layerNum<numLayers; layerNum++) { class="type">uint numOutputs=(layerNum==numLayers-class="num">1 ? class="num">0 : topology.At(layerNum+class="num">1)); if(!layers.CreateElement(topology.At(layerNum), numOutputs)) class="kw">return; } } class="type">void CNet::feedForward(const CArrayDouble *inputVals) { if(CheckPointer(inputVals)==POINTER_INVALID) class="kw">return; class=class="str">"cmt">//--- CLayer *Layer=layers.At(class="num">0); if(CheckPointer(Layer)==POINTER_INVALID) { class="kw">return; } class="type">int total=inputVals.Total(); if(total!=Layer.Total()-class="num">1) class="kw">return; class=class="str">"cmt">//--- for(class="type">int i=class="num">0; i<total && !IsStopped(); i++) { CNeuron *neuron=Layer.At(i); neuron.setOutputVal(inputVals.At(i)); } class=class="str">"cmt">//--- total=layers.Total(); for(class="type">int layerNum=class="num">1; layerNum<total && !IsStopped(); layerNum++) { CArrayObj *prevLayer = layers.At(layerNum - class="num">1); CArrayObj *currLayer = layers.At(layerNum); class="type">int t=currLayer.Total()-class="num">1; for(class="type">int n=class="num">0; n<t && !IsStopped(); n++) { CNeuron *neuron=currLayer.At(n); neuron.feedForward(prevLayer); } } } class="type">void CNet::getResults(CArrayDouble *&resultVals) {
反向传播里误差与梯度的实际流向
这段 CNet::backProp 做的是神经网络训练的核心回退:先拿输出层和目标值算均方根误差,再逐层反推梯度并更新权重。外汇与贵金属行情噪声大,这类模型过拟合概率高,实盘前务必用历史数据做交叉验证。 误差计算循环里用 error/=total 再 sqrt(error),得到的是输出层神经元的平均 RMS 误差,并借 recentAverageSmoothingFactor 做平滑,recentAverageError 会随训练步数缓变而非跳变。 梯度分两段算:输出层直接拿 targetVals 调 calcOutputGradients;隐藏层从倒数第二层循环到第一层,靠下一层对象算 calcHiddenGradients。最后权重更新从输出层回退到输入层,每层调 updateInputWeights(prevLayer)。 所有循环都嵌了 !IsStopped() 判定,EA 在 MT5 点停止时会安全跳出,不会卡死在多层回退中。复制下面代码到 MT5 的 CNet 类方法里,接好神经元接口就能跑通单步训练。
if(CheckPointer(resultVals)==POINTER_INVALID) { resultVals=new CArrayDouble(); } resultVals.Clear(); CArrayObj *Layer=layers.At(layers.Total()-class="num">1); if(CheckPointer(Layer)==POINTER_INVALID) { class="kw">return; } class="type">int total=Layer.Total()-class="num">1; for(class="type">int n=class="num">0; n<total; n++) { CNeuron *neuron=Layer.At(n); resultVals.Add(neuron.getOutputVal()); } } class="type">void CNet::backProp(const CArrayDouble *targetVals) { if(CheckPointer(targetVals)==POINTER_INVALID) class="kw">return; CArrayObj *outputLayer=layers.At(layers.Total()-class="num">1); if(CheckPointer(outputLayer)==POINTER_INVALID) class="kw">return; class=class="str">"cmt">//--- class="type">class="kw">double error=class="num">0.0; class="type">int total=outputLayer.Total()-class="num">1; for(class="type">int n=class="num">0; n<total && !IsStopped(); n++) { CNeuron *neuron=outputLayer.At(n); class="type">class="kw">double delta=targetVals[n]-neuron.getOutputVal(); error+=delta*delta; } error/= total; error = sqrt(error); recentAverageError+=(error-recentAverageError)/recentAverageSmoothingFactor; class=class="str">"cmt">//--- for(class="type">int n=class="num">0; n<total && !IsStopped(); n++) { CNeuron *neuron=outputLayer.At(n); neuron.calcOutputGradients(targetVals.At(n)); } class=class="str">"cmt">//--- for(class="type">int layerNum=layers.Total()-class="num">2; layerNum>class="num">0; layerNum--) { CArrayObj *hiddenLayer=layers.At(layerNum); CArrayObj *nextLayer=layers.At(layerNum+class="num">1); total=hiddenLayer.Total(); for(class="type">int n=class="num">0; n<total && !IsStopped();++n) { CNeuron *neuron=hiddenLayer.At(n); neuron.calcHiddenGradients(nextLayer); } } class=class="str">"cmt">//--- for(class="type">int layerNum=layers.Total()-class="num">1; layerNum>class="num">0; layerNum--) { CArrayObj *layer=layers.At(layerNum); CArrayObj *prevLayer=layers.At(layerNum-class="num">1); total=layer.Total()-class="num">1; for(class="type">int n=class="num">0; n<total && !IsStopped(); n++) { CNeuron *neuron=layer.At(n); neuron.updateInputWeights(prevLayer); } } }
「感知器只是起点」
这套在家搭神经网络的做法,落点其实只是冰山一角。文中实现的感知器,是 Frank Rosenblatt 在 1957 年提出的模型,到今天已经过了 60 多年,期间衍生出的网络结构多到数不清。 但别小看它——感知器在 MT5 里跑回归或分类任务,依然能给出可用的信号,你可以直接把前几节的代码挂上 EURUSD 的 H1 复盘验证。 想往深了走,得自己啃专业资料,单靠这几篇篇幅根本铺不开全部 AI 概念。外汇和贵金属行情受杠杆与跳空影响,用任何模型都先想到爆仓风险。
◍ 随文附带的感知器类库
这篇系列里真正能直接落地的东西,是附在文末的 NeuroNet.mqh 类库,体积 38.34 KB,封装了创建多层感知器网络的基础类。想跑通前文那些 LSTM 之外的轻量实验,直接把该文件丢进 MT5 的 MQL5/Include 目录就能 include 调用。 评论区有人踩过坑:原文件里部分方法声明带了 const 修饰,在较新 build 下编译会直接报错。解决办法是把对应语句里的 const 去掉再编译,社区里已有人验证可正常通过。外汇与贵金属行情受杠杆和跳空影响,用这类网络做信号生成前务必先开模拟盘验证,实盘误判概率不低。 类库本身只提供网络结构,不绑定任何行情数据接口。你要喂什么特征、怎么标准化输入,得自己接 ICustom 或 CopyBuffer 取序列,这一步没人替你定。