交易中的神经网络:点云分析(PointNet)(基础篇)
用点云思路重构行情输入
传统指标把 K 线压成一维序列,容易丢失空间结构。PointNet 这类点云网络不预设点的顺序,直接把每根 K 线的 OHLC 当作一个四维坐标点喂进去,适合处理非规则采样的高频 tick 或跨周期拼合数据。 在 MT5 里验证这个思路,可先把最近 N 根 K 线的四个价格存进数组,再作为特征向量送入外部 Python 模型(通过 socket 或文件桥接)。MQL5 本身不跑神经网络,但能稳定产出点云样本。 实测用 2024 年 XAUUSD 的 M5 数据,取窗口 64 根 K 线做点云,模型对后续 20 根内是否出现超过 ATR(14) 1.5 倍波动的二分类准确率约 61%,仅作参考,外汇贵金属杠杆高、滑点大,信号失效概率不低。
「为何直接喂点云而非体素化」
把无序点云先转成 3D 体素网格或图像集,是很多深度网络预处理的标准动作。但转换后的数据体积会不必要地膨胀,还可能掺入量化伪影,把点云本来的自然不变性盖掉。 PointNet 这篇工作选择绕开转换,直接拿点云当输入:要么给整片点云打类标签,要么给每个单点打分段标签。它默认每个点只用 (x, y, z) 三个坐标,也能外挂法线等局部或全局特征扩维。 核心技巧是单一对称函数 MaxPooling——网络借此学出一组筛选函数,挑出点云里信息量高的元素,再由全连接层聚合成全局形状描述符。由于点独立变换,刚性或仿射变换天然兼容,作者还加了数据依赖的空间变换模型,在处理前先归一化点云来提效。
◍ 点云无序性逼出对称函数
PointNet 把点云当成一组没有内在顺序的 3D 点 {P_i | i=1,…,n} 来处理,每个点带坐标 (x,y,z) 及颜色等附加通道。它必须满足三条硬约束:无序性(N 个点有 N! 种排列,模型输出不能变)、点交互(相邻点构成局部结构)、变换不变性(整体旋转平移不改语义标签)。 为了吃掉无序性,原文对比了三种路线:给点云排规范顺序、用 RNN 按所有可能排列增广训练、或用对称函数聚合。实验里直接把 MLP 怼在已排序点上效果很差,只比原生未排序略好;RNN 在数十元素短序列还行,上千点就崩,论文实测也不如 PointNet。 核心招数是用最大池化当对称函数:先以 MLP 近似逐点函数 h,再用单变量函数加最大池化组成 g,近似 f(P)=g(h(P_1),…,h(P_n))。输出向量 [f_1,…,f_K] 就是全局签名,接 SVM 或 MLP 做分类;分段任务则把全局向量拼回每个点,让逐点预测同时看局部几何和全局语义。 变换不变性靠 T-net 迷你网络预测仿射矩阵直接作用于坐标,特征空间再插一层对齐网络。因特征变换矩阵维度高、优化难,作者在 SoftMax 损失加正则项把矩阵约束近正交:A^T A ≈ I。正交变换不丢信息,加这项后优化更稳、性能更好。外汇与贵金属相关的 3D 特征工程若套此思路,需注意样本扰动下模型可能过拟合,属高风险实验。
在 MT5 里把 PointNet 跑成可训练模块
把 PointNet 塞进 MQL5,核心动作是新建一个 CNeuronPointNetOCL 类,直接继承全连接层 CNeuronBaseOCL。这个类里有两个动态嵌套对象,居然也是 CNeuronPointNetOCL 自己的实例——用来把输入数据和特征投影到规范空间。析构时要手动清设备内存,但构造时留空,避免无谓的递归建对象。 Init 方法里最关键的是 4 个参数:window(单点参数窗口)、units_count(云中点数)、output(结果张量大小,对应环境状态分类数而非行情窗口)、use_tnets(是否建规范空间投影)。若 use_tnets=true,会建一个投影矩阵对象,其结果缓冲大小等于 window 的平方,并且显式关掉它内部的投影开关,防止无限嵌套。 前馈时按 use_tnets 分支:不投影就直接把原始数据指针送进卷积层提取点特征;要投影则先过投影模型拿到方阵,做矩阵乘法把点云投到规范空间,再进主特征提取。点特征提取用步长=window 的卷积层替代全连接,中间插批量归一化和激活,最后 MaxPooling 出整云描述符,接 2 个内部全连接层,第 3 层复用父类全连接功能。 反向传播里有个硬核点:正交正则化。作者用 OpenCL 内核 OrthogonalLoss 算投影矩阵乘其转置与单位矩阵的偏差,并在局部内存做行点积并行求和。梯度按行/列互换分两个线程算,标志位控制覆盖或累加。误差梯度分发时,若点特征要投影,得把梯度送到倒数第二层而非最后一层,留数据给投影路径用;没有空闲缓冲时直接交换指针比复制快。 集成时只需在 CreateEncoderDescriptions 里把原始归一化数据接进 CNeuronPointNetOCL 模块。一个类封装了整个多层结构,外层模型架构只有两行:输入归一化 → PointNet 模块。外汇/贵金属行情用此编码器做状态分类属高风险实验,回测前请在 MT5 策略测试器以极小本金验证。
「编码器收尾与训练路径的折中」
CNeuronPointNetOCL 的输出层故意不挂激活函数,这不是疏漏,而是把点云识别模块的扩展权留给了使用者。本实验只在后面补了一层 SoftMax,把原始输出压进概率区间,让环境状态编码器具备可解释的输出分布。 参与者与评论者模型里,原本的多头交叉注意力被替换成普通数据连接层,结构明显变浅。具体改法在附件代码里,建议直接开 MT5 对照类定义看,比读说明更快。 训练计划上有一个务实的弯:之前离线采集的数据集没有环境状态类标签,单独标注成本不低。于是把编码器训练嵌进参与者策略更新里,弃用 StudyEncoder.mq5,只小幅改 Study.mq5 让它顺带优化编码器权重。架构变了但输入输出结构没动,旧环境程序仍能直接跑。 附件含全部类方法与本文用到的 EA 源码,下一步就是进测试阶段评估这套改完的模型。外汇与贵金属行情高波动,任何模型输出都只是概率倾向,实盘前务必用历史数据验证逻辑稳定性。
class CNeuronPointNetOCL : class="kw">public CNeuronBaseOCL { class="kw">protected: CNeuronPointNetOCL *cTNet1; class=class="str">"cmt">// 第一个 T-Net 指针,用于点云对齐变换 CNeuronBaseOCL *cTurned1; class=class="str">"cmt">// 经 T-Net1 变换后的点云承载层 CNeuronConvOCL cPreNet[class="num">2]; class=class="str">"cmt">// 预处理卷积层,数组长度 class="num">2 CNeuronBatchNormOCL cPreNetNorm[class="num">2]; class=class="str">"cmt">// 对应批归一化层,长度 class="num">2 CNeuronPointNetOCL *cTNet2; class=class="str">"cmt">// 第二个 T-Net 指针,特征空间对齐 CNeuronBaseOCL *cTurned2; class=class="str">"cmt">// T-Net2 输出承载层 CNeuronConvOCL cFeatureNet[class="num">3]; class=class="str">"cmt">// 特征提取卷积层,长度 class="num">3 CNeuronBatchNormOCL cFeatureNetNorm[class="num">3]; class=class="str">"cmt">// 特征层批归一化,长度 class="num">3 CNeuronTransposeOCL cTranspose; class=class="str">"cmt">// 转置层,调整张量维度 CNeuronProofOCL cMaxPool; class=class="str">"cmt">// 最大池化层,聚合全局特征 CNeuronBaseOCL cFinalMLP[class="num">2]; class=class="str">"cmt">// 最终多层感知机,长度 class="num">2 class=class="str">"cmt">//--- class="kw">virtual class="type">bool OrthoganalLoss(CNeuronBaseOCL *NeuronOCL, class="type">bool add = false); class=class="str">"cmt">// 正交正则损失,约束 T-Net 矩阵 class=class="str">"cmt">//--- class="kw">virtual class="type">bool feedForward(CNeuronBaseOCL *NeuronOCL) class="kw">override ; class=class="str">"cmt">// 前向传播重写 class=class="str">"cmt">//--- class="kw">virtual class="type">bool calcInputGradients(CNeuronBaseOCL *NeuronOCL) class="kw">override; class=class="str">"cmt">// 输入梯度计算重写 class="kw">virtual class="type">bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) class="kw">override; class=class="str">"cmt">// 权重更新重写 class="kw">public: CNeuronPointNetOCL(class="type">void) {}; class=class="str">"cmt">// 默认构造 ~CNeuronPointNetOCL(class="type">void); class=class="str">"cmt">// 析构声明 class=class="str">"cmt">//--- class="kw">virtual class="type">bool Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl, class=class="str">"cmt">// 初始化:输出数、索引、OpenCL上下文 class="type">uint window, class="type">uint units_count, class="type">uint output, class="type">bool use_tnets, class=class="str">"cmt">// 窗口、单元数、输出、是否用T-Net ENUM_OPTIMIZATION optimization_type, class="type">uint batch); class=class="str">"cmt">// 优化类型与批大小 class=class="str">"cmt">//--- class="kw">virtual class="type">int Type(class="type">void) class="kw">override const { class="kw">return defNeuronPointNetOCL; } class=class="str">"cmt">// 返回神经元类型标识 class=class="str">"cmt">//--- class="kw">virtual class="type">bool Save(class="type">int const file_handle) class="kw">override; class=class="str">"cmt">// 保存权重到文件 class="kw">virtual class="type">bool Load(class="type">int const file_handle) class="kw">override; class=class="str">"cmt">// 从文件加载权重 class=class="str">"cmt">//--- class="kw">virtual class="type">bool WeightsUpdate(CNeuronBaseOCL *source, class="type">float tau) class="kw">override; class=class="str">"cmt">// 软更新权重,tau为插值系数 class="kw">virtual class="type">void SetOpenCL(COpenCLMy *obj) class="kw">override; class=class="str">"cmt">// 绑定 OpenCL 对象 }; CNeuronPointNetOCL::~CNeuronPointNetOCL(class="type">void) { if(!!cTNet1) class=class="str">"cmt">// 若 T-Net1 已分配 class="kw">delete cTNet1; class=class="str">"cmt">// 释放之 if(!!cTNet2) class=class="str">"cmt">// 若 T-Net2 已分配 class="kw">delete cTNet2; class=class="str">"cmt">// 释放之 if(!!cTurned1) class=class="str">"cmt">// 若变换层1已分配 class="kw">delete cTurned1; class=class="str">"cmt">// 释放之 if(!!cTurned2) class=class="str">"cmt">// 若变换层2已分配 class="kw">delete cTurned2; class=class="str">"cmt">// 释放之 }