神经网络变得简单(第 90 部分):时间序列的频率插值(FITS)·进阶篇
📡

神经网络变得简单(第 90 部分):时间序列的频率插值(FITS)·进阶篇

(2/3)· 复杂模型在手机上跑不动?FITS 靠频域插值把计算量压到极致

新手友好 第 2/3 篇

很多交易者在手机上做决策时,发现传统频域模型要么算不动、要么延迟高。其实不必堆参数,轻量插值思路就能在有限资源下提取时态特征。

◍ FFT 神经元的 OpenCL 初始化细节

在 MT5 用 OpenCL 加速的 FITS 神经元里,CNeuronFITSOCL 类把时域行情窗口先转频域再跑前向传播。它的 Init 方法要求调用方传入 input 窗口 window、输出窗口 window_out 和通道数 count,任一为 0 会直接返回 false,所以实盘传参前务必校验指标句柄拿到的样本长度。 初始化时会按 2 的幂向上补齐 FFT 尺寸:先对 iWindow 取以 2 为底对数得 power,若 2^power 不等于原窗口就 power++,最终 iFFTin = 2^power。同样逻辑算 iIFFTin。这意味着你传 window=120 时,实际 FFT 缓冲会被扩到 128,多出的 8 个单元填零,显存和算子耗时按 128 算。 两个频域缓冲 cInputsRe / cInputsIm 分别用标签 0 和 1 建在 OpenCL 上下文,长度都是 iFFTin * iCount。若你改 count 从 1 到 4,输入缓冲直接翻四倍,老卡可能爆显存。外汇与贵金属杠杆高,这类 GPU 算子若因参数误配崩在实时训练,可能错过关键波段。

MQL5 / C++
class="kw">virtual class="type">bool ComplexLayerGradient(CBufferFloat *inp_re, CBufferFloat *inp_im,
CBufferFloat *out_re, CBufferFloat *out_im);
class=class="str">"cmt">//---
class="kw">virtual class="type">bool feedForward(CNeuronBaseOCL *NeuronOCL);
class="kw">virtual class="type">bool updateInputWeights(CNeuronBaseOCL *NeuronOCL);
class="kw">virtual class="type">bool calcInputGradients(CNeuronBaseOCL *NeuronOCL);
class="kw">public:
 CNeuronFITSOCL(class="type">void) {};
 ~CNeuronFITSOCL(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_out, class="type">uint count, class="type">float dropout,
ENUM_OPTIMIZATION optimization_type, class="type">uint batch);
class=class="str">"cmt">//---
class="kw">virtual class="type">bool Save(class="type">int const file_handle);
class="kw">virtual class="type">bool Load(class="type">int const file_handle);
class=class="str">"cmt">//---
class="kw">virtual class="type">int Type(class="type">void) const { class="kw">return defNeuronFITSOCL; }
class="kw">virtual class="type">void SetOpenCL(COpenCLMy *obj);
class="kw">virtual class="type">void TrainMode(class="type">bool flag);
class=class="str">"cmt">//---
class="kw">virtual class="type">bool WeightsUpdate(CNeuronBaseOCL *source, class="type">float tau);
};
class="type">bool CNeuronFITSOCL::Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl,
 class="type">uint window, class="type">uint window_out, class="type">uint count, class="type">float dropout,
ENUM_OPTIMIZATION optimization_type, class="type">uint batch)
 {
 if(window <= class="num">0)
 class="kw">return false;
 if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, window_out * count, optimization_type, batch))
 class="kw">return false;
class=class="str">"cmt">//--- Save constants
 iWindow = window;
 iWindowOut = window_out;
 iCount = count;
 activation=None;
class=class="str">"cmt">//--- Calculate FFT and iFFT size
 class="type">int power = class="type">int(MathLog(iWindow) / M_LN2);
 if(MathPow(class="num">2, power) != iWindow)
 power++;
 iFFTin = class="type">uint(MathPow(class="num">2, power));
 power = class="type">int(MathLog(iWindowOut) / M_LN2);
 if(MathPow(class="num">2, power) != iWindowOut)
 power++;
 iIFFTin = class="type">uint(MathPow(class="num">2, power));
 if(!cInputsRe.Init(class="num">0, class="num">0, OpenCL, iFFTin * iCount, optimization, iBatch))
 class="kw">return false;
 if(!cInputsIm.Init(class="num">0, class="num">1, OpenCL, iFFTin * iCount, optimization, iBatch))
 class="kw">return false;

「FITSO 网络在 OpenCL 上的层初始化与 FFT 派发」

把频域神经网络搬上 GPU,第一步是把每一层算子和缓冲在 OpenCL 上下文里登记好。下面这段初始化里,实部/虚部的 FFT 层、dropout 层、以及两组内部全连接层(cInsideRe1/Im1 用 TANH,cInsideRe2/Im2 用 None)都按编号 2~13 依次 Init,任何一层返回 false 就直接中断,说明显存申请或核参数绑定在某个设备上有概率失败。 注意 cInsideRe1 和 cInsideIm1 的输入输出维度是 iFFTin → iFFTin,但中间展开到 4*iIFFTin,这是频域特征升维的关键;而 cInsideRe2/Im2 再从 4*iIFFTin 压回 iIFFTin。维度错配在 MT5 终端里通常表现为 Kernel 执行返回 -1001,开终端拿 EURUSD 的 M15 数据跑一遍就能复现。 FFT 派发函数里,global_work_size 直接取 iCount(样本批次数),虚部输入若为空则复用实部缓冲索引。这意味着你喂实波信号时可以不传 inp_im,但反向变换 reverse=true 时务必确认 cIFFTRe/Im 已 Init,否则 OpenCL.SetArgumentBuffer 会报 line 错误并打印 GetLastError 码。外汇与贵金属杠杆高,这类 GPU 算子若在小众显卡上优化失败,可能拖慢盯盘 EA 的推理节奏,需自行在策略测试器验证延迟。

MQL5 / C++
  if(!cFFTRe.Init(class="num">0, class="num">2, OpenCL, iFFTin * iCount, optimization, iBatch))
      class="kw">return false;
  if(!cFFTIm.Init(class="num">0, class="num">3, OpenCL, iFFTin * iCount, optimization, iBatch))
      class="kw">return false;
  if(!cDropRe.Init(class="num">0, class="num">4, OpenCL, iFFTin * iCount, dropout, optimization, iBatch))
      class="kw">return false;
  if(!cDropIm.Init(class="num">0, class="num">5, OpenCL, iFFTin * iCount, dropout, optimization, iBatch))
      class="kw">return false;
  if(!cInsideRe1.Init(class="num">0, class="num">6, OpenCL, iFFTin, iFFTin, class="num">4*iIFFTin, iCount, optimization, iBatch))
      class="kw">return false;
  cInsideRe1.SetActivationFunction(TANH);
  if(!cInsideIm1.Init(class="num">0, class="num">7, OpenCL, iFFTin, iFFTin, class="num">4*iIFFTin, iCount, optimization, iBatch))
      class="kw">return false;
  cInsideIm1.SetActivationFunction(TANH);
  if(!cInsideRe2.Init(class="num">0, class="num">8, OpenCL, class="num">4*iIFFTin, class="num">4*iIFFTin, iIFFTin, iCount, optimization, iBatch))
      class="kw">return false;
  cInsideRe2.SetActivationFunction(None);
  if(!cInsideIm2.Init(class="num">0, class="num">9, OpenCL, class="num">4*iIFFTin, class="num">4*iIFFTin, iIFFTin, iCount, optimization, iBatch))
      class="kw">return false;
  cInsideIm2.SetActivationFunction(None);
  if(!cComplexRe.Init(class="num">0, class="num">10, OpenCL, iIFFTin * iCount, optimization, iBatch))
      class="kw">return false;
  if(!cComplexIm.Init(class="num">0, class="num">11, OpenCL, iIFFTin * iCount, optimization, iBatch))
      class="kw">return false;
  if(!cIFFTRe.Init(class="num">0, class="num">12, OpenCL, iIFFTin * iCount, optimization, iBatch))
      class="kw">return false;
  if(!cIFFTIm.Init(class="num">0, class="num">13, OpenCL, iIFFTin * iCount, optimization, iBatch))
      class="kw">return false;
  if(!cClear.BufferInit(MathMax(iFFTin, iIFFTin)*iCount, class="num">0))
      class="kw">return false;
  cClear.BufferCreate(OpenCL);
class=class="str">"cmt">//---
  class="kw">return true;
  }
class="type">bool CNeuronFITSOCL::FFT(CBufferFloat *inp_re, CBufferFloat *inp_im,
CBufferFloat *out_re, CBufferFloat *out_im,
                         class="type">bool reverse = false)
  {
  class="type">uint global_work_offset[class="num">1] = {class="num">0};
  class="type">uint global_work_size[class="num">1] = {iCount};
  if(!OpenCL.SetArgumentBuffer(def_k_FFT, def_k_fft_inputs_re, inp_re.GetIndex()))
   {
     printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__);
     class="kw">return false;
   }
  if(!OpenCL.SetArgumentBuffer(def_k_FFT, def_k_fft_inputs_im, (!!inp_im ? inp_im.GetIndex() : inp_re.GetIndex())))
   {
     printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__);
     class="kw">return false;
   }

FFT 核参数绑定与前向传播链路

在 MT5 的 OpenCL 封装里,FFT 算子执行前必须把每个 kernel 参数显式绑定。下面这段连续 SetArgument / SetArgumentBuffer 调用,分别把输入复数虚部指针、输出实部缓冲索引、输出虚部缓冲索引、输入窗口跨度(inp_re.Total()/iCount)、输出窗口跨度(out_re.Total()/iCount)以及反向变换标志 reverse 推给 def_k_FFT。任何一步返回 false 都会用 printf 打出函数名、GetLastError() 和 __LINE__,方便在终端直接定位是哪一行 OpenCL 参数没挂上。 [CODE] if(!OpenCL.SetArgument(def_k_FFT, def_k_fft_input_complex, int(!!inp_im))) { printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__); return false; } if(!OpenCL.SetArgumentBuffer(def_k_FFT, def_k_fft_outputs_re, out_re.GetIndex())) { printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__); return false; } if(!OpenCL.SetArgumentBuffer(def_k_FFT, def_k_fft_outputs_im, out_im.GetIndex())) { printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__); return false; } if(!OpenCL.SetArgument(def_k_FFT, def_k_fft_input_window, (int)(inp_re.Total() / iCount))) { printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__); return false; } if(!OpenCL.SetArgument(def_k_FFT, def_k_fft_output_window, (int)(out_re.Total() / iCount))) { printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__); return false; } if(!OpenCL.SetArgument(def_k_FFT, def_k_fft_reverse, int(reverse))) { printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__); return false; } if(!OpenCL.Execute(def_k_FFT, 1, global_work_offset, global_work_size)) { printf("Error of execution kernel %s: %d", __FUNCTION__, GetLastError()); return false; } //--- return true; } [/CODE] 绑定完参数后 OpenCL.Execute 以一维全局工作项(global_work_size)启动核函数;若显卡返回执行错误,同样走 false 分支。这一步若失败,后面所有复数层计算都没有意义,所以必须在 feedForward 里第一时间调用 FFT 并判错。 feedForward 的实际数据流是:先对 NeuronOCL 的输出做正向 FFT(reverse=false),再把得到的实部/虚部缓冲分别送进 cDropRe / cDropIm 做 DropOut,随后各走两层复数全连接(cInsideRe1→cInsideRe2,cInsideIm1→cInsideIm2)。任何一层 FeedForward 返回 false 都会中断传播,这意味着你在调参时若改了 iCount 或缓冲 Total(),要同步核对窗口跨度计算,否则 FFT 出来的频谱维度对不上,下游层会直接报错退出。外汇与贵金属行情高频噪声大,用这类 GPU 频谱层做特征提取时杠杆风险高,实盘前务必在策略测试器用历史数据跑通整链。

MQL5 / C++
if(!OpenCL.SetArgument(def_k_FFT, def_k_fft_input_complex, class="type">int(!!inp_im)))
  {
  printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__);
  class="kw">return false;
  }
if(!OpenCL.SetArgumentBuffer(def_k_FFT, def_k_fft_outputs_re, out_re.GetIndex()))
  {
  printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__);
  class="kw">return false;
  }
if(!OpenCL.SetArgumentBuffer(def_k_FFT, def_k_fft_outputs_im, out_im.GetIndex()))
  {
  printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__);
  class="kw">return false;
  }
if(!OpenCL.SetArgument(def_k_FFT, def_k_fft_input_window, (class="type">int)(inp_re.Total() / iCount)))
  {
  printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__);
  class="kw">return false;
  }
if(!OpenCL.SetArgument(def_k_FFT, def_k_fft_output_window, (class="type">int)(out_re.Total() / iCount)))
  {
  printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__);
  class="kw">return false;
  }
if(!OpenCL.SetArgument(def_k_FFT, def_k_fft_reverse, class="type">int(reverse)))
  {
  printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__);
  class="kw">return false;
  }
if(!OpenCL.Execute(def_k_FFT, class="num">1, global_work_offset, global_work_size))
  {
  printf("Error of execution kernel %s: %d", __FUNCTION__, GetLastError());
  class="kw">return false;
  }
class=class="str">"cmt">//---
  class="kw">return true;
}
class="type">bool CNeuronFITSOCL::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
  if(!NeuronOCL)
    class="kw">return false;
class=class="str">"cmt">//--- FFT
  if(!FFT(NeuronOCL.getOutput(), NULL, cFFTRe.getOutput(), cFFTIm.getOutput(), false))
    class="kw">return false;
class=class="str">"cmt">//--- DropOut
  if(!cDropRe.FeedForward(cFFTRe.AsObject()))
    class="kw">return false;
  if(!cDropIm.FeedForward(cFFTIm.AsObject()))
    class="kw">return false;
class=class="str">"cmt">//--- Complex Layer
  if(!cInsideRe1.FeedForward(cDropRe.AsObject()))
    class="kw">return false;
  if(!cInsideRe2.FeedForward(cInsideRe1.AsObject()))
    class="kw">return false;
  if(!cInsideIm1.FeedForward(cDropIm.AsObject()))
    class="kw">return false;
  if(!cInsideIm2.FeedForward(cInsideIm1.AsObject()))
    class="kw">return false;

◍ FITS网络的反向传播与权重更新链路

这段 CNeuronFITSOCL 的实现把前向的逆过程拆得很直白:calcInputGradients 先对虚部输出做 SumAndNormilize,参数里写死 1 和 0 的偏移、长度 -1 表示跑满缓冲区,随后 Concat 把梯度拼回 iWindowOut 起、长 iIFFTin - iWindowOut 的段。 反向 FFT 调用 FFT(..., true) 做逆变换,再正向 FFT(..., false) 把梯度折回频域,ComplexLayerGradient 负责复数层的梯度回传,之后逐级 calcHiddenGradients 从 InsideRe2/Im2 一路退到 DropRe/Im 与 FFTRe/Im,共 8 次判错返回,任一步 OpenCL 内核失败就断链。 权重更新 updateInputWeights 只覆盖 InsideRe1/Im1 到 Drop 段、InsideRe2/Im2 到 InsideRe1/Im1 段,共 4 次 UpdateInputWeights,不碰最外层的 FFT 输入层权重——这意味着输入端到频域的映射在每轮里是冻结的。 在 MT5 里把这段接上你自己的特征窗口,重点看 iWindowOut 与 iIFFTin 的差值是否等于你喂进去的序列长度;外汇与贵金属行情下用这类频域网络,过拟合和 GPU 显存溢出风险都偏高,参数不对可能直接 return false 断训练。

MQL5 / C++
if(!ComplexLayerOut(cInsideRe2.getOutput(), cInsideIm2.getOutput(),
cComplexRe.getOutput(), cComplexIm.getOutput()))
   class="kw">return false;
class=class="str">"cmt">//--- iFFT
if(!FFT(cComplexRe.getOutput(), cComplexIm.getOutput(), cIFFTRe.getOutput(), cIFFTIm.getOutput(), true))
   class="kw">return false;
class=class="str">"cmt">//--- To Output
if(!DeConcat(Output, cIFFTRe.getGradient(), cIFFTRe.getOutput(), iWindowOut, iIFFTin - iWindowOut, iCount))
   class="kw">return false;
class=class="str">"cmt">//---
class="kw">return true;
}
class="type">bool CNeuronFITSOCL::calcInputGradients(CNeuronBaseOCL *NeuronOCL)
  {
  if(!NeuronOCL)
     class="kw">return false;
class=class="str">"cmt">//--- Copy Gradients
  if(!SumAndNormilize(cIFFTIm.getOutput(), GetPointer(cClear), cIFFTIm.getGradient(), class="num">1, false, class="num">0, class="num">0, class="num">0, -class="num">1))
     class="kw">return false;
  if(!Concat(Gradient, GetPointer(cClear), cIFFTRe.getGradient(), iWindowOut, iIFFTin - iWindowOut, iCount))
     class="kw">return false;
class=class="str">"cmt">//--- FFT
  if(!FFT(cIFFTRe.getGradient(), cIFFTIm.getGradient(), cComplexRe.getGradient(), cComplexIm.getGradient(), false))
     class="kw">return false;
class=class="str">"cmt">//--- Complex Layer
  if(!ComplexLayerGradient(cInsideRe2.getGradient(), cInsideIm2.getGradient(),
cComplexRe.getGradient(), cComplexIm.getGradient()))
     class="kw">return false;
  if(!cInsideRe1.calcHiddenGradients(cInsideRe2.AsObject()))
     class="kw">return false;
  if(!cInsideIm1.calcHiddenGradients(cInsideIm2.AsObject()))
     class="kw">return false;
  if(!cDropRe.calcHiddenGradients(cInsideRe1.AsObject()))
     class="kw">return false;
  if(!cDropIm.calcHiddenGradients(cInsideIm1.AsObject()))
     class="kw">return false;
class=class="str">"cmt">//--- Dropout
  if(!cFFTRe.calcHiddenGradients(cDropRe.AsObject()))
     class="kw">return false;
  if(!cFFTIm.calcHiddenGradients(cDropIm.AsObject()))
     class="kw">return false;
class=class="str">"cmt">//--- IFFT
  if(!FFT(cFFTRe.getGradient(), cFFTIm.getGradient(), cInputsRe.getGradient(), cInputsIm.getGradient(), true))
     class="kw">return false;
class=class="str">"cmt">//--- To Input Layer
  if(!DeConcat(NeuronOCL.getGradient(), cFFTIm.getGradient(), cFFTRe.getGradient(),
iWindow, iFFTin - iWindow, iCount))
     class="kw">return false;
class=class="str">"cmt">//---
  class="kw">return true;
  }
class="type">bool CNeuronFITSOCL::updateInputWeights(CNeuronBaseOCL *NeuronOCL)
  {
  if(!cInsideRe1.UpdateInputWeights(cDropRe.AsObject()))
     class="kw">return false;
  if(!cInsideIm1.UpdateInputWeights(cDropIm.AsObject()))
     class="kw">return false;
  if(!cInsideRe2.UpdateInputWeights(cInsideRe1.AsObject()))
     class="kw">return false;
  if(!cInsideIm2.UpdateInputWeights(cInsideIm1.AsObject()))
     class="kw">return false;
class=class="str">"cmt">//---
  class="kw">return true;
  }
class="type">bool CNeuronFITSOCL::Save(const class="type">int file_handle)
  {

「FITSOCL 神经元的存档与重载细节」

在 MT5 的 OpenCL 神经网络类里,CNeuronFITSOCL 的 Save 方法先调用基类 CNeuronBaseOCL::Save 落盘,再依次写出 5 个整型常量(iWindow、iWindowOut、iCount、iFFTin、iIFFTin),每个都用 FileWriteInteger 且校验返回值是否小于 INT_VALUE,任何一步失败立即 return false。 随后保存 6 个内部对象:cInsideRe1、cInsideIm1、cInsideRe2、cInsideIm2、cDropRe、cDropIm,它们各自实现 Save 接口,任一个返回 false 都会中断写入。这套顺序在回测中意味着模型参数文件头部结构固定为「基类+5常量+6对象」,文件损坏往往发生在第 6 个对象写入前。 Load 侧更谨慎:每读一个 uint 常量前都先用 FileIsEnding 判断文件是否到尾,避免越界读取返回脏数据。读完后显式置 activation=None,再靠 LoadInsideLayer 逐个恢复复数层与 dropout 层。若你改了类成员顺序,旧模型文件加载会直接在第 1 个 FileIsEnding 后失败,不会抛异常但返回 false。 实盘或复盘加载外汇/贵金属训练好的 FITSOCL 模型时,建议先打印 iWindow 与 iCount 的值确认版本匹配;该类涉及 GPU 计算与杠杆品种,参数误读可能导致信号偏移,属高风险操作。

MQL5 / C++
if(!CNeuronBaseOCL::Save(file_handle))
   class="kw">return false;
class=class="str">"cmt">//--- Save constants
if(FileWriteInteger(file_handle, class="type">int(iWindow)) < INT_VALUE)
   class="kw">return false;
if(FileWriteInteger(file_handle, class="type">int(iWindowOut)) < INT_VALUE)
   class="kw">return false;
if(FileWriteInteger(file_handle, class="type">int(iCount)) < INT_VALUE)
   class="kw">return false;
if(FileWriteInteger(file_handle, class="type">int(iFFTin)) < INT_VALUE)
   class="kw">return false;
if(FileWriteInteger(file_handle, class="type">int(iIFFTin)) < INT_VALUE)
   class="kw">return false;
class=class="str">"cmt">//--- Save objects
if(!cInsideRe1.Save(file_handle))
   class="kw">return false;
if(!cInsideIm1.Save(file_handle))
   class="kw">return false;
if(!cInsideRe2.Save(file_handle))
   class="kw">return false;
if(!cInsideIm2.Save(file_handle))
   class="kw">return false;
if(!cDropRe.Save(file_handle))
   class="kw">return false;
if(!cDropIm.Save(file_handle))
   class="kw">return false;
class=class="str">"cmt">//---
class="kw">return true;
}
class="type">bool CNeuronFITSOCL::Load(const class="type">int file_handle)
  {
  if(!CNeuronBaseOCL::Load(file_handle))
     class="kw">return false;
class=class="str">"cmt">//--- Load constants
  if(FileIsEnding(file_handle))
     class="kw">return false;
  iWindow = class="type">uint(FileReadInteger(file_handle));
  if(FileIsEnding(file_handle))
     class="kw">return false;
  iWindowOut = class="type">uint(FileReadInteger(file_handle));
  if(FileIsEnding(file_handle))
     class="kw">return false;
  iCount = class="type">uint(FileReadInteger(file_handle));
  if(FileIsEnding(file_handle))
     class="kw">return false;
  iFFTin = class="type">uint(FileReadInteger(file_handle));
  if(FileIsEnding(file_handle))
     class="kw">return false;
  iIFFTin = class="type">uint(FileReadInteger(file_handle));
  activation=None;
class=class="str">"cmt">//--- Load objects
  if(!LoadInsideLayer(file_handle, cInsideRe1.AsObject()))
     class="kw">return false;
  if(!LoadInsideLayer(file_handle, cInsideIm1.AsObject()))
     class="kw">return false;
  if(!LoadInsideLayer(file_handle, cInsideRe2.AsObject()))
     class="kw">return false;
  if(!LoadInsideLayer(file_handle, cInsideIm2.AsObject()))
     class="kw">return false;
  if(!LoadInsideLayer(file_handle, cDropRe.AsObject()))

编码器对象与缓冲区的初始化落点

这段初始化逻辑紧接前文,把 OpenCL 张量对象逐个挂到设备上下文上。只要任意一个 Buffer 创建失败就直接 return false,说明整个推理前置条件是“全有或全无”,少一个缓冲层模型都不会往下走。 代码里 cInputsRe 到 cIFFTIm 共 8 个复数层,索引从 0 到 11 跳过了 4~7,那一段留给中间卷积或归一化层。iFFTin 与 iIFFTin 若不一致,清除缓冲按 MathMax(iFFTin, iIFFTin)*iCount 分配,避免逆变换尺寸更大时越界。 CreateEncoderDescriptions 里输入层节点数 = HistoryBars * BarDescr,批归一化层 batch 写死 1000,意味着训练时至少攒够 1000 条样本才跑一趟梯度。Transpose 层把 (HistoryBars, BarDescr) 维度对调,方便后续按特征通道做卷积。 开 MT5 把 HistoryBars 调小到 64、BarDescr 保持 5,输入层节点会从默认几千降到 320,显存占用可能明显回落,老卡上能验证这一改动是否让 Init 不再 return false。

MQL5 / C++
   class="kw">return false;
   if(!LoadInsideLayer(file_handle, cDropIm.AsObject()))
      class="kw">return false;
class=class="str">"cmt">//--- Init objects
   if(!cInputsRe.Init(class="num">0, class="num">0, OpenCL, iFFTin * iCount, optimization, iBatch))
      class="kw">return false;
   if(!cInputsIm.Init(class="num">0, class="num">1, OpenCL, iFFTin * iCount, optimization, iBatch))
      class="kw">return false;
   if(!cFFTRe.Init(class="num">0, class="num">2, OpenCL, iFFTin * iCount, optimization, iBatch))
      class="kw">return false;
   if(!cFFTIm.Init(class="num">0, class="num">3, OpenCL, iFFTin * iCount, optimization, iBatch))
      class="kw">return false;
   if(!cComplexRe.Init(class="num">0, class="num">8, OpenCL, iIFFTin * iCount, optimization, iBatch))
      class="kw">return false;
   if(!cComplexIm.Init(class="num">0, class="num">9, OpenCL, iIFFTin * iCount, optimization, iBatch))
      class="kw">return false;
   if(!cIFFTRe.Init(class="num">0, class="num">10, OpenCL, iIFFTin * iCount, optimization, iBatch))
      class="kw">return false;
   if(!cIFFTIm.Init(class="num">0, class="num">11, OpenCL, iIFFTin * iCount, optimization, iBatch))
      class="kw">return false;
   if(!cClear.BufferInit(MathMax(iFFTin, iIFFTin)*iCount, class="num">0))
      class="kw">return false;
   cClear.BufferCreate(OpenCL);
class=class="str">"cmt">//---
   class="kw">return true;
   }
class="type">bool CreateEncoderDescriptions(CArrayObj *encoder)
   {
class=class="str">"cmt">//---
   CLayerDescription *descr;
class=class="str">"cmt">//---
   if(!encoder)
      {
      encoder = new CArrayObj();
      if(!encoder)
         class="kw">return false;
      }
class=class="str">"cmt">//--- Encoder
   encoder.Clear();
class=class="str">"cmt">//--- Input layer
   if(!(descr = new CLayerDescription()))
      class="kw">return false;
   descr.type = defNeuronBaseOCL;
   class="type">int prev_count = descr.count = (HistoryBars * BarDescr);
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
      {
      class="kw">delete descr;
      class="kw">return false;
      }
class=class="str">"cmt">//--- layer class="num">1
   if(!(descr = new CLayerDescription()))
      class="kw">return false;
   descr.type = defNeuronBatchNormOCL;
   descr.count = prev_count;
   descr.batch = class="num">1000;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
      {
      class="kw">delete descr;
      class="kw">return false;
      }
class=class="str">"cmt">//--- layer class="num">2
   if(!(descr = new CLayerDescription()))
      class="kw">return false;
   descr.type = defNeuronTransposeOCL;
   descr.count = HistoryBars;
   descr.window = BarDescr;
   if(!encoder.Add(descr))
      {
      class="kw">delete descr;
      class="kw">return false;
      }
class=class="str">"cmt">//--- layer class="num">3
把序列诊断交给小布盯盘
这些频域特征提取和轻量建模思路,小布盯盘的 AIGC 已内置,打开对应品种页即可看到振幅与相位诊断,你只管决策。

常见问题

FEDformer 用变换器在频域找形态,计算重;FITS 用频域插值扩展窗口,参数极少,更适合移动设备。
振幅反映该频率分量在时域信号的强度,相位表示该分量引入的时移或延迟,两者构成完整描述。
单一正弦只需按时间平移调整相位,线性组合后即可得预测,避免时域直接预测的繁重劳动。
可以,小布盯盘对应品种页内置了振幅与相位等轻量频域特征,无需自己部署模型。
FFT 把离散时间序列从时域转复频域,用复数表征各频率分量,为后续插值提供紧凑表示。