English Русский Español Deutsch 日本語 Português
preview
交易中的神经网络:点云变换器(Pointformer)

交易中的神经网络:点云变换器(Pointformer)

MetaTrader 5交易系统 | 2 六月 2025, 14:49
98 0
Dmitriy Gizlyk
Dmitriy Gizlyk

概述

点云中的物体检测对于很多现世应用都很重要。相比图像,点云提供详细的几何信息,并能有效地捕捉场景结构。然而,它们的不规则性质为高效的特征学习带来了重大挑战。

基于变换器的架构在自然语言处理方面取得了可观的成就。它们在学习上下文相关的表示方面极其有效,并针对输入序列中的长期依赖关系建模。变换器,及其自注意力机制不仅满足了排列不变性的需求,而且还展现出很高的表达能力。无论如何,将百年唤起直接应用到点云计算上是令人望而却步的,在于成本会随着输入规模的增加而呈二次方增加。

为了解决这个障碍,在论文《使用 Pointformer 进行 3D 物体检测》中介绍的方法 Pointformer 的作者提出了一种方法,即在处理集合结构数据时利用变换器模型的优势。Pointformer 采用由多尺度 Pointformer 模块组成的 U-Net 结构。每个 Pointformer 模块都由基于变换器的模块组成,旨在具有高度的表达力,并且非常适合物体检测任务。

在他们的架构解决方案中,方法作者用到三个变换器模块:

  1. 局部变换器LT)针对局部区域内点之间的互动建模。它物体级别学习上下文感知特征。
  2. 局部-全局变换器LGT)有助于集成更高分辨率的局部和全局特征。
  3. 全局变换器GT)在场景级别捕获上下文相关表示。

结果如是,Pointformer 有效地对局部和全局依赖关系建模,显著改进了含有多个杂乱物体的复杂场景中的特征学习性能。



1. Pointformer 算法

在处理点云时,必须考虑其不规则、无序的性质和多变的大小。Pointformer 的作者专为点集操作,设计开发了基于变换器的模块。这些模块不仅强化了局部特征提取的表达能力,而且还将全局上下文信息协同到点表示之中。

Pointformer 模块由三个模型组成:局部变换器LT)、局部-全局变换器LGT)、和全局变换器GT)。每个模块都从 LT 开始,其接收来自前一层的高分辨率输入,并为新的、低分辨率的点集提取特征。接下来,LGT 模块应用多尺度交叉注意力机制来集成两种分辨率的特征。最后,GT 模块捕获场景级别上下文感知表示。对于上采样,作者采用了 PointNet++ 的特征传播模块。

为了构造点云场景的层次化表示,Pointformer 采用了一种高级方法,以不同分辨率构建特征学习模块。最初,最远点采样(FPS) 用于选择当作质心的点子集。对于每个质心,选择指定半径内的围绕点来定义局部邻域。然后将这些局部群组织成序列,并投喂到变换器层。共享的变换器模块将应用于所有局部区域。随着 Pointformer 模块中堆叠的变换器层越来越多,模块的表达力也会提升,从而改进特征表示。

该方法还考虑到相邻点之间的特征相关性。相邻点有时或许提供比质心本身更富含信息的上下文。通过启用局部区域内所有点之间的信息兑换,该模型将平等地对待每个点,从而更有效地提取局部特征。

最远点采样(FPS)广泛用于点云系统,因为它能够在保持输入整体形状的同时产生几乎均匀的样本分布。这确保了原始点云的广泛覆盖,并且质心数量有限。不过,FPS 有两个主要缺点:

  1. 它对异常值很敏感,这可能导致高度不稳定性,尤其是在处理真实世界的点云时。
  2. FPS 选择的点是原始点云的严格子集,这可能会阻碍准确的几何图形重造,尤其是在部分物体遮挡、或稀疏采样对象的情况下。

由于大多数点位于物体的表面,故第二个问题尤为关键。基于采样的生成提案,可能会导致提案品质和实物存在之间性质脱节。

为了克服这些限制,Pointformer 的作者引入了一个基于自注意力映射的坐标细化模块。该模块首先从最终的变换器层中提取每个注意力头的自注意力映射。然后,注意力映射均化。之后,通过对局部区域内的所有点应用注意力加权平均来计算优调的质心坐标。该过程能自适应质心坐标偏移,朝着物体的实际中心靠近。

全局上下文,包括内部物体边界关联、和场景级别信息,对于物体检测任务也很有价值。Pointformer 借助变换器模块的能力来为长范围、非局部的依赖关系建模。特别是,全局变换器模块旨在跨越整个点云传送信息。所有点都收集到一个群中,并用作 GT 模块的初始数据。

在场景级别使用变换器能够捕获上下文敏感的表示,并促进不同物体之间的信息兑换。这些全局表示对于检测仅由几个点表示的物体特别实用。

局部-全局变换器也是将 LTGT 模块提取的局部和全局函数组合在一起的关键模块。LGT 使用多尺度交叉注意力机制来建立低分辨率质心和高分辨率点之间的关系。形式上,它使用 变换器交叉注意力机制。LT 结果当作 查询,而更高分辨率的 GT 输出当作

位置编码是变换器模型的基本组件,提供了一种将位置信息合并到输入序列中的措施。当变换器适应了点云数据时,位置编码变得更加重要,在于点坐标本身具有高度信息量,对于捕获局部几何结构至关重要。

作者对 Pointformer 方法的可视化如下所示。



2. 利用 MQL5 实现

在回顾了 Pointformer 方法的理论层面之后,我们现在转到本文的实践部分,于其中我们利用 MQL5 实现对所提议方法的解释。

直至仔细验证所提议方式之后,我们能注意到与 PointNet++ 方法的一些相似之处。这两种算法都使用最远点采样机制来形成质心。这两种方法的基本操作都基于围绕质心将点群组。这就是为什么我决定使用 CNeuronPointNet2OCL 对象作为父对象来构造新的 CNeuronPointFormer 类。其结构如下所示。

class CNeuronPointFormer   :  public CNeuronPointNet2OCL
  {
protected:
   CNeuronMLMHSparseAttention    caLocalAttention[2];
   CNeuronMLCrossAttentionMLKV   caLocalGlobalAttention[2];
   CNeuronMLMHAttentionMLKV      caGlobalAttention[2];
   CNeuronLearnabledPE           caLocalPE[2];
   CNeuronLearnabledPE           caGlobalPE[2];
   CNeuronBaseOCL                cConcatenate;
   CNeuronConvOCL                cScale;
   //---
   CBufferFloat                 *cbTemp;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override ;
   //---
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronPointFormer(void) {};
                    ~CNeuronPointFormer(void) { delete cbTemp; }
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint window, uint units_count, uint output, bool use_tnets,
                          ENUM_OPTIMIZATION optimization_type, uint batch) override;
   //---
   virtual int       Type(void) override   const   {  return defNeuronPointFormer; }
   //---
   virtual bool      Save(int const file_handle) override;
   virtual bool      Load(int const file_handle) override;
   //--- 
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau) override;
   virtual void      SetOpenCL(COpenCLMy *obj) override;

  };

CNeuronPointNet2OCL 中,我们用了 2 个尺度级别来提取局部特征。在新类中,我们保持了类似的尺度级别,但我们通过使用提议的注意力模块将特征提取的品量提升到一个新的水平。这种改进反映在神经层的内部数组当中,在我们实现新的 CNeuronPointFormer 类方法过程中,其目的变得越发清晰。

在内部组件中,只有一个动态分配的缓冲区,我们需在类的析构函数中正确释放它。类构造函数将保持为空。所有内部对象的初始化都将在 Init 方法中执行,其参数复制自父类。

bool CNeuronPointFormer::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,

                              uint window, uint units_count, uint output, bool use_tnets,
                              ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronPointNet2OCL::Init(numOutputs, myIndex, open_cl, window, units_count, output, 
                                                      use_tnets, optimization_type, batch))
      return false;

在方法主体中,我们首先调用父类相应的方法,其控制接收的参数,并初始化继承的对象。

在父类中,我们创建了两个内部局部子采样层。这些层为输入点云中的每个点输出一个 64-维的特征向量。

在每个局部子采样层之后,我们将按照 Pointformer 方法作者的提议插入注意力模块。两层注意力模块的架构是雷同的,故我们将在一个循环内初始化对象。

   for(int i = 0; i < 2; i++)
     {
      if(!caLocalAttention[i].Init(0, i*5, OpenCL, 64, 16, 4, units_count, 2, optimization, iBatch))
         return false;

首先,我们初始化局部注意力模块,其是使用 CNeuronMLMHSparseAttention 模块实现的。

应当注意的是,我们的方法与原版的 Pointformer 算法略有不同。不过,我们相信它保留了核心逻辑。在 Pointformer 方法中,局部注意力模块配以共享的上下文特征,丰富了局部区域中的每个点,从而将注意力集中在整个对象上。显然,属于同一对象的点表现出很强的相互依赖性。通过使用稀疏注意力,我们不会被约束在固定的局部区域,而是可以强调具有高关系意义的点。这类似于在技术分析中判定支撑位和阻力位,其中价格在不同的历史部分反复与特定阈值相互作用。

接下来,我们初始化局部-全局注意力模块,其将原始数据的细粒度上下文集成到局部对象的特征之中。

      if(!caLocalGlobalAttention[i].Init(0, i*5+1, OpenCL, 64, 16, 4, 64, 2, units_count, 
                                                   units_count, 2, 2, optimization, iBatch))
         return false;

全局注意力模块用于识别场景级别的上下文依赖表示。

      if(!caGlobalAttention[i].Init(0, i*5+2, OpenCL, 64, 16, 4, 2, units_count, 2, 2, optimization, iBatch))
         return false;

当然,我们将添加可训练位置编码的内部层。在此,我们使用单独的位置编码来表示局部和全局。

      if(!caLocalPE[i].Init(0, i*5+3, OpenCL, 64*units_count, optimization, iBatch))
         return false;
      if(!caGlobalPE[i].Init(0, i*5+4, OpenCL, 64*units_count, optimization, iBatch))
         return false;
     }

重点要提及的是,我们没有实现 Pointformer 作者提议的质心坐标细化模块。首先,在实现 PointNet++ 时,我们已将云中的每个点指定为其局部区域的质心。因此,更改点坐标可能会扭曲整个场景。其二,部分优化函数本质上由可训练的位置编码层处理。

有关特征提取尺度的简要说明。初始化的模块本身并没有明确指示不同的特征提取尺度。不过,有两点。在父类中,我们使用不同的半径进行局部子采样。此处,我们将在局部注意力模块中引入不同级别的稀疏性。

   caLocalAttention[0].Sparse(0.1f);
   caLocalAttention[1].Sparse(0.3f);

我们将两个全局注意力级别的结果级联到一个张量之中。

   if(!cConcatenate.Init(0, 10, OpenCL, 128 * units_count, optimization, iBatch))
      return false;

然后,我们将其维数降低到全局点云描述符提取模块的原始数据级别,该模块在父类方法中初始化。

   if(!cScale.Init(0, 11, OpenCL, 128, 128, 64, units_count, 1, optimization, iBatch))
      return false;

在初始化方法的末尾,我们添加创建存储中间数据的缓冲区。

   if(!!cbTemp)
      delete cbTemp;
   cbTemp = new CBufferFloat();
   if(!cbTemp ||
      !cbTemp.BufferInit(caGlobalAttention[0].Neurons(), 0) ||
      !cbTemp.BufferCreate(OpenCL))
      return false;
//---
   return true;
  }

之后,我们将操作的逻辑结果返回给调用程序,并完成该方法。

下一阶段的开发涉及在 feedForward 方法中实现前馈通验算法。与初始化方法不同,于此我们不能完全依赖父类的相应方法。在这新方法中,我们必须集成继承组件和新引入组件涉及的操作。

如前,前馈通验方法接收一个指向输入数据对象的指针作为其参数之一。在方法主体中,我们立即将此指针存储在局部变量之中。

bool CNeuronPointFormer::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
//--- LocalNet
   CNeuronBaseOCL *inputs = NeuronOCL;

典型情况下,除非必要,否则我们避免将传入的指针存储在局部变量当中。然而,在本例中,我们正在实现一种算法,涉及由两个在不同尺度上运行的嵌套特征提取模块顺序进行处理。在该上下文中,搭配局部变量工作可以简化逻辑,在于它允许我们在迭代期间将指针重新分配给不同的对象。

接下来,我们继续创建上述循环。

   for(int i = 0; i < 2; i++)
     {
      if(!cTNetG || i > 0)
        {
         if(!caLocalPointNet[i].FeedForward(inputs))
            return false;
        }

在循环内,我们从局部子采样操作开始,使用在父类中声明和初始化的对象。

重点要记住,父类算法包括一个将输入数据投影到规范空间的选项。该操作仅在第一层局部子采样之前应用。因此,在循环开始时,我们检查是否需要投影。若否,我们直接进行局部子采样步骤。

若需投影,我们首先生成投影矩阵。

      else
        {
         if(!cTurnedG)
            return false;
         if(!cTNetG.FeedForward(inputs))
            return false;

然后我们实现原始数据的投影。

         int window = (int)MathSqrt(cTNetG.Neurons());
         if(IsStopped() ||
            !MatMul(NeuronOCL.getOutput(), cTNetG.getOutput(), cTurnedG.getOutput(), 
                                           NeuronOCL.Neurons() / window, window, window))
            return false;

仅在该步之后,我们才会对局部数据进行子采样。

         if(!caLocalPointNet[i].FeedForward(cTurnedG.AsObject()))
            return false;
        }

局部子采样层的输出直接传递到局部注意力模块之中。

      //--- Local Attention
      if(!caLocalAttention[i].FeedForward(caLocalPointNet[i].AsObject()))
         return false;

重点要注意的是,我们将数据传送道具不注意力模块,没有位置编码。我想提醒您,自注意力机制本质上与输入元素的顺序无关。因此,在局部注意力模块中,我们识别具有强烈相互影响的元素,而不管它们的空间坐标如何。

初看,“没有坐标依赖性的局部注意力”这句话听起来可能违反直觉。毕竟,局部注意力看似意味着一些空间或位置约束。但我们从不同的层面来看它。参考价格图表。我们可以将信息分为两类:坐标和特征。在这个类比中,时间是坐标,而价位代表特征。如果我们删除坐标(时间),我们在特征空间中会留下一个点云。价位出现频率较高的地区自然会具有更高的点密度。这些点在时间上可能相距甚远。迟早,这些区域会对应于支撑位和阻力位。在这个意义上,我们的局部注意力模块在局部特征空间内运行。

在该步骤之后,我们将位置编码应用于局部注意力模块的输出,和局部子采样层的输出。

      //--- Position Encoder
      if(!caLocalPE[i].FeedForward(caLocalAttention[i].AsObject()))
         return false;
      if(!caGlobalPE[i].FeedForward(caLocalPointNet[i].AsObject()))
         return false;

下一步,在局部-全局注意力模块中,我们配以来自全局上下文的信息丰富局部注意力数据,同时参考对象的坐标。

      //--- Local to Global Attention
      if(!caLocalGlobalAttention[i].FeedForward(caLocalPE[i].AsObject(), caGlobalPE[i].getOutput()))
         return false;

而我们的循环操作是由全局注意力模块完成的,其中物体的信息经场景的常见上下文而丰富。

      //--- Global Attention
      if(!caGlobalAttention[i].FeedForward(caLocalGlobalAttention[i].AsObject()))
         return false;
      inputs = caGlobalAttention[i].AsObject();
     }

在转到循环的下一次迭代之前,我们确保将指针改为局部变量中源数据对象。

顺序列举内层的循环成功完成所有迭代之后,我们将所有全局注意力模块的结果级联到一个张量之中。这令我们能够进一步参考不同比例物体的特征。

   if(!Concat(caGlobalAttention[0].getOutput(), caGlobalAttention[1].getOutput(), 
                  cConcatenate.getOutput(), 64, 64, cConcatenate.Neurons() / 128))
      return false;

我们使用缩放层稍微降低级联张量的大小。

   if(!cScale.FeedForward(cConcatenate.AsObject()))
      return false;

然后,我们将收到的数据传递给 CNeuronPointNetOCL 类的前馈方法,它是父类的先祖。它实现了一种生成全局点云描述符的机制。

   if(!CNeuronPointNetOCL::feedForward(cScale.AsObject()))
      return false;
//---
   return true;
  }

不要忘记控制每一步的过程。一旦方法中的所有操作都成功完成,我们将返回一个布尔值,向调用函数指示该结果。

现在我们继续构造反向传播算法。如您所知,这涉及实现两个关键方法:

  • calcInputGradients — 负责基于误差梯度对最终结果的贡献,将误差梯度分派给所有相关分量;
  • updateInputWeights — 负责更新模型的可训练参数。

为了构造第二种方法,我们可以简单地重用前面描述的前馈通验方法的结构。我们仅保留了针对包含可训练参数组件,顺序调用层次化方法。然后,我们将前馈方法的每次调用,替换为相应的参数更新方法。附件中提供了实现的成果,供您审阅。

calcInputGradients 方法的算法需要更仔细的研究。如前,该方法的结构反映了前馈通验的结构,但顺序相反。不过,模型中信息流的并行性质存在若干细微差别。

该方法接收指向上一层对象的指针作为参数,其中接收传播的误差梯度。这些梯度按每个数据元素对模型最终输出的影响,必须呈比例分布。 

bool CNeuronPointFormer::calcInputGradients(CNeuronBaseOCL *NeuronOCL)
  {
   if(!NeuronOCL)
      return false;

在方法主体中,我们立即检查接收指针的相关性。因为如果它无效,则无必要进一步的操作。

需要说明的是,在调用该方法时,层输出的误差梯度已经包含在相应的数据缓冲区当中。故此,我们通过调用祖先类的相应方法,将其传播到内部缩放层。

   if(!CNeuronPointNetOCL::calcInputGradients(cScale.AsObject()))
      return false;

接下来,我们将误差梯度传播到级联数据层。

   if(!cConcatenate.calcHiddenGradients(cScale.AsObject()))
      return false;

然后我们将其分派给相应的全局注意力模块。

   if(!DeConcat(caGlobalAttention[0].getGradient(), caGlobalAttention[1].getGradient(), 
                      cConcatenate.getGradient(), 64, 64, cConcatenate.Neurons() / 128))
      return false;

于此,我们必须始终如一地将误差梯度传递给所有内部层模块。为此,我们创建一个逆向迭代循环。

   CNeuronBaseOCL *inputs = caGlobalAttention[0].AsObject();
   for(int i = 1; i >= 0; i--)
     {
      //--- Global Attention
      if(!caLocalGlobalAttention[i].calcHiddenGradients(caGlobalAttention[i].AsObject()))
         return false;

在该循环中,我们首先定义局部-全局注意力模块级别的误差梯度。然后,我们将其分派至可训练位置编码的各个层。

      if(!caLocalPE[i].calcHiddenGradients(caLocalGlobalAttention[i].AsObject(), caGlobalPE[i].getOutput(),
                                           caGlobalPE[i].getGradient(),
                                           (ENUM_ACTIVATION)caGlobalPE[i].Activation()))
         return false;

之后,我们将误差梯度从相应的位置编码层传送到局部注意力模块和局部子采样层。

      if(!caLocalAttention[i].calcHiddenGradients(caLocalPE[i].AsObject()))
         return false;
      if(!caLocalPointNet[i].calcHiddenGradients(caGlobalPE[i].AsObject()))
         return false;

需要进一步注意的是,局部注意力模块也使用局部子采样层的结果作为输入数据。因此,它必须将其误差梯度部分传播至给定对象。不过,相应的数据缓冲区已经包含来自位置编码层的误差梯度,不应丢失。因此,在从局部注意力模块传递误差梯度之前,我们需要将现有信息保存在一个临时的存储缓冲区当中。

此处需要注意的是,我们特意创建了一个指向数据存储缓存区对象的动态指针。甚至,我们令其大小与局部子采样层的误差梯度缓冲区相等。这允许我们执行指向对象指针的简单交换,从而替代复制数据。

      CBufferFloat *temp = caLocalPointNet[i].getGradient();
      caLocalPointNet[i].SetGradient(cbTemp, false);
      cbTemp = temp;

现在我们可以安全地从局部注意力模块传输误差梯度,而不必担心丢失之前保存的数据。

      if(!caLocalPointNet[i].calcHiddenGradients(caLocalAttention[i].AsObject()))
         return false;
      if(!SumAndNormilize(caLocalPointNet[i].getGradient(), cbTemp, caLocalPointNet[i].getGradient(),
                                                                               64, false, 0, 0, 0, 1))
         return false;

然后,我们将两个数据线程的误差梯度相加。

下一步是将误差梯度传输至源数据级别。但此处也存在细微差别。根据循环迭代,我们将误差梯度传播至前一个内层的全局注意力模块层,或者传播到方法参数中接收到的源数据对象。在后一种情况下,该算法类似于父类方法。但在前者中,我们应该记住,当从模块断连数据以便生成所分析数据云的全局描述符时,我们已经保存了误差梯度。在这种情况下,我们还替换了指向数据缓冲区的指针。出于该原因,它们具有相同的大小。

      if(i > 0)
        {
         temp = inputs.getGradient();
         inputs.SetGradient(cbTemp, false);
         cbTemp = temp;
        }

接下来,我们检查是否需要调整规范空间投影上的误差梯度。若没有这样的需要,我们立即将梯度传递给相应的对象。

      if(!cTNetG || i > 0)
        {
         if(!inputs.calcHiddenGradients(caLocalPointNet[i].AsObject()))
            return false;
        }

不过,若投影至规范空间是在前馈通验期间执行的,那么我们首先将误差梯度传递至投影层模块级别。

      else
        {
         if(!cTurnedG)
            return false;
         if(!cTurnedG.calcHiddenGradients(caLocalPointNet[i].AsObject()))
            return false;

然后,我们在原始数据和投影矩阵之间分派误差梯度。

         int window = (int)MathSqrt(cTNetG.Neurons());
         if(IsStopped() ||
            !MatMulGrad(inputs.getOutput(), inputs.getGradient(), cTNetG.getOutput(), cTNetG.getGradient(), 
                                         cTurnedG.getGradient(), inputs.Neurons() / window, window, window))
            return false;

我们调整投影矩阵的梯度,以便适应与正交矩阵的偏差误差。

         if(!OrthoganalLoss(cTNetG, true))
            return false;

在此,我们还组织了数据缓冲区交换操作,从而保留 2 个数据线程的误差梯度。

         CBufferFloat *temp = inputs.getGradient();
         inputs.SetGradient(cTurnedG.getGradient(), false);
         cTurnedG.SetGradient(temp, false);

我们把误差梯度从投影矩阵生成模块传播到规范空间,直到原始数据的级别。

         if(!inputs.calcHiddenGradients(cTNetG.AsObject()))
            return false;

然后,我们汇总来自 2 条数据线程的初始数据级别的误差梯度。

         if(!SumAndNormilize(inputs.getGradient(), cTurnedG.getGradient(), inputs.getGradient(),
                                                                           1, false, 0, 0, 0, 1))
            return false;
        }

接下来,我们再次判定是否要对来自其它信息线程的误差梯度求和,并将局部变量中的指针替换为源数据对象。然后我们转到循环的下一次迭代。

      if(i > 0)
        {
         if(!SumAndNormilize(inputs.getGradient(), cbTemp, inputs.getGradient(), 64, false, 0, 0, 0, 1))
            return false;
         inputs = caGlobalAttention[i - 1].AsObject();
        }
      else
         inputs = NeuronOCL;
     }
//---
   return true;
  }

所有迭代完成后,我们将一个布尔值返回给调用函数,表明梯度分布操作成功,并结束其执行。

据此,我们完成了新开发的 CNeuronPointFormer 类方法的算法实现概览,该类集成了 Pointformer 方法作者提议的方式。附件中提供了该类及其所有关联方法的完整代码。

现在,我们转到描述集成新类的模型架构。这一次,集成相当简单。如前,新类被合并到处理环境状态信息的编码器模型之中。我们采用与上一篇文章相同的基本架构。模型架构几乎保持不变。我们仅将从父类继承的层类型替换为我们新开发的层类型,同时保留所有其它参数。这种修改不需要对参与者评论者模型的架构、训练算法、或与环境的交互机制进行任何修改。这些组件已重复使用,无需修改。因此,我们不会在本文中详述它们。所有模型的完整架构,以及准备本文时用到的所有程序的完整源代码,都可在附件中找到。


3. 测试

我们已经运作了大量实质性工作,并用 MQL5 实现我们对 Pointformer 作者提议技术的解释。

重点要注意的是,本文中讲述的实现与原版的 Pointformer 算法略有不同。如此这般,我们获得的结果或许与原始研究中报告的结果在某种程度上亦有所不同。

现在,是时候验证我们的实现成果了。如前行事,我们采用 EURUSD 的 2023 年真实历史数据,和 H1 时间帧来训练模型。所有指标参数均按其默认值设置。

最初,我们通过在实时模式下运行智能交易系统 “...\PointFormer\Study.mq5”,执行模型的迭代离线训练。该 EA 不执行任何交易操作。它的逻辑只专注于训练模型。

第一次训练迭代是依据模型训练过程时,从先前研究中收集的数据执行。训练数据的结构和参数保持不变。

然后,我们更新训练数据集,以便更好地反映参与者的当前动作政策。这允许在训练期间更准确地评估其行为,从而更好地调整政策优化方向。为此,我们在策略测试器中启动一个慢速优化模式,采用环境互动智能交易系统 “...\PointFormer\Research.mq5”。

之后,我们重复模型训练过程。

模型的训练、及训练数据集的更新在涵盖若干个轮次内迭代执行。训练结束的一个优秀标志是,数据集更新的最终迭代,所有通验都达成可接受结果。

值得注意的是,个别通验的结果存在微小差异是允许的。这是由于参与者使用了随机政策,这自然涉及到学行为范围内存在一些随机性的动作。随着模型继续训练,这种随机行为通常会减少。不过,如果动作中的一些可变性不会显著影响整体盈利能力,则它仍然是可接受的。

模型经过多次训练】及数据集更新迭代后,我们成功获得了能够在训练和测试数据集上产生盈利的政策。

我们利用 MetaTrader 5 策略测试器评估了训练模型的性能,依据 2024 年 1 月的历史数据运行测试,同时保持所有其它参数不变。测试结果呈现如下。 

在测试期间,经过训练的模型总共执行了 31笔次交易操作,其中一半以盈利平仓。尤其是,与亏损交易相比,最大和平均盈利交易的数值高出近 50%,导致盈利因子为 1.53。尽管在净值曲线中观察到上升趋势,但有限的交易数量令我们无法对模型在较长时间横向范围内的有效性得出任何明确的结论。



结束语

在本文中,我们探讨了 Pointformer 方法,其引入了一种处理点云数据的新架构。所提议算法结合了局部和全局变换器,能够从多维数据中有效地提取局部和全局空间形态。Pointformer 使用注意力机制来处理与空间上下文相关的信息,它支持学习,同时参考每个点的相对重要性。

在本文的实践部分,我们利用 MQL5 语言实现了我们对所提议方式的愿景。我们根据所描述的算法对模型进行了训练和测试。结果证明该方法在分析复杂数据结构方面具有潜力。 

也就是说,重点要认识到需进一步的研究和优化,才能更全面地了解 Pointformer 在财经数据分析背景下的能力。



参考

文章中所用程序

# 发行 类型 说明
1 Research.mq5 智能系统 收集样本的 EA
2 ResearchRealORL.mq5
智能系统
利用 Real ORL方法收集样本的 EA
3 Study.mq5 智能系统 模型训练 EA
4 Test.mq5 智能系统 模型测试 EA
5 Trajectory.mqh 类库 系统状态描述结构
6 NeuroNet.mqh 类库 创建神经网络的类库
7 NeuroNet.cl 函数库 OpenCL 程序代码库

本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/15820

附加的文件 |
MQL5.zip (1770.6 KB)
如何使用MQL5的控件类创建交互式仪表板/面板(第一部分):设置面板 如何使用MQL5的控件类创建交互式仪表板/面板(第一部分):设置面板
在本文中,我们将使用MQL5的控件类创建一个交互式交易仪表板,旨在简化交易操作。该面板包含标题、用于交易、平仓和信息的导航按钮,以及用于执行交易和管理仓位的专用操作按钮。到文章结束时,你将拥有一个基础面板,为未来的扩展做好准备。
交易中的神经网络:点云的层次化特征学习 交易中的神经网络:点云的层次化特征学习
我们继续研究从点云提取特征的算法。在本文中,我们将领略提升 PointNet 方法效率的机制。
您应当知道的 MQL5 向导技术(第 38 部分):布林带 您应当知道的 MQL5 向导技术(第 38 部分):布林带
布林带是一种非常常见的轨道线指标,许多交易者用它来手工下单和平仓。我们,通过考察尽可能多的由它生成的不同信号,来验证该指标,并看看如何在向导汇编的智能系统中运用它们。
Connexus中的正文(第四部分):添加HTTP请求正文 Connexus中的正文(第四部分):添加HTTP请求正文
在本文中,我们探讨了HTTP请求中的正文概念,这对于发送诸如JSON和纯文本之类的数据至关重要。我们讨论并解释了如何正确地使用正文,并结合适当的头部信息。此外,我们还介绍了Connexus库中的ChttpBody类,它将简化对请求正文的处理。