交易中的神经网络:配备注意力机制(MASAAT)的智代融汇(终章)·进阶篇
🧠

交易中的神经网络:配备注意力机制(MASAAT)的智代融汇(终章)·进阶篇

(2/3)· 从 TA 模块转置到投资组合生成,拆解多智代注意力框架的工程落地细节

实战向进阶 第 2/3 篇
不少人在复现多智代框架时,直接套用 CSA 模块处理时间维度,忽略了三维张量内转置的轴序差异。MASAAT 的 TA 模块与 CSA 结构近似却视角相反,用错张量形状会让注意力权重完全失真。先理清 [智代、资产、时间] 的排布,再谈再平衡。

智代怎么拼出自己的仓位权重

CSA 模块吐出资产间依赖,TA 模块吐出时间点间依赖,两者经注意力整合后,每个智代先形成带时间信息的资产嵌入,再过一个全连通层输出权重矢量,所有元素合计严格等于 1。实际落地时,我们没照搬原框架数学外的表述,而是把模块输出 reinterpret 成隐藏状态嵌入,方便后续接交易动作(方向、规模、止损止盈),因为原始输入里根本没有账户状态和产品动态。 核心类 CNeuronPortfolioGenerator 继承 CNeuronBaseOCL,内部对象全声明为静态,构造析构留空,初始化全挤在 Init 里。Init 必须收到 5 个大于零的参数:assets(CSA 资产数)、time_points(TA 时间点数)、dimension(嵌入维度,两模块共用)、agents(智代数)、projection(输出状态大小)。父类初始化传 projection,成功后存内部变量,再初始化内部对象——注意 TA 输出是 [智代, 时间, 嵌入] 三维张量且被用两次,所以要用 CNeuronTransposeVRCOCL 对最后两维做转置。 前馈时有两个数据源,TA 结果用两次,故设为主流。计算顺序:第二数据源乘第一数据源转置 → Softmax 归一化(每个资产每智代独立归一,头数 = 资产数 × 智代数)→ 乘原始 TA 流 → 父类全连通层投影到子空间。反向传播 calcInputGradients 完全逆着前馈走,但主流入梯度来自两条流,中间值暂存置换层辅助缓冲区,最后按主流激活函数导数调整再回传 CSA 与 TA。 参数调错一个就直接初始化失败,建议开 MT5 把 assets 和 agents 先设小(如 3 和 2)跑通前馈,再扩到实盘品种数。外汇与贵金属杠杆高,隐藏状态仅表征市场态势,不等于下单信号,需接风控层。

MQL5 / C++
class CNeuronPortfolioGenerator  :  class="kw">public CNeuronBaseOCL
  {
class="kw">protected:
   class="type">uint                iAssets;
   class="type">uint                iTimePoints;
   class="type">uint                iAgents;
   class="type">uint                iDimension;
   class=class="str">"cmt">//---
   CNeuronBaseOCL      cAssetTime[class="num">2];
   CNeuronTransposeVRCOCL  cTransposeVRC;
   CNeuronSoftMaxOCL   cSoftMax;
   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 *prevLayer) 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">public:
                     CNeuronPortfolioGenerator(class="type">void) {};
                    ~CNeuronPortfolioGenerator(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 assets, class="type">uint time_points, class="type">uint dimension,
                     class="type">uint agents,  class="type">uint projection,

「组合生成器的初始化与前向传播骨架」

CNeuronPortfolioGenerator 在 MT5 的 OpenCL 神经网络框架里,承担把多资产、多时间点的特征压缩成组合权重的角色。它的 Init 方法先卡死四个底线参数:assets、time_points、dimension、agents 任一为 0 直接返回 false,避免后续矩阵维度算出 0 长度缓冲。 Init 里依次挂了转置层 cTransposeVRC(维度 iAgents×iTimePoints×iDimension)、两个 AssetTime 缓冲与 SoftMax 头。注意 cSoftMax.SetHeads(iAssets * iAgents) 这行:把 softmax 拆成多个头,意味着输出不是单向量而是分资产分智能体的概率分布,调参时 head 数必须和前面 iAssets*iAgents 对齐,否则 FeedForward 会越界。 前向传播 feedForward 接收 SecondInput 作为第二路特征。流程是先转置 VRC 张量,再做 MatMul 把 SecondInput 与转置结果乘进 cAssetTime[0],维度顺序是 (iAssets, iDimension) × (iDimension, iTimePoints) 按 iAgents 并行。外汇与贵金属行情的高波动下,这种张量并行能压低单根 K 线的噪声权重,但 GPU 显存占用随 iAgents 线性放大,实盘前建议在策略测试器里先跑小 batch 验证。 下面这段是类声明与两个核心方法的原码,逐行看能少踩很多 OpenCL 缓冲绑定的坑。

MQL5 / C++
ENUM_OPTIMIZATION optimization_type, class="type">uint batch);
  class=class="str">"cmt">//---
  class="kw">virtual class="type">int      Type(class="type">void)  class="kw">const class="kw">override   { class="kw">return defNeuronPortfolioGenerator;  }
  class=class="str">"cmt">//---
  class="kw">virtual class="type">bool     Save(class="type">int class="kw">const file_handle) class="kw">override;
  class="kw">virtual class="type">bool     Load(class="type">int class="kw">const file_handle) class="kw">override;
  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 CNeuronPortfolioGenerator::Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl,
                                     class="type">uint assets, class="type">uint time_points, class="type">uint dimension,
                                     class="type">uint agents, class="type">uint projection,
                                     ENUM_OPTIMIZATION optimization_type, class="type">uint batch)
  {
   if(assets <= class="num">0 || time_points <= class="num">0 || dimension <= class="num">0 || agents <= class="num">0)
      class="kw">return class="kw">false;
   if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, projection, optimization_type, batch))
      class="kw">return class="kw">false;
   iAssets = assets;
   iTimePoints = time_points;
   iDimension = dimension;
   iAgents = agents;
   if(!cTransposeVRC.Init(class="num">0, class="num">0, OpenCL, iAgents, iTimePoints, iDimension, optimization, iBatch))
      class="kw">return class="kw">false;
   if(!cAssetTime[class="num">0].Init(class="num">0, class="num">1, OpenCL, iAssets * iTimePoints * iAgents, optimization, iBatch))
      class="kw">return class="kw">false;
   cAssetTime[class="num">0].SetActivationFunction(None);
   if(!cSoftMax.Init(class="num">0, class="num">2, OpenCL, cAssetTime[class="num">0].Neurons(), optimization, iBatch))
      class="kw">return class="kw">false;
   cSoftMax.SetHeads(iAssets * iAgents);
   if(!cAssetTime[class="num">1].Init(Neurons(), class="num">3, OpenCL, iAssets * iDimension * iAgents, optimization, iBatch))
      class="kw">return class="kw">false;
   cAssetTime[class="num">1].SetActivationFunction(None);
class=class="str">"cmt">//---
   class="kw">return true;
  }
class="type">bool CNeuronPortfolioGenerator::feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput)
  {
   if(!SecondInput)
      class="kw">return class="kw">false;
class=class="str">"cmt">//---
   if(!cTransposeVRC.FeedForward(NeuronOCL))
      class="kw">return class="kw">false;
   if(!MatMul(SecondInput, cTransposeVRC.getOutput(), cAssetTime[class="num">0].getOutput(), iAssets, iDimension,
iTimePoints, iAgents))
      class="kw">return class="kw">false;
   if(!cSoftMax.FeedForward(cAssetTime[class="num">0].AsObject()))

◍ 组合层反向传播里的矩阵梯度链

这一段是组合生成器(PortfolioGenerator)在训练时回传梯度的核心实现,直接决定了多资产权重矩阵能否在 OpenCL 上正确更新。

MQL5 / C++
class="type">bool CNeuronPortfolioGenerator::calcInputGradients(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput,
CBufferFloat *SecondGradient, ENUM_ACTIVATION SecondActivation = -class="num">1)
  {
  if(!NeuronOCL || !SecondGradient || !SecondInput)
    class="kw">return class="kw">false;
  if(!CNeuronBaseOCL::calcInputGradients(cAssetTime[class="num">1].AsObject()))
    class="kw">return class="kw">false;
  if(!MatMulGrad(cSoftMax.getOutput(), cSoftMax.getGradient(),
                 NeuronOCL.getOutput(), cTransposeVRC.getPrevOutput(),
                 cAssetTime[class="num">1].getGradient(),
                 iAssets, iTimePoints, iDimension, iAgents))
    class="kw">return class="kw">false;
  if(!cAssetTime[class="num">0].calcHiddenGradients(cSoftMax.AsObject()))
    class="kw">return class="kw">false;
  if(!MatMulGrad(SecondInput, SecondGradient,
                 cTransposeVRC.getOutput(), cTransposeVRC.getGradient(),
                 cAssetTime[class="num">0].getGradient(),
                 iAssets, iDimension, iTimePoints, iAgents))
    class="kw">return class="kw">false;
  if(SecondActivation != None)
    if(!DeActivation(SecondInput, SecondGradient, SecondGradient, SecondActivation))
      class="kw">return class="kw">false;
  if(!NeuronOCL.calcHiddenGradients(cTransposeVRC.AsObject()) ||
     !SumAndNormilize(NeuronOCL.getGradient(), cTransposeVRC.getPrevOutput(), NeuronOCL.getGradient(),
     iDimension, class="kw">false, class="num">0, class="num">0, class="num">0, class="num">1))
    class="kw">return class="kw">false;
  if(NeuronOCL.Activation() != None)
    if(!DeActivation(NeuronOCL.getOutput(), cTransposeVRC.getPrevOutput(), cTransposeVRC.getPrevOutput(),
    NeuronOCL.Activation()))
      class="kw">return class="kw">false;
class=class="str">"cmt">//---
  class="kw">return true;
  }
逐行拆解:函数入口先判空,NeuronOCL、SecondGradient、SecondInput 任一为空直接返回 false,避免空指针在显存上炸核。接着调基类 calcInputGradients 对 cAssetTime[1] 做基础梯度初始化,失败即退出。 第一个 MatMulGrad 用 softmax 的输出与梯度,结合 NeuronOCL 输出和转置缓冲的上一帧输出,算出 cAssetTime[1] 的梯度;维度参数是 iAssets × iTimePoints × iDimension × iAgents,这四个量必须和前向 feedForward 里 MatMul 调用完全一致,否则梯度形状对不上会静默返回 false。 第二个 MatMulGrad 处理 SecondInput 这支旁路,把梯度写回 cAssetTime[0];之后若 SecondActivation 不是 None 就做 DeActivation 反激活。最后对 NeuronOCL 隐藏层梯度计算后做 SumAndNormilize(末位参数 1 表示按特定轴归一),若 NeuronOCL 自身有激活函数再补一次 DeActivation。 在 MT5 里接这套网络时,重点核对 iDimension 与 SumAndNormilize 的归一轴:若组合层权重更新方向明显偏离,优先打印这四个维度变量与 PrevOutput 的 buffer 大小。外汇与贵金属市场波动剧烈,此类 GPU 训练策略仅作研究验证,实盘存在较高回撤风险。

MQL5 / C++
class="type">bool CNeuronPortfolioGenerator::calcInputGradients(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput,
CBufferFloat *SecondGradient, ENUM_ACTIVATION SecondActivation = -class="num">1)
  {
  if(!NeuronOCL || !SecondGradient || !SecondInput)
    class="kw">return class="kw">false;
  if(!CNeuronBaseOCL::calcInputGradients(cAssetTime[class="num">1].AsObject()))
    class="kw">return class="kw">false;
  if(!MatMulGrad(cSoftMax.getOutput(), cSoftMax.getGradient(),
                 NeuronOCL.getOutput(), cTransposeVRC.getPrevOutput(),
                 cAssetTime[class="num">1].getGradient(),
                 iAssets, iTimePoints, iDimension, iAgents))
    class="kw">return class="kw">false;
  if(!cAssetTime[class="num">0].calcHiddenGradients(cSoftMax.AsObject()))
    class="kw">return class="kw">false;
  if(!MatMulGrad(SecondInput, SecondGradient,
                 cTransposeVRC.getOutput(), cTransposeVRC.getGradient(),
                 cAssetTime[class="num">0].getGradient(),
                 iAssets, iDimension, iTimePoints, iAgents))
    class="kw">return class="kw">false;
  if(SecondActivation != None)
    if(!DeActivation(SecondInput, SecondGradient, SecondGradient, SecondActivation))
      class="kw">return class="kw">false;
  if(!NeuronOCL.calcHiddenGradients(cTransposeVRC.AsObject()) ||
     !SumAndNormilize(NeuronOCL.getGradient(), cTransposeVRC.getPrevOutput(), NeuronOCL.getGradient(),
     iDimension, class="kw">false, class="num">0, class="num">0, class="num">0, class="num">1))
    class="kw">return class="kw">false;
  if(NeuronOCL.Activation() != None)
    if(!DeActivation(NeuronOCL.getOutput(), cTransposeVRC.getPrevOutput(), cTransposeVRC.getPrevOutput(),
    NeuronOCL.Activation()))
      class="kw">return class="kw">false;
class=class="str">"cmt">//---
  class="kw">return true;
  }

把分散模块收进一个类里

MASAAT 各个子模块写完后,要塞进同一个类才方便跑端到端。这里选 CNeuronPortfolioGenerator 当父类,因为它本来就是流程末端的组合模块,继承它就能直接复用输出逻辑,不必再在内部 new 一个对象。新类 CNeuronMASAAT 里只声明了转置、分段线性表示(PLR)、级联层、横截面分析(CSA)和时序分析(TA)这几个成员,所有方法基本就是按顺序调内部对象。 所有成员都声明为静态,构造和析构函数留空,初始化全丢给 Init 方法。Init 的参数里包含输入窗口大小、序列长度、智代头数等常量。调用父类 Init 时有个坑:原始多模态序列会被压成三段式线性表示,时间点数量缩为原来的 1/3,所以传给父类的序列长度要除以 3。 智代数量也比阈值偏差向量长度多 1。作者早先验证过,把分段线性表示和原始序列拼起来用,模型效率倾向更高,所以多分配一个智代直接在未经修改的原始序列上干活。 前向通验 feedForward 看着平铺直叙:输入指针交给转置对象,输出和原始数据级联后送进 CSA 与 TA,再喂给父类方法。但信息流背后有分叉——转置后的原始数据和级联张量都被用了两次,这给反向传播的梯度分发埋了雷。 calcInputGradients 里先调父类方法,让 CSA 和 TA 按对输出的影响分梯度;两个模块都作用在级联张量上,所以梯度要从两条流汇回来,这里用缓冲区替换技术把第二支流的梯度提出来相加。级联张量再把梯度派给转置对象和 PLR 对象,必要时按激活函数导数修正,最后回传到输入层。外汇与贵金属信号建模属高风险,回测结论仅代表历史概率。 模型接进参与者架构时,动态窗口数组指定了分析窗口大小和隐藏状态长度,三位智代的阈值用几何级数生成,其余参数走标准值。完整代码在附件,架构基本沿用前作,不用重头捋。

MQL5 / C++
class CNeuronMASAAT  :  class="kw">public CNeuronPortfolioGenerator
  {
class="kw">protected:
   CNeuronTransposeOCL                cTranspose;
   CNeuronPLRMultiAgentsOCL          cPLR;
   CNeuronBaseOCL                    cConcat;
   CNeuronCrossSectionalAnalysis     cCrossSectionalAnalysis;
   CNeuronTemporalAnalysis           cTemporalAnalysis;
   class=class="str">"cmt">//---
   class="kw">virtual class="type">bool      feedForward(CNeuronBaseOCL *NeuronOCL) class="kw">override;
   class="kw">virtual class="type">bool      feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput)  class="kw">override
     { class="kw">return      feedForward(NeuronOCL); }
   class="kw">virtual class="type">bool      calcInputGradients(CNeuronBaseOCL *prevLayer) class="kw">override;
   class="kw">virtual class="type">bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput,
CBufferFloat *SecondGradient, ENUM_ACTIVATION SecondActivation = None) class="kw">override
     { class="kw">return      calcInputGradients(NeuronOCL); }
   class="kw">virtual class="type">bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) class="kw">override;
class="kw">public:
                    CNeuronMASAAT(class="type">void)  {};
                    ~CNeuronMASAAT(class="type">void)  {};
   class=class="str">"cmt">//---
   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 heads, class="type">uint units_cout,
                    class="type">uint layers, vector<class="type">class="kw">float> &min_distance, class="type">uint projection,
                    ENUM_OPTIMIZATION optimization_type, class="type">uint batch);
   class=class="str">"cmt">//---

「MASAAT 神经元的初始化与前向传播链路」

CNeuronMASAAT 在类声明里重写了 Type、Save、Load、WeightsUpdate 和 SetOpenCL 五个虚函数,Type 直接返回 defNeuronMASAAT 常量,用于运行时类型辨识。其余几个留作外部持久化与 OpenCL 上下文注入的接口,具体实现由派生层完成。 Init 函数是这个神经元的装配核心。它先调用基类 CNeuronPortfolioGenerator::Init,注意这里把传入的 units_cout 除以 3 再喂给基类,同时用 min_distance.Size()+1 作为某一维规模——这种维度压缩在外接多资产序列时容易踩坑,调参时建议先打印确认实际张量形状。 随后依次初始化 cTranspose、cPLR、cConcat、cCrossSectionalAnalysis、cTemporalAnalysis 五个子模块,索引号从 0 到 4 硬编码在第二个参数里。cCrossSectionalAnalysis 与 cTemporalAnalysis 的 heads 维都用了 heads/2 作为中间通道数,若 heads 为奇数会截断,回测中 heads=8 时两模块各占 4 通道。 feedForward 严格按 Transpose → PLR → Concat → 双分析模块的顺序流动。Concat 调用把前两步输出按样本轴(第三参为 1)拼起来,再同时送进横截面和时间序列两个分析分支;任一步返回 false 就中断,说明中间张量尺寸不匹配是主要失败原因,开 MT5 跑自定义 EA 时可在每行后加 Print 查断点。

MQL5 / C++
class="kw">virtual class="type">int Type(class="type">void) class="kw">const class="kw">override { class="kw">return defNeuronMASAAT; }
class=class="str">"cmt">//---
class="kw">virtual class="type">bool Save(class="type">int class="kw">const file_handle) class="kw">override;
class="kw">virtual class="type">bool Load(class="type">int class="kw">const file_handle) class="kw">override;
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 CNeuronMASAAT::Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl,
                         class="type">uint window, class="type">uint window_key, class="type">uint heads, class="type">uint units_cout,
                         class="type">uint layers, vector<class="type">class="kw">float> &min_distance, class="type">uint projection,
                         ENUM_OPTIMIZATION optimization_type, class="type">uint batch)
  {
   if(!CNeuronPortfolioGenerator::Init(numOutputs, myIndex, open_cl, window, units_cout / class="num">3,
                                       window_key, (class="type">uint)min_distance.Size() + class="num">1, projection, optimization_type, batch))
      class="kw">return class="kw">false;
   if(!cTranspose.Init(class="num">0, class="num">0, OpenCL, units_cout, window, optimization, iBatch))
      class="kw">return class="kw">false;
   if(!cPLR.Init(class="num">0, class="num">1, OpenCL, window, units_cout, class="kw">false, min_distance, optimization, iBatch))
      class="kw">return class="kw">false;
   if(!cConcat.Init(class="num">0, class="num">2, OpenCL, cTranspose.Neurons() + cPLR.Neurons(), optimization, iBatch))
      class="kw">return class="kw">false;
   if(!cCrossSectionalAnalysis.Init(class="num">0, class="num">3, OpenCL, units_cout, window_key, heads, heads / class="num">2, window, layers, class="num">1,
iAgents, optimization, iBatch))
      class="kw">return class="kw">false;
   if(!cTemporalAnalysis.Init(class="num">0, class="num">4, OpenCL, units_cout, window_key, heads, heads / class="num">2, window, layers, class="num">1,
iAgents, optimization, iBatch))
      class="kw">return class="kw">false;
class=class="str">"cmt">//---
   class="kw">return true;
  }
class="type">bool CNeuronMASAAT::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   if(!cTranspose.FeedForward(NeuronOCL))
      class="kw">return class="kw">false;
   if(!cPLR.FeedForward(cTranspose.AsObject()))
      class="kw">return class="kw">false;
   if(!Concat(cTranspose.getOutput(), cPLR.getOutput(), cConcat.getOutput(), cTranspose.Neurons(), cPLR.Neurons(), class="num">1))
      class="kw">return class="kw">false;
   if(!cCrossSectionalAnalysis.FeedForward(cConcat.AsObject()))
      class="kw">return class="kw">false;
   if(!cTemporalAnalysis.FeedForward(cConcat.AsObject()))
      class="kw">return class="kw">false;
class=class="str">"cmt">//---
把张量排布交给小布核对
这些维度校验与模块装配的重复劳动,小布盯盘背后的 AIGC 已内置了检查逻辑,打开对应品种页就能看到张量形状提示,你只需专注策略层面的调参。

常见问题

结构高度相似,但分析视角正交;工程上可对原始序列转置后复用 CNeuronCrossSectionalAnalysis,但需确保三维张量按 [智代、资产、时间] 正确转置,否则跨时间点依赖会被误读。
不同阈值控制提取关键趋势的灵敏度,阈值宽则只捕捉显著变动、过滤噪声,阈值窄会纳入弱信号,可能在高波动外汇贵金属市场放大误判概率。
可以,小布盯盘集成的诊断视图能并行呈现多品种跨时间点的相关性热力,省去手动核对 CSA 与 TA 模块产出的繁琐,但外汇贵金属属高风险,信号仅作概率参考。
模块依据 CSA 与 TA 的注意力评估动态调配资本,波动期倾向降权高依赖资产,具体权重受分段线性尺度影响,实际效果随市况可能偏移。
每个模块作为独立类实现后,需统一结构体接收多模态序列对象;重点测试模块间张量接口,避免维度和粒度层级错配导致融汇失效。