神经网络变得轻松:OpenCL 中的多线程计算

文章介绍了如何通过MetaTrader5的多线程功能和OpenCL技术来提高神经网络的计算效率。通过为每个神经元分配单独的线程,实现了并行计算,减少了训练时间。作者详细阐述了如何创建和使用OpenCL内核进行前馈、反向传播和权重更新的计算过程,展示了如何在C++代码中实现这一优化策略。
摘要由CSDN通过智能技术生成

在之前的文章中,我们讨论过某些类型的神经网络实现。 如您所见,神经网络由大量相同类型的神经元组成,并在其中执行相同的操作。 然而,网络拥有的神经元越多,它消耗的计算资源也就越多。 结果就是,训练神经网络所需的时间呈指数增长,这是因为在隐藏层添加一个神经元,需要了解上一层和下一层中所有神经元的连接。 有一种减少神经网络训练时间的方法。 现代计算机的多线程功能可以同时计算多个神经元。 由于线程数量的增加,时间将可预见地大大减少。

1. MQL5 中如何组织多线程计算

MetaTrader 5 终端具有多线程体系架构。 终端中的线程分布受到严格控制。 根据文档,脚本和智能交易系统是在单独的线程中启动。 至于指示器,每个品种会提供单独的线程。 即时报价处理和历史记录同步于指标所在线程中执行。 这意味着终端只为每个智能交易系统分配一个线程。 某些计算可以在指标中执行,其可提供一个额外的线程。 然而,指标中过多的计算会减慢与即时报价数据处理相关的终端操作,这可能会导致针对市场状况的失控。 这种状况能对 EA 性能产生负面影响。

不过,有一个解决方案。 MetaTrader 5 开发人员为其提供了利用第三方 DLL 的能力。 在多线程体系结构上创建动态库会自动为函数库中实现的操作提供多线程支持。 在此,EA 操作以及与函数库之间的数据交换依然保留在智能交易系统的主线程之中。

第二个选项是利用 OpenCL 技术。 在这种情况下,我们可以用标准方法在支持该技术的处理器和视频卡上规划多线程计算。 对于此选项,程序代码不依赖所使用的设备。 该站点上有许多与 OpenCL 技术有关的出版物。 特别是,该主题在 [第五篇] 和 [第六篇] 文章里已有很好介绍。

因此,我决定使用 OpenCL。 首先,运用该技术时,用户不需要额外配置终端,并为第三方 DLL 设置权限。 其次,这样的智能交易系统可通过一个 EX5 文件在终端之间传送。 这允许将计算部分转移到视频卡,因视频卡通常在终端操作期间处于空闲状态。

2. 神经网络中的多线程计算

我们已选择了该技术。 现在,我们需要决定将计算部分拆分为线程的过程。 您还记得完全连接感知器算法吗? 信号顺序从输入层转至隐藏层,然后转至输出层。 没必要为每个层分配线程,因为计算必须按顺序执行。 直到收到来自上一层的结果之后,该层才能开始计算。 一层中独立神经元的计算不依赖该层中其他神经元的计算结果。 这意味着我们可为每个神经元分配单独的线程,并发送一整层的所有神经元进行并行计算。  

深入到一个神经元的运算,我们可以研究把计算输入值与权重系数的乘积并行化的可能性。 不过,结果值的进一步求和,以及计算激活函数的数值被合并到一个线程当中。 我决定利用 vector 函数在单个 OpenCL 内核中实现这些操作。

类似的方法也用来拆分反馈线程。 其实现如下所示。

3. 利用 OpenCL 实现多线程计算

选择了基本方法后,我们就能够继续实现了。 我们从创建内核(可执行的OpenCL函数)开始。 根据以上逻辑,我们将创建 4 个内核。

3.1. 前馈内核。

与之前文章中讨论的方法类似,我们创建一个前馈推算内核 FeedForward 

不要忘记内核是在每个线程中运行的函数。 调用内核时需设置此类线程的数量。 在内核内部的操作是特定循环内的嵌套操作;循环的迭代次数等于被调用线程的次数。如此,在前馈内核中,我们可以指定计算独立神经元状态的操作,并可从主程序调用内核时以指定神经元数量。

内核从参数中接收权重矩阵,输入数据数组和输出数据数组的引用,以及输入数组的元素数量,和激活函数类型。 请注意,OpenCL 中的所有数组都是一维的。 因此,如果在 MQL5 中将二维数组用做权重系数,则此处我们需要计算初始位置的位移,以便读取第二个、及后续神经元的数据。

__kernel void FeedForward(__global double *matrix_w,
                              __global double *matrix_i,
                              __global double *matrix_o,
                              int inputs, int activation)

在内核的开头,我们获得线程的序列号,其可判定所计算神经元的序列号。 声明私密(内部)变量,包括向量变量 inp  weight。 还要定义我们的神经元权重的位移。

  {
   int i=get_global_id(0);
   double sum=0.0;
   double4 inp, weight;
   int shift=(inputs+1)*i;

接下来,组织一个循环来获取输入值与其权重的乘积的合计。 如上所述,我们用到 4 个元素 inp  weight 的向量来计算乘积合计。 然而,内核接收的所有数组并非都是 4 的倍数,因此缺少的元素应替换为零值。 注意输入数据向量中的一个 “1” - 它对应于贝叶斯偏差的权重。

   for(int k=0; k<=inputs; k=k+4)
     {
      switch(inputs-k)
        {
         case 0:
           inp=(double4)(1,0,0,0);
           weight=(double4)(matrix_w[shift+k],0,0,0);
           break;
         case 1:
           inp=(double4)(matrix_i[k],1,0,0);
           weight=(double4)(matrix_w[shift+k],matrix_w[shift+k+1],0,0);
           break;
         case 2:
           inp=(double4)(matrix_i[k],matrix_i[k+1],1,0);
           weight=(double4)(matrix_w[shift+k],matrix_w[shift+k+1],matrix_w[shift+k+2],0);
           break;
         case 3:
           inp=(double4)(matrix_i[k],matrix_i[k+1],matrix_i[k+2],1);
           weight=(double4)(matrix_w[shift+k],matrix_w[shift+k+1],matrix_w[shift+k+2],matrix_w[shift+k+3]);
           break;
         default:
           inp=(double4)(matrix_i[k],matrix_i[k+1],matrix_i[k+2],matrix_i[k+3]);
           weight=(double4)(matrix_w[shift
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值