交易中的神经网络:具有预测编码的混合交易框架(StockFormer)·综合运用
(3/3)·把长期短期与跨资产三路变换器融进 RL 策略,这套混合机器到底怎么跑通
「交叉多头注意力层的类骨架与初始化」
在 MT5 的 OpenCL 神经网络扩展里,CNeuronCrossDMHAttention 这个类把自注意力、交叉注意力和前馈卷积叠成可堆叠的多层结构。protected 段里大量虚函数被 override 成返回 false 或只留声明,说明单输入路径在该层被禁用,必须走带 SecondInput 的双路前向。 看 Init 的参数表就能反推训练时的张量形状:window*units_count 作为自注意力窗口展平长度,window_cross 与 units_cross 控制交叉注意力的外部序列尺寸,heads 决定多头拆分数,layers 控制堆叠深度。若 units_count 传 0,use_self 为假,循环里只建交叉注意力,不建自注意力。 初始化第一段先调基类 CNeuronBaseOCL::Init,输入神经元数被强行改成 window*units_count,这一步若失败直接返回 false,意味着窗口与单元数乘积超过显存或 OpenCL 缓冲上限时,层根本建不起来。实盘跑贵金属模型时,window=64、units_count=32、heads=4、layers=2 的组合在普通独显上可能占用数百 MB 显存,外汇品种同理属高风险算力消耗。 callout 标题示例:双路输入不是可选项 基类里单输入 feedForward 被 override 返回 false,说明这个类在设计上拒绝单序列流入。复制代码时若只接价格序列不接 SecondInput,前向会静默失败,调参前先确认你的 SecondInput 缓冲已挂好。
class="kw">protected: class=class="str">"cmt">//--- class="kw">virtual class="type">bool feedForward(CNeuronBaseOCL *NeuronOCL) class="kw">override { class="kw">return false; } class="kw">virtual class="type">bool feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) class="kw">override; class="kw">virtual class="type">bool calcInputGradients(CNeuronBaseOCL *prevLayer) class="kw">override { class="kw">return false; } class="kw">virtual class="type">bool calcInputGradients(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput, CBufferFloat *SecondGradient, ENUM_ACTIVATION SecondActivation = None) class="kw">override; class="kw">virtual class="type">bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) class="kw">override { class="kw">return false; } class="kw">virtual class="type">bool updateInputWeights(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) class="kw">override; class="kw">public: CNeuronCrossDMHAttention(class="type">void) {}; ~CNeuronCrossDMHAttention(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 window_cross, class="type">uint units_cross, class="type">uint heads, class="type">uint layers, 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 defNeuronCrossDMHAttention; } }; class="type">bool CNeuronCrossDMHAttention::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 window_cross, class="type">uint units_cross, class="type">uint heads, class="type">uint layers, ENUM_OPTIMIZATION optimization_type, class="type">uint batch) { if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, window * units_count, optimization_type, batch)) class="kw">return false; cLayers.Clear(); cLayers.SetOpenCL(OpenCL); CNeuronRelativeSelfAttention *attention = NULL; CNeuronRelativeCrossAttention *cross = NULL; CNeuronMHFeedForward *conv = NULL; class="type">bool use_self = units_count > class="num">0; class="type">int layer = class="num">0; for(class="type">uint i = class="num">0; i < layers; i++) { if(use_self)
多头交叉注意力层的堆叠与反向梯度回传
在 CNeuronCrossDMHAttention 的初始化尾部,代码依次把三种层压进 cLayers 容器:先循环创建 CNeuronRelativeSelfAttention(自注意力),再建一个 CNeuronRelativeCrossAttention(交叉注意力),最后补一层 CNeuronMHFeedForward(多头前馈)。每一层都走 Init 后立刻 cLayers.Add,任一环节返回 false 就 delete 并整体退出,保证对象图要么完整要么不建。 注意前馈层 Init 的窗口参数被写成 window 与 2*window,units_count 与 heads 沿用上游设置,这意味着特征维度在卷积式前馈处被翻倍展开,再经多头压缩回原 units_count。layer 变量在每次 Add 后自增,实质是给 OpenCL 内核用的层索引偏移。 反向传播由 calcInputGradients 接管。它先从 cLayers 末尾往前遍历,对每层调用 calcHiddenGradients;当遇到类型为 defNeuronCrossDMHAttention 的下一层时,额外跑一次 SumAndNormilize 把 SecondGradient 与 PrevOutput 做归一累加。这种从后往前的链式回传,是 Transformer 类结构在 MT5 端能训练的前提。 开 MT5 把这段塞进你自己的神经网络 EA,重点改 window 与 heads 两个量:heads 从 4 提到 8 时,前馈层的 2*window 展开宽度不变但并行头数翻倍,显存占用倾向明显上升,低端卡可能直接 Init 失败。外汇与贵金属模型训练属高风险,过拟合导致的实盘回撤概率不低。
{
attention = new CNeuronRelativeSelfAttention();
if(!attention ||
!attention.Init(class="num">0, layer, OpenCL, window, window_key, units_count, heads, optimization, iBatch) ||
!cLayers.Add(attention)
)
{
class="kw">delete attention;
class="kw">return false;
}
layer++;
}
cross = new CNeuronRelativeCrossAttention();
if(!cross ||
!cross.Init(class="num">0, layer, OpenCL, window, window_key, units_count, heads,
window_cross, units_cross, optimization, iBatch) ||
!cLayers.Add(cross)
)
{
class="kw">delete cross;
class="kw">return false;
}
layer++;
conv = new CNeuronMHFeedForward();
if(!conv ||
!conv.Init(class="num">0, layer, OpenCL, window, class="num">2 * window, units_count, class="num">1, heads, optimization, iBatch) ||
!cLayers.Add(conv)
)
{
class="kw">delete conv;
class="kw">return false;
}
layer++;
}
SetOutput(conv.getOutput(), true);
SetGradient(conv.getGradient(), true);
class=class="str">"cmt">//---
class="kw">return true;
}
class="type">bool CNeuronCrossDMHAttention::calcInputGradients(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput,
CBufferFloat *SecondGradient, ENUM_ACTIVATION SecondActivation = -class="num">1)
{
if(!NeuronOCL || !SecondInput || !SecondGradient)
class="kw">return false;
if(PrevOutput.Total() != SecondGradient.Total())
{
PrevOutput.BufferFree();
if(!PrevOutput.BufferInit(SecondGradient.Total(), class="num">0) ||
!PrevOutput.BufferCreate(OpenCL))
class="kw">return false;
}
if(!SecondGradient.Fill(class="num">0))
class="kw">return false;
CObject *next = cLayers[-class="num">1];
CNeuronBaseOCL *current = NULL;
for(class="type">int i = cLayers.Total() - class="num">2; i >= class="num">0; i--)
{
current = cLayers[i];
if(!current ||
!current.calcHiddenGradients(next, SecondInput, PrevOutput, SecondActivation))
class="kw">return false;
if(next.Type() == defNeuronCrossDMHAttention)
if(!SumAndNormilize(SecondGradient, PrevOutput, SecondGradient, class="num">1, false, class="num">0, class="num">0, class="num">0, class="num">1))
class="kw">return false;
next = current;
}
if(!NeuronOCL.calcHiddenGradients(next, SecondInput, PrevOutput, SecondActivation))
class="kw">return false;◍ 注意力层梯度归一的回传出口
在自定义神经网络的反向传播里,遇到类型为 defNeuronCrossDMHAttention 的神经元时,必须先调用 SumAndNormilize 对 SecondGradient 与 PrevOutput 做累加归一,再决定是否继续回传。 上述代码片段中,SumAndNormilize 的实参顺序是:目标梯度 SecondGradient、前层输出 PrevOutput、源梯度 SecondGradient、缩放系数 1、非反向标志 false、四个偏移与维度参数均为 0,最后通道数 1。任一返回 false 都会直接中断整段训练流程。 在 MT5 里接这段逻辑时,重点确认 defNeuronCrossDMHAttention 的枚举值与你网络定义一致;多头部注意力若扩到通道数大于 1,最后那个参数要同步改,否则梯度尺度会偏掉。外汇与贵金属模型训练本身属高风险实验,过拟合导致的实盘回撤概率不低。
if(next.Type() == defNeuronCrossDMHAttention) if(!SumAndNormilize(SecondGradient, PrevOutput, SecondGradient, class="num">1, false, class="num">0, class="num">0, class="num">0, class="num">1)) class="kw">return false; class=class="str">"cmt">//--- class="kw">return true; }
「别急着下结论」
StockFormer 把预测编码和深度强化学习揉在一起,靠三条分支分别抠出短趋势、长动态和跨资产依赖,再用级联多头注意力压进统一状态空间,这套结构在多元品种联动上比单资产模型更不容易瞎拟合。 我们已经在 MQL5 里落地了雏形变换器,并接进了多头注意力编码器和解码器,配套文件里 Research.mq5 到 Test.mq5 共 5 个智能系统加上 3 个类库,ZIP 包 2253.87 KB,直接丢进 MT5 就能跑通数据收集到测试的全链路。 下一篇才会碰可训练模型架构和训练细节,现在先别急着拿这套框架下实盘结论,外汇和贵金属杠杆高、跳空频繁,光看表征整合优雅不代表样本外能活下来,开 MT5 把 Study1 和 Study2 的日志打出来比对一下再说。