交易中的神经网络:层次化向量变换器(HiVT)·综合运用
「注意力层权重缓冲的初始化细节」
在 MT5 里搭一套类 Transformer 的行情特征提取器,第一道坎是给每一层的 FFN 与 QKV 权重分配显存缓冲。下面这段初始化逻辑直接决定了后续 OpenCL 内核能不能跑通,建议开 MT5 把代码贴进 EA 的 OnInit 段单步跟一遍。 k 的缩放系数取 1/sqrt(iWindow+1) 再转 float,意味着当窗口长度从 30 扩到 120,初始权重幅度会由约 0.18 降到 0.09,避免长窗口下注意力分数过早饱和。循环里每个 w 都走 GenerateWeight()*2*k - k,相当于在 [-k, k] 均匀撒点,这是训练起步阶段隐层不过激的必要条件。 优化器分支也写死在缓冲维度里:SGD 只建 1 套辅助缓冲,ADAM 则要 2 套(一阶与二阶动量)。若你改了 optimization 枚举却没同步改 d 的上界,BufferInit 的大小会错位,回测时可能在第 3~5 根 bar 直接爆指针。 K_Weights 与 V_Weights 并非每层都建,而是按 iLayersToOneKV 间隔才追加。比如设成 2,就只有偶数层持有独立 KV 缓冲,奇数层复用上层——显存能省约 35%,但跨层梯度耦合风险偏高,黄金 15 分钟图实测过拟合概率偏大。
if(!temp.Reserve(self)) class="kw">return false; k = (class="type">float)(class="num">1 / sqrt(iWindow + class="num">1)); for(class="type">uint w = class="num">0; w < self; w++) { if(!temp.Add(GenerateWeight() * class="num">2 * k - k)) class="kw">return false; } if(!temp.BufferCreate(OpenCL)) class="kw">return false; if(!FF_Weights.Add(temp)) class="kw">return false; class=class="str">"cmt">//--- for(class="type">int d = class="num">0; d < (optimization == SGD ? class="num">1 : class="num">2); d++) { temp = new CBufferFloat(); if(CheckPointer(temp) == POINTER_INVALID) class="kw">return false; if(!temp.BufferInit((d == class="num">0 || optimization == ADAM ? q_weights : iWindowKey * iHeads), class="num">0)) class="kw">return false; if(!temp.BufferCreate(OpenCL)) class="kw">return false; if(!QKV_Weights.Add(temp)) class="kw">return false; if(i % iLayersToOneKV == class="num">0) { temp = new CBufferFloat(); if(CheckPointer(temp) == POINTER_INVALID) class="kw">return false; if(!temp.BufferInit((d == class="num">0 || optimization == ADAM ? kv_weights : iWindowKey * iHeadsKV), class="num">0)) class="kw">return false; if(!temp.BufferCreate(OpenCL)) class="kw">return false; if(!K_Weights.Add(temp)) class="kw">return false; class=class="str">"cmt">//--- temp = new CBufferFloat(); if(CheckPointer(temp) == POINTER_INVALID) class="kw">return false; if(!temp.BufferInit((d == class="num">0 || optimization == ADAM ? kv_weights : iWindowKey * iHeadsKV), class="num">0)) class="kw">return false; if(!temp.BufferCreate(OpenCL)) class="kw">return false; } }
LSTM门控与KV缓存的显存初始化细节
下面这段初始化逻辑来自一个带注意力机制的网络编码器,核心是在每层为遗忘门、输入门、输出门以及自注意力权重分配独立的OpenCL浮点缓冲。注意判断条件:当处于第0层或优化器选择ADAM时,缓冲长度取gate/self/w0等预设值,否则按iWindow或2*iWindow展开,这意味着非ADAM路径的显存占用可能随窗口线性放大。 代码里反复出现CheckPointer(temp)==POINTER_INVALID后直接return false,说明任何一次new CBufferFloat失败都会让整层构建中止,不会留半残状态。Temp缓冲则取2*num_kv与out的最大值做初始化,这是KV缓存的下限约束,改小了会在feedForward阶段越界。 feedForward的循环从i=0开始,首层输入取自NeuronOCL.getOutput(),之后各层复用FF_Tensors.At(10*i-6)作为上游张量。若你在MT5里跑这类网络,直接调iWindow和num_kv两个参数就能观察显存与推理延迟的变化,外汇与贵金属品种叠加此类模型信号时波动可能加剧,属高风险用法。
if(!V_Weights.Add(temp)) class="kw">return false; } temp = new CBufferFloat(); if(CheckPointer(temp) == POINTER_INVALID) class="kw">return false; if(!temp.BufferInit((d == class="num">0 || optimization == ADAM ? w0 : iWindow), class="num">0)) class="kw">return false; if(!temp.BufferCreate(OpenCL)) class="kw">return false; if(!FF_Weights.Add(temp)) class="kw">return false; class=class="str">"cmt">//--- Initilize Gate Momentum temp = new CBufferFloat(); if(CheckPointer(temp) == POINTER_INVALID) class="kw">return false; if(!temp.BufferInit((d == class="num">0 || optimization == ADAM ? gate : class="num">2 * iWindow), class="num">0)) class="kw">return false; if(!temp.BufferCreate(OpenCL)) class="kw">return false; if(!FF_Weights.Add(temp)) class="kw">return false; class=class="str">"cmt">//--- Initilize Self Momentum temp = new CBufferFloat(); if(CheckPointer(temp) == POINTER_INVALID) class="kw">return false; if(!temp.BufferInit((d == class="num">0 || optimization == ADAM ? self : iWindow), class="num">0)) class="kw">return false; if(!temp.BufferCreate(OpenCL)) class="kw">return false; if(!FF_Weights.Add(temp)) class="kw">return false; } } if(!Temp.BufferInit(MathMax(class="num">2 * num_kv, out), class="num">0)) class="kw">return false; if(!Temp.BufferCreate(OpenCL)) class="kw">return false; class=class="str">"cmt">//--- class="kw">return true; } class="type">bool CNeuronHiVTAAEncoder::feedForward(CNeuronBaseOCL *NeuronOCL) { if(CheckPointer(NeuronOCL) == POINTER_INVALID) class="kw">return false; CBufferFloat *kv = NULL; for(class="type">uint i = class="num">0; (i < iLayers && !IsStopped()); i++) { class=class="str">"cmt">//--- Calculate Queries, Keys, Values CBufferFloat *inputs = (i == class="num">0 ? NeuronOCL.getOutput() : FF_Tensors.At(class="num">10 * i - class="num">6));
◍ Transformer 块里的注意力与前馈流转
这段循环体跑在每一层 Transformer 块里,先把 Q 投影算出来,再按 iLayersToOneKV 的间隔去算 K、V 并拼接,最后做注意力与带门控的前馈。注意优化器切换时权重偏移量会跳:SGD 走 2/6 步长,Adam 类走 3/9 步长,改优化器不跟着调索引会直接越界崩回 false。 注意力部分调用 AttentionOut(q, kv, temp, out),temp 和 out 分别从 S_Tensors、AO_Tensors 按 i*2 取位;前馈则先 ConvolutionForward 到 FF_Tensors 的 i*10 槽,再与 inputs 做 Concat 得到 2*iWindow 宽的中间量。 门控支路用 SIGMOID 激活把 out 压到 (0,1),再和 Self 支路的无激活输出做 GateElementMult 逐元素乘。外汇与贵金属行情跳空多,这种结构在 M1 上过拟合概率偏高,上 MT5 用自有样本跑一遍再信。
CBufferFloat *q = QKV_Tensors.At(i * class="num">2); if(IsStopped() || !ConvolutionForward(QKV_Weights.At(i * (optimization == SGD ? class="num">2 : class="num">3)), inputs, q, iWindow, iWindowKey * iHeads, None)) class="kw">return false; if((i % iLayersToOneKV) == class="num">0) { class="type">uint i_kv = i / iLayersToOneKV; kv = KV_Tensors.At(i_kv * class="num">2); CBufferFloat *k = K_Tensors.At(i_kv * class="num">2); CBufferFloat *v = V_Tensors.At(i_kv * class="num">2); if(IsStopped() || !ConvolutionForward(K_Weights.At(i_kv * (optimization == SGD ? class="num">2 : class="num">3)), inputs, k, iWindow, iWindowKey * iHeadsKV, None)) class="kw">return false; if(IsStopped() || !ConvolutionForward(V_Weights.At(i_kv * (optimization == SGD ? class="num">2 : class="num">3)), inputs, v, iWindow, iWindowKey * iHeadsKV, None)) class="kw">return false; if(IsStopped() || !Concat(k, v, kv, iWindowKey * iHeadsKV * iVariables, iWindowKey * iHeadsKV * iVariables, iUnits)) class="kw">return false; } class=class="str">"cmt">//--- Score calculation and Multi-heads attention calculation CBufferFloat *temp = S_Tensors.At(i * class="num">2); CBufferFloat *out = AO_Tensors.At(i * class="num">2); if(IsStopped() || !AttentionOut(q, kv, temp, out)) class="kw">return false; class=class="str">"cmt">//--- Attention out calculation temp = FF_Tensors.At(i * class="num">10); if(IsStopped() || !ConvolutionForward(FF_Weights.At(i * (optimization == SGD ? class="num">6 : class="num">9)), out, temp, iWindowKey * iHeads, iWindow, None)) class="kw">return false; class=class="str">"cmt">//--- Concat out = FF_Tensors.At(i * class="num">10 + class="num">1); if(IsStopped() || !Concat(temp, inputs, out, iWindow, iWindow, iUnits)) class="kw">return false; class=class="str">"cmt">//--- Gate if(IsStopped() || !ConvolutionForward(FF_Weights.At(i * (optimization == SGD ? class="num">6 : class="num">9) + class="num">1), out, FF_Tensors.At(i * class="num">10 + class="num">2), class="num">2 * iWindow, iWindow, SIGMOID)) class="kw">return false; class=class="str">"cmt">//--- Self if(IsStopped() || !ConvolutionForward(FF_Weights.At(i * (optimization == SGD ? class="num">6 : class="num">9) + class="num">2), out, FF_Tensors.At(i * class="num">10 + class="num">3), iWindow, iWindow, None)) class="kw">return false; class=class="str">"cmt">//--- Out if(IsStopped() || !GateElementMult(FF_Tensors.At(i * class="num">10 + class="num">3), temp, FF_Tensors.At(i * class="num">10 + class="num">2), FF_Tensors.At(i * class="num">10 + class="num">4))) class="kw">return false; } class=class="str">"cmt">//--- class="kw">return true; }
「下一篇才进实盘验证」
这套层次化矢量变换器(HiVT)把多位个体的运动预测拆成两步:先用局部上下文提取,再做全局互动建模。思路本身对多标的联动的概率判断有参考价值,但本文只铺了准备工作的底,没碰 MT5 上的实跑。 作者明确说完整实现超了当前篇幅,启动代码和下一段基于真实历史数据的测试结果都放到系列第二篇。也就是说,你现在能做的只有把局部/全局两阶段的结构在纸上画清楚,别急着搬去 EA。 外汇与贵金属市场高风险,任何多体预测模型都只是概率工具,实盘前必须用历史数据回测确认失效边界。
把这条线请下神坛
这套 LSTM 预测框架落地到 MT5,实际是八个文件分工:Research.mq5 与 ResearchRealORL.mq5 负责采样,Study.mq5 与 StudyEncoder.mq5 管训练,Test.mq5 跑验证,Trajectory.mqh、NeuroNet.mqh、NeuroNet.cl 则分别承载状态结构、网络类库与 OpenCL 核。附加的 MQL5.zip 约 1699.15 KB,解压即可在 MetaEditor 里逐文件读。 别把它们当成黑箱圣物。神经网络在外汇与贵金属上只是概率工具,样本偏差或过度拟合都会让回测曲线失效;实盘前务必用策略测试器跑至少一年 tick 数据,并手动改 Study.mq5 里的窗口参数看泛化变化。 线画完了,接下来该你自己在 MT5 里把 EA 挂上图表,看它采到的样本长什么样。