English Русский Español Deutsch 日本語 Português
preview
交易中的神经网络:配备注意力机制(MASAAT)的智代融汇

交易中的神经网络:配备注意力机制(MASAAT)的智代融汇

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

概述

金融工具的投资组合管理是制定投资决策的关键组成部分,旨在经由跨资产间的动态资本配置,提升回报的同时降低风险。金融市场具有高波动性,其中资产价格依赖多种因素,这令构建一个同时满足两个冲突目标:利润最大化、和风险最小化的最佳投资组合变得异常复杂。建立在各种投资原则基础上的传统金融模型,往往被证明在单一市场中有效,但在现代复杂、且动态的市场条件下或许失效。

近年来,机器学习方法在分析非稳态价格序列方面的关注度日益增加。其中,深度学习和强化学习策略在计算金融领域取得了显著成功。然而,金融市场的价格数据属于典型的噪音时间序列,从中提取未来趋势的指示性信号颇具挑战性。

一种颇有前景的方式出现在论文《开发基于注意力的融汇学习框架,进行金融投资组合优化》当中。作者讲述了一个创新的自适应交易框架,整合了注意力机制和时间序列分析(多智代与自适应投资组合优化框架结合注意力机制和时间序列MASAAT)。在该框架内,部署多个智代来观察和分析不同层次粒度资产价格中的方向性变化。目标是实现全面的投资组合再新配重,从而在高度波动的市场中平衡回报与风险。

通过应用不同阈值的方向性走势滤波器捕捉显著的价格变化,智代首先从原汁时间序列中提取趋势特征。这令它们能够从多个角度追踪市场体制的偏转。该方式引入了一种新颖的顺序生成词元的途径,令智代内的横断面注意力(CSA)和时态注意力(TA)模块能够有效捕捉资产相关性、以及时态依赖性。具体而言,在重造特征映射时,CSA 模块中的序列词元基于各自资产特征,优化跨资产的注意力嵌入;而 TA 模块中的词元基于单独时间点,捕捉当前与过去观测之间的相关性。

进而,资产和时态依赖性的信息被整合进时空注意力模块之中。CSATA 的角色划分明确,智代掌握了更丰富的资产趋势洞察,能够基于自身独特视角提议投资组合。最终,不同智代产生的投资组合会被合并为一个新的融汇投资组合,能够动态适应当前市场状况。即使单个智代误解了市场趋势,并产生偏见推荐,MASAAT 框架经由多智代集成,能够自适应地细化最终投资组合,减轻负面后果。


MASAAT 算法

MASAAT 框架采用多向走势滤波器,按不同阈值,跨多尺度感知域捕捉价格的显著波动,从而分析对未来价格走势的潜在影响。这些感知域代表了不同层次的资产价格波动性,令智代能够直观地感知市场动态。通过将 CSA 模块中的资产导向移动特征,和 TA 模块中的时间点导向特征重造为序列词元,多智代 MASAAT 框架从不同尺度并发收集价格变化的空间和时间信息。这促进了识别即将来临的趋势方向和规模。原汁价格序列被直接转化为面向资产和时间导向的特征,随后在 CSATA 模块内进行横断面和时态分析。

值得注意的是,CSATA 模块均基于自注意力编码器构建,其中跨整个词元序列计算注意力分数。这能够公平估算所有资产的相似性,这与卷积神经网络(CNN)不同,卷积神经网络对特征映射中的局部位置结构高度敏感,且依赖核心大小。通过使用明确量化词元相似度的注意力评分,MASAAT 生成的交易信号本质上更具可解读性。经由时空注意力模块,构造资产序列与历史时间点序列之间的映射。该过程生成嵌入,代表每个资产在观察窗口内所有时间点的注意力得分。这些嵌入随后用于提出投资组合分配。投资组合生成器将智代层级的提案整合为修订后的融汇投资组合,从而能够自适应响应演化中的市场条件。

N 为资产数量,M 为可观测市场特征数量,Ma为交易智代数量。对于给定的历史深度,每个智代首先观察覆盖窗口 Tw 的被观测价格特征 𝐏 ∈ RN×M×Tw。然后使用方向走势滤波器提取基于趋势的函数 𝐏DC={𝐏DC,1, 𝐏DC,2,…,𝐏DC,𝐌a} ∈ RMa, 𝐏DC,i ∈ RN×M×Tw。如上所述,方法 𝐏DC,i 针对模块 CSA 被转换至 𝐏DC,i,CSA ∈ RN×MTw,以及针对模块 ТА 的 𝐏DC,i,TA ∈ RTw×NM。这些相互依赖关系随后在变换器编码器中进行分析。类似地,原汁价格序列 𝐏 被转换为 𝐏CSA ∈ RN×MTw 和 𝐏TA ∈ RTw×NM

分析词元依赖性之后,CSATA 模块输出面向资产的嵌入 𝐎CSA ∈ RN×D,以及时间导向嵌入 𝐎TA ∈ RTD,其中 D 是嵌入向量维度。这些嵌入被合并后,用以构造更新的投资组合,并与其它智代的输出进一步整合,从而获得最终依赖性向量 W𝐭 ,并细化投资组合。

交易操作执行后,奖励 rt 会被收集,并存储在经验回放缓冲区 Ď,连同 W𝐭, 𝐏 和 𝐏DC。此外,参与者政策 π 按政策梯度方法从 Ď 的采样迭代更新。

由于高回报通常伴随着更高的风险,多样化投资既关键、又充满挑战。智代必须为异构资产分配相应的权重,从而达成对冲效果。因此,持续学习资产相关性,令智代能够在动荡的市场条件下更有效地管理风险。

趋势特征先转化为词元序列,然后经由自注意力编码器进行相关性分析。优化的注意力向量量化资产之间的相关性,其中拥有相似注意力的资产注意力向量共享相关特征。

超出资产相关性,MASAAT 还调研跨观察窗口的时态相关性,意图在多层次上预测价格趋势。在这种情况下,每个时间点都被视为序列词元,时间点之间的相关性则经由变换器编码器学习。两个注意力向量相似的时间点可卡频率共享可比较的趋势动态。

汇聚来自 CSATA 模块的信息,MASAAT 智代将资产级和时间级注意力评分结合到一起,估算每个资产相对于观察期内各时间点的重要性。每个智代提出的投资组合能够表示为:

其中 𝐕ibiMLP 的可学习参数。

多个智代的输出,各自在不同粒度观察价格波动,然后对应当前金融环境,被整合为融汇投资组合。相比单独智代生成的投资组合,MASAAT 的多智代结构提供了来自多元视角的多个候选投资组合。这大大强化了系统的适应性,尤其是在高度波动的市场中。

下面提供了 MASAAT 框架的原版可视化。




实现 MQL5 版本

讨论过 MASAAT 框架的理论层面之后,我们现在转入本文的实施部分,其中提呈我们对于拟议方式诠释的 MQL5 版本实现。如早前所述,MASAAT 是一个综合性框架。为了维护不同模块之间功能的清晰分离,我们将其设计为由独立对象组成的模块化结构,每个对象负责部分 MASAAT 功能。

我们从趋势检测机制开始。分段线性表示(PLR)层用于时间序列,非常适合识别局部趋势。然而,这有一个局限:之前实现的对象仅能作为单一智代。由于 MASAAT 需要灵活的功能来构建多智代模型,我们需要更具可伸缩的方案。

一个选项是使用包含指向所分析时间序列中多个 PLR 对象的动态数组,按不同的阈值操作。然而,该方式会导致顺序执行,这并非最优。取而代之,我们将开发一个新对象,支持多趋势检测智代的并行运算。为达成这一点,我们首先需要按新的内核扩展 OpenCL 端程序。

OpenCL 端程序扩展


在尝试适配现有 PLR 内核时,我们面临需要用一个阈值向量替代单一阈值参数,每个智代对应一个。这一变化不仅需要修改内核算法,还需要重构依赖对象。为了简化开发,我们创建了新的前向和后向通验内核,部分重用了现有实现的逻辑。

针对前馈通验,我们开发了 PLRMultiAgents 内核。它接收四个数据缓冲区指针。两个缓冲区包含原汁时间序列、和智代特殊阈值。另外两个缓冲区存储分析结果、及趋势逆转标志。

__kernel void PLRMultiAgents(__global const float *inputs,
                             __global float *outputs,
                             __global int *isttp,
                             const int transpose,
                             __global const float *min_step
                            )
  {
   const size_t i = get_global_id(0);
   const size_t lenth = get_global_size(0);
   const size_t v = get_global_id(1);
   const size_t variables = get_global_size(1);
   const size_t a = get_global_id(2);
   const size_t agents = get_global_size(2);

该内核在 3D 任务空间中执行。第一维对应所分析序列的大小。第二维对应于多模序列中单变量序列的数量。第三维对应智代数量。在内核中,每个线程都标识其在所有任务维度中的位置。之后我们判定数据缓冲区中的偏移量。

//--- constants
   const int shift_in = ((bool)transpose ? (i * variables + v) : (v * lenth + i));
   const int step_in = ((bool)transpose ? variables : 1);
   const int shift_ag = a * lenth * variables;

重点要注意,所有智代分析的是相同的多模序列。因此,智代识标识符仅影响结果的缓冲区偏移量和阈值。

初始化以后,内核开始寻找趋势逆转点(极值)。每个流判定当前元素位置是否存在趋势逆转点。所分析时间序列中的极点自动收到趋势逆转点的状态,因为它们是该段落的先验极点。

//--- look for ttp
   float value = IsNaNOrInf(inputs[shift_in], 0);
   bool bttp = false;
   if(i == 0 || i == lenth - 1)
      bttp = true;

至于其它点,算法会向后搜索偏差超过阈值的最近元素。在该过程期间,我们记录所检查间隔内的最小值和最大值。

   else
     {
      float prev = value;
      int prev_pos = i;
      float max_v = value;
      float max_pos = i;
      float min_v = value;
      float min_pos = i;
      while(fmax(fabs(prev - max_v), fabs(prev - min_v)) < min_step[a] && prev_pos > 0)
        {
         prev_pos--;
         prev = IsNaNOrInf(inputs[shift_in - (i - prev_pos) * step_in], 0);
         if(prev >= max_v && (prev - min_v) < min_step[a])
           {
            max_v = prev;
            max_pos = prev_pos;
           }
         if(prev <= min_v && (max_v - prev) < min_step[a])
           {
            min_v = prev;
            min_pos = prev_pos;
           }
        }

按所需偏差前向寻找下一个元素。

      float next = value;
      int next_pos = i;
      while(fmax(fabs(next - max_v), fabs(next - min_v)) < min_step[a] && next_pos < (lenth - 1))
        {
         next_pos++;
         next = IsNaNOrInf(inputs[shift_in + (next_pos - i) * step_in], 0);
         if(next > max_v && (next - min_v) < min_step[a])
           {
            max_v = next;
            max_pos = next_pos;
           }
         if(next < min_v && (max_v - next) < min_step[a])
           {
            min_v = next;
            min_pos = next_pos;
           }
        }

判定当前元素是否符合极值资质。

      if(
         (value >= prev && value > next) ||
         (value > prev && value == next) ||
         (value <= prev && value < next) ||
         (value < prev && value == next)
      )
         if(max_pos == i || min_pos == i)
            bttp = true;
     }

但于此我们要记住,当依据最小所需偏差搜索元素时,我们可从序列的若干个元素中收集数值走廊,形成一个极值停滞期。因此,仅当元素是该走廊中的极值时,才会获得一个标志。如果有若干个元素值相同,我们会将极值标志分配给第一个元素。

我们保存获得的标志,并清除输出缓冲区。同时,我们同步工作组流。

   isttp[shift_in + shift_ag] = (int)bttp;
   outputs[shift_in + shift_ag] = 0;
   barrier(CLK_LOCAL_MEM_FENCE);

后续步骤仅由与已确认趋势逆转的相关线程执行。其余不满足设定条件,实际上完成了操作。

首先我们判定当前极值的位置。为此,我们基于保存的标志,计数所有前导极值直至所分析位置,并将前一个极值的位置从源数据缓冲区保存到局部缓冲区。

//--- calc position
   int pos = -1;
   int prev_in = 0;
   int prev_ttp = 0;
   if(bttp)
     {
      pos = 0;
      for(int p = 0; p < i; p++)
        {
         int current_in = ((bool)transpose ? (p * variables + v) : (v * lenth + p));
         if((bool)isttp[current_in + shift_ag])
           {
            pos++;
            prev_ttp = p;
            prev_in = current_in;
           }
        }
     }

然后计算该区段线性近似的参数。

//--- cacl tendency
   if(pos > 0 && pos < (lenth / 3))
     {
      float sum_x = 0;
      float sum_y = 0;
      float sum_xy = 0;
      float sum_xx = 0;
      int dist = i - prev_ttp;
      for(int p = 0; p < dist; p++)
        {
         float x = (float)(p);
         float y = IsNaNOrInf(inputs[prev_in + p * step_in], 0);
         sum_x += x;
         sum_y += y;
         sum_xy += x * y;
         sum_xx += x * x;
        }
      float slope = IsNaNOrInf((dist * sum_xy - sum_x * sum_y) / (dist > 1 ? (dist * sum_xx - sum_x * sum_x) : 1), 0);
      float intercept = IsNaNOrInf((sum_y - slope * sum_x) / dist, 0);

之后,我们将获得的数值保存到结果缓冲区当中。

      int shift_out = ((bool)transpose ? ((pos - 1) * 3 * variables + v) : (v * lenth + (pos - 1) * 3)) + shift_ag;
      outputs[shift_out] = slope;
      outputs[shift_out + step_in] = intercept;
      outputs[shift_out + 2 * step_in] = ((float)dist) / lenth;
     }

每个得到的区段由三个参数表征:

  • 斜率 — 趋势线角度,
  • 截距 — 数据空间中的线性偏移,
  • 距离 — 区段的归一化长度。

在这种情况下,将序列长度存储为整数并非最佳选项。因为对于模型的有效运行,归一化的数据表示格式是首选。因此,我们将整数区段长度转换为所分析单变量序列长度的分数。为此,我们将区段中的元素数量除以单变量时间序列整个序列中的元素数量。为了不落入整数运算的“陷阱”,我们首先将区段中的元素数量从 int 转换为 float 类型。

此外,我们将为最后一个区段创建一个单独的运算分支。在该阶段,我们还不知道在任何时间点将形成的区段数量。假设在极端情况下(例如小阈值和高波动率),近乎所有元素都或许会发生逆转。虽然不太可能,但这种情况会显著增加数据体量。同时,我们不想丢失数据。

因此,我们从 MQL5 中时间序列表示的先验知识出发,来理解所分析数据的结构:时间靠后的数据位于我们时间序列的开头。我们来更详细地探讨它们。所分析序列末尾的数据发生的历史更久远,因此对后续事件的影响较小。尽管如此我们也不排除这样的依赖性。

因此,为了写入结果,我们所用的数据缓冲区大小,类似于输入时间序列张量的大小。这就允许我们写入比序列长度小 3 倍的区段(3 个元素写入 1 个区段)。我们预计这个数量绰绰有余。不过,若有更多区段,我们会将最后几个区段的数据合并到一个,以避免数据丢失。

   else
     {
      if(pos == (lenth / 3))
        {
         float sum_x = 0;
         float sum_y = 0;
         float sum_xy = 0;
         float sum_xx = 0;
         int dist = lenth - prev_ttp;
         for(int p = 0; p < dist; p++)
           {
            float x = (float)(p);
            float y = IsNaNOrInf(inputs[prev_in + p * step_in], 0);
            sum_x += x;
            sum_y += y;
            sum_xy += x * y;
            sum_xx += x * x;
           }
         float slope = IsNaNOrInf((dist * sum_xy - sum_x * sum_y) / (dist > 1 ? (dist * sum_xx - sum_x * sum_x) : 1),0);
         float intercept = IsNaNOrInf((sum_y - slope * sum_x) / dist, 0);
         int shift_out = ((bool)transpose ? ((pos - 1) * 3 * variables + v) : (v * lenth + (pos - 1) * 3)) + shift_ag;
         outputs[shift_out] = slope;
         outputs[shift_out + step_in] = intercept;
         outputs[shift_out + 2 * step_in] = IsNaNOrInf((float)dist / lenth, 0);
        }
     }
  }

在大多数情况下,我们期待有更少的区段,然后我们在结果缓冲区的最后一个元素里填充零值。

如您所见,我们在前馈通验算法中未使用可训练参数。故此,反向传播通验被简化为误差梯度分布。该功能在 PLRMultiAgentsGradient 内核中实现。

所有智代分析相同的时间序列。因此,所有智代的梯度必须在原汁数据层面进行聚合。给定期待的适中智代数量,我们选择不让内核过于复杂化。取而代之,我们复用单一智代的梯度分布算法。不过,我们还添加了一个环路,从所有智代收集梯度,以及一个参数指定它们的数量。我鼓励您独立探索它们的实现。完整的 OpenCL 程序,包括这些内核,均已在附件中提供。

趋势检测机制对象


完成 OpenCL 端实现后,我们现在转到主函数库,并在对象 CNeuronPLRMultiAgentsOCL 内实现多智代趋势检测算法。正如您或许注意到的,该对象本质上扩展自之前开发的分段式时间序列线性表示(PLR)。这也是我们选择它作为父类的原因。新对象的结构如下所示。

class CNeuronPLRMultiAgentsOCL  :  public CNeuronPLROCL
  {
protected:
   int               iAgents;
   CBufferFloat      cMinDistance;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL);
   //---
   virtual bool      calcInputGradients(CNeuronBaseOCL *prevLayer);

public:
                     CNeuronPLRMultiAgentsOCL(void)  : iAgents(1) {};
                    ~CNeuronPLRMultiAgentsOCL(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint window_in, uint units_count, bool transpose,
                          vector<float> &min_distance,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void)   const   {  return defNeuronPLRMultiAgentsOCL;   }
   //---
   virtual bool      Save(int const file_handle);
   virtual bool      Load(int const file_handle);
   virtual void      SetOpenCL(COpenCLMy *obj);
  };

在该新类中,我们声明一个定义活跃智代数量的常数(iAgents),并设置一个缓冲区,存储所分析时间序列中特征变化的阈值(cMinDistance)。

鉴于所有内部对象都声明为静态,我们就能保持构造和析构函数为空。这些声明和继承对象的初始化均在 Init 方法中执行。

bool CNeuronPLRMultiAgentsOCL::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                                    uint window_in, uint units_count, bool transpose,
                                    vector<float> &min_distance,
                                    ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   iAgents = (int)min_distance.Size();
   if(iAgents <= 0)
      return false;

注意,该方法仅接收阈值向量作为输入。我们并未显式传递智代的数量。该数字由阈值向量本身的大小推导得出。这降低了外部参数数量,并确保阈值参数与缓冲区长度之间的一致性。

在方法内,智代计数保存到内部变量,并验证(正常操作至少需要一个智代)后,我们调用基础对象的初始化方法,建立核心接口。

   if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, window_in * units_count * iAgents, optimization_type, batch))
      return false;

重要的是,我们调用基础对象的 Init 方法,而非直接父对象的那个。这是因为结果缓冲区的大小与智代数量成正比。然而,这需要对继承组件进行更深层次的初始化。

首先,我们将接收到的参数值保存在继承变量之中。

   iVariables = (int)window_in;
   iCount = (int)units_count;
   bTranspose = transpose;

然后我们初始化极值标志缓冲区。

   icIsTTP = OpenCL.AddBuffer(sizeof(int) * Neurons(), CL_MEM_READ_WRITE);
   if(icIsTTP < 0)
      return false;

注意这些标志在每次前馈通验后都会重新计算。它们的大小与结果缓冲区一致。显然,没有必要永久存储它们的数值。因此,缓冲区仅在 OpenCL 关联环境内存中创建。该对象只保留指向它的指针。

接下来我们初始化阈值缓冲区。

   if(!cMinDistance.AssignArray(min_distance) ||
      !cMinDistance.BufferCreate(OpenCL))
      return false;
//---
   return true;
  }

之后,我们将初始化过程的逻辑结果返回给调用程序,完成该方法。

前馈和反向传播方法也被覆盖。然而,它们的唯一功能是调用前面描述的 OpenCL 内核。由于它们的逻辑很直接了当,我们就留给您独立研究。

这标志着多智代趋势检测对象 CNeuronPLRMultiAgentsOCL 的实现完毕。其方法的完整源代码已在附件中提供。

横断面注意力模块(CSA))


一旦我们获得了所分析时间序列的多尺度分段线性表示,每个智代都会处理其所分配的尺度进行深度分析。在 MASAAT 框架下,时间序列由两种预测进行分析:跨资产、和跨时间点。

MASAAT 框架内的时间序列分析由跨资产注意力模块执行,我们以 CNeuronCrossSectionalAnalysis 对象的形式实现。但在往下实现之前,我们先来谈谈 CSA 模块构造算法。

正如 MASAAT 理论章节所诠释,CSA 模块使用自注意力编码器捕捉资产依赖性。我们的函数库已包含了多个编码器实现。然而,这里有个细微差别:在 MASAAT 中,多智代并行工作,每个智代只分析其所分配数据子集内的依赖关系。经过复查,我们能够辨别合适的解决方案。

例如,CNeuronMVMHAttentionMLKV 模块用于独立通道分析,最初是为 InjectTST 框架开发。不错的解决方案。虽然该模块设计用于分析单一资产多尺度上的依赖性,但我们的任务是找出同一尺度内不同资产之间的依赖性。为了适配它,我们首先将三维输入张量沿其前两个轴转置。我们的函数库中已有这样的转置层:CNeuronTransposeRCDOCL

我们已决定了编码器。但在将数据投喂至编码器之前,我们还需要生成资产轨迹嵌入。MASAAT 作者建议使用跨资产共享参数的 MLP。遵循我们的惯例,我们将 MLP 替换为卷积层。具体而言,我们添加一个带有 GELU 激活的单卷积层。第二个 MLP 角色(生成查询实体)由编码器内部处理。

这就是我们 CSA 模块的结构。于其内,我们将依次使用数据转置层、卷积嵌入层、和独立通道分析模块(自注意力编码器)。为了提高效率,我们将卷积层放于转置之前。操作结果不会改变。然而,这会积极影响解决方案的功效。

我们将部分时间序列的表示、以及所分析资产的价格变化投喂至 CSA 模块。由此,随着所分析历史深度的递增,源数据的体量也随之递增。由于 PLR 往往包含许多零填充元素,故能用较小的嵌入。这降低了卷积嵌入层操作后需要转置的张量大小,降低计算开销,并提升性能。

在辨别实现的关键层面之后,我们能够转入构造新的对象 CNeuronCrossSectionalAnalysis。其结构呈现如下。

class CNeuronCrossSectionalAnalysis :  public CNeuronMVMHAttentionMLKV
  {
protected:
   CNeuronConvOCL          cEmbeding;
   CNeuronTransposeRCDOCL  cTransposeRCD;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *prevLayer) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronCrossSectionalAnalysis(void) {};
                    ~CNeuronCrossSectionalAnalysis(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 layers, uint layers_to_one_kv,
                          uint variables, ENUM_OPTIMIZATION optimization_type, uint batch) override;
   //---
   virtual int       Type(void)   const override   {  return defNeuronCrossSectionalAnalysis;   }
   //---
   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 CNeuronCrossSectionalAnalysis::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                                         uint window, uint window_key, uint heads, uint heads_kv,
                                         uint units_count, uint layers, uint layers_to_one_kv, uint variables,
                                         ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronMVMHAttentionMLKV::Init(numOutputs, myIndex, open_cl, window_key, window_key, heads, heads_kv,
                                      variables, layers, layers_to_one_kv, units_count, optimization_type, batch))
      return false;

在方法主体中,如常,我们首先调用父类的相关方法。有一个前提。在实现 CSA 模块功能的同时,我们计划充分利用所有继承方法。在前馈通验内,原汁数据的转置嵌入投喂至父类方法的输入。因此,在调用父类初始化方法时,我们会调整源数据窗口大小,以便匹配嵌入维度,并将所分析序列长度的参数与自变量数量交换。

在父类对象的初始化操作成功完成后,我们依次初始化卷积嵌入层、和数据转置层。

   if(!cEmbeding.Init(0, 0, OpenCL, window, window, window_key, units_count, variables, optimization, iBatch))
      return false;
   cEmbeding.SetActivationFunction(GELU);
   if(!cTransposeRCD.Init(0,1,OpenCL,variables,units_count,window_key,optimization,iBatch))
      return false;

之后,我们强制禁用激活函数,并终止该方法,已有的之前操作逻辑结果返回给调用程序。

   SetActivationFunction(None);
//---
   return true;
  }

接下来,我们在 feedForward 方法里构建 CSA 模块的前馈通验算法。此处的一切都相当简单明了。在方法参数中,我们收到一个指向输入数据对象的指针,立即将其传递给卷积层中同名的方法。

bool CNeuronCrossSectionalAnalysis::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   if(!cEmbeding.FeedForward(NeuronOCL))
      return false;
   if(!cTransposeRCD.FeedForward(cEmbeding.AsObject()))
     return false;
//---
   return CNeuronMVMHAttentionMLKV::feedForward(cTransposeRCD.AsObject());
  }

我们将卷积层的输出转置,并传递给父类的同名方法。该方法完结时将操作的逻辑结果返回给调用程序。

反向传播算法也很简单。因此,我建议您自行探索它。我们完成了对 CNeuron 横断面分析对象的研究。您可在附件中找到所有这些方法的完整代码。

我们的工作日已经结束了。然而,工作尚未完成。我们稍事休息,在下一篇文章中,我们将把这个项目带至逻辑上的结局。



结束语

本文探讨了利用多智代自适应注意力时间序列框架(MASAAT)进行投资组合优化,其利用一组交易智代的融汇,从多个角度分析价格数据。这降低了所生成交易行为中的偏见。每个智代都利用注意力机制进行横断面和时间分析,捕捉资产与时间点之间的相关性,随后通过时空融合模块整合提取信息。

在实际操作部分,我们开始实现自己理解的 MASAAT 的 MQL5 版本,包括多智代趋势检测机制,和横断面注意力模块。在下一篇文章中,我们将继续这项工作,并评估已实现的解决方案在真实历史数据上的表现。


参考


文章中所用程序

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

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

附加的文件 |
MQL5.zip (2222.61 KB)
接受者操作特征(ROC)曲线入门 接受者操作特征(ROC)曲线入门
ROC 曲线是用于评估分类器性能的图形工具。尽管 ROC 图形相对简单,但在实践中使用它们时,仍存在一些常见的误解和误区。本文旨在为那些希望理解分类器性能评估的交易者提供一份关于 ROC 图形的入门介绍。
辩证搜索(DA) 辩证搜索(DA)
本文介绍了辩证算法(DA),这是一种受辩证法哲学概念启发的新的全局优化方法。该算法利用了人口中独特的划分,将其分为投机思想者和实践思想者。测试表明,在低维问题上,性能令人印象深刻,高达 98%,整体效率为 57.95%。本文解释了这些度量,并详细描述了算法和不同类型函数的实验结果。
优化中自定义准则的新方法(第一部分):激活函数示例 优化中自定义准则的新方法(第一部分):激活函数示例
本系列文章首篇将探讨自定义准则的数学原理,重点聚焦神经网络中使用的非线性函数、MQL5实现代码,以及目标导向与校正偏移量的应用。
血液遗传优化算法(BIO) 血液遗传优化算法(BIO)
我向大家介绍我的新种群优化算法——血液遗传优化算法(Blood Inheritance Optimization,BIO),该算法的灵感源自人类血型遗传系统。在该算法中,每个解都有其自身的“血型”,这一血型决定了其进化方式。正如自然界中,孩子的血型是依据特定规则遗传而来,在BIO算法中,新解通过一套遗传与变异机制来获取自身特性。