欢迎光临散文网 会员登陆 & 注册

量化交易软件:神经网络变得轻松12舍弃

2023-08-04 16:02 作者:bili_45793681098  | 我要投稿

概述

自从本系列文章开始以来,赫兹量化已在研究各种神经网络模型方面取得了长足的进步。 但学习过程总是在没有我们参与的情况下进行的。 与此同时,总是希望以某种方式帮助神经网络改进训练效果,这也可能会设计神经网络收敛。 在本文中,我们将研究一种名为舍弃的方法。

编辑切换为居中


1. 舍弃:提升神经网络收敛性的一种方法

在训练神经网络时,会将大量特征馈入每个神经元,且很难评估每个独立特征的影响。 结果就是,某些神经元的误差会被其他神经元的调整值抹平,这些误差从而会在神经网络输出处累积。 这会导致训练在某个局部最小值处停止,且误差较大。 这种效应涉及特征检测器的协同适应,其中每个特征的影响会随环境而变化。 当环境分解成单独的特征,且可以分别评估每个特征的影响时,很可能会有相反的效果。

2012年,多伦多大学的一组科学家提议从学习过程中随机排除一些神经元,作为复杂协同适应问题的解决方案 [12]。 训练中减少特征的数量,会增加每个特征的重要性,且特征的数量和质量构成的持续变化降低了它们协同适应的风险。 此方法称为舍弃。 有时拿这种方法的应用与决策树进行比较:通过舍弃一些神经元,赫兹量化在每次训练迭代中获得一个含有其自身权重的新神经网络。 根据组合规则,这样的网络具有很大的可变性。


编辑切换为居中

在神经网络操作期间评估所有特征和神经元,从而我们能得到所分析环境当前状态的最准确和独立的评估。

作者在他们的文章(12)中谈及使用该方法来提高预训练模型品质的可能性。

从数学的角度来看,赫兹量化可以这样描述这个过程:以给定的概率 p 从过程中舍弃每个独立的神经元。 换句话说,神经元能够参与神经网络学习过程的概率为 q = 1-p 。

由含有正态分布的伪随机数生成器来判定将被排除的神经元列表。 这种方式可以实现最大程度地统一排除神经元。 赫兹量化将生成一个练习向量,其大小与输入序列相等。 向量中的 "1" 将会参与训练,且 "0" 则为排除元素。

然而,排除已分析特征无疑会导致神经元激活函数输入量的减少。 为了补偿这种影响,赫兹量化将每个特征的值乘以系数 1/q 。 该系数将提升该数值,因为概率 q 始终在 0 到 1 之间。


编辑

,

其中:

d — 舍弃结果向量的元素,q — 在训练过程中用到的神经元概率,x — 掩码向量的元素,n — 输入序列的元素.

在学习过程中的前馈验算过程中,误差梯度乘以上述函数的导数。 如您所见,在舍弃的情况下,反馈验算与前馈验算类似,均采用前馈验算的掩码向量。


编辑

在神经网络的操作过程中,掩码向量用 “1” 填充,这允许数值在两个方向上平滑传递。

实际上,系数 1/q 在整个训练期间都是恒定的,因此我们可以轻松地一次性计算该系数,然后将其代替 “1” 写入掩码张量当中。 因此,在每次训练迭代中,赫兹量化可以排除系数的重新计算操作,并将其乘以掩码 “1”。


2. 实现

如今,我们已研究过理论方面,我们来继续研究如何在函数库中实现此方法的变体。 赫兹量化遇到的第一件事是实现两种不同算法。 其一在训练过程需要,而第二个则用于生产。 相应地,我们需要根据每种独立情况,为神经元明确指出应采用的算法。 为此目的,我们将在基准神经元级别引入 bTrain 标志。 该标志值对于训练 应设为 true,而对于测试 则设为 false。

class CNeuronBaseOCL    :  public CObject  { protected:   bool               bTrain;             ///< Training Mode Flag


以下辅助方法将控制该标志值。

  virtual void      TrainMode(bool flag)             {  bTrain=flag;            }///< Set Training Mode Flag       virtual bool      TrainMode(void)                  {  return bTrain;          }///< Get Training Mode Flag    

该标志特意在基准神经元级别实现。 如此在以后开发时能够启用舍弃相关的代码。

2.1. 为我们的模型创建一个新类

为了实现舍弃算法,赫兹量化来创建新的 CNeuronDropoutOCL 类,它将包含在我们的模型当中作为单独的层。 新类将直接继承自 CNeuronBaseOCL 基准神经元类。 在受保护模块中声明变量:


  • OutProbability — 指定神经元的舍弃概率。

  • OutNumber  — 神经元的舍弃数量。

  • dInitValue  — 掩码向量初始化值;在本文的理论部分,该系数被指定为 1/q。

另外,声明两个指向类的指针:


  • DropOutMultiplier  — 舍弃向量。

  • PrevLayer  — 指向上一层对象的指针;它在测试和实际应用时会用到。

class CNeuronDropoutOCL    :  public   CNeuronBaseOCL  { protected:   CNeuronBaseOCL    *PrevLayer;   double            OutProbability;   double            OutNumber;   CBufferDouble     *DropOutMultiplier;   double            dInitValue; //---   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL);               ///<\brief Feed Forward method of calling kernel ::FeedForward().@param NeuronOCL Pointer to previous layer.   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) {return true;}        ///< Method for updating weights.@param NeuronOCL Pointer to previous layer. //---   int               RND(void)   { xor128; return (int)((double)(Neurons()-1)/UINT_MAX*rnd_w);  }   ///< Generates a random neuron position to turn off public:                     CNeuronDropoutOCL(void);                    ~CNeuronDropoutOCL(void); //---   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint numNeurons,double out_prob, ENUM_OPTIMIZATION optimization_type);    ///< Method of initialization class.@param[in] numOutputs Number of connections to next layer.@param[in] myIndex Index of neuron in layer.@param[in] open_cl Pointer to #COpenCLMy object. #param[in] numNeurons Number of neurons in layer #param[in] out_prob Probability of neurons shutdown @param optimization_type Optimization type (#ENUM_OPTIMIZATION)@return Boolen result of operations. //---   virtual int       getOutputIndex(void)          {  return (bTrain ? Output.GetIndex() : PrevLayer.getOutputIndex());             }  ///< Get index of output buffer @return Index   virtual int       getGradientIndex(void)        {  return (bTrain ? Gradient.GetIndex() : PrevLayer.getGradientIndex());          }  ///< Get index of gradient buffer @return Index   //---   virtual int       getOutputVal(double &values[])   {  return (bTrain ? Output.GetData(values) : PrevLayer.getOutputVal(values)); }  ///< Get values of output buffer @param[out] values Array of data @return number of items   virtual int       getOutputVal(CArrayDouble *values)   {  return (bTrain ? Output.GetData(values) : PrevLayer.getOutputVal(values)); }  ///< Get values of output buffer @param[out] values Array of data @return number of items   virtual int       getGradient(double &values[])    {  return (bTrain ? Gradient.GetData(values) : PrevLayer.getGradient(values));    }  ///< Get values of gradient buffer @param[out] values Array of data @return number of items   virtual CBufferDouble   *getOutput(void)           {  return (bTrain ? Output : PrevLayer.getOutput());      }                 ///< Get pointer of output buffer @return Pointer to object   virtual CBufferDouble   *getGradient(void)         {  return (bTrain ? Gradient : PrevLayer.getGradient());  }                 ///< Get pointer of gradient buffer @return Pointer to object //---   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL);          ///< Method to transfer gradient to previous layer by calling kernel ::CalcHiddenGradient(). @param NeuronOCL Pointer to next layer.   //---   virtual bool      Save(int const file_handle);///< Save method @param[in] file_handle handle of file @return logical result of operation   virtual bool      Load(int const file_handle);///< Load method @param[in] file_handle handle of file @return logical result of operation //---   virtual int       Type(void)        const                      {  return defNeuronDropoutOCL;                }///< Identificator of class.@return Type of class  };

您必须熟悉类方法的清单,因为它们都会覆盖父类的方法。 唯一排除在外的是 RND 方法,它用来生成均匀分布的伪随机数。 在文章的第十三部分中已讲述过该方法的算法。 在我们的神经网络里,为了确保所有对象中数值的最大可能随机性,伪随机序列生成器在实现时以宏替换来定义全局变量。

#define xor128 rnd_t=(rnd_x^(rnd_x<<11)); \               rnd_x=rnd_y; \               rnd_y=rnd_z; \               rnd_z=rnd_w; \               rnd_w=(rnd_w^(rnd_w>>19))^(rnd_t^(rnd_t>>8)) uint rnd_x=MathRand(), rnd_y=MathRand(), rnd_z=MathRand(), rnd_w=MathRand(), rnd_t=0;

所提议算法将生成一个范围在 [0,UINT_MAX=4294967295] 内的整数序列。 因此,在伪随机序列生成器方法中,宏替换执行之后,将结果值常规化为序列的大小。

int               RND(void)   { xor128; return (int)((double)(Neurons()-1)/UINT_MAX*rnd_w);  }

如果您阅读过本系列中的早前文章,您可能已经注意到,在以前的版本中,赫兹量化没有覆盖来自其他对象的操控类数据缓冲区的方法。 当神经元访问上一层或下一层的数据时,这些方法可在神经网络的各层之间交换数据。

选择该解决方案是为了在实际应用中优化神经网络的运行。 不要忘记仅在神经网络训练时才会用到舍弃层。 在测试和以后的应用期间,会禁用此算法。 通过覆盖数据缓冲区的访问方法,赫兹量化启用略过舍弃层。 所有被覆盖的方法都应遵循相同的原理。 取代复制数据,赫兹量化实现了用上一层缓冲区替换舍弃层缓冲区。 因此,在以后的操作期间,含有舍弃层的神经网络在速度上可比没有舍弃层的类似网络,而我们在训练阶段已获得了神经元舍弃的所有优势。

virtual int       getOutputIndex(void)     {  return (bTrain ? Output.GetIndex() : PrevLayer.getOutputIndex());      }

在附件中可找到所有类方法的完整代码。

2.2. 前馈

传统上,赫兹量化在 feedForward 方法中实现前馈验算。 在方法伊始,检查接收到的指向神经网络上一层的指针,和指向 OpenCL 对象的指针的有效性。 此后,保存上一层所用的激活函数,和指向上一层对象的指针。 对于神经网络实际操作模式,舍弃层的前馈验算到此结束。 以后尝试从下一层访问该层将激活上述替换数据缓冲区的机制。

bool CNeuronDropoutOCL::feedForward(CNeuronBaseOCL *NeuronOCL)  {   if(CheckPointer(OpenCL)==POINTER_INVALID || CheckPointer(NeuronOCL)==POINTER_INVALID)      return false; //---   activation=(ENUM_ACTIVATION)NeuronOCL.Activation();   PrevLayer=NeuronOCL;   if(!bTrain)      return true;

后续迭代仅与神经网络训练模式相关。 首先,生成一个掩码向量,在其中,赫兹量化需定义在此步骤中舍弃的神经元。 将掩码写入 DropOutMultiplier 缓冲区中,检查之前创建对象的可用性,并在必要时创建一个新对象。 用初始值初始化缓冲区。 为了降低计算量,赫兹量化以递增的因子 1/q 来初始化缓冲区。

  if(CheckPointer(DropOutMultiplier)==POINTER_INVALID)      DropOutMultiplier=new CBufferDouble();   if(!DropOutMultiplier.BufferInit(NeuronOCL.Neurons(),dInitValue))      return false;   for(int i=0;i<OutNumber;i++)     {      uint p=RND();      double val=DropOutMultiplier.At(p);      if(val==0 || val==DBL_MAX)        {         i--;         continue;        }      if(!DropOutMultiplier.Update(RND(),0))         return false;     }

缓冲区初始化后,规划一个循环,而其重复次数等于要舍弃的神经元数量。 缓冲区中随机选择的元素将以零值替换。 为避免在一个单元内两次写入 “0” 的风险,在循环内部实现额外检查。

生成掩码后,直接在 GPU 内存中创建一个缓冲区,并传输数据。


量化交易软件:神经网络变得轻松12舍弃的评论 (共 条)

分享到微博请遵守国家法律