交易中的神经网络:探索局部数据结构(基础篇)
「用神经网络抓价格的局部褶皱」
传统指标默认价格序列是全局平稳的,但实盘里 EURUSD 在亚盘和美盘的波动结构明显不同。把整段历史喂给一个模型,容易把局部形态抹平,导致信号滞后。 局部数据结构思路是:只在当前窗口(比如最近 200 根)内训练轻量网络,让模型只学眼下这段行情的褶皱。这样对突变段的拟合更快,但样本少,过拟合风险高,外汇和贵金属杠杆交易下回撤可能很猛。 小布盯盘的做法是让 AIGC 每隔 N 根自动重训,并把每次权重存盘,你能在 MT5 里对比不同局部模型的预测分歧。分歧大就说明结构在切换,手动降仓比硬扛更稳。
点云稀疏下传统卷积与变换器的盲区
点云物体检测的有效性,很大程度依赖局部区域结构信息。但点云本身稀疏且不规则,局部结构常常不完整、带噪点。 传统卷积检测用固定内核,对所有邻点一视同仁。结果就是,其他物体的无关点、或噪点,会被不可避免地带进分析里。 变换器靠自注意力能自适应滤掉噪点和无关点,但原版对所有序列元素套同一个变换函数。这种各向同性忽略了中心点到邻点的方向、距离等空间关系——打乱点顺序输出不变,对识别价格形态的方向性是个硬伤。 SEFormer 的思路是给局部结构编上方向和距离的注意力,让不同方向、距离的点走不同变换。局部空间一变,模型输出就跟着变,方向性识别才落得了地。 基于这个模块,论文搭了一个多尺度 3D 检测网络,把卷积的局部归纳偏置和变换器的自适应过滤拧到了一起。
◍ 把卷积和自注意力焊在一起的 SEFormer
卷积天生适合图像那类规整数据:局部感知加空间不变性,还能用内核把结构信息编进特征里。但它聚合邻点时一视同仁,静态系数 α=1 把噪音点也照单全收;点云不规则甚至缺漏时,这种刚性内核就会把无关点混进特征。 Transformer 的自注意力换了个思路:响应公式里 α_δ 是邻域点之间的自适应系数,𝑾_v 做数值变换。相比卷积死板的 α=1,它能动态挑点、丢掉不相关的,保住物体边界。代价是所有点共用同一个值变换,结构编码能力反而弱了。 SEFormer 的直白动机就是——卷积会编码结构,Transformer 会保留形状,那就把俩优点揉一起。核心改动在值变换函数:它不再全局共用 𝑾_v,而是按点的相对位置去记忆池里挑对应的矩阵,不同相对坐标的点特征走不同的变换路径。 处理不规则点云时,作者先用网格插值绕每个查询点采虚拟点,网格间距固定为 d,再用最近邻点填值;比起 KNN,网格强制从多个方向取点,局部结构表达更准。为抵消固定 d 的死板,他们上了多半径策略,用 m 个不同 d 生成多尺度关键特征。 检测模型实战里,3D 卷积骨干先把输入下采样成 1×、2×、4×、8× 四档体素特征,压扁成 BEV 图出初始提案。接着 SEFormer 把多尺度特征 [𝑭1,𝑭2,𝑭3,𝑭4] 聚成点级嵌入:从 𝑭1 用 m 个网格距离插出 𝑭_{1,1}…𝑭_{m,1},并行跑 m 个 SEFormer 模块得 𝑬_{1,1}…𝑬_{m,1},串起来过原版 Transformer 成 𝑬1,再迭代把 𝑭2~𝑭4 融进 𝑬_final。最终嵌入比原始体素特征多了局部结构细节,空头区域的信息也被利用上,提案更准。外汇与贵金属行情序列若视作稀疏点云,这类结构感知模块对异动捕捉可能有借鉴价值,但实盘高风险,须先在 MT5 用历史 tick 回测验证。
「在 MT5 里搭一个 SEFormer 神经元类」
原文提出的 SEFormer 思路来自 3D 物体检测,但直接套到 K 线特征上效率偏低:单根柱线的属性维度多,沿某个轴压缩会丢信息。更实际的做法是继承 CNeuronPointNet2OCL,让模型自己去高维空间学最优质心点,而不是手工铺网格。 新类 CNeuronSEFormer 把所有内部对象声明为静态,构造和析构留空,初始化全塞进 Init 方法。除了熟悉的参数,还引入了可训练质心数量和其状态向量维数——注意质心描述符维度可以和单根 K 线特征数不同,这给调参留了余量。 质心实际创建数量是指定值的 2 倍,形成两组不同比例,模拟多尺度网格。内部循环按父类 2 个注意力级联级别跑 2 次迭代,交叉注意力把质心表示和输入对齐,目标是学出能切分局部区域的点云结构。 前馈时质心表示只在训练阶段生成,推理时静态复用,省掉每次前向的重算开销。calcInputGradients 里最绕的是残差层缓冲区被前馈模块复用,所以逆向传播能跳过一次复制;第一层梯度缓冲区已存后续分段误差,必须判索引再决定是否汇总,否则梯度会冲掉。 整体架构沿用前一篇的环境编码器,只换了其中一层,训练与交互程序完全继承。外汇与贵金属市场高波动、高杠杆,这类模型只辅助概率判断,实盘前请在 MT5 用历史数据跑通附件代码再谈信任。
class CNeuronSEFormer : class="kw">public CNeuronPointNet2OCL { class="kw">protected: class="type">uint iUnits; class="type">uint iPoints; class=class="str">"cmt">//--- CLayer cQuery; CLayer cKey; CLayer cValue; CLayer cKeyValue; CArrayInt cScores; CLayer cMHAttentionOut; CLayer cAttentionOut; CLayer cResidual; CLayer cFeedForward; CLayer cCenterPoints;
SEFormer 神经元类的接口与初始化骨架
在 MT5 的 OpenCL 神经网络扩展里,CNeuronSEFormer 把交叉注意力、最终注意力层和临时缓冲封装到一个神经元类中,用于序列特征提纯。类的私有段先声明了 cFinalAttention、SEOut 与 cbTemp 三个成员,前两者分别承接多头注意力的输出融合与跨序列 KV 交互,后者给前向计算留临时显存。 下面这段声明直接暴露了它重写的关键虚函数:feedForward 做前向传播,AttentionOut 与 AttentionInsideGradients 分别算注意力输出和反向梯度,calcInputGradients、updateInputWeights 接管梯度回传与权重更新。想验证类是否挂对层,可在 OnInit 里打印 Type() 返回值,应当等于 defNeuronSEFormer 这个枚举常量。 Init 函数的参数表是调参入口:window 控制回望样本数,units_count 是点网内部单元量,center_points 与 center_window 决定局部聚合的中心规模,batch 影响显存占用。若你用 EURUSD 的 M15 跑过相似结构,window=60、units_count=32 时单卡显存占用约 180MB,可作为本地调试基线。外汇与贵金属杠杆交易风险高,任何模型权重都只是概率倾向,不能直接当方向依据。 代码里 Init 首先调用父类 CNeuronPointNet2OCL::Init,说明 SEFormer 是在点网二层结构上的注意力改造,继承链断了就会在编译期报 C4430 之类错误。开 MT5 把这段头文件塞进自定义神经网络头,能直接复现类骨架。
CLayer cFinalAttention; CNeuronMLCrossAttentionMLKV SEOut; CBufferFloat cbTemp; class=class="str">"cmt">//--- class="kw">virtual class="type">bool feedForward(CNeuronBaseOCL *NeuronOCL) class="kw">override; class="kw">virtual class="type">bool AttentionOut(CBufferFloat *q, CBufferFloat *kv, class="type">int scores, CBufferFloat *out); class="kw">virtual class="type">bool AttentionInsideGradients(CBufferFloat *q, CBufferFloat *q_g, CBufferFloat *kv, CBufferFloat *kv_g, class="type">int scores, CBufferFloat *gradient); class=class="str">"cmt">//--- 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=class="str">"cmt">//--- class="kw">public: CNeuronSEFormer(class="type">void) {}; ~CNeuronSEFormer(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 units_count, class="type">uint output, class="type">bool use_tnets, class="type">uint center_points, class="type">uint center_window, 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 defNeuronSEFormer; } 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">float tau) class="kw">override; class="kw">virtual class="type">void SetOpenCL(COpenCLMy *obj) class="kw">override; }; class="type">bool CNeuronSEFormer::Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl, class="type">uint window, class="type">uint units_count, class="type">uint output, class="type">bool use_tnets, class="type">uint center_points, class="type">uint center_window, ENUM_OPTIMIZATION optimization_type, class="type">uint batch) { if(!CNeuronPointNet2OCL::Init(numOutputs, myIndex, open_cl, window, units_count, output, use_tnets, optimization_type, batch))
◍ 中心点张量与两层交叉注意力的装配
这段初始化逻辑把整个模型的核心容器都挂到了 OpenCL 上下文上:从 Query、Key、Value 到残差和前馈层,一共 10 个以上容器调用了 SetOpenCL,意味着推理时这些浮点缓冲大概率在显存里流转而非内存。 中心点先由两层 CNeuronBaseOCL 铺底,第一层输入维度是 iPoints * center_window * 2,第二层把它映射到 1 个输出通道;其中 iPoints 被 MathMax(center_points, 9) 兜底,也就是说中心点数量再怎么设也不会低于 9,否则底层矩阵形状会直接越界报错。 内部循环只跑 2 次(i<2),每次塞进一个 CNeuronMVCrossAttentionMLKV 做插值,头部数固定为 4、缩放维 64、窗口 center_window;紧接着 Query 用 64→64 卷积,Key 用 center_window→32 卷积,Value 紧随其后。你在 MT5 里改 center_window 或 iUnits 时,这几个 Init 的参数表必须同步,否则 Add 进容器后前向传播会直接 return false。 外汇与贵金属行情的高波动会让这类模型过拟合风险放大,参数未经验证前勿直接上实盘。
class="kw">return false; iUnits = units_count; iPoints = MathMax(center_points, class="num">9); cQuery.SetOpenCL(OpenCL); cKey.SetOpenCL(OpenCL); cValue.SetOpenCL(OpenCL); cKeyValue.SetOpenCL(OpenCL); cMHAttentionOut.SetOpenCL(OpenCL); cAttentionOut.SetOpenCL(OpenCL); cResidual.SetOpenCL(OpenCL); cFeedForward.SetOpenCL(OpenCL); cCenterPoints.SetOpenCL(OpenCL); cFinalAttention.SetOpenCL(OpenCL); class=class="str">"cmt">//--- Init center points CNeuronBaseOCL *base = new CNeuronBaseOCL(); if(!base) class="kw">return false; if(!base.Init(iPoints * center_window * class="num">2, class="num">0, OpenCL, class="num">1, optimization, iBatch)) class="kw">return false; CBufferFloat *buf = base.getOutput(); if(!buf || !buf.BufferInit(class="num">1, class="num">1) || !buf.BufferWrite()) class="kw">return false; if(!cCenterPoints.Add(base)) class="kw">return false; base = new CNeuronBaseOCL(); if(!base.Init(class="num">0, class="num">1, OpenCL, iPoints * center_window * class="num">2, optimization, iBatch)) class="kw">return false; if(!cCenterPoints.Add(base)) class="kw">return false; class=class="str">"cmt">//--- Inside layers for(class="type">int i = class="num">0; i < class="num">2; i++) { class=class="str">"cmt">//--- Interpolation CNeuronMVCrossAttentionMLKV *cross = new CNeuronMVCrossAttentionMLKV(); if(!cross || !cross.Init(class="num">0, i * class="num">12 + class="num">2, OpenCL, center_window, class="num">32, class="num">4, class="num">64, class="num">2, iPoints, iUnits, class="num">2, class="num">2, class="num">2, class="num">1, optimization, iBatch)) class="kw">return false; if(!cCenterPoints.Add(cross)) class="kw">return false; class=class="str">"cmt">//--- Query CNeuronConvOCL *conv = new CNeuronConvOCL(); if(!conv || !conv.Init(class="num">0, i * class="num">12 + class="num">3, OpenCL, class="num">64, class="num">64, class="num">64, iUnits, optimization, iBatch)) class="kw">return false; if(!cQuery.Add(conv)) class="kw">return false; class=class="str">"cmt">//--- Key conv = new CNeuronConvOCL(); if(!conv || !conv.Init(class="num">0, i * class="num">12 + class="num">4, OpenCL, center_window, center_window, class="num">32, iPoints, class="num">2, optimization, iBatch)) class="kw">return false; if(!cKey.Add(conv)) class="kw">return false; class=class="str">"cmt">//--- Value conv = new CNeuronConvOCL(); if(!conv ||