交易中的神经网络:受控分段(终章)(基础篇)
◍ 用分段把神经网络关进笼子
直接用原始价格序列喂给神经网络,多数情况下会过拟合到失效。Dmitriy Gizlyk 在 2025-07-02 发布的 MT5 系统文章里给出一条实操路径:把连续行情切成受控段,再让网络在段内学习局部映射。 关键不在网络多深,而在分段规则是否稳定。原文示例以 511 根 K 线为回测窗口、2 个对照变体做交叉验证,说明分段长度本身就会改变模型泛化概率。 外汇与贵金属波动受杠杆和跳空影响,分段过短会放大噪声,过长则丢失状态切换。建议开 MT5 把历史数据按不同段长跑一遍,观察 equity 曲线断裂点再定参数。
RefMask3D 里还差的一块拼图
上一篇文章已经把 RefMask3D 的大半骨架搭起来了:几何-强化群-单词注意力模块、语言原语构造(LPC)都在各自类里跑通,解码器也能借现有交叉注意力实现顶上。唯一卡住的是对象聚类模块(OCM)——它负责收拢全局信息、吐出对象嵌入,算法开发当时停在那儿,这一篇就接着把它补完。 回顾一下框架全貌:点编码器靠集成注意力在子云和文本描述间做交叉模态对齐,语言模型把描述转成令牌,LPC 承载形状/颜色/位置等语义属性,变换器解码器强化语义专注度。OCM 是最后一环,没它前面的嵌入进不了聚类与定位的闭环。 所以本篇的技术落点非常具体:在 MT5 之外的 Python 侧把 OCM 的算法写完并接回原有类结构,验证它能从前面模块拿到整体信息并生成可用嵌入。这一步不通,RefMask3D 在多模态点云目标识别上就始终缺一条腿。
「对象聚类模块的注意力堆叠与代码骨架」
对象聚类模块(OCM)在 RefMask3D 里干的事,是把点云基元嵌入和目标对象的文本描述嵌入对齐并融合。它的物理结构是两个自注意力夹一个交叉注意力,末端接 FFN,整体像一个被剪枝的 Transformer 解码器——编码器侧省掉了 FeedForward,交叉注意力被挪到两个自注意力之间。作者给出的实验数字是:协同该模块把模型性能抬了 1.57%,这在 3D 点云实例分割任务里不算小增量。 输入有两条线:一条来自例子解码器的点云富集基元,进第一个自注意力当上下文;另一条是目标文本描述的嵌入,在交叉注意力里生成 Query,而 Key/Value 来自前一个自注意力输出。第二个自注意力再对交叉结果做内部依赖建模,最后 FFN 用两个卷积夹一层 LReLU。 CNeuronOCM 类直接继承 CNeuronBaseOCL,内部对象全部声明为静态,所以构造/析构留空,初始化全塞进 Init。下面这段是类声明的原样,能看到窗口、头数等维度变量和每层指针的归属。 代码里 iPrimWindow / iContWindow 等 uint 控制注意力窗口大小,cQuery/cKey/cValue 是卷积生成器,cMHAttentionOut 记多头输出,cScores 存注意力系数索引。注意 CreateBuffers 被单独抽出来,注意力系数缓冲区只在 OpenCL 环境里建,不在 CPU 侧初始化——想改架构先动这几个 uint 和 Init 里的层挂载顺序。 前向 feedForward 里,第一个自注意力强调与点云最相关的基元,交叉注意力用文本嵌入当 Query 去“点亮”点云里对应的基元,第二个自注意力再把这些选中嵌入互作用成一张连贯图。反向分 calcInputGradients 和 updateInputWeights 两阶段,后者就是顺次调内部层同名方法。附件有全量代码,开 MT5 接 OpenCL 跑一遍最能验证这套数据流。
class CNeuronOCM : class="kw">public CNeuronBaseOCL { class="kw">protected: class="type">uint iPrimWindow; class="type">uint iPrimUnits; class="type">uint iPrimHeads; class="type">uint iContWindow; class="type">uint iContUnits; class="type">uint iContHeads; class="type">uint iWindowKey; class=class="str">"cmt">//--- CLayer cQuery; CLayer cKey; CLayer cValue; CLayer cMHAttentionOut; CLayer cAttentionOut; CArrayInt cScores; CLayer cResidual; CLayer cFeedForward; class=class="str">"cmt">//--- class="kw">virtual class="type">bool CreateBuffers(class="type">void); class="kw">virtual class="type">bool AttentionOut(CNeuronBaseOCL *q, CNeuronBaseOCL *k, CNeuronBaseOCL *v,
◍ 注意力单元的多头参数接口
在 MT5 的 OpenCL 神经网络扩展里,CNeuronOCM 类把多头注意力拆成了两组独立维度:主分支用 units/heads 控制,键值分支用 units_kv/heads_kv 控制,两者可以不等宽。 AttentionInside 与 AttentionInsideGradients 的函数签名都要求显式传入 scores、dimension 以及上述四组整数,说明注意力矩阵的计算粒度是在调用层就锁死的,而不是在类内部自适应。 feedForward、calcInputGradients、updateInputWeights 三个虚函数被直接 override 返回 false,意味着这个神经元类型不走标准前馈链路,梯度回传必须依赖 AttentionInsideGradients 单独实现。 Init 里同时收 prim_window / window_key / cont_window 三组窗口尺寸,配合 batch 参数,实际能验证的一点是:该类初始化时就把时序上下文长度写死了,改窗口得重新 Init,不能热调。
class="kw">const class="type">int scores, CNeuronBaseOCL *out, 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">virtual class="type">bool AttentionInsideGradients(CNeuronBaseOCL *q, CNeuronBaseOCL *k, CNeuronBaseOCL *v, class="kw">const class="type">int scores, CNeuronBaseOCL *out, 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=class="str">"cmt">//--- class="kw">virtual class="type">bool feedForward(CNeuronBaseOCL *NeuronOCL) class="kw">override { class="kw">return class="kw">false; } class="kw">virtual class="type">bool calcInputGradients(CNeuronBaseOCL *NeuronOCL) class="kw">override { class="kw">return class="kw">false; } class="kw">virtual class="type">bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) class="kw">override { class="kw">return class="kw">false; } class="kw">public: CNeuronOCM(class="type">void) {}; ~CNeuronOCM(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 prim_window, class="type">uint window_key, class="type">uint prim_units, class="type">uint prim_heads, class="type">uint cont_window, class="type">uint cont_units, class="type">uint cont_heads, 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 defNeuronOCM; } 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">//--- class="kw">virtual class="type">bool WeightsUpdate(CNeuronBaseOCL *source, class="type">class="kw">float tau) class="kw">override;
在 MT5 里把 OCM 神经元的初始化拆开看
CNeuronOCM 的 Init 函数是把原始窗口、上下文窗口和多头注意力组件一次性挂到 OpenCL 环境下的入口。它先调用基类 CNeuronBaseOCL::Init,用 cont_window * cont_units 作为基础维度,若失败直接返回 false,这意味着上下文单元数和窗口长度的乘积就是后续张量的基础容量。 初始化里连续 new 了 CNeuronConvOCL 与 CNeuronBaseOCL 实例:Query / Key / Value 三个卷积层都用了 iPrimWindow 作核宽与步长,输出通道数为 iPrimHeads * iWindowKey,而多头注意力输出层维度固定为 iPrimHeads * iWindowKey * iPrimUnits。你在调参时若改了 iPrimHeads 或 iWindowKey,这三处卷积的输出通道会同步放大,显存占用可能非线性增长。 下面这段是类声明里虚函数与取值器的骨架,以及 Init 前半段的真实代码,可直接贴进 MT5 的 .mqh 里对照编译: 虚函数 override 了 SetOpenCL,并声明了 feedForward、calcInputGradients、updateInputWeights 三个训练期必跑的方法;GetPrimitiveWindow 与 GetContextWindow 是 inline 取值器,分别回传 iPrimWindow 和 iContWindow,方便外部调度时确认当前神经元视野。
class="kw">virtual class="type">void SetOpenCL(COpenCLMy *obj) class="kw">override; class=class="str">"cmt">//--- class="kw">virtual class="type">bool feedForward(CNeuronBaseOCL *Primitives, CNeuronBaseOCL *Context); class="kw">virtual class="type">bool calcInputGradients(CNeuronBaseOCL *Primitives, CNeuronBaseOCL *Context); class="kw">virtual class="type">bool updateInputWeights(CNeuronBaseOCL *Primitives, CNeuronBaseOCL *Context); class=class="str">"cmt">//--- class="kw">virtual class="type">uint GetPrimitiveWindow(class="type">void) class="kw">const { class="kw">return iPrimWindow; } class="kw">virtual class="type">uint GetContextWindow(class="type">void) class="kw">const { class="kw">return iContWindow; } }; class="type">bool CNeuronOCM::Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl, class="type">uint prim_window, class="type">uint window_key, class="type">uint prim_units, class="type">uint prim_heads, class="type">uint cont_window, class="type">uint cont_units, class="type">uint cont_heads, ENUM_OPTIMIZATION optimization_type, class="type">uint batch) { if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, cont_window * cont_units, optimization_type, batch)) class="kw">return class="kw">false; iPrimWindow = prim_window; iPrimUnits = prim_units; iPrimHeads = prim_heads; iContWindow = cont_window; iContUnits = cont_units; iContHeads = cont_heads; iWindowKey = window_key; cQuery.Clear(); cKey.Clear(); cValue.Clear(); cMHAttentionOut.Clear(); cAttentionOut.Clear(); cResidual.Clear(); cFeedForward.Clear(); CNeuronBaseOCL *neuron = NULL; CNeuronConvOCL *conv = NULL; class=class="str">"cmt">//--- Primitives Self-Attention class=class="str">"cmt">//--- Query conv = new CNeuronConvOCL(); if(!conv || !conv.Init(class="num">0, class="num">0, OpenCL, iPrimWindow, iPrimWindow, iPrimHeads * iWindowKey, iPrimUnits, class="num">1, optimization, iBatch) || !cQuery.Add(conv) ) class="kw">return class="kw">false; class=class="str">"cmt">//--- Key conv = new CNeuronConvOCL(); if(!conv || !conv.Init(class="num">0, class="num">1, OpenCL, iPrimWindow, iPrimWindow, iPrimHeads * iWindowKey, iPrimUnits, class="num">1, optimization, iBatch) || !cKey.Add(conv) ) class="kw">return class="kw">false; class=class="str">"cmt">//--- Value conv = new CNeuronConvOCL(); if(!conv || !conv.Init(class="num">0, class="num">2, OpenCL, iPrimWindow, iPrimWindow, iPrimHeads * iWindowKey, iPrimUnits, class="num">1, optimization, iBatch) || !cValue.Add(conv) ) class="kw">return class="kw">false; class=class="str">"cmt">//--- Multi-Heads Attention Out neuron = new CNeuronBaseOCL(); if(!neuron || !neuron.Init(class="num">0, class="num">3, OpenCL, iPrimHeads * iWindowKey * iPrimUnits, optimization, iBatch) || !cMHAttentionOut.Add(neuron) ) class="kw">return class="kw">false; class=class="str">"cmt">//--- Attention Out conv = new CNeuronConvOCL(); if(!conv ||