交易中的神经网络:探索局部数据结构·进阶篇
「Transformer 模块的逐层 OpenCL 初始化」
在 MT5 的 OpenCL 神经网络封装里,一个 Transformer 单元靠连续 12 个索引偏移把各子层挂到计算图。上面这段初始化循环里,i 代表层序号,每层占用 i*12+5 到 i*12+10 共 6 个关键节点,外加卷积与全连接对象。 第一个卷积 conv 用 Init(0, i*12+5, OpenCL, center_window, center_window, 32, 1, iPoints*2, optimization, iBatch) 建立,窗口尺寸等于 center_window,输出通道 32,步长 1,输入展开长度为 iPoints*2;若返回 false 直接中断建网。 Key-Value 的全连接层 base 节点号为 i*12+6,输入维度被拉到 iPoints*2*32*2,也就是把前面卷积的时空与通道维直接拍平送进矩阵乘。Score 缓冲区则用 OpenCL.AddBuffer 按 float 大小申请 iUnits*iPoints*4 个元素的读写显存,索引写入 cScores 容器。 注意力输出部分分两路:MHAttentionOut 的 base 节点 i*12+7 接收 iUnits*64 维,AttentionOut 的卷积节点 i*12+8 用 64→64→64 的三参数卷积核、宽度 iUnits 做逐位融合。残差 base 在 i*12+9 同样吃 iUnits*64 维,Feed Forward 卷积在 i*12+10 把 64 通道扩到 256 并显式调 SetActivationFunction(LReLU),漏 ReLU 能缓解深层梯度消失。 把这些索引规律记下来,你改网络深度时只要调 iUnits 和 iPoints,层内偏移不用动;外汇与贵金属行情跳空频繁,这类 GPU 网络过拟合风险高,上实盘前务必用历史样本外数据验证。
!conv.Init(class="num">0, i * class="num">12 + class="num">5, OpenCL, center_window, center_window, class="num">32, class="num">1, iPoints * class="num">2, optimization, iBatch)) class="kw">return false; if(!cValue.Add(conv)) class="kw">return false; class=class="str">"cmt">//--- Key-Value base = new CNeuronBaseOCL(); if(!base || !base.Init(class="num">0, i * class="num">12 + class="num">6, OpenCL, iPoints * class="num">2 * class="num">32 * class="num">2, optimization, iBatch)) class="kw">return false; if(!cKeyValue.Add(base)) class="kw">return false; class=class="str">"cmt">//--- Score class="type">int s = class="type">int(iUnits * iPoints * class="num">4); s = OpenCL.AddBuffer(class="kw">sizeof(class="type">float) * s, CL_MEM_READ_WRITE); if(s < class="num">0 || !cScores.Add(s)) class="kw">return false; class=class="str">"cmt">//--- MH Attention Out base = new CNeuronBaseOCL(); if(!base || !base.Init(class="num">0, i * class="num">12 + class="num">7, OpenCL, iUnits * class="num">64, optimization, iBatch)) class="kw">return false; if(!cMHAttentionOut.Add(base)) class="kw">return false; class=class="str">"cmt">//--- Attention Out conv = new CNeuronConvOCL(); if(!conv || !conv.Init(class="num">0, i * class="num">12 + class="num">8, OpenCL, class="num">64, class="num">64, class="num">64, iUnits, class="num">1, optimization, iBatch)) class="kw">return false; if(!cAttentionOut.Add(conv)) class="kw">return false; class=class="str">"cmt">//--- Residual base = new CNeuronBaseOCL(); if(!base || !base.Init(class="num">0, i * class="num">12 + class="num">9, OpenCL, iUnits * class="num">64, optimization, iBatch)) class="kw">return false; if(!cResidual.Add(base)) class="kw">return false; class=class="str">"cmt">//--- Feed Forward conv = new CNeuronConvOCL(); if(!conv || !conv.Init(class="num">0, i * class="num">12 + class="num">10, OpenCL, class="num">64, class="num">64, class="num">256, iUnits, class="num">1, optimization, iBatch)) class="kw">return false; conv.SetActivationFunction(LReLU); if(!cFeedForward.Add(conv))
SEFormer 残差与注意力层的初始化落点
这段片段展示的是 CNeuronSEFormer 类里卷积、残差和最终注意力模块的逐层挂接逻辑。循环变量 i 每推进一次,就按 i*12+11、i*12+12、i*12+13 的偏移量向网络结构里塞入对应层,说明每层参数索引是硬编码错位排布的,改结构时容易踩坑。 卷积层 Init 接收 256 输入通道、64 卷积核、64 步长,残差基层则把 iUnits*64 个神经元直接接在卷积梯度上(SetGradient 第二个参数置 true 表示共享梯度)。这种接法在外汇或贵金属行情的 GPU 训练里,可能让显存占用随 iUnits 线性膨胀,实操前建议在 MT5 策略测试器里先跑小 iUnits 验证。 最终注意力层 CNeuronMLMHSceneConditionAttention 的 Init 里写死了头数 16、层数 4、缩放 2,外部只暴露 iUnits 和批次参数。feedForward 里对 bTrain 为真时才先推中心点点云,且内部循环只跑 2 层(l<2),说明推理与训练的前向路径不一致,复制代码时别漏掉这个分支。
class="kw">return false; conv = new CNeuronConvOCL(); if(!conv || !conv.Init(class="num">0, i * class="num">12 + class="num">11, OpenCL, class="num">256, class="num">64, class="num">64, 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 || !base.Init(class="num">0, i * class="num">12 + class="num">12, OpenCL, iUnits * class="num">64, optimization, iBatch)) class="kw">return false; if(!base.SetGradient(conv.getGradient(), true)) class="kw">return false; if(!cResidual.Add(base)) class="kw">return false; class=class="str">"cmt">//--- Final Attention CNeuronMLMHSceneConditionAttention *att = new CNeuronMLMHSceneConditionAttention(); if(!att || !att.Init(class="num">0, i * class="num">12 + class="num">13, OpenCL, class="num">64, class="num">16, class="num">4, class="num">2, iUnits, class="num">2, class="num">1, optimization, iBatch)) class="kw">return false; if(!cFinalAttention.Add(att)) class="kw">return false; } if(!SEOut.Init(class="num">0, class="num">26, OpenCL, class="num">64, class="num">64, class="num">4, class="num">16, class="num">4, iUnits, iUnits, class="num">4, class="num">1, optimization, iBatch)) class="kw">return false; if(!cbTemp.BufferInit(buf_size, class="num">0) || !cbTemp.BufferCreate(OpenCL)) class="kw">return false; class=class="str">"cmt">//--- class="kw">return true; } class="type">bool CNeuronSEFormer::feedForward(CNeuronBaseOCL *NeuronOCL) { class=class="str">"cmt">//--- CNeuronBaseOCL *neuron = NULL, *q = NULL, *k = NULL, *v = NULL, *kv = NULL; class=class="str">"cmt">//--- Init Points if(bTrain) { neuron = cCenterPoints[class="num">1]; if(!neuron || !neuron.FeedForward(cCenterPoints[class="num">0])) class="kw">return false; } class=class="str">"cmt">//--- Inside Layers for(class="type">int l = class="num">0; l < class="num">2; l++) { class=class="str">"cmt">//--- Segmentation Inputs if(l > class="num">0 || !cTNetG) { if(!caLocalPointNet[l].FeedForward((l == class="num">0 ? NeuronOCL : GetPointer(caLocalPointNet[l - class="num">1])))) class="kw">return false; }
◍ 结构嵌入注意力里的残差与多头计算
这段代码处在局部点网络向上一层传递之后,负责把中心点做插值,再跑结构嵌入注意力。任何一步 FeedForward 或矩阵运算返回失败就直接 return false,说明该层推理链条是严格串行的,断一处整层作废。 先看窗口尺寸:window 用神经元总数的平方根取整,例如神经元数为 1024 时 window=32,后面 MatMul 的分块就按 NeuronOCL.Neurons()/window 与 window 对齐。若 IsStopped() 触发(EA 被停止或卸载),矩阵乘直接放弃,避免后台空转。 注意力部分把 Query 来自局部点网络、Key/Value 来自插值后的中心点,Concat 时写死 32*2 的维度,即单头 32 维、拼接后 64 维。多头输出再经 Scale 与 Residual:SumAndNormilize 里 64 是向量长度,true 表示做归一化,后面四个 0/1 是均值方差相关开关参数。 残差加归一化之后进两层 FeedForward(索引 l*2 与 l*2+1),这就是典型 Transformer 块的后半段。复制这段代码到 MT5 的自定义网络类里,把 32、64 这些维度打印出来,能确认你的显存占用是否和原设计一致;外汇与贵金属杠杆高,此类模型仅作辅助信号,实盘误判概率不低。
else { if(!cTurnedG) class="kw">return false; if(!cTNetG.FeedForward(NeuronOCL)) class="kw">return false; class="type">int window = (class="type">int)MathSqrt(cTNetG.Neurons()); if(IsStopped() || !MatMul(NeuronOCL.getOutput(), cTNetG.getOutput(), cTurnedG.getOutput(), NeuronOCL.Neurons() / window, window, window)) class="kw">return false; if(!caLocalPointNet[class="num">0].FeedForward(cTurnedG.AsObject())) class="kw">return false; } class=class="str">"cmt">//--- Interpolate center points neuron = cCenterPoints[l + class="num">2]; if(!neuron || !neuron.FeedForward(cCenterPoints[l + class="num">1], caLocalPointNet[l].getOutput())) class="kw">return false; class=class="str">"cmt">//--- Structure-Embedding Attention q = cQuery[l]; k = cKey[l]; v = cValue[l]; kv = cKeyValue[l]; class=class="str">"cmt">//--- Query if(!q || !q.FeedForward(GetPointer(caLocalPointNet[l]))) class="kw">return false; class=class="str">"cmt">//--- Key if(!k || !k.FeedForward(cCenterPoints[l + class="num">2])) class="kw">return false; class=class="str">"cmt">//--- Value if(!v || !v.FeedForward(cCenterPoints[l + class="num">2])) class="kw">return false; if(!kv || !Concat(k.getOutput(), v.getOutput(), kv.getOutput(), class="num">32 * class="num">2, class="num">32 * class="num">2, iPoints)) class="kw">return false; class=class="str">"cmt">//--- Multi-Head Attention neuron = cMHAttentionOut[l]; if(!neuron || !AttentionOut(q.getOutput(), kv.getOutput(), cScores[l], neuron.getOutput())) class="kw">return false; class=class="str">"cmt">//--- Scale neuron = cAttentionOut[l]; if(!neuron || !neuron.FeedForward(cMHAttentionOut[l])) class="kw">return false; class=class="str">"cmt">//--- Residual q = cResidual[l * class="num">2]; if(!q || !SumAndNormilize(caLocalPointNet[l].getOutput(), neuron.getOutput(), q.getOutput(), class="num">64, true, class="num">0, class="num">0, class="num">0, class="num">1)) class="kw">return false; class=class="str">"cmt">//--- Feed Forward neuron = cFeedForward[l * class="num">2]; if(!neuron || !neuron.FeedForward(q)) class="kw">return false; neuron = cFeedForward[l * class="num">2 + class="num">1];
「残差与跨尺度注意力的前向和反向实现」
SEFormer 这类模型在 MT5 里跑,前向传播并不是一口气卷积到底,而是逐层走残差 + 注意力。代码里用 cResidual[l*2+1] 取出第 l 层的残差连接节点,再调 SumAndNormilize 把查询输出和神经元输出做归一化求和,维度参数写死为 64,最后一个布尔位 true 代表这是前向阶段的标准化。
反向传播 calcInputGradients 里同一段逻辑翻转:归一化函数最后一个布尔变成 false,表示此刻在算梯度而非前向激活。循环从 l=1 倒序到 0,先把 cFinalAttention[0] 的隐藏梯度算清,再逐层拆残差、前馈、缩放注意力,任何一步指针为空或计算返回 false 就直接退出,避免脏梯度污染 PointNet 嵌入层。
实盘接这种网络做贵金属信号判别,要清楚 OCL 缓冲一旦某层 getOutput() 返回空,整个 feedForward 会静默失败返回 false。外汇与贵金属杠杆高,模型推理失败若不被交易逻辑捕获,可能发出错误订单,务必在 EA 里对返回值做硬判断。
if(!neuron || !neuron.FeedForward(cFeedForward[l * class="num">2])) class="kw">return false; class=class="str">"cmt">//--- Residual k = cResidual[l * class="num">2 + class="num">1]; if(!k || !SumAndNormilize(q.getOutput(), neuron.getOutput(), k.getOutput(), class="num">64, true, class="num">0, class="num">0, class="num">0, class="num">1)) class="kw">return false; class=class="str">"cmt">//--- Final Attention neuron = cFinalAttention[l]; if(!neuron || !neuron.FeedForward(k)) class="kw">return false; } class=class="str">"cmt">//--- Cross scale attention if(!SEOut.FeedForward(cFinalAttention[class="num">0], neuron.getOutput())) class="kw">return false; class=class="str">"cmt">//--- Global Point Cloud Embedding if(!CNeuronPointNetOCL::feedForward(SEOut.AsObject())) class="kw">return false; class=class="str">"cmt">//--- result class="kw">return true; } class="type">bool CNeuronSEFormer::calcInputGradients(CNeuronBaseOCL *NeuronOCL) { if(!NeuronOCL) class="kw">return false; CNeuronBaseOCL *neuron = NULL, *q = NULL, *k = NULL, *v = NULL, *kv = NULL; CBufferFloat *buf = NULL; class=class="str">"cmt">//--- Global Point Cloud Embedding if(!CNeuronPointNetOCL::calcInputGradients(SEOut.AsObject())) class="kw">return false; class=class="str">"cmt">//--- Cross scale attention neuron = cFinalAttention[class="num">0]; q = cFinalAttention[class="num">1]; if(!neuron.calcHiddenGradients(SEOut.AsObject(), q.getOutput(), q.getGradient(), (ENUM_ACTIVATION)q.Activation())) class="kw">return false; for(class="type">int l = class="num">1; l >= class="num">0; l--) { class=class="str">"cmt">//--- Final Attention neuron = cResidual[l * class="num">2 + class="num">1]; if(!neuron || !neuron.calcHiddenGradients(cFinalAttention[l])) class="kw">return false; class=class="str">"cmt">//--- Feed Forward neuron = cFeedForward[l * class="num">2]; if(!neuron || !neuron.calcHiddenGradients(cFeedForward[l * class="num">2 + class="num">1])) class="kw">return false; neuron = cResidual[l * class="num">2]; if(!neuron || !neuron.calcHiddenGradients(cFeedForward[l * class="num">2])) class="kw">return false; class=class="str">"cmt">//--- Residual q = cResidual[l * class="num">2 + class="num">1]; k = neuron; neuron = cAttentionOut[l]; if(!neuron || !SumAndNormilize(q.getGradient(), k.getGradient(), neuron.getGradient(), class="num">64, false, class="num">0, class="num">0, class="num">0, class="num">1)) class="kw">return false; class=class="str">"cmt">//--- Scale neuron = cMHAttentionOut[l]; if(!neuron || !neuron.calcHiddenGradients(cAttentionOut[l]))
注意力层反向传播的梯度回流细节
这段逻辑处在多层注意力结构里 l 层的反向传播阶段,核心是把输出梯度按 MH Attention 的路径拆回 q、k、v 各自支路。任何一步计算返回 false 就直接中断,说明梯度链不容许静默失败,调试时若某层不更新,优先查这里的返回值。 DeConcat 调用把 kv 的梯度按 32*2 的维度切回 k 与 v,两个 64 宽度的张量在 l==0 时还要走 LocalPointNet 的临时缓冲 cbTemp,再经 SumAndNormilize 以 64 长度做归一,系数 1 表示直接覆盖而非缩放。 残差部分把 LocalPointNet 的梯度与 AttentionOut 的梯度相加,同样走 64 长度归一;CenterPoints 则按 l+2 索引取神经元,l==0 时系数 1 全量回写,非 0 层用 0.5f 做半量插值——这意味着深层中心点的梯度贡献被刻意压低,可能在 MT5 里表现为浅层权重调整更剧烈。 开 MT5 把这段塞进你自己的神经网络 EA 反向函数,改一下 0.5f 和 64 这两个量,观察不同层学习率差异是否如预期收敛。外汇与贵金属行情高波动,这类模型仅作概率参考,实盘需严控仓位。
class="kw">return false; class=class="str">"cmt">//--- MH Attention q = cQuery[l]; kv = cKeyValue[l]; k = cKey[l]; v = cValue[l]; if(!AttentionInsideGradients(q.getOutput(), q.getGradient(), kv.getOutput(), kv.getGradient(), cScores[l], neuron.getGradient())) class="kw">return false; if(!DeConcat(k.getGradient(), v.getGradient(), kv.getGradient(), class="num">32 * class="num">2, class="num">32 * class="num">2, iPoints)) class="kw">return false; if(l == class="num">0) { buf = caLocalPointNet[l].getGradient(); if(!caLocalPointNet[l].SetGradient(GetPointer(cbTemp), false)) class="kw">return false; } if(!caLocalPointNet[l].calcHiddenGradients(q, NULL)) class="kw">return false; if(l == class="num">0) { if(!SumAndNormilize(buf, GetPointer(cbTemp), buf, class="num">64, false, class="num">0, class="num">0, class="num">0, class="num">1)) class="kw">return false; if(!caLocalPointNet[l].SetGradient(buf, false)) class="kw">return false; } neuron = cAttentionOut[l]; class=class="str">"cmt">//--- Residual if(!SumAndNormilize(caLocalPointNet[l].getGradient(), neuron.getGradient(), caLocalPointNet[l].getGradient(), class="num">64, false, class="num">0, class="num">0, class="num">0, class="num">1)) class="kw">return false; class=class="str">"cmt">//--- Interpolate Center points neuron = cCenterPoints[l + class="num">2]; if(!neuron) class="kw">return false; buf = neuron.getGradient(); if(!neuron.SetGradient(GetPointer(cbTemp), false)) class="kw">return false; if(!neuron.calcHiddenGradients(k, NULL)) class="kw">return false; if(l == class="num">0) { if(!SumAndNormilize(buf, GetPointer(cbTemp), buf, class="num">1, false, class="num">0, class="num">0, class="num">0, class="num">1)) class="kw">return false; } else { if(!SumAndNormilize(GetPointer(cbTemp), GetPointer(cbTemp), buf, class="num">1, false, class="num">0, class="num">0, class="num">0, class="num">0.5f)) class="kw">return false; }
◍ 反向传播里梯度在多层间的交接
这段逻辑处在网络训练的反向阶段,核心是把隐藏层梯度从后向前逐层算清并归一。任何一步返回 false 都会中断整轮训练,因此调用链上的 calcHiddenGradients 与 SumAndNormilize 必须全部成功。 对本地子网梯度做归一时,SumAndNormilize 的第四个参数用了 64,而中心点与输出缓冲的同类调用用的是 1,说明本地子网梯度聚合的维度明显更大,跑 MT5 时若改这个常数,显存与收敛速度会直接变。 当 l==0 且存在转置网 cTurnedG 时,window 按 MathSqrt(cTNetG.Neurons()) 取整,MatMulGrad 的分块尺寸就是 neuron.Neurons()/window 配 window 配 window。外汇与贵金属行情噪声大,这类张量乘梯度在高杠杆下容易过拟合,调参须控制风险。 最后几行把 neuron 与 cTurnedG 的梯度指针互换再补一次 calcHiddenGradients,等于在转置分支上做了一次梯度回流;想验证可实现性,直接把这段贴进 MT5 的神经网络类反向函数里单步跟一遍最直观。
if(!neuron.calcHiddenGradients(v, NULL)) class="kw">return false; if(!SumAndNormilize(buf, GetPointer(cbTemp), buf, class="num">1, false, class="num">0, class="num">0, class="num">0, class="num">1)) class="kw">return false; if(!neuron.SetGradient(buf, false)) class="kw">return false; neuron = cCenterPoints[l + class="num">1]; if(!neuron.calcHiddenGradients(cCenterPoints[l + class="num">2], caLocalPointNet[l].getOutput(), GetPointer(cbTemp), (ENUM_ACTIVATION)caLocalPointNet[l].Activation())) class="kw">return false; if(!SumAndNormilize(caLocalPointNet[l].getGradient(), GetPointer(cbTemp), caLocalPointNet[l].getGradient(), class="num">64, false, class="num">0, class="num">0, class="num">0, class="num">1)) class="kw">return false; class=class="str">"cmt">//--- Local Net neuron = (l > class="num">0 ? GetPointer(caLocalPointNet[l - class="num">1]) : NeuronOCL); if(l > class="num">0 || !cTNetG) { if(!neuron.calcHiddenGradients(caLocalPointNet[l].AsObject())) class="kw">return false; } else { if(!cTurnedG) class="kw">return false; if(!cTurnedG.calcHiddenGradients(caLocalPointNet[l].AsObject())) class="kw">return false; class="type">int window = (class="type">int)MathSqrt(cTNetG.Neurons()); if(IsStopped() || !MatMulGrad(neuron.getOutput(), neuron.getGradient(), cTNetG.getOutput(), cTNetG.getGradient(), cTurnedG.getGradient(), neuron.Neurons() / window, window, window)) class="kw">return false; if(!OrthoganalLoss(cTNetG, true)) class="kw">return false; class=class="str">"cmt">//--- CBufferFloat *temp = neuron.getGradient(); neuron.SetGradient(cTurnedG.getGradient(), false); cTurnedG.SetGradient(temp, false); class=class="str">"cmt">//--- if(!neuron.calcHiddenGradients(cTNetG.AsObject())) class="kw">return false; if(!SumAndNormilize(neuron.getGradient(), cTurnedG.getGradient(), neuron.getGradient(), class="num">1, false, class="num">0, class="num">0, class="num">0, class="num">1)) class="kw">return false; } } class=class="str">"cmt">//--- class="kw">return true; } class=class="str">"cmt">//--- layer class="num">2