交易中的神经网络:降低锐度强化变换器效率(SAMformer)(基础篇)
◍ SAMformer 用平滑换稳定
MQL5 社区在 2025 年 9 月 15 日发布的交易神经网络文章里,提出用 SAMformer 替代标准 Transformer 做行情序列建模。核心改动是在注意力计算后引入锐度感知最小化(Sharpness-Aware Minimization),让损失曲面变平,从而降低过拟合概率。 原文给出的社区互动数据为发布当日 447 次浏览、0 条评论,说明该思路尚处早期验证阶段,未形成共识。外汇与贵金属市场具有高杠杆和高波动风险,任何模型都只是概率工具,实盘前务必在 MT5 策略测试器做样本外回测。 具体落地动作:打开 MT5 → 打开 MetaEditor → 新建一个基于时间序列的 EA 框架,把标准多头注意力替换为带 SAM 扰动项的版本,先拿 XAUUSD 的 H1 数据跑一轮 walk-forward,观察 equity 曲线斜率是否比原 Transformer 更缓。
变换器进时间序列的暗坑
多变量时间序列预测本质是用历史形态推未来走势,难点在特征互相纠缠、长期依赖难抓。股票、贵金属价格序列就属于这类按顺序采样的现实数据,对模型结构是硬考验。 NLP 和 CV 里 transformer 已经封神,处理顺序数据本该顺手,但 multivariate 预测榜单上,不少 SOTA 反而靠更朴素的 MLP 模型撑着。原因不是架构不行,而是训练端出了岔子。 《SAMformer》论文点出一个被忽视的事实:数据量不够时,transformer 在时间序列上训练极不稳定,注意力矩阵会出现熵坍缩或秩坍缩,和 CV/NLP 里观察到的毛病同源。此前学界多忙着改注意力降算力或拆序列,却没正面解决「小数据过拟合+训不动」。 作者验证了一件事——只要把训练不稳定性按下去,transformer 在长期多变量预测上的表现就能明显跳升,直接打脸「变换器天生不适合时序」的旧结论。外汇和贵金属波动序列样本有限、噪声大,这类发现对实盘建模属于高风险高信息量的提醒。
「SAMformer 怎么把 transformer 砍到能跑时间序列」
做多变量长周期预测时,输入是 D×L 的矩阵 X,要吐出 D×H 的 Y。传统思路以为 transformer 必然更强,但近期回测显示:直接把输入投影到输出的线性网络,在很多任务上和 transformer 成绩相当。SAMformer 就是冲着这件事去拆架构的。 作者先用生成式模型造了 15000 个输入-输出对,10000 练、5000 验,合成数据由随机输入经线性延续加少量噪声得到。基于这套数据,他们只留自注意力加残差连接,把 FeedForward 换成线性预测层,做出一个不冗余的编码器。 关键点在于它用通道级注意力而不是时空注意力:注意力矩阵是 D×D 而非 L×L,在多数实盘数据集 D≤L 时显存和计算都更小,也顺带免了位置编码。注意力学的是「每个特征在所有时间步上对其它特征的平均影响」。 但实验暴露了 transformer 的硬伤。即便架构已经简化,标准 transformer 和随机注意力(只训预测层、注意力冻成随机值)的模型,都没法复原合成任务里的线性依赖。可视化发现注意力矩阵第一局后就近似单位阵、之后几乎不动,softmax 放大差异后触发熵坍缩,全秩却僵化,导致收敛到锐度极高的坏极小值。 外汇与贵金属序列短样本多、分布漂移快,这种坍缩在小数据集上更易翻车,实盘前务必用 MT5 历史数据复算。 解法上了两招:锐度感知最小化(SAM,ρ>0 控制扰动半径)和 σReparam 重参数化。结果只有 SAM 真的把极小值磨平——锐度比标准 transformer 低若干数量级,而注意力熵后期才略升,说明此处熵坍缩是良性的。再叠 RevIN 做可逆实例归一化抗分布偏移,最终就是单编码器的轻量模型。
◍ 在 OpenCL 里给全连接层算扰动权重
SAM 优化的核心一步,是先按当前梯度造出一组扰动参数 ω+ε,再拿这组参数跑第二遍前馈。原文给出的 OpenCL 内核 CalcEpsilonWeights 就是干这事:它接收权重、梯度、输入三个只读缓冲,外加一个输出缓冲 matrix_epsw,以及锐度系数 rho。 内核按二维任务空间派发,第一维对线程分组。每个线程先算出所分析元素的误差梯度(输出梯度与输入梯度的乘积),再按关联参数的绝对值缩放——对层输出贡献大的参数,扰动也会被放大。 随后用局部内存数组做两轮归约,求出梯度的 L2 范数。该范数的平方根用来把梯度归一化,再乘 rho 得到 ε,写进 matrix_epsw。这一步在 GPU 上跑,比在 CPU 端逐参数算要快一个数量级,尤其当全连接层参数超过 1e4 时差异明显。 下面这段是内核头部与线程辨识的原代码,注意它只声明到取线程 ID,后续归约循环在附件完整版里: __kernel void CalcEpsilonWeights( __global const float *matrix_w, // 当前层权重 __global const float *matrix_g, // 输出梯度 __global const float *matrix_i, // 输入梯度 __global float *matrix_epsw, // 输出的扰动权重 const float rho // 锐度半径系数 ) { const size_t inp = get_local_id(0); // 组内线程号 const size_t inputs = get_local_size(0) - 1; // 输入维度-1 const size_t out = get_global_id(1); // 输出神经元编号 } 别把归一化当全局黑盒。SAMformer 原作者是跨整个模型归一化梯度,计算密度极高;这里改成逐层、甚至逐参数组归一化,配合 OpenCL 内核,是把显存压力和耗时压下来的关键。开 MT5 接上自家 EA 时,若发现 SAM 训练卡顿,先查是不是误用了全局归一化。
__kernel <span class="keyword">class="type">void</span> CalcEpsilonWeights(__global <span class="keyword">const</span> <span class="keyword">class="type">class="kw">float</span> *matrix_w, __global <span class="keyword">const</span> <span class="keyword">class="type">class="kw">float</span> *matrix_g, __global <span class="keyword">const</span> <span class="keyword">class="type">class="kw">float</span> *matrix_i, __global <span class="keyword">class="type">class="kw">float</span> *matrix_epsw, <span class="keyword">const</span> <span class="keyword">class="type">class="kw">float</span> rho ) { <span class="keyword">const</span> class="type">size_t inp = get_local_id(<span class="number">class="num">0</span>); <span class="keyword">const</span> class="type">size_t inputs = get_local_size(<span class="number">class="num">0</span>) - <span class="number">class="num">1</span>; <span class="keyword">const</span> class="type">size_t <span class="keyword">out</span> = get_global_id(<span class="number">class="num">1</span>);