打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
神经网络变得轻松(第二部分):网络训练和测试

内容

  • 概述
  • 1. 定义问题
  • 2. 神经网络模型项目
    • 2.1. 判定输入层中神经元的数量
    • 2.2. 设计隐藏层
    • 2.3. 判定输出层中神经元的数量
  • 3. 编程
    • 3.1. 准备工作
    • 3.2. 初始化类
    • 3.3. 训练神经网络
    • 3.4. 改进梯度计算方法
  • 4. 测试
  • 结束语
  • 本文中用到的程序

概述

在上一篇名为神经网络变得轻松的文章中,我们曾研究过利用 MQL5 配合完全连接的神经网络一起操作的 CNet 构造原理。 在本文中,我将演示一个示例,说明如何在 EA 中利用该类,并在实际条件下评估该类。

1. 定义问题

在开始创建智能交易系统之前,必须定义将为新神经网络设定的目标。 当然,金融市场上所有智能交易系统的共同目标是获利。 然而,此目的太笼统宽泛。 我们需要为神经网络指定更具体的任务。 甚至,我们需要了解如何评估神经网络的未来结果。

另一个重要的时刻是,先前创建的 CNet 类使用了监督学习的原理,因此它需要标记数据作为训练集合。

如果您查看价格图表,自然会希望在价格峰值时执行交易操作,这可以通过标准的比尔·威廉姆斯(Bill Williams)分形指标来示意。 而指标的问题在于,它会判断 3 根烛条的峰值,且产生的信号始终会延迟 1 根烛条,而这可能会产生相反的信号。 如果我们设置神经网络以判定第三根烛条形成之前的枢轴点,该怎么办? 这种方法至少会在交易方向移动有一根之前走势的烛条。

这是指训练集合:

  • 在正向递进中,我们会将当前行情状况输入到神经网络,并输出最后一根收盘烛条上提取的形成概率的评估。
  • 对于逆向递进中,在下一根烛条形成之后,我们将检查前一根烛条上是否存在分形,并将输入结果按权重调整。

为了评估网络运算的结果,我们可以使用均方预测误差,正确的分形预测的百分比,和无法识别的分形的百分比。

现在我们需要判定哪些数据应输入到我们的神经网络中。 您还记得,当您尝试根据图表评估行情状况时所做的事情吗?

首先,建议交易新手从图表中直观评估趋势方向。 因此,我们必须将有关价格变动的信息数字化,并将其输入到神经网络中。 我建议输入有关的开盘价和收盘价、最高价和最低价、交易量和形成时间的数据。

另一种判定趋势的流行方法是使用振荡器指标。 此类指标操作很方便,因为指标会输出标准化数据。 我决定为本次实验准备四个标准指标:RCI,CCI,ATR 和 MACD,所有指标均带采用标准参数。 我在选择指标及其参数时,没有进行任何其他分析。

有人可能会说利用指标是没有意义的,因为指标的数据是通过重新计算烛条的价格数据而建立的,我们已经将其输入到神经网络当中。 但这并非完全正确。 指标值是通过计算来自多根烛条的数据来判定的,从而可对所分析的样本进行一定程度的扩展。 神经网络训练过程将判定它们如何影响结果。

为了能够评估行情动态,我们在一定的历史时期内将全部信息输入到神经网络之中。

2. 神经网络模型项目

2.1. 判定输入层中神经元的数量

此处,我们需要知晓输入层中神经元的数量。 为此,请评估每根烛条上的初始信息,然后将其乘以分析历史记录的深度。

由于指标数据已经标准化,且指标缓冲区的相关数量已知,因此无需预处理指标数据(上述 4 个指标总共有 5 个值)。 因此,若要在输入层中接收这些指标,我们需要为每根所分析烛条创建 5 个神经元。

烛条价格数据的情况略有不同。 从图表直观地判定趋势方向和强度时,我们首先分析烛条方向和大小。 只有在此之后,当我们要判定趋势方向,和可能的枢轴点时,我们要注意所分析品种的价位。 因此,有必要在把该数据输入到神经网络之前对其进行标准化。 我个人输入了所述烛条的开盘价与收盘价、最高价和最低价的差值。 在这种方法中,定义三个神经元就足够了,其中第一个神经元的符号判定烛条方向。

有许多不同的资料论述了各种时间因素对货币波动的影响。 例如,季度线、周线和日线的动态差异,以及欧洲、美洲和亚洲的交易时段,均以不同的方式影响货币汇率。 若要分析这些因素,将烛条形成的月份、时刻和星期几输入进神经网络。 我特意将烛条形成的时间和日期分为几个部分,因为这可令神经网络能够泛化,并找到依赖性。

另外,我们来包含有关成交量的信息。 如果您的经纪商提供真实的交易量数据,则指明这些交易量;否则指定即时报价的交易量。

故此,为了应对每根烛条,我们需要 12 个神经元。 将此数字乘以所分析的历史深度,您可得到神经网络输入层的大小。

2.2. 设计隐藏层

下一步是准备神经网络的隐藏层。 网络结构(层数和神经元数)的选择是最困难的任务之一。 单层感知器善于类的线性分离。 双层网络可跟踪非线性边界。 三层网络可以描述复杂的多连接区域。 当我们增加层数时,功能类别会扩展,但这会导致收敛性变差,和训练成本增加。 每层当中,神经元的数量必须满足功能的预期变化。 实际上,非常简单的网络无法在实际条件下按要求的精度模拟行为,而过于复杂的网络不仅会重复训练目标函数,还有噪声。

在首篇文章中,我提到了 “5 个为什么” 方法。 现在,我建议继续此实验,并创建一个包含 4 个隐藏层的网络。 我将首个隐藏层中的神经元数量设置为 1000。 不过,也有可能根据分析周期的深度建立一些依赖关系。 遵照帕累托(Pareto)规则,我们将每个后续层中的神经元数量减少 70%。 此外,将遵循如下限制:隐藏层中的神经元数量不得少于 20。

2.3. 判定输出层中神经元的数量

输出层当中神经元的数量取决于任务,及其解决方案。 若要解决回归问题,只需要一个神经元就能产生期望值即可。 为了解决分类问题,我们需要与期望的类数量相等的神经元 - 每个神经元将为分配给每个类的原始对象生成概率。 而在实际中,对象的类别由最大概率判定。

对于我们的情况,我建议创建 2 个神经网络变体,并评估它们在实践中应对我们之问题的适用性。 在第一种情况下,输出层仅有一个神经元。 数值在 0.5...1.0 范围内与买入分形对应,而数值在 -0.5..-1.0 范围内与卖出信号对应,数值在 -0.5...0.5 范围内表示没有信号。 在此解决方案中,双曲正切用作激活函数 - 它的输出值范围为 -1.0 到 +1.0。

在第二种情况下,将在输出层中创建 3 个神经元(买、卖、无信号)。 在这个变体中,我们来训练神经网络,从而获得范围为 0.0...1.0 的结果。 在此,结果就是分形出现的概率。信号将依据最大概率来判定,并根据含有最高概率的神经元的索引来判定信号的方向。

3. 编程

3.1. 准备工作

现在,到编程的时候了。 首先,加入所需的函数库:

  • NeuroNet.mqh — 前一篇文章中创建神经网络的函数库
  • SymbolInfo.mqh — 接收品种数据的标准库
  • TimeSeries.mqh — 处理时间序列的标准库
  • Volumes.mqh — 接收交易量数据的标准库
  • Oscilators.mqh — 含有振荡器类的标准库
#include 'NeuroNet.mqh'#include <Trade\SymbolInfo.mqh>#include <Indicators\TimeSeries.mqh>#include <Indicators\Volumes.mqh>#include <Indicators\Oscilators.mqh>

下一步是编写程序参数,通过它们来设置神经网络和指标参数。

//+------------------------------------------------------------------+//|   input parameters                                               |//+------------------------------------------------------------------+input int                  StudyPeriod =  10;            //Study period, yearsinput uint                 HistoryBars =  20;            //Depth of historyENUM_TIMEFRAMES            TimeFrame   =  PERIOD_CURRENT;//---input group                '---- RSI ----'input int                  RSIPeriod   =  14;            //Periodinput ENUM_APPLIED_PRICE   RSIPrice    =  PRICE_CLOSE;   //Applied price//---input group                '---- CCI ----'input int                  CCIPeriod   =  14;            //Periodinput ENUM_APPLIED_PRICE   CCIPrice    =  PRICE_TYPICAL; //Applied price//---input group                '---- ATR ----'input int                  ATRPeriod   =  14;            //Period//---input group                '---- MACD ----'input int                  FastPeriod  =  12;            //Fastinput int                  SlowPeriod  =  26;            //Slowinput int                  SignalPeriod=  9;             //Signalinput ENUM_APPLIED_PRICE   MACDPrice   =  PRICE_CLOSE;   //Applied price

接下来,声明全局变量 - 稍后会讲解它们的用法。

CSymbolInfo *Symb;CiOpen *Open;CiClose *Close;CiHigh *High;CiLow *Low;CiVolumes *Volumes;CiTime *Time;CNet *Net;CArrayDouble *TempData;CiRSI *RSI;CiCCI *CCI;CiATR *ATR;CiMACD *MACD;//---double dError;double dUndefine;double dForecast;double dPrevSignal;datetime dtStudied;bool bEventStudy;

准备工作至此完成。 现在继续进行类的初始化。

3.2 初始化类

类的初始化将在 OnInit 函数中执行。 首先,我们创建处理品种的 CSymbolInfo 类的实例,并更新有关图表品种的数据。

//+------------------------------------------------------------------+//| Expert initialization function                                   |//+------------------------------------------------------------------+int OnInit()  {//---   Symb=new CSymbolInfo();   if(CheckPointer(Symb)==POINTER_INVALID || !Symb.Name(_Symbol))      return INIT_FAILED;   Symb.Refresh();

然后创建时间序列实例。 在您每次创建类实例时,请检查它是否已成功创建,并初始化。 如果发生错误,则以 INIT_FAILED 作为结果退出函数。

Open=new CiOpen(); if(CheckPointer(Open)==POINTER_INVALID || !Open.Create(Symb.Name(),TimeFrame)) return INIT_FAILED;//--- Close=new CiClose(); if(CheckPointer(Close)==POINTER_INVALID || !Close.Create(Symb.Name(),TimeFrame)) return INIT_FAILED;//--- High=new CiHigh(); if(CheckPointer(High)==POINTER_INVALID || !High.Create(Symb.Name(),TimeFrame)) return INIT_FAILED;//--- Low=new CiLow(); if(CheckPointer(Low)==POINTER_INVALID || !Low.Create(Symb.Name(),TimeFrame)) return INIT_FAILED;//--- Volumes=new CiVolumes(); if(CheckPointer(Volumes)==POINTER_INVALID || !Volumes.Create(Symb.Name(),TimeFrame,VOLUME_TICK)) return INIT_FAILED;//--- Time=new CiTime(); if(CheckPointer(Time)==POINTER_INVALID || !Time.Create(Symb.Name(),TimeFrame)) return INIT_FAILED;

在此示例中采用了即时报价交易量。 若您希望采用真实交易量,则在调用 Volumes.Creare 方法时将 “VOLUME_TICK” 替换为 “VOLUME_REAL”。

在声明了时间序列之后,创建类的实例,从而以类似方式使用指标。

   RSI=new CiRSI();         if(CheckPointer(RSI)==POINTER_INVALID || !RSI.Create(Symb.Name(),TimeFrame,RSIPeriod,RSIPrice))      return INIT_FAILED;//---   CCI=new CiCCI();         if(CheckPointer(CCI)==POINTER_INVALID || !CCI.Create(Symb.Name(),TimeFrame,CCIPeriod,CCIPrice))      return INIT_FAILED;//---   ATR=new CiATR();         if(CheckPointer(ATR)==POINTER_INVALID || !ATR.Create(Symb.Name(),TimeFrame,ATRPeriod))      return INIT_FAILED;//---   MACD=new CiMACD();         if(CheckPointer(MACD)==POINTER_INVALID || !MACD.Create(Symb.Name(),TimeFrame,FastPeriod,SlowPeriod,SignalPeriod,MACDPrice))      return INIT_FAILED;

现在我们可以直接利用神经网络类运作了。 首先,创建一个类的实例。 在 CNet 类初始化期间,构造函数参数会将引用传递给含有网络结构规范的数组。 请注意,网络训练过程当中会消耗计算资源,且会花费大量时间。 因此,每次重启之后,网络都是不正确的,需要训练。 此处是我如何操作的:首先,我在声明网络实例时未指定结构,然后尝试从本地存储加载先前已训练过的网络(文件名在 #define 中提供)。

#define FileName Symb.Name()+'_'+EnumToString((ENUM_TIMEFRAMES)Period())+'_'+IntegerToString(HistoryBars,3)+'fr_ea'............ Net=new CNet(NULL); ResetLastError(); if(CheckPointer(Net)==POINTER_INVALID || !Net.Load(FileName+'.nnw',dError,dUndefine,dForecast,dtStudied,false)) { printf('%s - %d -> Error of read %s prev Net %d',__FUNCTION__,__LINE__,FileName+'.nnw',GetLastError());

如果无法加载先前已训练的数据,则会将消息打印到日志,指示错误代码,然后开始创建新的未经训练的网络。 首先,声明 CArrayInt 类的实例,并指定神经网络的结构。 元素的数量表示神经网络层的数量,而元素的数值表示相应层中神经元的数量。

      CArrayInt *Topology=new CArrayInt();      if(CheckPointer(Topology)==POINTER_INVALID)         return INIT_FAILED;

正如早前所提到的,我们在输入层中需要 12 个神经元来应对每根烛条。 因此,在第一个数组元素中,用 12 乘以所分析历史记录的深度。

if(!Topology.Add(HistoryBars*12)) return INIT_FAILED;

然后定义隐藏层。 我们已判定在第一个隐藏层中将包括 4 个含 1000 个神经元的隐藏层。 然后,在后续的每个层中,神经元的数量将减少 70%,但每一层至少含有 20 个神经元。 数据将循环添加到数组当中。

      int n=1000;      bool result=true;      for(int i=0;(i<4 && result);i++)        {         result=(Topology.Add(n) && result);         n=(int)MathMax(n*0.3,20);        }      if(!result)        {         delete Topology;         return INIT_FAILED;        }

在输出层中指示 1 来构建回归模型。

if(!Topology.Add(1)) return INIT_FAILED;

如果我们采用分类模型,则需要为输出神经元指定 3。

接下来,删除先前创建的 CNet 类实例,并创建一个新实例,并在其中指明要创建的神经网络的结构。 创建新的神经网络实例后,删除网络结构的类,因为以后不会再用到它。

      delete Net;      Net=new CNet(Topology);      delete Topology;      if(CheckPointer(Net)==POINTER_INVALID)         return INIT_FAILED;

设置变量的初始值,以便收集统计数据:

  • dError - 标准偏差(误差)
  • dUndefine - 未定义分形的百分比
  • dForecast - 正确预测分形的百分比
  • dtStudied — 最后一根已训练烛条的日期。
dError=-1; dUndefine=0; dForecast=0; dtStudied=0; }

不要忘记,只当没有先前训练过的神经网络,无需从本地存储加载的情况下,我们才需要设置神经网络结构,创建神经网络类的新实例,并初始化统计变量。
在 OnInit 函数的末尾,创建 CArrayDouble() 类的实例,该实例用来与神经网络交换数据,并开始神经网络训练过程。

我想在这里分享另一种解决方案。 MQL5 不支持异步函数调用。 如果我们从 OnInit 函数显式调用学习函数,则终端将误认为程序初始化过程尚未完成,直到训练完成。 这就是为什么我们要创建一个自定义事件,从 OnChartEvent 函数调用该训练函数,替代直接调用该函数的原因。 创建事件时,请在 lparam 参数中指定训练开始日期。 这种方法可令我们调用函数,并完成 OnInit 函数。

   TempData=new CArrayDouble();   if(CheckPointer(TempData)==POINTER_INVALID)      return INIT_FAILED;//---   bEventStudy=EventChartCustom(ChartID(),1,(long)MathMax(0,MathMin(iTime(Symb.Name(),PERIOD_CURRENT,(int)(100*Net.recentAverageSmoothingFactor*(dForecast>=70 ? 1 : 10))),dtStudied)),0,'Init');//---   return(INIT_SUCCEEDED);  }//+------------------------------------------------------------------+//| ChartEvent function                                              |//+------------------------------------------------------------------+void OnChartEvent(const int id,                  const long &lparam,                  const double &dparam,                  const string &sparam)  {//---   if(id==1001)     {      Train(lparam);      bEventStudy=false;      OnTick();     }  }

不要忘记清除 OnDeinit 函数中的内存。

//+------------------------------------------------------------------+//| Expert deinitialization function |//+------------------------------------------------------------------+void OnDeinit(const int reason) {//--- if(CheckPointer(Symb)!=POINTER_INVALID) delete Symb;//--- if(CheckPointer(Open)!=POINTER_INVALID) delete Open;//--- if(CheckPointer(Close)!=POINTER_INVALID) delete Close;//--- if(CheckPointer(High)!=POINTER_INVALID) delete High;//--- if(CheckPointer(Low)!=POINTER_INVALID) delete Low;//--- if(CheckPointer(Time)!=POINTER_INVALID) delete Time;//--- if(CheckPointer(Volumes)!=POINTER_INVALID) delete Volumes;//--- if(CheckPointer(RSI)!=POINTER_INVALID) delete RSI;//--- if(CheckPointer(CCI)!=POINTER_INVALID) delete CCI;//--- if(CheckPointer(ATR)!=POINTER_INVALID) delete ATR;//--- if(CheckPointer(MACD)!=POINTER_INVALID) delete MACD;//--- if(CheckPointer(Net)!=POINTER_INVALID) delete Net; if(CheckPointer(TempData)!=POINTER_INVALID) delete TempData; }

3.3. 训练神经网络

为了训练神经网络,创建 Train 函数。 训练期的开始日期应作为参数传递给函数。

void Train(datetime StartTrainBar=0)

在函数伊始声明局部变量:

  • count - 学习期计数
  • prev_un - 前一学习期内未识别分形的百分比
  • prev_for - 前一学习期内分形正确“预测”的百分比
  • prev_er - 前一个学习期的误差
  • bar_time - 重新计算酒吧日期
  • stop - 用于跟踪强制程序终止调用的标志。
int count=0; double prev_up=-1; double prev_for=-1; double prev_er=-1; datetime bar_time=0; bool stop=IsStopped(); MqlDateTime sTime;

接下来,检查在函数参数中获得的日期是否未超过最初指定的训练周期。

   MqlDateTime start_time;   TimeCurrent(start_time);   start_time.year-=StudyPeriod;   if(start_time.year<=0)      start_time.year=1900;   datetime st_time=StructToTime(start_time);   dtStudied=MathMax(StartTrainBar,st_time);

神经网络训练将在 do-while 循环语句中实现。 在循环开始时,重新计算训练神经网络所需历史柱线数量,并保存先前的递次统计信息。

do { int bars=(int)MathMin(Bars(Symb.Name(),TimeFrame,dtStudied,TimeCurrent())+HistoryBars,Bars(Symb.Name(),TimeFrame)); prev_un=dUndefine; prev_for=dForecast; prev_er=dError; ENUM_SIGNAL bar=Undefine;

然后,调整缓冲区的大小,并加载必要的历史数据。

      if(!Open.BufferResize(bars) || !Close.BufferResize(bars) || !High.BufferResize(bars) || !Low.BufferResize(bars) || !Time.BufferResize(bars) ||         !RSI.BufferResize(bars) || !CCI.BufferResize(bars) || !ATR.BufferResize(bars) || !MACD.BufferResize(bars) || !Volumes.BufferResize(bars))         break;      Open.Refresh(OBJ_ALL_PERIODS);      Close.Refresh(OBJ_ALL_PERIODS);      High.Refresh(OBJ_ALL_PERIODS);      Low.Refresh(OBJ_ALL_PERIODS);      Volumes.Refresh(OBJ_ALL_PERIODS);      Time.Refresh(OBJ_ALL_PERIODS);      RSI.Refresh(OBJ_ALL_PERIODS);      CCI.Refresh(OBJ_ALL_PERIODS);      ATR.Refresh(OBJ_ALL_PERIODS);      MACD.Refresh(OBJ_ALL_PERIODS);

更新跟踪强制程序终止的标志,并声明一个新的标志,指示学习期已经过去(add_loop)。

stop=IsStopped(); bool add_loop=false;

遍历所有历史数据来组织嵌套的训练循环。 在循环的伊始,检查历史数据是否已触及末尾。 如有必要,更改 add_loop 标志。 另外,在图表上用注释显示神经网络训练的当前状态。 这将有助于监视训练过程。

      for(int i=(int)(bars-MathMax(HistoryBars,0)-1); i>=0 && !stop; i--)        {         if(i==0)            add_loop=true;         string s=StringFormat('Study -> Era %d -> %.2f -> Undefine %.2f%% foracast %.2f%%\n %d of %d -> %.2f%% \nError %.2f\n%s -> %.2f',count,dError,dUndefine,dForecast,bars-i+1,bars,(double)(bars-i+1.0)/bars*100,Net.getRecentAverageError(),EnumToString(DoubleToSignal(dPrevSignal)),dPrevSignal);         Comment(s);

然后检查循环的上一步是否已计算出预测的系统状态。 如果有,则沿正确值的方向调整权重。 为此,清除 TempData 数组的内容,检查在上一根烛条上是否分形已形成,然后向 TempData 数组添加正确的值(以下是在输出层中含有一个神经元的回归神经网络的代码)。 之后,调用神经网络的 backProp 方法,把 TempData 数组的引用作为参数传递。 更新 dForecast(正确预测分形的百分比)和 dUndefine(无法识别分形的百分比)中的统计数据。

if(i<(int)(bars-MathMax(HistoryBars,0)-1) && i>1 && Time.GetData(i)>dtStudied && dPrevSignal!=-2) { TempData.Clear(); bool sell=(High.GetData(i+2)<High.GetData(i+1) && High.GetData(i)<High.GetData(i+1)); bool buy=(Low.GetData(i+2)<Low.GetData(i+1) && Low.GetData(i)<Low.GetData(i+1)); TempData.Add(buy && !sell ? 1 : !buy && sell ? -1 : 0); Net.backProp(TempData); if(DoubleToSignal(dPrevSignal)!=Undefine) { if(DoubleToSignal(dPrevSignal)==DoubleToSignal(TempData.At(0))) dForecast+=(100-dForecast)/Net.recentAverageSmoothingFactor; else dForecast-=dForecast/Net.recentAverageSmoothingFactor; dUndefine-=dUndefine/Net.recentAverageSmoothingFactor; } else { if(sell || buy) dUndefine+=(100-dUndefine)/Net.recentAverageSmoothingFactor; } }

调整神经网络权重系数后,计算在当前历史柱线上出现分形的概率(如果等于 0,则计算在当前柱线上形成分形的概率)。 为此,清除 TempData 数组,并将神经网络输入层的当前数据添加到其中。 如果数据添加失败或没有足够的数据,则退出循环。

         TempData.Clear();         int r=i+(int)HistoryBars;         if(r>bars)            continue;//---         for(int b=0; b<(int)HistoryBars; b++)           {            int bar_t=r+b;            double open=Open.GetData(bar_t);            TimeToStruct(Time.GetData(bar_t),sTime);            if(open==EMPTY_VALUE || !TempData.Add(Close.GetData(bar_t)-open) || !TempData.Add(High.GetData(bar_t)-open) || !TempData.Add(Low.GetData(bar_t)-open) ||               !TempData.Add(Volumes.Main(bar_t)/1000) || !TempData.Add(sTime.mon) || !TempData.Add(sTime.hour) || !TempData.Add(sTime.day_of_week) ||               !TempData.Add(RSI.Main(bar_t)) ||               !TempData.Add(CCI.Main(bar_t)) || !TempData.Add(ATR.Main(bar_t)) || !TempData.Add(MACD.Main(bar_t)) || !TempData.Add(MACD.Signal(bar_t)))                  break;           }         if(TempData.Total()<(int)HistoryBars*12)            break;

准备好初始数据后,运行 feedForward 方法,并将神经网络结果写入 dPrevSignal 变量。 以下是在输出层中含有一个神经元的回归神经网络的代码。 在输出层中含有三个神经元的分类神经网络的代码随附于后。

Net.feedForward(TempData); Net.getResults(TempData); dPrevSignal=TempData[0];

为了在图表上可视化神经网络的操作,显示最后 200 根蜡烛的预测分形的标签。

         bar_time=Time.GetData(i);         if(i<200)           {            if(DoubleToSignal(dPrevSignal)==Undefine)               DeleteObject(bar_time);            else               DrawObject(bar_time,dPrevSignal,High.GetData(i),Low.GetData(i));           }

在历史数据周期结束时,更新强制程序终止的标志。

stop=IsStopped(); }

一旦在所有可用的历史数据上针对神经网络进行了训练,就可以增加训练期的计数,并将神经网络的当前状态保存到本地文件之中。 下次启动神经网络数据时,我们将会用到此数据。

      if(add_loop)         count++;      if(!stop)        {         dError=Net.getRecentAverageError();         if(add_loop)           {            Net.Save(FileName+'.nnw',dError,dUndefine,dForecast,dtStudied,false);            printf('Era %d -> error %.2f %% forecast %.2f',count,dError,dForecast);           }         }

最后,指定退出训练循环的条件。 条件可以如下:接收信号表示所达目标高于预定级别以上的概率;达到目标误差参数;或在训练期之后,统计数据没有变化,或变化不大(训练停止在局部最小值处)。 您可以自行定义退出训练过程的条件。

} while((!(DoubleToSignal(dPrevSignal)!=Undefine || dForecast>70) || !(dError<0.1 && MathAbs(dError-prev_er)<0.01 && MathAbs(dUndefine-prev_up)<0.1 && MathAbs(dForecast-prev_for)<0.1)) && !stop);

在退出训练函数之前,请保存最后一根已训练烛条的时间。

   if(count>0)     {      dtStudied=bar_time;     }  }

3.4. 改进梯度计算方法

我想提请您注意,我在测试过程中发现的如下几点。 当训练神经网络时,在某些情况下,隐藏层神经元的权重系数不受控制地增加,这是因为超出了最大允许变量值,结果导致整个神经网络瘫痪了。 当随后的层错误要求神经元输出的数值,超出了激活函数可能的数值范围时,就会发生这种情况。 我找到的解决方案是标准化神经元的目标值。 经调整后的梯度计算方法代码如下所示。

void CNeuron::calcOutputGradients(double targetVals) { double delta=(targetVals>1 ? 1 : targetVals<-1 ? -1 : targetVals)-outputVal; gradient=(delta!=0 ? delta*CNeuron::activationFunctionDerivative(targetVals) : 0); }

附件中提供了所有方法和函数的完整代码。

4. 测试

在 H1 时间帧内,在 EURUSD 对上进行了神经网络的测试训练。 将 20 根烛条的数据输入到神经网络。 针对最近两年进行了训练。 为了检查结果,我在同一终端的两张图表上同时启动了这个智能交易系统:一个 EA 具有回归神经网络(分形 - 输出层中有 1 个神经元),另一个则是分类神经网络(Fractal_2 - 输出层中有 3 个神经元的分类)。

第一个训练期是 12432 根柱线,历时 2 小时 20 分钟。 两种 EA 的表现相似,命中率刚超过 6%。

第一个训练期强烈依赖于在初始阶段随机选择的神经网络的权重。

经过 35 期的训练,统计数据的差异略有增加 - 回归神经网络模型的效果更好:

数值

回归神经网络

分类神经网络

根均方误差

0.68

0.78

命中率

12.68%

11.22%

未识别的分形

20.22%

24.65%

测试结果表明,两种神经网络组织变体在训练时间和预测准确性方面产生的结果相似。 于此同时,获得的结果表明神经网络需要额外的时间和资源进行训练。 如果您希望分析神经网络的学习动态,请查看附件中每个学习期的屏幕截图。

结束语

在本文中,我们研究了神经网络创建、训练和测试的过程。 获得的结果表明,这种技术有潜在的利用价值。 然而,神经网络训练过程会消耗大量计算资源,并会花费大量时间。

本文中用到的程序

#

发行

类型

说明

Experts\NeuroNet_DNG\

1

Fractal.mq5

智能交易系统

一款含有回归神经网络(输出层中有 1 个神经元)的智能交易系统

2

Fractal_2.mq5

智能交易系统

一款含有分类神经网络的智能交易系统(输出层中有 3 个神经元)

3

NeuroNet.mqh

类库

创建神经网络(感知器)的类库

Files\

4

Fractal

目录

包含显示回归神经网络测试的屏幕截图

5

Fractal_2

目录

包含显示分类神经网络测试的屏幕截图

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
医学3D透视下的人体奇观(组图)。
invalid stack pointer on return from function call
16防御式编程2
'交易报告及短信通知的创建和发布
美图!美到骨子里了
KEIL编译错误信息表
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服