MQL5中的范畴论(第20部分):自我注意的迂回与转换·进阶篇
自注意力里的矩阵初始化与脏数清理
在 MT5 用 MQL5 写自注意力模块时,输入矩阵先调一次 ReplaceNan(0.0) 是必要动作,否则后续点积容易把 NaN 扩散进权重。 Queries、Keys、Values 三个矩阵都按 Input 的行列数 Init 后 Fill(0.0),保证未计算位置零值占位,不残留上一次推理的脏数据。 逐行跑 MLPProcess 拿到 _q_outputs 等向量后,必须对每个元素用 MathIsValidNumber 判一遍,无效数强行置 0.0;实测不判的话,外汇 1 分钟序列在跳空时段有概率吐出 NaN 让整个 Decoder 失效。 最后写回 Queries[i][ii] 等位置再 ReplaceNan(0.0) 兜底,三重清理看似冗余,但在贵金属夜盘低流动性段能明显降低矩阵崩坏概率。
class="type">void CSignalCT::SelfAttention(class="type">int &DecoderIndex,matrix &Input,matrix &Queries,matrix &Keys,matrix &Values) { Input.ReplaceNan(class="num">0.0); class=class="str">"cmt">// Queries.Init(class="type">int(Input.Rows()),class="type">int(Input.Cols()));Queries.Fill(class="num">0.0); Keys.Init(class="type">int(Input.Rows()),class="type">int(Input.Cols()));Keys.Fill(class="num">0.0); Values.Init(class="type">int(Input.Rows()),class="type">int(Input.Cols()));Values.Fill(class="num">0.0); for(class="type">int i=class="num">0;i<class="type">int(Input.Rows());i++) { class="type">class="kw">double _x_inputs[],_q_outputs[],_k_outputs[],_v_outputs[]; vector _i=Input.Row(i);ArrayResize(_x_inputs,class="type">int(_i.Size())); for(class="type">int ii=class="num">0;ii<class="type">int(_i.Size());ii++){ _x_inputs[ii]=_i[ii]; } m_base_q[DecoderIndex].MLPProcess(m_mlp_q[DecoderIndex],_x_inputs,_q_outputs); m_base_k[DecoderIndex].MLPProcess(m_mlp_k[DecoderIndex],_x_inputs,_k_outputs); m_base_v[DecoderIndex].MLPProcess(m_mlp_v[DecoderIndex],_x_inputs,_v_outputs); for(class="type">int ii=class="num">0;ii<class="type">int(_q_outputs.Size());ii++){ if(!MathIsValidNumber(_q_outputs[ii])){ _q_outputs[ii]=class="num">0.0; }} for(class="type">int ii=class="num">0;ii<class="type">int(_k_outputs.Size());ii++){ if(!MathIsValidNumber(_k_outputs[ii])){ _k_outputs[ii]=class="num">0.0; }} for(class="type">int ii=class="num">0;ii<class="type">int(_v_outputs.Size());ii++){ if(!MathIsValidNumber(_v_outputs[ii])){ _v_outputs[ii]=class="num">0.0; }} for(class="type">int ii=class="num">0;ii<class="type">int(Queries.Cols());ii++){ Queries[i][ii]=_q_outputs[ii]; } Queries.ReplaceNan(class="num">0.0); for(class="type">int ii=class="num">0;ii<class="type">int(Keys.Cols());ii++){ Keys[i][ii]=_k_outputs[ii]; } Keys.ReplaceNan(class="num">0.0); for(class="type">int ii=class="num">0;ii<class="type">int(Values.Cols());ii++){ Values[i][ii]=_v_outputs[ii]; } Values.ReplaceNan(class="num">0.0); } }
「用注意力机制筛出可交易的品种」
把变换解码器用在实盘里,最直接的一件事就是替你圈出哪些证券值得拿去跟解码器搭伙交易。把时间轴拉到几十年、跨多个品种回测,能筛出哪一些该继续深挖、哪一些碰都别碰——这一步没有额外系统开发做支撑,结论大概率会漂移。 在解码器的堆叠结构里,自注意层才是命门。它前面的前馈网络非常直白,真正产生优势的是:价格变化的相对重要性可以被相关函数按均值关注的方式轻松掩盖掉噪声。也就是说,哪一段历史波动对当下更管用,注意力权重自己会挑。 用多层感知机去学查询、键、值向量的权重矩阵只是其中一种路子,中间还可以换别的机器学习模块来顶替。但不管怎么换,自注意力对网络可预测性的敏感度必须摸透,否则外汇和贵金属这种高杠杆、高波动市场里,模型一抖你可能就被洗出去。
◍ 训练权重不可存,结果必然漂移
信号类的网络训练在每个新柱上增量执行,且 MQL5 框架内没有加载预训练权重的接口。实测中,同一段信号类代码在不同时刻运行,得到的权重矩阵和输出序列往往不同——这是随机初始化叠加增量训练导致的必然现象,交易者不能把它当稳定模型用。 更麻烦的是,信号类结束时不支持把训练好的权重落盘。也就是说,你这趟跑出来的网络状态,下一次 MT5 加载 EA 时全丢了,没法在已有基础上继续训,也没法拿去对样本外行情做固定权重的回测。 在把这类变换解码器往实盘交易系统推之前,得先补上「权重存取」和「样本外检验」两块。外汇与贵金属杠杆高、滑点跳空频繁,用每次随机权重的信号直接下单,风险敞口完全不可控。
自注意给交易者的两点实在增益
自注意算法本质上在量化标记之间的相对相似性。在我们这套框架里,标记就是不同但连续时间点上的价格变化;换到别的模型,可能是经济指标、新闻情绪值,过程一致,但输出会暴露这些输入的复杂动态关系,便于建模理解。 即便解码器转换器没做严格的权重记录与读取,也展现出了一定潜力。它能在后续训练中自适应从输入令牌提取相关特征,面对大量新闻爆料的动荡行情,模型可能筛掉白噪声、表现得更有弹性。 当输入带不同时间点的滞后,自注意能量化各时期的相对重要性,捕捉长期依赖。这带来跨时间框架处理预测的能力——若你用滞后时间指标或价格做输入,模型不仅可能给出各类经济指标的侧重,还会透露不同时间框架的权重分布,这点对交易者直接有用。 外汇与贵金属属高风险品种,此类算法优势只是概率性辅助,实盘前务必在 MT5 用历史数据自测。
「最后一句大实话」
这套仅解码变换器的源码(ct_20_r2.mq5 约 6.48 KB、SignalCT_20_r2.mqh 约 17.04 KB)并非开箱即用的 EA,必须走 MQL5 向导编译成信号类才能进策略测试器跑回测,权重读写还得自己补。 外汇与贵金属市场高杠杆、高波动,这类网络实现只适合拿来验证想法,实盘前请在 MT5 用历史数据跑通权重存取逻辑,别直接挂真仓。 作者把材料权利留在平台方,转载受限;你真要改,就本地 fork 一份离线研究,别碰分发红线。