数据科学和机器学习(第 12 部分):自训练神经网络能否帮助您跑赢股市?·进阶篇
◍ 回归网络的损失度量选项
在 MT5 的神经网络回归实现里,损失函数决定模型如何量化预测值与真实价格的偏差,标准库内置了多类度量供切换。 上述枚举列出了四种可直接调用的损失类型:LOSS_MSE 对应均方误差,对大偏差惩罚重;LOSS_MAE 为平均绝对误差,对异常点更鲁棒;LOSS_MSLE 是均方对数误差,适合价格跨度大的序列;LOSS_HUBER 则兼顾前两者的折中特性。 实盘接 MT5 验证时,换用不同 loss 枚举值跑同一段历史数据,观察验证集误差曲线的收敛形态,可能比死磕一种度量更有用。外汇与贵金属波动剧烈,模型误差仅作概率参考,杠杆交易高风险。
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 防卡死。
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。外汇与贵金属行情噪声远高于此演示数据,直接套用这类线性拟合网络大概率过拟合,属高风险尝试。
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曲线是否还能这么丝滑。
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 行日志即可确认是否掉进同样的平坦区。
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序列分别用三种方式缩一遍,对比隐藏层输出分布再定。