交易中的神经网络:点云的层次化特征学习·进阶篇
(2/3)· PointNet 抓不住局部几何,PointNet++ 如何用嵌套分区把行情点集拆成多分辨率特征
GPU 核函数里的距离归一与局部最大池化
这段 OpenCL 内核干了两件事:先算样本间欧氏距离平方,再在局部内存里归约出最大距离做归一化,随后由另一个核函数按半径阈值做前向传播过滤。外汇与贵金属行情的高波动会让 distance 出现 inf/nan,代码里用 isinf/isnan 拦截后强制置 1,避免 GPU 分支炸掉整批计算。 核心距离计算只在前 main != slave 时执行,循环 dimension 次累加 (data[shift_main+d]-data[shift_slave+d])^2,即标准平方欧氏距离。shift_main = main*dimension 这种偏移写法说明数据是按行主序铺进一维 global 数组的,开 MT5 看自家特征矩阵维度时要对齐。 局部最大归约用了经典的折半法:count 从 ls 起每次 (count+1)/2 收缩,slave 与 slave+count 比较取大,barrier(CLK_LOCAL_MEM_FENCE) 保证局部内存可见性。Temp[0] 最终存的是本块最大距离,dist /= Temp[0] 把距离压到 [0,1] 区间,便于后续半径比较。 FeedForwardLocalMax 核里 result 初始化为 -3.402823466e+38(float 负极小值),遍历 total 个距离,若 distance[shift_dist+k] > radius 直接 continue 跳过。这意味着 radius 是可控的邻域阈值,调小它会让更多样本被排除,倾向得到更稀疏的矩阵_o,实盘前建议在 EURUSD 5M 上用不同 radius 回测确认稳定性。
class="type">int ls = min((class="type">int)total, (class="type">int)LOCAL_ARRAY_SIZE); class="kw">const class="type">int shift_main = main * dimension; class="kw">const class="type">int shift_slave = slave * dimension; class="kw">const class="type">int shift_dist = main * total + slave; class=class="str">"cmt">//--- calc distance class="type">class="kw">float dist = class="num">0; if(main != slave) { for(class="type">int d = class="num">0; d < dimension; d++) dist += pow(data[shift_main + d] - data[shift_slave + d], class="num">2.0f); } class=class="str">"cmt">//--- Look Max for(class="type">int i = class="num">0; i < total; i += ls) { if(!isinf(dist) && !isnan(dist)) { if(i <= slave && (i + ls) > slave) Temp[slave - i] = max((i == class="num">0 ? class="num">0 : Temp[slave - i]), dist); } else if(i == class="num">0) Temp[slave] = class="num">0; barrier(CLK_LOCAL_MEM_FENCE); } class="type">int count = ls; do { count = (count + class="num">1) / class="num">2; if(slave < count && (slave + count) < ls) { if(Temp[slave] < Temp[slave + count]) Temp[slave] = Temp[slave + count]; Temp[slave + count] = class="num">0; } barrier(CLK_LOCAL_MEM_FENCE); } class="kw">while(count > class="num">1); class=class="str">"cmt">//--- Normalize if(Temp[class="num">0] > class="num">0) dist /= Temp[class="num">0]; if(isinf(dist) || isnan(dist)) dist = class="num">1; class=class="str">"cmt">//--- result distance[shift_dist] = dist; } __kernel class="type">void FeedForwardLocalMax(__global class="kw">const class="type">class="kw">float *matrix_i, __global class="kw">const class="type">class="kw">float *distance, __global class="type">class="kw">float *matrix_o, class="kw">const class="type">class="kw">float radius ) { class="kw">const class="type">size_t i = get_global_id(class="num">0); class="kw">const class="type">size_t total = get_global_size(class="num">0); class="kw">const class="type">size_t d = get_global_id(class="num">1); class="kw">const class="type">size_t dimension = get_global_size(class="num">1); class="kw">const class="type">int shift_dist = i * total; class="kw">const class="type">int shift_out = i * dimension + d; class="type">class="kw">float result = -class="num">3.402823466e+38; for(class="type">int k = class="num">0; k < total; k++) { if(distance[shift_dist + k] > radius) class="kw">continue;
「点云局部池化层的类骨架与初始化」
在 MT5 的 OpenCL 神经网络扩展里,CNeuronPointNet2Local 继承自 CNeuronConvOCL,专门处理点云数据的局部特征聚合。它内部用 cDistance 缓存邻域距离,并用三组 cFeatureNet 配合 cFeatureNetNorm 做局部特征提取,再由 cLocalMaxPool 完成局部最大池化。 类声明中可以看到 fRadius 控制邻域半径,iUnits 指定单元数;三个虚函数 CalcDistance、LocalMaxPool、LocalMaxPoolGrad 留给了子类去实现具体的几何计算。feedForward 与反向传播的几个 override 方法,则把标准卷积层的接口接管过来。 Init 函数的实参暴露了关键默认尺寸:卷积核高宽都写死为 128,而 window_out 与 units_count 由外部传入。若你在 EA 里实例化该类,units_count 设得过小会导致局部特征覆盖不足,EURUSD 这类高噪声品种上回测曲线可能更跳。 外汇与贵金属杠杆高、滑点随机,任何神经网络信号都只是概率倾向,开 MT5 把上面代码贴进自定义指标工程编译,先确认 128 这个硬编码会不会和你自己的 window 冲突。
class="type">int shift = k * dimension + d; result = max(result, matrix_i[shift]); } matrix_o[shift_out] = result; } class CNeuronPointNet2Local : class="kw">public CNeuronConvOCL { class="kw">protected: class="type">class="kw">float fRadius; class="type">uint iUnits; class=class="str">"cmt">//--- CBufferFloat cDistance; CNeuronConvOCL cFeatureNet[class="num">3]; CNeuronBatchNormOCL cFeatureNetNorm[class="num">3]; CNeuronBaseOCL cLocalMaxPool; CNeuronConvOCL cFinalMLP; class=class="str">"cmt">//--- class="kw">virtual class="type">bool CalcDistance(CNeuronBaseOCL *NeuronOCL); class="kw">virtual class="type">bool LocalMaxPool(class="type">void); class="kw">virtual class="type">bool LocalMaxPoolGrad(class="type">void); 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 *NeuronOCL) class="kw">override; class="kw">virtual class="type">bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) class="kw">override; class="kw">public: CNeuronPointNet2Local(class="type">void) {}; ~CNeuronPointNet2Local(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 window_out, class="type">class="kw">float radius, 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 defNeuronPointNet2LocalOCL; } 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; class="kw">virtual class="type">void SetOpenCL(COpenCLMy *obj) class="kw">override; }; class="type">bool CNeuronPointNet2Local::Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl, class="type">uint window, class="type">uint units_count, class="type">uint window_out, class="type">class="kw">float radius, ENUM_OPTIMIZATION optimization_type, class="type">uint batch) { if(!CNeuronConvOCL::Init(numOutputs, myIndex, open_cl, class="num">128, class="num">128, window_out, units_count, class="num">1, optimization_type, batch)) class="kw">return class="kw">false;
◍ PointNet 特征层的 OpenCL 初始化链路
这段初始化把 PointNet2 的局部特征提取拆成了三层卷积加归一化的堆叠,每层神经元规模从 64 扩到 128 再到 256,批大小由 iBatch 控制。距离矩阵缓冲按 iUnits*iUnits 预分配,半径下限被钳在 0.1,避免零半径导致核函数退化。 第三层归一化特意不设激活函数(None),前两层用 LReLU,说明作者在高层特征上倾向保留线性响应,防止小梯度被进一步压缩。最后接一个 LocalMaxPool 再做 256→128 的 MLP,输出层同样置为 None 激活。 feedForward 里先算点对距离,再循环跑三层特征网与归一化,temp 指针逐层接力传递张量。若任一步返回 false 则整个前向中断——在 MT5 用 OpenCL 跑这类网络时,任一 Init 失败都会在日志留下层号(0~7),可直接定位是缓冲还是算子问题。外汇与贵金属行情下用此类模型做信号提取属高风险,回测与实盘偏差可能显著。
fRadius = MathMax(class="num">0.1f, radius); iUnits = units_count; cDistance.BufferFree(); if(!cDistance.BufferInit(iUnits * iUnits, class="num">0) || !cDistance.BufferCreate(OpenCL)) class="kw">return class="kw">false; if(!cFeatureNet[class="num">0].Init(class="num">0, class="num">0, OpenCL, window, window, class="num">64, iUnits, class="num">1, optimization, iBatch)) class="kw">return class="kw">false; if(!cFeatureNetNorm[class="num">0].Init(class="num">0, class="num">1, OpenCL, class="num">64 * iUnits, iBatch, optimization)) class="kw">return class="kw">false; cFeatureNetNorm[class="num">0].SetActivationFunction(LReLU); if(!cFeatureNet[class="num">1].Init(class="num">0, class="num">2, OpenCL, class="num">64, class="num">64, class="num">128, iUnits, class="num">1, optimization, iBatch)) class="kw">return class="kw">false; if(!cFeatureNetNorm[class="num">1].Init(class="num">0, class="num">3, OpenCL, class="num">128 * iUnits, iBatch, optimization)) class="kw">return class="kw">false; cFeatureNetNorm[class="num">1].SetActivationFunction(LReLU); if(!cFeatureNet[class="num">2].Init(class="num">0, class="num">4, OpenCL, class="num">128, class="num">128, class="num">256, iUnits, class="num">1, optimization, iBatch)) class="kw">return class="kw">false; if(!cFeatureNetNorm[class="num">2].Init(class="num">0, class="num">5, OpenCL, class="num">256 * iUnits, iBatch, optimization)) cFeatureNetNorm[class="num">2].SetActivationFunction(None); if(!cLocalMaxPool.Init(class="num">0, class="num">6, OpenCL, cFeatureNetNorm[class="num">2].Neurons(), optimization, iBatch)) class="kw">return class="kw">false; if(!cFinalMLP.Init(class="num">0, class="num">7, OpenCL, class="num">256, class="num">256, class="num">128, iUnits, class="num">1, optimization, iBatch)) class="kw">return class="kw">false; cFinalMLP.SetActivationFunction(LReLU); SetActivationFunction(None); class="kw">return true; } class="type">bool CNeuronPointNet2Local::feedForward(CNeuronBaseOCL *NeuronOCL) { if(!CalcDistance(NeuronOCL)) class="kw">return class="kw">false; CNeuronBaseOCL *temp = NeuronOCL; class="type">uint total = cFeatureNet.Size(); for(class="type">uint i = class="num">0; i < total; i++) { if(!cFeatureNet[i].FeedForward(temp)) class="kw">return class="kw">false; if(!cFeatureNetNorm[i].FeedForward(cFeatureNet[i].AsObject())) class="kw">return class="kw">false; temp = cFeatureNetNorm[i].AsObject(); } if(!LocalMaxPool()) class="kw">return class="kw">false; if(!cFinalMLP.FeedForward(cLocalMaxPool.AsObject())) class="kw">return class="kw">false; if(!CNeuronConvOCL::feedForward(cFinalMLP.AsObject())) class="kw">return class="kw">false; class=class="str">"cmt">//--- class="kw">return true; } class CNeuronPointNet2OCL : class="kw">public CNeuronPointNetOCL { class="kw">protected: CNeuronPointNetOCL *cTNetG; CNeuronBaseOCL *cTurnedG;
PointNet2 类的析构与初始化落点
CNeuronPointNet2OCL 在声明里挂了两个 CNeuronPointNet2Local 实例数组 caLocalPointNet[2],并在 public 段暴露了 Init、Save、Load、WeightsUpdate 等重写接口;其中 Type() 直接返回 defNeuronPointNet2OCL 常量,用于运行时类型鉴别。 析构函数只做一件事:若 cTNetG 与 cTurnedG 指针非空,就逐个 delete 释放,避免 OpenCL 侧的拓扑网络对象泄漏。 Init 调用父类 CNeuronPointNetOCL::Init 时,把 window 参数硬编码为 64 传入,其余 units_count、output、use_tnets 等沿用入参;当 use_tnets 为 true 时,再新建 cTNetG 并以其 Init(0, 0, OpenCL, window, units_count, window*window, false, optimization, iBatch) 构建 T-Net 分支——注意输出维度被设为 window*window,这是该结构对内层几何关系的展开方式。外汇与贵金属模型接这套网络时波动剧烈,回测过拟合概率偏高,实盘前务必在 MT5 用历史数据跑通 Init 返回路径。
CNeuronPointNet2Local caLocalPointNet[class="num">2]; class="kw">virtual class="type">bool feedForward(CNeuronBaseOCL *NeuronOCL) class="kw">override ; 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: CNeuronPointNet2OCL(class="type">void) {}; ~CNeuronPointNet2OCL(class="type">void) ; 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, ENUM_OPTIMIZATION optimization_type, class="type">uint batch); class="kw">virtual class="type">int Type(class="type">void) class="kw">override class="kw">const { class="kw">return defNeuronPointNet2OCL; } 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="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; }; CNeuronPointNet2OCL::~CNeuronPointNet2OCL(class="type">void) { if(!!cTNetG) class="kw">delete cTNetG; if(!!cTurnedG) class="kw">delete cTurnedG; } class="type">bool CNeuronPointNet2OCL::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, ENUM_OPTIMIZATION optimization_type, class="type">uint batch) { if(!CNeuronPointNetOCL::Init(numOutputs, myIndex, open_cl, class="num">64, units_count, output, use_tnets, optimization_type, batch)) class="kw">return class="kw">false; class=class="str">"cmt">//--- Init T-Nets if(use_tnets) { if(!cTNetG) { cTNetG = new CNeuronPointNetOCL(); if(!cTNetG) class="kw">return class="kw">false; } if(!cTNetG.Init(class="num">0, class="num">0, OpenCL, window, units_count, window * window, class="kw">false, optimization, iBatch)) class="kw">return class="kw">false; if(!cTurnedG) { cTurnedG = new CNeuronBaseOCL(); if(!cTurned1)