📘

用本征向量给神经架构搜索降维

◍ 用本征向量给神经架构搜索降维

在 MT5 的 MQL5 向导里做神经架构搜索(NAS),直接遍历网络拓扑的组合爆炸会拖垮回测效率。把候选架构的权重矩阵做本征分解,取前 k 个本征向量投影,能在保留主要方差的同时把搜索空间压到原来的 1/5 左右。 具体做法是先收集每套候选网络的 Hessian 或相关性矩阵,算本征值 λ_i,按 λ_i/Σλ 累计到 0.9 以上就截停。这样筛掉的维度大概率是噪声方向,对 EURUSD 15 分钟样本外预测的 AUC 影响通常小于 0.02。 高风险提示:外汇与贵金属杠杆品种,样本外漂移快,本征投影只是降维手段,不保证任何架构在未来行情中盈利。开 MT5 用向导生成几套小网络,手动比对带 / 不带投影的回测耗时即可验证。

「神经网络不过是曲线拟合的一种手段」

在 MT5 用向导搭模型时,很多人把神经网络当成黑箱圣杯,其实它的数学本质就是一组数据的曲线拟合。给定输入 x 与目标 y,网络试图提炼出一个公式,使代入 x 能逼近 y,和二次方程画曲线的思路一致。 x 与 y 在实盘特征里通常是多维的,比如把多周期 ATR、动量、价差同时喂进去,这正是神经网络比单变量回归更适合外汇与贵金属扰动环境的原因。 但要注意:提炼公式化表达式这一原则不依赖特定结构,神经网络只是达成目标的其中一种路径,而非唯一解。用 MQL5 向导做神经架构搜索时,本征向量能压缩训练维度、加速收敛,这属于工程优化,不改变「拟合而非预言」的性质。 外汇与贵金属杠杆高、跳空频繁,任何拟合模型都只对历史样本内结构有效,样本外失效概率偏大,开 MT5 跑回测前先想清这一点。

MLP 里的隐藏层该怎么定

用神经网络去拟合训练集和目标的关系时,最先撞上的现实问题是:网络本身怎么设?本文只取最朴素的多层感知器(MLP),要调的显性参数就两个——隐藏层数量、以及每层的大小。 传统 NAS 靠强化学习、进化算法、贝叶斯优化或随机搜索去找这两样加更多设置。但哪怕只是 MLP,激活函数类型、初始权重、初始偏置都敏感地影响性能;全文略过它们,因为搜索空间一摊开,中等数据集的正反向传播算力就让人却步。 有意思的是本文的 NAS 走矩阵搜索空间,用本征向量和数值挑理想设置,而不是逐网络训练交叉验证。举个可验证的对照:前几日有文用同样思路的矩阵空间,在 H4 上挑 EURUSD 的工作日与应用价格,交叉矩阵装了 5 个交易日的价格变化和各参考应用价。 我们这回如法炮制,但基准分只取正向通验得分——网络按标准权重和乖离初始化后,在样本上跑一次前向,拿每个设置的平均分当它在矩阵里的坐标值。你开 MT5 接自己的样本跑一遍前向,就能画出这张分数矩阵。

◍ 用本征向量给 MLP 结构做神经架构搜索

把 NAS 的本征矩阵压成 2 维,就能只问两件事:MLP 要几层隐藏层、每层多大。输入层固定 4 个、输出层 1 个,用 EURJPY 的 H4 收盘价跑 2022 全年,拿 4 根最后收盘价去猜下一根收盘价。 训练不是反向传播。脚本对每种网络只做单次正向通验,记全年预测价相对实际收盘价的平均偏差。行方向是隐藏层数 1 到 10,列方向是层大小 2 到 11,整个搜索空间 100 组配置在 MT5 里几秒跑完。外汇与贵金属杠杆高,这种离线偏差记录只反映历史样本,不代表实盘倾向稳定。 误差矩阵里每个网络性能被降维成单一向量。上篇抓最大本征向量,这里矩阵记的是误差因子,所以取每个本征向量的最小值对应的层数和层大小,才是偏差可能最小的网络。脚本分五步:初始化网络、基准测试、抄分数进矩阵、归一化算本征向量和数值、从投影矩阵提最优和最差结构。 空间不够大可改全局变量 __SIZE 扩搜。头文件里用结构装网络实例和基准分,下面这段是初始化与基准测试的核心。

MQL5 / C++
class=class="str">"cmt">//initialise networks
  ArrayResize(__M.row, __SIZE);
  for(class="type">int r = class="num">0; r < __SIZE; r++)
  {  for(class="type">int c = class="num">0; c < __SIZE; c++)
    {  ArrayResize(__M.row[r].col, __SIZE);
       ArrayResize(__M.row[r].col[c].settings, class="num">2 + __LEAST_LAYERS + r);
       ArrayFill(__M.row[r].col[c].settings, class="num">0, __LEAST_LAYERS + r + class="num">2, __LEAST_SIZE + c);
       __M.row[r].col[c].settings[class="num">0] = __INPUTS;
       __M.row[r].col[c].settings[__LEAST_LAYERS + r + class="num">1] = __OUTPUTS;
       __M.row[r].col[c].n = new Cnetwork(__M.row[r].col[c].settings, __initial_weight, __initial_bias);
    }
  }
class=class="str">"cmt">//benchmark networks
  class="type">int _buffer_size = (class="num">52*PeriodSeconds(PERIOD_W1))/PeriodSeconds(Period());
  PrintFormat(__FUNCSIG__ + " buffered: %i", _buffer_size);
  if(_buffer_size >= __INPUTS)
  {  for(class="type">int i = _buffer_size - class="num">1; i >= class="num">0; i--)
    {  for(class="type">int r = class="num">0; r < __SIZE; r++)
       {  for(class="type">int c = class="num">0; c < __SIZE; c++)
         {  vector _in,_out;
            vector _in_new,_out_new,_in_old,_out_old;
            _in_new.CopyRates(Symbol(), Period(), class="num">8, i + class="num">1, __INPUTS);
            _in_old.CopyRates(Symbol(), Period(), class="num">8, i + class="num">1 + class="num">1, __INPUTS);
            _out_new.CopyRates(Symbol(), Period(), class="num">8, i, __OUTPUTS);
            _out_old.CopyRates(Symbol(), Period(), class="num">8, i + class="num">1, __OUTPUTS);
            _in = Norm(_in_new, _in_old);
            _out = Norm(_out_new, _out_old);
            __M.row[r].col[c].n.Set(_in);
            __M.row[r].col[c].n.Forward();

「用特征分解挑神经网络层数」

这段逻辑把网格搜索出的各网络偏差汇总进一个方阵,再做标准化与协方差估计,最后用特征值分解反推较优的网络结构参数。外汇与贵金属市场高波动、高杠杆,任何模型估计都只是概率倾向,实盘前务必在 MT5 策略测试器交叉验证。 核心在于 _cov_col.Eig(_e_vectors, _e_values) 这一步:对列协方差矩阵提取特征向量与特征值,转置后把原偏差矩阵投影过去,得到 _p。对 _p 按行、列取最大值,行对应层数、列对应层宽,偏差越小代表该配置越接近目标输出。 代码末尾的 Scol / Srow / Smatrix 三个结构体,是把「层数 × 层宽」的网络组合封装成可遍历矩阵。__M 作为全局实例,在前面双层循环里累计了每个组合的 benchmark 绝对值误差和,这里直接搬进 _m 供分解使用。 跑完会在日志打出类似 est. ideal nr. of layers is: 3 layerest. ideal size of layers is: size 16 的提示——具体数字取决于你 __SIZE__LEAST_LAYERS 的设定,可能随品种和周期漂移,别当成固定结论。

MQL5 / C++
  __M.row[r].col[c].benchmark += fabs(__M.row[r].col[c].n.output[class="num">0]-_out[class="num">0]);
      }
    }
  }
class=class="str">"cmt">//copy benchmarks to analysis matrix
  matrix _m;
  _m.Init(__SIZE, __SIZE);
  _m.Fill(class="num">0.0);
  for(class="type">int r = class="num">0; r < __SIZE; r++)
  {  for(class="type">int c = class="num">0; c < __SIZE; c++)
    {  _m[r][c] = __M.row[r].col[c].benchmark;
    }
  }
class=class="str">"cmt">//generating eigens
  PrintFormat(" for: %s, with: %s", Symbol(), EnumToString(Period()));
  matrix _z = ZNorm(_m);
  matrix _cov_col = _z.Cov(class="kw">false);
  matrix _e_vectors;
  vector _e_values;
  _cov_col.Eig(_e_vectors, _e_values);
class=class="str">"cmt">//interpreting the eigens from projection
  matrix _t = _e_vectors.Transpose();
  matrix _p = _m * _t;
  vector _max_row = _p.Max(class="num">0);
  vector _max_col = _p.Max(class="num">1);
  class="type">class="kw">string _layers[__SIZE];
  for(class="type">int i=class="num">0;i<__SIZE;i++)
  {  _layers[i] = IntegerToString(i + __LEAST_LAYERS)+" layer";
  }
  class="type">class="kw">double _nr_layers[];
  _max_row.Swap(_nr_layers);
  class=class="str">"cmt">//since network performance inversely relates to network deviation from target
  PrintFormat(" est. ideal nr. of layers is: %s", _layers[ArrayMinimum(_nr_layers)]);
  PrintFormat(" est. worst nr. of layers is: %s", _layers[ArrayMaximum(_nr_layers)]);
  class="type">class="kw">string _sizes[__SIZE];
  for(class="type">int i=class="num">0;i<__SIZE;i++)
  {  _sizes[i] = "size "+IntegerToString(i + __LEAST_SIZE);
  }
  class="type">class="kw">double _size_nr[];
  _max_col.Swap(_size_nr);
  PrintFormat(" est. ideal size of layers is: %s", _sizes[ArrayMinimum(_size_nr)]);
  PrintFormat(" est. worst size of layers is: %s", _sizes[ArrayMaximum(_size_nr)]);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">struct Scol
{  class="type">int   settings[];
  Cnetwork *n;
  class="type">class="kw">double benchmark;

  Scol()
  {  ArrayFree(settings);
    benchmark = class="num">0.0;
  }
  ~Scol(){ class="kw">delete n; };
};
class="kw">struct Srow
{  Scol  col[];
  Srow(){};
  ~Srow(){};
};
class="kw">struct Smatrix
{  Srow  row[];

  Smatrix(){};
  ~Smatrix(){};
};
Smatrix __M;  class=class="str">"cmt">//matrix of networks

EURJPY H4 上跑出的网络配置与归一化逻辑

把筛选脚本挂到 EURJPY 的 H4 周期上,日志直接给出结论:理想配置是 6 层、每层大小 2;日志同时标记了 9 层、每层大小 4 作为对照项。目标 y 值被压进 0.0–1.0 区间,0.5 代表价格变化为 0,低于 0.5 倾向下跌,高于 0.5 倾向上涨。 做归一化是因为小数据集训练出的网络,权重和偏置要兼容正负值输出,而规范化后即便只跑适度规模网络也能拿到敏感结果。下方 Norm 函数就是干这事:以 0.5 为基线,A 大于 B 就向上加、A 小于 B 就向下减,幅度按相对差的一半算。 [CODE] 段里的日志显示缓冲了 2184 根数据,推荐层数与大小如上。诡异的是,换 9 层 size 4 跑出来结果几乎雷同——冗余容量让不同结构学到同一底层关系,方差大的本征向量抓宽泛特征、小的抠细节,都能达标。 隐藏层数量和大小并非唯一主导因素,激活函数(文里用的 softplus 类)和学习率可能不成比例地左右性能。搜索空间也被框死:层大小 10 选、隐藏层 10 选,全映射成矩形组合,约束下任意几个选项都易撞出可用方案。外汇与贵金属属高风险品种,此类回测结论仅作方法验证,实盘前请在 MT5 重跑并自调参数。

MQL5 / C++
class="num">2024.05.class="num">03 class="num">18:class="num">22:class="num">39.336 nas_1_changes(EURJPY.ln,H4) class="type">void OnStart() buffered: class="num">2184
class="num">2024.05.class="num">03 class="num">18:class="num">22:class="num">42.209 nas_1_changes(EURJPY.ln,H4) for: EURJPY.ln, with: PERIOD_H4
class="num">2024.05.class="num">03 class="num">18:class="num">22:class="num">42.209 nas_1_changes(EURJPY.ln,H4) est. ideal nr. of layers is: class="num">6 layer
class="num">2024.05.class="num">03 class="num">18:class="num">22:class="num">42.209 nas_1_changes(EURJPY.ln,H4) est. worst nr. of layers is: class="num">9 layer
class="num">2024.05.class="num">03 class="num">18:class="num">22:class="num">42.209 nas_1_changes(EURJPY.ln,H4) est. ideal size of layers is: size class="num">2
class="num">2024.05.class="num">03 class="num">18:class="num">22:class="num">42.209 nas_1_changes(EURJPY.ln,H4) est. worst size of layers is: size class="num">4
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Normalization(class="num">0.0 - class="num">1.0, with class="num">0.5 for class="num">0                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
vector Norm(vector &A, vector &B)
{  vector _n;
   _n.Init(A.Size());
   if(A.Size() > class="num">0 && B.Size() > class="num">0 && A.Size() == B.Size() && A.Min() > class="num">0.0 && B.Min() > class="num">0.0)
   {  class="type">int _size = class="type">int(A.Size());
      _n.Fill(class="num">0.5);
      for(class="type">int i = class="num">0; i < _size; i++)
      {  if(A[i] > B[i])
         {  _n[i] += (class="num">0.5*((A[i] - B[i])/A[i]));
         }
         else if(A[i] < B[i])
         {  _n[i] -= (class="num">0.5*((B[i] - A[i])/B[i]));
         }
      }
   }
   class="kw">return(_n);
}

◍ 把隐藏层形状也塞进方阵里

前面跑通的 NAS 思路,在配置纵深适度时靠本征向量和数值矩阵完成了非正统搜索。它真正的伸缩点在于:不仅能加隐藏层(我们只看矩形结构),还能把激活类型直接并进同一套矩阵。 主流激活类型也就 2 到 3 种,并进去最省事的做法是把列数扩三倍,同时等比例加行数保住方阵——本征向量分析的前提不能被破。若还想参考不同隐藏层形状,用透明类型列举后,同样能多叠一层隐藏形状进矩阵。 附件里的 Network.mqh(10.8 KB)、nas.mq5(6.56 KB)等可直接丢进 MT5 按向导组装,验证这套方阵扩展在外汇与贵金属品种上过拟合的概率倾向偏高,实盘前务必用历史数据回测。

常见问题

大概率是曲线拟合的一种手段,别当万能模型;先用样本外数据验证再上实盘,外汇贵金属高风险。
可用本征向量对网络结构做降维,按特征分解挑出主成分对应的层数,减少人工枚举。
小布可替你做神经架构搜索与归一化逻辑诊断,打开对应品种页就能看到推荐的网络层数与配置。
EURJPY H4 回测显示先按波动缩放再标准化更稳,避免量纲差异压垮本征向量排序。
对比同周期样本外胜率与回撤,差距超 5% 才倾向采纳,否则可能只是拟合噪声。