English Русский Español Português
preview
神经网络在交易中的应用:时间序列的概率预测(编码器)

神经网络在交易中的应用:时间序列的概率预测(编码器)

MetaTrader 5 — 交易系统 |
56 0
Dmitriy Gizlyk
Dmitriy Gizlyk

引言

我们继续探索 K²VAE 框架 — 一种专为高不确定性条件下的时间序列建模而设计的高级架构。该模型基于三个关键思想的综合:潜在空间中的线性动力学(通过 Koopman 算子实现)、自适应误差滤波(使用 KalmanNet)和概率建模(基于变分自编码器)。这种方法使得同时考虑数据中的模式和对这些模式的置信度成为可能。

K²VAE 的主要优势不在于生成预测,而在于构建系统未来状态的概率分布。与传统模型局限于单一最可能情景不同,这种方法能产生一系列可能的结果。此外,这一可能结果区间的宽度取决于模型对当前状态的置信程度。这使得该框架在需要考虑风险和不确定性的领域特别有用,例如金融预测、物流或技术系统管理。

为了理解这种灵活性和适应性是如何实现的,让我们来考察一下该模型的总体架构。K²VAE 架构大致可分为三个主要组件:Patching、Encoder 和 Decoder,每个组件都各司其职,同时与其他组件紧密相连。

  1. Patching 负责准备输入数据并将其映射到潜在表示。
  2. Encoder 则负责从观测到的时间序列 X 中提取隐藏状态 Z。与标准 VAE 模型不同,该模型采用复杂的架构,其中包括:
    • KoopmanNet,它是 Koopman 算子的可训练对应物,用于将隐藏特征的演化建模为线性系统并进行预测;
    • 注意力模块,用于分析重构值与实际值之间的差异,从而能够识别出模型偏离现实的时刻;
    • KalmanNet,它是卡尔曼滤波器的混合神经网络实现,基于注意力控制信号形成不确定性协方差矩阵;
    • VAE 机制根据 KalmanNet 和 KoopmanNet 提供的参数对未来 token(或未来时序片段)进行采样。
  3. Decoder 将隐藏变量转换回观测变量,从而重建时间序列的预测值。同时,为了保持模型的概率性质,Decoder 也被实现为一个可训练的神经网络结构,具有两个输出:均值和方差。这使我们能够完全模拟分布 P(Y|Z ),并考虑预测中的不确定性。

下面展示了作者提供的 K²VAE 框架示意图。

在上一篇文章中,我们重点介绍了基础工作:我们实现了基本的基础设施,确保支持可训练矩阵的重用,并为构建稳健且可扩展的架构奠定了基础。我们创建了用于生成参数的通用类,调试了通过标准反向传播过程更新这些参数的机制,并描述了一种适合进一步扩展组件的结构。因此,我们已为构建更复杂的模块奠定了基础。

下一步合乎逻辑的步骤是开发 Encoder,它执行一项关键功能:将原始时间序列转换为适合线性分析和概率建模的隐藏潜在表示。此外,这一转换并非通过一组简单的层来实现,而是通过一个精心设计的结构来实现。



设计方法探讨

在我们开始使用 MQL5 语言实际实现 Encoder 之前,让我们详细了解一下其架构和设计逻辑。这将不仅有助于我们正确开展后续工作,还有助于我们理解每个模块如何为模型的整体行为做出贡献。

K²VAE 框架中使用的 Encoder 是按照模块化设计构建的,由四个相互连接的组件组成,每个组件在时间序列处理工作流程中执行严格定义的功能。

处理链中的第一个元素是 KoopmanNet — 这是对 Koopman 算子的神经网络解释,其主要任务是近似时间序列中的线性动力学。它以隐藏状态为输入,并预测下一个状态,假设系统的行为可以在某个潜在空间中表示为线性位移。然而,由于现实世界的时间序列很少遵循严格的线性轨迹,因此仅投影到潜在的 KoopmanNet 空间不足以进行可靠的建模。为了使模型不仅能够重现趋势,还能评估自身的准确性,该框架的作者提出了一种非常原创的机制。

除了预测下一个状态外, KoopmanNet 还经过训练,可以重建已经发生的转变 — 本质上,就是根据时间序列中的先前值,追溯地重建轨迹。因此,该模型不仅限于向前预测;它还能回顾过去,以验证其当前参数与过去观测数据的吻合程度。这种双向推理使模型能够计算出预测动态与系统实际状态之间的差异。

正是这种偏差(即 KoopmanNet 计算出的转换值与实际历史值之间的差异)作为评估线性近似质量的基础。因此,该模型获得了自我诊断的能力,并且可以在不脱离其底层线性结构的情况下动态适应变化或不稳定情况。

在 K²VAE 框架的原始实现中,KoopmanNet 模块由两个独立的小型全连接模型(多层感知器)组成,每个模型都对潜在空间中转换的不同方面进行建模:一个负责全局依赖关系,另一个负责局部依赖关系。从理论角度来看,这种方法非常合理,但在实践中,它可能会显得过于僵化和局限 — 尤其是在市场数据波动较大的情况下。

我们提出通过使用一个更通用、更灵活的稀疏专家混合模块 — CNeuronTimeMoESparseExperts,来增强 KoopmanNet。这是 TimeMoE 框架中的一个模块,它实现了专家混合(MoE)原理 — 这是一种机制,其中一组专门的子模型(专家)并行运行,而控制机制则根据输入上下文选择最合适的子模型。

稀疏专家混合模块不仅仅是一种技术实现,它是区分金融时间序列中存在的局部模式和全局模式的逻辑基础。在这个模块内,有两类专家。第一类是一组专门的本地专家,每个专家负责识别短期转变和依赖于上下文的模式。这些专家主要在有限的时间段内分析市场行为,以适应当前的波动性和微观趋势。

第二类是全局专家。它内置于 CNeuronTimeMoESparseExperts 结构中,并作为广义线性 Koopman 算子发挥作用。它的目的是通过识别在整个训练窗口中持续存在的模式来捕捉时间序列的稳定、长期动态。这样,该模型就获得了对数据的双重视角:详细的本地分析和广泛的战略视角。

这种架构不仅使 KoopmanNet 能够预测下一个状态,而且还能够通过追溯导致当前状态的一系列转换来重建先前的轨迹。将重建的跃迁与实际值进行比较,可以让系统评估自身近似的准确性。

KoopmanNet 完成动态重建并预测下一步后,注意力模块就会发挥作用;它的任务不仅仅是比较实际值和模型值,而是直接分析它们之间的偏差。与比较两个数据流的传统交叉注意力机制不同,这种方法专门关注误差张量,识别其中的重复结构和模式。

K²VAE 框架的作者建议将这些偏差视为完整的信息来源,而不是副产品。其理念是,误差本身包含一个信号:它们指示 KoopmanNet 在描述系统时在哪些方面和程度上取得了成功,以及动态的哪些局部特征仍然超出线性模型的范围。注意力机制使得从这些误差中提取控制信号张量成为可能 — 这是一种在下一阶段调整预测的指令。

因此,该模型不再只是简单地将预测值与实际观测值进行比较,而是学会以有意义的方式解释自身的弱点。正是这种方法支撑了 K²VAE 的高度适应性和智能性,使其能够在不断变化、不稳定的环境中有效运行。

作为拟议实施方案的一部分,我们可以使用已在实践中验证且之前已成功应用于时间序列分析的自注意力模块之一。这不仅显著加快了开发进程,还确保了与框架中其他组件的兼容性。该模块能够通过检测不同时间间隔内偏差之间的重复模式和局部依赖关系,识别错误结构中的内部规律。

值得注意的是,在这种情况下,自注意力机制并不像标准 Transformer 那样用于处理原始输入序列,而是用于分析在 KoopmanNet 中重建动态时出现的重建误差。实际上,我们将模型的关注点从数据本身转移到其自身工作的结果上,使系统能够从外部评估自身,并从其错误中提取有用的信息。

然而,在实现这种方法时,会出现一个有趣的细微差别。所分析的历史深度和生成控制信号的规划范围的长度可能不同。在框架的原始版本中,这个问题是通过线性投影来解决的 — 这是一种紧凑的变换,使控制向量能够适配所需的预测时域。

就我们而言,情况要简单一些。由于该模型旨在仅生成单个下一 token,因此我们不需要一系列控制信号,而只需要一个向量。当然,我们也可以使用同样的线性投影。但我们建议采取不同的方法 — 将注意力集中在前面一系列偏差的背景下,关注最后一个标记的错误。

这种方法开辟了新的可能性。我们不把错误视为孤立的量,而是将其与累积的不准确历史联系起来进行分析。这使得注意力机制能够确定过去动态的哪些特定部分对当前错误的影响最大。结果是一个控制向量,它不仅对当前的不准确性做出响应,还反映了其根源,这些根源编码在系统之前的状态中。

这种方法具有许多明显的优势:

  • 目标注意力:模型聚焦于最后一步的错误,而不是在整个过程中分散资源;
  • 计算负载减少:无需为每个未来步骤生成长向量;
  • 更好的可解释性:控制信号变得有意义且适合进行视觉分析和诊断。

最终,我们在 Encoder 中构建了一个灵活的内部控制机制,使模型能够适应自身的弱点,并通过 KalmanNet 和 VAE 更自信地形成下一步的分布。这种方法不仅使架构更加稳健,而且使其行为更加透明,这在市场不确定的情况下尤其重要。

Encoder 架构中的下一个组件是 KalmanNet — 一个充当自适应协方差矩阵生成器的模块。它的任务不仅是传递信息,而且是积极评估从 KoopmanNet 获得的线性预测的置信度。来自注意力模块的控制信号可以作为上游模型可靠性的一种指标。如果偏差很小且持续存在, KalmanNet 会将其解释为高置信度,并相应地收紧协方差分布 — 假设时间序列的后续行为将有很高的概率遵循预测的情景。然而,如果误差中观察到明显的失衡或结构性变化,则该模型反而会扩展协方差矩阵,从而为更多可变、概率性的结果开辟空间。

因此, KalmanNet 有效地充当了置信度指标 — 一种对数据波动敏感的晴雨表。它不仅传递信号,还对信号进行解释,并将细微波动转化为可供后续计算使用的数学表示。

Encoder 的操作由变分采样模块( VAE )完成。未来状态的最终潜在表征正是在这里形成的。然而,抽样并非盲目进行 — 它遵循前几个阶段建立的逻辑。平均值取自 KoopmanNet ,作为预测动态的基础,而离散程度由 KalmanNet 生成的协方差矩阵指定。因此,我们得到的不是一个点预测,而是一个概率模型,该模型既考虑了时间序列的线性结构,又考虑了市场的随机性。

这种方法具有明显的战略优势。它使模型能够在不确定性下保持灵活性,而不局限于单一的发展情景,而是根据具体情况调整其置信度。这使得该框架特别适用于那些基于风险和置信度评估的动态决策比预测后续状态更为重要的应用场景。

在我们的实施中,为了传达不确定性程度和潜在风险水平,我们并未局限于单一预测。相反,我们针对时间序列下一步可能如何演变,抽取了指定数量的可能情景。这些场景中的每一个都对应于 KoopmanNet 和 KalmanNet 形成的共同分布单独生成的轨迹。这种方法不仅可以让我们获得平均估计值,还可以涵盖所有可能的结果,从而揭示该模型预测未来的稳健性。

综合来看, KoopmanNet 、注意力模块、 KalmanNet 和 VAE 之间的交互形成了一个复杂但又出人意料地合乎逻辑的结构,其中每个组件都在构建可靠且适应性强的未来表示中发挥着自己的作用。



对象结构

在详细讨论了 K²VAE Encoder 的架构原则和组件交互逻辑之后,现在是时候着手研究其实际应用了。为了确保模块化、灵活性和代码可读性,我们已将所有关键元素合并到 CNeuronK2VAEEncoder 类的单个对象中,该类继承了 CNeuronBaseOCL 的核心功能。

该对象集成了 Encoder 完整运行所需的全部组件 — 从使用 KoopmanNet 分析时间序列的动态,到在 KalmanNet 中构建未来状态的协方差结构,再到通过 VAE 机制对概率预测进行采样。该对象包含 Koopman 网络、注意力模块和 Kalman 滤波器等专用组件,以及用于变换、线性运算和矩阵计算的技术层。

下面给出 CNeuronK2VAEEncoder 类的结构定义,其中每个组件都在对观察到的序列进行编码的整个过程中执行特定的功能。这种实现方式能够在模型的任何深度层次上进行逐步分析和调试,并且在处理各种架构配置和不同分析深度时,还提供了高级扩展功能。

class CNeuronK2VAEEncoder   :  public CNeuronBaseOCL
  {
protected:
   //--- Koopman
   CNeuronTimeMoESparseExperts   cKoopman;
   CNeuronBaseOCL                cKoopmanPred;
   CNeuronBaseOCL                cKoopmanRest;
   //---
   CNeuronTimeMoEAttention       cAuxiliaryNet;
   //--- Kalman Filter
   CParams              cB;      // Control input matrix B
   CParams              cF;      // State transition matrix F
   CParams              cH;      // Observation matrix H
   CParams              cQ;      // Learnable covariance matrices Q
   CParams              cR;      // Learnable covariance matrices R
   CNeuronBaseOCL       cP;      // Covariance matrices P
   //---
   CNeuronTransposeOCL  cFT;
   CNeuronTransposeOCL  cHT;
   CNeuronTransposeOCL  cQT;
   CNeuronTransposeOCL  cRT;
   CNeuronTransposeOCL  cPT;
   //---
   CNeuronBaseOCL       cQ_QT;
   CNeuronBaseOCL       cR_RT;
   CNeuronBaseOCL       cXPred;
   CNeuronBaseOCL       cF_P;
   CNeuronBaseOCL       cPPred;
   //---
   CNeuronBaseOCL       cP_HT;
   CNeuronBaseOCL       cH_P_HT;
   matrix<double>       mS, mSGrad;
   CNeuronBaseOCL       cSInv;
   CNeuronBaseOCL       cK;
   CNeuronTransposeOCL  cKT;
   CNeuronBaseOCL       cYPred;
   CNeuronBaseOCL       cDeltY;
   CNeuronBaseOCL       cX;
   CNeuronBaseOCL       cK_H;
   CNeuronBaseOCL       cIdifK_H;
   CNeuronTransposeOCL  cIdifK_HT;
   matrix<double>       mP;
   matrix<double>       mPGrad;
   matrix<double>       mNoise;
   matrix<double>       mGrad;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronK2VAEEncoder(void) {};
                    ~CNeuronK2VAEEncoder(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint window, uint window_key, uint units_cross,
                          uint heads, uint layers, uint scenarios,
                          uint experts, uint experts_dimension, uint topK,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual bool      Save(const int file_handle) override;
   virtual bool      Load(const int file_handle) override;
   //---
   virtual int       Type(void)        const                      {  return defNeuronK2VAEEncoder; }
   virtual void      TrainMode(bool flag) override;
   virtual void      SetOpenCL(COpenCLMy *obj);
   //---
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau);
  }; 

在本文介绍的新类的结构中,应特别注意反映整个 Encoder 架构逻辑的关键组件。KoopmanNet 采用了功能强大的 CNeuronTimeMoESparseExperts 模块,将稀疏混合专家的局部灵活性与全局预测器的稳定性相结合。该解决方案能够有效地对时间序列中的短期和持续动态进行建模。

CNeuronTimeMoEAttention 用作注意力模块,负责分析重建误差的结构。它识别偏差中的模式,形成控制特征,以评估 KoopmanNet 预测的置信度。

Kalman 滤波器模块在实现过程中占据着特殊的地位。这里我们看到可训练矩阵 B 、 F 、 H 、 Q 和 R ,它们分别表示为 CParams 类的一个单独实例。这种方法不仅允许对滤波器参数进行集中管理,还能确保在训练过程中这些参数能够得到充分调整。

除了这些组件外,类结构还包含许多辅助对象,这些对象确保了 KalmanNet 的分步逻辑得以正确实现,并能够高效管理 Encoder 中的所有张量运算。在实现类的方法时,我们将更详细地讨论辅助对象的功能。

CNeuronK2VAEEncoder 类的内部组织假定它使用的所有组件都是静态声明的。该解决方案简化了对象的生命周期:我们不需要手动分配和释放内存。因此,该类的构造函数和析构函数可以保持为空,避免给代码增加不必要的逻辑负担。

所有架构设置,包括 KoopmanNet 的配置、注意力参数和 Kalman 滤波器参数,都在 Init 方法中完全处理。这就是对象具体成形的地方:配置注意力参数,指定控制特征的维度,并设置专家数量、输入窗口深度以及其他至关重要的模型特征。

bool CNeuronK2VAEEncoder::Init(uint numOutputs, uint myIndex, COpenCLMy * open_cl,
                               uint window, uint window_key, uint units_cross,
                               uint heads, uint layers, uint scenarios,
                               uint experts, uint experts_dimension, uint topK,
                               ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, window * scenarios, optimization_type, batch))
      return false;

在第一阶段,控制权传递给基类中同名的函数。这使得我们能够利用已实现的机制对输入参数进行基本验证,同时初始化继承的接口和共享组件。这种方法确保所有神经层都具有统一的初始化标准,从而简化了架构的维护和扩展。

内存组织问题值得特别关注。由于在我们的实现中, K²VAE 编码器返回的不是单个向量,而是一组通过采样生成的可能的未来场景,因此结果缓冲区的大小必须相应地调整。

接下来,我们继续初始化 KoopmanNet 模块,在我们的实现中,该模块由 CNeuronTimeMoESparseExperts 模块表示。我们通过指定模型的数量及其维度来初始化稀疏混合专家模块。该组件负责通过模拟 Koopman 算子的行为,从时间序列中提取局部和全局模式。

//--- Koopman
   int index = 0;
   if(!cKoopman.Init(0, index, OpenCL, window, 2 * window, units_cross, 1, experts, topK, optimization, iBatch))
      return false;
   index++;
   if(!cKoopmanPred.Init(0, index, OpenCL, window, optimization, iBatch))
      return false;
   index++;
   if(!cKoopmanRest.Init(0, index, OpenCL, window * (units_cross - 1), optimization, iBatch))
      return false;

接下来,我们初始化两个辅助对象: cKoopmanPred 和 cKoopmanRest 。第一个存储单元将用于存储 KoopmanNet 计算出的预测值,第二个存储单元将用于重建时间序列中已观测到的状态。

下一步是初始化注意力模块,在我们的实现中,它由 cAuxiliaryNet 对象表示。

index++;
if(!cAuxiliaryNet.Init(0, index, OpenCL, window, window_key, 1, window, units_cross - 1,
                                                   heads, layers, optimization, iBatch))
   return false;

然而,新类内部架构的一个关键部分集中在卡尔曼滤波器操作的组织上:这是 Init 方法中实现的大部分计算和最大逻辑量集中的地方。

第一步,我们依次初始化滤波器的可训练参数的方阵:状态转移矩阵 F 、控制输入矩阵 B 、观测矩阵 H ,以及模型误差 Q 和观测误差 R 的协方差矩阵。

//--- Kalman Filter
   index++;
   if(!cB.Init(0, index, OpenCL, window * window, optimization, iBatch) ||
      !cB.Identity(window, window))
      return false;
   index++;
   if(!cF.Init(0, index, OpenCL, window * window, optimization, iBatch) ||
      !cF.Identity(window, window))
      return false;
   index++;
   if(!cH.Init(0, index, OpenCL, window * window, optimization, iBatch) ||
      !cH.Identity(window, window))
      return false;
   index++;
   if(!cQ.Init(0, index, OpenCL, window * window, optimization, iBatch) ||
      !cQ.Identity(window, window))
      return false;
   index++;
   if(!cR.Init(0, index, OpenCL, window * window, optimization, iBatch) ||
      !cR.Identity(window, window))
      return false;
   index++;
   if(!cP.Init(0, index, OpenCL, window * window, optimization, iBatch) ||
      !cP.getOutput().Fill(matrix<float>::Identity(window, window)))
      return false;

初始化这些矩阵时,会指定其维度。接下来,为了确保 Kalman 滤波器在初始训练迭代期间的数值稳定性,所有这些矩阵都用对角线值初始化。这种初始化有助于协方差相关矩阵保持正定,并提高初始训练阶段的数值稳定性。

对角初始化方法不仅使训练的初始阶段更加稳定,还能确保模型对潜在状态的不同组成部分保持一致的敏感性。在处理时间序列数据时,这一点至关重要,因为主导趋势可能会掩盖微弱但重要的信号。

下一步是准备负责转置相应矩阵的对象。这是一个重要且乍一看似乎是技术细节的问题,但它直接关系到整个算法的正确运行。这是因为在 Kalman 滤波器计算过程中,我们不仅需要反复参考矩阵 F 、 H 、 Q 、 R 和 P 本身,还需要参考它们的转置版本。为了确保这一点,我们预先分配并初始化相应的转置对象。

index++;
if(!cFT.Init(0, index, OpenCL, window, window, optimization, iBatch))
   return false;
index++;
if(!cHT.Init(0, index, OpenCL, window, window, optimization, iBatch))
   return false;
index++;
if(!cQT.Init(0, index, OpenCL, window, window, optimization, iBatch))
   return false;
index++;
if(!cRT.Init(0, index, OpenCL, window, window, optimization, iBatch))
   return false;
index++;
if(!cPT.Init(0, index, OpenCL, window, window, optimization, iBatch))
   return false;

因此,实现了转置表示的缓冲形式,这大大加快了计算速度,并简化了滤波算法主要部分的代码结构。此外,这增强了架构的模块化,因为它允许每个过滤元素独立处理,而不会破坏整体执行逻辑。

值得特别关注的是噪声协方差矩阵:Q (过程协方差)和 R (观测协方差)。为了使 Kalman 滤波算法正常工作,这些矩阵必须具有两个关键特性:它们必须关于主对角线对称且为正定矩阵。不满足这些条件可能会导致滤波不稳定、矩阵求逆过程中出现虚值,或者状态估计完全退化。

为了确保这些特性在训练过程中成立,我们采用了一种经过时间检验且广泛使用的技术:我们将矩阵 Q 和 R 表示为每个矩阵与其转置的乘积。除非原始矩阵是奇异的,否则这种表示法会自动使结果矩阵对称且正定。这消除了手动监控对角线元素或引入正则化的需求。

index++;
if(!cQ_QT.Init(0, index, OpenCL, window * window, optimization, iBatch))
   return false;
index++;
if(!cR_RT.Init(0, index, OpenCL, window * window, optimization, iBatch))
   return false;

配置 Encoder 的下一步是初始化用于存储中间计算结果的对象。这些对象在 Kalman 滤波算法的逐步实现中起着关键作用,其中每次计算都基于先前操作的结果。此外,我们将在反向传播过程中使用这些值,以确保误差梯度得到正确分布。

我们首先初始化 cXPred 对象,该对象将存储应用校正步骤之前系统的预测状态。

index++;
if(!cXPred.Init(0, index, OpenCL, window, optimization, iBatch))
   return false;
index++;
if(!cF_P.Init(0, index, OpenCL, window * window, optimization, iBatch))
   return false;
index++;
if(!cPPred.Init(0, index, OpenCL, window * window, optimization, iBatch))
   return false;

接下来,我们初始化对象 cF_P 和 cPPred ,它们分别存储转移矩阵和协方差矩阵的乘积以及预测的协方差。这些数据对于后续评估预测可靠性的操作是必需的。

接下来, cP_HT 和 cH_P_HT模块按顺序配置;它们在计算 S 矩阵(观测预测误差的协方差)时计算中间值。

   index++;
   if(!cP_HT.Init(0, index, OpenCL, window * window, optimization, iBatch))
      return false;
   index++;
   if(!cH_P_HT.Init(0, index, OpenCL, window * window, optimization, iBatch))
      return false;
//---
   mS = mSGrad = matrix<double>::Zeros(window, window);

同时,创建并初始化矩阵 mS 和 mSGrad ;它们将用于存储协方差值本身以及训练期间相应的梯度。

cSInv 对象旨在存储 S 矩阵的逆矩阵,这是计算 Kalman 增益 cK 所必需的;该增益也有自己的转置矩阵 cKT 。

   index++;
   if(!cSInv.Init(0, index, OpenCL, window * window, optimization, iBatch))
      return false;
   index++;
   if(!cK.Init(0, index, OpenCL, window * window, optimization, iBatch))
      return false;
   index++;
   if(!cKT.Init(0, index, OpenCL, window, window, optimization, iBatch))
      return false;
//---
   index++;
   if(!cYPred.Init(0, index, OpenCL, window, optimization, iBatch))
      return false;
//---
   index++;
   if(!cDeltY.Init(0, index, OpenCL, window, optimization, iBatch))
      return false;

接下来,初始化 cYPred块(用于预测观测值)和 cDeltY 块(表示预测值与实际观测值之间的差异)。

特别关注 cX 对象,它存储应用 Kalman 滤波器后的最终校正状态。

   index++;
   if(!cX.Init(0, index, OpenCL, window, optimization, iBatch))
      return false;
//---
   index++;
   if(!cK_H.Init(0, index, OpenCL, window * window, optimization, iBatch))
      return false;
//---
   index++;
   if(!cIdifK_H.Init(0, index, OpenCL, window * window, optimization, iBatch))
      return false;
//---
   index++;
   if(!cIdifK_HT.Init(0, index, OpenCL, window, window, optimization, iBatch))
      return false;

接下来是 cK_H 、 cIdifK_H 和 cIdifK_HT ,它们旨在记录正确误差反向传播和协方差估计更新所需的数学变换的结果。

最后,我们初始化矩阵 mP 和 mPGrad ,以及辅助数组 mNoise 和 mGrad ,它们将用于在采样阶段生成噪声,并在模型训练期间计算梯度。

   mP = mPGrad = mS;
   mNoise = mGrad = matrix<double>::Zeros(scenarios, window);
//---
   return true;
  }

因此,目前正在创建一个完善的计算基础设施,以确保 K²VAE Encoder 中 Kalman 滤波器的稳定和正确运行。在成功初始化所有内部组件后,初始化方法完成,并向调用程序返回一个布尔值结果,指示操作的结果。



前向传播的组织

从初始化存储对象阶段过渡到前向传递方法 feedForward 的描述,我们有效地深入到了 K²VAE Encoder 的计算逻辑的深处。在这里,所有先前准备好的组件被集成到一个单一的系统中。整个结构就像一个协调良好的机制,开始运作,将最初的观察转化为内部表征。

前向传播从 Koopman 模块开始 — 这是模型的一个关键组成部分,用于在训练过程中学习系统的线性动力学。在推理过程中,作为前向传播的一部分,该模块根据正在分析的序列和先前学习到的变化线性结构,生成一系列重构和一个预测状态。因此,它的输出由两个重要部分组成:首先,是对未来状态的预测,完全基于最近的观察和已学习的线性模式;其次,是对先前状态的整个序列的重建,追溯到历史数据分析的深度。

bool CNeuronK2VAEEncoder::feedForward(CNeuronBaseOCL * NeuronOCL)
  {
//--- Koopman
   if(!cKoopman.FeedForward(NeuronOCL))
      return false;
//--- Pred / Rest
   if(!DeConcat(cKoopmanPred.getOutput(), cKoopmanRest.getPrevOutput(), cKoopman.getOutput(),
                cKoopman.GetWindow(), cKoopman.Neurons() - cKoopman.GetWindow(), 1))
      return false;
   if(!Different(NeuronOCL.getOutput(), cKoopmanRest.getPrevOutput(),
                 cKoopmanRest.getOutput(), cKoopman.GetWindow()))
      return false;

这种分离尤为重要:预测结果用于卡尔曼滤波器模块,而残差序列(实际数据与其重建之间的差异)则传递给注意力模块,该模块处理那些不符合线性模型的动态方面。

//--- Rest Attention
   if(!cAuxiliaryNet.FeedForward(cKoopmanRest.AsObject()))
      return false;

接下来,如果模型处于训练模式,则 Kalman 滤波器的所有可训练组件都会被激活。这些是状态转移矩阵、控制输入矩阵和观测矩阵,以及模型误差和测量误差的协方差矩阵。

//--- Kalman Filter
   if(bTrain)
     {
      if(!cB.FeedForward())
         return false;
      if(!cF.FeedForward())
         return false;
      if(!cH.FeedForward())
         return false;
      if(!cQ.FeedForward())
         return false;
      if(!cR.FeedForward())
         return false;
      if(!cFT.FeedForward(cF.AsObject()))
         return false;
      if(!cHT.FeedForward(cH.AsObject()))
         return false;
      if(!cQT.FeedForward(cQ.AsObject()))
         return false;
      if(!cRT.FeedForward(cR.AsObject()))
         return false;

由于矩阵 Q 和 R 必须是正定的才能使滤波器正常工作,因此首先将它们乘以自身的转置来稳定它们。

 if(!MatMul(cQ.getOutput(), cQT.getOutput(), cQ_QT.getOutput(),
            cQT.GetCount(), cQT.GetWindow(), cQT.GetCount(), 1, true))
    return false;
 if(!MatMul(cR.getOutput(), cRT.getOutput(), cR_RT.getOutput(),
            cRT.GetCount(), cRT.GetWindow(), cRT.GetCount(), 1, true))
    return false;
}

所有参数准备就绪后,预测阶段就开始了。首先,通过结合两个信息流(输入数据和注意力模块的输出)来计算下一步的状态预测。使用可训练参数矩阵 F 和 B 对这些向量进行投影,然后将它们相加。因此,形成了中间态 XPred 。

//--- Prediction step
   if(!MatMul(NeuronOCL.getOutput(), cFT.getOutput(), cXPred.getGradient(),
              1, cFT.GetWindow(), cFT.GetCount(), 1, true))
      return false;
   if(!MatMul(cAuxiliaryNet.getOutput(), cB.getOutput(), cXPred.getPrevOutput(),
              1, cFT.GetWindow(), cFT.GetCount(), 1, true))
      return false;
   if(!SumAndNormilize(cXPred.getGradient(), cXPred.getPrevOutput(), cXPred.getOutput(),
                       cFT.GetCount(), false, 0, 0, 0, 1))
      return false;

接下来,计算状态误差的预测协方差。为此,将转移矩阵和当前协方差矩阵依次相乘,然后加上模型噪声矩阵 Q ,从而生成预测协方差 P_pred 。

if(!MatMul(cF.getOutput(), cP.getOutput(), cF_P.getOutput(),
           cFT.GetCount(), cFT.GetWindow(), cP.Neurons() / cFT.GetWindow(), 1, true))
   return false;
if(!MatMul(cF_P.getOutput(), cFT.getOutput(), cPPred.getOutput(),
           cFT.GetCount(), cFT.GetWindow(), cFT.GetCount(), 1, true))
   return false;
if(!SumAndNormilize(cPPred.getOutput(), cQ_QT.getOutput(), cPPred.getOutput(),
                    cHT.GetWindow(), false, 0, 0, 0, 1))
   return false;

在这个阶段,预测调整开始 — 这正是 Kalman 滤波器如此受重视的原因。首先,计算测量误差的协方差,形成矩阵 S ,表示观测方差和预测方差之和。

//--- Update step
   if(!MatMul(cPPred.getOutput(), cHT.getOutput(), cP_HT.getOutput(),
              cPPred.Neurons() / cHT.GetWindow(), cHT.GetWindow(), cHT.GetCount(), 1, true))
      return false;
   if(!MatMul(cH.getOutput(), cP_HT.getOutput(), cH_P_HT.getOutput(),
              cHT.GetCount(), cHT.GetWindow(), cHT.GetCount(), 1, true))
      return false;
   if(!SumAndNormilize(cH_P_HT.getOutput(), cR_RT.getOutput(), cR_RT.getPrevOutput(),
                       cRT.GetWindow(), false, 0, 0, 0, 1))
      return false;

为了计算 Kalman 增益 K (在状态校正中用作加权系数),需要使用矩阵 S 的逆。我们决定不从头开始构建一个矩阵求逆算法。相反,我们使用了 MQL5 中现有的矩阵运算功能。如有必要,对矩阵进行稳定化、对角化处理,并进行重构以避免出现奇异性。

if(cR_RT.getPrevOutput().GetData(mSGrad) <= 0)
   return false;
mS = mSGrad.Inv();
if(mS.Rows() == 0)
  {
   mSGrad = mSGrad + mSGrad.Transpose();
   vector<double> eigvals;
   matrix<double> eigvecs;
   if(!mSGrad.Eig(eigvecs, eigvals))
      return false;
   if(eigvals.Size() > 0)
     {
      if(!eigvals.Clip(1e-6, DBL_MAX))
         return false;
      mSGrad = matrix<double>::Zeros(eigvals.Size(), eigvals.Size());
      mSGrad.Diag(eigvals);
      mSGrad = eigvecs.MatMul(mSGrad.MatMul(eigvecs.Transpose()));
      mSGrad = mSGrad + mSGrad.Transpose();
      mS = mSGrad.Inv();
     }
   if(mS.Rows() == 0)
     {
      mSGrad.Identity();
      mS = mSGrad;
     }
  }
cSInv.getOutput().Fill(mS);
if(!MatMul(cP_HT.getOutput(), cSInv.getOutput(), cK.getOutput(),
           cHT.GetCount(), (int)mS.Rows(), (int)mS.Cols(), 1, true))
   return false;

在测量更新阶段,模型不是基于实际观测值来调整先前预测的状态,而是基于从 KoopmanNet 获得的另一种预测来调整。首先,通过将预测状态 XPred 乘以转置观测矩阵 H 来计算观测的期望值。该值( YPred )反映了如果模型在预测中没有出现任何错误,观测值将会是多少。

//--- Measurement update
   if(!MatMul(cXPred.getOutput(), cHT.getOutput(), cYPred.getOutput(),
              1, cHT.GetWindow(), cHT.GetCount(), 1, true))
      return false;
   if(!Different(cKoopmanPred.getOutput(), cYPred.getOutput(), cDeltY.getOutput(),
                 1, 0, 0, 0, 1))
      return false;
   if(!MatMul(cDeltY.getOutput(), cK.getOutput(), cX.getPrevOutput(), 1,
              cDeltY.Neurons(), cX.Neurons(), 1, true))
      return false;
   if(!SumAndNormilize(cX.getPrevOutput(), cXPred.getOutput(), cX.getOutput(), 1, false, 0, 0, 0, 1))
      return false;

两种模型预测结果之间的差异被视为误差。将此误差乘以 Kalman 增益 K ,我们得到一个校正向量,将其添加到初始预测 XPred 中,从而得到校正后的状态 X。

同时,采用 Joseph 稳定形式更新状态协方差矩阵 P ,这有助于防止数值误差的累积并保持对称性。

//--- Joseph stabilized form for P
   if(!MatMul(cK.getOutput(), cH.getOutput(), cK_H.getOutput(), cKT.GetCount(),
              cKT.GetWindow(), cHT.GetWindow(), 1, true))
      return false;
   if(!IdentDifferent(cK_H.getOutput(), cIdifK_H.getOutput(), cHT.GetWindow(), 0, 0, 1))
      return false;
   if(!cIdifK_HT.FeedForward(cIdifK_H.AsObject()))
      return false;
   if(!cKT.FeedForward(cK.AsObject()))
      return false;
   if(!MatMul(cIdifK_H.getOutput(), cPPred.getOutput(), cIdifK_H.getPrevOutput(),
              cIdifK_HT.GetCount(), cIdifK_HT.GetWindow(), cIdifK_HT.GetWindow(), 1, true))
      return false;
   if(!MatMul(cIdifK_H.getPrevOutput(), cIdifK_HT.getOutput(), cP.getPrevOutput(),
              cIdifK_HT.GetCount(), cIdifK_HT.GetWindow(), cIdifK_HT.GetCount(), 1, true))
      return false;
   if(!MatMul(cK.getOutput(), cR_RT.getOutput(), cK.getPrevOutput(),
              cKT.GetCount(), cRT.GetCount(), cRT.GetCount(), 1, true))
      return false;
   if(!MatMul(cK.getPrevOutput(), cKT.getOutput(), cKT.getPrevOutput(),
              cKT.GetCount(), cKT.GetWindow(), cKT.GetCount(), 1, true))
      return false;
   if(!SumAndNormilize(cP.getPrevOutput(), cKT.getPrevOutput(), cP.getOutput(), cPT.GetWindow(), false, 0, 0, 0, 1))
      return false;
   if(!cPT.FeedForward(cP.AsObject()))
      return false;
   if(!SumAndNormilize(cP.getOutput(), cPT.getOutput(), cP.getOutput(), 1, false, 0, 0, 0, 0.5f))
      return false;

所有计算完成后,最后一步是生成输出向量。为此,根据获得的逆协方差矩阵 P⁻¹ 进行抽样。随机噪声乘以 P ,并将结果添加到状态向量 X 中,形成最终表示。

   if(!SumAndNormilize(cX.getOutput(), cAuxiliaryNet.getOutput(), cX.getOutput(), 1, false, 0, 0, 0, 1))
      return false;
//--- Sample Output
   if(!cP.getOutput().GetData(mPGrad))
      return false;
   if(mPGrad.HasNan() > 0)
     {
      mPGrad.Identity();
      if(!cP.getOutput().Fill(mPGrad))
         return false;
     }
   mP = mPGrad.Inv();
   if(mP.Rows() == 0)
     {
      mPGrad = mPGrad + mPGrad.Transpose();
      vector<double> eigvals;
      matrix<double> eigvecs;
      if(!mPGrad.Eig(eigvecs, eigvals))
         return false;
      if(eigvals.Size() > 0)
        {
         if(!eigvals.Clip(1e-6, DBL_MAX))
            return false;
         mPGrad = matrix<double>::Zeros(eigvals.Size(), eigvals.Size());
         mPGrad.Diag(eigvals);
         mPGrad = eigvecs.MatMul(mPGrad.MatMul(eigvecs.Transpose()));
         mPGrad = mPGrad + mPGrad.Transpose();
         mP = mPGrad.Inv();
        }
      if(mP.Rows() == 0)
        {
         mPGrad.Identity();
         if(!cP.getOutput().Fill(mPGrad))
            return false;
         mP = mPGrad.Inv();
        }
     }
   mNoise.Random(-1, 1);
   matrix<double> temp = mNoise.MatMul(mP);
   if(!PrevOutput.Fill(temp))
      return false;
   if(!SumVecMatrix(cX.getOutput(), PrevOutput, Output, (int)mNoise.Cols(), 0, 0, 0, 1))
      return false;
//---
   return true;
  }

值得强调的是,生成结果张量的最后阶段并不局限于构建单一场景。相反,该模型生成了一系列可能的轨迹,每一条轨迹都是协方差矩阵 P 所描述的多变量分布的一种实现。这不仅仅是一个巧妙的数学技巧,更反映了模型对其自身预测的置信度。

正是这种方法使得该模型在市场不稳定或缺乏可靠信息的情况下特别有价值:它不仅可以预测单一可能的未来情景;它还可以根据训练结果勾勒出一整套可能的轨迹。这将 feedForward 输出表现为一组概率化的解云。它们分别反映了事件可能发展的不同方面。



误差梯度分布的特征

一旦模型生成了一系列可能的轨迹,前向传播阶段就结束了,一个同样重要的阶段开始了:误差梯度的反向传播。我们在 calcInputGradients 方法中实现了它,该方法的主要任务是将梯度从模型输出级别传递到输入数据,正确地将误差传播到所有相关组件。

该算法首先将梯度分配到线性模型的平均预测值和协方​​差矩阵 P 之间。

bool CNeuronK2VAEEncoder::calcInputGradients(CNeuronBaseOCL *NeuronOCL)
  {
   if(!NeuronOCL)
      return false;
//--- From Output
   if(!SumVecMatrixGrad(cX.getGradient(), PrevOutput, Gradient, (int)mNoise.Cols(), 0, 0, 0, 1))
      return false;
   if(!PrevOutput.GetData(mGrad))
      return false;
   mPGrad = mNoise.Transpose().MatMul(mGrad);
   mP = mP.Transpose();
   mP = (mP * (-1)).MatMul(mPGrad.MatMul(mP));

我们还会将误差梯度传递给 Joseph 稳定形式。

//--- Joseph stabilized form for P
   if(!cPT.getGradient().Fill(mP))
      return false;
   if(!cP.CalcHiddenGradients(cPT.AsObject()))
      return false;
   if(!SumAndNormilize(cP.getGradient(), cPT.getGradient(), cP.getGradient(), 1, false, 0, 0, 0, 0.5f))
      return false;
//---
   if(!MatMulGrad(cK.getPrevOutput(), cKT.getPrevOutput(),
                  cKT.getOutput(), cKT.getGradient(),
                  cP.getGradient(), cKT.GetCount(),
                  cKT.GetWindow(), cKT.GetCount(), 1, true))
      return false;
   if(!MatMulGrad(cK.getOutput(), cK.getPrevOutput(),
                  cR_RT.getOutput(), cR_RT.getGradient(),
                  cKT.getPrevOutput(), cKT.GetCount(),
                  cRT.GetCount(), cRT.GetCount(), 1, true))
      return false;
   if(!MatMulGrad(cIdifK_H.getPrevOutput(), cIdifK_HT.getPrevOutput(),
                  cIdifK_HT.getOutput(), cIdifK_HT.getGradient(),
                  cP.getGradient(), cIdifK_HT.GetCount(),
                  cIdifK_HT.GetWindow(), cIdifK_HT.GetCount(), 1, true))
      return false;
   if(!MatMulGrad(cIdifK_H.getOutput(), cIdifK_H.getPrevOutput(),
                  cPPred.getOutput(), cPPred.getGradient(),
                  cIdifK_HT.getPrevOutput(), cIdifK_HT.GetCount(),
                  cIdifK_HT.GetWindow(), cIdifK_HT.GetWindow(), 1, true))
      return false;
//---
   if(!cK.CalcHiddenGradients(cKT.AsObject()))
      return false;
   if(!SumAndNormilize(cK.getGradient(), cK.getPrevOutput(), cK.getGradient(), 1, false, 0, 0, 0, 1))
      return false;

得到的矩阵被传递下去,并初始化 Kalman 滤波器模块中的梯度响应链。首先,通过测量更新模块。

//--- Measurement update
   if(!cIdifK_H.CalcHiddenGradients(cIdifK_HT.AsObject()))
      return false;
   if(!SumAndNormilize(cIdifK_H.getGradient(), cIdifK_H.getPrevOutput(), cIdifK_H.getGradient(),
                                                                          1, false, 0, 0, 0, 1))
      return false;
   if(!IdentDifferentGrad(cK_H.getGradient(), cIdifK_H.getGradient(), cHT.GetWindow(), 0, 0, 1))
      return false;
   if(!MatMulGrad(cK.getOutput(), cK.getPrevOutput(),
                  cH.getOutput(), cH.getGradient(),
                  cK_H.getGradient(), cKT.GetCount(),
                  cKT.GetWindow(), cHT.GetWindow(), 1, true))
      return false;
//---
   if(!MatMulGrad(cDeltY.getOutput(), cDeltY.getGradient(),
                  cK.getOutput(), cK.getPrevOutput(),
                  cX.getGradient(), 1,
                  cDeltY.Neurons(), cX.Neurons(), 1, true))
      return false;
   if(!SumAndNormilize(cK.getGradient(), cK.getPrevOutput(), cK.getGradient(), 1, false, 0, 0, 0, 1))
      return false;
   if(!DifferentGrad(cKoopmanPred.getGradient(), cYPred.getGradient(), cDeltY.getGradient(),
                     1, 0, 0, 0, 1))
      return false;
   if(!MatMulGrad(cXPred.getOutput(), cXPred.getGradient(),
                  cHT.getOutput(), cHT.getGradient(),
                  cYPred.getGradient(),
                  1, cHT.GetWindow(), cHT.GetCount(), 1, true))
      return false;
   if(!SumAndNormilize(cXPred.getGradient(), cX.getGradient(), cXPred.getGradient(), 1, false, 0, 0, 0, 1))
      return false;

接下来,在校正块中,梯度传播会经过预测矩阵和误差矩阵。所有这些步骤都精心构建了一个从输出到隐藏空间的梯度流,并且最重要的是,它们考虑了变量之间的结构关系。

//--- Update step
   if(!MatMulGrad(cP_HT.getOutput(), cP_HT.getGradient(),
                  cSInv.getOutput(), cSInv.getGradient(),
                  cK.getGradient(), cHT.GetCount(),
                  (int)mS.Rows(), (int)mS.Cols(), 1, true))
      return false;
   if(cSInv.getGradient().GetData(mSGrad) <= 0)
      return false;
   mS = mS.Transpose();
   mS = (mS * (-1)).MatMul(mSGrad.MatMul(mS));
   if(cH_P_HT.getGradient().Fill(mS) <= 0)
      return false;
   if(!MatMulGrad(cH.getOutput(), cH.getPrevOutput(),
                  cP_HT.getOutput(), cP_HT.getPrevOutput(),
                  cH_P_HT.getGradient(), cHT.GetCount(),
                  cHT.GetWindow(), cHT.GetCount(), 1, true))
      return false;
   if(!SumAndNormilize(cH_P_HT.getGradient(), cR_RT.getGradient(),
                       cR_RT.getGradient(), int(mS.Cols()), false, 0, 0, 0, 1))
      return false;
   if(!SumAndNormilize(cH.getGradient(), cH.getPrevOutput(),
                       cH.getPrevOutput(), 1, false, 0, 0, 0, 1))
      return false;
   if(!SumAndNormilize(cP_HT.getGradient(), cP_HT.getPrevOutput(),
                       cP_HT.getGradient(), 1, false, 0, 0, 0, 1))
      return false;
   if(!MatMulGrad(cPPred.getOutput(), cPPred.getPrevOutput(),
                  cHT.getOutput(), cHT.getPrevOutput(),
                  cP_HT.getGradient(), cPPred.Neurons() / cHT.GetWindow(),
                  cHT.GetWindow(), cHT.GetCount(), 1, true))
      return false;
   if(!SumAndNormilize(cPPred.getGradient(), cPPred.getPrevOutput(),
                       cQ_QT.getGradient(), int(cQT.GetWindow()), false, 0, 0, 0, 1))
      return false;
   if(!SumAndNormilize(cHT.getGradient(), cHT.getPrevOutput(), cHT.getGradient(), 1, false, 0, 0, 0, 1))
      return false;

接下来是预测模块 — 预测步骤。

//--- Prediction step
   if(!MatMulGrad(cF_P.getOutput(), cF_P.getGradient(),
                  cFT.getOutput(), cFT.getGradient(),
                  cQ_QT.getGradient(), cFT.GetCount(),
                  cFT.GetWindow(), cFT.GetCount(), 1, true))
      return false;
   if(!MatMulGrad(cF.getOutput(), cF.getPrevOutput(),
                  cP.getOutput(), cP.getGradient(),
                  cF_P.getGradient(),
                  cFT.GetCount(), cFT.GetWindow(), cP.Neurons() / cFT.GetWindow(), 1, true))
      return false;
   if(!MatMulGrad(cAuxiliaryNet.getOutput(), cAuxiliaryNet.getGradient(),
                  cB.getOutput(), cB.getGradient(),
                  cXPred.getGradient(), 1,
                  cFT.GetWindow(), cFT.GetCount(), 1, true))
      return false;
   if(!MatMulGrad(NeuronOCL.getOutput(), NeuronOCL.getGradient(),
                  cFT.getOutput(), cFT.getPrevOutput(),
                  cXPred.getGradient(), 1,
                  cFT.GetWindow(), cFT.GetCount(), 1, true))
      return false;
   if(!SumAndNormilize(cFT.getGradient(), cFT.getPrevOutput(),
                       cFT.getGradient(), 1, false, 0, 0, 0, 1))
      return false;
//---
   if(!MatMulGrad(cR.getOutput(), cR.getPrevOutput(),
                  cRT.getOutput(), cRT.getGradient(),
                  cR_RT.getGradient(), cRT.GetCount(),
                  cRT.GetWindow(), cRT.GetCount(), 1, false))
      return false;
   if(!MatMulGrad(cQ.getOutput(), cQ.getPrevOutput(),
                  cQT.getOutput(), cQT.getGradient(),
                  cQ_QT.getGradient(), cQT.GetCount(),
                  cQT.GetWindow(), cQT.GetCount(), 1, false))
      return false;
   if(!cR.CalcHiddenGradients((CObject*)cRT.AsObject()))
      return false;
   if(!SumAndNormilize(cR.getGradient(), cR.getPrevOutput(),
                       cR.getGradient(), cRT.GetWindow(), false, 0, 0, 0, 0.01f))
      return false;
   if(!cQ.CalcHiddenGradients(cQT.AsObject()))
      return false;
   if(!SumAndNormilize(cQ.getGradient(), cQ.getPrevOutput(),
                       cQ.getGradient(), cQT.GetWindow(), false, 0, 0, 0, 0.01f))
      return false;
   if(!cH.CalcHiddenGradients(cHT.AsObject()))
      return false;
   if(!SumAndNormilize(cH.getGradient(), cH.getPrevOutput(),
                       cH.getGradient(), cHT.GetWindow(), false, 0, 0, 0, 0.01f))
      return false;
   if(!cF.CalcHiddenGradients(cFT.AsObject()))
      return false;
   if(!SumAndNormilize(cF.getGradient(), cF.getPrevOutput(),
                       cF.getGradient(), cFT.GetWindow(), false, 0, 0, 0, 0.01f))
      return false;

最后一个模块涉及 Koopman 空间中的表示和注意力模块。在这里,梯度被传播到预测部分和重建部分( KoopmanPred , KoopmanRest )。

//--- Rest Attention
   if(!cKoopmanRest.CalcHiddenGradients(cAuxiliaryNet.AsObject()))
      return false;
//--- Pred / Rest
   if(!NeuronOCL.getPrevOutput().Fill(0))
      return false;
   if(!DifferentGrad(NeuronOCL.getPrevOutput(), cKoopmanRest.getPrevOutput(),
                     cKoopmanRest.getGradient(), cKoopman.GetWindow()))
      return false;
   if(!SumAndNormilize(NeuronOCL.getGradient(), NeuronOCL.getPrevOutput(),
                       NeuronOCL.getPrevOutput(), 1, false, 0, 0, 0, 1))
      return false;
   if(NeuronOCL.Activation() != None)
     {
      if(!DeActivation(NeuronOCL.getOutput(), NeuronOCL.getPrevOutput(),
                       NeuronOCL.getPrevOutput(), NeuronOCL.Activation()))
         return false;
     }
   if(!Concat(cKoopmanPred.getGradient(), cKoopmanRest.getPrevOutput(), cKoopman.getGradient(),
              cKoopman.GetWindow(), cKoopman.Neurons() - cKoopman.GetWindow(), 1))
      return false;

对于后者,梯度是根据差值计算的,然后将两部分合并成一个 Koopman 结构。

最后阶段,将梯度传播到原始数据的层面。

//--- Koopman
   if(!NeuronOCL.CalcHiddenGradients(cKoopman.AsObject()))
      return false;
   if(!SumAndNormilize(NeuronOCL.getGradient(), NeuronOCL.getPrevOutput(),
                       NeuronOCL.getGradient(), 1, false, 0, 0, 0, 1))
      return false;
//---
   return true;
  }

因此,该方法逐步追踪完整的反向传播路径,涵盖模型的所有关键组件:概率潜在组件、状态滤波、预测、校正以及在 Koopman 空间中的变换。所有这些确保了模型参数得到精细调整,并使其能够有效地从时间序列数据中学习。

CNeuronK2VAEEncoder 对象及其所有方法的完整源代码已在附件中提供。

今天,我们已经完成了大量且细致的工作,文章篇幅已经相当可观。我建议稍作休息,让内容深入人心,然后再以全新的视角审视它。在下一篇文章中,我们将完成未尽之事:详细探讨剩余的要点,并使用真实的历史数据来测试所实施的方法。这不仅有助于巩固理论,还能评估其实际效果。


结论

在本文中,我们详细研究了在 K²VAE 框架中实现 Encoder 的架构和主要阶段,该框架将 KoopmanNet 和 Kalman 滤波器的功能结合到一个系统中,用于时间序列分析。这种方法通过将经典线性预测与基于观测值的灵活自适应调整相结合,能够有效地对金融数据的复杂动态进行建模。本文讨论的框架清晰地展示了如何将经过时间检验的方法与现代神经网络技术无缝集成,为分析和预测金融市场开辟了新的可能性。

在下一篇文章中,我们将转向基于真实历史数据的实测,以客观评估模型在真实交易条件下的效果与潜力。


参考


本文中使用的程序

# 名称 类型 描述
1 Study.mq5 EA 离线模型训练的 EA
2 StudyOnline.mq5 EA 在线模型训练的 EA
3 Test.mq5 EA 模型测试 EA
4 Trajectory.mqh 类库 用于描述系统状态和模型架构的结构
5 NeuroNet.mqh 类库 用于构建神经网络的类库
6 NeuroNet.cl 程序库 OpenCL 程序的代码库


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

附加的文件 |
MQL5.zip (2915.29 KB)
借助PythonAnywhere对接MetaTrader与谷歌表格:安全数据传输指南 借助PythonAnywhere对接MetaTrader与谷歌表格:安全数据传输指南
本文介绍一种将MetaTrader数据导出至谷歌表格的安全实现方案。谷歌表格是十分理想的选择,它基于云端,存储在其中的数据可随时随地访问。交易者可以读取导出到谷歌表格的交易数据,随时随地展开分析,指导后续交易。
从新手到专家:使用 Reporting EA 打造详尽的交易报告 从新手到专家:使用 Reporting EA 打造详尽的交易报告
本文深入探讨如何丰富交易报告的细节,并以 PDF 格式通过电子邮件发送最终报告。本文是前序文章内容的延伸,我们将继续探索如何利用 MQL5 与 Python,生成并定时输出格式专业、使用便捷的交易报告。跟随本文,你将了解如何在 MQL5 生态中优化交易报告的生成流程。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
简化MQL5中的数据库操作(第一部分):数据库与SQL简介 简化MQL5中的数据库操作(第一部分):数据库与SQL简介
本文会讲解如何运用MQL5的原生函数操作数据库。我们的内容涵盖建表、数据插入、更新、删除以及数据导入导出,全部配有示例代码。本文可以帮助读者扎实理解数据访问的内部工作机制,同时为后续ORM相关内容做好铺垫 —— 我们将在MQL5中构建一个迷你ORM。