利用 MQL5 矩阵的反向传播神经网络·进阶篇
📘

利用 MQL5 矩阵的反向传播神经网络·进阶篇

第 2/3 篇

用验证集掐断过度拟合

神经网络在训练集上把误差压到极小并不难,难的是面对未见过的行情数据仍保持同等隐式依赖的捕捉能力。若训练误差单调下降、前向测试却失灵,就是典型的过度拟合,外汇与贵金属这类高波动品种上该风险尤其突出,可能让模型在实盘中断崖式失效。 提早停止是最直接的正则化手段:另备一份验证集(输入 validation、输出 check),每个世代先算 msev 再算训练集 mse。验证误差通常先降后升——网络先抓主形态,随后开始死记训练集特异噪声。代码里用 EMA 平滑误差,p = sqrt(epochs) 经验定平滑周期,ema = 2/(p+1),避开随机波动误判。 停止条件藏在 scale 变量里:scale = data.Rows()/(validation.Rows()+1)+1,当 msevma > msevmap + scale*(msemap-msema) 且已过 p 个世代,循环 break。这相当于用训练/验证样本量比给容差,防止小验证集上的正常抖动触发早停。 Dropout 在 MatrixNet 里走低成本路线:不真删神经元,而是按 dropOutRate 百分比把权重矩阵随机置 0,每世代开始前用 DropOutState 恢复再重选。enableDropOut 默认 0 即关,开启后能在无验证集时也压制过拟合。 训练循环还留了 progress 虚函数钩子,每秒回传一次衡量值,返回 false 可手动中断。默认实现里 NaN 检测也前置——网络溢出或数据错直接 PrintFormat 报 epoch 并 break,避免脏权重写回。

MQL5 / C++
class="type">class="kw">double train(const matrix &data, const matrix &target,
const matrix &validation, const matrix &check,
const class="type">int epochs = class="num">1000, const class="type">class="kw">double accuracy = class="num">0.001,
const ENUM_LOSS_FUNCTION lf = LOSS_MSE)
{
if(!ready) class="kw">return NaN();
speed = accuracy;
...
class="type">class="kw">double mse = DBL_MAX;
class="type">class="kw">double msev = DBL_MAX;
class="type">class="kw">double msema = class="num">0;       class=class="str">"cmt">// MSE averaging of the training set
class="type">class="kw">double msemap = class="num">0;      class=class="str">"cmt">// MSE averaging of the training set in the previous epoch
class="type">class="kw">double msevma = class="num">0;      class=class="str">"cmt">// MSE averaging of the validation dataset
class="type">class="kw">double msevmap = class="num">0;     class=class="str">"cmt">// MSE averaging of the validation dataset in the previous epoch
class="type">class="kw">double ema = class="num">0;         class=class="str">"cmt">// exponential smoothing factor
class="type">int p = class="num">0;              class=class="str">"cmt">// EMA period
p = (class="type">int)sqrt(epochs);  class=class="str">"cmt">// empirically choose the period of the EMA averaging of errors
ema = class="num">2.0 / (p + class="num">1);
PrintFormat("EMA for early stopping: %d(%f)", p, ema);
class="type">int ep = class="num">0;
for(; ep < epochs; ep++)
{
if(validation.Rows() && check.Rows())
{
class=class="str">"cmt">// if there is validation, run it before normal pass/training
msev = test(validation, check, lf);
class=class="str">"cmt">// smooth errors
msevma = (msevma ? msevma : msev) * (class="num">1 - ema) + ema * msev;
}
mse = test(data, target, lf);  class=class="str">"cmt">// enable feedForward(data) run
msema = (msema ? msema : mse) * (class="num">1 - ema) + ema * mse;
...
if(!MathIsValidNumber(mse))
{
PrintFormat("NaN at epoch %d", ep);
break; class=class="str">"cmt">// will class="kw">return NaN as error indication
}
const class="type">int scale = (class="type">int)(data.Rows() / (validation.Rows() + class="num">1)) + class="num">1;
if(msevmap != class="num">0 && ep > p && msevma > msevmap + scale * (msemap - msema))
{
class=class="str">"cmt">// skip the first p epochs to accumulate values for averaging
PrintFormat("Stop by validation at %d, v: %f > %f, t: %f vs %f", ep, msevma, msevmap, msema, msemap);
break;
}
msevmap = msevma;
msemap = msema;
...
if(mse <= accuracy)
{
PrintFormat("Done by accuracy limit %f at epoch %d", accuracy, ep);
break;
}
if(!progress(ep, epochs, mse, msev, msema, msevma))
{
PrintFormat("Interrupted by user at epoch %d", ep);
break;
}

「训练循环里的容错与进度节流」

这段 CNet 派生类代码展示了神经网络训练收尾与过程监控的两种典型写法。反向传播若失败,立即把 mse 置为 NaN 并 break,避免脏权重被后续逻辑误用。 当训练跑满 epoch 上限时,用 PrintFormat 输出 "Done by epoch limit %d with accuracy %f",这里的 accuracy 实际是 mse 值,回测时若看到该值接近 0 说明拟合倾向过强,外汇与贵金属样本上过拟合概率偏高,属高风险信号。 progress 函数用 static uint trap 配合 GetTickCount 做每秒一次的日志节流,防止 MT5 终端被高频 Print 刷屏。dropOutRate 默认 10%,enableDropOut 可改比例,实盘前建议在 EURUSD 的 M5 历史上把 dropout 调到 20% 对比泛化表现。

MQL5 / C++
if(!backProp(target))
{
mse = NaN(); class=class="str">"cmt">// error flag
break;
}
}
if(ep == epochs)
{
PrintFormat("Done by epoch limit %d with accuracy %f", ep, mse);
}
class="kw">return mse;
}
class="kw">virtual class="type">bool progress(const class="type">int epoch, const class="type">int total,
const class="type">class="kw">double error, const class="type">class="kw">double valid = DBL_MAX,
const class="type">class="kw">double ma = DBL_MAX, const class="type">class="kw">double mav = DBL_MAX)
{
class="kw">static class="type">uint trap;
if(GetTickCount() > trap)
{
PrintFormat("Epoch %d of %d, loss %.5f%s%s%s", epoch, total, error,
ma == DBL_MAX ? "" : StringFormat(" ma(%.5f)", ma),
valid == DBL_MAX ? "" : StringFormat(", validation %.5f", valid),
valid == DBL_MAX ? "" : StringFormat(" v.ma(%.5f)", mav));
trap = GetTickCount() + class="num">1000;
}
class="kw">return !IsStopped();
}
class="type">void enableDropOut(const class="type">uint percent = class="num">10)
{
dropOutRate = (class="type">int)percent;
}

◍ 用批量 rprop 替网络换上自适应步频

恒定学习率在实际训练里两头不讨好:调小了权重收敛拖沓,调大了又在误差面上来回弹跳。把弹性传播(rprop)搬进全量矩阵计算,就得到批量 rprop——它逐权重比对上一代与当前代增量符号,同向就加速、反向就减速。 启用方式很直接:在包含 MatrixNet.mqh 之前定义 BATCH_PROP 宏。此时类里用 speed[] 和 deltas[] 矩阵数组替换原来的标量 speed,前者存每层学习率,后者存上代权重增量,用于符号比较。 四个附加变量框定调节边界:plus=1.1 为加速倍数,minus=0.1 为减速倍数,max=50 封顶,min=0.0 保底。allocate 里给它们写了默认值,想改就在构造后调 setupSpeedAdjust。 训练启动时,train 方法不再给标量赋精度,而是用 accuracy 把 speed[i] 每个矩阵填满,deltas[i] 填 0。backProp 里算完 delta 立刻调 adjustSpeed,把 delta*deltas[i] 乘积传进去判定方向;随后把新 delta 存回 deltas[i] 供下代使用。 adjustSpeed 的逻辑毫无花活:矩阵元素乘积为正,对应速度乘 plus 且不超 max;为负则乘 minus 且不破 min。这样每层、每个连接都有自己的节奏,外汇与贵金属模型训练的高波动样本下,过冲概率可能降低,但杠杆品种仍属高风险,参数须先在 MT5 历史数据上回测。

MQL5 / C++
class="macro">#define BATCH_PROP
  class MatrixNet
  {
  class="kw">protected:
     ...
     class="macro">#ifdef BATCH_PROP
     matrix speed[];
     matrix deltas[];
     class="macro">#else
     class="type">class="kw">double speed;
     class="macro">#endif
     class="type">class="kw">double plus;
     class="type">class="kw">double minus;
     class="type">class="kw">double max;
     class="type">class="kw">double min;
     class="type">void allocate()
     {
        ArrayResize(weights, n);
        ArrayResize(outputs, n + class="num">1);
        ArrayResize(bestWeights, n);
        dropOutRate = class="num">0;
        class="macro">#ifdef BATCH_PROP
        ArrayResize(speed, n);
        ArrayResize(deltas, n);
        plus = class="num">1.1;
        minus = class="num">0.1;
        max = class="num">50;
        min = class="num">0.0;
        class="macro">#endif
     }
     class="type">class="kw">double train(const matrix &data, const matrix &target,
        const matrix &validation, const matrix &check,
        const class="type">int epochs = class="num">1000, const class="type">class="kw">double accuracy = class="num">0.001,
        const ENUM_LOSS_FUNCTION lf = LOSS_MSE)
     {
        ...
        class="macro">#ifdef BATCH_PROP
        for(class="type">int i = class="num">0; i < n; ++i)
        {
           speed[i].Fill(accuracy); class=class="str">"cmt">// adjust speeds on the fly
           deltas[i].Fill(class="num">0);
        }
        class="macro">#else
        speed = accuracy;
        class="macro">#endif
        ...
     }
     class="type">bool backProp(const matrix &target)
     {
        ...
        for(class="type">int i = n - class="num">1; i >= class="num">0; --i) class=class="str">"cmt">// all layers except the output in reverse order
        {
           ...
           class="macro">#ifdef BATCH_PROP
           matrix delta = speed[i] * outputs[i].Transpose().MatMul(loss);
           adjustSpeed(speed[i], delta * deltas[i]);
           deltas[i] = delta;
           class="macro">#else
           matrix delta = speed * outputs[i].Transpose().MatMul(loss);
           class="macro">#endif
           ...
        }
        ...
     }
     class="type">void adjustSpeed(matrix &subject, const matrix &product)
     {
        for(class="type">int i = class="num">0; i < (class="type">int)product.Rows(); ++i)
        {
           for(class="type">int j = class="num">0; j < (class="type">int)product.Cols(); ++j)
           {
              if(product[i][j] > class="num">0)
              {
                 subject[i][j] *= plus;
                 if(subject[i][j] > max) subject[i][j] = max;
              }
              else if(product[i][j] < class="num">0)
              {
                 subject[i][j] *= minus;
                 if(subject[i][j] < min) subject[i][j] = min;
              }
           }
        }
     }

训练里抓最小误差权重并落盘

矩阵网络按“世代”循环训练:每个世代把整批训练向量灌进网络,行是样本记录(比如一根根的 OHLC+成交量报价栏),列是字段维度。沿梯度调权本质仍是随机过程——目标函数形态和可变学习速率不均,会让某些世代反而跑到更差的权重配置,所以世代数堆上去并不保证误差单调下降。 因此要在循环里持续盯整体误差:一旦当前世代算出的候选误差(验证集优先,没有就用训练集 mse)小于已记录最小值,就把这一刻的全部权重矩阵拷进 bestWeights。代码里 stats.bestLoss 初值设 DBL_MAX、bestEpoch 设 -1,就是用来接住第一个真正更小的候选值。 训练结束后,getWeights 取最终权重,getBestWeights 取历史最优权重;两者都先判 ready 和矩阵非空,避免拿到空壳。单独一个接收 matrix 数组的构造函数 MatrixNet(w[], ...) 负责把现成权重直接装载成可推理的网络,跳过训练。 这些矩阵数组可以序列化进文件,下次 MT5 启动直接读回,恢复一个已训练就绪的网络。外汇与贵金属行情高波动、过拟合风险大,恢复的网络在历史样本上误差低,不代表实盘推演误差倾向同样低,上实盘前务必用样本外数据复核。

MQL5 / C++
<span class="keyword">class </span>MatrixNet
  {
     ...
  <span class="keyword">class="kw">public</span>:
     <span class="keyword">class="kw">struct</span> Stats
     {
        <span class="keyword">class="type">class="kw">double</span> bestLoss; <span class="comment">class=class="str">"cmt">// smallest error for all epochs</span>
        <span class="keyword">class="type">int</span> bestEpoch;   <span class="comment">class=class="str">"cmt">// index of the epoch with the minimum error</span>
        <span class="keyword">class="type">int</span> epochsDone;  <span class="comment">class=class="str">"cmt">// total number of completed epochs</span>
     };
     
     Stats getStats() <span class="keyword">const</span>
     {
        <span class="keyword">class="kw">return</span> stats;
     }
     
  <span class="keyword">class="kw">protected</span>:
     <span style="class="type">class="kw">color:rgb(class="num">0, class="num">87, class="num">174);">matrix</span> bestWeights[];
     Stats stats;
     ...
     <span class="keyword">class="type">class="kw">double</span> train(<span class="keyword">const</span> <span style="class="type">class="kw">color:rgb(class="num">0, class="num">87, class="num">174);">matrix</span> &amp;data, <span class="keyword">const</span> <span style="class="type">class="kw">color:rgb(class="num">0, class="num">87, class="num">174);">matrix</span> &amp;target,
        <span class="keyword">const</span> <span style="class="type">class="kw">color:rgb(class="num">0, class="num">87, class="num">174);">matrix</span> &amp;validation, <span class="keyword">const</span> <span style="class="type">class="kw">color:rgb(class="num">0, class="num">87, class="num">174);">matrix</span> &amp;check,
        <span class="keyword">const</span> <span class="keyword">class="type">int</span> epochs = class="num">1000, <span class="keyword">const</span> <span class="keyword">class="type">class="kw">double</span> accuracy = class="num">0.001,
        <span class="keyword">const</span> <span style="class="type">class="kw">color:rgb(class="num">226, class="num">8, class="num">0);">ENUM_LOSS_FUNCTION</span> lf = <span style="class="type">class="kw">color:rgb(class="num">226, class="num">8, class="num">0);">LOSS_MSE</span>)
     {
        ...
        stats.bestLoss = <span class="macro">DBL_MAX</span>;
        stats.bestEpoch = -class="num">1;
        DropOutState state(dropOutRate);
        <span class="keyword">class="type">int</span> ep = class="num">0;
        <span class="keyword">for</span>(; ep &lt; epochs; ep++)
        {
           ...
           <span class="keyword">const</span> <span class="keyword">class="type">class="kw">double</span> candidate = (msev != <span class="macro">DBL_MAX</span>) ? msev : mse;
           <span class="keyword">if</span>(candidate &lt; stats.bestLoss)
           {
              stats.bestLoss = candidate;
              stats.bestEpoch = ep;
              <span class="comment">class=class="str">"cmt">// save best weights from &class="macro">#x27;weights&class="macro">#x27;</span>
              <span class="keyword">for</span>(<span class="keyword">class="type">int</span> i = class="num">0; i &lt; n; ++i)
              {
                 bestWeights[i].Assign(weights[i]);
              }
           }
        }
        ...
     <span class="keyword">class="type">bool</span> getWeights(<span style="class="type">class="kw">color:rgb(class="num">0, class="num">87, class="num">174);">matrix</span> &amp;array[]) <span class="keyword">const</span>
     {
        <span class="keyword">if</span>(!ready) <span class="keyword">class="kw">return</span> <span class="keyword"><span class="macro">false</span></span>;
        
        <span class="functions">ArrayResize</span>(array, n);
        <span class="keyword">for</span>(<span class="keyword">class="type">int</span> i = class="num">0; i &lt; n; ++i)
        {
           array[i] = weights[i];
        }
        
        <span class="keyword">class="kw">return</span> <span class="keyword"><span class="macro">true</span></span>;
     }
     
     <span class="keyword">class="type">bool</span> getBestWeights(<span style="class="type">class="kw">color:rgb(class="num">0, class="num">87, class="num">174);">matrix</span> &amp;array[]) <span class="keyword">const</span>
     {
        <span class="keyword">if</span>(!ready) <span class="keyword">class="kw">return</span> <span class="keyword"><span class="macro">false</span></span>;
        <span class="keyword">if</span>(!n || !bestWeights[class="num">0].Rows()) <span class="keyword">class="kw">return</span> <span class="keyword"><span class="macro">false</span></span>;
        
        <span class="functions">ArrayResize</span>(array, n);
        <span class="keyword">for</span>(<span class="keyword">class="type">int</span> i = class="num">0; i &lt; n; ++i)
        {
           array[i] = bestWeights[i];
        }
        
        <span class="keyword">class="kw">return</span> <span class="keyword"><span class="macro">true</span></span>;
     }
     MatrixNet(<span class="keyword">const</span> <span style="class="type">class="kw">color:rgb(class="num">0, class="num">87, class="num">174);">matrix</span> &amp;w[], <span class="keyword">const</span> <span style="class="type">class="kw">color:rgb(class="num">226, class="num">8, class="num">0);">ENUM_ACTIVATION_FUNCTION</span> f1 = <span style="class="type">class="kw">color:rgb(class="num">226, class="num">8, class="num">0);">AF_TANH</span>,
        <span class="keyword">const</span> <span style="class="type">class="kw">color:rgb(class="num">226, class="num">8, class="num">0);">ENUM_ACTIVATION_FUNCTION</span> f2 = <span style="class="type">class="kw">color:rgb(class="num">226, class="num">8, class="num">0);">AF_NONE</span>):
        ready(<span class="keyword"><span class="macro">false</span></span>), af(f1), of(f2), n(<span class="functions">ArraySize</span>(w))
     {
        <span class="keyword">if</span>(n &lt; class="num">2) <span class="keyword">class="kw">return</span>;
        
        allocate();
        <span class="keyword">for</span>(<span class="keyword">class="type">int</span> i = class="num">0; i &lt; n; ++i)
        {
           weights[i] = w[i];
           <span class="keyword">class="macro">#ifdef</span> BATCH_PROP
           speed[i] = weights[i];  <span class="comment">class=class="str">"cmt">// instead .Init(.Rows(), .Cols())</span>
           deltas[i] = weights[i]; <span class="comment">class=class="str">"cmt">// instead .Init(.Rows(), .Cols())</span>
           <span class="keyword">class="macro">#endif</span>
        }
        
        ready = <span class="keyword"><span class="macro">true</span></span>;
     }

「用五条曲线把训练误差画在图上」

MatrixNet 自带的 progress 方法只往日志里吐数字,看不出训练走向。所以在 MatrixNet.mqh 里另写了一个从 MatrixNet 派生的 MatrixNetVisual 类,用 CGraphic 的派生小类 CMyGraphic 把按世代变化的误差直接绘成图,图形对象占满整个图表窗口。 这个类内部挂了 5 条 CCurve 曲线,横轴 p 是世代数,纵轴分别装:x 训练集误差、y 验证集误差、z 平滑验证误差(EMA)、q 平滑训练误差(EMA)、b 最小误差对应的世代点。构造函数一上来就调 graph() 建图并加好这五条线。 重写的 progress 方法里,每跑完一代就把 epoch、error、valid、ma、mav 压进对应双精度数组,然后调 plot() 刷新。plot() 用 Update 把数组推给各曲线,最后 CurvePlotAll + graphic.Update 完成重绘。外汇与贵金属模型训练样本外表现不确定,过拟合概率不低,图形只辅助观察、不构成任何方向暗示。 想自己看效果,在 MT5 里用 MatrixNetVisual 替代 MatrixNet 实例化,跑训练时图表会自动出图;若只想改显示范围,调 graph() 里 ChartGetInteger 取到的 width/height 即可。

MQL5 / C++
class MatrixNetVisual: class="kw">public MatrixNet
  {
     CMyGraphic graphic;
     CCurve *c[class="num">5];
     class="type">class="kw">double p[], x[], y[], z[], q[], b[];
     ...

  class="type">void graph()
  {

    class="type">ulong width = ChartGetInteger(class="num">0, CHART_WIDTH_IN_PIXELS);

    class="type">ulong height = ChartGetInteger(class="num">0, CHART_HEIGHT_IN_PIXELS);

    class="type">bool res = false;

    const class="type">class="kw">string objname = "BPNNERROR";

    if(ObjectFind(class="num">0, objname) >= class="num">0) res = graphic.Attach(class="num">0, objname);

    else res = graphic.Create(class="num">0, objname, class="num">0, class="num">0, class="num">0, (class="type">int)(width - class="num">0), (class="type">int)(height - class="num">0));

    if(!res) class="kw">return;

    c[class="num">0] = graphic.CurveAdd(p, x, CURVE_LINES, "Training");

    c[class="num">1] = graphic.CurveAdd(p, y, CURVE_LINES, "Validation");

    c[class="num">2] = graphic.CurveAdd(p, z, CURVE_LINES, "Val.EMA");

    c[class="num">3] = graphic.CurveAdd(p, q, CURVE_LINES, "Train.EMA");

    c[class="num">4] = graphic.CurveAdd(p, b, CURVE_POINTS, "Best/Minimum");

    ...
  }
class="kw">public:
  MatrixNetVisual(const class="type">int &layers[], const ENUM_ACTIVATION_FUNCTION f1 = AF_TANH,

      const ENUM_ACTIVATION_FUNCTION f2 = AF_NONE): MatrixNet(layers, f1, f2)
  {
    graph();
  }
     class="kw">virtual class="type">bool progress(const class="type">int epoch, const class="type">int total,
        const class="type">class="kw">double error, const class="type">class="kw">double valid = DBL_MAX,
        const class="type">class="kw">double ma = DBL_MAX, const class="type">class="kw">double mav = DBL_MAX) class="kw">override
     {
        class=class="str">"cmt">// fill all the arrays
        PUSH(p, epoch);
        PUSH(x, error);
        if(valid != DBL_MAX) PUSH(y, valid); else PUSH(y, nan);
        if(ma != DBL_MAX) PUSH(q, ma); else PUSH(q, nan);
        if(mav != DBL_MAX) PUSH(z, mav); else PUSH(z, nan);
        plot();
        
        class="kw">return MatrixNet::progress(epoch, total, error, valid, ma, mav);
     }

  class="type">void plot()
  {

    c[class="num">0].Update(p, x);

    c[class="num">1].Update(p, y);

    c[class="num">2].Update(p, z);

    c[class="num">3].Update(p, q);

    class="type">class="kw">double point[class="num">1] = {stats.bestEpoch};

    b[class="num">0] = stats.bestLoss;

    c[class="num">4].Update(point, b);

    ...

    graphic.CurvePlotAll();

    graphic.Update();
  }

◍ 用脚本跑通第一遍反向传播

MatrixNet 家族第一次实操,是靠 MatrixNet.mq5 脚本完成的。原始数据按已知分析记录人工生成,核心映射用了这个公式:f = ((x + y + z)^2 / (x^2 + y^2 + z^2)) / 3,比机器学习帮助主题里的示例多除了个 3,把值域压到 0~1。x、y、z 在 -10 到 +10 之间随机取点,单点输出就是 f 的一个标量。 脚本输入里 Epochs=1000、Accuracy=0.001、RandomNoise=0.0,噪声默认关掉。CreateData 负责填 data(100 行 3 列)和 target(100 行 1 列)两个矩阵;再调一次生成 25 行的验证集。网络结构写死成 {3, 11, 7, 1},在预处理前定义 BATCH_PROP 宏开启变速率加速。 训练时有效集误差转头向上就提前终止。默认参数下,日志里第 155 轮触发停止:验证误差 0.034642 已超过滑动均值 0.034371,而训练集误差停在 0.0166 附近。最佳权重拷给 net2 后,训练集误差 0.01546、验证集 0.03212——验证略差,但没崩。 把 RandomNoise 拉到 3.0,情况就难看了。同样跑完,训练结果误差 0.424,验证集 net2 误差直接 1.0629,接近函数值上限 1.0 的量级,关系几乎认不出来。实盘前自己洗数、去异常、查因子独立性的功夫,在这类自造数据里省了,但噪声实验说明真实报价丢进网络前不处理,识别依赖会非常吃力。 脚本末尾挂 while(!IsStopped()) 循环,把图留给你慢慢看。开 MT5 把这段直接丢进脚本,改 RandomNoise 从 0 调到 3,就能复现误差从 0.03 跳到 1.06 的过程。

MQL5 / C++
input class="type">int Epochs = class="num">1000;
  input class="type">class="kw">double Accuracy = class="num">0.001;
  input class="type">class="kw">double RandomNoise = class="num">0.0;
  class="type">bool CreateData(matrix &data, matrix &target, const class="type">int count)
  { 
     if(!data.Init(count, class="num">3) || !target.Init(count, class="num">1))
        class="kw">return false;
     data.Random(-class="num">10, class="num">10);
     vector X1 = MathPow(data.Col(class="num">0) + data.Col(class="num">1) + data.Col(class="num">2), class="num">2);
     vector X2 = MathPow(data.Col(class="num">0), class="num">2) + MathPow(data.Col(class="num">1), class="num">2) + MathPow(data.Col(class="num">2), class="num">2);
     if(!target.Col(X1 / X2 / class="num">3.0, class="num">0))
        class="kw">return false;
     if(RandomNoise > class="num">0)
     {
        matrix noise;
        noise.Init(count, class="num">3);
        noise.Random(class="num">0, RandomNoise);
        data += noise - RandomNoise / class="num">2;
        
        noise.Resize(count, class="num">1);
        noise.Random(-RandomNoise / class="num">2, RandomNoise / class="num">2);
        target += noise;
     }
     class="kw">return true; 
  }
  class="macro">#define BATCH_PROP
  class="macro">#include <MatrixNet.mqh>
  class="type">void OnStart()
  {
     const class="type">int layers[] = {class="num">3, class="num">11, class="num">7, class="num">1};
     MatrixNetVisual net(layers);
     matrix data, target;
     CreateData(data, target, class="num">100);
     matrix valid, test;
     CreateData(valid, test, class="num">25);
     ...
     Print("Training result: ", net.train(data, target, valid, test, Epochs, Accuracy));
     matrix w[];
     if(net.getBestWeights(w))
     {
        MatrixNet net2(w);
        if(net2.isReady())
        {
           Print("Best copy on training data: ", net2.test(data, target));
           Print("Best copy on validation data: ", net2.test(valid, test));
        }
     }
     while(!IsStopped())
     {
        Sleep(class="num">1000);
     }
  }
  EMA for early stopping: class="num">31 (class="num">0.062500)
  Epoch class="num">0 of class="num">1000, loss class="num">0.20296 ma(class="num">0.20296), validation class="num">0.18167 v.ma(class="num">0.18167)
  Epoch class="num">120 of class="num">1000, loss class="num">0.02319 ma(class="num">0.02458), validation class="num">0.04566 v.ma(class="num">0.04478)
  Stop by validation at class="num">155, v: class="num">0.034642 > class="num">0.034371, t: class="num">0.016614 vs class="num">0.016674
  Training result: class="num">0.015707719706513287
  Best copy on training data: class="num">0.015461956812387292
  Best copy on validation data: class="num">0.03211748853774414
  Epoch class="num">0 of class="num">1000, loss class="num">2.40352 ma(class="num">2.40352), validation class="num">2.23536 v.ma(class="num">2.23536)
  Stop by validation at class="num">163, v: class="num">1.082419 > class="num">1.080340, t: class="num">0.432023 vs class="num">0.432526
  Training result: class="num">0.4244786772678285
  Best copy on training data: class="num">0.4300476339855798
  Best copy on validation data: class="num">1.062895214094978

常见问题

大概率是过拟合。在训练循环里监控验证集误差,一旦连续若干轮不再下降就提前停止,并把验证误差最低那轮的权重存盘。
每 N 轮才刷新一次绘图与日志,异常输入做容错跳过单轮不中断,既省资源也能看清训练节奏。
可以。小布能按你给的品种与参数自动跑通训练、抓最小误差权重落盘,并把五条误差曲线画在对应页面供你直接看。
批量 rprop 用每层独立步长自适应更新,通常不用手工定学习率,收敛更稳,适合第一次跑通反向传播的新手。
看训练集与验证集误差曲线是否同步走低且验证集不反弹,并核对落盘权重对应的那轮是否为全局最小误差轮。