交易中的神经网络:双曲型潜在扩散模型(终篇)·进阶篇
「双曲线神经元层的初始化骨架」
在 MT5 的 OpenCL 神经网络扩展里,CNeuronHyperboloids 用双曲几何做特征映射,构造函数把 iWindows、iUnits、iCentroids 全置 0,iProducts/iDistances/iNormes 置 -1,意味着这些缓冲区延迟到 Init 才分配。 Init 的入口先调基类 CNeuronBaseOCL::Init,传入的神经元总数是 window*units_count*centroids——比如 window=10、units=8、centroids=4,就是 320 个输入节点,规模直接随三个参数乘积膨胀。 内部搭了两级卷积:第一级 CNeuronConvOCL 用 TANH 激活,核宽 iWindows;第二级核宽 1、通道数 iCentroids,做窄卷积压缩。中间还插了一个转置层 CNeuronTransposeOCL 且激活设为 None,纯粹做维度重排。 任何一级 new 或 Init 失败就 delete 并返回 false,调用方拿不到半初始化对象。开 MT5 把 window、units_count、centroids 三个参量改小跑一遍,能直观看到显存占用的非线性跳变。
class="kw">virtual class="type">bool feedForward(CNeuronBaseOCL *NeuronOCL); class="kw">virtual class="type">bool calcInputGradients(CNeuronBaseOCL *prevLayer); class="kw">virtual class="type">bool updateInputWeights(CNeuronBaseOCL *NeuronOCL); class="kw">public: CNeuronHyperboloids(class="type">void) : iWindows(class="num">0), iUnits(class="num">0), iCentroids(class="num">0), iProducts(-class="num">1), iDistances(-class="num">1), iNormes(-class="num">1) {}; ~CNeuronHyperboloids(class="type">void) {}; class="kw">virtual class="type">bool Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl, class="type">uint window, class="type">uint units_count, class="type">uint centroids, ENUM_OPTIMIZATION optimization_type, class="type">uint batch); class="kw">virtual class="type">int Type(class="type">void) class="kw">const { class="kw">return defNeuronHyperboloids; } class=class="str">"cmt">//--- methods for working with files class="kw">virtual class="type">bool Save(class="type">int class="kw">const file_handle); class="kw">virtual class="type">bool Load(class="type">int class="kw">const file_handle); class=class="str">"cmt">//--- class="kw">virtual class="type">bool WeightsUpdate(CNeuronBaseOCL *source, class="type">float tau); class="kw">virtual class="type">void SetOpenCL(COpenCLMy *obj); class="kw">virtual class="type">void TrainMode(class="type">bool flag); }; class="type">bool CNeuronHyperboloids::Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl, class="type">uint window, class="type">uint units_count, class="type">uint centroids, ENUM_OPTIMIZATION optimization_type, class="type">uint batch) { if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, window*units_count*centroids, optimization_type, batch)) class="kw">return class="kw">false; iWindows = window; iUnits = units_count; iCentroids = centroids; cHyperCentroids.Clear(); cHyperCurvatures.Clear(); cHyperCentroids.SetOpenCL(OpenCL); cHyperCurvatures.SetOpenCL(OpenCL); CNeuronTransposeOCL *transp = new CNeuronTransposeOCL(); if(!transp || !transp.Init(class="num">0, class="num">0, OpenCL, iUnits, iWindows, optimization, iBatch) || !cHyperCentroids.Add(transp)) { class="kw">delete transp; class="kw">return class="kw">false; } transp.SetActivationFunction(None); CNeuronConvOCL *conv = new CNeuronConvOCL(); if(!conv || !conv.Init(class="num">0, class="num">1, OpenCL, iUnits, iUnits, iCentroids, iWindows, class="num">1, optimization, iBatch) || !cHyperCentroids.Add(conv)) { class="kw">delete conv; class="kw">return class="kw">false; } conv.SetActivationFunction(TANH); conv = new CNeuronConvOCL(); if(!conv || !conv.Init(class="num">0, class="num">2, OpenCL, iCentroids, iCentroids, iCentroids, class="num">1, iWindows, optimization, iBatch) || !cHyperCentroids.Add(conv)) { class="kw">delete conv; class="kw">return class="kw">false; }
双路卷积与对数映射的前向实现
在 CNeuronHyperboloids 的初始化里,质心支路先挂一个转置层 CNeuronTransposeOCL,Init 参数用了 0、3 两个维度标记,后接卷积层 CNeuronConvOCL 以 TANH 激活收口;曲率支路则叠了两层卷积,其中一层激活设为 None,另一层用 TANH。任何一层 Init 返回 false 都会 delete 指针并整体回退,避免悬空对象吃显存。 三个 OpenCL 缓冲 iProducts、iDistances、iNormes 都按 iCentroids * iUnits * sizeof(float) 字节数申请 CL_MEM_READ_WRITE,只要有一个 AddBuffer 返回负值就直接 return false。这意味着你改 iCentroids 或 iUnits 时,显存占用是线性跳变的,调参前先在终端看一眼显存余量比较稳妥。 feedForward 分两遍循环:先跑 cHyperCentroids 容器里的层,再把 prev 指到质心输出,继续跑 cHyperCurvatures。两路都通了才调 LogMap 做对数映射耦合。反向的 calcInputGradients 则从容器末尾取 [-1] 层,先算 LogMapGrad 再把梯度回传 prevLayer,结构对称但顺序倒挂。外汇与贵金属行情下用这类 GPU 网络做特征提取,模型误判概率不低,属高风险用法。
conv.SetActivationFunction(None); transp = new CNeuronTransposeOCL(); if(!transp || !transp.Init(class="num">0, class="num">3, OpenCL, iWindows, iCentroids, optimization, iBatch) || !cHyperCentroids.Add(transp)) { class="kw">delete transp; class="kw">return class="kw">false; } transp.SetActivationFunction((ENUM_ACTIVATION)conv.Activation()); conv = new CNeuronConvOCL(); if(!conv || !conv.Init(class="num">0, class="num">4, OpenCL, iWindows, iWindows, iWindows, iCentroids, class="num">1, optimization, iBatch) || !cHyperCurvatures.Add(conv)) { class="kw">delete conv; class="kw">return class="kw">false; } conv.SetActivationFunction(TANH); class=class="str">"cmt">//--- conv = new CNeuronConvOCL(); if(!conv || !conv.Init(class="num">0, class="num">5, OpenCL, iWindows, iWindows, class="num">1, class="num">1, iCentroids, optimization, iBatch) || !cHyperCurvatures.Add(conv)) { class="kw">delete conv; class="kw">return class="kw">false; } conv.SetActivationFunction(None); class="type">uint size = iCentroids * iUnits * class="kw">sizeof(class="type">float); iProducts = OpenCL.AddBuffer(size, CL_MEM_READ_WRITE); if(iProducts < class="num">0) class="kw">return class="kw">false; iDistances = OpenCL.AddBuffer(size, CL_MEM_READ_WRITE); if(iDistances < class="num">0) class="kw">return class="kw">false; iNormes = OpenCL.AddBuffer(size, CL_MEM_READ_WRITE); if(iNormes < class="num">0) class="kw">return class="kw">false; class=class="str">"cmt">//--- class="kw">return true; } class="type">bool CNeuronHyperboloids::feedForward(CNeuronBaseOCL *NeuronOCL) { CNeuronBaseOCL *prev = NeuronOCL; CNeuronBaseOCL *centroids = NULL; CNeuronBaseOCL *curvatures = NULL; class=class="str">"cmt">//--- Centroids for(class="type">int i = class="num">0; i < cHyperCentroids.Total(); i++) { centroids = cHyperCentroids[i]; if(!centroids || !centroids.FeedForward(prev)) class="kw">return class="kw">false; prev = centroids; } class=class="str">"cmt">//--- Curvatures for(class="type">int i = class="num">0; i < cHyperCurvatures.Total(); i++) { curvatures = cHyperCurvatures[i]; if(!curvatures || !curvatures.FeedForward(prev)) class="kw">return class="kw">false; prev = curvatures; } if(!LogMap(NeuronOCL, centroids, curvatures, AsObject())) class="kw">return class="kw">false; class=class="str">"cmt">//--- class="kw">return true; } class="type">bool CNeuronHyperboloids::calcInputGradients(CNeuronBaseOCL *prevLayer) { if(!prevLayer) class="kw">return class="kw">false; CObject *next = NULL; CNeuronBaseOCL *centroids = cHyperCentroids[-class="num">1]; CNeuronBaseOCL *curvatures = cHyperCurvatures[-class="num">1]; if(!LogMapGrad(prevLayer, centroids, curvatures, AsObject())) class="kw">return class="kw">false; class=class="str">"cmt">//--- Curvatures
◍ 反向传播里曲率与质心层的梯度串联
这段逻辑干的事,是从双曲线曲率层往质心层、再往前一层逐层回传梯度。循环从倒数第二层(Total()-2)倒序到 0,每层先把当前指针存进 next,再取本层对象,调用 calcHiddenGradients 把下一层梯度算进来,任意一步返回空或失败就直接 return false。
曲率层处理完,质心层先取自身梯度缓冲 temp,若激活函数不是 None 就做 DeActivation 反激活;随后 SetGradient 写入前输出、calcHiddenGradients 接曲率层对象,再用 SumAndNormilize 以 iWindows 做归一(后四个参数依次为 0,0,0,1),最后把 temp 设回质心梯度。
质心层同样用倒序循环把梯度传给前一层 cHyperCentroids,前面那层 prevLayer 走一遍相同的反激活 + 隐藏梯度 + 归一流程。外汇与贵金属行情噪声大,这类多层回传在实盘高频信号里可能对过拟合敏感,验证时建议先用 M1 历史分段跑。
所有层串完没报错才 return true。直接把下面代码贴进 MT5 的自定义类方法里,断点打在 SumAndNormilize 那行,能看到 temp 每窗归一后的数值变化。
for(class="type">int i = cHyperCurvatures.Total() - class="num">2; i >= class="num">0; i--) { next = curvatures; curvatures = cHyperCurvatures[i]; if(!curvatures || !curvatures.calcHiddenGradients(next)) class="kw">return class="kw">false; } CBufferFloat *temp = centroids.getGradient(); if(centroids.Activation()!=None) if(!DeActivation(centroids.getOutput(),temp,temp,centroids.Activation())) class="kw">return class="kw">false; if(!centroids.SetGradient(centroids.getPrevOutput(), class="kw">false) || !centroids.calcHiddenGradients(curvatures.AsObject()) || !SumAndNormilize(temp, centroids.getGradient(), temp, iWindows, class="kw">false, class="num">0, class="num">0, class="num">0, class="num">1) || !centroids.SetGradient(temp, class="kw">false) ) class="kw">return class="kw">false; class=class="str">"cmt">//--- Centroids for(class="type">int i = cHyperCentroids.Total() - class="num">2; i >= class="num">0; i--) { next = centroids; centroids = cHyperCentroids[i]; if(!centroids || !centroids.calcHiddenGradients(next)) class="kw">return class="kw">false; } temp = prevLayer.getGradient(); if(prevLayer.Activation()!=None) if(!DeActivation(prevLayer.getOutput(),temp,temp,prevLayer.Activation())) class="kw">return class="kw">false; if(!prevLayer.SetGradient(prevLayer.getPrevOutput(), class="kw">false) || !prevLayer.calcHiddenGradients(centroids.AsObject()) || !SumAndNormilize(temp, prevLayer.getGradient(), temp, iWindows, class="kw">false, class="num">0, class="num">0, class="num">0, class="num">1) || !prevLayer.SetGradient(temp, class="kw">false) ) class="kw">return class="kw">false; class=class="str">"cmt">//--- class="kw">return true; }
「把双曲扩散拼进一个顶层类」
CNeuronHypDiff 是 HypDiff 框架的顶层统一对象,核心功能直接继承 CNeuronRMAT——后者已能规划小型线性模型操作,所以这里只需重写初始化并指定内嵌架构,其余进程交给父类。初始化参数里收齐了 window、units_count、heads、layers、centroids 等常量,用来确凿解释对象结构。 方法体内先调 CNeuronBaseOCL::Init 做核心接口初始化,刻意绕过直接父类初始化,因为内嵌模型架构差异太大。随后清掉 cLayers 并挂上 OpenCL 上下文,再把输入数据投影进双曲空间的 CNeuronHyperProjection 实例压进模型。 双曲编码器原本用图神经网络加卷积,这里换成带相对位置编码的变换器。嵌入生成后全量投影到所有切平面,信息量显著增大,代价是噪声也放大,所以把每个节点嵌入维度调低来部分对冲。质心与切线投影由 CNeuronHyperboloids 负责,加进线性模型即可。 扩散阶段用 CNeuronDiffusion 处理多平面投影,但不把同序列元素的多种投影合并,而是让扩散模型当独立对象学,形成底层数据的三维视图;不同投影注入不同噪声,等于把模糊立体化。原版反向投影回双曲空间并用费米-狄拉克解码,我们改为依赖关系池化层派生统一表示,直接喂给参与者模型。 GetWindow 返回 cLayers[1] 的窗口减 1,GetUnits 返回其单元数;若指针为空则返回 0。这套实现偏离原版最彻底处在输出端——不重建图表示,只求潜在丰富表示。环境编码器仅做批归一化后直送扩散模型,末端一个全连接层降维进参与者。外汇与贵金属行情下用此类复杂模型属高风险,历史回测收益不必然延续。
class CNeuronHypDiff : class="kw">public CNeuronRMAT { class="kw">public: CNeuronHypDiff(class="type">void) {}; ~CNeuronHypDiff(class="type">void) {}; class=class="str">"cmt">//--- class="kw">virtual class="type">bool Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl, class="type">uint window, class="type">uint window_key, class="type">uint units_count, class="type">uint heads, class="type">uint layers, class="type">uint centroids, ENUM_OPTIMIZATION optimization_type, class="type">uint batch); class=class="str">"cmt">//--- class="kw">virtual class="type">int Type(class="type">void) class="kw">override class="kw">const { class="kw">return defNeuronHypDiff; } class=class="str">"cmt">//--- class="kw">virtual class="type">uint GetWindow(class="type">void) class="kw">const class="kw">override { CNeuronRMAT* neuron = cLayers[class="num">1]; class="kw">return (!neuron ? class="num">0 : neuron.GetWindow() - class="num">1); } class="kw">virtual class="type">uint GetUnits(class="type">void) class="kw">const class="kw">override { CNeuronRMAT* neuron = cLayers[class="num">1]; class="kw">return (!neuron ? class="num">0 : neuron.GetUnits()); } }; class="type">bool CNeuronHypDiff::Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl, class="type">uint window, class="type">uint window_key, class="type">uint units_count, class="type">uint heads, class="type">uint layers, class="type">uint centroids, ENUM_OPTIMIZATION optimization_type, class="type">uint batch) { if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, window * units_count, optimization_type, batch)) class="kw">return class="kw">false; cLayers.Clear(); cLayers.SetOpenCL(OpenCL); class="type">int layer = class="num">0; class=class="str">"cmt">//--- Projection CNeuronHyperProjection *lorenz = new CNeuronHyperProjection(); if(!lorenz ||