交易中的神经网络:运用形态变换器进行市场分析·进阶篇
注意力池化与形态神经元的底层接线
这段实现把多头注意力池化(CNeuronMHAttentionPooling)的末端拼装讲清楚了:在循环累加子层后,先清掉激活函数,再挂一个 CNeuronSoftMaxOCL 做归一化,并用 SetHeads(iUnits) 把输出头数对齐到单元数。 前向传播 feedForward 里,代码用 MatMul 把当前层输出与输入层输出做矩阵乘,参数写死为 (1, iHeads, iWindow, iUnits) 的维度排布,这意味着批内单样本、头数×窗口×单元的三维展开。外汇与贵金属行情用此类结构提取局部模式时,过拟合概率偏高,属高风险实验。 随后定义的 CNeuronMotifs 继承自 CNeuronBaseOCL,内部只保一个 CNeuronConvOCL cMotifs,说明形态提取被收敛到一层卷积上;Init 接口暴露了 dimension / window / step / units_count 四个关键超参,调 step 就能改变滑窗重叠度。 打开 MT5 的 MetaEditor,把这段粘进自定义神经元类,先只改 iWindow 从 10 到 30,看显存占用和前向耗时变化,比直接跑全模型更安全。
)
class="kw">return class="kw">false;
idx++;
conv.SetActivationFunction(None);
CNeuronSoftMaxOCL *softmax = new CNeuronSoftMaxOCL();
if(!softmax ||
!softmax.Init(class="num">0, idx, OpenCL, iHeads * iUnits, optimization, iBatch) ||
!cNeurons.Add(softmax)
)
class="kw">return class="kw">false;
softmax.SetHeads(iUnits);
class=class="str">"cmt">//---
class="kw">return true;
}
class="type">bool CNeuronMHAttentionPooling::feedForward(CNeuronBaseOCL *NeuronOCL)
{
CNeuronBaseOCL *current = NULL;
CObject *prev = NeuronOCL;
for(class="type">int i = class="num">0; i < cNeurons.Total(); i++)
{
current = cNeurons[i];
if(!current ||
!current.FeedForward(prev)
)
class="kw">return class="kw">false;
prev = current;;
}
if(!MatMul(current.getOutput(), NeuronOCL.getOutput(), Output,
class="num">1, iHeads, iWindow, iUnits))
class="kw">return class="kw">false;
class=class="str">"cmt">//---
class="kw">return true;
}
class CNeuronMotifs : class="kw">public CNeuronBaseOCL
{
class="kw">protected:
CNeuronConvOCL cMotifs;
class=class="str">"cmt">//---
class="kw">virtual class="type">bool feedForward(CNeuronBaseOCL *NeuronOCL) class="kw">override;
class="kw">virtual class="type">bool calcInputGradients(CNeuronBaseOCL *NeuronOCL) class="kw">override;
class="kw">virtual class="type">bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) class="kw">override;
class="kw">public:
CNeuronMotifs(class="type">void) {};
~CNeuronMotifs(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 dimension, class="type">uint window, class="type">uint step, class="type">uint units_count,
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 const { class="kw">return defNeuronMotifs; }
class=class="str">"cmt">//---
class="kw">virtual class="type">bool Save(class="type">int const file_handle) class="kw">override;
class="kw">virtual class="type">bool Load(class="type">int const file_handle) class="kw">override;
class=class="str">"cmt">//---
class="kw">virtual class="type">bool WeightsUpdate(CNeuronBaseOCL *source, class="type">class="kw">float tau) class="kw">override;
class="kw">virtual class="type">void SetOpenCL(COpenCLMy *obj) class="kw">override;
class=class="str">"cmt">//---「Motifs 层与多尺度注意力类的骨架」
CNeuronMotifs 在初始化时先按 units_count、step 与 window 算出输入规模:inputs = (units_count * step + (window - step)) * dimension,再叠加 motifs = units_count * dimension 作为拼接后的总输入维度。这个加法直接决定了 CNeuronBaseOCL::Init 要申请的显存宽度,调参时若 window 从 10 改到 20,inputs 会线性拉长,MT5 终端的 OpenCL 缓冲可能溢出。 SetActivationFunction 被重写后做了两件事:先调基类 CNeuronBaseOCL 的同名方法,再把激活类型透传给内部 cMotifs 子层。注意代码里写的是 cMotifs.SetActivationFunction(activation),而传入参数是 value,这属于变量名错配,实盘编译前必须改成 cMotifs.SetActivationFunction(value),否则激活函数不会真正落地。 feedForward 里先用 NeuronOCL.Activation() 对齐激活类型,再跑 cMotifs.FeedForward,最后用 Concat 把上游输出与 motifs 输出按第 1 轴拼起来。反向的 calcInputGradients 则用 DeConcat 把梯度按同样维度拆回两端,保证子层与主干的梯度不串。 后面定义的 CNeuronMultiScaleAttention 公开继承了 CNeuronBaseOCL,内部挂了 4 个 CNeuronRelativeSelfAttention 实例(cAttentions[4]),配合 cWideInputs、cConcatAttentions 与 cPooling 做多尺度池化。它只声明了 feedForward、calcInputGradients、updateInputWeights 三个虚函数覆盖,具体实现留到后续节。
class="kw">virtual class="type">void SetActivationFunction(ENUM_ACTIVATION value) class="kw">override; }; class="type">void CNeuronMotifs::SetActivationFunction(ENUM_ACTIVATION value) { CNeuronBaseOCL::SetActivationFunction(value); cMotifs.SetActivationFunction(activation); } class="type">bool CNeuronMotifs::Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl, class="type">uint dimension, class="type">uint window, class="type">uint step, class="type">uint units_count, ENUM_OPTIMIZATION optimization_type, class="type">uint batch ) { class="type">uint inputs = (units_count * step + (window - step)) * dimension; class="type">uint motifs = units_count * dimension; if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, inputs + motifs, optimization_type, batch)) class="kw">return class="kw">false; if(!cMotifs.Init(class="num">0, class="num">0, OpenCL, dimension * window, dimension * step, dimension, units_count, class="num">1, optimization, iBatch)) class="kw">return class="kw">false; SetActivationFunction(None); class=class="str">"cmt">//--- class="kw">return true; } class="type">bool CNeuronMotifs::feedForward(CNeuronBaseOCL *NeuronOCL) { if(!NeuronOCL) class="kw">return class="kw">false; if(NeuronOCL.Activation() != activation) SetActivationFunction((ENUM_ACTIVATION)NeuronOCL.Activation()); if(!cMotifs.FeedForward(NeuronOCL)) class="kw">return class="kw">false; if(!Concat(NeuronOCL.getOutput(), cMotifs.getOutput(), Output, NeuronOCL.Neurons(), cMotifs.Neurons(), class="num">1)) class="kw">return class="kw">false; class=class="str">"cmt">//--- class="kw">return true; } class="type">bool CNeuronMotifs::calcInputGradients(CNeuronBaseOCL *NeuronOCL) { if(!NeuronOCL) class="kw">return class="kw">false; if(!DeConcat(NeuronOCL.getGradient(),cMotifs.getGradient(),Gradient,NeuronOCL.Neurons(),cMotifs.Neurons(),class="num">1)) class="kw">return class="kw">false; class=class="str">"cmt">//--- class="kw">return true; } class CNeuronMultiScaleAttention : class="kw">public CNeuronBaseOCL { class="kw">protected: class="type">uint iWindow; class="type">uint iUnits; class=class="str">"cmt">//--- CNeuronBaseOCL cWideInputs; CNeuronRelativeSelfAttention cAttentions[class="num">4]; CNeuronBaseOCL cConcatAttentions; CNeuronMHAttentionPooling cPooling; class=class="str">"cmt">//--- class="kw">virtual class="type">bool feedForward(CNeuronBaseOCL *NeuronOCL) class="kw">override; class="kw">virtual class="type">bool calcInputGradients(CNeuronBaseOCL *NeuronOCL) class="kw">override; class="kw">virtual class="type">bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) class="kw">override; class="kw">public: CNeuronMultiScaleAttention(class="type">void) {}; ~CNeuronMultiScaleAttention(class="type">void) {}; class=class="str">"cmt">//---
◍ 多尺度注意力层的初始化拆解
在 MT5 用 OpenCL 跑神经网络时,多尺度注意力神经元靠 Init 把不同时间窗口压进同一张宽输入缓冲。注意它先调基类 CNeuronBaseOCL::Init,传入的窗口维度是 window * units_count,而不是原始 window,这一步决定了显存占用的基准规模。 代码里对 units 做了三档切分:units1=(iUnits+1)/2、units2=(iUnits+2)/3、units3=(iUnits+3)/4,再用 MathMax 取出 wide 的最大值。假设 iUnits=64,则三档分别是 32、22、16,wide 会取到 max(64, 64, 66, 64)=66,意味着宽缓冲实际宽度比原始单元数略大。 随后 cWideInputs 以 wide*iWindow 作输入长度建缓冲,并立刻 Fill(0) 清零;若返回空或填充失败直接返回 false,这是很多自定义层在 EA 加载期静默失败的高发点。 三个注意力子层分别绑定 iWindow、2*iWindow 等倍数窗口,heads 参数沿用到每个子层。你在抄这段代码时,重点核对 iUnits 与 wide 的取整偏差,否则后续 cAttentions 的维度对齐可能越界。
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, 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 const { class="kw">return defNeuronMultiScaleAttention; } class=class="str">"cmt">//--- class="kw">virtual class="type">bool Save(class="type">int const file_handle) class="kw">override; class="kw">virtual class="type">bool Load(class="type">int const file_handle) class="kw">override; class=class="str">"cmt">//--- class="kw">virtual class="type">bool WeightsUpdate(CNeuronBaseOCL *source, class="type">class="kw">float tau) class="kw">override; class="kw">virtual class="type">void SetOpenCL(COpenCLMy *obj) class="kw">override; }; class="type">bool CNeuronMultiScaleAttention::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, 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; iWindow = window; iUnits = units_count; class="type">uint units1 = (iUnits + class="num">1) / class="num">2; class="type">uint units2 = (iUnits + class="num">2) / class="num">3; class="type">uint units3 = (iUnits + class="num">3) / class="num">4; class="type">uint wide = MathMax(MathMax(iUnits, units1 * class="num">2), MathMax(units2 * class="num">3, units3 * class="num">4)); class="type">int idx = class="num">0; if(!cWideInputs.Init(class="num">0, idx, OpenCL, wide * iWindow, optimization, iBatch)) class="kw">return class="kw">false; CBufferFloat *temp = cWideInputs.getOutput(); if(!temp || !temp.Fill(class="num">0)) class="kw">return class="kw">false; idx++; if(!cAttentions[class="num">0].Init(class="num">0, idx, OpenCL, iWindow, window_key, iUnits, heads, optimization, iBatch)) class="kw">return class="kw">false; idx++; if(!cAttentions[class="num">1].Init(class="num">0, idx, OpenCL, class="num">2 * iWindow, window_key, units1, heads, optimization, iBatch))
多尺度注意力网络的初始化与前向链路
这段代码展示了 CNeuronMultiScaleAttention 的初始化与 feedForward 实现,核心是把 4 路不同窗口的注意力并行接起来,再拼接到一个池化层输出。Init 里 idx 从 0 递增,cAttentions[0]~[3] 分别占用 1*iWindow、2*iWindow、3*iWindow、4*iWindow 的偏移量,最后 cConcatAttentions 接收 4*iWindow*iUnits 的展平维度,cPooling 以 iWindow×iUnits×4 做池化。 feedForward 中先跑第 0 路注意力,再用 Concat 把原输出自我拼接进 cWideInputs;若两者激活函数不一致就强制对齐。随后 for 循环 i=1 到 3 跑多尺度注意力,全部基于 cWideInputs 对象。四路输出再次 Concat 进 cConcatAttentions,维度各占 iWindow×iUnits,最后送 cPooling 出结果。 在 MT5 里验证时,重点看 iWindow 和 iUnits 的实际取值:若 iWindow=24、iUnits=16,则拼接层输入固定为 4*24*16=1536 个浮点,显存申请失败会直接 return false。外汇与贵金属行情用这类结构做特征提取时,过拟合概率偏高,建议先用小 batch 在 EURUSD 的 M15 上跑通再放大。
class="kw">return class="kw">false; idx++; if(!cAttentions[class="num">2].Init(class="num">0, idx, OpenCL, class="num">3 * iWindow, window_key, units2, heads, optimization, iBatch)) class="kw">return class="kw">false; idx++; if(!cAttentions[class="num">3].Init(class="num">0, idx, OpenCL, class="num">4 * iWindow, window_key, units3, heads, optimization, iBatch)) class="kw">return class="kw">false; idx++; if(!cConcatAttentions.Init(class="num">0, idx, OpenCL, class="num">4 * iWindow * iUnits, optimization, iBatch)) class="kw">return class="kw">false; idx++; if(!cPooling.Init(class="num">0, idx, OpenCL, iWindow, iUnits, class="num">4, optimization, iBatch)) class="kw">return class="kw">false; SetActivationFunction(None); if(!SetOutput(cPooling.getOutput()) || !SetGradient(cPooling.getGradient())) class="kw">return class="kw">false; class=class="str">"cmt">//--- class="kw">return true; } class="type">bool CNeuronMultiScaleAttention::feedForward(CNeuronBaseOCL *NeuronOCL) { class=class="str">"cmt">//--- Attention if(!cAttentions[class="num">0].FeedForward(NeuronOCL)) class="kw">return class="kw">false; if(!Concat(NeuronOCL.getOutput(), NeuronOCL.getOutput(), cWideInputs.getOutput(), iWindow, class="num">0, iUnits)) class="kw">return class="kw">false; if(cWideInputs.Activation() != NeuronOCL.Activation()) cWideInputs.SetActivationFunction((ENUM_ACTIVATION)NeuronOCL.Activation()); class=class="str">"cmt">//--- Multi scale attentions for(class="type">int i = class="num">1; i < class="num">4; i++) if(!cAttentions[i].FeedForward(cWideInputs.AsObject())) class="kw">return class="kw">false; class=class="str">"cmt">//--- Concatenate Multi-Scale Attentions if(!Concat(cAttentions[class="num">0].getOutput(), cAttentions[class="num">1].getOutput(), cAttentions[class="num">2].getOutput(), cAttentions[class="num">3].getOutput(), cConcatAttentions.getOutput(), iWindow, iWindow, iWindow, iWindow, iUnits)) class="kw">return class="kw">false; class=class="str">"cmt">//--- Attention pooling if(!cPooling.FeedForward(cConcatAttentions.AsObject())) class="kw">return class="kw">false; class=class="str">"cmt">//--- class="kw">return true; } class CNeuronMolformer : class="kw">public CNeuronRMAT { class="kw">public: CNeuronMolformer(class="type">void) {}; ~CNeuronMolformer(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 motif_window, class="type">uint motif_step, ENUM_OPTIMIZATION optimization_type, class="type">uint batch); class=class="str">"cmt">//Molformer class=class="str">"cmt">//---
「Molformer 初始化时的层堆叠逻辑」
CNeuronMolformer::Init 负责把 motif 提取、多尺度注意力与残差卷积按序拼成网络。先调基类 CNeuronBaseOCL::Init,输入维度被扩成 window * units_count,这一步决定了后面所有层的张量形状。 motif 单元数不是直接给的,而是用 units_count 减去 (motif_window - motif_step) 的下界再向上取整:motif_units = (units_count - MathMax(motif_window - motif_step, 0) + motif_step - 1) / motif_step。改 motif_step 会直接改变 motif 分支宽度,进而让 units_total = units_count + motif_units 浮动。 主循环按 layers 次数堆叠:每轮先挂一个 CNeuronMultiScaleAttention(用 units_total 和 heads 控制多头),再挂一个 CResidualConv 做前馈,idx 每次加 2。层数越高,显存与 OpenCL kernel 启动次数线性上升,在 MT5 策略测试器里可能拖慢每根 K 线的推理。 收尾先转置再接一个 1×1 卷积把通道压回 units_count,最后再转置回 (window, units_count) 布局。跑不通时优先查 iBatch 与 OpenCL 上下文是否和前面层一致,外汇与贵金属行情高频跳变,这类 GPU 网络在高波动时段可能产出不稳定信号,需自行回测验证。
class="kw">virtual class="type">int Type(class="type">void) class="kw">override const { class="kw">return defNeuronMolformer; } }; class="type">bool CNeuronMolformer::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 motif_window, class="type">uint motif_step, 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 idx = class="num">0; CNeuronMotifs *motif = new CNeuronMotifs(); class="type">uint motif_units = units_count - MathMax(motif_window - motif_step, class="num">0); motif_units = (motif_units + motif_step - class="num">1) / motif_step; if(!motif || !motif.Init(class="num">0, idx, OpenCL, window, motif_window, motif_step, motif_units, optimization, iBatch) || !cLayers.Add(motif) ) class="kw">return class="kw">false; idx++; CNeuronMultiScaleAttention *msat = NULL; CResidualConv *ff = NULL; class="type">uint units_total = units_count + motif_units; for(class="type">uint i = class="num">0; i < layers; i++) { class=class="str">"cmt">//--- Attention msat = new CNeuronMultiScaleAttention(); if(!msat || !msat.Init(class="num">0, idx, OpenCL, window, window_key, units_total, heads, optimization, iBatch) || !cLayers.Add(msat) ) class="kw">return class="kw">false; idx++; class=class="str">"cmt">//--- FeedForward ff = new CResidualConv(); if(!ff || !ff.Init(class="num">0, idx, OpenCL, window, window, units_total, optimization, iBatch) || !cLayers.Add(ff) ) class="kw">return class="kw">false; idx++; } class=class="str">"cmt">//--- Out CNeuronTransposeOCL *transp = new CNeuronTransposeOCL(); if(!transp || !transp.Init(class="num">0, idx, OpenCL, units_total, window, optimization, iBatch) || !cLayers.Add(transp) ) class="kw">return class="kw">false; idx++; CNeuronConvOCL *conv = new CNeuronConvOCL(); if(!conv || !conv.Init(class="num">0, idx, OpenCL, units_total, units_total, units_count, window, class="num">1, optimization, iBatch) || !cLayers.Add(conv) ) class="kw">return class="kw">false; idx++; idx++; transp = new CNeuronTransposeOCL(); if(!transp || !transp.Init(class="num">0, idx, OpenCL, window, units_count, optimization, iBatch) || !cLayers.Add(transp)