用本征向量给神经架构搜索降维
◍ 用本征向量给神经架构搜索降维
在 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 扩搜。头文件里用结构装网络实例和基准分,下面这段是初始化与基准测试的核心。
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 layer 与 est. ideal size of layers is: size 16 的提示——具体数字取决于你 __SIZE、__LEAST_LAYERS 的设定,可能随品种和周期漂移,别当成固定结论。
__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 重跑并自调参数。
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 按向导组装,验证这套方阵扩展在外汇与贵金属品种上过拟合的概率倾向偏高,实盘前务必用历史数据回测。