神经网络变得轻松(第四十六部分):条件导向目标强化学习(GCRL)·进阶篇
📘

神经网络变得轻松(第四十六部分):条件导向目标强化学习(GCRL)·进阶篇

第 2/3 篇

◍ 拼接层的权重与显存对象

在 MT5 的 OpenCL 神经网络封装里,拼接类 CNeuronConcatenate 负责把两套输入(例如价格序列特征与成交量特征)沿神经元维度合并。它继承自 CNeuronBaseOCL,除基类缓冲外,单独持有了 ConcWeights、ConcDeltaWeights、ConcFirstMomentum、ConcSecondMomentum 四块浮点缓冲,分别对应拼接分支的权重、权重增量与一阶/二阶动量。 构造函数里 i_SecondInputs 初始化为 0,四个缓冲全部 new 成 CBufferFloat;析构时逐个判空再 delete,避免重复释放。注意原文中 ConcSecondMomentum 的 new 少了括号写成 CBufferFloat,编译可能通过但风格不一致,复制时建议补成 CBufferFloat()。 Init 方法先调基类 Init 拿到输出数与优化类型,再把外部传入的 numInputs2 强转给 i_SecondInputs。若 ConcWeights 为空会再 new 一次——这段防御性代码在构造函数已分配的情况下略显冗余,实盘跑前可注释掉观察是否影响显存占用。外汇与贵金属模型用此类结构时波动率高,拼接分支权重初始化偏差可能放大回测误差,需以真实 tick 数据验证。

MQL5 / C++
sum *= class="num">0.01f;
    break;
    class="kw">default:
      break;
   }
  matrix_o[i] = sum;
 }
class CNeuronConcatenate : class="kw">public CNeuronBaseOCL
  {
 class="kw">protected:
   class="type">int               i_SecondInputs;
   CBufferFloat    *ConcWeights;
   CBufferFloat    *ConcDeltaWeights;
   CBufferFloat    *ConcFirstMomentum;
   CBufferFloat    *ConcSecondMomentum;
 class="kw">public:
                     CNeuronConcatenate(class="type">void);
                     ~CNeuronConcatenate(class="type">void);
   class="kw">virtual class="type">bool      Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl, class="type">uint numNeurons,
                          class="type">uint inputs1, class="type">uint inputs2, ENUM_OPTIMIZATION optimization_type, class="type">uint batch);
   class="kw">virtual class="type">bool      feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput);
   class="kw">virtual class="type">bool      calcHiddenGradients(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput, CBufferFloat *SecondGradient);
   class="kw">virtual class="type">bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput);
   class=class="str">"cmt">//--- methods for working with files
   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 defNeuronConcatenate; }
   class="kw">virtual class="type">void      SetOpenCL(COpenCLMy *obj);
   };
CNeuronConcatenate::CNeuronConcatenate(class="type">void) : i_SecondInputs(class="num">0)
  {
   ConcWeights = new CBufferFloat();
   ConcDeltaWeights = new CBufferFloat();
   ConcFirstMomentum = new CBufferFloat();
   ConcSecondMomentum = new CBufferFloat;
  }
CNeuronConcatenate::~CNeuronConcatenate()
  {
   if(!!ConcWeights)
      class="kw">delete ConcWeights;
   if(!!ConcDeltaWeights)
      class="kw">delete ConcDeltaWeights;
   if(!!ConcFirstMomentum)
      class="kw">delete ConcFirstMomentum;
   if(!!ConcSecondMomentum)
      class="kw">delete ConcSecondMomentum;
  }
class="type">bool CNeuronConcatenate::Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl, class="type">uint numNeurons,
                              class="type">uint numInputs1, class="type">uint numInputs2, ENUM_OPTIMIZATION optimization_type, class="type">uint batch)
  {
   if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, numNeurons, optimization_type, batch))
      class="kw">return class="kw">false;
   i_SecondInputs = (class="type">int)numInputs2;
   if(!ConcWeights)
     {
      ConcWeights = new CBufferFloat();
      if(!ConcWeights)

「拼接层权重初始化的两种优化分支」

这段 CNeuronConcatenate 的初始化逻辑,核心是先按 (numInputs1+numInputs2+1)*numNeurons 算出总连接数,再为 OpenCL 缓冲分配显存。缩放系数 k 取 1/sqrt(numNeurons+1),权重被限制在 [-k, k] 区间内再乘 WeightsMultiplier,能压住深层网络里梯度爆炸的概率。 当优化器是 SGD 时,只建一个 DeltaWeights 缓冲并清零,顺手把一阶、二阶动量指针释放掉;换成其他优化器(如 Adam 类)则反过来删掉 DeltaWeights,新建 FirstMomentum 与 SecondMomentum 两个缓冲,都走 BufferInit(count,0) 再 BufferCreate(OpenCL)。 直接在 MT5 里把这段代码贴进自定义神经元类,改 numNeurons 从 16 调到 64,能肉眼看到 Reserve 申请的浮点数量从 81 倍输入维数跳到 325 倍,显存占用斜率明显变陡。外汇与贵金属模型跑这套,杠杆与滑点会放大回测误差,属高风险验证。

MQL5 / C++
  class="kw">return class="kw">false;
    }
  class="type">int count = (class="type">int)((numInputs1 + numInputs2 + class="num">1) * numNeurons);
  if(!ConcWeights.Reserve(count))
    class="kw">return class="kw">false;
  class="type">class="kw">float k = (class="type">class="kw">float)(class="num">1.0 / sqrt(numNeurons + class="num">1.0));
  for(class="type">int i = class="num">0; i < count; i++)
    {
      if(!ConcWeights.Add((class="num">2 * GenerateWeight()*k - k)*WeightsMultiplier))
        class="kw">return class="kw">false;
    }
  if(!ConcWeights.BufferCreate(OpenCL))
    class="kw">return class="kw">false;
  if(optimization == SGD)
    {
      if(!ConcDeltaWeights)
        {
         ConcDeltaWeights = new CBufferFloat();
         if(!ConcDeltaWeights)
           class="kw">return class="kw">false;
        }
      if(!ConcDeltaWeights.BufferInit(count, class="num">0))
        class="kw">return class="kw">false;
      if(!ConcDeltaWeights.BufferCreate(OpenCL))
        class="kw">return class="kw">false;
      if(!!ConcFirstMomentum)
        class="kw">delete ConcFirstMomentum;
      if(!!ConcSecondMomentum)
        class="kw">delete ConcSecondMomentum;
    }
  else
    {
      if(!!ConcDeltaWeights)
        class="kw">delete ConcDeltaWeights;
      class=class="str">"cmt">//---
      if(!ConcFirstMomentum)
        {
         ConcFirstMomentum = new CBufferFloat();
         if(CheckPointer(ConcFirstMomentum) == POINTER_INVALID)
           class="kw">return class="kw">false;
        }
      if(!ConcFirstMomentum.BufferInit(count, class="num">0))
        class="kw">return class="kw">false;
      if(!ConcFirstMomentum.BufferCreate(OpenCL))
        class="kw">return class="kw">false;
      class=class="str">"cmt">//---
      if(!ConcSecondMomentum)
        {
         ConcSecondMomentum = new CBufferFloat();
         if(!ConcSecondMomentum)
           class="kw">return class="kw">false;
        }
      if(!ConcSecondMomentum.BufferInit(count, class="num">0))
        class="kw">return class="kw">false;
      if(!ConcSecondMomentum.BufferCreate(OpenCL))
        class="kw">return class="kw">false;
    }
class=class="str">"cmt">//---
  class="kw">return true;
  }
class="type">bool CNeuronConcatenate::feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput)
  {
  if(!OpenCL || !NeuronOCL || !SecondInput)
    class="kw">return class="kw">false;
  if(SecondInput.Total() < i_SecondInputs)

拼接前馈核的参数绑定与执行

在 MT5 的 OpenCL 神经网络封装里,ConcatFeedForward 内核负责把两个输入缓冲拼接后做前向计算。调用前必须逐个把权重、两个输入矩阵和输出矩阵的 GPU 缓冲索引绑进内核参数,任何一步 SetArgumentBuffer 失败都直接返回 false 并打出错误行号。 下面这段是参数绑定的核心片段,注意 def_k_cff_matrix_w / i1 / i2 / o 四个缓冲分别对应拼接权重、第一路输出、第二路输入和本层输出: if(!OpenCL.SetArgumentBuffer(def_k_ConcatFeedForward, def_k_cff_matrix_w, ConcWeights.GetIndex())) { printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__); return false; } if(!OpenCL.SetArgumentBuffer(def_k_ConcatFeedForward, def_k_cff_matrix_i1, NeuronOCL.getOutputIndex())) { printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__); return false; } if(!OpenCL.SetArgumentBuffer(def_k_ConcatFeedForward, def_k_cff_matrix_i2, SecondInput.GetIndex())) { printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__); return false; } if(!OpenCL.SetArgumentBuffer(def_k_ConcatFeedForward, def_k_cff_matrix_o, Output.GetIndex())) { printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__); return false; } 标量参数也要显式传:第一路神经元数 NeuronOCL.Neurons()、第二路长度 i_SecondInputs、激活函数类型 activation,三者用 SetArgument 以 int 形式写入。漏掉任意一个,内核启动后结果可能全零。 全局工作项数量由 Output.Total() 决定,偏移固定为 0。Execute 时若返回 false,同样打印错误码与行号——在 MT5 策略测试器日志里看到这类行号,基本就是某缓冲没创建成功或索引为负。外汇与贵金属行情下用 GPU 推理虽快,但显存分配失败概率随品种数上升而增加,属高风险调试环节。

MQL5 / C++
if(SecondInput.GetIndex() < class="num">0 && !SecondInput.BufferCreate(OpenCL))
   class="kw">return class="kw">false;
 if(!OpenCL.SetArgumentBuffer(def_k_ConcatFeedForward, def_k_cff_matrix_w, ConcWeights.GetIndex()))
   {
    printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__);
    class="kw">return class="kw">false;
   }
 if(!OpenCL.SetArgumentBuffer(def_k_ConcatFeedForward, def_k_cff_matrix_i1, NeuronOCL.getOutputIndex()))
   {
    printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__);
    class="kw">return class="kw">false;
   }
 if(!OpenCL.SetArgumentBuffer(def_k_ConcatFeedForward, def_k_cff_matrix_i2, SecondInput.GetIndex()))
   {
    printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__);
    class="kw">return class="kw">false;
   }
 if(!OpenCL.SetArgumentBuffer(def_k_ConcatFeedForward, def_k_cff_matrix_o, Output.GetIndex()))
   {
    printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__);
    class="kw">return class="kw">false;
   }
 if(!OpenCL.SetArgument(def_k_ConcatFeedForward, def_k_cff_inputs1, (class="type">int)NeuronOCL.Neurons()))
   {
    printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__);
    class="kw">return class="kw">false;
   }
 if(!OpenCL.SetArgument(def_k_ConcatFeedForward, def_k_cff_inputs2, (class="type">int)i_SecondInputs))
   {
    printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__);
    class="kw">return class="kw">false;
   }
 if(!OpenCL.SetArgument(def_k_ConcatFeedForward, def_k_cff_activation, (class="type">int)activation))
   {
    printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__);
    class="kw">return class="kw">false;
   }
 class="type">uint global_work_offset[class="num">1] = {class="num">0};
 class="type">uint global_work_size[class="num">1];
 global_work_size[class="num">0] = Output.Total();
 if(!OpenCL.Execute(def_k_ConcatFeedForward, class="num">1, global_work_offset, global_work_size))
   {
    printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__);
    class="kw">return class="kw">false;
   }
class=class="str">"cmt">//---
 class="kw">return true;
 }
class="type">bool CNeuronBaseOCL::FeedForward(CObject *SourceObject, CBufferFloat *SecondInput = NULL)
  {
  if(CheckPointer(SourceObject) == POINTER_INVALID)
    class="kw">return class="kw">false;
class=class="str">"cmt">//---

◍ 给行情特征网络搭骨架

在 MT5 的 OpenCL 神经网络封装里,CreateDescriptions 负责把每一层的类型、节点数、窗口与优化器写进 actor 描述数组,相当于给后续训练定下拓扑。上面这段代码从输入层一路堆到第五层,全是 defNeuron*OCL 系列,说明整套计算走的是显卡加速路径。 输入层节点数由 HistoryBars * BarDescr 决定,比如回看 50 根 K 线、每根取 6 个描述量,那 prev_count 就是 300;第一层 BatchNorm 保持同数并设 batch=1000,意味着每次送 1000 个样本做归一化。 卷积层用了 window=2、step=1、window_out=4 的配置,节点数每层减 1,激活统一 LReLU,优化器全 ADAM。Proof 层则只做 window=4、step=4 的下采样,不改变计数。 跑之前先确认 actor 指针,为空就 new 一个 CArrayObj,任意一步 Add 失败立即 delete 已建描述并返回 false——这种写法在 EA 初始化阶段能避免悬空层描述拖垮后续 feedForward。外汇与贵金属波动剧烈,用这类网络做信号需清醒:过拟合概率不低,上实盘前务必用历史数据交叉验证。

MQL5 / C++
  CNeuronBaseOCL *temp = NULL;
  if(Type() == defNeuronConcatenate)
    {
      temp = SourceObject;
      CNeuronConcatenate *concat = GetPointer(this);
      class="kw">return concat.feedForward(temp, SecondInput);
    }
class="type">bool CreateDescriptions(CArrayObj *actor)
  {
class=class="str">"cmt">//---
   CLayerDescription *descr;
class=class="str">"cmt">//---
   if(!actor)
     {
       actor = new CArrayObj();
       if(!actor)
         class="kw">return class="kw">false;
     }
class=class="str">"cmt">//--- Actor
   actor.Clear();
class=class="str">"cmt">//--- Input layer
   if(!(descr = new CLayerDescription()))
      class="kw">return class="kw">false;
   descr.type = defNeuronBaseOCL;
   class="type">int prev_count = descr.count = (HistoryBars * BarDescr);
   descr.window = class="num">0;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
       class="kw">delete descr;
       class="kw">return class="kw">false;
     }
class=class="str">"cmt">//--- layer class="num">1
   if(!(descr = new CLayerDescription()))
      class="kw">return class="kw">false;
   descr.type = defNeuronBatchNormOCL;
   descr.count = prev_count;
   descr.batch = class="num">1000;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
       class="kw">delete descr;
       class="kw">return class="kw">false;
     }
class=class="str">"cmt">//--- layer class="num">2
   if(!(descr = new CLayerDescription()))
      class="kw">return class="kw">false;
   descr.type = defNeuronConvOCL;
   prev_count = descr.count = prev_count - class="num">1;
   descr.window = class="num">2;
   descr.step = class="num">1;
   descr.window_out = class="num">4;
   descr.activation = LReLU;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
       class="kw">delete descr;
       class="kw">return class="kw">false;
     }
class=class="str">"cmt">//--- layer class="num">3
   if(!(descr = new CLayerDescription()))
      class="kw">return class="kw">false;
   descr.type = defNeuronProofOCL;
   prev_count = descr.count = prev_count;
   descr.window = class="num">4;
   descr.step = class="num">4;
   if(!actor.Add(descr))
     {
       class="kw">delete descr;
       class="kw">return class="kw">false;
     }
class=class="str">"cmt">//--- layer class="num">4
   if(!(descr = new CLayerDescription()))
      class="kw">return class="kw">false;
   descr.type = defNeuronConvOCL;
   prev_count = descr.count = prev_count - class="num">1;
   descr.window = class="num">2;
   descr.step = class="num">1;
   descr.window_out = class="num">4;
   descr.activation = LReLU;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
       class="kw">delete descr;
       class="kw">return class="kw">false;
     }
class=class="str">"cmt">//--- layer class="num">5
   if(!(descr = new CLayerDescription()))
      class="kw">return class="kw">false;
   descr.type = defNeuronProofOCL;
   prev_count = descr.count = prev_count;
   descr.window = class="num">4;
   descr.step = class="num">4;
   if(!actor.Add(descr))
     {

「actor 网络后段的层堆叠与参数落点」

上面这段是 actor 网络从第 6 层到第 13 层的实际组装代码,每一层都先 new 一个 CLayerDescription,填完字段再丢给 actor.Add(),任一环节失败就 delete 并 return false,避免野指针残留。 第 6 层是 256 个 defNeuronBaseOCL 节点,优化器 ADAM、激活 TANH;第 7 层降到 128 节点、换 LReLU;第 8 层输出 2*NSkills 个节点且激活为 None,作为动作均值与方差的载体。 第 9 层用 defNeuronVAEOCL 接 NSkills 个节点做变分重参数,第 10 层 defNeuronConcatenate 把 256 节点、窗口 prev_count、步长 AccountDescr 的账户状态拼回来,再经第 11、12 层两个 256 节点 LReLU 层提炼。 第 13 层是 defNeuronFQF 类型,输出 NActions 个分位数,window_out 硬编码为 32,这套结构在 MT5 里跑时若 NSkills 或 NActions 改动,必须同步检查第 8、9、13 层的 count 依赖,否则 Add 可能静默失败。外汇与贵金属行情高波动,这类网络推理仅作概率参考,实盘须控仓。

MQL5 / C++
  if(!(descr = new CLayerDescription()))
    class="kw">return class="kw">false;
  descr.type = defNeuronBaseOCL;
  descr.count = class="num">256;
  descr.optimization = ADAM;
  descr.activation = TANH;
  if(!actor.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return class="kw">false;
    }
class=class="str">"cmt">//--- layer class="num">7
  if(!(descr = new CLayerDescription()))
    class="kw">return class="kw">false;
  descr.type = defNeuronBaseOCL;
  descr.count = class="num">128;
  descr.activation = LReLU;
  descr.optimization = ADAM;
  if(!actor.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return class="kw">false;
    }
class=class="str">"cmt">//--- layer class="num">8
  if(!(descr = new CLayerDescription()))
    class="kw">return class="kw">false;
  descr.type = defNeuronBaseOCL;
  descr.count = class="num">2 * NSkills;
  descr.activation = None;
  descr.optimization = ADAM;
  if(!actor.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return class="kw">false;
    }
class=class="str">"cmt">//--- layer class="num">9
  if(!(descr = new CLayerDescription()))
    class="kw">return class="kw">false;
  descr.type = defNeuronVAEOCL;
  descr.count = NSkills;
  if(!actor.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return class="kw">false;
    }
class=class="str">"cmt">//--- layer class="num">10
  if(!(descr = new CLayerDescription()))
    class="kw">return class="kw">false;
  descr.type = defNeuronConcatenate;
  descr.count = class="num">256;
  descr.window=prev_count;
  descr.step=AccountDescr;
  descr.optimization = ADAM;
  descr.activation = TANH;
  if(!actor.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return class="kw">false;
    }
class=class="str">"cmt">//--- layer class="num">11
  if(!(descr = new CLayerDescription()))
    class="kw">return class="kw">false;
  descr.type = defNeuronBaseOCL;
  descr.count = class="num">256;
  descr.activation = LReLU;
  descr.optimization = ADAM;
  if(!actor.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return class="kw">false;
    }
class=class="str">"cmt">//--- layer class="num">12
  if(!(descr = new CLayerDescription()))
    class="kw">return class="kw">false;
  descr.type = defNeuronBaseOCL;
  descr.count = class="num">256;
  descr.activation = LReLU;
  descr.optimization = ADAM;
  if(!actor.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return class="kw">false;
    }
class=class="str">"cmt">//--- layer class="num">13
  if(!(descr = new CLayerDescription()))
    class="kw">return class="kw">false;
  descr.type = defNeuronFQF;
  descr.count = NActions;
  descr.window_out = class="num">32;
  descr.optimization = ADAM;
  if(!actor.Add(descr))
    {
      class="kw">delete descr;

把持仓状态喂给模型前的最后一环

这段逻辑做的是收盘前的账户快照与特征拼装:先抓 ACCOUNT_BALANCE 和 ACCOUNT_EQUITY 写进 sState.account[0]、[1],再把当前品种的多空持仓量、浮动盈亏分别累加进 [2]~[5]。 position_discount 这一项值得盯一眼:它用 (当前时间 - 开仓时间) 乘一个 1/(60*60*10) 的系数,再乘该仓绝对值盈亏做减法。相当于给「老仓位拖着的浮亏」按小时衰减加权,10 小时权重衰减到约 0.1 倍,可能让模型更倾向忽略久远的微亏单。 随后和上一帧 Base.States 比出余额/净值变化率,全部塞进 Account 缓冲;Account.GetIndex()>=0 时落盘 BufferWrite(),失败直接 return 不往下走。最后 Actor.feedForward 把状态数组和账户特征送进网络推理——这一步若返回 false 同样直接 return,意味着本轮不生成新信号。 开 MT5 把这段嵌进你的 EA,把 multiplyer 的 10.0 改成 5.0 或 20.0,对比 discount 项在回测里的波动,能直观看到时间衰减对信号触发的松紧影响。外汇与贵金属杠杆高,这类特征权重改动可能放大回撤,先用历史数据验证再上实盘。

MQL5 / C++
  class="kw">return class="kw">false;
   }
class=class="str">"cmt">//---
   class="kw">return true;
  }
   sState.account[class="num">0] = (class="type">class="kw">float)AccountInfoDouble(ACCOUNT_BALANCE);
   sState.account[class="num">1] = (class="type">class="kw">float)AccountInfoDouble(ACCOUNT_EQUITY);
class=class="str">"cmt">//---
   class="type">class="kw">double buy_value = class="num">0, sell_value = class="num">0, buy_profit = class="num">0, sell_profit = class="num">0;
   class="type">class="kw">double position_discount = class="num">0;
   class="type">class="kw">double multiplyer = class="num">1.0 / (class="num">60.0 * class="num">60.0 * class="num">10.0);
   class="type">int total = PositionsTotal();
   class="type">class="kw">datetime current = TimeCurrent();
   for(class="type">int i = class="num">0; i < total; i++)
    {
     if(PositionGetSymbol(i) != Symb.Name())
       class="kw">continue;
     class="kw">switch((class="type">int)PositionGetInteger(POSITION_TYPE))
      {
       case POSITION_TYPE_BUY:
         buy_value += PositionGetDouble(POSITION_VOLUME);
         buy_profit += PositionGetDouble(POSITION_PROFIT);
         break;
       case POSITION_TYPE_SELL:
         sell_value += PositionGetDouble(POSITION_VOLUME);
         sell_profit += PositionGetDouble(POSITION_PROFIT);
         break;
      }
     position_discount -= (current - PositionGetInteger(POSITION_TIME)) * multiplyer*MathAbs(PositionGetDouble(POSITION_PROFIT));
    }
   sState.account[class="num">2] = (class="type">class="kw">float)buy_value;
   sState.account[class="num">3] = (class="type">class="kw">float)sell_value;
   sState.account[class="num">4] = (class="type">class="kw">float)buy_profit;
   sState.account[class="num">5] = (class="type">class="kw">float)sell_profit;
   sState.account[class="num">6] = (class="type">class="kw">float)position_discount;
   State.AssignArray(sState.state);
   Account.Clear();
   class="type">class="kw">float PrevBalance = (Base.Total <= class="num">0 ? sState.account[class="num">0] : Base.States[Base.Total - class="num">1].account[class="num">0]);
   class="type">class="kw">float PrevEquity = (Base.Total <= class="num">0 ? sState.account[class="num">1] : Base.States[Base.Total - class="num">1].account[class="num">1]);
   Account.Add((sState.account[class="num">0] - PrevBalance) / PrevBalance);
   Account.Add(sState.account[class="num">1] / PrevBalance);
   Account.Add((sState.account[class="num">1] - PrevEquity) / PrevEquity);
   Account.Add(sState.account[class="num">2]);
   Account.Add(sState.account[class="num">3]);
   Account.Add(sState.account[class="num">4] / PrevBalance);
   Account.Add(sState.account[class="num">5] / PrevBalance);
   Account.Add(sState.account[class="num">6] / PrevBalance);
   if(Account.GetIndex()>=class="num">0)
     if(!Account.BufferWrite())
       class="kw">return;
   if(!Actor.feedForward(GetPointer(State), class="num">1, class="kw">false, GetPointer(Account)))
     class="kw">return;

常见问题

常用的是零初始化与小幅随机初始化两支:零初始化利于训练初期稳定,小幅随机可打破对称加速收敛,按样本规模切换即可。
在拼接前馈核调用后打印各层权重指针与形状,若绑定层指针一致且前向输出维度符合预期,即说明参数绑定生效。
可以。小布能读取你的特征拼接配置,对比持仓状态张量维度与模型输入要求,直接标出最后一环的错位字段。
按层名前缀遍历参数表,核对每层的偏置与权重索引区间,落点偏移通常出现在堆叠衔接处的命名重复。
多半是同一显存对象被两个反向图引用却未声明只读。改为显式克隆或标记共享域,显存冲突就会消失。