强化学习基于由智能体对环境进行独立探索的范式。智能体会影响环境,从而致其变化。作为回报,智能体会获得某种奖励。
强化学习的两个主要问题就此得以突出:环境探索和奖励函数。正确结构的奖励函数鼓励智能体探索环境,并寻找最优行为策略。
然而,在解决大多数实际问题时,我们面临着稀疏的外部奖励。为了克服这一障碍,提出了使用所谓的内部奖励。它们允许智能体掌握新技能,这样也许有助于将来获得外部奖励。然而,由于环境随机性,内部奖励可能会很嘈杂。将嘈杂的预测值直接应用于观测值,可能会对智能体政策训练的效率产生负面影响。甚至,许多方法使用 L2 范数,或方差来衡量研究的新颖性,这会由于平方操作而增加产生的噪声。
1. 核规范及其应用
矩阵范数,包括核范数,广泛用于线性代数的分析和计算方法。核范数在矩阵性质研究、优化问题、条件评估、以及数学和应用科学的许多其它领域中发挥着重要作用。
矩阵的核范数是决定矩阵“大小”的数值特征。它是 Schatten 范数的特例,等于矩阵奇异值的总和。
2. 利用 MQL5 实现
在我们开始实现核范数最大化(NNM)方法之前,我们重点讲解一下它的主要创新 — 新的内部奖励方程。因此,这种方式可作为几乎任何之前研究过的强化学习算法的补充来实现。
还应当注意的是,该算法使用编码器将环境状态转换为某种压缩表示。k-最近邻算法也可用于形成环境状态的压缩表示矩阵。
我的观点是,最简单的解决方案看似是将拟议的内部奖励引入 RE3 算法。它还用编码器将环境状态转换为压缩表示形式。出于该目的,我们在 RE3 中使用随机卷积编码器。这令我们能够降低编码器的训练成本。
此外,RE3 还应用 k-最近环境条件来形成内部奖励。不过,这种奖励的形成方式不同。
我们的动作方向很明确,故到了开始工作的时间了。首先,我们将所有文件从 “...\Experts\RE3\” 复制到 “...\Experts\NNM\” 目录。您也许还记得,它包含四个文件:
Trajectory.mqh — 常用常量、结构和方法的函数库。
Research.mq5 — 与环境交互和收集训练样本的 EA。
Study.mq5 — 直接模型训练的 EA。
Test.mq5 — 测试已训练模型的 EA。
我们还将使用分解的奖励。奖励向量的结构如下所示。
//+------------------------------------------------------------------+
//| Rewards structure |
//| 0 - Delta Balance |
//| 1 - Delta Equity ( "-" Drawdown / "+" Profit) |
//| 2 - Penalty for no open positions |
//| 3 - NNM |
//+------------------------------------------------------------------+
在 “...\NNM\Trajectory.mqh” 文件中,我们把环境状态的压缩表示和模型内部全连接层的大小都增加了。
#define EmbeddingSize 16
#define LatentCount 512
该文件还包含定义所用模型架构的 CreateDescriptions 方法。在此,我们将用到三个神经网络模型:Actor、Critic 和 Encoder。至于后者,我们将选用随机卷积编码器。
bool CreateDescriptions(CArrayObj *actor, CArrayObj *critic, CArrayObj *convolution)
{
//---
CLayerDescription *descr;
//---
if(!actor)
{
actor = new CArrayObj();
if(!actor)
return false;
}
if(!critic)
{
critic = new CArrayObj();
if(!critic)
return false;
}
if(!convolution)
{
convolution = new CArrayObj();
if(!convolution)
return false;
}
在该方法的主体中,我们创建一个局部变量,存储指向一个定义 CLayerDescription 神经层的对象指针,并在必要时初始化定义所用模型架构方案的动态数组。
首先,我们将创建一个扮演者架构的定义,它由两个模块组成:源数据初步处理,和决策。
我们将所分析金融产品的价格走势和指标读数的历史数据提交给初始数据初步处理模块的输入端。如您所见,不同的指标具有不同的参数范围。这对模型训练效率有负面影响。因此,我们使用 CNeuronBatchNormOCL 批量归一化层对接收到的数据进行常规化。
//--- Actor
actor.Clear();
//--- Input layer
if(!(descr = new CLayerDescription()))
return false;
descr.type = defNeuronBaseOCL;
int prev_count = descr.count = (HistoryBars * BarDescr);
descr.activation = None;
descr.optimization = ADAM;
if(!actor.Add(descr))
{
delete descr;
return false;
}
//--- layer 1
if(!(descr = new CLayerDescription()))
return false;
descr.type = defNeuronBatchNormOCL;
descr.count = prev_count;
descr.batch = 1000;
descr.activation = None;
descr.optimization = ADAM;
if(!actor.Add(descr))
{
delete descr;
return false;
}
我们将常规化数据传递到两个卷积层,以便搜索独立指标形态。
//--- layer 2
if(!(descr = new CLayerDescription()))
return false;
descr.type = defNeuronConvOCL;
prev_count = descr.count = BarDescr;
descr.window = HistoryBars;
descr.step = HistoryBars;
int prev_wout=descr.window_out = HistoryBars/2;
descr.activation = LReLU;
descr.optimization = ADAM;
if(!actor.Add(descr))
{
delete descr;
return false;
}
//--- layer 3
if(!(descr = new CLayerDescription()))
return false;
descr.type = defNeuronConvOCL;
prev_count = descr.count = prev_count;
descr.window = prev_wout;
descr.step = prev_wout;
descr.window_out = 8;
descr.activation = LReLU;
descr.optimization = ADAM;
if(!actor.Add(descr))
{
delete descr;
return false;
}
获得的数据由全连接神经层处理。
//--- layer 4
if(!(descr = new CLayerDescription()))
return false;
descr.type = defNeuronBaseOCL;
descr.count = LatentCount;
descr.optimization = ADAM;
descr.activation = LReLU;
if(!actor.Add(descr))
{
delete descr;
return false;
}
//--- layer 5
if(!(descr = new CLayerDescription()))
return false;
descr.type = defNeuronBaseOCL;
prev_count = descr.count = LatentCount;
descr.activation = LReLU;
descr.optimization = ADAM;
if(!actor.Add(descr))
{
delete descr;
return false;
}
在初始数据预处理模块操作的这个阶段,我们期望收到所分析金融产品的历史数据的某种潜在表示。这也许足以判定开仓或持仓的方向,但尚不足以实现资金管理函数。我们用有关的帐户状态信息来补充数据。
//--- layer 6
if(!(descr = new CLayerDescription()))
return false;
descr.type = defNeuronConcatenate;
descr.count = LatentCount;
descr.window = prev_count;
descr.step = AccountDescr;
descr.optimization = ADAM;
descr.activation = SIGMOID;
if(!actor.Add(descr))
{
delete descr;
return false;
}
接下来是全连接层的决策模块,最后是变分自动编码器的潜在表示的随机层。如前,我们在模型的输出中使用这种类型的层来实现随机扮演者政策。
//--- layer 7
if(!(descr = new CLayerDescription()))
return false;
descr.type = defNeuronBaseOCL;
descr.count = LatentCount;
descr.activation = LReLU;
descr.optimization = ADAM;
if(!actor.Add(descr))
{
delete descr;
return false;
}
//--- layer 8
if(!(descr = new CLayerDescription()))
return false;
descr.type = defNeuronBaseOCL;
descr.count = LatentCount;
descr.activation = LReLU;
descr.optimization = ADAM;
if(!actor.Add(descr))
{
delete descr;
return false;
}
//--- layer 9
if(!(descr = new CLayerDescription()))
return false;
descr.type = defNeuronBaseOCL;
descr.count = 2 * NActions;
descr.activation = LReLU;
descr.optimization = ADAM;
if(!actor.Add(descr))
{
delete descr;
return false;
}
//--- layer 10
if(!(descr = new CLayerDescription()))
return false;
descr.type = defNeuronVAEOCL;
descr.count = NActions;
descr.optimization = ADAM;
if(!actor.Add(descr))
{
delete descr;
return false;
}
我们已经完整地讲述了扮演者架构。同时,我们构建了一个实现随机政策的模型,从而强调使用核范数最大化方法进行此类决策的可能性。此外,我们的扮演者将在连续动作空间中操作。不过,这并未限制使用 NNM 方法的范围。