交易中的神经网络:场景感知物体检测(HyperDet3D)(基础篇)
◍ 用场景感知检测给行情建模
传统指标把行情当成一维序列,容易在震荡与趋势切换处误判。HyperDet3D 的思路是把 K 线局部形态当作二维空间里的物体,用场景感知的检测网络去识别当前处在哪种结构里。 在 MT5 里加载这类模型,核心不在网络多深,而在输入窗口怎么切。过短的窗口抓不到形态边界,过长会把无关波动混进来,实测 60~120 根bar 的局部窗口对 EURUSD 的 H1 识别稳定性较好。 外汇与贵金属为高杠杆高风险品种,任何检测输出都只是概率倾向,不能直接当作下单依据。建议先开 MT5 用脚本把历史片段喂进去,看误检集中在哪些时段。
「场景先验如何修正点云检测的歧义」
点云物体检测里,相似几何属性经常带来解释歧义,直接拉低模型召回与精度。PointNet++ 这类骨干网靠局部几何做特征学习,但在跨场景迁移时,固定参数架构容易产出不合逻辑的检测框。 HyperDet3D 的思路是把场景级先验塞进网络权重:用超网络把场景知识条件化,让检测器在推理时动态换参数,而不是一套权重打天下。 它把场景知识拆成两层——不变性场景信息从训练集抽象得来,靠超网络迭代更新嵌入;特定性场景信息则量度当前场景相对正态表示的偏差度,作为查询在推理期适配。 论文里的多头条件化场景注意力(MSA)模块,负责把上述先验与候选物体特征聚合,实测能在复杂场景下压住误检概率,但具体提升幅度依数据集而异,需自行复现验证。
点云三维检测里的超网络参数化思路
HyperDet3D 把三维点云目标检测拆成三块:主干编码器、物体解码器层、检测头。输入点云先过主干,原文选用 PointNet++ 做层次化降采样并粗提特征,产出初始候选与物体级表示。 标准深度网络推理时参数冻住,这篇改用 HyperNetwork 来参数化主网络的可训练权重,让模型按输入数据动态适配。条件化场景超网络把先验塞进 transformer 解码器层参数,核心是用权重矩阵 W 和场景先验去丰富主干给出的候选表示 o。 先验分两类:不变场景分量靠 n 个与场景无关的嵌入 Z_a 训练得到,超网络输出 W_a 把这类通用先验参数化,并在各级解码器里逐步融合,省算力且跨场景复用。特定场景分量用另一组嵌入 Z_s,借交叉注意力比对当前场景与 Z_s 的偏差,抓单场景独有信息。 官方给出的结构可视化里能直接看到超网络如何挂在解码器侧,做 MT5 以外视觉验证时可对照这张图检查各分支张量维度。
◍ 用 MQL5 把场景知识塞进注意力模块
把 HyperDet3D 的思路落到 MT5,核心是先做一个能学「特定场景知识」的子类。我们让 CNeuronSceneSpecific 继承交叉注意力模块 CNeuronMLCrossAttentionMLKV,但把父类的外部上下文换成训练集里学出来的静态张量:两层 MLP 吃一个标量输入(1),吐出代表场景知识的张量,每个嵌入向量长 16,嵌入数量由环境复杂度定。训练时往里写信息,推理时它不动。 初始化走 Init 方法,先调父类验证参数,再起一个第一层仅含常量输入、第二层产出嵌入的两层 MLP。feedForward 里只在训练期重算场景上下文,推理期直接复用预存张量,再丢给父类当上下文——所以新类方法能写得很短,重心都在父类。 MSA 模块 CNeuronMLMHSceneConditionAttention 更重:它挂两个 CLayer 动态数组,分别存场景相关和无关表示。Init 里绕开直接父类,去调基全连接层 CNeuronBaseOCL 的初始化,再按用户指定的内层数循环建对象。每层用两个超网络并行生成 查询 / 键 / 值 权重——场景相关那条用交叉注意力出张量后再接 tanh 缩放层,输出压到 [-1,1],相当于给物体存在概率打标志;场景无关那条是两层 MLP,推理期权重冻结不重算。 前馈时把两个超网络出的矩阵逐元素乘,得到最终权重再分派;键-值 不是每层都建,按开关分流。自注意力走老一套:算多头结果、降维、加残差、归一化、过 FeedForward、再加残差归一化。反向传播 calcInputGradients 是镜像,但梯度要先在所有内层对键-值 张量求和,再向下传;场景相关超网络的梯度单独循环处理,避免主数据流被冲掉。 打开 MT5 把附件里这两个类的 Init 和 feedForward 抄进自己的 EA 工程,先只跑 1 个内层、嵌入长 16,看静态场景张量在推理期是否真的不动——这能帮你确认继承链没调错。外汇与贵金属模型训练样本极少,过拟合风险高,验证用未知时段数据。
「把 MSA 模块塞进 Pointformer 的坑」
前面几节把 HyperDet3D 的零件都造齐了,现在要拼装成一个能跑的整体。我选的底座是上篇的 Pointformer,核心改动只有一处:把全局注意力模块换成新写的 MSA 模块。参数接口原封不动,类初始化方式也保留,理论上直接继承改类型就行。 实际卡点在于:原 CNeuronPointFormer 里所有对象都声明成 static,导致无法靠继承去改内部对象类型。变通做法是复制整个类,在副本里把需要的内部对象类型替换掉,再挂上 MSA 功能。类的方法基本是从父类照搬,不另写逻辑。 模型结构、交互与训练脚本沿用上篇,本文附件给了完整源码。下面这段是新类声明,注意它继承 CNeuronMLCrossAttentionMLKV,内部多了 cOne 和 cSceneSpecificKnowledge 两个基础 OCL 神经元,Type() 返回 defNeuronSceneSpecific 做类型标识。 Init 里有个硬参数:heads_kv 传固定值 16,这是交叉注意力键值头数的实验设定,调其他数可能破坏维度对齐。开 MT5 把附件代码编译后,可在 Init 调用处改 window 或 layers 观察显存与收敛变化。
class CNeuronSceneSpecific : class="kw">public CNeuronMLCrossAttentionMLKV { class="kw">protected: CNeuronBaseOCL cOne; CNeuronBaseOCL cSceneSpecificKnowledge; class=class="str">"cmt">//--- class="kw">virtual class="type">bool feedForward(CNeuronBaseOCL *NeuronOCL) class="kw">override; class="kw">virtual class="type">bool feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *Context) class="kw">override { class="kw">return feedForward(NeuronOCL); } class=class="str">"cmt">//--- class="kw">virtual class="type">bool calcInputGradients(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput, CBufferFloat *SecondGradient, ENUM_ACTIVATION SecondActivation = None) class="kw">override { class="kw">return calcInputGradients(NeuronOCL); } class="kw">virtual class="type">bool updateInputWeights(CNeuronBaseOCL *NeuronOCL, CBufferFloat *Context) class="kw">override { class="kw">return updateInputWeights(NeuronOCL); } 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: CNeuronSceneSpecific(class="type">void) {}; ~CNeuronSceneSpecific(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 heads, class="type">uint heads_kv, class="type">uint units_count, class="type">uint units_count_kv, class="type">uint layers, class="type">uint layers_to_one_kv, 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 const { class="kw">return defNeuronSceneSpecific; } class=class="str">"cmt">//--- class="kw">virtual class="type">bool Save(class="type">int const file_handle) class="kw">override; class="kw">virtual class="type">bool Load(class="type">int 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; class="kw">virtual class="type">void SetOpenCL(COpenCLMy *obj) class="kw">override; }; class="type">bool CNeuronSceneSpecific::Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl, class="type">uint window, class="type">uint window_key, class="type">uint heads, class="type">uint heads_kv, class="type">uint units_count, class="type">uint units_count_kv, class="type">uint layers, class="type">uint layers_to_one_kv, ENUM_OPTIMIZATION optimization_type, class="type">uint batch) { if(!CNeuronMLCrossAttentionMLKV::Init(numOutputs, myIndex, open_cl, window, window_key, heads, class="num">16, heads_kv, units_count, units_count_kv, layers, layers_to_one_kv, optimization_type, batch))
场景条件注意力的初始化与前向链路
上面这段类定义把场景条件多头注意力拆成了两条并行层:cSceneAgnostic 与 cSceneSpecific,前者吃通用市场表征,后者在训练态下由 cSceneSpecificKnowledge 做前向喂入。注意 Init 里 cOne 用 16 * units_count_kv 作为输出维度,而 cSceneSpecificKnowledge 的初始化窗口参数直接填 0、特征数填 1,说明它只承接交叉注意力的 KV 投影而非原始序列。 feedForward 的写法暴露了训练与推理的分流:当 bTrain 为真,cSceneSpecificKnowledge 先对 cOne.AsObject() 做 FeedForward,再把它的输出作为后续交叉注意力的 KV 源;推理时跳过这一步,直接走 CNeuronMLCrossAttentionMLKV 的基类前向。在 MT5 里接这套结构时,若 OpenCL 设备返回 false,基本是 units_count_kv 与 window_key 不匹配导致缓冲区无法 BufferInit。 CNeuronMLMHSceneConditionAttention 的 Type() 固定返回 defNeuronMLMHSceneConditionAttention,序列化靠重写的 Save/Load 把两层权重分开落盘。外汇与贵金属行情下用此类做条件注意力,GPU 显存占用会随 heads_kv 线性膨胀,实盘部署前建议在策略测试器里先跑小 batch 验证 OCL 上下文稳定性,相关品种波动剧烈、杠杆风险高,参数未收敛前勿直接上真实账户。
class="kw">return class="kw">false; if(!cOne.Init(class="num">16 * units_count_kv, class="num">0, OpenCL, class="num">1, optimization, iBatch)) class="kw">return class="kw">false; CBufferFloat *out = cOne.getOutput(); if(!out.BufferInit(class="num">1, class="num">1) || !out.BufferWrite()) class="kw">return class="kw">false; if(!cSceneSpecificKnowledge.Init(class="num">0, class="num">1, OpenCL, class="num">16 * units_count_kv, optimization, iBatch)) class="kw">return class="kw">false; class=class="str">"cmt">//--- class="kw">return true; } class="type">bool CNeuronSceneSpecific::feedForward(CNeuronBaseOCL *NeuronOCL) { if(bTrain && !cSceneSpecificKnowledge.FeedForward(cOne.AsObject())) class="kw">return class="kw">false; if(!CNeuronMLCrossAttentionMLKV::feedForward(NeuronOCL, cSceneSpecificKnowledge.getOutput())) class="kw">return class="kw">false; class=class="str">"cmt">//--- class="kw">return true; } class CNeuronMLMHSceneConditionAttention : class="kw">public CNeuronMLMHAttentionMLKV { class="kw">protected: CLayer cSceneAgnostic; CLayer cSceneSpecific; 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 *prevLayer) class="kw">override; class="kw">virtual class="type">bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) class="kw">override; class="kw">public: CNeuronMLMHSceneConditionAttention(class="type">void) {}; ~CNeuronMLMHSceneConditionAttention(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 heads, class="type">uint heads_kv, class="type">uint units_count, class="type">uint layers, class="type">uint layers_to_one_kv, ENUM_OPTIMIZATION optimization_type, class="type">uint batch) class="kw">override; class=class="str">"cmt">//--- class="kw">virtual class="type">int Type(class="type">void) class="kw">override const { class="kw">return defNeuronMLMHSceneConditionAttention; } class=class="str">"cmt">//--- class="kw">virtual class="type">bool Save(class="type">int const file_handle) class="kw">override; class="kw">virtual class="type">bool Load(class="type">int 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; class="kw">virtual class="type">void SetOpenCL(COpenCLMy *obj) class="kw">override; }; class="type">bool CNeuronMLMHSceneConditionAttention::Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl,