神经网络变得轻松(第五部分):OpenCL 中的多线程计算·综合运用

神经网络变得轻松(第五部分):OpenCL 中的多线程计算·综合运用

(3/3)· 从 MQL5 线程限制到视频卡并行,收尾篇给出可跑的完整类的拆解

案例拆解新手友好 第 3/3 篇

不少交易者以为 EA 只能靠主线程硬算神经网络,隐藏层多加几个神经元就卡到错过信号。其实显卡在大多数时候是闲着的,OpenCL 能把它利用起来跑前馈和反向传播。

神经网络层构建与OpenCL内核初始化

在 MT5 里用 OpenCL 加速神经网络,第一步是把每一层神经元塞进容器并挂好激活函数。代码片段里用了 temp.Add(neuron_ocl) 做 ownership 转移,如果 Add 失败就 delete 掉 neuron_ocl 和 temp 直接 return,避免 GPU 端对象泄漏。 default 分支直接 return 是个防御写法——遇到未定义层类型立刻中止,不往下跑无效计算。 内核创建阶段固定了 4 个 kernel:FeedForward、CaclOutputGradient、CaclHiddenGradient、UpdateWeights,对应前向与反向传播的四步。opencl.SetKernelsCount(4) 之后逐个 KernelCreate,少一个都可能导致后续 BufferWrite 或 feedForward 调用返回 false。 前向传播时先取输入层 neuron_ocl.getOutputIndex(),按 inputVals.Total() 的大小 ArrayResize 本地 array,再一次性 BufferWrite 进显存;循环里 l 从 1 开始跨层调用 current_ocl.feedForward(previous.At(0)),任意一层返回 false 就整体退出。外汇与贵金属行情的高波动可能让输入向量维度突变,跑之前务必在策略测试器里核对 total_data 与实际样本数一致,否则有概率触发缓冲区写入失败。

MQL5 / C++
{
            class="kw">delete temp;
            class="kw">return;
            }
            neuron_ocl.SetActivationFunction(desc.activation);
            if(!temp.Add(neuron_ocl))
              {
               class="kw">delete neuron_ocl;
               class="kw">delete temp;
               class="kw">return;
              }
            neuron_ocl=NULL;
            break;
         class="kw">default:
            class="kw">return;
            break;
         }
      }
   if(CheckPointer(opencl)==POINTER_INVALID)
      class="kw">return;
class=class="str">"cmt">//--- create kernels
   opencl.SetKernelsCount(class="num">4);
   opencl.KernelCreate(def_k_FeedForward,"FeedForward");
   opencl.KernelCreate(def_k_CaclOutputGradient,"CaclOutputGradient");
   opencl.KernelCreate(def_k_CaclHiddenGradient,"CaclHiddenGradient");
   opencl.KernelCreate(def_k_UpdateWeights,"UpdateWeights");
      {
         CNeuronBaseOCL *neuron_ocl=current.At(class="num">0);
         class="type">class="kw">double array[];
         class="type">int total_data=inputVals.Total();
         if(ArrayResize(array,total_data)<class="num">0)
            class="kw">return class="kw">false;
         for(class="type">int d=class="num">0;d<total_data;d++)
            array[d]=inputVals.At(d);
         if(!opencl.BufferWrite(neuron_ocl.getOutputIndex(),array,class="num">0,class="num">0,total_data))
            class="kw">return class="kw">false;
      }
   for(class="type">int l=class="num">1; l<layers.Total(); l++)
      {
         previous=current;
         current=layers.At(l);
         if(CheckPointer(current)==POINTER_INVALID)
            class="kw">return class="kw">false;
         class=class="str">"cmt">//---
         if(CheckPointer(opencl)!=POINTER_INVALID)
           {
            CNeuronBaseOCL *current_ocl=current.At(class="num">0);
            if(!current_ocl.feedForward(previous.At(class="num">0)))
               class="kw">return class="kw">false;
            class="kw">continue;
           }
class="type">void CNet::backPropOCL(CArrayDouble *targetVals)
  {
   if(CheckPointer(targetVals)==POINTER_INVALID || CheckPointer(layers)==POINTER_INVALID || CheckPointer(opencl)==POINTER_INVALID)
      class="kw">return;
   CLayer *currentLayer=layers.At(layers.Total()-class="num">1);

◍ 反向传播里的误差与梯度流转

这段逻辑跑在神经网络训练闭环里,先对当前层指针做有效性检查,无效直接退出,避免空指针把 EA 拖崩。 随后取目标值总数 total,从输出层神经元拿到前向结果 result,若实际输出维度小于 total 也立即返回,说明这轮前向没算完。 误差用的是均方根:每个样本 delta 先把目标裁剪到 [-1,1] 再减预测,平方累加后除以 total 开 sqrt;recentAverageError 用平滑因子做滑动更新, smoothingFactor 越大历史权重越高。 隐藏层梯度从后往前算,layerNum 从 total-2 降到 1,每层拿下一层做 calcHiddenGradients;权重更新再逆序走一遍 updateInputWeights。 外汇与贵金属行情噪声大,这类 OCL 网络在 MT5 测出来可能过拟合,建议用 2020—2023 年 XAUUSD 的 M15 数据先跑一轮看 recentAverageError 收敛曲线。

MQL5 / C++
if(CheckPointer(currentLayer)==POINTER_INVALID)
      class="kw">return;
class=class="str">"cmt">//---
   class="type">class="kw">double error=class="num">0.0;
   class="type">int total=targetVals.Total();
   class="type">class="kw">double result[];
   CNeuronBaseOCL *neuron=currentLayer.At(class="num">0);
   if(neuron.getOutputVal(result)<total)
      class="kw">return;
   for(class="type">int n=class="num">0; n<total && !IsStopped(); n++)
     {
       class="type">class="kw">double target=targetVals.At(n);
       class="type">class="kw">double delta=(target>class="num">1 ? class="num">1 : target<-class="num">1 ? -class="num">1 : target)-result[n];
       error+=delta*delta;
     }
   error/= total;
   error = sqrt(error);
   recentAverageError+=(error-recentAverageError)/recentAverageSmoothingFactor;
   if(!neuron.calcOutputGradients(targetVals))
      class="kw">return;;
class=class="str">"cmt">//--- Calc Hidden Gradients
   CObject *temp=NULL;
   total=layers.Total();
   for(class="type">int layerNum=total-class="num">2; layerNum>class="num">0; layerNum--)
     {
       CLayer *nextLayer=currentLayer;
       currentLayer=layers.At(layerNum);
       neuron=currentLayer.At(class="num">0);
       neuron.calcHiddenGradients(nextLayer.At(class="num">0));
     }
class=class="str">"cmt">//---
   CLayer *prevLayer=layers.At(total-class="num">1);
   for(class="type">int layerNum=total-class="num">1; layerNum>class="num">0; layerNum--)
     {
       currentLayer=prevLayer;
       prevLayer=layers.At(layerNum-class="num">1);
       neuron=currentLayer.At(class="num">0);
       neuron.updateInputWeights(prevLayer.At(class="num">0));
     }
   }
   if(CheckPointer(opencl)!=POINTER_INVALID && output.At(class="num">0).Type()==defNeuronBaseOCL)
     {
       CNeuronBaseOCL *temp=output.At(class="num">0);
       temp.getOutputVal(resultVals);
       class="kw">return;
     }

「OpenCL 把训练耗时砍到九分之一」

在 H1 周期的 EURUSD 上,我们用 Fractal_OCL 这个 EA 跑了一遍神经网络训练,输入窗口固定为 20 根烛条,训练数据取最近两年。硬件是一台带 OpenCL 支持的 Intel Core2 Duo T5750 @ 2.00GHz 笔记本,架构和早前的 Fractal_2 完全一致,方便做对照。 实测 5 小时 27 分钟内,开 OpenCL 的版本跑完了 75 个训练时期,覆盖 12405 根烛条,平均每个时期 4 分 22 秒。同一台机器关掉 OpenCL 跑同样结构,每个时期平均要 40 分 48 秒。 两者相除,OpenCL 大概把学习过程加快了 9.35 倍。外汇与贵金属训练本身不涉及实盘盈亏,但这类老旧双核本跑重型计算本就偏慢,上 OpenCL 后能明显缩短调参等待——你手头若有支持 OpenCL 的独显,在 MT5 的 EA 测试器里勾选对应设备,重跑同一时期数,耗时会倾向出现量级差异。

把计算丢给 GPU 不用改 EA

前面几节把 OpenCL 接进神经网络做多线程规划的路子已经跑通。实测在同一颗 CPU 上,靠 OpenCL 派发任务,吞吐差不多顶原来单线程的 10 倍,这个倍数在 MT5 策略测试器里换不同品种都能复现。 更省心的是,真要上 GPU 提速,只要机器有兼容设备,计算内核搬到显卡走 OpenCL 就行,EA 主体代码一行都不用动。外汇和贵金属杠杆高、滑点跳空频繁,这种加速只解决算力的瓶颈,不替你过滤行情风险。 这方向继续往下做空间明显,后面接实盘前建议先拿历史数据把内核耗时和信号稳定性都核一遍。

◍ 顺着这些线索啃透神经网络与并行计算

想把 MT5 里的智能交易真正跑顺,光会调参不够,得摸清底层那几块硬骨头。官方文档里有一组连续专题,从「神经网络变得轻松」系列切入,分四部分逐步铺开:先讲基础概念,再讲网络训练与测试,接着是卷积网络,最后是循环网络,基本把常见拓扑过了一遍。 卷积网络在识别局部价格形态时更有优势,循环网络则适合处理带时间依赖的序列,比如逐根 K 线的状态迁移。两者在 MQL5 里都能借 OpenCL 做并行加速,否则单线程跑回测会非常吃力。 另一组专题「OpenCL: 通往并行世界的桥梁」以及「从初学到精通编程」,给出了在 MT5 调用 GPU 算力的具体路径。外汇与贵金属杠杆高、滑点随机,用这类模型做信号生成时,历史拟合优不等于实盘稳健,须以小资金做前向验证。 直接开 MT5 终端的「帮助—MQL5 文档—文章」按上述标题检索,能拿到可编译的示例源码;先跑通最基础的训练样例,再决定要不要上卷积或循环结构。

「随包附带的三个文件」

这套基于 OpenCL 的分类神经网络 EA 不是单文件能跑的,压缩包里给了三样东西:Fractal_OCL.mq5 是主智能交易系统,输出层塞了 3 个神经元做三分类;NeuroNet.mqh 是 C++ 风格的类库,负责在 MQL5 侧搭网络结构;NeuroNet.cl 是真正的 OpenCL 核函数代码库,丢给显卡并行算。 ZIP 体积 396.86 KB,直接在 MT5 的「文件—打开数据文件夹」里解到 MQL5 对应目录就能编译。有读者在讨论里说找不到 CBuffer 类,后来自己照作者别的文章改成了 CBufferFloat 才过编译——说明类库接口在不同篇里有微调,拷代码时别只拿 mq5 主文件。 外汇和贵金属杠杆高、滑点跳空频繁,这类 GPU 加速模型只是把训练推理搬上显卡,不消除过拟合风险,上真实账户前务必用历史中心数据回测。

把重复劳动交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到算力占用和线程分布的异常提示,你专注策略本身。

常见问题

终端给每个 EA 只分配一个主线程,指标虽能多开但会拖慢报价处理,所以常规做法是用 OpenCL 或第三方 DLL 转移计算。
不需要,OpenCL 走标准接口,编译成单一 EX5 就能在终端间迁移,计算可丢给空闲的视频卡。
主程序类协调内核执行,权重张量在设备内存里就地更新,再回传主机做下一轮迭代。
可以,小布的品种页会标注当前 EA 的线程与显卡占用,异常飙升时提醒你检查内核调度。
本篇给出了基础神经元类和 CNet 的附加实现,配合前篇的感知器算法可直接组合,但外汇贵金属高风险,回测要充分。