English Русский Español Deutsch 日本語 Português
preview
交易中的神经网络:受控分段

交易中的神经网络:受控分段

MetaTrader 5交易系统 |
238 0
Dmitriy Gizlyk
Dmitriy Gizlyk

概述

引导分段任务需要基于目标对象的自然语言描述隔离点云中的特定区域。为了解决该任务,模型针对复杂、细粒度的语义依赖关系执行详细分析,并生成目标对象的逐点掩码。论文《RefMask3D:3D 引用分段的语言引导变换器》讲述了一个高效而全面的框架,即广泛利用语言信息。所提议 RefMask3D 方法强化了多模态交互和理解能力。

作者建议使用早期特征编码来提取丰富的多模态上下文。为此,他们引入了几何增强组词注意力模块,其能在特征编码的每个阶段,实现自然语言对象描述和局部点组(子云)之间的跨模态注意力。这种集成不仅降低了常由点云稀疏和不规则性引起的直接“点-字”关联的噪声,而且还利用了云内的几何关系和精细的结构细节。这显著提升了模型处理语言和几何数据的能力。

此外,作者还加入了一个可学习的“背景”词元,防止不相关的语言特征与局部组特征纠缠在一起。这种机制确保点-级表征依据相关的语义语言信息加以丰富,维护点云内跨越每个组或对象的相应语言上下文的连续性、和上下文感知的一致性。

通过结合计算机视觉和自然语言处理功能,作者开发了一套在解码器中识别目标对象的有效策略,称为语言原语构造LPC)。该策略涉及初始化一组多样化原语,每个都旨在表示特定的语义属性,诸如形状、颜色、大小、关系、位置、等等。经由与相关语言信息的交互,这些原语获得了相应的属性。

在解码器内使用语义丰富的原语强化了模型对点云多样化语义的关注,从而显著改善了其准确定位、及识别目标对象的能力。

为了收集整体信息,并生成对象嵌入,RefMask3D 框架引入了对象聚类模块(OCM)。语言基元用于强调点云中与其语义属性相关联的特定部分。然而,终极目标是基于所提供描述识别目标对象。这需要对语言有综合的理解。这是通过集成对象聚类模块来达成的。在该模块内,首先分析语言原语之间的关系,以便辨别其核心区域中的共同特征和区别。使用该信息,初始化基于自然语言的查询。这允许我们捕获这些共同特征,为目标对象识别形成本质性的最终嵌入。

所提议对象聚类模块,在令模型达成更深入、更全面地理解语言和视觉信息方面,扮演着至关重要的角色。


1. RefMask3D 算法

RefMask3D 分析初始点云场景,以及期待属性的文本描述,来生成目标对象的逐点掩码。所分析场景由 N 个点组成,每个点包含 3D 坐标数据 P,及描述颜色、形状、和其它属性属性的辅助特征向量 F

最初,是利用文本编码器按文本描述来生成嵌入 Ft。然后由点编码器提取逐点特征,其通过几何强化组-词注意力模块,在观察到的几何形状和文本输入之间建立深度交互。点编码器充当类似于 3D U-Net 的主干。

语言原语构造器生成一组原语 𝒪′,来表示不同的语义属性,利用了信息丰富的语言线索。这增强了模型依靠关注特定语义信号,来准确定位和识别目标对象的能力。

语言原语 𝒪′、多尺度点特征 {𝑭1′,𝑭2′,𝑭3′,𝑭4′},以及语言特征 𝑭t 作为建立在变换器架构上的四层跨模态解码器的输入。

然后,丰富后的语言原语和对象查询 𝒪c 被传递到对象聚类模块(OCM)之中,以便分析原语其中的相互关系,统一它们的语义理解,并提取共同特征。

模态融合模块被部署在视觉和语言模型主干之上。作者把多模态融合集成到了点编码器之中。跨模态特征的早期合并提升了合并过程的效率。几何强化组词注意力机制创新地处理了具有几何相邻点的局部点群(子云)。这种方式减少了来自直接点-词相关性的噪声,并利用了点云内固有的几何关系,提升了模型将语言信息与 3D 结构准确融合的能力。

当一个点缺乏相应的描述性词汇时,原来的跨模态注意力机制往往会陷入卡顿。为了解决这个难题,作者引入了可学习的背景词元。该策略令没有相关文本数据的点能够专注于共享的背景词元嵌入,因此把来自不相关文本关联的失真最小化。

将缺乏语言匹配的点合并到背景对象聚类之中,可进一步降低不相关元素的影响。点特征里的结果按语言为基础的属性细化,据局部质心为中心,不受不相关词汇的影响。背景嵌入是一个可训练参数,捕获数据集的整体分布,并有效地表示原始输入信息。它专门用在注意力计算阶段。经由该机制,模型达成了更精确的跨模态交互,不受不相关语言线索的影响。

大多数现有方法典型情况下依赖于直接来自点云的采样质心。但这种方式的一个紧要局限性是忽视了语言上下文,而这是准确分段的本质。仅从最远点进行采样往往会导致预测偏离真实目标,特别是在稀疏场景中,从而阻碍收敛,并导致漏检。当所选点不能准确表示对象、或与单一描述性词汇挂钩时,这尤其成为问题。为了解决这一点,作者提议语言原语构造,包含语义内容,能够令模型遵照目标相关对象,学习各种语义属性。

这些原语是从不同的高斯分布采样来初始化的。每个分布代表一个不同的语义属性。原语旨在对形状、颜色、大小、材料、关系、和位置等属性进行编码。每个原语聚合特定的语言特征,并提取相应的信息。语言原语旨在表达语义形态。经由变换器解码器通验它们,令其能够提取广泛的语言线索,从而改进以后阶段的对象识别。

每个语言原语都侧重于给定点云中、与其各自语言属性相关的不同语义形态。然而,终极目标仍然是基于所提供文本描述识别出唯一的目标对象。这需要综合理解对象描述的语义解释。为达成这一点,作者采用了对象聚类模块,其中分析语言原语之间的关系,识别跨越它们关键区域的共同和发散特征。这就促进了对所描述对象的更深入理解。自注意力机制用于从语言原语中提取共享特征。在解码期间,引入对象查询作为 查询,经语言原语丰富化的共享特征则充当 键-值。这种配置令解码器能够将来自原语的语言释义合并到对象查询当中,从而有效地识别与目标对象相关的查询,并将其分组到 𝒪c′之中,并达成精准识别。

虽然拟议的对象聚类模块能显著辅助目标对象的识别,但它并未剔除其它部署中可能出现的推理过程歧义。这种歧义可能会导致误报。为了缓解这一点,RefMask3D 的作者实现了对比学习,以便将目标词元与其它东西区分开来。这是通过最大化与正确文本引用的相似性,同时最小化与负面(非目标)配对的相似性来完成的。

下面呈现的是 RefMask3D 方法的可视化。



2. 利用 MQL5 实现

在研究了 RefMask3D 方法的理论层面之后,我们转到文章的实施部分。在这一部分中,我们将利用 MQL5 实现我们对所提议方式的愿景。

在上述中,RefMask3D 方法的作者将复杂的算法切分成若干个功能模块。因此,按相应模块的形式构建我们的相应实现看似是合乎逻辑的。

2.1 几何强化组-词注意力


我们从构造点编码器开始,在原始方法中,它包含几何强化的组-词注意力模块。我们将在一个名为 CNeuronGEGWA 的新类中实现该模块。正如 RefMask3D 的理论概览中提到的,点编码器设计用于 U-Net 样式 主干。相应地,我们选择 CNeuronUShapeAttention 作为父类,其将提供对象所需的基本功能。新类结构如下所示。

class CNeuronGEGWA   :  public CNeuronUShapeAttention
  {
protected:
   CNeuronBaseOCL    cResidual;
   CNeuronMLCrossAttentionMLKV   cCrossAttention;
   CBufferFloat      cTemp;
   bool              bAddNeckGradient;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override { return false; }
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *prevLayer) override { return false; }
   virtual bool      calcInputGradients(CNeuronBaseOCL *prevLayer, CBufferFloat *SecondInput,
                                        CBufferFloat *SecondGradient,
                                        ENUM_ACTIVATION SecondActivation = None) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override { return false; }
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) override;

public:
                     CNeuronGEGWA(void)   :  bAddNeckGradient(false) {};
                    ~CNeuronGEGWA(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint window, uint window_key, uint heads, uint units_count,
                          uint window_kv, uint heads_kv, uint units_count_kv,
                          uint layers, uint inside_bloks,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void)   const   {  return defNeuronGEGWA;   }
   //--- methods for working with files
   virtual bool      Save(int const file_handle);
   virtual bool      Load(int const file_handle);
   //---
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau);
   virtual void      SetOpenCL(COpenCLMy *obj);
   //---
   virtual CNeuronBaseOCL* GetInsideLayer(const int layer) const;
   virtual void      AddNeckGradient(const bool flag) {  bAddNeckGradient = flag; }
  }; 

我们组织 U-Net 主干时的大多数变量和对象大都从父类继承而来。不过,我们引入了构建跨模态注意力机制的额外组件。

所有对象都声明为静态,这允许我们将类构造函数和析构函数留空。继承对象和新添加对象的初始化都在 Init 方法中处理。如您所知,该方法接收参数提供有关正创建对象的所需架构的显式信息。

bool CNeuronGEGWA::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                        uint window, uint window_key, uint heads, uint units_count,
                        uint window_kv, uint heads_kv, uint units_count_kv,
                        uint layers, uint inside_bloks,
                        ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, window * units_count, optimization_type, batch))
      return false;

在方法主体中,我们首先从基础全连接层类 CNeuronBaseOCL 中调用同名方法,其是我们所有神经层对象的终极祖先。

注意,在这种情况下,我们从基类调用方法,而非直接父类。这是由于我们构建 U-Net 主干的某些架构特性。具体来说,在构造“颈部”时,我们使用递归方式创建对象。在该阶段,我们需要利用来自不同类的组件。

随后,我们继续初始化主要注意力,并缩放对象。

   if(!cAttention[0].Init(0, 0, OpenCL, window, window_key, heads, units_count, layers, optimization, iBatch))
      return false;
   if(!cMergeSplit[0].Init(0, 1, OpenCL, 2 * window, 2*window, window, (units_count + 1) / 2, optimization, iBatch))
      return false;

接下来是创建“颈部”的算法。“颈部”对象的类型取决于其大小。通常,我们创建一个与当前类似的对象。我们只是将内部“颈部”的大小减小 “1”。

   if(inside_bloks > 0)
     {
      CNeuronGEGWA *temp = new CNeuronGEGWA();
      if(!temp)
         return false;
      if(!temp.Init(0, 2, OpenCL, window, window_key, heads, (units_count + 1) / 2, window_kv, heads_kv, units_count_kv, layers, inside_bloks - 1, optimization, iBatch))
        {
         delete temp;
         return false;
        }
      cNeck = temp;
     }

对于最后一层,我们用到一个交叉注意力模块。

   else
     {
      CNeuronMLCrossAttentionMLKV *temp = new CNeuronMLCrossAttentionMLKV();
      if(!temp)
         return false;
      if(!temp.Init(0, 2, OpenCL, window, window_key, heads, window_kv, heads_kv, (units_count + 1) / 2, units_count_kv, layers, 1, optimization, iBatch))
        {
         delete temp;
         return false;
        }
      cNeck = temp;
     }

然后我们初始化重新注意力、和反向缩放模块。

   if(!cAttention[1].Init(0, 3, OpenCL, window, window_key, heads, (units_count + 1) / 2, layers, optimization, iBatch))
      return false;
   if(!cMergeSplit[1].Init(0, 4, OpenCL, window, window, 2*window, (units_count + 1) / 2, optimization, iBatch))
      return false;

之后,我们添加一个残差连接层,和一个多模态交叉注意力模块。

   if(!cResidual.Init(0, 5, OpenCL, Neurons(), optimization, iBatch))
      return false;
   if(!cCrossAttention.Init(0, 6, OpenCL, window, window_key, heads, window_kv, heads_kv, units_count, units_count_kv, layers, 1, optimization, iBatch))
      return false;

我们还初始化了一个临时存储数据的辅助缓冲区。

   if(!cTemp.BufferInit(MathMax(cCrossAttention.GetSecondBufferSize(),
                                cAttention[0].Neurons()), 0) ||
      !cTemp.BufferCreate(OpenCL))
      return false;

在初始化方法结束处,我们替换指向数据缓冲区的指针,以便最大程度地减少数据复制操作。

   if(Gradient != cCrossAttention.getGradient())
     {
      if(!SetGradient(cCrossAttention.getGradient(), true))
         return false;
     }
   if(cResidual.getGradient() != cMergeSplit[1].getGradient())
     {
      if(!cResidual.SetGradient(cMergeSplit[1].getGradient(), true))
         return false;
     }
   if(Output != cCrossAttention.getOutput())
     {
      if(!SetOutput(cCrossAttention.getOutput(), true))
         return false;
     }
//---
   return true;
  }

然后,我们返回一个布尔值至调用程序,指示方法操作的执行结果。

新对象初始化工作完成后,我们转到 feedForward 方法中构造前馈算法。与父类不同,我们的新对象需要两个数据源。因此,设计处置单一数据源的继承方法已被覆盖。对比之下,新方法是从头开始编写的。

在方法参数中,我们接收指向两个输入数据对象的指针。不过,在该阶段,不会对其中任何一个执行验证检查。

bool CNeuronGEGWA::feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput)
  {
   if(!cAttention[0].FeedForward(NeuronOCL))
      return false;

首先,我们将指向其中一个数据源的指针传递给主注意力子层中的同名方法。指针验证已由该方法在内部处理。故以,我们只需检查它执行的逻辑结果。然后我们缩放注意力模块的输出。

   if(!cMergeSplit[0].FeedForward(cAttention[0].AsObject()))
      return false;

我们将缩放后的数据,和指向第二个数据源对象的指针传递给“颈部”。

   if(!cNeck.FeedForward(cMergeSplit[0].AsObject(), SecondInput))
      return false;

我们将获得的结果传递给第二个注意力模块,并执行逆数据缩放。

   if(!cAttention[1].FeedForward(cNeck))
      return false;
   if(!cMergeSplit[1].FeedForward(cAttention[1].AsObject()))
      return false;

之后,我们添加残差连接,并执行交叉-模态依赖分析。

   if(!SumAndNormilize(NeuronOCL.getOutput(), cMergeSplit[1].getOutput(), cResidual.getOutput(), 1, false))
      return false;
   if(!cCrossAttention.FeedForward(cResidual.AsObject(), SecondInput))
      return false;
//---
   return true;
  }

在我们开始反向传播方法的研究之前,需要讨论一点。请看下面 RefMask3D 方法可视化的摘录。

此处的关键点是,在解码器中,在可训练原语和点编码器的过渡输出之间执行交叉-模态注意力。这个看似简单的操作实际上意味着需要相应的误差梯度流。当然,我们必须实现相应的接口来支持这一点。在实现统一的 RefMask3D 模块的梯度分派时,我们首先计算解码器梯度,然后计算点编码器的梯度。然而,对于经典的梯度反向传播模型,这种排序将导致从解码器传递的梯度数据丢失。然而,我们认识到,该模块的这种特定用途代表了一种特殊情况。因此,在 calcInputGradients 方法中,我们提供了两种作模式:一种是清除以前存储的梯度(标准行为),另一种是保留它们(对于像这样的特殊情况)。为了启用该功能,我们引入了一个内部标志变量 bAddNeckGradient 和相应的设定器方法 AddNeckGradient

virtual void      AddNeckGradient(const bool flag) {  bAddNeckGradient = flag; }

但我们回到反向传播算法。在 calcInputGradients 方法参数中,我们得到指向 3 个对象的指针,和第二个数据源的激活函数的常量。

bool CNeuronGEGWA::calcInputGradients(CNeuronBaseOCL *prevLayer, CBufferFloat *SecondInput,
                                      CBufferFloat *SecondGradient,
                                      ENUM_ACTIVATION SecondActivation = -1)
  {
   if(!prevLayer)
      return false;

在方法主体中,我们仅检查指向第一个数据源的指针相关性。其余指针在内部层的误差梯度分派方法主体中进行检查。

由于我们实现了指向数据缓冲区的指针替换,因此误差梯度分派算法从跨-模态注意力的内层开始。

   if(!cResidual.calcHiddenGradients(cCrossAttention.AsObject(), SecondInput, SecondGradient, SecondActivation))
      return false;

之后,我们执行误差梯度的缩放。

   if(!cAttention[1].calcHiddenGradients(cMergeSplit[1].AsObject()))
      return false;

然后,我们根据是否需要保留先前累积的误差梯度,来规划算法分支。如果需要保留误差,我们将用第一个数据缩放层的类似梯度缓冲区,替换“颈部”中的误差梯度缓冲区。于此,我们利用以下属性:指定缩放层的输出张量的大小等于“颈部”的大小。稍后我们将把误差梯度传送到这一层。因此,在这种情况下,其操作是安全的。

   if(bAddNeckGradient)
     {
      CBufferFloat *temp = cNeck.getGradient();
      if(!cNeck.SetGradient(cMergeSplit[0].getGradient(), false))
         return false;

接下来,我们调用经典方法获得“颈部”级别的误差梯度。我们将两个信息流的结果相加,并返回指向对象的指针。

      if(!cNeck.calcHiddenGradients(cAttention[1].AsObject()))
         return false;
      if(!SumAndNormilize(cNeck.getGradient(), temp, temp, 1, false, 0, 0, 0, 1))
         return false;
      if(!cNeck.SetGradient(temp, false))
         return false;
     }

在不需要先前累积误差梯度的情况下,我们只需调用标准方法获得误差梯度即可。

   else
      if(!cNeck.calcHiddenGradients(cAttention[1].AsObject()))
         return false;

接下来,我们需要经由“颈部”对象传播误差梯度。这次我们调用经典方法。于此,我们接收第二个数据源的误差梯度,置于临时数据存储缓冲区当中。稍后,我们将要汇总从当前对象和颈部的跨-模态注意力模块获得的数值。

   if(!cMergeSplit[0].calcHiddenGradients(cNeck.AsObject(), SecondInput, GetPointer(cTemp), SecondActivation))
      return false;
   if(!SumAndNormilize(SecondGradient, GetPointer(cTemp), SecondGradient, 1, false, 0, 0, 0, 1))
      return false;

然后,我们将误差梯度传播到第一个输入数据源的级别。

   if(!cAttention[0].calcHiddenGradients(cMergeSplit[0].AsObject()))
      return false;
   if(!prevLayer.calcHiddenGradients(cAttention[0].AsObject()))
      return false;

我们经由激活函数的导数传播残差连接的误差梯度,并汇总来自两个流的信息。

   if(!DeActivation(prevLayer.getOutput(), GetPointer(cTemp), cMergeSplit[1].getGradient(), prevLayer.Activation()))
      return false;
   if(!SumAndNormilize(prevLayer.getGradient(), GetPointer(cTemp), prevLayer.getGradient(), 1, false))
      return false;
//---
   return true;
  }

更新模型参数的 updateInputWeights 方法非常简单。我们调用包含可训练参数的内层相应更新方法。因此,我鼓励您独立探索它们的实现。该类及其所有方法的完整实现均可在附件中找到。

我想补充几句关于创建访问“颈部”对象接口。为了实现该功能,我们创建了 GetInsideLayer 方法。在其参数中,我们将传递所需层的索引。

CNeuronBaseOCL* CNeuronGEGWA::GetInsideLayer(const int layer) const
  {
   if(layer < 0)
      return NULL;

如果获得负索引,则表示发生了错误。在这种情况下,该方法返回一个 NULL 指针。零值表示正在访问当前层。因此,该方法将返回指向“颈部”对象的指针。

   if(layer == 0)
      return cNeck;

否则,颈部必须是相应类的对象,且我们会按需层索引减 1 来递归调用该方法。

   if(!cNeck || cNeck.Type() != Type())
      return NULL;
//---
   CNeuronGEGWA* temp = cNeck;
   return temp.GetInsideLayer(layer - 1);
  }

2.2语言原语构造


在下一步中,我们在 CNeuronLPC 类中创建一个语言原语构造模块的对象。该方法的原始可视化呈现如下。

于此我们注意到与经典交叉注意力模块的相似之处,其建议选择相关的父类。在本例中,我们使用 CNeuronMLCrossAttentionMLKV 交叉注意力对象类。新类结构如下所示。

class CNeuronLPC  :  public CNeuronMLCrossAttentionMLKV
  {
protected:
   CNeuronBaseOCL    cOne;
   CNeuronBaseOCL    cPrimitives;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *Context) override { return feedForward(NeuronOCL); }
   //---
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput, CBufferFloat *SecondGradient, ENUM_ACTIVATION SecondActivation = None) override { return calcInputGradients(NeuronOCL); }
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL, CBufferFloat *Context) override { return updateInputWeights(NeuronOCL); }
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronLPC(void) {};
                    ~CNeuronLPC(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint window, uint window_key, uint heads, uint heads_kv, uint units_count, uint units_count_kv, uint layers, uint layers_to_one_kv, ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void) override  const   {  return defNeuronLPC;   }
   //---
   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;
  };

在前一案例中,我们为前馈和反向传播通验添加了输入数据源。然而,在这种情况下,其另有玄机。尽管交叉注意力模块需要两个数据源,但我们在本实现中仅用一个。这是因为第二个数据源(可训练原语)是在该对象内部生成的。

为了生成这些可训练原语,我们定义了两个内部全连接层对象。这两个对象都声明为静态,允许我们将类构造函数和析构函数留空。这些声明和继承对象的初始化均在 Init 方法中执行。

bool CNeuronLPC::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, 
                      uint window, uint window_key, uint heads, uint heads_kv, 
                      uint units_count, uint units_count_kv, uint layers, 
                      uint layers_to_one_kv, ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronMLCrossAttentionMLKV::Init(numOutputs, myIndex, open_cl, window, window_key, 
                                         heads, window, heads_kv, units_count, units_count_kv, 
                                         layers, layers_to_one_kv, optimization_type, batch))
      return false;

在方法参数中,我们接收常量,令我们能够唯一地确定正创建对象的架构。在方法主体中,我们立即调用父类的相关方法,其中实现了针对所接收参数的控制、及继承对象的初始化。

请注意,我们使用所生成原语的参数作为有关主数据源的信息。

接下来,我们生成一个由一个元素组成的全连接层。

   if(!cOne.Init(window * units_count, 0, OpenCL, 1, optimization, iBatch))
      return false;
   CBufferFloat *out = cOne.getOutput();
   if(!out.BufferInit(1, 1) || !out.BufferWrite())
      return false;

然后我们配以一个原语初始化生成层。

   if(!cPrimitives.Init(0, 1, OpenCL, window * units_count, optimization, iBatch))
      return false;
//---
   return true;
  }

注意,在这种情况下,我们未用到位置编码层。根据原有的逻辑,一些原语负责捕获对象的位置,而另一些则积累其语义属性。

本实现中的 feedForward 方法也非常简单。它取指向输入数据对象的指针作为参数,第一步是验证该指针的有效性。我应当指出,在最近的前馈方法中这样做并不常见。

bool CNeuronLPC::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   if(!NeuronOCL)
      return false;

这样的检查典型情况下由嵌套组件在内部处理。不过,在这种情况下,从外部程序接收的数据能当作上下文。这意味着当我们调用内部对象方法时,我们将需要访问所提供输入对象的嵌套成员。就该原因,我们有义务显式检查传入指针的有效性。

接下来,我们生成特征张量。

   if(bTrain && !cPrimitives.FeedForward(cOne.AsObject()))
      return false;

此处应注意的是,为了减少决策过程的持续时间,该操作仅在训练期间执行。在部署阶段,原始张量维持静态,因此不需要在每次迭代时重新生成。

调用父类 feedForward 方法的前向通验完结。对于该方法,我们将生成的原语张量作为主要数据源传递,并将来自外部程序的上下文信息作为辅助输入。

   if(!CNeuronMLCrossAttentionMLKV::feedForward(cPrimitives.AsObject(), NeuronOCL.getOutput()))
      return false;
//---
   return true;
  }

calcInputGradients 梯度传播方法中,我们按相反的顺序执行前馈通验算法的操作。

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

在此,我们还首先检查所接收指向源数据对象的指针。然后我们调用父类同名方法,在原语和原始上下文之间分派误差梯度。

   if(!CNeuronMLCrossAttentionMLKV::calcInputGradients(cPrimitives.AsObject(), NeuronOCL.getOutput(),
                                                       NeuronOCL.getGradient(),
                                                       (ENUM_ACTIVATION)NeuronOCL.Activation()))
      return false;

之后,我们添加原始多样化的误差梯度。

   if(!DiversityLoss(cPrimitives.AsObject(), iUnits, iWindow, true))
      return false;
//---
   return true;
  }

将误差梯度向下传播到各个层级几乎没有实际价值,故我们省略了该操作。参数更新算法也留待独立探索。您可在附件中找到该类、及其所有方法的完整代码。

RefMask3D 管道中的下一个组件是原始变换器解码器模块,它实现了点云和可学习原语之间的多模态交叉注意力机制。可用我们之前开发的工具来涵盖该功能。故此,我们不会专门为此目的创建新模块。

我们需要实现的另一个模块是对象聚类模块。该模块的算法将在 CNeuronOCM 类中实现。这是一个相当复杂的模块。它结合了两个自注意力模块:一个针对原语,另一个针对语义特征。它们通过交叉注意力模块得以增强。新类结构如下所示。

class CNeuronOCM  :  public CNeuronBaseOCL
  {
protected:
   uint              iPrimWindow;
   uint              iPrimUnits;
   uint              iPrimHeads;
   uint              iContWindow;
   uint              iContUnits;
   uint              iContHeads;
   uint              iWindowKey;
   //---
   CLayer            cQuery;
   CLayer            cKey;
   CLayer            cValue;
   CLayer            cMHAttentionOut;
   CLayer            cAttentionOut;
   CArrayInt         cScores;
   CLayer            cResidual;
   CLayer            cFeedForward;
   //---
   virtual bool      CreateBuffers(void);
   virtual bool      AttentionOut(CNeuronBaseOCL *q, CNeuronBaseOCL *k, CNeuronBaseOCL *v,
                                  const int scores, CNeuronBaseOCL *out,
                                  const int units,
                                  const int heads,
                                  const int units_kv,
                                  const int heads_kv,
                                  const int dimension);
   virtual bool      AttentionInsideGradients(CNeuronBaseOCL *q, CNeuronBaseOCL *k, CNeuronBaseOCL *v,
                                              const int scores, CNeuronBaseOCL *out,
                                              const int units, const int heads,
                                              const int units_kv, const int heads_kv,
                                              const int dimension);
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override { return false; }
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override { return false; }
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override { return false; }

public:
                     CNeuronOCM(void) {};
                    ~CNeuronOCM(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint prim_window, uint window_key, uint prim_units, uint prim_heads,
                          uint cont_window, uint cont_units, uint cont_heads,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void) override   const   {  return defNeuronOCM; }
   //---
   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;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *Primitives, CNeuronBaseOCL *Context);
   virtual bool      calcInputGradients(CNeuronBaseOCL *Primitives, CNeuronBaseOCL *Context);
   virtual bool      updateInputWeights(CNeuronBaseOCL *Primitives, CNeuronBaseOCL *Context);
  };

我相信这很明显,该类中的方法涉及相当复杂的算法。它们中的每一个都需要详细解释。然而,本文的格式有限。因此,为了针对所实现算法提供全面和高品质的概述,我提议在后续文章中继续讨论。该文章还将呈现运用所提议方式在真实世界数据上进行模型测试的结果。


结束语

在本文中,我们探讨了 RefMask3D 方法,其旨在分析复杂的多模态交互和特征理解。该方法作为交易领域的创新具有显而易见的的潜力。通过利用多维数据,它能解释市场行为的当前和历史形态。RefMask3D 采用一系列机制来专注于关键特征,同时最大限度地减少噪声、和不相关输入的影响。

在实践部分,我们开始利用 MQL5 实现所提议概念,并为其中两个建议的模块开发了对象。然而,已完成工作的纵深超出了单篇文章所能合理涵盖的篇幅。因此,我们已开始的工作将会继续。

参考

文章中所用程序

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

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

附加的文件 |
MQL5.zip (1950.24 KB)
算术优化算法(AOA):从AOA到SOA(简单优化算法) 算术优化算法(AOA):从AOA到SOA(简单优化算法)
在本文中,我们介绍了基于简单算术运算(加法、减法、乘法和除法)的算术优化算法(AOA)。这些基本的数学运算是为各种问题寻找最优解的基础。
使用莱文贝格-马夸尔特(Levenberg-Marquardt,LM)算法训练多层感知器 使用莱文贝格-马夸尔特(Levenberg-Marquardt,LM)算法训练多层感知器
本文介绍了一种用于训练前馈神经网络的莱文贝格-马夸尔特(Levenberg-Marquardt,LM)算法的实现。与Python的scikit-learn库中的算法进行性能比较分析。初步探讨更简便的学习方法,如梯度下降、带动量的梯度下降和随机梯度下降。
市场轮廓指标 市场轮廓指标
在本文中,我们将探讨市场轮廓指标。我们将探究这个名称背后隐藏的内容,尝试理解其运行原理,并分析其程序代码(MarketProfile)。
使用 Python 分析天气对农业国家货币的影响 使用 Python 分析天气对农业国家货币的影响
天气与外汇之间有什么关系?传统经济理论长期忽视天气对市场行为的影响。但一切都已改变。让我们尝试找出天气条件与农业货币在市场上的走势之间的联系。