交易中的神经网络:对比形态变换器(终章)·综合运用
📘

交易中的神经网络:对比形态变换器(终章)·综合运用

第 3/3 篇

反向传播里的梯度注销与二层描述构造

这段逻辑跑在神经网络反向传播阶段,先对编码器、motifs、prop 三个模块做反激活(DeActivation),任一失败直接 return false 中断。注意判断条件是 Activation() != None,说明未激活的层不参与梯度回传,省掉无效计算。 随后 motifs.calcAlignmentGradient 以 cAtomEncoder.AsObject() 为参照、参数 true 计算对齐梯度;接着两个倒序 for 循环从 Total()-2 扫到 0,逐级调用 calcHiddenGradients 把梯度向更早的投影层传递,循环里只要某一层空指针或计算失败就退出。 cMotifEncoder 与 NeuronOCL 都用了 SumAndNormilize 做梯度累加归一,参数序列里有 window 和末尾的 1,暗示按时间窗做缩放;temp 缓冲在 SetGradient 间倒手,避免覆盖上游梯度。外汇与贵金属模型用这套结构时,过拟合概率偏高,务必用小样本先验证。 最后 layer 2 用 new CLayerDescription() 建描述符,type 设 defNeuronAMCT,window 绑 BarDescr;units 数组写死 {HistoryBars, 100},代表 Bars 与 Properties 两维,ArrayCopy 返回值小于 temp.Size() 就判失败。开 MT5 把 HistoryBars 调小到 50 附近,能直观看到二层输入维度变化。

MQL5 / C++
if (cAtomEncoder.Activation() != None) {
  if (!DeActivation(cAtomEncoder.getOutput(), cAtomEncoder.getGradient(), cAtomEncoder.getGradient(),
                     cAtomEncoder.Activation()))
    class="kw">return false;
}
if (motifs.Activation() != None) {
  if (!DeActivation(motifs.getOutput(), motifs.getGradient(), motifs.getGradient(),
                     motifs.Activation()))
    class="kw">return false;
}
if (prop.Activation() != None) {
  if (!DeActivation(prop.getOutput(), prop.getGradient(), prop.getGradient(),
                     prop.Activation()))
    class="kw">return false;
}
if(!motifs.calcAlignmentGradient(cAtomEncoder.AsObject(), true))
  class="kw">return false;
for (class="type">int i = cMotifProjection.Total() - class="num">2; i >= class="num">0; i--)
{
  motifs = cMotifProjection[i];
  if (!motifs ||
      !motifs.calcHiddenGradients(cMotifProjection[i + class="num">1]))
    class="kw">return false;
}
for (class="type">int i = cPropertyProjection.Total() - class="num">2; i >= class="num">0; i--)
{
  prop = cPropertyProjection[i];
  if (!prop ||
      !prop.calcHiddenGradients(cPropertyProjection[i + class="num">1]))
    class="kw">return false;
}
if (!cPropertyDecoder.calcHiddenGradients(cPropertyProjection[class="num">0]) ||
    !cMotifEncoder.calcHiddenGradients(cPropertyDecoder.AsObject()))
  class="kw">return false;
CBufferFloat *temp = cMotifEncoder.getGradient();
if (!cMotifEncoder.SetGradient(cConcatenate.getGradient(), false) ||
    !cMotifEncoder.calcHiddenGradients(cMotifProjection[class="num">0]) ||
    !SumAndNormilize(temp, cMotifEncoder.getGradient(), temp, window, false, class="num">0, class="num">0, class="num">0, class="num">1) ||
    !cMotifEncoder.SetGradient(temp, false) ||
    !NeuronOCL.calcHiddenGradients(cMotifEncoder.AsObject()))
  class="kw">return false;
temp = NeuronOCL.getGradient();
if (!NeuronOCL.SetGradient(cConcatenate.getGradient(), false) ||
    !NeuronOCL.calcHiddenGradients(cAtomEncoder.AsObject()) ||
    !SumAndNormilize(temp, NeuronOCL.getGradient(), temp, window, false, class="num">0, class="num">0, class="num">0, class="num">1) ||
    !NeuronOCL.SetGradient(temp, false))
  class="kw">return false;
class=class="str">"cmt">//---
class="kw">return true;
}
class=class="str">"cmt">//--- layer class="num">2
if(!(descr = new CLayerDescription()))
  class="kw">return false;
descr.type = defNeuronAMCT;
descr.window = BarDescr;            class=class="str">"cmt">// Window(Indicators to bar)
  {
    class="type">int temp[] = {HistoryBars, class="num">100}; class=class="str">"cmt">// Bars, Properties
    if(ArrayCopy(descr.units, temp) < (class="type">int)temp.Size())
      class="kw">return false;
  }

◍ Transformer 编码器的关键维度与层数设定

在 MT5 里搭一个基于注意力机制的行情编码器时,描述结构体里的几个字段直接决定模型容量和推理开销。下面这段配置把键维度压到嵌入维度的一半,层数设 5,注意力头步长设 4,属于中等复杂度的折中方案。 descr.window_out = EmbeddingSize / 2; // 键维度取嵌入尺寸一半,控制 QK 点积的计算宽度 descr.layers = 5; // 堆叠 5 层编码器,层数越多拟合越强但易过拟合 descr.step = 4; // 头步长 4,影响多头注意力的切片方式 descr.batch = 1e4; // 批大小 1 万,显存吃紧时可能要下调 descr.activation = None; // 层间不用额外激活 descr.optimization = ADAM; // 用 ADAM 优化器更新权重 if(!encoder.Add(descr)) // 把描述体加进编码器,失败就清内存并返回 { delete descr; return false; } 实盘前建议把 batch 从 1e4 往下调到 2e3 附近跑通再说,外汇与贵金属波动剧烈、杠杆高风险大,配置只是起点,回测不过就是废纸。

MQL5 / C++
  descr.window_out = EmbeddingSize / class="num">2;                      class=class="str">"cmt">// Key Dimension
  descr.layers = class="num">5;                                        class=class="str">"cmt">// Layers
  descr.step = class="num">4;                                          class=class="str">"cmt">// Heads
  descr.batch = class="num">1e4;
  descr.activation = None;
  descr.optimization = ADAM;
  if(!encoder.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return false;
    }

「三个月只跑21笔,统计模型的天花板」

框架在 MQL5 里写完,下一步就是拿真实历史数据验成色。我们照例用覆盖 2023 全年的离线训练集做迭代训练,每迭代几次就按当前策略对 agent 的操作反馈刷新一次数据集,让训练目标尽量贴住实盘动作。 训练集和测试集上都出了正收益,但模型只肯下单极少次。把测试窗口拉到 3 个月,总共才成交 21 笔,胜率刚过一半。余额曲线前 1.5 个月爬升,之后横盘——这不算翻车,反而印证了设计前提:模型只统计训练集里出现过的市场状态。 一年训练数据大约只撑得起 1.2 到 1.5 个月的像样表现。顺着推,若喂十年数据,有可能得到稳跑一年的模型,更大的集子也可能识别出更多关键形态、把交易频率提上来。不过这还只是假设,真要坐实得继续深挖模型。外汇与贵金属属高风险品种,此类统计结论不构成立仓依据,请以 MT5 策略测试器自测为准。

别急着下结论

前面三篇把原子-基序对比变换器(AMCT)的骨架拆开了:烛条当原子、形态当基序,用对比学习去区分哪些局部结构带信息、哪些是噪声。变换器在这套框架里负责抓烛条与形态之间的长期依赖,理论上能比单纯看K线组合多一层市场行为推断。 我们在 MT5 里落地了全套流程——Research.mq5 收样本、Study.mq5 训模型、Test.mq5 跑历史。实测结果偏冷:训出来的模型在真实数据上交易信号极少,说明信息性形态筛选阈值或对比损失权重可能还需要调。外汇与贵金属属高风险品种,这类实验模型不能直接当信号源。 潜力不在「马上能跑」,而在它把价格结构分层编码的思路。下一步若想验证,可先改 NeuroNet.mqh 里的采样窗口,把基序长度从 5 根放宽到 8 根,看 Test.mq5 的信号密度是否改善。

常见问题

看隐藏层梯度绝对值是否长期接近0;若是,降低学习率或换激活函数,并减少无用二层描述特征。
小样本下建议2~4层、维度32~64;层数过多易过拟合,先跑基线再微调。
可以,小布能读取你的训练日志并标出梯度异常层,给出精简特征与调参建议,省去手动排查。
样本太少只能看概率倾向,别当确定结论;优先用样本内交叉验证,外汇贵金属属高风险勿重仓。
可能只是拟合了噪声;先拆开二层描述构造与编码器设定,做样本外检验再决定是否上手。