您应当知道的 MQL5 向导技术(第 34 部分):采用非常规 RBM 进行价格嵌入·进阶篇
🧠

您应当知道的 MQL5 向导技术(第 34 部分):采用非常规 RBM 进行价格嵌入·进阶篇

(2/3)· 当趋势逆转时常用设置集体失灵,用非传统 RBM 降维提取价格特征或许能多一层缓冲

含代码示例实战向 第 2/3 篇
很多交易者把 RBM 当成只能靠吉布斯采样和对比散度训练的黑盒,遇到反向传播就绕路。其实在 GPU 算力普及时,用反向传播跑 RBM 做价格嵌入已具备可行性,只是多数向导模板没暴露这条路径。

「RBM 的正负压相位与权重复用机制」

受限玻尔兹曼机跑起来就两个周期:正相位把输入往隐藏层送,负相位用同一套权重把隐藏层重建回输入。它本质是无监督的——训练数据没标签,输入自己就是自己的目标值,重建误差代替了常规监督里的外部标注。 在 MT5 里我们写 C_u_rbm 类继承 Cmlp,但只留两层结构(输入层节点数必须多于隐藏层,否则 validated 置 false)。虽然只有 1 个权重矩阵 weights[0],却要配 2 个乖离向量、2 个增量向量和 2 个梯度矩阵,因为正、负相位各做一次反向传播,权重每次更新两次。 正相位第一次碰权重矩阵,乘积进第一个乖离向量并抓梯度;负相位复用同权重,但乖离向量是新的,乘积出来的是对输入数据的重建。重建值和原始输入的差就是增量,喂回反向传播。外汇与贵金属行情用这类模型做特征降维时,样本非平稳,过拟合概率高,需严控学习率。 下面两段是相位函数的核心:GetPositive 算隐藏层输出,GetNegative 用 output 乘回权重得到重建 label。注意负相位偏置用的是 biases[1] 而非 biases[0],这是两相位解耦的关键点。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void C_u_rbm::GetPositive(class="type">void)
{  vector _positive = weights[class="num">0].MatMul(inputs), _output;
   _positive += biases[class="num">0];
   _positive.Activation(_output, THIS.activation);
   output = _output;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void C_u_rbm::GetNegative(class="type">void)
{  vector _negative = output.MatMul(weights[class="num">0]), _output;
   _negative += biases[class="num">1];
   _negative.Activation(_output, THIS.activation);
   label = _output;
}
class="macro">#include <My\Cmlp-.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Unconventional RBM that uses:                                    |
class=class="str">"cmt">//| reconstruction-error instead of free-energy                       |
class=class="str">"cmt">//| and back-propagation instead of contrastive divergence            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class C_u_rbm : class="kw">public Cmlp
{
class="kw">protected:
class="kw">public:
   class="type">void              GetPositive();
   class="type">void              GetNegative();

   class="type">void              BackPropagate(class="type">class="kw">double LearningRate = class="num">0.1);
   class="type">class="kw">double            Get(ENUM_REGRESSION_METRIC R)
   {                 class="kw">return(label.RegressionMetric(inputs, R));
   }
   class="type">void              C_u_rbm(Smlp &MLP) : Cmlp(MLP)
   {   validated = false;
       class="type">int _layers =   ArraySize(MLP.arch);
       if(_layers == class="num">2 && MLP.arch[class="num">0] > MLP.arch[class="num">1])
       {   ArrayResize(biases, _layers);
           class=class="str">"cmt">//

◍ 多层感知机权重容器的初始化细节

在 MT5 里搭多层感知机,第一步不是算前向传播,而是把每一层的梯度与 delta 容器按架构尺寸撑开。代码里先用 ArrayResize 把 gradients、gradients_1st_moment、gradients_2nd_moment、sum_gradients、sum_gradients_update 以及 deltas 系列各数组统一扩到 _layers 长度,这是 Adam 类优化器要用的动量占位。 随后循环 i 从 0 到 _layers-1,取 _rows = MLP.arch[_layers-1-i]、_columns = MLP.arch[i],即对称地用输出侧层数做行、输入侧层数做列。biases[i] 按 _rows 初始化并填 MLP.initial_bias,权重梯度矩阵则 Init(_rows,_columns) 后全部 Fill(0.0),一阶二阶矩与累加器同理清零下。 有个易踩的坑:架构校验不通过时走 else 分支,用 printf 抛出 __FUNCSIG__ 加具体尺寸信息,包括 ArrayMaximum(MLP.arch) 与 ArrayMinimum(MLP.arch) 算出的层大小极值和激活函数枚举名。你拷这段去自己 EA 里跑,若终端日志出现 'invalid network arch',先核对 MLP.arch 数组是否含 0 或超限值,而不是怀疑优化器写错。 外汇与贵金属行情用神经网络拟合本身属高风险尝试,历史结构失配对权重的冲击可能很大,实盘前务必在策略测试器用多段样本外数据验容器初始化是否每次重置干净。

MQL5 / C++
  ArrayResize(gradients, _layers);
  ArrayResize(gradients_1st_moment, _layers);
  ArrayResize(gradients_2nd_moment, _layers);
  ArrayResize(sum_gradients, _layers);
  ArrayResize(sum_gradients_update, _layers);
  class=class="str">"cmt">//
  ArrayResize(deltas, _layers);
  ArrayResize(deltas_1st_moment, _layers);
  ArrayResize(deltas_2nd_moment, _layers);
  ArrayResize(sum_deltas, _layers);
  ArrayResize(sum_deltas_update, _layers);
  class=class="str">"cmt">//
  hidden_layers = class="num">0;
  class="type">bool _norm_validated = true;
  for(class="type">int i = class="num">0; i < _layers; i++)
  {  class="type">int _rows = MLP.arch[_layers - class="num">1 - i], _columns = MLP.arch[i];
    class=class="str">"cmt">//
    biases[i].Init(_rows);
    biases[i].Fill(MLP.initial_bias);
    class=class="str">"cmt">//
    gradients[i].Init(_rows, _columns);
    gradients[i].Fill(class="num">0.0);
    class=class="str">"cmt">//
    gradients_1st_moment[i].Init(_rows, _columns);
    gradients_1st_moment[i].Fill(class="num">0.0);
    gradients_2nd_moment[i].Init(_rows, _columns);
    gradients_2nd_moment[i].Fill(class="num">0.0);
    class=class="str">"cmt">//
    sum_gradients[i].Init(_rows, _columns);
    sum_gradients[i].Fill(class="num">0.0);
    sum_gradients_update[i].Init(_rows, _columns);
    sum_gradients_update[i].Fill(class="num">0.0);
    class=class="str">"cmt">//
    deltas[i].Init(_rows);
    deltas[i].Fill(class="num">0.0);
    deltas_1st_moment[i].Init(_rows);
    deltas_1st_moment[i].Fill(class="num">0.0);
    deltas_2nd_moment[i].Init(_rows);
    deltas_2nd_moment[i].Fill(class="num">0.0);
    sum_deltas[i].Init(_rows);
    sum_deltas[i].Fill(class="num">0.0);
    sum_deltas_update[i].Init(_rows);
    sum_deltas_update[i].Fill(class="num">0.0);
  }
  validated = true;
  }
  else
  {  printf(__FUNCSIG__ +
      " invalid network arch! Settings size is: %i, Max layer size is: %i, Min layer size is: %i, and activation is %s ",
      _layers, MLP.arch[ArrayMaximum(MLP.arch)], MLP.arch[ArrayMinimum(MLP.arch)], EnumToString(MLP.activation)
     );

RBM 反向传播里的梯度与权重更新

这段 C_u_rbm::BackPropagate 的实现把受限玻尔兹曼机的训练闭环写得很直接:先算各层 delta,再算梯度,最后按学习率回写权重和偏置。默认 LearningRate=0.1,在 MT5 里跑小样本行情特征重构时,这个初值通常能让损失在百步内进入平缓区,但具体收敛速度取决于输入归一化质量。 代码先用 label.LossGradient 拿到输出层误差梯度,再通过权重矩阵乘当前激活反向推隐藏层 delta;注意 _negative 与 _positive 都套了 Derivative 做激活函数求导,Hadamard 逐元素相乘才是真正的链式法则落点。 梯度矩阵由 deltas 与输入/输出的转置相乘得到,循环里 h 从 1 到 0 仅两轮:h==1 时梯度需转置才能对齐权重维度,随后 weights[0] 和 biases[h] 同步减去学习率乘梯度。外汇与贵金属品种用这类自编码做特征压缩时波动剧烈,过拟合概率偏高,建议先拿历史 Tick 做离线验证再上实盘。

MQL5 / C++
class="type">void C_u_rbm::BackPropagate(class="type">class="kw">double LearningRate = class="num">0.1)
{  
class=class="str">"cmt">//COMPUTE DELTAS
  vector _loss = label.LossGradient(inputs, THIS.loss);
  class=class="str">"cmt">//
  vector _negative = output.MatMul(weights[class="num">0]), _negative_derivative;
  _negative.Derivative(_negative_derivative, THIS.activation);
  deltas[class="num">1] = Hadamard(_loss, _negative_derivative);
  class=class="str">"cmt">//
  vector _positive = weights[class="num">0].MatMul(inputs), _positive_derivative;
  _positive.Derivative(_positive_derivative, THIS.activation);
  matrix _weights;
  _weights.Copy(weights[class="num">0]);
  _weights.Transpose();
  vector _product = _weights.MatMul(deltas[class="num">1]);
  deltas[class="num">0] = Hadamard(_product, _positive_derivative);
class=class="str">"cmt">//COMPUTE GRADIENTS
  gradients[class="num">0] = TransposeCol(deltas[class="num">0]).MatMul(TransposeRow(inputs));
  gradients[class="num">1] = TransposeCol(deltas[class="num">1]).MatMul(TransposeRow(output));
  
class=class="str">"cmt">// UPDATE WEIGHTS AND BIASES
  for(class="type">int h = class="num">1; h >= class="num">0;  h--)
  {  matrix _gradients;
    _gradients.Copy(gradients[h]);
    if(h == class="num">1)
    {  _gradients = _gradients.Transpose();
    }
    weights[class="num">0] -= LearningRate * _gradients;
    biases[h] -= LearningRate * deltas[h];
  }
}

「信号类里怎么接住 RBM 的嵌入输出」

把自定义 RBM 类塞进信号类后,EA 只是半成品——按设计,RBM 隐藏层数值要再喂给一个 MLP 当输入。RBM 在这里干的是嵌入的活:把价格变化压进更小维度。我们的实测网络是 8-4 结构,即可见层 8 节点、隐藏层 4 节点。 我倾向把 RBM 当分类器而非回归器用,所以损失函数锁了分类交叉熵、激活用 soft-sign。这些在代码里是常量,不参与参数优化,避免过拟合噪声。 负责嵌入的函数叫 Embedder。注意 MT5 向量复制价格时索引是倒序的:最高索引装的是最新值,链式训练 RBM→MLP 时要同时拿到 RBM 输入和 MLP 标签,所以 if 判断写得长。 我们采用 RBM 与 MLP 几乎同步训练:在每个训练点上先训 RBM 再跟训 MLP,这比先跑完 RBM 再训 MLP 更省事。外汇与贵金属波动剧烈,这种嵌套结构实盘前务必在策略测试器跑通。 下面这段代码就是 Embedder 本体,核心在 _in_new 与 _in_old 做差得到价格变化,再送进 RBM 正向反向传播:

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CSignalEmbedding::Embedder(vector &Extraction)
{  m_learning.rate = m_learning_rate;
   for(class="type">int i = class="num">1; i <= m_epochs; i++)
   {  U_RBM.LearningType(m_learning, i);
      for(class="type">int ii = m_train_set; ii >= class="num">0; ii--)
      {  vector _in, _in_new, _in_old, _out, _out_new, _out_old;
         if
         (
         _in_new.Init(__RBM_VISIBLE) && 
         _in_new.CopyRates(m_symbol.Name(), m_period, class="num">8, ii + __MLP_OUTPUTS, __RBM_VISIBLE) && 
         _in_new.Size() == __RBM_VISIBLE &&
         _in_old.Init(__RBM_VISIBLE) && 
         _in_old.CopyRates(m_symbol.Name(), m_period, class="num">8, ii + __MLP_OUTPUTS + class="num">1, __RBM_VISIBLE) && 
         _in_old.Size() == __RBM_VISIBLE
         &&
         _out_new.Init(__MLP_OUTPUTS) &&
         _out_new.CopyRates(m_symbol.Name(), m_period, class="num">8, ii, __MLP_OUTPUTS) &&
         _out_new.Size() == __MLP_OUTPUTS &&
         _out_old.Init(__MLP_OUTPUTS) &&
         _out_old.CopyRates(m_symbol.Name(), m_period, class="num">8, ii + __MLP_OUTPUTS, __MLP_OUTPUTS) &&
         _out_old.Size() == __MLP_OUTPUTS
         )
         {  _in = _in_new - _in_old;
            _out = _out_new - _out_old;
            U_RBM.Set(_in);
            U_RBM.GetPositive();
            U_RBM.GetNegative();
            U_RBM.BackPropagate(m_learning.rate);
            Extraction = Extractor(U_RBM.output, _out, ii > class="num">0);
         }
      }
   }
}
把非常规网络跑批交给小布
小布盯盘的 AIGC 已内置这类非常规特征提取的看板原型,打开对应品种页即可切换 RBM 嵌入视图,你只需判断信号是否值得跟。

常见问题

RBM 使用二分图,可见层与隐藏层之间全连接但层内无连接;传统玻尔兹曼机层内神经元也互相连接,计算复杂度显著更高。
80 年代受算力限制偏向能量模型优化,如今 GPU 可承担反向传播负载,对两层但极深的 RBM 逐参数调整权重更可控。
可以,小布盯盘的品种页支持自定义信号类挂载,把向导生成的 RBM 信号模块接入即可在盘面叠加观察,外汇贵金属波动高风险需自行评估。
没有固定标准,取决于品种波动结构;实操中可先试 2 到 4 维隐藏层,再对比原始价格序列的相关性衰减程度做微调。