交易中的神经网络:点云的层次化特征学习·进阶篇
🧠

交易中的神经网络:点云的层次化特征学习·进阶篇

(2/3)· PointNet 抓不住局部几何,PointNet++ 如何用嵌套分区把行情点集拆成多分辨率特征

偏理论进阶 第 2/3 篇
很多交易者把 PointNet 当万能点云编码器,却忽略它只用一次 MaxPooling 聚合全局,局部邻域的密度异变完全被抹平。用这种表征做品种筛选,容易在震荡转趋势的边界上给出滞后信号。进阶模型要先补上层次化局部抽象这一课。

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 回测确认稳定性。

MQL5 / C++
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 冲突。

MQL5 / C++
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),可直接定位是缓冲还是算子问题。外汇与贵金属行情下用此类模型做信号提取属高风险,回测与实盘偏差可能显著。

MQL5 / C++
  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 返回路径。

MQL5 / C++
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)
把点集分区交给小布跑
这些分区与最远点采样的诊断,小布盯盘的 AIGC 已内置,打开对应品种页即可看到不同尺度下的局部密度热力,你只管判断形态是否值得跟。

常见问题

卷积沿规则网格以固定步幅扫描,感知域由结构预定;PointNet++ 的邻域球依赖输入点分布与距离量值,质心由最远点采样得出,覆盖更贴合数据密度。
它把每点编码后仅用全局 MaxPooling 合成签名,丢掉了小区域内的几何关系,对异质点密度不敏感,倾向漏掉微观反转先兆。
可以,对应品种页的 AIGC 模块已内置多级别抽象与采样覆盖图,省去你自己写 MQL5 推理脚本的重复劳动。
贵金属分时点常聚簇于成交密集区,最远点采样能强制质心拉开,避免分区重叠失效,让层次化特征在稀疏跳空段也不断档。