神经网络变得轻松(第十一部分):自 GPT 获取·进阶篇
🧠

神经网络变得轻松(第十一部分):自 GPT 获取·进阶篇

(2/3)·解码器-only的GPT和前篇变换器差在哪,又该怎么写进EA的神经类里

案例拆解新手友好 第 2/3 篇
很多交易者把GPT和通用变换器混为一谈,直接套用带编码器的结构,结果前向维度对不上还查不出原因。预训练模型吃资源但不是黑盒,搞清楚解码器差异才能少走弯路。

多头注意力里的梯度回流路径

在反向传播循环的开头,先由 ConvolutionInputGradients 方法计算前馈模块全连接层传来的误差梯度,方法跑完立即释放本地缓冲区。因为整个算法走的是数据流贯穿,误差梯度也要走同一套路:把前馈模块拿到的梯度和上一层传来的梯度相加,再做一次常规化来压住崩溃梯度(vanishing/exploding),这部分封装在 SumAndNormilize 里。 随后把误差梯度按关注目击者(attention heads)拆分,通过 ConvolutionInputGradients 作用于 W0 矩阵实现;AttentionInsideGradients 方法则规划沿 head 维度的梯度传播。循环末尾,把上一次迭代收到的梯度经级联张量 QKV_Weights 送回上一层,并和自注意力前馈模块的梯度求和、常规化。注意前一层的数据缓冲区要留在 GPU 内存,别误删。 调用频次最高的是 ConvolutionInputGradients,它借用了卷积层内核并针对当前任务裁剪过。入参包含权重张量、下一层梯度、上一层输出、结果存放张量,以及输入/输出窗口尺寸和激活函数标识;方法先校验指针有效性,在 GPU 开缓冲区,再发起 OpenCL 内核调用。 MHAttentionInsideGradients 内核以二维线程启动(序列元素 × 关注目击者),先取序列与 head 序号和尺寸、重算 Scores 系数,再循环算 Scores 梯度,用屏障同步确保所有线程完成后再切到 QKV 梯度循环。权重更新走 updateInputWeights,顺序调 ConvolutuionUpdateWeights 即可,思路和前几篇的 feedForward 一致。 基类改动很机械:加新类标识符、在定义模块补 OpenCL 内核常量、构造函数里声明并实例化新神经元,再把新类接进 CNeuronBaseOCL 的调度方法。附件里有完整类和方法代码,开 MT5 把头文件挂上就能编译验证。

MQL5 / C++
class CNeuronMLMHAttentionOCL : class="kw">public CNeuronBaseOCL
  {
class="kw">protected:
   class="type">uint iLayers; class=class="str">"cmt">/// Number of inner layers
   class="type">uint iHeads; class=class="str">"cmt">/// Number of heads
   class="type">uint iWindow; class=class="str">"cmt">/// Input window size
   class="type">uint iUnits; class=class="str">"cmt">/// Number of units
   class="type">uint iWindowKey; class=class="str">"cmt">/// Size of Key/Query window
class=class="str">"cmt">//---
   CCollection *QKV_Tensors; class=class="str">"cmt">/// The collection of tensors of Queries, Keys and Values
   CCollection *QKV_Weights; class=class="str">"cmt">/// The collection of Matrix of weights to previous layer
   CCollection *S_Tensors; class=class="str">"cmt">/// The collection of Scores tensors
   CCollection *AO_Tensors; class=class="str">"cmt">/// The collection of Attention Out tensors
   CCollection *FF_Tensors; class=class="str">"cmt">/// The collection of tensors of Feed Forward output

◍ 多头部注意力层的 OpenCL 接口拆解

在 MT5 的神经网络扩展里,CNeuronMLMHAttentionOCL 把多头注意力机制的计算全部下沉到 OpenCL 内核,类内只保留调度入口。理解这些虚函数,等于看清了 GPU 上一次前向和反向传播究竟调了哪些 kernel。 前向侧由 feedForward 接上层指针拉起基础全连接,ConvolutionForward 单独处理带 window 与 window_out 参数的卷积前向,AttentionScore 与 AttentionOut 则分别算注意力分数与输出,mask 默认开启以避免未来信息泄漏。SumAndNormilize 内部连发 SumMatrix 与 Normalize 两个内核,完成残差相加后的层归一化。 反向侧按优化器分支:updateInputWeights 依据 ENUM_OPTIMIZATION 在 Momentum 与 Adam 内核间二选一;卷积梯度由 ConvolutionInputGradients 与 ConvolutuionUpdateWeights 配合传递并更新,后者同样按优化类型切内核。AttentionInsideGradients 负责把梯度穿过注意力层回传。 Init 的形参直接决定拓扑:window 控制输入输出窗步长,heads 设头数,units_count 与 layers 定神经元和层数,optimization_type 在初始化时就锁死了反向更新路径。开 MT5 把这类头文件丢进 MetaEditor 搜 CNeuronMLMHAttentionOCL,对照调用栈能验证上述内核名是否真实存在。

MQL5 / C++
  CCollection             *FF_Weights;                                        class=class="str">"cmt">///< The collection of Matrix of Feed Forward weights
class=class="str">"cmt">///\ingroup neuron_base_ff
  class="kw">virtual class="type">bool            feedForward(CNeuronBaseOCL *NeuronOCL);                class=class="str">"cmt">///< \brief Feed Forward method of calling kernel ::FeedForward().@param NeuronOCL Pointer to previos layer.
  class="kw">virtual class="type">bool            ConvolutionForward(CBufferDouble *weights, CBufferDouble *inputs,CBufferDouble *outputs, class="type">uint window, class="type">uint window_out, ENUM_ACTIVATION activ);
  class=class="str">"cmt">///< \brief Convolution Feed Forward method of calling kernel ::FeedForwardConv().
  class="kw">virtual class="type">bool            AttentionScore(CBufferDouble *qkv, CBufferDouble *scores, class="type">bool mask=true);
  class=class="str">"cmt">///< \brief Multi-heads attention scores method of calling kernel ::MHAttentionScore().
  class="kw">virtual class="type">bool            AttentionOut(CBufferDouble *qkv, CBufferDouble *scores, CBufferDouble *out);
  class=class="str">"cmt">///< \brief Multi-heads attention out method of calling kernel ::MHAttentionOut().
  class="kw">virtual class="type">bool            SumAndNormilize(CBufferDouble *tensor1, CBufferDouble *tensor2, CBufferDouble *out);
  class=class="str">"cmt">///< \brief Method sum and normalize class="num">2 tensors by calling class="num">2 kernels ::SumMatrix() and ::Normalize().
class=class="str">"cmt">///\ingroup neuron_base_opt
  class="kw">virtual class="type">bool            updateInputWeights(CNeuronBaseOCL *NeuronOCL);         class=class="str">"cmt">///< Method for updating weights.\details Calling one of kernels ::UpdateWeightsMomentum() or ::UpdateWeightsAdam() in depends on optimization type(class="macro">#ENUM_OPTIMIZATION).@param NeuronOCL Pointer to previos layer.
  class="kw">virtual class="type">bool            ConvolutuionUpdateWeights(CBufferDouble *weights, CBufferDouble *gradient, CBufferDouble *inputs, CBufferDouble *momentum1, CBufferDouble *momentum2, class="type">uint window, class="type">uint window_out);
  class=class="str">"cmt">///< Method for updating weights in convolution layer.\details Calling one of kernels ::UpdateWeightsConvMomentum() or ::UpdateWeightsConvAdam() in depends on optimization type(class="macro">#ENUM_OPTIMIZATION).
  class="kw">virtual class="type">bool            ConvolutionInputGradients(CBufferDouble *weights, CBufferDouble *gradient, CBufferDouble *inputs, CBufferDouble *inp_gradient, class="type">uint window, class="type">uint window_out, class="type">uint activ);
  class=class="str">"cmt">///< Method of passing gradients through a convolutional layer.
  class="kw">virtual class="type">bool            AttentionInsideGradients(CBufferDouble *qkv,CBufferDouble *qkv_g,CBufferDouble *scores,CBufferDouble *scores_g,CBufferDouble *gradient);
  class=class="str">"cmt">///< Method of passing gradients through attention layer.
class="kw">public:
  class=class="str">"cmt">/** Constructor */CNeuronMLMHAttentionOCL(class="type">void);
  class=class="str">"cmt">/** Destructor */~CNeuronMLMHAttentionOCL(class="type">void);
  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_count, class="type">uint layers, ENUM_OPTIMIZATION optimization_type);
  class=class="str">"cmt">///< Method of initialization class.@param[in] numOutputs Number of connections to next layer.@param[in] myIndex Index of neuron in layer.@param[in] open_cl Pointer to class="macro">#COpenCLMy object.@param[in] window Size of in/out window and step.@param[in] units_countNumber of neurons.@param[in] optimization_type Optimization type(class="macro">#ENUM_OPTIMIZATION)@class="kw">return Boolen result of operations.
  class="kw">virtual class="type">bool            calcInputGradients(CNeuronBaseOCL *prevLayer);   class=class="str">"cmt">///< Method to transfer gradients to previous layer @param[in] prevLayer Pointer to previous layer.
  class=class="str">"cmt">//---

「多头注意力神经元的构造与张量尺寸」

CNeuronMLMHAttentionOCL 这个类封装了基于 OpenCL 的多头自注意力机制,用在 MT5 的行情序列建模上。构造函数里把 iLayers、iHeads、iWindow、iWindowKey、iUnits 全部初始化为 0,随后新建 6 个 CCollection 对象分别管 QKV 张量、QKV 权重、Score 张量、Attention 输出、前馈张量和前馈权重。 析构时逐个用 CheckPointer 判断有效性再 delete,避免悬空指针。这一套在 EA 退出或重新训练时很关键,否则 MT5 终端可能悄悄漏内存。 Init 方法里先用 fmax 把 window、window_key、units_count、heads、layers 都兜底成至少 1,再算各张量尺寸:QKV 张量 = 3*iWindowKey*iHeads*iUnits,QKV 权重矩阵 = 3*(iWindow+1)*iWindowKey*iHeads,Score 张量 = iUnits*iUnits*iHeads,多头输出 = iWindowKey*iHeads*iUnits,本层输出 = iWindow*iUnits。 以外汇或贵金属品种跑这套网络属于高风险实验,过拟合和历史漂移都可能让样本外表现明显走弱。改 iHeads 或 iUnits 会直接撬动显存占用,建议先在策略测试器里用小窗口验证再放大。

MQL5 / C++
class="kw">virtual class="type">int      Type(class="type">void) class="kw">const   { class="kw">return defNeuronMLMHAttentionOCL;  }class=class="str">"cmt">///< Identificator of class.@class="kw">return Type of class
class=class="str">"cmt">//--- methods for working with files
class="kw">virtual class="type">bool      Save(class="type">int class="kw">const file_handle);  class=class="str">"cmt">///< Save method @param[in] file_handle handle of file @class="kw">return logical result of operation
class="kw">virtual class="type">bool      Load(class="type">int class="kw">const file_handle);  class=class="str">"cmt">///< Load method @param[in] file_handle handle of file @class="kw">return logical result of operation
};
CNeuronMLMHAttentionOCL::CNeuronMLMHAttentionOCL(class="type">void)  :  iLayers(class="num">0),
  iHeads(class="num">0),
  iWindow(class="num">0),
  iWindowKey(class="num">0),
  iUnits(class="num">0)
  {
  QKV_Tensors=new CCollection();
  QKV_Weights=new CCollection();
  S_Tensors=new CCollection();
  AO_Tensors=new CCollection();
  FF_Tensors=new CCollection();
  FF_Weights=new CCollection();
  }
CNeuronMLMHAttentionOCL::~CNeuronMLMHAttentionOCL(class="type">void)
  {
  if(CheckPointer(QKV_Tensors)!=POINTER_INVALID)
      class="kw">delete QKV_Tensors;
  if(CheckPointer(QKV_Weights)!=POINTER_INVALID)
      class="kw">delete QKV_Weights;
  if(CheckPointer(S_Tensors)!=POINTER_INVALID)
      class="kw">delete S_Tensors;
  if(CheckPointer(AO_Tensors)!=POINTER_INVALID)
      class="kw">delete AO_Tensors;
  if(CheckPointer(FF_Tensors)!=POINTER_INVALID)
      class="kw">delete FF_Tensors;
  if(CheckPointer(FF_Weights)!=POINTER_INVALID)
      class="kw">delete FF_Weights;
  }
class="type">bool CNeuronMLMHAttentionOCL::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_count,class="type">uint layers,ENUM_OPTIMIZATION optimization_type)
  {
  if(!CNeuronBaseOCL::Init(numOutputs,myIndex,open_cl,window*units_count,optimization_type))
      class="kw">return class="kw">false;
class=class="str">"cmt">//---
  iWindow=fmax(window,class="num">1);
  iWindowKey=fmax(window_key,class="num">1);
  iUnits=fmax(units_count,class="num">1);
  iHeads=fmax(heads,class="num">1);
  iLayers=fmax(layers,class="num">1);
  class="type">uint num=class="num">3*iWindowKey*iHeads*iUnits;            class=class="str">"cmt">//Size of QKV tensor
  class="type">uint qkv_weights=class="num">3*(iWindow+class="num">1)*iWindowKey*iHeads; class=class="str">"cmt">//Size of weights&class="macro">#x27; matrix of QKV tensor
  class="type">uint scores=iUnits*iUnits*iHeads;               class=class="str">"cmt">//Size of Score tensor
  class="type">uint mh_out=iWindowKey*iHeads*iUnits;           class=class="str">"cmt">//Size of multi-heads self-attention
  class="type">uint out=iWindow*iUnits;                        class=class="str">"cmt">//Size of our tensor

Transformer 张量缓冲的堆内存预分配

在 MT5 里手搓 Transformer 类模型,第一道坎不是矩阵乘,而是把每层要用到的中间张量提前在堆上开好。下面这段初始化逻辑按层循环,每层又跑两遍(d<2 大概率对应前向与反向或双流),把 QKV、注意力分数、多头输出、注意力输出、前馈层缓冲依次挂进各自的动态数组。 关键尺寸先算清楚:w0=(iWindowKey+1)*iHeads*iWindow 是 W0 张量体积,ff_1=4*(iWindow+1)*iWindow 是第一前馈层权矩阵尺寸,ff_2=(4*iWindow+1)*iWindow 是第二前馈层权矩阵尺寸。假设 iWindow=32、iHeads=8、iWindowKey=3,那么 w0=4*8*32=1024,ff_1=4*33*32=4224,ff_2=(128+1)*32=4128——这些数直接决定你 EA 的内存占用峰值。 [CODE] uint w0=(iWindowKey+1)*iHeads*iWindow; //W0 张量尺寸 uint ff_1=4*(iWindow+1)*iWindow; //第一前馈层权矩阵尺寸 uint ff_2=(4*iWindow+1)*iWindow; //第二前馈层权矩阵尺寸 for(uint i=0; i<iLayers; i++) { CBufferDouble *temp=NULL; for(int d=0; d<2; d++) { //--- 初始化 QKV 张量 temp=new CBufferDouble(); if(CheckPointer(temp)==POINTER_INVALID) return false; if(!temp.BufferInit(num,0)) return false; if(!QKV_Tensors.Add(temp)) return false; //--- 初始化 scores temp=new CBufferDouble(); if(CheckPointer(temp)==POINTER_INVALID) return false; if(!temp.BufferInit(scores,0)) return false; if(!S_Tensors.Add(temp)) return false; //--- 初始化多头注意力输出 temp=new CBufferDouble(); if(CheckPointer(temp)==POINTER_INVALID) return false; if(!temp.BufferInit(mh_out,0)) return false; if(!AO_Tensors.Add(temp)) return false; //--- 初始化注意力输出 temp=new CBufferDouble(); if(CheckPointer(temp)==POINTER_INVALID) return false; if(!temp.BufferInit(out,0)) return false; if(!FF_Tensors.Add(temp)) return false; //--- 初始化前馈层 1 temp=new CBufferDouble(); if(CheckPointer(temp)==POINTER_INVALID) [/CODE] 逐行拆一下:w0/ff_1/ff_2 三个 uint 只是记账用尺寸,不分配内存;外层 for 走 iLayers 层,内层 for 走 d=0,1 两次。每次 new CBufferDouble() 都在堆上开一块,BufferInit(num,0) 按 num 长度清零——num、scores、mh_out、out 分别是各张量的元素数,必须在此之前算好。任何一步 CheckPointer 失效或 BufferInit 失败就 return false,调用方得自己释放已挂入数组的 temp,否则 EA 跑久了内存泄漏会把终端拖崩。 开 MT5 把这段塞进你的 CIneuralNet 类构造里,先打日志看 iLayers=6、iWindow=32 时 AO_Tensors 总项数是否是 6*2=12。外汇和贵金属杠杆高,这类自定义推理 EA 未经充分回测直接上实盘,滑点和重算延迟可能让信号失效,风险自负。

MQL5 / C++
class="type">uint w0=(iWindowKey+class="num">1)*iHeads*iWindow;            class=class="str">"cmt">//Size W0 tensor
class="type">uint ff_1=class="num">4*(iWindow+class="num">1)*iWindow;                class=class="str">"cmt">//Size of weights&class="macro">#x27; matrix class="num">1-st feed forward layer
class="type">uint ff_2=(class="num">4*iWindow+class="num">1)*iWindow;                class=class="str">"cmt">//Size of weights&class="macro">#x27; matrix class="num">2-nd feed forward layer
for(class="type">uint i=class="num">0; i<iLayers; i++)
  {
    CBufferDouble *temp=NULL;
    for(class="type">int d=class="num">0; d<class="num">2; d++)
      {
        class=class="str">"cmt">//--- Initialize QKV tensor
        temp=new CBufferDouble();
        if(CheckPointer(temp)==POINTER_INVALID)
          class="kw">return class="kw">false;
        if(!temp.BufferInit(num,class="num">0))
          class="kw">return class="kw">false;
        if(!QKV_Tensors.Add(temp))
          class="kw">return class="kw">false;
        class=class="str">"cmt">//--- Initialize scores
        temp=new CBufferDouble();
        if(CheckPointer(temp)==POINTER_INVALID)
          class="kw">return class="kw">false;
        if(!temp.BufferInit(scores,class="num">0))
          class="kw">return class="kw">false;
        if(!S_Tensors.Add(temp))
          class="kw">return class="kw">false;
        class=class="str">"cmt">//--- Initialize multi-heads attention out
        temp=new CBufferDouble();
        if(CheckPointer(temp)==POINTER_INVALID)
          class="kw">return class="kw">false;
        if(!temp.BufferInit(mh_out,class="num">0))
          class="kw">return class="kw">false;
        if(!AO_Tensors.Add(temp))
          class="kw">return class="kw">false;
        class=class="str">"cmt">//--- Initialize attention out
        temp=new CBufferDouble();
        if(CheckPointer(temp)==POINTER_INVALID)
          class="kw">return class="kw">false;
        if(!temp.BufferInit(out,class="num">0))
          class="kw">return class="kw">false;
        if(!FF_Tensors.Add(temp))
          class="kw">return class="kw">false;
        class=class="str">"cmt">//--- Initialize Feed Forward class="num">1
        temp=new CBufferDouble();
        if(CheckPointer(temp)==POINTER_INVALID)

◍ Transformer 前向张量与权重的初始化落点

这段初始化逻辑跑在模型构建阶段,核心是把每一层的 Feed Forward 张量、QKV 权重和前馈权重逐一挂到容器里,任何一步分配失败就直接 return false 中断,保证后续推理不会拿到空指针。 循环里对最后一层做了特判:i==iLayers-1 时只把 Output 或 Gradient 缓冲塞进 FF_Tensors 然后 continue,其余层则按 out 长度初始化并预留 4*out 的临时缓冲(BufferInit 第二参数填 0 表示初值清零)。 权重侧分三块:QKV_Weights 按 qkv_weights 预留并循环 GenerateWeight() 填充;FF_Weights 对应 w0 长度;另一块前馈权重按 ff_1 预留。你在 MT5 里改网络结构时,若想加宽注意力头,优先调 qkv_weights 与 ff_1 这两个量,能直接看到 Reserve 内存占用变化。 外汇与贵金属行情下跑这类自定义神经网络指标属于高风险实验,过拟合与实时重算延迟都可能导致信号失真,建议先在策略测试器用历史数据验证权重初始化稳定性。

MQL5 / C++
      class="kw">return class="kw">false;
      if(!temp.BufferInit(class="num">4*out,class="num">0))
         class="kw">return class="kw">false;
      if(!FF_Tensors.Add(temp))
         class="kw">return class="kw">false;
      class=class="str">"cmt">//--- Initialize Feed Forward class="num">2
      if(i==iLayers-class="num">1)
         {
         if(!FF_Tensors.Add(d==class="num">0 ? Output : Gradient))
            class="kw">return class="kw">false;
         class="kw">continue;
         }
      temp=new CBufferDouble();
      if(CheckPointer(temp)==POINTER_INVALID)
         class="kw">return class="kw">false;
      if(!temp.BufferInit(out,class="num">0))
         class="kw">return class="kw">false;
      if(!FF_Tensors.Add(temp))
         class="kw">return class="kw">false;
      }
      class=class="str">"cmt">//--- Initialize QKV weights
      temp=new CBufferDouble();
      if(CheckPointer(temp)==POINTER_INVALID)
         class="kw">return class="kw">false;
      if(!temp.Reserve(qkv_weights))
         class="kw">return class="kw">false;
      for(class="type">uint w=class="num">0; w<qkv_weights; w++)
         {
         if(!temp.Add(GenerateWeight()))
            class="kw">return class="kw">false;
         }
      if(!QKV_Weights.Add(temp))
         class="kw">return class="kw">false;
      class=class="str">"cmt">//--- Initialize Weights0
      temp=new CBufferDouble();
      if(CheckPointer(temp)==POINTER_INVALID)
         class="kw">return class="kw">false;
      if(!temp.Reserve(w0))
         class="kw">return class="kw">false;
      for(class="type">uint w=class="num">0; w<w0; w++)
         {
         if(!temp.Add(GenerateWeight()))
            class="kw">return class="kw">false;
         }
      if(!FF_Weights.Add(temp))
         class="kw">return class="kw">false;
      class=class="str">"cmt">//--- Initialize FF Weights
      temp=new CBufferDouble();
      if(CheckPointer(temp)==POINTER_INVALID)
         class="kw">return class="kw">false;
      if(!temp.Reserve(ff_1))
         class="kw">return class="kw">false;

「前馈与注意力权重的初始化落点」

这段逻辑紧接前面的结构定义,把两层前馈网络(ff_1、ff_2 神经元数)的权重容器逐个塞进 FF_Weights,每层都先 Reserve 出空间再循环填 GenerateWeight() 的返回值,任何一次 Add 失败就直接 return false 中断。 随后按优化方式分流:SGD 只跑 1 轮(d<1),其他优化器跑 2 轮(d<2)。每轮里先给 QKV_Weights 挂一个用 qkv_weights 初始化的缓冲,再往 FF_Weights 追加 w0、ff_1、ff_2 三个零初始化缓冲,指针无效或 BufferInit 失败同样立即退出。 在 MT5 里跑这套,你可以把 ff_1、ff_2 改成 16/8 之类的小值先验证内存分配是否按预期翻倍;外汇与贵金属品种上接这种模型前请记住杠杆交易的高风险,权重初始化只是起点,过拟合倾向得靠样本外数据压住。

MQL5 / C++
for(class="type">uint w=class="num">0; w<ff_1; w++)
  {
   if(!temp.Add(GenerateWeight()))
     class="kw">return class="kw">false;
  }
 if(!FF_Weights.Add(temp))
   class="kw">return class="kw">false;
 class=class="str">"cmt">//---
 temp=new CBufferDouble();
 if(CheckPointer(temp)==POINTER_INVALID)
   class="kw">return class="kw">false;
 if(!temp.Reserve(ff_2))
   class="kw">return class="kw">false;
 for(class="type">uint w=class="num">0; w<ff_1; w++)
  {
   if(!temp.Add(GenerateWeight()))
     class="kw">return class="kw">false;
  }
 if(!FF_Weights.Add(temp))
   class="kw">return class="kw">false;
 class=class="str">"cmt">//---
 for(class="type">int d=class="num">0; d<(optimization==SGD ? class="num">1 : class="num">2); d++)
  {
   temp=new CBufferDouble();
   if(CheckPointer(temp)==POINTER_INVALID)
     class="kw">return class="kw">false;
   if(!temp.BufferInit(qkv_weights,class="num">0))
     class="kw">return class="kw">false;
   if(!QKV_Weights.Add(temp))
     class="kw">return class="kw">false;
   temp=new CBufferDouble();
   if(CheckPointer(temp)==POINTER_INVALID)
     class="kw">return class="kw">false;
   if(!temp.BufferInit(w0,class="num">0))
     class="kw">return class="kw">false;
   if(!FF_Weights.Add(temp))
     class="kw">return class="kw">false;
   class=class="str">"cmt">//--- Initialize FF Weights
   temp=new CBufferDouble();
   if(CheckPointer(temp)==POINTER_INVALID)
     class="kw">return class="kw">false;
   if(!temp.BufferInit(ff_1,class="num">0))
     class="kw">return class="kw">false;
   if(!FF_Weights.Add(temp))
     class="kw">return class="kw">false;
   temp=new CBufferDouble();
   if(CheckPointer(temp)==POINTER_INVALID)
     class="kw">return class="kw">false;
   if(!temp.BufferInit(ff_2,class="num">0))
把模型差异比对交给小布
这些GPT与变换器结构差异的诊断,小布盯盘的AIGC已内置,打开对应品种页即可看到网络层对比,你专注调参决策。

常见问题

理论上只要数字化序列充足即可,但实践中GPT-3级用了570GB语料,个人EA可用小规模金融序列做领域微调,效果倾向有限但能跑通。
GPT面向生成任务,自回归预测下一个元素,不需要编码器做源序列映射,去掉编码器后也省去编码器-解码器自关注层,结构更浅。
需新增GPT类、重写前馈与反馈、调整基类接口以容纳解码器缓存,具体代码见本文实现小节。
目前小布内置的是结构诊断与差异比对,不直接托管亿级参数模型;可把本地推理结果接入小布看盘页做可视化。
外汇贵金属属高风险市场,小样本微调可能令模型过拟合局部行情,概率上仅适合辅助过滤信号而非单独开仓。