交易中的神经网络:超点变换器(SPFormer)·进阶篇
(2/3)· 跳过语义分支与后期处理,用超点+查询解码器直接出实例掩码
SPFormer 神经元的掩码梯度与结构骨架
在基于 OpenCL 的 Transformer 变体里,掩码梯度不能直接反向透传,必须按阈值截断。下面这段内核逻辑遍历 key 维度,当掩码值低于 mask_level 时梯度置 0,否则回写 1 减原掩码值,等于把软门控的饱和区掐掉。 for(int k = q_id; k < kunits; k += qunits) { float m = mask[shift_s + k]; if(m < mask_level) mask_g[shift_s + k] = 0; else mask_g[shift_s + k] = 1 - m; } CNeuronSPFormer 这个类把整套稀疏点交叉注意力打包进一个神经元层。它显式持有 iWindow / iUnits / iHeads 与对应的 SP(SuperPoints)分支窗口、单元数、头数,以及 iLayers、iLayersSP 控制堆叠深度。 内部缓冲区分两类:cSuperPoints、cQuery、cSPKeyValue、cMask 等 CLayer 负责参数与激活;cTempSP、cTempQ、cTempSelfKV、cTempCrossKV 这些 CBufferFloat 则是前向和梯度计算时的临时张量,避免每次推理重复申请显存。 AttentionOut 虚函数把 query 分支、key-value 分支、scores 索引、输出层和 mask 层全串起来,units 与 heads 作为参数传入,说明多头拆分在调用点动态决定,而不是写死在类里。外汇与贵金属行情用这类结构做序列建模时波动剧烈,属高风险场景,回测结论仅代表历史样本倾向。
__global class="kw">const class="type">float *mask, __global class="type">float *mask_g, __global class="kw">const class="type">float *scores, __global class="kw">const class="type">float *gradient, class="kw">const class="type">int kunits, class="kw">const class="type">int heads_kv, class="kw">const class="type">float mask_level ) { class=class="str">"cmt">//--- Mask&class="macro">#x27;s gradient for(class="type">int k = q_id; k < kunits; k += qunits) { class="type">float m = mask[shift_s + k]; if(m < mask_level) mask_g[shift_s + k] = class="num">0; else mask_g[shift_s + k] = class="num">1 - m; } } class CNeuronSPFormer : class="kw">public CNeuronBaseOCL { class="kw">protected: class="type">uint iWindow; class="type">uint iUnits; class="type">uint iHeads; class="type">uint iSPWindow; class="type">uint iSPUnits; class="type">uint iSPHeads; class="type">uint iWindowKey; class="type">uint iLayers; class="type">uint iLayersSP; class=class="str">"cmt">//--- CLayer cSuperPoints; CLayer cQuery; CLayer cSPKeyValue; CLayer cMask; CArrayInt cScores; CLayer cMHCrossAttentionOut; CLayer cCrossAttentionOut; CLayer cResidual; CLayer cQKeyValue; CLayer cMHSelfAttentionOut; CLayer cSelfAttentionOut; CLayer cFeedForward; CBufferFloat cTempSP; CBufferFloat cTempQ; CBufferFloat cTempSelfKV; CBufferFloat cTempCrossKV; class=class="str">"cmt">//--- class="kw">virtual class="type">bool CreateBuffers(class="type">void); class="kw">virtual class="type">bool AttentionOut(CNeuronBaseOCL *q, CNeuronBaseOCL *kv, class="kw">const class="type">int scores, CNeuronBaseOCL *out, CNeuronBaseOCL *mask, class="kw">const class="type">int units, class="kw">const class="type">int heads,
◍ SPFormer 神经元的接口与重载方法
在 MT5 的 OpenCL 神经网络框架里,CNeuronSPFormer 类把标准注意力与前馈逻辑做成了一套可序列化的算子。它对外暴露的虚函数几乎全部 override 了基类,意味着你替换网络层时不需要改训练循环。 注意力部分给了两个入口:AttentionInside 做前向打分,AttentionInsideGradients 回传梯度。两个方法都接收 q、kv 张量指针,以及 units_kv、heads_kv、dimension 等形状参数,mask_level 默认 0.5f,用来控制注意力遮罩的阈值。 Init 函数的参数表值得细看:window 与 window_key 分别控制查询和键的回溯窗口,units_count 与 heads 定义多头维度,window_sp、units_sp、heads_sp 则是稀疏(SP)分支的独立配置。layers 与 layers_to_sp 决定总层数和多少层走稀疏路径,最后 optimization_type 与 batch 接优化器与批大小。 类还重载了 Save / Load,文件句柄由调用方传入,方便你把训好的 SPFormer 权重直接落盘到 .nnw 文件。外汇与贵金属行情噪声大,用这类结构做特征提取时务必用小样本先验证过拟合风险。
class="kw">const class="type">int units_kv, class="kw">const class="type">int heads_kv, class="kw">const class="type">int dimension, class="kw">const class="type">float mask_level = class="num">0.5f); class="kw">virtual class="type">bool AttentionInsideGradients(CNeuronBaseOCL *q, CNeuronBaseOCL *kv, class="kw">const class="type">int scores, CNeuronBaseOCL *out, CNeuronBaseOCL *mask, class="kw">const class="type">int units, class="kw">const class="type">int heads, class="kw">const class="type">int units_kv, class="kw">const class="type">int heads_kv, class="kw">const class="type">int dimension, class="kw">const class="type">float mask_level = class="num">0.5f); class=class="str">"cmt">//--- class="kw">virtual class="type">bool feedForward(CNeuronBaseOCL *NeuronOCL) class="kw">override; class=class="str">"cmt">//--- class="kw">virtual class="type">bool calcInputGradients(CNeuronBaseOCL *NeuronOCL) class="kw">override; class="kw">virtual class="type">bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) class="kw">override; class="kw">public: CNeuronSPFormer(class="type">void) {}; ~CNeuronSPFormer(class="type">void) {}; class=class="str">"cmt">//--- class="kw">virtual class="type">bool Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl, class="type">uint window, class="type">uint window_key, class="type">uint units_count, class="type">uint heads, class="type">uint window_sp, class="type">uint units_sp, class="type">uint heads_sp, class="type">uint layers, class="type">uint layers_to_sp, ENUM_OPTIMIZATION optimization_type, class="type">uint batch); class=class="str">"cmt">//--- class="kw">virtual class="type">int Type(class="type">void) class="kw">override class="kw">const { class="kw">return defNeuronSPFormer; } class=class="str">"cmt">//--- class="kw">virtual class="type">bool Save(class="type">int class="kw">const file_handle) class="kw">override; class="kw">virtual class="type">bool Load(class="type">int class="kw">const file_handle) class="kw">override; class=class="str">"cmt">//---
「SPFormer 神经元的初始化链路」
在 MT5 的 OpenCL 神经网络扩展里,CNeuronSPFormer 的 Init 方法负责把时序窗口、多头维度与超点(SuperPoint)分支一次性铺开。它先调用基类 CNeuronBaseOCL::Init,输入维度被强制设为 window * units_count,这一步若返回 false,整个神经元直接废掉。 初始化里最值得盯的是两段 Query 缓冲:第一段 base 以 iWindow*iUnits 作输入、输出维度 1,第二段反过来用 1 作输入、iWindow*iUnits 作输出,两者都塞进 cQuery 数组。任何一次 BufferInit(1,1) 或 BufferWrite() 失败,函数都会提前 return false,开 MT5 跑自定义 EA 时若报空指针,八成卡在这两块缓冲没写通。 超点部分用 for(r=0; r<4; r++) 循环最多叠 4 层 CResidualConv。当 iSPUnits 为偶数时折半并接一个卷积残差块,窗口参数传 2*iSPWindow 与 iSPWindow;奇数则 iSPUnits-- 后跳过当前层。这种硬上限 4 层的写法意味着,layers_to_sp 参数实际生效层数不会超过 4,调参时别指望堆更多。 外汇与贵金属行情受杠杆与跳空影响,这类 GPU 加速模型仅作概率侧辅助,实盘前务必在策略测试器用历史数据回测验证。
class="kw">virtual class="type">bool WeightsUpdate(CNeuronBaseOCL *source, class="type">float tau) class="kw">override; class="kw">virtual class="type">void SetOpenCL(COpenCLMy *obj) class="kw">override; }; class="type">bool CNeuronSPFormer::Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl, class="type">uint window, class="type">uint window_key, class="type">uint units_count, class="type">uint heads, class="type">uint window_sp, class="type">uint units_sp, class="type">uint heads_sp, class="type">uint layers, class="type">uint layers_to_sp, ENUM_OPTIMIZATION optimization_type, class="type">uint batch) { if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, window * units_count, optimization_type, batch)) class="kw">return false; iWindow = window; iUnits = units_count; iHeads = heads; iSPUnits = units_sp; iSPWindow = window_sp; iSPHeads = heads_sp; iWindowKey = window_key; iLayers = MathMax(layers, class="num">1); iLayersSP = MathMax(layers_to_sp, class="num">1); class=class="str">"cmt">//--- Init Querys CNeuronBaseOCL *base = new CNeuronBaseOCL(); if(!base) class="kw">return false; if(!base.Init(iWindow * iUnits, class="num">0, OpenCL, class="num">1, optimization, iBatch)) class="kw">return false; CBufferFloat *buf = base.getOutput(); if(!buf || !buf.BufferInit(class="num">1, class="num">1) || !buf.BufferWrite()) class="kw">return false; if(!cQuery.Add(base)) class="kw">return false; base = new CNeuronBaseOCL(); if(!base.Init(class="num">0, class="num">1, OpenCL, iWindow * iUnits, optimization, iBatch)) class="kw">return false; if(!cQuery.Add(base)) class="kw">return false; class=class="str">"cmt">//--- Init SuperPoints for(class="type">int r = class="num">0; r < class="num">4; r++) { if(iSPUnits % class="num">2 == class="num">0) { iSPUnits /= class="num">2; CResidualConv *residual = new CResidualConv(); if(!residual) class="kw">return false; if(!residual.Init(class="num">0, r+class="num">2, OpenCL, class="num">2*iSPWindow, iSPWindow, iSPUnits, optimization, iBatch)) class="kw">return false; if(!cSuperPoints.Add(residual)) class="kw">return false; } else { iSPUnits--;
交叉注意力层的卷积初始化细节
在 MT5 的 OpenCL 神经网络封装里,跨注意力模块不是直接堆全连接,而是先用一维卷积把 Query、Key-Value 和 Mask 各自投影到不同头空间。上面这段就是逐层循环里对第 l 层做初始化的核心代码,每层偏移量用 l * 14 + 6 起步,说明单层占了至少 14 个内部索引槽位。 Query 分支的卷积窗口设为 iWindow,输出通道是 iWindowKey * iHeads,意味着多头数会直接放大显存占用;若 iHeads=8、iWindowKey=16,单这路就产出 128 个特征图。 Key-Value 只在 l % iLayersSP == 0 时新建,相当于每 iLayersSP 层才做一次超分辨率分支的下采样投影,能省掉重复计算。Mask 分支最后挂了 SIGMOID 激活,并用转置卷积把 iSPUnits 投回 iUnits * iHeads,这一步决定了注意力权重是否会被压到 0~1 区间。 开 MT5 把 iHeads 从 4 调到 8,若显卡是入门级核显,Init 返回 false 的概率会明显上升——显存不够时别硬撑。外汇与贵金属行情受杠杆影响,这类模型仅作辅助,实盘高风险。
CNeuronConvOCL *conv = new CNeuronConvOCL(); if(!conv.Init(class="num">0, r+class="num">2, OpenCL, class="num">2*iSPWindow, iSPWindow, iSPWindow, iSPUnits, class="num">1, optimization, iBatch)) class="kw">return false; if(!cSuperPoints.Add(conv)) class="kw">return false; } } CNeuronConvOCL *conv = NULL; CNeuronTransposeOCL *transp = NULL; for(class="type">uint l = class="num">0; l < iLayers; l++) { class=class="str">"cmt">//--- Cross Attention class=class="str">"cmt">//--- Query conv = new CNeuronConvOCL(); if(!conv) class="kw">return false; if(!conv.Init(class="num">0, l * class="num">14 + class="num">6, OpenCL, iWindow, iWindow, iWindowKey * iHeads, iUnits, class="num">1, optimization, iBatch)) class="kw">return false; if(!cQuery.Add(conv)) class="kw">return false; class=class="str">"cmt">//--- Key-Value if(l % iLayersSP == class="num">0) { conv = new CNeuronConvOCL(); if(!conv) class="kw">return false; if(!conv.Init(class="num">0, l * class="num">14 + class="num">7, OpenCL, iSPWindow, iSPWindow, iWindowKey * iSPHeads, iSPUnits, class="num">1, optimization, iBatch)) class="kw">return false; if(!cSPKeyValue.Add(conv)) class="kw">return false; } class=class="str">"cmt">//--- Mask conv = new CNeuronConvOCL(); if(!conv) class="kw">return false; if(!conv.Init(class="num">0, l * class="num">14 + class="num">8, OpenCL, iSPWindow, iSPWindow, iUnits * iHeads, iSPUnits, class="num">1, optimization, iBatch)) class="kw">return false; conv.SetActivationFunction(SIGMOID); if(!cMask.Add(conv)) class="kw">return false; transp = new CNeuronTransposeOCL(); if(!transp) class="kw">return false; if(!transp.Init(class="num">0, l * class="num">14 + class="num">9, OpenCL, iSPUnits, iUnits * iHeads, optimization, iBatch)) class="kw">return false; if(!cMask.Add(transp)) class="kw">return false; class=class="str">"cmt">//--- MH Cross Attention out base = new CNeuronBaseOCL(); if(!base) class="kw">return false;
◍ Transformer 块里多头注意力的层叠组装
这段初始化逻辑跑在循环体里,每一层 transformer 都用固定的 14 个索引步长来排布子模块,例如 Cross Attention 输出层占用 l*14+11,残差层占用 l*14+12,自注意力 Query 卷积落在 l*14+13。读懂这个偏移规律,你就能在 MT5 的神经网络调试器里直接定位某一层的张量维度异常。
Key-Value 分支不是每层都建:代码里用 l % iLayersSP == 0 做门控,只有当层号能被稀疏层间隔整除时才新增 cQKeyValue 卷积,这意味着稀疏多头注意力是隔层插入的,显存占用会呈现阶梯式跳变。
所有神经元都走 OpenCL 后端初始化,iBatch 作为批维度贯穿 Conv 与 Base 层。若你在贵金属 EA 里复用这套结构,注意 iWindow*iUnits*iHeads 的乘积一旦超过显卡缓冲区,Init 会静默返回 false,外汇与贵金属杠杆交易本身高风险,模型加载失败不等于策略失效,先排查显存再谈信号。
if(!base.Init(class="num">0, l * class="num">14 + class="num">10, OpenCL, iWindow * iUnits * iHeads, optimization, iBatch)) class="kw">return false; if(!cMHCrossAttentionOut.Add(base)) class="kw">return false; class=class="str">"cmt">//--- Cross Attention out conv = new CNeuronConvOCL(); if(!conv) class="kw">return false; if(!conv.Init(class="num">0, l * class="num">14 + class="num">11, OpenCL, iWindow * iHeads, iWindow * iHeads, iWindow, iUnits, class="num">1, optimization, iBatch)) class="kw">return false; if(!cCrossAttentionOut.Add(conv)) class="kw">return false; class=class="str">"cmt">//--- Residual base = new CNeuronBaseOCL(); if(!base) class="kw">return false; if(!base.Init(class="num">0, l * class="num">14 + class="num">12, OpenCL, iWindow * iUnits, optimization, iBatch)) class="kw">return false; if(!cResidual.Add(base)) class="kw">return false; class=class="str">"cmt">//--- Self-Attention class=class="str">"cmt">//--- Query conv = new CNeuronConvOCL(); if(!conv) class="kw">return false; if(!conv.Init(class="num">0, l*class="num">14+class="num">13, OpenCL, iWindow, iWindow, iWindowKey * iHeads, iUnits, class="num">1, optimization, iBatch)) class="kw">return false; if(!cQuery.Add(conv)) class="kw">return false; class=class="str">"cmt">//--- Key-Value if(l % iLayersSP == class="num">0) { conv = new CNeuronConvOCL(); if(!conv) class="kw">return false; if(!conv.Init(class="num">0, l*class="num">14+class="num">14, OpenCL, iWindow, iWindow, iWindowKey * iSPHeads, iUnits, class="num">1, optimization, iBatch)) class="kw">return false; if(!cQKeyValue.Add(conv)) class="kw">return false; } class=class="str">"cmt">//--- MH Attention out base = new CNeuronBaseOCL(); if(!base) class="kw">return false; if(!base.Init(class="num">0, l * class="num">14 + class="num">15, OpenCL, iWindow * iUnits * iHeads, optimization, iBatch)) class="kw">return false;
「自注意力层后的残差与前馈拼接」
在多层自注意力结构里,每一层除了注意力输出,还要接残差连接和前馈网络,否则深层梯度容易在反向传播时衰减到接近 0。下面这段初始化逻辑把第 l 层的输出维度算成 l*14+16 起步,每层多占 5 个索引号,写死在 Init 的参数里。 注意力输出用一个 1 维卷积把维度从 iWindow*iHeads 压回 iWindow,再进残差容器;紧接着的前馈部分先用卷积扩到 iWindow*4 并挂 LReLU 激活,再用第二个卷积收回 iWindow,这两步的索引分别是 l*14+18 和 l*14+19。 残差节点 CNeuronBaseOCL 在每层出现两次,第二次会调用 SetGradient(conv.getGradient()) 把前馈末层的梯度直接接过来。最后一层(l == iLayers-1)额外把梯度设给整体网络,这一步漏掉的话回测时损失可能卡住不降。外汇与贵金属行情噪声大,这类深度结构过拟合概率偏高,上 MT5 用历史数据跑前先小规模验证梯度通路。
if(!cMHSelfAttentionOut.Add(base)) class="kw">return false; class=class="str">"cmt">//--- Attention out conv = new CNeuronConvOCL(); if(!conv) class="kw">return false; if(!conv.Init(class="num">0, l * class="num">14 + class="num">16, OpenCL, iWindow * iHeads, iWindow * iHeads, iWindow, iUnits, class="num">1, optimization, iBatch)) class="kw">return false; if(!cSelfAttentionOut.Add(conv)) class="kw">return false; class=class="str">"cmt">//--- Residual base = new CNeuronBaseOCL(); if(!base) class="kw">return false; if(!base.Init(class="num">0, l * class="num">14 + class="num">17, OpenCL, iWindow * iUnits, optimization, iBatch)) class="kw">return false; if(!cResidual.Add(base)) class="kw">return false; class=class="str">"cmt">//--- FeedForward conv = new CNeuronConvOCL(); if(!conv) class="kw">return false; if(!conv.Init(class="num">0, l * class="num">14 + class="num">18, OpenCL, iWindow, iWindow, iWindow * class="num">4, iUnits, class="num">1, optimization, iBatch)) class="kw">return false; conv.SetActivationFunction(LReLU); if(!cFeedForward.Add(conv)) class="kw">return false; conv = new CNeuronConvOCL(); if(!conv) class="kw">return false; if(!conv.Init(class="num">0, l * class="num">14 + class="num">19, OpenCL, iWindow * class="num">4, iWindow * class="num">4, iWindow, iUnits, class="num">1, optimization, iBatch)) class="kw">return false; if(!cFeedForward.Add(conv)) class="kw">return false; class=class="str">"cmt">//--- Residual base = new CNeuronBaseOCL(); if(!base) class="kw">return false; if(!base.Init(class="num">0, l * class="num">14 + class="num">20, OpenCL, iWindow * iUnits, optimization, iBatch)) class="kw">return false; if(!cResidual.Add(base)) class="kw">return false; if(!base.SetGradient(conv.getGradient())) class="kw">return false; if(l == (iLayers - class="num">1)) { if(!SetGradient(conv.getGradient()))