交易中的神经网络:受控分段(终章)·综合运用
◍ RefMask 网络的初始化与前向骨架
下面这段是 CNeuronRefMask 类里构建内部结构的核心代码,直接决定了内容编码器、背景分支、语言原语模块和 decoder 的层数拓扑。任何一层 Init 失败都会让整个网络返回 false,MT5 策略加载时若报空指针,优先查这里。 内容编码器连续挂了三个 CNeuronBaseOCL:第一层输入维度是 window*content_units、输出 1;第二层输出 2;第三层输出 3 且输入维度多了 3 个额外单元(window*(content_units+3))。背景分支则用了 window*3 的输入和两条独立神经元。 语言原语构造 cLPC 与解码器 cDecoder 通过 CNeuronOCM 对象堆叠,decoder 里有一个 for 循环按 layers 数量动态挂层,每层 OCM 的神经元数取自 cGEGWA 内部层。最后 cOCM 的层索引被固定为 layers+8,输出和梯度指针在此绑定。 外汇与贵金属行情下用这类 GPU 加速网络做特征提取,属于高风险实验,回测不保证实盘概率稳定,建议先在 MT5 的 OpenCL 环境跑通 Init 再谈调参。
!neuron.Init(window * content_units, class="num">1, OpenCL, content_size, optimization, iBatch) || !cContentEncoder.Add(neuron) ) class="kw">return class="kw">false; neuron = new CNeuronBaseOCL(); if(!neuron || !neuron.Init(class="num">0, class="num">2, OpenCL, window * content_units, optimization, iBatch) || !cContentEncoder.Add(neuron) ) class="kw">return class="kw">false; neuron = new CNeuronBaseOCL(); if(!neuron || !neuron.Init(class="num">0, class="num">3, OpenCL, window * (content_units + class="num">3), optimization, iBatch) || !cContentEncoder.Add(neuron) ) class="kw">return class="kw">false; class=class="str">"cmt">//--- Background cBackGround.Clear(); cBackGround.SetOpenCL(OpenCL); neuron = new CNeuronBaseOCL(); if(!neuron || !neuron.Init(window * class="num">3, class="num">4, OpenCL, content_size, optimization, iBatch) || !cBackGround.Add(neuron) ) class="kw">return class="kw">false; neuron = new CNeuronBaseOCL(); if(!neuron || !neuron.Init(class="num">0, class="num">5, OpenCL, window * class="num">3, optimization, iBatch) || !cBackGround.Add(neuron) ) class="kw">return class="kw">false; class=class="str">"cmt">//--- Linguistic Primitive Construction if(!cLPC.Init(class="num">0, class="num">6, OpenCL, window, window_key, heads, heads, primitive_units, content_units, class="num">2, class="num">1, optimization, iBatch)) class="kw">return class="kw">false; class=class="str">"cmt">//--- Decoder cDecoder.Clear(); cDecoder.SetOpenCL(OpenCL); CNeuronOCM *ocm = new CNeuronOCM(); if(!ocm || !ocm.Init(class="num">0, class="num">7, OpenCL, window, window_key, units_count, heads, window, primitive_units, heads, optimization, iBatch) || !cDecoder.Add(ocm) ) class="kw">return class="kw">false; for(class="type">uint i = class="num">0; i < layers; i++) { neuron = cGEGWA.GetInsideLayer(i); ocm = new CNeuronOCM(); if(!ocm || !neuron || !ocm.Init(class="num">0, i + class="num">8, OpenCL, window, window_key, neuron.Neurons() / window, heads, window, primitive_units, heads, optimization, iBatch) || !cDecoder.Add(ocm) ) class="kw">return class="kw">false; } class=class="str">"cmt">//--- Object Cluster Module if(!cOCM.Init(class="num">0, layers + class="num">8, OpenCL, window, window_key, primitive_units, heads, window, content_units, heads, optimization, iBatch)) class="kw">return class="kw">false; if(!SetOutput(cOCM.getOutput()) || !SetGradient(cOCM.getGradient()) ) class="kw">return class="kw">false; class=class="str">"cmt">//--- class="kw">return true; } class="type">bool CNeuronRefMask::feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) { if(!SecondInput) class="kw">return class="kw">false; class=class="str">"cmt">//--- Context Encoder CNeuronBaseOCL *context = cContentEncoder[class="num">0]; if(context.getOutput() != SecondInput) { if(!context.SetOutput(SecondInput, true))
「RefMask 前向传播与梯度回传的骨架」
下面这段 CNeuronRefMask 的实现把文档级 Transformer 的推理链路拆得很直白:训练态下背景编码器要逐层 FeedForward,推理态只取最后一层,省掉中间计算。 内容编码器从索引 1 跑到 content_total-1,每层都拿前一层输出做前向;任意一层指针为空或传播失败就直接 return false,说明该网络对层间拓扑连续性零容忍。 梯度回传侧,calcInputGradients 先校验神经元与梯度缓冲指针,再把 content_encoder[0] 的梯度重定向到 SecondGradient,并切换激活函数;若你改了 OCM 的输入梯度逻辑,必须从 cDecoder 末层反推到 context,否则反向图会断。 外汇与贵金属行情序列用这类结构做特征抽取时,过拟合概率偏高,建议先在 MT5 用小规模样本跑通前向再扩维。
class="kw">return class="kw">false; } class="type">int content_total = cContentEncoder.Total(); for(class="type">int i = class="num">1; i < content_total - class="num">1; i++) { context = cContentEncoder[i]; if(!context || !context.FeedForward(cContentEncoder[i - class="num">1]) ) class="kw">return class="kw">false; } class=class="str">"cmt">//--- Background Encoder CNeuronBaseOCL *background = NULL; if(bTrain) { for(class="type">int i = class="num">1; i < cBackGround.Total(); i++) { background = cBackGround[i]; if(!background || !background.FeedForward(cBackGround[i - class="num">1]) ) class="kw">return class="kw">false; } } else { background = cBackGround[cBackGround.Total() - class="num">1]; if(!background) class="kw">return class="kw">false; } CNeuronBaseOCL *neuron = cContentEncoder[content_total - class="num">1]; if(!neuron || !Concat(context.getOutput(), background.getOutput(), neuron.getOutput(), context.Neurons(), background.Neurons(), class="num">1)) class="kw">return class="kw">false; class=class="str">"cmt">//--- Geometry-Enhaced Group-Word Attention if(!cGEGWA.FeedForward(NeuronOCL, neuron.getOutput())) class="kw">return class="kw">false; class=class="str">"cmt">//--- Linguistic Primitive Construction if(!cLPC.FeedForward(context)) class="kw">return class="kw">false; class=class="str">"cmt">//--- Decoder CNeuronOCM *decoder = cDecoder[class="num">0]; if(!decoder.feedForward(GetPointer(cGEGWA), GetPointer(cLPC))) class="kw">return class="kw">false; for(class="type">int i = class="num">1; i < cDecoder.Total(); i++) { decoder = cDecoder[i]; if(!decoder.feedForward(cGEGWA.GetInsideLayer(i - class="num">1), cDecoder[i - class="num">1])) class="kw">return class="kw">false; } class=class="str">"cmt">//--- Object Cluster Module if(!cOCM.feedForward(decoder, context)) class="kw">return class="kw">false; class=class="str">"cmt">//--- class="kw">return true; } class="type">bool CNeuronRefMask::calcInputGradients(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput, CBufferFloat *SecondGradient, ENUM_ACTIVATION SecondActivation = None) { if(!NeuronOCL || !SecondGradient) class="kw">return class="kw">false; CNeuronBaseOCL *neuron = cContentEncoder[class="num">0]; if(!neuron) class="kw">return class="kw">false; if(neuron.getGradient() != SecondGradient) { if(!neuron.SetGradient(SecondGradient)) class="kw">return class="kw">false; neuron.SetActivationFunction(SecondActivation); } class=class="str">"cmt">//--- Object Cluster Module CNeuronBaseOCL *context = cContentEncoder[cContentEncoder.Total() - class="num">2]; if(!cOCM.calcInputGradients(cDecoder[cDecoder.Total() - class="num">1], context))
逆向传播里的梯度回传链路
这段反向传播代码把解码器、语境编码器与背景网络的梯度逐层往回送,任何一层 calcHiddenGradients 或 DeConcat 失败就直接 return false,训练不会继续。 解码器循环从 cDecoder.Total()-1 倒序到 1,用 calcInputGradients 把误差传给前一层;索引 0 的解码器则把梯度算到 cGEGWA 和 cLPC 上,这是几何增强注意力的入口。 语境原语构建段先把 context 梯度取出,再用 SetGradient(PrevOutput,false) 重置,随后 FeedForward 并 SumAndNormilize,归一化系数写死为 1、偏移 0,说明这一步不做缩放只做累加。 背景与内容编码器分别走 DeActivation 和 calcHiddenGradients:背景从倒数第二层循环到 1,内容编码器从 Total()-3 到 0,跳过最后两层是因为它们已在前面 GEGWA 段处理过。 在 MT5 里把这段贴进你自己的 CNet 派生类,把 cContentEncoder.Total()-3 改成实际层数减 2 做断点,能直接看出哪一层先返回 false,外汇与贵金属模型训练高风险,梯度爆炸可能让回测失效。
class="kw">return class="kw">false; class=class="str">"cmt">//--- Decoder CNeuronOCM *decoder = NULL; for(class="type">int i = cDecoder.Total() - class="num">1; i > class="num">0; i--) { decoder = cDecoder[i]; if(!decoder.calcInputGradients(cGEGWA.GetInsideLayer(i - class="num">1), cDecoder[i - class="num">1])) class="kw">return class="kw">false; } decoder = cDecoder[class="num">0]; if(!decoder.calcInputGradients(GetPointer(cGEGWA), GetPointer(cLPC))) class="kw">return class="kw">false; class=class="str">"cmt">//--- Linguistic Primitive Construction CBufferFloat *context_grad = context.getGradient(); if(!context.SetGradient(PrevOutput, class="kw">false)) class="kw">return class="kw">false; if(!cLPC.FeedForward(context) || !SumAndNormilize(context_grad, context.getGradient(), context_grad, class="num">1, class="kw">false, class="num">0, class="num">0, class="num">0, class="num">1) ) class="kw">return class="kw">false; class=class="str">"cmt">//--- Geometry-Enhaced Group-Word Attention neuron = cContentEncoder[cContentEncoder.Total() - class="num">1]; if(!neuron || !NeuronOCL.calcHiddenGradients((CObject*)GetPointer(cGEGWA), neuron.getOutput(), neuron.getGradient(), (ENUM_ACTIVATION)neuron.Activation())) class="kw">return class="kw">false; if(!DiversityLoss(neuron, cOCM.GetContextWindow(), neuron.Neurons() / cOCM.GetContextWindow(), true)) class="kw">return class="kw">false; CNeuronBaseOCL *background = cBackGround[cBackGround.Total() - class="num">1]; if(!background || !DeConcat(context.getGradient(), background.getGradient(), neuron.getGradient(), context.Neurons(), background.Neurons(), class="num">1) || !DeActivation(context.getOutput(), context.getGradient(), context.getGradient(), context.Activation()) || !SumAndNormilize(context_grad, context.getGradient(), context_grad, class="num">1, class="kw">false, class="num">0, class="num">0, class="num">0, class="num">1) || !context.SetGradient(context_grad, class="kw">false) ) class="kw">return class="kw">false; class=class="str">"cmt">//--- Context Encoder for(class="type">int i = cContentEncoder.Total() - class="num">3; i >= class="num">0; i--) { context = cContentEncoder[i]; if(!context || !context.calcHiddenGradients(cContentEncoder[i + class="num">1]) ) class="kw">return class="kw">false; } class=class="str">"cmt">//--- Background if(!DeActivation(background.getOutput(), background.getGradient(), background.getGradient(), background.Activation())) class="kw">return class="kw">false; for(class="type">int i = cBackGround.Total() - class="num">2; i > class="num">0; i--) { background = cBackGround[i]; if(!background || !background.calcHiddenGradients(cBackGround[i + class="num">1]) ) class="kw">return class="kw">false; } class=class="str">"cmt">//--- class="kw">return true; }
◍ 用2024年1月数据验模型成色
模型架构改动不波及输入输出结构,所以前面在 H1 上取的 EURUSD 2023 全年真实历史训练集能直接复用,指标全部走默认值。训练是离线跑的,但我们会按参与者政策不断把新局次补进数据集再重训,直到性能够用。 准备本文时我们写了一套挺有意思的参与者政策,拿它跑 2024 年 1 月行情做测试——这段测试期根本没进训练集,等于逼模型在没见过的数据上干活,最贴近实盘用法。 结果这一个月模型下了 21 单,14 单盈利,胜率 66% 出头。多空两边盈利单占比都压过亏损单,而且盈利单平均利润是亏损单平均亏损的 2 倍,最大盈利差不多是最大亏损的 3 倍,余额曲线是肉眼可见的向上台阶。 样本只有 21 笔,远谈不上证明长期有效,外汇和高杠杆贵金属本就高风险,这套思路只是显出了可挖的空间,值得接着调。
「画得少,看得清」
前面两篇把 RefMask3D 思路落进 MQL5 时做了本地化改动,跑出来的样本和训练痕迹说明这条路径值得继续挖,但眼下这套程序只是演示骨架,直接挂真仓不合适,外汇和贵金属的高风险不会因为模型新颖就消失。 从日志看,Study EA 在 EURUSD H1 上训到 295 步时 Critic 损失降到 0.0005726、Actor 损失 0.0803357,随后主动退出;同时报了 19968 字节内存泄漏和两处 deprecated 警告,说明类库还没收干净。 想自己复现,就把 Research / Study / Test 三个 EA 加 NeuroNet 系列类库丢进 MT5,先跑 EURUSD H1 看训练曲线,别急着接实盘。
class="num">2024.10.class="num">08 class="num">21:class="num">28:class="num">01.820 Study(EURUSD,H1) RefMaskAct.nnw class="num">2024.10.class="num">08 class="num">21:class="num">28:class="num">01.896 Study(EURUSD,H1) RefMaskCrt.nnw class="num">2024.10.class="num">08 class="num">22:class="num">48:class="num">49.440 Study(EURUSD,H1) Train -> class="num">294 -> Actor class="num">0.0803357 class="num">2024.10.class="num">08 class="num">22:class="num">48:class="num">49.440 Study(EURUSD,H1) Train -> class="num">295 -> Critic class="num">0.0005726 class="num">2024.10.class="num">08 class="num">22:class="num">48:class="num">49.440 Study(EURUSD,H1) ExpertRemove() function called class="num">2024.10.class="num">08 class="num">22:class="num">48:class="num">49.558 Study(EURUSD,H1) class="num">14 undeleted dynamic objects found: class="num">2024.10.class="num">08 class="num">22:class="num">48:class="num">49.558 Study(EURUSD,H1) class="num">14 objects of class &class="macro">#x27;CBufferFloat&class="macro">#x27; class="num">2024.10.class="num">08 class="num">22:class="num">48:class="num">49.558 Study(EURUSD,H1) class="num">19968 bytes of leaked memory found Series.mqh ArrayDouble.mqh &class="macro">#x27;NeuroNet.cl&class="macro">#x27; as &class="macro">#x27;class="kw">const class="type">class="kw">string cl_program&class="macro">#x27; class="num">1 deprecated behavior, hidden method calling will be disabled in a future MQL compiler version NeuroNet.mqh class="num">30478 class="num">22 deprecated behavior, hidden method calling will be disabled in a future MQL compiler version NeuroNet.mqh class="num">30700 class="num">22 code generated class="num">1 class="num">0 errors, class="num">2 warnings, class="num">6344 msec elapsed, cpu=&class="macro">#x27;X64 Regular&class="macro">#x27; class="num">3