城市直播房产教育博客汽车
投稿投诉
汽车报价
买车新车
博客专栏
专题精品
教育留学
高考读书
房产家居
彩票视频
直播黑猫
投资微博
城市上海
政务旅游

神经网络变得轻松(第五部分)OpenCL中的多线程计算

3月7日 牵手手投稿
  内容概述1。MQL5中如何组织多线程计算2。神经网络中的多线程计算3。利用OpenCL实现多线程计算3。1。前馈内核3。2。反向传播内核3。3。更新权重3。4。创建主程序的类3。5。创建基础神经元类来操控OpenCL3。6。CNet类中的附加4。测试结束语链接本文中用到的程序
  概述
  在之前的文章中,我们讨论过某些类型的神经网络实现。如您所见,神经网络由大量相同类型的神经元组成,并在其中执行相同的操作。然而,网络拥有的神经元越多,它消耗的计算资源也就越多。结果就是,训练神经网络所需的时间呈指数增长,这是因为在隐藏层添加一个神经元,需要了解上一层和下一层中所有神经元的连接。有一种减少神经网络训练时间的方法。现代计算机的多线程功能可以同时计算多个神经元。由于线程数量的增加,时间将可预见地大大减少。
  1。MQL5中如何组织多线程计算
  MetaTrader5终端具有多线程体系架构。终端中的线程分布受到严格控制。根据文档,脚本和智能交易系统是在单独的线程中启动。至于指示器,每个品种会提供单独的线程。即时报价处理和历史记录同步于指标所在线程中执行。这意味着终端只为每个智能交易系统分配一个线程。某些计算可以在指标中执行,其可提供一个额外的线程。然而,指标中过多的计算会减慢与即时报价数据处理相关的终端操作,这可能会导致针对市场状况的失控。这种状况能对EA性能产生负面影响。
  不过,有一个解决方案。MetaTrader5开发人员为其提供了利用第三方DLL的能力。在多线程体系结构上创建动态库会自动为函数库中实现的操作提供多线程支持。在此,EA操作以及与函数库之间的数据交换依然保留在智能交易系统的主线程之中。
  第二个选项是利用OpenCL技术。在这种情况下,我们可以用标准方法在支持该技术的处理器和视频卡上规划多线程计算。对于此选项,程序代码不依赖所使用的设备。该站点上有许多与OpenCL技术有关的出版物。特别是,该主题在〔第五篇〕和〔第六篇〕文章里已有很好介绍。
  因此,我决定使用OpenCL。首先,运用该技术时,用户不需要额外配置终端,并为第三方DLL设置权限。其次,这样的智能交易系统可通过一个EX5文件在终端之间传送。这允许将计算部分转移到视频卡,因视频卡通常在终端操作期间处于空闲状态。
  2。神经网络中的多线程计算
  我们已选择了该技术。现在,我们需要决定将计算部分拆分为线程的过程。您还记得完全连接感知器算法吗?信号顺序从输入层转至隐藏层,然后转至输出层。没必要为每个层分配线程,因为计算必须按顺序执行。直到收到来自上一层的结果之后,该层才能开始计算。一层中独立神经元的计算不依赖该层中其他神经元的计算结果。这意味着我们可为每个神经元分配单独的线程,并发送一整层的所有神经元进行并行计算。
  深入到一个神经元的运算,我们可以研究把计算输入值与权重系数的乘积并行化的可能性。不过,结果值的进一步求和,以及计算激活函数的数值被合并到一个线程当中。我决定利用vector函数在单个OpenCL内核中实现这些操作。
  类似的方法也用来拆分反馈线程。其实现如下所示。3。利用OpenCL实现多线程计算
  选择了基本方法后,我们就能够继续实现了。我们从创建内核(可执行的OpenCL函数)开始。根据以上逻辑,我们将创建4个内核。3。1。前馈内核。
  与之前文章中讨论的方法类似,我们创建一个前馈推算内核FeedForward。
  不要忘记内核是在每个线程中运行的函数。调用内核时需设置此类线程的数量。在内核内部的操作是特定循环内的嵌套操作;循环的迭代次数等于被调用线程的次数。如此,在前馈内核中,我们可以指定计算独立神经元状态的操作,并可从主程序调用内核时以指定神经元数量。
  内核从参数中接收权重矩阵,输入数据数组和输出数据数组的引用,以及输入数组的元素数量,和激活函数类型。请注意,OpenCL中的所有数组都是一维的。因此,如果在MQL5中将二维数组用做权重系数,则此处我们需要计算初始位置的位移,以便读取第二个、及后续神经元的数据。kernelvoidFeedForward(globaldoublematrixw,globaldoublematrixi,globaldoublematrixo,intinputs,intactivation)
  在内核的开头,我们获得线程的序列号,其可判定所计算神经元的序列号。声明私密(内部)变量,包括向量变量inp和weight。还要定义我们的神经元权重的位移。{intigetglobalid(0);doublesum0。0;double4inp,intshift(inputs1)i;
  接下来,组织一个循环来获取输入值与其权重的乘积的合计。如上所述,我们用到4个元素inp和weight的向量来计算乘积合计。然而,内核接收的所有数组并非都是4的倍数,因此缺少的元素应替换为零值。注意输入数据向量中的一个1它对应于贝叶斯偏差的权重。for(intk0;kk4){switch(inputsk){case0:inp(double4)(1,0,0,0);weight(double4)(matrixw〔shiftk〕,0,0,0);case1:inp(double4)(matrixi〔k〕,1,0,0);weight(double4)(matrixw〔shiftk〕,matrixw〔shiftk1〕,0,0);case2:inp(double4)(matrixi〔k〕,matrixi〔k1〕,1,0);weight(double4)(matrixw〔shiftk〕,matrixw〔shiftk1〕,matrixw〔shiftk2〕,0);case3:inp(double4)(matrixi〔k〕,matrixi〔k1〕,matrixi〔k2〕,1);weight(double4)(matrixw〔shiftk〕,matrixw〔shiftk1〕,matrixw〔shiftk2〕,matrixw〔shiftk3〕);default:inp(double4)(matrixi〔k〕,matrixi〔k1〕,matrixi〔k2〕,matrixi〔k3〕);weight(double4)(matrixw〔shiftk〕,matrixw〔shiftk1〕,matrixw〔shiftk2〕,matrixw〔shiftk3〕);}sumdot(inp,weight);}
  获得乘积之和后,计算激活函数,并将结果写入输出数据数组。switch(activation){case0:sumtanh(sum);case1:sumpow((1exp(sum)),1);}matrixo〔i〕}3。2。反向传播内核。
  为反向传播误差梯度创建两个内核。在第一个CaclOutputGradient中计算输出层误差。它的逻辑很简单。所获参考值在激活函数的数值范围进行常规化。然后,将参考值和实际值之间的差乘以激活函数的导数。将结果值写入梯度数组的相应单元格中。kernelvoidCaclOutputGradient(globaldoublematrixt,globaldoublematrixo,globaldoublematrixig,intactivation){intigetglobalid(0);doubletemp0;doubleoutmatrixo〔i〕;switch(activation){case0:tempclamp(matrixt〔i〕,1。0,1。0)temptemp(1out)(1(out1?0。99:out));case1:tempclamp(matrixt〔i〕,0。0,1。0)temptemp(out0?0。01:out)(1(out1?0。99:out));}matrixig〔i〕}
  在第二个内核中,在CaclHiddenGradient里计算隐藏层神经元的误差梯度。内核构建类似于上述的前馈内核。它还用到了向量运算。区别在于前馈推算中以下一层的梯度向量替代前一层的输出值,并采用不同的权重矩阵。而且,代替计算激活函数,结果合计是与激活函数导数的乘积。内核代码给出如下。kernelvoidCaclHiddenGradient(globaldoublematrixw,globaldoublematrixg,globaldoublematrixo,globaldoublematrixig,intoutputs,intactivation){intigetglobalid(0);doublesum0;doubleoutmatrixo〔i〕;double4grad,intshift(outputs1)i;for(intk0;k4){switch(outputsk){case0:grad(double4)(1,0,0,0);weight(double4)(matrixw〔shiftk〕,0,0,0);case1:grad(double4)(matrixg〔k〕,1,0,0);weight(double4)(matrixw〔shiftk〕,matrixw〔shiftk1〕,0,0);case2:grad(double4)(matrixg〔k〕,matrixg〔k1〕,1,0);weight(double4)(matrixw〔shiftk〕,matrixw〔shiftk1〕,matrixw〔shiftk2〕,0);case3:grad(double4)(matrixg〔k〕,matrixg〔k1〕,matrixg〔k2〕,1);weight(double4)(matrixw〔shiftk〕,matrixw〔shiftk1〕,matrixw〔shiftk2〕,matrixw〔shiftk3〕);default:grad(double4)(matrixg〔k〕,matrixg〔k1〕,matrixg〔k2〕,matrixg〔k3〕);weight(double4)(matrixw〔shiftk〕,matrixw〔shiftk1〕,matrixw〔shiftk2〕,matrixw〔shiftk3〕);}sumdot(grad,weight);}switch(activation){case0:sumclamp(sumout,1。0,1。0);sum(sumout)(1out)(1(out1?0。99:out));case1:sumclamp(sumout,0。0,1。0);sum(sumout)(out0?0。01:out)(1(out1?0。99:out));}matrixig〔i〕}3。3。更新权重。
  我们创建另一个更新权重的内核UpdateWeights。更新每个独立权重的过程不依赖于某个神经元之内及来自外部神经元的权重。这允许发送批量任务,同时并行计算一层中所有神经元的所有权重。在这种情况下,我们在线程的二维空间中运行一个内核:第一维表示神经元的序列号,第二维表示神经元内的连接数。以下代码显示的是内核代码的前两行,其中它以二维接收线程ID。kernelvoidUpdateWeights(globaldoublematrixw,globaldoublematrixg,globaldoublematrixi,globaldoublematrixdw,intinputs,doublelearningrates,doublemomentum){intigetglobalid(0);intjgetglobalid(1);intwii(inputs1)j;doubledeltalearningratesmatrixg〔i〕(jinputs?matrixi〔j〕:1)momentummatrixdw〔wi〕;matrixdw〔wi〕matrixw〔wi〕};
  接下来,确定权重数组中已更新权重的偏移量,计算其增量(变化),然后将结果值添加到增量数组之中,并将其添加到当前权重里。
  所有内核都放在单独的文件NeuroNet。cl之中,该文件将作为资源连接到主程序。resourceNeuroNet。classtringclprogram3。4。创建主程序类。
  创建内核之后,我们返回MQL5,并开始操控主程序代码。主程序和内核之间的数据通过一维数组作为缓冲区进行交换(这在〔文章第五部分〕里的解释)。为了在主程序端规划此类缓冲区,我们来创建CBufferDouble类。该类包含指向操控OpenCL的类对象引用,以及当用OpenCL创建时接收的缓冲区索引。classCBufferDouble:publicCArrayDouble{protected:COpenCLMyOpenCL;intmmyIpublic:CBufferDouble(void);CBufferDouble(void);virtualboolBufferInit(uintcount,doublevalue);virtualboolBufferCreate(COpenCLMyopencl);virtualboolBufferFree(void);virtualboolBufferRead(void);virtualboolBufferWrite(void);virtualintGetData(doublevalues〔〕);virtualintGetData(CArrayDoublevalues);virtualintGetIndex(void){returnmmyI}virtualintType(void)const{returndefBufferD}};
  请注意,一旦创建了OpenCL缓冲区,其句柄将被返回。该句柄被存储在COpenCL类的mbuffers数组当中。在mmyIndex变量中,仅存储指定数组中的索引。这是因为整个COpenCL类操作都会用到指定的此类索引,而非内核或缓冲区句柄。还应注意,COpenCL类原装操作算法需要初始指定所用缓冲区编号,进而按指定索引创建缓冲区。在我们的例子中,我们将在创建神经层时动态添加缓冲区。这就是为何COpenCLMy类是从COpenCL派生而来的。该类仅包含一个附加方法。您可以在附件中找到其代码。
  在CBufferDouble类中创建了以下操控缓冲区的方法:BufferInit按照指定值初始化缓冲区数组BufferCreate在OpenCL中创建一个缓冲区BufferFree在OpenCL中删除一个缓冲区BufferRead从OpenCL缓冲区读取数据到数组BufferWrite将数组中的数据写入OpenCL缓冲区GetData根据请求获取数组数据。它以两种变体实现,可将数据返回到数组和CArrayDouble类。GetIndex返回缓冲区索引
  所有方法的体系结构都很简单,它们的代码占用12行。下面的附件中提供了所有方法的完整代码。3。5。创建操控OpenCL的神经元基类。
  我们来继续研究CNeuronBaseOCL类,它包括主要的附加项和操作算法。很难将创建的对象命名为神经元,因为它包含了整个完全连接神经层的工作。对于早前研究的卷积层和LSTM模块也是如此。但这种方式可保留以前构建的神经网络体系结构。
  类CNeuronBaseOCL包含一个指向COpenCLMy类对象的指针,和四个缓冲区:输出值、权重系数矩阵、最后的权重增量和误差梯度。classCNeuronBaseOCL:publicCObject{protected:COpenCLMyOpenCL;CBufferDoubleOCBufferDoubleWCBufferDoubleDeltaWCBufferDoubleG
  同样,声明学习和动量系数,层中神经元的序数,以及激活函数类型。intmmyIENUMACTIVATION
  在类的受保护模块中再添加三个方法:前馈、隐藏梯度计算,和更新权重矩阵。virtualboolfeedForward(CNeuronBaseOCLNeuronOCL);virtualboolcalcHiddenGradients(CNeuronBaseOCLNeuronOCL);virtualboolupdateInputWeights(CNeuronBaseOCLNeuronOCL);
  在公开部分中,声明类构造函数,和析构函数,神经元初始化方法,和指定激活函数的方法。public:CNeuronBaseOCL(void);CNeuronBaseOCL(void);virtualboolInit(uintnumOutputs,uintmyIndex,COpenCLMyopencl,uintnumNeurons);virtualvoidSetActivationFunction(ENUMACTIVATIONvalue){}
  为了从神经元外部访问数据,声明获取缓冲区索引的方法(在调用内核时会用到它们),和从缓冲区以数组形式接收当前信息的方法。此外,添加轮询神经元数量和激活函数的方法。virtualintgetOutputIndex(void){returnOutput。GetIndex();}virtualintgetGradientIndex(void){returnGradient。GetIndex();}virtualintgetWeightsIndex(void){returnWeights。GetIndex();}virtualintgetDeltaWeightsIndex(void){returnDeltaWeights。GetIndex();}virtualintgetOutputVal(doublevalues〔〕){returnOutput。GetData(values);}virtualintgetOutputVal(CArrayDoublevalues){returnOutput。GetData(values);}virtualintgetGradient(doublevalues〔〕){returnGradient。GetData(values);}virtualintgetWeights(doublevalues〔〕){returnWeights。GetData(values);}virtualintNeurons(void){returnOutput。Total();}virtualENUMACTIVATIONActivation(void){}
  当然,还可以创建前馈传递、误差梯度计算和更新权重矩阵的调度方法。不要忘记重写保存和读取数据的虚拟函数。virtualboolfeedForward(CObjectSourceObject);virtualboolcalcHiddenGradients(CObjectTargetObject);virtualboolcalcOutputGradients(CArrayDoubleTarget);virtualboolupdateInputWeights(CObjectSourceObject);virtualboolSave(intconstfilehandle);virtualboolLoad(intconstfilehandle);virtualintType(void)const{returndefNeuronBaseOCL;}};
  我们来研究构造方法的算法。类的构造函数和析构函数非常简单。附件中提供了它们的代码。先看一下类的初始化函数。该方法从参数中接收下一层神经元的数量,神经元的序数,指向COpenCLMy类对象的指针,以及要创建的神经元的数量。
  请注意,该方法从参数中接收指向COpenCLMy类对象的指针,但不会实例化该类内部的对象。这样可以确保在EA操作期间仅用COpenCLMy对象的一个实例。所有内核和数据缓冲区将在一个对象中创建,因此我们无需为了在神经网络各层之间传递数据而浪费时间。它们均直接访问同一数据缓冲区。
  在方法开始时,检查指向COpenCLMy类对象的指针的有效性,并确保至少已创建了一个神经元。接下来,创建缓冲区对象的实例,以初始值初始化数组,并在OpenCL中创建缓冲区。Output缓冲区的大小等于所要创建的神经元数量,且梯度缓冲区的大小应多1个元素。权重矩阵及其增量缓冲区的大小等于梯度缓冲区大小与下一层神经元数量的乘积。由于输出层的乘积将为0,因此不会为此层创建缓冲区。boolCNeuronBaseOCL::Init(uintnumOutputs,uintmyIndex,COpenCLMyopencl,uintnumNeurons){if(CheckPointer(opencl)POINTERINVALIDnumNeurons0)OpenCLif(CheckPointer(Output)POINTERINVALID){OutputnewCBufferDouble();if(CheckPointer(Output)POINTERINVALID)}if(!Output。BufferInit(numNeurons,1。0))if(!Output。BufferCreate(OpenCL))if(CheckPointer(Gradient)POINTERINVALID){GradientnewCBufferDouble();if(CheckPointer(Gradient)POINTERINVALID)}if(!Gradient。BufferInit(numNeurons1,0。0))if(!Gradient。BufferCreate(OpenCL))if(numOutputs0){if(CheckPointer(Weights)POINTERINVALID){WeightsnewCBufferDouble();if(CheckPointer(Weights)POINTERINVALID)}intcount(int)((numNeurons1)numOutputs);if(!Weights。Reserve(count))for(inti0;i){doubleweigh(MathRand()1)32768。00。5;if(weigh0)weigh0。001;if(!Weights。Add(weigh))}if(!Weights。BufferCreate(OpenCL))if(CheckPointer(DeltaWeights)POINTERINVALID){DeltaWeightsnewCBufferDouble();if(CheckPointer(DeltaWeights)POINTERINVALID)}if(!DeltaWeights。BufferInit(count,0))if(!DeltaWeights。BufferCreate(OpenCL))}}
  feedForward调度程序方法类似于CNeuronBase类里同名的方法。现在,此处仅指定一个种类的神经元,但以后可以添加更多的种类。boolCNeuronBaseOCL::feedForward(CObjectSourceObject){if(CheckPointer(SourceObject)POINTERINVALID)CNeuronBaseOCLtempNULL;switch(SourceObject。Type()){casedefNeuronBaseOCL:tempSourceOreturnfeedForward(temp);}}
  在feedForward(CNeuronBaseOCLNeuronOCL)方法中直接调用OpenCL内核。在方法伊始,检查指向COpenCLMy类对象的指针和接收到的指向神经网络前一层的指针有效性。boolCNeuronBaseOCL::feedForward(CNeuronBaseOCLNeuronOCL){if(CheckPointer(OpenCL)POINTERINVALIDCheckPointer(NeuronOCL)POINTERINVALID)
  指示线程空间的一维性,并设置所需线程的数量等于神经元的数量。uintglobalworkoffset〔1〕{0};uintglobalworksize〔1〕;globalworksize〔0〕Output。Total();
  接下来,设置指向所用数据缓冲区的指针,和内核操作参数。OpenCL。SetArgumentBuffer(defkFeedForward,defkffmatrixw,NeuronOCL。getWeightsIndex());OpenCL。SetArgumentBuffer(defkFeedForward,defkffmatrixi,NeuronOCL。getOutputIndex());OpenCL。SetArgumentBuffer(defkFeedForward,defkffmatrixo,Output。GetIndex());OpenCL。SetArgument(defkFeedForward,defkffinputs,NeuronOCL。Neurons());OpenCL。SetArgument(defkFeedForward,defkffactivation,(int)activation);
  之后,调用内核。if(!OpenCL。Execute(defkFeedForward,1,globalworkoffset,globalworksize))
  我打算于此结束,但在测试过程中遇到了一个问题:COpenCL::Execute方法并未启动内核,而只是将其排队。执行本身则是在尝试读取内核结果时才会发生。这就是为什么在退出该方法之前必须将处理结果加载到数组中的原因。Output。BufferRead();}
  启动其他内核的方法与上述算法相似。附件中提供了所有方法和类的完整代码。3。6。CNet类中的附加。
  一旦创建了所有必需的类,我们需针对主神经网络的CNet类进行一些调整。
  在类构造函数中,我们需要为COpenCLMy类实例添加创建和初始化。不要忘记删除析构函数中的类对象。openclnewCOpenCLMy();if(CheckPointer(opencl)!POINTERINVALID!opencl。Initialize(clprogram,true))
  此外,在构造函数中往层里添加神经元的代码模块中,加入代码来创建和初始化早前创建的CNeuronBaseOCL类对象。if(CheckPointer(opencl)!POINTERINVALID){CNeuronBaseOCLneuronoclNULL;switch(desc。type){casedefNeuron:casedefNeuronBaseOCL:neuronoclnewCNeuronBaseOCL();if(CheckPointer(neuronocl)POINTERINVALID){}if(!neuronocl。Init(outputs,0,opencl,desc。count)){}neuronocl。SetActivationFunction(desc。activation);if(!temp。Add(neuronocl)){}neuronoclNULL;default:}}
  进而,在构造函数中添加创建OpenCL内核。if(CheckPointer(opencl)POINTERINVALID)createkernelsopencl。SetKernelsCount(4);opencl。KernelCreate(defkFeedForward,FeedForward);opencl。KernelCreate(defkCaclOutputGradient,CaclOutputGradient);opencl。KernelCreate(defkCaclHiddenGradient,CaclHiddenGradient);opencl。KernelCreate(defkUpdateWeights,UpdateWeights);
  在CNet::feedForward方法中添加将源数据写入缓冲区的代码{CNeuronBaseOCLneuronoclcurrent。At(0);doublearray〔〕;inttotaldatainputVals。Total();if(ArrayResize(array,totaldata)0)for(intd0;d)array〔d〕inputVals。At(d);if(!opencl。BufferWrite(neuronocl。getOutputIndex(),array,0,0,totaldata))}
  还要为新创建的类CNeuronBaseOCL添加相应的方法调用。for(intl1;llayers。Total();l){currentlayers。At(l);if(CheckPointer(current)POINTERINVALID)if(CheckPointer(opencl)!POINTERINVALID){CNeuronBaseOCLcurrentoclcurrent。At(0);if(!currentocl。feedForward(previous。At(0)))}
  对于反向传播过程,我们为其创建一个新方法CNet::backPropOCL。它的算法类似于第一篇文章中所述的主要方法CNet::backProp。voidCNet::backPropOCL(CArrayDoubletargetVals){if(CheckPointer(targetVals)POINTERINVALIDCheckPointer(layers)POINTERINVALIDCheckPointer(opencl)POINTERINVALID)CLayercurrentLayerlayers。At(layers。Total()1);if(CheckPointer(currentLayer)POINTERINVALID)doubleerror0。0;inttotaltargetVals。Total();doubleresult〔〕;CNeuronBaseOCLneuroncurrentLayer。At(0);if(neuron。getOutputVal(result)total)for(intn0;ntotal!IsStopped();n){doubletargettargetVals。At(n);doubledelta(target1?1:target1?1:target)result〔n〕;}errorsqrt(error);recentAverageError(errorrecentAverageError)recentAverageSmoothingFif(!neuron。calcOutputGradients(targetVals));CalcHiddenGradientsCObjecttempNULL;totallayers。Total();for(intlayerNumtotal2;layerNum0;layerNum){CLayernextLayercurrentLcurrentLayerlayers。At(layerNum);neuroncurrentLayer。At(0);neuron。calcHiddenGradients(nextLayer。At(0));}CLayerprevLayerlayers。At(total1);for(intlayerNumtotal1;layerNum0;layerNum){currentLayerprevLprevLayerlayers。At(layerNum1);neuroncurrentLayer。At(0);neuron。updateInputWeights(prevLayer。At(0));}}
  针对getResult方法略微进行了一些修改。if(CheckPointer(opencl)!POINTERINVALIDoutput。At(0)。Type()defNeuronBaseOCL){CNeuronBaseOCLtempoutput。At(0);temp。getOutputVal(resultVals);}
  附件中提供了所有方法和函数的完整代码。4。测试
  采用与之前测试相同的条件,测试所创建类的操作。已创建FractalOCLEA用于测试,它与先前创建的Fractal2完全相同。在H1时间帧,EURUSD货币对上测试了神经网络的训练。将20根烛条的数据输入到神经网络。训练时采用最近两年的数据。实验在支持OpenCL的Intel(R)Core(TM)2DuoCPUT57502。00GHz设备上运行。
  在5小时27分钟的测试中,利用OpenCL技术的EA共执行了75个训练时期。对于12405根烛条的区间,这平均需要4分22秒。未利用OpenCL技术的同一智能交易系统,在同一台笔记本电脑上的相同神经网络体系结构下,每个时期平均要花费40分钟48秒。如此,利用OpenCL可以令学习过程快9。35倍。
  结束语
  本文演示了利用OpenCL技术在神经网络中规划多线程计算的可能性。测试表明,在同一CPU上,性能几乎提高了10倍。期望利用GPU进一步提高算法性能在这种情况下,将计算转移到兼容的GPU不需要修改智能交易系统代码。
  总体而言,结果证明该方向的进一步发展具有良好的前景。
  链接神经网络变得轻松神经网络变得轻松(第二部分):网络训练和测试神经网络变得轻松(第三部分):卷积网络神经网络变得轻松(第四部分):循环网络OpenCL:通往并行世界的桥梁OpenCL:从初学到精通编程
  本文中用到的程序
  名称
  类型
  说明
  1hrFractalOCL。mq5
  智能交易系统
  利用OpenCL技术的含有分类神经网络(输出层中有3个神经元)的智能交易系统
  2hrNeuroNet。mqh
  类库
  用于创建神经网络的类库
  3hrNeuroNet。cl
  代码库
  OpenCL程序代码库
投诉 评论 转载

人生没有草稿壹人生没有草稿,写完今天的这张就不可能再有同样的另一张。www。摄影英国摄影师Carrie人生没有草稿,写完今天的这张就不可能再有同样的另一张。平常的日子总会……有滋有味造句用有滋有味造句大全(61)给生活加点佐料,有滋有味;给人生加点亮色,丰富多彩;给年华加点活力,神采飞扬;给生命加点动力,精彩无限;给朋友送份问候,真挚无限:愿你开怀!(62)妇女节到了,祝……2023连云港文化旅游消费季100余项新春文化旅游活动邀您过为了给广大市民、游客提供丰富多彩的文化旅游大餐,恢复和扩大文旅消费,连云港市文广旅局围绕打卡旅游休闲打开欢乐春节主题,精心策划推出100余项新春文化旅游活动,涵盖春节民俗、文艺……过犹不及高考前饮食进补注意点学生们马上就要奔赴高考这个特殊的战场了,在高考前,家长们在饮食上,真是尽心尽力,为考生准备各样有营养的食物帮学生进补。但是须知,进补也讲究技巧和方法,补得恰到好处方好。下面,佰……差不多林子方去相亲,对方姓柳,年纪三十余,风姿绰约!林子方一见大喜,遂问:柳小姐,你是做什么的?柳MM:呵呵,我已经不算小姐了吧!林子方会意:那我应该称呼你为女士!……我的绝招学了《绝招》这篇课文,二福和小柱子都怀有绝活,历害的不得了,我很羡慕他们。我可没那么能干,啥绝活都没有。就在双休日,我躺在沙发上看着电视,不过双手也没闲着,左手是彩纸纸、……美洲杯升级南美足球有望继续与欧洲足球一拼高下美洲足联联手打造世界足球最强洲近日2024美洲杯官宣举办地为美国,可以看成是美加墨2026年世界杯的前哨战,更表明了美洲杯赛事的提质升级,可以预想欧美足球谁最强的比拼将更……美妙造句用美妙造句大全151啊!春天的天空是最好的天空!好想在你那浩浩长空中遨翔,美妙而又清静,有鸟儿的悦儿歌声,陪伴着我,有云彩的轻柔身体,温暖着我,更有那美好的蔚蓝色彩,使我有一个甜美的梦。……了不起的爸爸我的爸爸长着浓眉大眼,圆得像个大皮球。他个子也挺高的,但绝对没有高楼那么高。他是一个了不起的爸爸!想知道为什么吗?那就让我慢慢告诉你吧!我的爸爸开车很了不起,不信你可以瞧……名人传读书笔记【第一篇】名人传读书笔记600字自由和进步是艺术的目标,正如它们是我们整个人生的目标一样,这是伟大的音乐家贝多芬的名言。今年寒假,我读了一本名著名人传》。这本名著深……神经网络变得轻松(第五部分)OpenCL中的多线程计算内容概述1。MQL5中如何组织多线程计算2。神经网络中的多线程计算3。利用OpenCL实现多线程计算3。1。前馈内核3。2。反向传播内核3。3。更新权重3。4。创建主程序的类3……关于信用卡逾期银行那点儿猫腻儿亲身经历告诉大家如何维权还款日是21号赶上过年,我又没设置自动还款,因为月薪一般都是20号,习惯发完月薪先还信用卡。但赶上过年忘了,22号收到短信通知告知扣除了一笔逾期违约金,遂赶紧22号还信用卡。本……
波心的爱再晕也要回荡滑雪爱好者竞逐中国瓦萨国际滑雪节老耕牛和小肉牛的故事华东七地人均存款20强城市苏州第4,舟山超青岛,合肥20最敬业外援?沪媒忧心奥斯卡的竞技状态,球迷批上港当冤大头真实场景到虚拟环境的重现方法如何做可食用的饼干面团注意比赛时间!CBA广东vs天津,重点盯防外线,易建联或将轮芙蓉国评论外贸进出口稳增长,中国经济活力强金奖造句用金奖造句大全儿童可以接受紫外线光疗吗?什么时候病毒清零?

友情链接:中准网聚热点快百科快传网快生活快软网快好知文好找江西南阳嘉兴昆明铜陵滨州广东西昌常德梅州兰州阳江运城金华广西萍乡大理重庆诸暨泉州安庆南充武汉辽宁