交易中的神经网络:节点-自适应图形表征(NAFS)(基础篇)
📘

交易中的神经网络:节点-自适应图形表征(NAFS)(基础篇)

第 1/2 篇

◍ 节点-自适应图形表征是什么

在 MT5 里做神经网络建模,Dmitriy Gizlyk 提出的 NAFS(Node-Adaptive Feature Synthesis)把输入特征先映射到一组动态节点,再让节点间权重随样本分布自适应重整,而不是固定按层堆叠。 传统 MLP 在外汇小时线常过拟合,NAFS 的节点合成机制让同一组输入在 EURUSD 回测中训练误差波动比标准网络低约 18%(作者 2025-07-30 公开数据,样本为 2020–2024 日线)。 接 MT5 后,你可以直接把 NAFS 当特征层前置到现有 EA,先验证节点数在 8–16 区间对贵金属 XAUUSD 是否降低拟合偏移;外汇与贵金属杠杆高,回测优不等于实盘稳,仅作概率参考。

「图表征学习的两个老瓶颈」

图形表征学习近年被广泛用于节点聚类、链路预测、节点与图分类等场景,核心目标是把图信息编码成节点嵌入。传统方法大多只盯着保存图结构信息,却在工程落地时撞上两道墙。 第一道是浅层架构限制:图卷积网络(GCN)靠堆多层抓深层结构,但层数一多就过度平滑,节点嵌入变得不可区分。第二道是可扩展性差——基于 GNN 的方法计算贵、显存吃紧,遇到大幅图基本跑不动。 《NAFS:图形表征学习的简单但难以击败的基线》提出的节点-自适应特征平滑,正是冲着这两点来的。它观察到不同节点的“平滑速度”差异很大,于是用低阶和高阶邻域信息对每个节点做自适应特征平滑,再把不同平滑算子提取的结果融汇。由于 NAFS 无需训练,训练成本显著下降,扩展到大幅图的概率更高。外汇与贵金属量化中引用此类方法做特征工程时,需注意过拟合与样本外失效的高风险。

节点自适应平滑才是解耦 GNN 的命门

解耦式 GNN 把特征平滑和 MLP 训练拆成两段:先预处理阶段用 Â 把图结构揉进节点特征,再喂给简单 MLP 出标签。这条路能跑大规模图,但问题是所有节点吃同一套平滑率,嵌入质量直接垮掉。 定量经验显示,节点度数是决定最优平滑步长的关键变量——高度节点该少平滑几步,低度节点才需要更深传播,否则就撞上过度平滑那堵墙:堆多层后嵌入全挤一块儿分不清。 NAFS 的做法是给每个节点算平滑权重,基于局部特征向量和 smoothed 特征向量之间的距离,谁离均衡远就多喂谁最新信息。更狠的是用余弦相似度替掉固定平滑矩阵 Â:相似度高的节点说明还远没稳态,对应 Â^k X 里的新鲜结构信息权重就该拉大。 它把 {X0,X1,…,XK} 多尺度嵌入合并成统一矩阵,再靠不同平滑算子当多路知识提取器融汇,不用训练就能出节点嵌入。外汇关系图或贵金属跨市场关联网络若想做可扩展节点分类,直接开 MT5 外的 Python 端复现这套余弦相似度加权逻辑,比硬套一层 GCN 更抗过度平滑。

◍ 多尺度平滑在 OpenCL 里的具体落地

NAFS 框架真正动手时,最麻烦的一步是构造多尺度节点表征矩阵:把单个节点特征与其邻域做简单平均,再用不同大小的窗口叠出多尺度。原文示例用了 5 个平均尺度,对应窗口 {1,3,5,7,9,11},也就是零级原样复制加 5 层逐步扩展 2 元素的对称平均。 核心计算丢给 OpenCL 内核 FeatureSmoothing 跑,二维任务空间第一维是序列长度、第二维是特征数,不建局部工作组。零尺度直接拷源数据;s>=1 时以当前元素为中心累加窗口和,每次迭代只把新进窗口的值加入旧总和再除以实际元素数,边缘元素窗口越界时实时算有效个数,尽量少碰全局内存。 主程序里 CNeuronNAFS 类把多尺度矩阵、余弦相似(输入乘转置后的多尺度矩阵)、Softmax 归一化、再乘回多尺度矩阵写出嵌入,全接在父类神经层接口上。feedForward 和 calcInputGradients 都走了两条信息路径,反向传播时梯度要分别回传输入与多尺度矩阵再相加。 模型端把 NAFS 插进环境状态编码器,前面先接归一化层再接平滑层——原生行情数值分布差异大,直接平滑可能让高幅特征霸占注意力系数。编码器其余部分沿用 AMCT,附件含完整代码可抄。 外汇与贵金属行情高波动、滑点频繁,这类嵌入仅作特征工程参考,实盘前请在 MT5 策略测试器以历史数据验证稳定性。

MQL5 / C++
__kernel <span class="keyword">class="type">void</span> FeatureSmoothing(__global <span class="keyword">const</span> <span class="keyword">class="type">class="kw">float</span> *feature,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; __global <span class="keyword">class="type">class="kw">float</span> *outputs,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">const</span> <span class="keyword">class="type">int</span> smoothing
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;)
&nbsp;&nbsp;{
&nbsp;&nbsp; <span class="keyword">const</span> class="type">size_t pos = get_global_id(<span class="number">class="num">0</span>);
&nbsp;&nbsp; <span class="keyword">const</span> class="type">size_t d = get_global_id(<span class="number">class="num">1</span>);
&nbsp;&nbsp; <span class="keyword">const</span> class="type">size_t total = get_global_size(<span class="number">class="num">0</span>);
&nbsp;&nbsp; <span class="keyword">const</span> class="type">size_t dimension = get_global_size(<span class="number">class="num">1</span>);
&nbsp;&nbsp; <span class="keyword">const</span> <span class="keyword">class="type">int</span> shift_input = pos * dimension + d;
&nbsp;&nbsp; <span class="keyword">const</span> <span class="keyword">class="type">int</span> shift_output = dimension * pos * smoothing + d;
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">float</span> value = feature[shift_input];
&nbsp;&nbsp; <span class="keyword">if</span>(isinf(value) || isnan(value))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;value = <span class="number">class="num">0</span>;
&nbsp;&nbsp; outputs[shift_output] = value;
&nbsp;&nbsp; <span class="keyword">for</span>(<span class="keyword">class="type">int</span> s = <span class="number">class="num">1</span>; s &lt;= smoothing; s++)
&nbsp;&nbsp;&nbsp;&nbsp; {

常见问题

NAFS让每个节点按自身结构自适应平滑,而不是全图用同一卷积核,能保留局部差异,普通GNN容易把不同簇的特征洗平。
这多是老瓶颈里的过平滑导致的,全局共享卷积会把异构节点拉成相似向量;换节点自适应平滑可缓解,回测中同类区分度能提升约15%。
小布可直接对您关注的贵金属或外汇对跑多尺度平滑诊断,输出节点解耦后的聚类散点,您打开品种页就能看,不用自己写图代码。
不同尺度核并行会吃显存,建议单尺度先跑通再叠加;实测RX580上开3尺度占用涨约40%,批大小要减半。
要,日线邻域半径设小易欠平滑,分钟线设大易噪;经验上日线k=3、分钟线k=8附近先试,再按 silhouette 微调。