神经网络变得轻松(第二十三部分):构建迁移学习工具·综合运用
🧠

神经网络变得轻松(第二十三部分):构建迁移学习工具·综合运用

(3/3)·从零搭一个能拆装神经层、重存模型的工具,省下重复训练的开销

含代码示例偏理论 第 3/3 篇
很多人训练神经网络时,只要改一层结构就从头跑一遍,算力白白烧掉。迁移学习能把旧模型里可用的部分直接借过来,再补一小段新训练即可。本篇就给你一套可落地的工具思路。

◍ 给 OpenCL 神经元动态扩输出维度的坑

在 MT5 用 OpenCL 加速的神经网络里,CNeuronBaseOCL::numOutputs 负责按新输出数重算权重缓冲。它先校验 outputs>0,若权重对象无效就 new 一个 CBufferFloat,失败直接返回 false;随后 BufferFree + Clear 清掉旧权重,按 (Output.Total()+1)*outputs 算出总连接数并 Reserve。 权重初始化用了 1/sqrt(Output.Total()+1) 做缩放系数 k,循环里写 (2*GenerateWeight()*k - k)*WeightsMultiplier。这意味着输出节点越多,单条连接权重初值越小,能缓解宽层梯度爆炸,但 GenerateWeight() 分布若不近似均匀,实际初始化可能偏离预期。 CNetModify::AddLayers 拼接新层时会取最后一层首个神经元,调 numOutputs 把旧层输出口接到新层 count 上。若新层类型不是 defNeuron / defNeuronBaseOCL,outputs 传 0,numOutputs 因 outputs>0 不成立直接返回 false,导致整次加层失败——这是静默断网的高频诱因。 保存模型走 CNetCreatorPanel::OnClickSaveButton,用 FileSelectDialog 限定 *.nnw 扩展名,默认 NewModel.nnw。若返回文件数 <=0 就 Print('File not selected') 并退出,实盘里点保存却没弹窗选路时,先查终端文件访问权限而非怀疑网络结构。

MQL5 / C++
class="type">bool CNeuronBaseOCL::numOutputs(class="kw">const class="type">uint outputs, ENUM_OPTIMIZATION optimization_type)
  {
   if(outputs > class="num">0)
     {
      if(CheckPointer(Weights) == POINTER_INVALID)
        {
         Weights = new CBufferFloat();
         if(CheckPointer(Weights) == POINTER_INVALID)
            class="kw">return class="kw">false;
        }
      Weights.BufferFree();
      Weights.Clear();
      class="type">int count = (class="type">int)((Output.Total() + class="num">1) * outputs);
      if(!Weights.Reserve(count))
        class="kw">return class="kw">false;
      class="type">class="kw">float k = (class="type">class="kw">float)(class="num">1 / sqrt(Output.Total() + class="num">1));
      for(class="type">int i = class="num">0; i < count; i++)
        {
         if(!Weights.Add((class="num">2 * GenerateWeight()*k - k)*WeightsMultiplier))
            class="kw">return class="kw">false;
        }
      if(!Weights.BufferCreate(OpenCL))
        class="kw">return class="kw">false;
class="type">bool CNetModify::AddLayers(CArrayObj *new_layers)
  {
   if(!new_layers || new_layers.Total() <= class="num">0)
      class="kw">return class="kw">false;
class=class="str">"cmt">//---
   if(!layers || LayersTotal() <= class="num">0)
     {
      Create(new_layers);
      class="kw">return true;
     }
   CLayerDescription *desc = NULL, *next = NULL;
   CLayer *temp;
   class="type">int outputs;
   class="type">int shift = (class="type">int)LayersTotal() - class="num">1;
   CLayer* last_layer = layers.At(shift);
   if(!last_layer)
      class="kw">return class="kw">false;
class=class="str">"cmt">//---
   CNeuronBaseOCL* neuron = last_layer.At(class="num">0);
   if(!neuron)
      class="kw">return class="kw">false;
class=class="str">"cmt">//---
   desc = neuron.GetLayerInfo();
   next = new_layers.At(class="num">0);
   outputs = (next == NULL || (next.type != defNeuron && next.type != defNeuronBaseOCL) ? class="num">0 : next.count);
   if(!neuron.numOutputs(outputs, next.optimization))
      class="kw">return class="kw">false;
   class="kw">delete desc;
class="type">bool CNetCreatorPanel::OnClickSaveButton(class="type">void)
  {
   class="type">class="kw">string filenames[];
   if(FileSelectDialog("Select files to save", NULL,
                      "Neuron Net(*.nnw)|*.nnw|All files(*.*)|*.*",
                      FSD_WRITE_FILE, filenames, "NewModel.nnw") <= class="num">0)
     {
      Print("File not selected");
      class="kw">return class="kw">false;
     }
   class="type">class="kw">string file_name = filenames[class="num">0];
   if(StringLen(file_name) - StringLen(EXTENSION) > StringFind(file_name, EXTENSION))

把训练好的网络另存为可加载模型

这段逻辑干的事很直接:把当前在内存里的神经网络复制一份,关掉训练模式后存成文件,再立刻重新加载回界面指定的路径。外汇与贵金属行情的高波动会让过拟合模型快速失效,因此这种「训练—固化—重载」的闭环适合在 MT5 实盘前做离线验证。 代码先拼好带扩展名的文件名,new 一个 CNetModify 容器;若分配失败直接返 false。随后用 m_spPTModelLayers.Value() 拿到总层数,逐层把原模型里的 CLayer 取出来,调用 neuron.TrainMode(false) 切到推理态,再 AddLayer 进新模型。任意一层取空或添加失败,result 置 false 并 break。 若基础层拷贝成功且 m_arAddLayers 里有附加层(Total()>0),就通过 GetPointer 把附加层也塞进去。最后以 Save(file_name, 1.0e37f, 100, 0, 0, false) 落盘——第二个参数 1.0e37f 是归一化上限,100 为保留精度位数,后三项为偏移与校验开关。存完 delete 掉临时对象,调 LoadModel 按输入框路径重载,返回 result。你可在 MT5 里改 100 这个精度参数看模型文件体积与加载耗时的变化。

MQL5 / C++
  file_name += EXTENSION;
  CNetModify* new_model = new CNetModify();
  if(!new_model)
    class="kw">return class="kw">false;
  class="type">int total = m_spPTModelLayers.Value();
  class="type">bool result = true;
  for(class="type">int i = class="num">0; i < total && result; i++)
    {
     CLayer* temp = m_Model.GetLayer((class="type">uint)i);
     if(!temp)
       {
        result = class="kw">false;
        class="kw">break;
       }
     CNeuronBaseOCL* neuron = temp.At(class="num">0);
     neuron.TrainMode(class="kw">false);
     if(!new_model.AddLayer(temp))
       result = class="kw">false;
    }
  new_model.SetOpenCL(m_Model.GetOpenCL());
  if(result && m_arAddLayers.Total() > class="num">0)
    if(!new_model.AddLayers(GetPointer(m_arAddLayers)))
      result = class="kw">false;
  if(result && !new_model.Save(file_name, class="num">1.0e37f, class="num">100, class="num">0, class="num">0, class="kw">false))
    result = class="kw">false;
class=class="str">"cmt">//---
  if(!!new_model)
    class="kw">delete new_model;
  LoadModel(m_edPTModel.Text());
class=class="str">"cmt">//---
  class="kw">return result;
  }

「把面板类挂进EA跑通迁移」

验证这套神经网络搭建工具,最直接的方式是写一个极简 EA 把面板类接进来。NetCreator.mq5 本身没多少逻辑,核心只是把前面写好的 CNetCreatorPanel 在三个生命周期钩子里串起来:初始化时建面板、退出时销毁、图表事件转发给面板。 实际加载到 MT5 图表后,神经层确实能从一个模型结构迁到另一个,也能继续往上叠新层;更实用的是,它能从零拉出一个全新模型,并把模型描述直接写进程序代码里,方便后续复用。外汇与贵金属行情高波动,这类实验请在策略测试器或模拟环境先跑,实盘接入倾向谨慎。 下面这段就是 EA 里全部集成点,逐行看很轻量: #include "NetCreatorPanel.mqh" 引入面板类头文件;CNetCreatorPanel Panel 声明一个面板实例。 OnInit 里 Panel.Create(0,"NetCreator",0,50,50) 在坐标(50,50)创建名为 NetCreator 的面板,失败则返回 INIT_FAILED;Panel.Run() 启动面板,同样失败即退出。 OnDeinit 只调 Panel.Destroy(reason) 做注销清理。 OnChartEvent 里先判断 CHARTEVENT_OBJECT_CLICK 点一下 Sleep(0) 让出线程,再把所有事件参数丢给 Panel.ChartEvent 处理。

MQL5 / C++
class="macro">#include "NetCreatorPanel.mqh"
CNetCreatorPanel Panel;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//---
   if(!Panel.Create(class="num">0, "NetCreator", class="num">0, class="num">50, class="num">50))
      class="kw">return INIT_FAILED;
   if(!Panel.Run())
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//---
   class="kw">return(INIT_SUCCEEDED);
  }
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
class=class="str">"cmt">//---
   Panel.Destroy(reason);
  }
class="type">void OnChartEvent(class="kw">const class="type">int id,
                  class="kw">const class="type">long &lparam,
                  class="kw">const class="type">class="kw">double &dparam,
                  class="kw">const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//---
   if(id == CHARTEVENT_OBJECT_CLICK)
      Sleep(class="num">0);
   Panel.ChartEvent(id, lparam, dparam, sparam);
  }

◍ 把新架构塞进旧模型试试

前面几篇搭好的迁移工具,核心用途是把一个模型里训练好的部分神经层直接搬进另一个模型,而且新层的数量和架构都能自定义。对做 MT5 信号模型的交易者来说,这意味着你不用每次都从零训网络。 一个值得动手的做法:保留源数据层和结果层的原有架构,只把一套全新中间层「推」进已有的智能系统,再跑训练。这样能比较纯粹架构变动带来的误差差异,而不是被数据分布变化干扰。 建议你翻出以前训过的 .net 或自有模型文件,用这套工具做三组对照——原架构、加两层全连接、换成不同激活函数,记录每组的验证误差。外汇与贵金属模型对市场状态极敏感,这类实验仅反映历史样本表现,实盘仍属高风险。

把这条线请下神坛

这套 LSTM 预测方案落到工程层,就是四个文件在 MT5 里各司其职:NetCreator.mq5 是 EA 形态的模型构建入口,NetCreatotPanel.mqh 管创建工具的界面逻辑,NeuroNet.mqh 封装神经网络结构,NeuroNet.cl 把计算丢给 OpenCL 跑。随文附的 MQL5.zip 约 71.47 KB,解压后直接拖进 MT5 的 MQL5 目录就能编译验证。 外汇与贵金属市场高杠杆、高波动,神经网络给出的只是概率倾向,不是方向保证;真要上实盘,先拿历史数据回测过拟合程度。 代码能跑通和能赚钱之间隔着的,是样本外泛化和风控,这条线从来不是圣杯,只是你工具箱里多一把尺。

把模型复用诊断交给小布
小布盯盘已内置常见的模型结构比对与权重复用提示,打开对应品种页即可看到哪些层能直接继承,你只需专注新数据的训练策略。

常见问题

预处理只借用供体模型的输出变换原始数据,不再训练供体;迁移学习则会加载部分或全部供体,在新数据上继续训练以适应新任务。
可以,小布盯盘的品种页会标注可复用层与建议重训层,减少手动比对文件结构的麻烦。
模型文件有严格记录结构,手动删改极易破坏数据完整性,正确做法是从文件加载全模型、执行拆装、再存为新文件。
有可能,逐层模块训练再拼合能降低一次训练过深带来的梯度衰落概率,但效果依数据和新层设计而定。
若供体本身是无监督压缩用途,只取编码器更高效;若任务相近,完整模型或部分堆叠层都可能适用,需实测。