交易中的神经网络:具有层化记忆的智代·进阶篇
◍ 记忆神经元的双路前向与梯度回传
CNeuronMemory 在初始化时同时挂了 LSTM 与 Mamba 两条序列通路:cLSTM 用 iWindow 和 iUnits 做标准门控循环,cMamba 则把状态维度拉到 2*iWindow,在 Init 里以第二索引 1 区别于 LSTM 的 0。两者都先各自 FeedForward,再把 Mamba 输出与 LSTM 输出送进 RelativeCrossAttention 做交叉对齐。 前向函数 feedForward 的返回行直接调用父类方法,传入 cMamba.AsObject() 与 cLSTM.getOutput(),说明注意力层吃的是两条路的拼接特征,而非单路隐藏态。 反向的 calcInputGradients 里有个细节:先用 SetGradient 把 Mamba 的上一步输出暂存进 NeuronOCL,跑完 LSTM 的隐藏梯度后,再用 SumAndNormilize 把 temp 与 cMamba.getPrevOutput() 按 iWindow 做归一化求和。这一手保证了两条序列梯度的量级不会在回传时互相淹没。 开 MT5 把这段挂到自定义神经网络 EA 里,调 iWindow 从 32 改到 64,大概率能直接看到 cMamba 的 2*iWindow 状态缓冲占用翻倍,回测外汇 EURUSD 时显存峰值可能抬升 15%~20%,属高杠杆品种下的高算力风险。
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">//--- class="kw">virtual class="type">bool Clear(class="type">void) class="kw">override; }; class="type">bool CNeuronMemory::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(!CNeuronRelativeCrossAttention::Init(numOutputs, myIndex, open_cl, window, window_key, units_count, heads, window, units_count, optimization_type, batch)) class="kw">return class="kw">false; if(!cLSTM.Init(class="num">0, class="num">0, OpenCL, iWindow, iUnits, optimization, iBatch)) class="kw">return class="kw">false; if(!cMamba.Init(class="num">0, class="num">1, OpenCL, iWindow, class="num">2 * iWindow, iUnits, optimization, iBatch)) class="kw">return class="kw">false; class=class="str">"cmt">//--- class="kw">return true; } class="type">bool CNeuronMemory::feedForward(CNeuronBaseOCL *NeuronOCL) { if(!cLSTM.FeedForward(NeuronOCL)) class="kw">return class="kw">false; if(!cMamba.FeedForward(NeuronOCL)) class="kw">return class="kw">false; class="kw">return CNeuronRelativeCrossAttention::feedForward(cMamba.AsObject(), cLSTM.getOutput()); } class="type">bool CNeuronMemory::calcInputGradients(CNeuronBaseOCL *NeuronOCL) { if(!NeuronOCL) class="kw">return class="kw">false; if(!CNeuronRelativeCrossAttention::calcInputGradients(cMamba.AsObject(), cLSTM.getOutput(), cLSTM.getGradient(), (ENUM_ACTIVATION)cLSTM.Activation())) class="kw">return class="kw">false; if(!NeuronOCL.calcHiddenGradients(cMamba.AsObject())) class="kw">return class="kw">false; CBufferFloat *temp = NeuronOCL.getGradient(); if(!NeuronOCL.SetGradient(cMamba.getPrevOutput(), class="kw">false)) class="kw">return class="kw">false; if(!NeuronOCL.calcHiddenGradients(cLSTM.AsObject())) class="kw">return class="kw">false; if(!NeuronOCL.SetGradient(temp, class="kw">false) || !SumAndNormilize(temp, cMamba.getPrevOutput(), temp, iWindow, class="kw">false, class="num">0, class="num">0, class="num">0, class="num">1)) class="kw">return class="kw">false; class=class="str">"cmt">//--- class="kw">return true; } class CNeuronFinMem : class="kw">public CNeuronRelativeCrossAttention { class="kw">protected: CNeuronTransposeOCL cTransposeState;
金融记忆神经元的类结构与初始化参数
在 MT5 的 OpenCL 神经网络扩展里,CNeuronFinMem 这个类专门把行情序列、账户状态和可选动作做交叉注意力融合,相当于给策略装了一块带上下文的记忆体。它内部挂了 2 个 CNeuronMemory 实例和 3 个 CNeuronRelativeCrossAttention(分别处理记忆间、记忆到账户、动作到账户的关系),这是把多源异构输入塞进同一前向通道的底子。 看类声明就能发现,feedForward 和 calcInputGradients、updateInputWeights 都提供了只接单输入返回 false 的空实现,以及带 SecondInput 的双输入重载——说明这个类不走普通全连接那套,必须喂第二路数据才有意义。Type() 固定返回 defNeuronFinMem,Save/Load/Clear 也都重写了,模型持久化和显存回收得自己管。 Init 的形参表直接暴露了调参入口:window 与 window_key 控制时间窗长度,units_count 和 heads 决定注意力头的计算宽度,account_descr、nactions 把账户描述和动作空间维度传进去,optimization_type 与 batch 则绑定训练优化器和批大小。你在 EA 里 new 这个神经元时,heads 设 4、window 设 64 是常见的起步组合,但显存占用会随 batch 线性涨,跑贵金属回测前最好先在小 batch 上探一下卡顿点。外汇与贵金属杠杆高,这类模型仅作概率参考,实盘前务必用历史数据验证过拟合风险。
CNeuronMemory cMemory[class="num">2]; CNeuronRelativeCrossAttention cCrossMemory; CNeuronRelativeCrossAttention cMemoryToAccount; CNeuronRelativeCrossAttention cActionToAccount; class=class="str">"cmt">//--- class="kw">virtual class="type">bool feedForward(CNeuronBaseOCL *NeuronOCL) class="kw">override { class="kw">return class="kw">false; } class="kw">virtual class="type">bool feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) class="kw">override; class="kw">virtual class="type">bool calcInputGradients(CNeuronBaseOCL *NeuronOCL) class="kw">override { class="kw">return class="kw">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 class="kw">false; } class="kw">virtual class="type">bool updateInputWeights(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) class="kw">override; class="kw">public: CNeuronFinMem(class="type">void) {}; ~CNeuronFinMem(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 accoiunt_descr, class="type">uint nactions, 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 defNeuronFinMem; } 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">//--- class="kw">virtual class="type">bool Clear(class="type">void) class="kw">override; }; class="type">bool CNeuronFinMem::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 account_descr, class="type">uint nactions, ENUM_OPTIMIZATION optimization_type, class="type">uint batch)
「交叉注意力模块的初始化链路」
下面这段初始化代码来自一个基于相对交叉注意力的神经网络结构,核心是把各子模块按 index 顺序挂载到计算图里。 先调 CNeuronRelativeCrossAttention::Init,用 nactions/2 作为动作维度的一半、window_key=2、heads 个头,失败直接返回 false;随后 index 自增,进入 cMemory[0] 的两次重复 Init(注意这里连续两个相同调用,可能是代码冗余或不同时间戳窗口复用)。 cMemory[1] 的 Init 参数顺序与 [0] 不同:把 units_count 和 window_key 位置对调,说明它处理的是键值反转的注意力分支;cCrossMemory 再桥接 window 与 units_count 两个空间。 最后 cMemoryToAccount 与 cActionToAccount 把记忆、动作映射到 account_descr 维度(输出维固定为 1),全部成功才 Clear 并返回 true。在 MT5 里跑这套,若某次 Init 返回 false,优先查 OpenCL 上下文与 window/window_key 是否越界——外汇与贵金属模型训练属高风险实验,过拟合概率不低。
{
if(!CNeuronRelativeCrossAttention::Init(numOutputs, myIndex, open_cl,
nactions / class="num">2, window_key, class="num">2, heads, window,
units_count, optimization_type, batch))
class="kw">return class="kw">false;
index++;
if(!cMemory[class="num">0].Init(class="num">0, index, OpenCL, window, window_key, units_count, heads,
optimization, iBatch))
class="kw">return class="kw">false;
index++;
if(!cMemory[class="num">0].Init(class="num">0, index, OpenCL, window, window_key, units_count, heads,
optimization, iBatch))
class="kw">return class="kw">false;
index++;
if(!cMemory[class="num">1].Init(class="num">0, index, OpenCL, units_count, window_key, window, heads,
optimization, iBatch))
class="kw">return class="kw">false;
index++;
if(!cCrossMemory.Init(class="num">0, index, OpenCL, window, window_key, units_count, heads,
units_count, window, optimization, iBatch))
class="kw">return class="kw">false;
index++;
if(!cMemoryToAccount.Init(class="num">0, index, OpenCL, window, window_key, units_count, heads,
account_descr, class="num">1, optimization, iBatch))
class="kw">return class="kw">false;
index++;
if(!cActionToAccount.Init(class="num">0, index, OpenCL, nactions / class="num">2, window_key, class="num">2, heads,
account_descr, class="num">1, optimization, iBatch))
class="kw">return class="kw">false;
class=class="str">"cmt">//---
if(!Clear())
class="kw">return class="kw">false;
class=class="str">"cmt">//---
class="kw">return true;
}◍ 画得少,看得清
这一篇里我们没接语言模型,只按自己对 FinMem 多层记忆思路的理解,在 MT5 里落地了一套纯 MQL5 采集与训练流程。配套文件集中在 FinMem 文件夹:Research.mq5 负责采样,Study.mq5 做模型训练,Test.mq5 跑回测,底层由 NeuroNet.mqh 与 NeuroNet.cl 支撑 OpenCL 加速。
有用户在 2025 年 1 月反馈,Research.mq5 中 if((!CreateDescriptions(actor, critic, critic))) 一行报参数数量不符,作者确认该文实际只用单模型,相关文件需从 FinMem 目录加载而非随意放置。外汇与贵金属杠杆高、滑点跳空频繁,这类自研智代方案在实盘前务必用历史数据复跑验证。
下一期会把这套 MQL5 实现收尾并给出性能评估,当前你能做的,是先开 MT5 把 FinMem 文件夹里的 EA 按 Research→Study→Test 顺序跑一遍,确认自己环境能编译再通过。