神经网络变得轻松(第五部分):OpenCL 中的多线程计算·综合运用
(3/3)· 从 MQL5 线程限制到视频卡并行,收尾篇给出可跑的完整类的拆解
不少交易者以为 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 与实际样本数一致,否则有概率触发缓冲区写入失败。
{
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 收敛曲线。
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 加速模型只是把训练推理搬上显卡,不消除过拟合风险,上真实账户前务必用历史中心数据回测。