数据科学和机器学习(第 12 部分):自训练神经网络能否帮助您跑赢股市?·进阶篇
📘

数据科学和机器学习(第 12 部分):自训练神经网络能否帮助您跑赢股市?·进阶篇

第 2/3 篇

◍ 回归网络的损失度量选项

在 MT5 的神经网络回归实现里,损失函数决定模型如何量化预测值与真实价格的偏差,标准库内置了多类度量供切换。 上述枚举列出了四种可直接调用的损失类型:LOSS_MSE 对应均方误差,对大偏差惩罚重;LOSS_MAE 为平均绝对误差,对异常点更鲁棒;LOSS_MSLE 是均方对数误差,适合价格跨度大的序列;LOSS_HUBER 则兼顾前两者的折中特性。 实盘接 MT5 验证时,换用不同 loss 枚举值跑同一段历史数据,观察验证集误差曲线的收敛形态,可能比死磕一种度量更有用。外汇与贵金属波动剧烈,模型误差仅作概率参考,杠杆交易高风险。

MQL5 / C++
enum loss
  {
    LOSS_MSE_ = LOSS_MSE,
    LOSS_MAE_ = LOSS_MAE,
    LOSS_MSLE_ = LOSS_MSLE,
    LOSS_HUBER_ = LOSS_HUBER
  };

用矩阵运算改写增量规则训练单层网络

增量规则本质是梯度下降在单层神经元上的特例:对每个神经元 j,权重 Wji 按目标输出与实际输出的差、激活函数导数和学习率来修正。但照公式写 for 循环会陷入维度泥潭,正确做法是用矩阵表达——ΔW = (损失函数导数 ⊙ 激活函数导数) · 输入矩阵转置,其中 ⊙ 是逐元素哈达玛积,L 与 O 同尺寸,右侧结果须与 W 同尺寸,否则维度直接崩。 MT5 自带的矩阵库把微积分藏起来了:你不用手写损失导数或激活导数,LossGradient 和 Derivative 方法直接给向量和矩阵。训练只需盯两样东西——epochs(整个数据集前向+反向走完一遍算一个世代,世代越多学得越细但耗时线性增长)和 alpha(步长,常见区间 0.00001~0.1,调大收敛快但可能跳过局部极小)。 先用玩具数据验证:X 为 {{1,2},{2,3},{3,4},{4,5},{5,6}},Y 为 {3,5,7,9,11},明显是 2*i+1 的线性形态。仅 5 个世代后准确率到 100%;喂入新值 {7,8} 得 14.97,四舍五入即 15,说明网络真的学到了映射。 接真实品种试试水:用苹果和特斯拉股价作自变量,去拟合 NAS100 指数(这两只占纳指权重较高)。把经纪商品种名改成你自己的 symbol_x / symbol_x2,EA 挂纳指图,跑完日志却可能输出一团噪声——神经网络大部分时间就这样,理解原理比堆库重要。外汇与贵金属类标的波动更无序,实盘训练属高风险尝试,参数失效概率不低。 下面这段是单样本训练循环的核心:先前向拿预测,算损失梯度与激活导数做哈达玛积,转置输入后矩阵乘得权重增量,再按 alpha 步进。外层 epoch 套内层样本 iter,IsStopped 防卡死。

MQL5 / C++
for (class="type">class="kw">ulong iter=class="num">0; iter<m_rows; iter++)
  {
    OUTPUT = ForwardPass(m_x_matrix.Row(iter)); class=class="str">"cmt">//前向传播拿输出
    pred = matrix_utils.MatrixToVector(OUTPUT); class=class="str">"cmt">//矩阵转向量
    
    actual[class="num">0] = m_y_vector[iter]; class=class="str">"cmt">//取当前样本真实值
    
    preds[iter] = pred[class="num">0]; class=class="str">"cmt">//记录预测
    actuals[iter] = actual[class="num">0]; class=class="str">"cmt">//记录实际
    
class=class="str">"cmt">//---
    
    INPUT = matrix_utils.VectorToMatrix(m_x_matrix.Row(iter)); class=class="str">"cmt">//输入转矩阵
    
    vector loss_v = pred.LossGradient(actual, ENUM_LOSS_FUNCTION(L_FX)); class=class="str">"cmt">//损失梯度
    
    LOSS_DX.Col(loss_v, class="num">0); class=class="str">"cmt">//塞进损失导数列
    
    OUTPUT.Derivative(OUTPUT, ENUM_ACTIVATION_FUNCTION(A_FX)); class=class="str">"cmt">//激活导数
    
    OUTPUT = LOSS_DX * OUTPUT; class=class="str">"cmt">//哈达玛积
    
    INPUT = INPUT.Transpose(); class=class="str">"cmt">//输入转置
    DX_W = OUTPUT.MatMul(INPUT); class=class="str">"cmt">//矩阵乘得权重梯度
    
    this.W -= (alpha * DX_W); class=class="str">"cmt">//梯度下降更新权重
}
  for (class="type">class="kw">ulong epoch=class="num">0; epoch<epochs && !IsStopped(); epoch++)
    {
      for (class="type">class="kw">ulong iter=class="num">0; iter<m_rows; iter++)
        {
          OUTPUT = ForwardPass(m_x_matrix.Row(iter));
          pred = matrix_utils.MatrixToVector(OUTPUT);
          
          actual[class="num">0] = m_y_vector[iter];
          
          preds[iter] = pred[class="num">0];
          actuals[iter] = actual[class="num">0];
          
          class=class="str">"cmt">//---
          
          INPUT = matrix_utils.VectorToMatrix(m_x_matrix.Row(iter));
          
          vector loss_v = pred.LossGradient(actual, ENUM_LOSS_FUNCTION(L_FX));

「反向传播里权重怎么动」

这段训练循环的核心在反向传播:先把损失对输出的梯度存进 LOSS_DX 的第 0 列,再对 OUTPUT 做激活函数求导,两者逐元素相乘得到当前层误差信号。 INPUT 被转置后与 OUTPUT 做矩阵乘法得到 DX_W,即损失对权重的梯度;随后 this.W 按学习率 alpha 做梯度下降更新。注意这里 alpha 若设大,权重可能跳过局部最优。 OnInit 里用 5 行 3 列矩阵做演示,前 two 列作特征、末列作标签,调用 XandYSplitMatrices 拆出 x_matrix 与 y_vector,再以 0.01 学习率、100 轮、ReLU、MSE 建网。 日志给出实打实的数字:第 1 轮 Loss=56.22401001、accuracy -6.028;第 2 轮 Loss 降到 2.81560904、accuracy 0.648;第 3 轮 Loss=0.11757813、accuracy 0.985。外汇与贵金属行情噪声远高于此演示数据,直接套用这类线性拟合网络大概率过拟合,属高风险尝试。

MQL5 / C++
LOSS_DX.Col(loss_v, class="num">0);

OUTPUT.Derivative(OUTPUT, ENUM_ACTIVATION_FUNCTION(A_FX));

OUTPUT = LOSS_DX * OUTPUT;

INPUT = INPUT.Transpose();
DX_W = OUTPUT.MatMul(INPUT);

this.W -= (alpha * DX_W);
}

printf("[ %d/%d ] Loss = %.8f | accuracy %.3f ",epoch+class="num">1,epochs,preds.Loss(actuals,ENUM_LOSS_FUNCTION(L_FX)),metrics.r_squared(actuals, preds));
}
class="macro">#include <MALE5\Neural Networks\selftrain NN.mqh>
class="macro">#include <MALE5\matrix_utils.mqh>
CRegNeuralNets *nn;
CMatrixutils matrix_utils;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                   | 
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//---
   matrix Matrix = {
                  {class="num">1,class="num">2,class="num">3},
                  {class="num">2,class="num">3,class="num">5},
                  {class="num">3,class="num">4,class="num">7},
                  {class="num">4,class="num">5,class="num">9},
                  {class="num">5,class="num">6,class="num">11}
                   };

  matrix x_matrix; vector y_vector;

  matrix_utils.XandYSplitMatrices(Matrix,x_matrix,y_vector);

  nn = new CRegNeuralNets(x_matrix,y_vector,class="num">0.01,class="num">100, AF_RELU_, LOSS_MSE_); class=class="str">"cmt">//Training the Network

class=class="str">"cmt">//---
   class="kw">return(INIT_SUCCEEDED);
  }

◍ 训练日志里的过拟合信号

自训练神经网络EA在AAPL.O的H1周期上跑100轮迭代,日志里从第5轮起accuracy就锁在1.000,Loss压到0.0012~0.0019区间微幅波动,到第100轮Loss=0.00177732仍满准确率。这种训练集上完美拟合的曲线,在外汇与贵金属实盘里往往意味着过拟合概率偏高,样本外泛化能力可能骤降,属典型高风险信号。 日志末尾用 vector new_data={7,8} 做前向推理,nn.ForwardPass返回[[14.96557787153696]],说明网络把输入映射到一个具体数值。你可以直接把这段推理代码抄进MT5脚本,换自己的symbol和输入向量,看预测值是否随训练轮次剧烈漂移。 别把满准确率当圣经 训练日志accuracy=1.000只证明EA背下了这100个样本,不代表下一根K线还能命中。开MT5把n_samples调到500以上重训,观察Loss曲线是否还能这么丝滑。

MQL5 / C++
  vector new_data = {class="num">7,class="num">8};

  Print("Test ");
  Print(new_data," pred = ",nn.ForwardPass(new_data));

class="kw">input class="type">class="kw">string symbol_x = "Apple_Inc_(AAPL.O)";
class="kw">input class="type">class="kw">string symbol_x2 = "Tesco_(TSCO.L)";
class="kw">input ENUM_COPY_RATES copy_rates_x = COPY_RATES_OPEN;
class="kw">input class="type">int n_samples = class="num">100;

训练日志暴露的收敛停滞

把两个品种的标准化价格序列塞进 2 列矩阵,再把当前图表收盘价作为监督目标,初始化一个学习率 0.01、迭代 1000 次、ReLU 激活、MSE 损失的自回归神经网络,这套写法在 MT5 里能直接跑。 但日志不会骗人:第 1 次迭代 Loss 高达 353809311769.09,accuracy 为 -27061631.73;从第 2 次到第 1000 次,Loss 卡死在 149221473.48,accuracy 固定在 -11412.427,说明梯度在这套数据上已经完全不动。 这种停滞大概率来自输入未做归一化或标签尺度过大,使得 MSE 梯度被数值范围淹没。外汇与贵金属品种价差量级差异大,直接 CopyRates 拼矩阵极易复现该现象,属于高风险建模坑。 开 MT5 把这段矩阵构造和训练循环贴上,盯前 10 行日志即可确认是否掉进同样的平坦区。

MQL5 / C++
  matrix x_matrix(n_samples,class="num">2); vector y_vector;
  vector x_vector;
class=class="str">"cmt">//---
  x_vector.CopyRates(symbol_x,PERIOD_CURRENT,copy_rates_x,class="num">0,n_samples);
  x_matrix.Col(x_vector, class="num">0);
  x_vector.CopyRates(symbol_x2, PERIOD_CURRENT,copy_rates_x,class="num">0,n_samples);
  x_matrix.Col(x_vector, class="num">1);

  y_vector.CopyRates(Symbol(), PERIOD_CURRENT,COPY_RATES_CLOSE,class="num">0,n_samples);

  nn = new CRegNeuralNets(x_matrix,y_vector,class="num">0.01,class="num">1000, AF_RELU_, LOSS_MSE_);
CS      class="num">0      class="num">21:class="num">29:class="num">20.698    Self Trained NN EA(NAS100,M30) [ class="num">1/class="num">1000 ] Loss = class="num">353809311769.08959961 | accuracy -class="num">27061631.733
CS      class="num">0      class="num">21:class="num">29:class="num">20.698    Self Trained NN EA(NAS100,M30) [ class="num">2/class="num">1000 ] Loss = class="num">149221473.48209998 | accuracy -class="num">11412.427
CS      class="num">0      class="num">21:class="num">29:class="num">20.699    Self Trained NN EA(NAS100,M30) [ class="num">3/class="num">1000 ] Loss = class="num">149221473.48209998 | accuracy -class="num">11412.427
CS      class="num">0      class="num">21:class="num">29:class="num">20.699    Self Trained NN EA(NAS100,M30) [ class="num">4/class="num">1000 ] Loss = class="num">149221473.48209998 | accuracy -class="num">11412.427
CS      class="num">0      class="num">21:class="num">29:class="num">20.699    Self Trained NN EA(NAS100,M30) [ class="num">5/class="num">1000 ] Loss = class="num">149221473.48209998 | accuracy -class="num">11412.427
CS      class="num">0      class="num">21:class="num">29:class="num">20.699    Self Trained NN EA(NAS100,M30) [ class="num">6/class="num">1000 ] Loss = class="num">149221473.48209998 | accuracy -class="num">11412.427
....
....
CS      class="num">0      class="num">21:class="num">29:class="num">20.886    Self Trained NN EA(NAS100,M30) [ class="num">1000/class="num">1000 ] Loss = class="num">149221473.48209998 | accuracy -class="num">11412.427

「归一化选错会让网络直接抽风」

别以为把数据往模型里一塞就能训练。即便不是所有数据集都在规范化后表现最好,有些简单测试集一旦做归一化,反而出恼人的结果:网络回吐的值会飘到离谱区间,我实测过,比原始标度还糟。 常用手段就三板斧:最小-最大缩放、均值归一化、Z分数标准化。本例只用了最小-最大缩放,因为它在这套数据上给出了我们想要的输出,其他两种没在这轮验证里跑通。 外汇与贵金属行情序列做NN预处理时高风险,缩放区间若选错,回测曲线好看实盘可能直接崩。建议开MT5把同一段XAUUSD的M1序列分别用三种方式缩一遍,对比隐藏层输出分布再定。

常见问题

异常点多的行情优先用平均绝对误差,均方误差会放大极端值导致权重乱跑;先在小样本上对比两者曲线再定。
矩阵化后梯度量级通常变大,学习率建议降到逐样本更新的 1/10 左右,看训练误差是否平滑下降再微调。
小布可接入你的训练日志,当验证误差连续抬升或损失多轮无变化时报预警,省去你人工刷屏看盘。
拿一个微小扰动测_loss变化,若权重朝使损失下降的方向动就对了;可用打印单步前后损失差来确认。
改用稳健归一化如分位数缩放,或在线更新归一化边界并截断输入,避免极值把激活层推到饱和区。