English Русский Español Português
preview
交易中的神经网络:基于 ResNeXt 模型的多任务学习

交易中的神经网络:基于 ResNeXt 模型的多任务学习

MetaTrader 5交易系统 |
74 9
Dmitriy Gizlyk
Dmitriy Gizlyk

概述

人工智能的快速发展导致了深度学习方法与数据分析的积极整合,包括金融板块。金融输入具有高维度、异质性、和时态结构,这令传统处理方法的应用变得复杂。同时,深度学习在分析复杂和非结构化数据方面演绎出高效率。

在潮流卷积架构中,有一个特别突出:ResNeXt,该架构在《深度神经网络的聚合残差变换》一文中有所讲述。ResNeXt 具备捕捉局部和全局依赖关系的能力,并有效处理多维数据,同时经由分组卷积降低计算复杂度。

利用深度学习进行金融分析的一个关键领域是多任务学习(MTL)。该方式允许同时解决多个相关任务,提升模型的准确性和普适能力。不同于经典方式中每个模型只定位单一任务,MTL 利用共享数据表示,令模型对市场波动更具韧性,并强化训练过程。该方式对于市场趋势预测、风险评估、和资产估值尤其宝贵,在于金融市场动态变化,且受多种因素影响。

《经由深度学习和 ResNeXt 在金融数据挖掘中的协同优化》的研究中讲述了一个 ResNeXt 架构整合到多任务模型中的框架。该方案为处理时间序列、识别时空形态、及生成准确预测开辟了新可能。ResNeXt 的分组卷积和残差模块加速训练,降低了关键特征丢失的风险,令该方法尤为适用金融分析。

该方式的另一个显著优势是能自动从原产数据中提取有意义特征。传统的金融分析通常需要大量的特征工程,而深度神经网络则能够自主识别关键形态。这在分析多模态金融数据时尤为重要,因为其涉及市场指标、宏观经济报告、和新闻发布、等多个来源。MTL 的灵活性允许动态调整任务权重、及损失函数,提升模型对市场变化的适应性、以及预测准确性。


ResNeXt 架构

ResNeXt 架构基于模块化设计、及分组卷积。其核心是含有残差连接的卷积模块,受两个关键原则管辖:

  • 如果输出特征映射具有相同大小,模块使用雷同的超参数(宽度和滤波器大小)。
  • 如果特征映射大小萎缩,模块宽度会按比例增加。

遵循这些原则可维持所有模型层的计算复杂度大致恒定,简化设计。如此定义一个模板模块就足够了,其它模块则自动生成,确保标准化、更易调优、以及流水线架构分析。

人工神经网络中的标准神经元执行输数据入加权求和,这是卷积层和全连接层的主要运算。这一过程能被划分为三个阶段:拆分、变换、和聚合。ResNeXt 引入了一个更灵活的方式,允许变换函数更复杂,甚至是微模型本身。这就催生了神经网络概念,经由一个新的维度:基数性,扩展了架构的能力。不同于宽度或深度,基数性判定每个模块内独立、复变换的数量。实验表明,增加基数性较之增加深度或宽度更有效,从而在性能和计算效率之间提供更佳平衡。

所有 ResNeXt 模块共享统一的瓶颈结构。其组成有:

  • 一个初始 1×1 卷积层,降低特征维度,
  • 一个主 3×3 卷积层,执行核心数据处理,
  • 最后一个 1×1 卷积层恢复原始维度。

这种设计降低了计算复杂度,同时维持了模型的高表现力。此外,残余连接在训练期间保留梯度,预防梯度消散,这也是深度网络的关键因素。

ResNeXt 的一个重大强化是使用了分组卷积。于此,输入数据被划分为多个独立分组,每个分组由单独的卷积滤波器处理,结果随后被聚合。这减少了模型参数,维持了网络高吞吐量,并提升了计算效率,没有明显的准确性损失。

为了在更改组数时维持稳定的计算复杂度,ResNeXt 调整瓶颈模块的宽度,控制内层的通道数量。这令伸缩模型,且计算开销不至于过份。

基于 ResNeXt 的多任务学习框架代表了一种进步的及内容数据处理方式,跨越各种分析任务的共享特征利用、协作建模。它由三个关键结构化元件组成:

  • 特征提取模块,
  • 共享学习模块,
  • 任务特定的输出层。

该方式将高效的深度学习机制,与金融时间序列相结合,衍生出高预测精度,并适应动态市场条件的模型。

特征提取模块依赖于 ResNeXt 架构,有效捕捉金融数据的局部和全局特征。在多维金融数据中,关键参数是模型中的分组数量。它权衡了详细特征表示,及计算成本。ResNeXT 中的每个分组卷积识别通道分组中的特定形态,然后把它们聚合成统一表示。

送经非线性变换层之后,所提取特征形成了后续多任务学习和任务特定适配的基础。共享学习模块采用一种权重共享机制,将共同特征投射到任务特定的空间之中。这就确保模型能够为每个任务生成独立表示,同时避免干扰,达成高度特征共享效率。基于相关性的任务聚类进一步强化了共享学习机制。

任务特定的输出层由全连接感知器组成,将专业特征投射到最终的预测空间。输出层能够适配每个任务的性质。具体而言,分类任务使用交叉熵损失,而回归任务使用均方误差(MSE)。对于多任务学习,最终损失函数表示为单个任务损失的加权和。

训练在多个阶段发生。最初,模型会针对单个任务进行预训练,以确保专业 MLP 的有效收敛。随后,模型在多任务架构中进行优调,以提升整体性能。优化使用具备动态学习率调整的 Adam 算法。



实现 MQL5 版本

在回顾了基于 ResNeXt 的多任务学习框架的理论层面后,我们开始按我们的诠释实现 MQL5 版本。我们将从构造基础的 ResNeXt 架构模块开始 — 瓶颈模块

瓶颈模块


瓶颈模块由三层卷积层组成,每层都在处理原产数据中发挥关键作用。第一层降低特征维度,拉低后续处理的计算复杂度。

第二卷积层执行主要卷积,提取准确解读数据所需的复杂高级特征。它分析元素间的相互依赖性,识别后期关键的形态。该方式令模型能够适应金融数据中的非线性依赖性,从而提升预测的准确性。

最后一层恢复了原始张量维度,保留了所有重要信息。早期特征提取时沿时间轴降维,通过扩展特征空间得到补偿,这与 ResNeXt 原则一致。

为了稳定训练,每个卷积层都后随批量归一化。它降低内部协变量偏移,并加速收敛。ReLU 激活强化模型的非线性,提升了捕捉复杂依赖度和普适品质的能力。

上述架构在 CNeuronResNeXtBottleneck 对象中实现,其结构如下。

class CNeuronResNeXtBottleneck :  public CNeuronConvOCL
  {
protected:
   CNeuronConvOCL          cProjectionIn;
   CNeuronBatchNormOCL     cNormalizeIn;
   CNeuronTransposeRCDOCL  cTransposeIn;
   CNeuronConvOCL          cFeatureExtraction;
   CNeuronBatchNormOCL     cNormalizeFeature;
   CNeuronTransposeRCDOCL  cTransposeOut;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronResNeXtBottleneck(void){};
                    ~CNeuronResNeXtBottleneck(void){};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint chanels_in, uint chanels_out, uint window,
                          uint step, uint units_count, uint group_size, uint groups,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void)   override const   {  return defNeuronResNeXtBottleneck;   }
   //--- methods for working with files
   virtual bool      Save(int const file_handle) override;
   virtual bool      Load(int const file_handle) override;
   //---
   virtual CLayerDescription* GetLayerInfo(void) override;
   virtual void      SetOpenCL(COpenCLMy *obj) override;
   //---
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau) override;
  };

作为父类,我们用到一个卷积层对象,它执行恢复特征空间的功能。此外,该结构还包含多个内部对象,每个对象都在我们构造的算法中被赋予关键角色。随着我们构建新类的方法,将会详细披露它们的功能。

所有内部对象都声明为静态,允许我们将类构造和析构函数留空。这些声明和继承对象的初始化均在 Init 方法中执行。该方法接收一组常量,明确定义所创建模块的架构。

bool CNeuronResNeXtBottleneck::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                                   uint chanels_in, uint chanels_out, uint window,
                                   uint step, uint units_count, uint group_size, uint groups,
                                   ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   int units_out = ((int)units_count - (int)window + (int)step - 1) / (int)step + 1;
   if(!CNeuronConvOCL::Init(numOutputs, myIndex, open_cl, group_size * groups, group_size * groups,
                                              chanels_out, units_out, 1, optimization_type, batch))
      return false;

在方法主体中,我们通常调用父类的同名方法,其中包含继承对象和接口的初始化算法。然而,在这种情况下,父类作为模块的最终卷积层。其输入接收提取后的特征数据,在处理期间张量的维数或许会发生变化。因此,我们首先在模块输出处判定序列长度,然后才会调用父类方法。

继承对象初始化成功后,我们处理新声明的对象。工作从数据投影模块开始。第一卷积层按所需工作组数量准备原产数据的投影。

//--- Projection In
   uint index = 0;
   if(!cProjectionIn.Init(0, index, OpenCL, chanels_in, chanels_in, group_size * groups,
                                                    units_count, 1, optimization, iBatch))
      return false;
   index++;
   if(!cNormalizeIn.Init(0, index, OpenCL, cProjectionIn.Neurons(), iBatch, optimization))
      return false;
   cNormalizeIn.SetActivationFunction(LReLU);

这些投影随后调用 LReLU 函数进行归一化和激活。

注意这些操作的结果是一个三维张量 [时间, 分组, 维度]。为了实现各个分组的独立处理,我们使用一个 3D 张量置换对象将分组标识符维度移至第一位置。

   index++;
   if(!cTransposeIn.Init(0, index, OpenCL, units_count, groups, group_size, optimization, iBatch))
      return false;
   cTransposeIn.SetActivationFunction((ENUM_ACTIVATION)cNormalizeIn.Activation());

接下来是特征提取模块。于此,我们用到一个卷积层,指定分组数量作为独立序列的数量。这就确保了各个分组的数值不会“混合”。每个分组使用自己的可训练参数矩阵。

//--- Feature Extraction
   index++;
   if(!cFeatureExtraction.Init(0, index, OpenCL, group_size * window, group_size * step, group_size,
                                                           units_out, groups, optimization, iBatch))
      return false;

此外,注意该方法还接收卷积窗口大小、及其在时态维度上的步长、等参数。当把这些参数传递给内部卷积层初始化时,我们将相应的数值乘以分组大小。

卷积层之后,我们加入配备 LReLU 激活函数的批次归一化。

   index++;
   if(!cNormalizeFeature.Init(0, index, OpenCL, cFeatureExtraction.Neurons(), iBatch, optimization))
      return false;
   cNormalizeFeature.SetActivationFunction(LReLU);

最终的特征空间向后投影模块仅由一个 3D 张量置换对象组成,将分组合并至单一序列。如早前所述,实际的数据投影是调用父类继承方法来执行。

//--- Projection Out
   index++;
   if(!cTransposeOut.Init(0, index, OpenCL, groups, units_out, group_size, optimization, iBatch))
      return false;
   cTransposeOut.SetActivationFunction((ENUM_ACTIVATION)cNormalizeFeature.Activation());
//---
   return true;
  }

现在我们只需将操作的逻辑结果返回给调用者,并完结对象初始化方法。

下一步是构建前馈通验算法,该算法在 feedForward 方法中实现。

bool CNeuronResNeXtBottleneck::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
//--- Projection In
   if(!cProjectionIn.FeedForward(NeuronOCL))
      return false;

该方法接收到指向原产数据对象的指针,其会立即传递给数据投影模块第一个内部卷积层中的同名方法。我们不检查指针的有效性,在于该验证已由内部层处理,这令额外的检查变得多余。

投影结果被归一化,并转置为独立的分组表示。

   if(!cNormalizeIn.FeedForward(cProjectionIn.AsObject()))
      return false;
   if(!cTransposeIn.FeedForward(cNormalizeIn.AsObject()))
      return false;

在特征提取模块中,执行分组卷积操作,并将结果归一化。

//--- Feature Extraction
   if(!cFeatureExtraction.FeedForward(cTransposeIn.AsObject()))
      return false;
   if(!cNormalizeFeature.FeedForward(cFeatureExtraction.AsObject()))
      return false;

从单个分组中提取的特征会被重新置换回单一多维序列,并经父类投影到指定的特征空间。

//--- Projection Out
   if(!cTransposeOut.FeedForward(cNormalizeFeature.AsObject()))
      return false;
   return CNeuronConvOCL::feedForward(cTransposeOut.AsObject());
  }

这些操作的逻辑结果会返回至调用程序,并结束方法。

如您所见,前馈算法是线性的。误差梯度传播也是线性的。因此,反向传播方法留待独立研究。该对象及其所有方法的完整代码已在附件中提供。

残余连接模块


ResNeXt 架构为每个瓶颈模块提供残差连接,在反向传播期间,其促进了高效误差梯度传播。这些连接允许模型重用先前提取的特征,提升收敛性,并降低梯度消散的风险。如是结果,模型能被训练到更深层次,而不会显著增加计算成本。

重点要注意,瓶颈模块的输出张量大致维持相同的整体大小,但各个维度或许会有变化。时态步长的减少会由特征空间维度的增加所补偿,保留关键信息,并捕捉长期依赖关系。一个独立的投影模块通过将输入数据映射到所需维度,确保残余连接的正确集成。这可防止不匹配,即使在深度架构中也能维持训练稳定性。

在我们的实现中,我们所创建的模块作为 CNeuronResNeXtResidual 对象,其结构如下所示。

class CNeuronResNeXtResidual:  public CNeuronConvOCL
  {
protected:
   CNeuronTransposeOCL     cTransposeIn;
   CNeuronConvOCL          cProjectionTime;
   CNeuronBatchNormOCL     cNormalizeTime;
   CNeuronTransposeOCL     cTransposeOut;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronResNeXtResidual(void){};
                    ~CNeuronResNeXtResidual(void){};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint chanels_in, uint chanels_out,
                          uint units_in, uint units_out,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void)   override const   {  return defNeuronResNeXtResidual;   }
   //--- methods for working with files
   virtual bool      Save(int const file_handle) override;
   virtual bool      Load(int const file_handle) override;
   //---
   virtual CLayerDescription* GetLayerInfo(void) override;
   virtual void      SetOpenCL(COpenCLMy *obj) override;
   //---
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau) override;
  };

在开发该对象时,我们所用的方式类似于构造瓶颈模块,但针对输入张量的不同维度进行了适配。

在呈现的对象结构中,您能见到若干个嵌套对象 — 它们的功能将在新类方法的实现期间讲述。所有内部对象均声明为静态。这就允许我们将类的构造和析构函数留空。所有对象,包括继承对象的初始化都在 Init 模块中执行,其接收一套定义对象架构的常量。

bool CNeuronResNeXtResidual::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                                  uint chanels_in, uint chanels_out,
                                  uint units_in, uint units_out,
                                  ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronConvOCL::Init(numOutputs, myIndex, open_cl, chanels_in, chanels_in, chanels_out,
                            units_out, 1, optimization_type, batch))
      return false;

在方法内,我们首先调用父类的同名方法,传递必要的参数。正如瓶颈模块,我们用到一个卷积层作为父类。它还处理数据投影到新特征空间。

继承对象和接口初始化成功后,我们转到处置新声明的对象。为了方便协同时态维度工作,我们首先转置输入数据。

   int index=0;
   if(!cTransposeIn.Init(0, index, OpenCL, units_in, chanels_in, optimization, iBatch))
      return false;

接下来,卷积层将单个单元序列投影到指定的维度之中。

   index++;
   if(!cProjectionTime.Init(0, index, OpenCL, units_in, units_in, units_out, chanels_in, 1, optimization, iBatch))
      return false;

结果会被归一化,类似于瓶颈模块。然而,未应用激活函数,在于残差模块必须无损传递所有信息。

   index++;
   if(!cNormalizeTime.Init(0, index, OpenCL, cProjectionTime.Neurons(), iBatch, optimization))
      return false;

然后我们调整特征空间。为此,我们执行逆置换。投影随后由父类处理。

   index++;
   if(!cTransposeOut.Init(0, index, OpenCL, chanels_in, units_out, optimization, iBatch))
      return false;
//---
   return true;
  }

其余所有需要我们做的,就是将操作的逻辑结果返回给调用程序,并完结新对象初始化方法的工作。

接下来,我们转到 feedForward 方法构造前馈通验算法。

bool CNeuronResNeXtResidual::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
//--- Projection Timeline
   if(!cTransposeIn.FeedForward(NeuronOCL))
      return false;

该方法接收到指向包含输入数据的对象指针。该指针会被传递给内部数据置换层,其将数据转换为单位序列。

接下来,我们通过卷积层将单位序列的维度调整到目标尺寸。

   if(!cProjectionTime.FeedForward(cTransposeIn.AsObject()))
      return false;

结果会被归一化。

   if(!cNormalizeTime.FeedForward(cProjectionTime.AsObject()))
      return false;

最后,应用逆置换,并将数据投影到特征空间之中。

//--- Projection Chanels
   if(!cTransposeOut.FeedForward(cNormalizeTime.AsObject()))
      return false;
   return CNeuronConvOCL::feedForward(cTransposeOut.AsObject());
  }

最终投影由父类执行。操作的逻辑结果随后返回给调用程序,完成前馈方法。

前馈通验算法是线性的。因此,在反向传播期间,我们得到一条线性梯度流。故此,反向传播方法留给独立研究,类似于 CNeuronResNeXtBottleneck 对象。这些对象及其所有模块的完整代码已在附件中提供。

ResNeXt 模块


上文,我们创建了表达 ResNeXt 框架两条信息流的独立对象。现在是时候将这些对象合并为一个结构,以实现更高效的数据处理。为此目的,我们创建了 CNeuronResNeXtBlock 对象,作为后续数据处理的主要模块。该对象的结构呈现如下。

class CNeuronResNeXtBlock :  public CNeuronBaseOCL
  {
protected:
   uint                     iChanelsOut;
   CNeuronResNeXtBottleneck cBottleneck;
   CNeuronResNeXtResidual   cResidual;
   CBufferFloat             cBuffer;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronResNeXtBlock(void){};
                    ~CNeuronResNeXtBlock(void){};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint chanels_in, uint chanels_out, uint window,
                          uint step, uint units_count, uint group_size, uint groups,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void)   override const   {  return defNeuronResNeXtBlock;   }
   //--- methods for working with files
   virtual bool      Save(int const file_handle) override;
   virtual bool      Load(int const file_handle) override;
   //---
   virtual CLayerDescription* GetLayerInfo(void) override;
   virtual void      SetOpenCL(COpenCLMy *obj) override;
   //---
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau) override;
  };

在该结构中,我们见到熟悉的对象,和一套标准的虚拟方法,它们都需要我们去覆盖。

所有内部对象都声明为静态,允许我们将类构造和析构函数留空。这些声明和继承对象的初始化均在 Init 方法中执行。其参数结构完全继承自 CNeuronResNeXtBottleneck 对象。

bool CNeuronResNeXtBlock::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                               uint chanels_in, uint chanels_out, uint window,
                               uint step, uint units_count, uint group_size, uint groups,
                               ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   int units_out = ((int)units_count - (int)window + (int)step - 1) / (int)step + 1;
   if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, units_out * chanels_out, optimization_type, batch))
      return false;

在方法主体中,我们首先确判定模块输出处的序列维度,然后初始化从父对象继承的基础接口。

父类初始化方法执行成功后,我们将必要的参数保存到对象的变量当中。

   iChanelsOut = chanels_out;

我们初始化先前构造的信息流的内部对象。

   int index = 0;
   if(!cBottleneck.Init(0, index, OpenCL, chanels_in, chanels_out, window, step, units_count,
                       group_size, groups, optimization, iBatch))
      return false;
   index++;
   if(!cResidual.Init(0, index, OpenCL, chanels_in, chanels_out, units_count, units_out, optimization, iBatch))
      return false;

在模块输出处,我们期望接收两条信息流的数值之和。因此,接收到的误差梯度能够完全传播到两条数据流。为避免不必要的数据复制,交换相应数据缓冲区的指针。

   if(!cResidual.SetGradient(cBottleneck.getGradient(), true))
      return false;
   if(!SetGradient(cBottleneck.getGradient(), true))
      return false;
//---
   return true;
  }

最后,我们返回一个布尔结果至调用程序,并完结初始化方法。

接下来,我们在 feedForward 方法中构建前馈通验算法。此处的一切都十分简单。

bool CNeuronResNeXtBlock::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   if(!cBottleneck.FeedForward(NeuronOCL))
      return false;
   if(!cResidual.FeedForward(NeuronOCL))
      return false;

该方法接收指向输入数据对象的指针,其会被立即传递给两条信息流对象的方法。然后汇总结果,并归一化。

   if(!SumAndNormilize(cBottleneck.getOutput(), cResidual.getOutput(), Output, iChanelsOut, true, 0, 0, 0, 1))
      return false;
//--- result
   return true;
  }

操作的逻辑结果随后返回给调用程序,完成前馈方法。

虽然该结构乍看之下看似简单,但实际上包含了两条信息流,增加了误差梯度分布的复杂性。负责该过程的算法在 calcInputGradients 方法中实现。

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

该方法接收一个指向输入数据对象的指针,其会在前馈通验期间用到。在这种情况下,误差梯度必须根据输入数据对最终模型输出的影响传播。数据仅能传递给有效对象。因此,在继续操作之前,我们首先检查接收指针的相关性。

通过该验证后,误差梯度会经由第一条信息流传播。

   if(!NeuronOCL.calcHiddenGradients(cBottleneck.AsObject()))
      return false;

在将梯度传播到第二条流之前,必须保留先前获得的数据。我们采用了指针交换机制,而非完全复制数据。输入数据误差梯度缓冲区的指针被保存在局部变量之中。

   CBufferFloat *temp = NeuronOCL.getGradient();

接下来,我们检查辅助缓冲区是否与梯度缓冲区的尺寸相匹配。必要时调整。

   if(cBuffer.GetOpenCL() != OpenCL ||
      cBuffer.Total() != temp.Total())
     {
      if(!cBuffer.BufferInitLike(temp))
         return false;
     }

然后其指针被传递给输入数据对象。

   if(!NeuronOCL.SetGradient(GetPointer(cBuffer), false))
      return false;

现在,误差梯度可以安全地经由第二条信息流传播,而不会有数据丢失的风险。

   if(!NeuronOCL.calcHiddenGradients(cResidual.AsObject()))
      return false;

将两条流的值相加,缓冲指针恢复到原始状态。

   if(!SumAndNormilize(temp, NeuronOCL.getGradient(), temp, 1, false, 0, 0, 0, 1))
      return false;
   if(!NeuronOCL.SetGradient(temp, false))
      return false;
//---
   return true;
  }

然后我们将操作的逻辑结果返回给调用者,并完结误差梯度分派方法。

构建 ResNeXt 模块对象方法算法的概览至此完毕。该对象及其所有方法的完整代码已在附件中提供。

本文已达尾声,但我们的工作尚未完成。我们将稍事停顿,并在下一篇文章中继续。



结束语

本文讲述了一个基于 ResNeXt 架构的多任务学习框架,专为处理金融数据而设计。该框架实现了高效的特征提取和处理,优化了高维和时间序列数据环境中的分类和回归任务。

在实践部分,我们构造了 ResNeXt 架构的主要元素。在下一篇文章中,我们将构建多任务学习框架,并评估所实现方法在真实历史数据上的有效性。

参考


文章中所用程序

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

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

附加的文件 |
MQL5.zip (2430.99 KB)
最近评论 | 前往讨论 (9)
Edgar Akhmadeev
Edgar Akhmadeev | 16 1月 2026 在 21:25
Alain Verleyen #:
根据我的经验,能够分享真正有用的东西的交易者从来不会分享任何东西。

是的,他们知道(就像我从 1998 年开始就知道),一个有效的策略在发布后很快就会失效。

这就是为什么论坛上的程序员会分享个人解决方案,而有效的(盈利的)策略却从未发布过。或出售。

lynxntech
lynxntech | 16 1月 2026 在 21:29
Edgar Akhmadeev #:

是的,他们知道(我从 1998 年开始就知道),一个有效的战略一旦传播开来,很快就会失效。

这就是为什么论坛上的程序员会分享各自的解决方案,而有效的(盈利的)策略却从未公布过。或出售。

国家间转账的需求就不算数了吗?)

你怎么能成为这样的系统?

如果你在回调时买入,交易机器人就会一直工作,问题是回调在哪里?

lynxntech
lynxntech | 16 1月 2026 在 21:47
我看过翻译,我绝对不能翻译。
Edgar Akhmadeev
Edgar Akhmadeev | 16 1月 2026 在 22:21
lynxntech #:
我看过译文,我绝对不能翻译

我得承认,我不够聪明,看不懂原文。

"我整晚都在自言自语,他们却听不懂!"(日瓦涅茨基

Vitaly Muzichenko
Vitaly Muzichenko | 17 1月 2026 在 00:40
Edgar Akhmadeev #:
是的,他们知道(我从 1998 年开始就知道),有效的策略一旦传播开来,很快就会失效。

这适用于流动性有限的交易所,但不适用于外汇交易,那里有足够的流动性供所有人使用。

附注:我想起了米哈伊尔,他在莫斯科交易所有一套对冲系统,他分享了这套系统,它很有效,将来也应该有效。一切都取决于个人资本,100 美元在那里什么也做不了。

在这里,每个人都在寻找一个100英镑的系统,每天盈利10%。这就是搜索结果如此糟糕的原因。

斐波那契(Fibonacci)数列在外汇交易中的应用(第一部分):探究价格与时间的关系 斐波那契(Fibonacci)数列在外汇交易中的应用(第一部分):探究价格与时间的关系
市场如何遵循基于斐波那契数列的关系?在斐波那契数列中,每个后续数字都等于前两个数字之和(1, 1, 2, 3, 5, 8, 13, 21……),该数列不仅描述了兔子种群的增长情况。我们将考虑毕达哥拉斯的假设,即世间万物都遵循某种数字关系……
MQL5 MVC模式中表格的视图组件:基础图形元素 MQL5 MVC模式中表格的视图组件:基础图形元素
本文介绍了在MQL5中实现MVC(模型-视图-控制器)范式下表格视图组件时,开发基础图形元素的过程。这是关于视图组件的首篇文章,也是为MetaTrader 5客户端开发表格功能系列文章的第三篇。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
使用 MetaTrader 5 Python 构建类似 MQL5 的交易类 使用 MetaTrader 5 Python 构建类似 MQL5 的交易类
MetaTrader 5 Python 包提供了一种使用 Python 语言为 MetaTrader 5 平台构建交易应用程序的简便方法。虽然它是一个强大而有用的工具,但在创建算法交易解决方案方面,该模块不如 MQL5 编程语言那么容易。在本文中,我们将构建类似于 MQL5 中提供的交易类,以创建类似的语法,使在 Python 中创建交易机器人比在 MQL5 中更容易。