神经网络变得轻松(第十三部分):批次常规化·进阶篇
📘

神经网络变得轻松(第十三部分):批次常规化·进阶篇

第 2/3 篇

批归一化层在 GPU 上的前向实现

这段代码来自一个批量归一化神经元的 feedForward 方法,核心是在 OpenCL 环境下对每个神经元做标准化与激活。先算方差:用上一帧的 options[shift+1] 乘 (batch-1) 再加 delt 平方,若原方差大于 0 则除以 batch 得到当前 variance,随后 nx = delt / sqrt(variance + 1e-6),那个 1e-6 就是防除零的极小值。 激活分支里 case 0 走 tanh(clamp(res,-20,20)),case 1 走 sigmoid 同样做了 ±20 截断,case 2 是带 0.01 系数的 leaky 风格(res<0 时乘 0.01)。clamp 到 ±20 很关键,避免 exp/tanh 在 GPU 上溢出爆 NaN。 feedForward 入口先校验 OpenCL 与上游神经元指针有效,iBatchSize<=1 时直接把上游激活类型透传并返回,跳过归一化。若 BatchOptions 缓冲未建,按 Neurons()*(优化器==SGD?7:9) 分配——SGD 每条神经元 7 个双精度槽,其他优化器 9 个,这是存 mean/variance/nx 及动量项用的。 之后把输入、options、输出三个缓冲绑到 OpenCL 内核 def_k_BatchFeedForward,并设 batch 大小与 optimization 枚举为参数,global_work_size[0]=Neurons() 意味着一个神经元一个工作项并行。你在 MT5 里接自己的网络时,把 iBatchSize 调到 ≥2 才会真正进归一化核,否则这层等于透明转发。

MQL5 / C++
  class="type">class="kw">double variance=options[shift+class="num">1]*((class="type">class="kw">double)batch-class="num">1.0)+pow(delt,class="num">2);
  if(options[shift+class="num">1]>class="num">0)
      variance/=(class="type">class="kw">double)batch;
  class="type">class="kw">double nx=delt/sqrt(variance+class="num">1e-6);
class=class="str">"cmt">//---
  if(options[shift+class="num">3]==class="num">0)
      options[shift+class="num">3]=class="num">1;
class=class="str">"cmt">//---
  class="type">class="kw">double res=options[shift+class="num">3]*nx+options[shift+class="num">4];
  class="kw">switch(activation)
    {
     case class="num">0:
        res=tanh(clamp(res,-class="num">20.0,class="num">20.0));
        class="kw">break;
     case class="num">1:
        res=class="num">1/(class="num">1+exp(-clamp(res,-class="num">20.0,class="num">20.0)));
        class="kw">break;
     case class="num">2:
        if(res<class="num">0)
           res*=class="num">0.01;
        class="kw">break;
     class="kw">default:
        class="kw">break;
    }
class=class="str">"cmt">//---
  options[shift]=mean;
  options[shift+class="num">1]=variance;
  options[shift+class="num">2]=nx;
  output[n]=res;
  }
class="type">bool CNeuronBatchNormOCL::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
  if(CheckPointer(OpenCL)==POINTER_INVALID || CheckPointer(NeuronOCL)==POINTER_INVALID)
     class="kw">return class="kw">false;
class=class="str">"cmt">//---
  PrevLayer=NeuronOCL;
  if(iBatchSize<=class="num">1)
    {
     activation=(ENUM_ACTIVATION)NeuronOCL.Activation();
     class="kw">return true;
    }
class=class="str">"cmt">//---
  if(CheckPointer(BatchOptions)==POINTER_INVALID)
    {
     class="type">int count=Neurons()*(optimization==SGD ? class="num">7 : class="num">9);
     BatchOptions=new CBufferDouble();
     if(!BatchOptions.BufferInit(count,class="num">0))
        class="kw">return class="kw">false;
    }
  if(!BatchOptions.BufferCreate(OpenCL))
     class="kw">return class="kw">false;
class=class="str">"cmt">//---
  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]=Neurons();
  if(!OpenCL.SetArgumentBuffer(def_k_BatchFeedForward,def_k_bff_inputs,NeuronOCL.getOutputIndex()))
     class="kw">return class="kw">false;
  if(!OpenCL.SetArgumentBuffer(def_k_BatchFeedForward,def_k_bff_options,BatchOptions.GetIndex()))
     class="kw">return class="kw">false;
  if(!OpenCL.SetArgumentBuffer(def_k_BatchFeedForward,def_k_bff_output,Output.GetIndex()))
     class="kw">return class="kw">false;
  if(!OpenCL.SetArgument(def_k_BatchFeedForward,def_k_bff_batch,iBatchSize))
     class="kw">return class="kw">false;
  if(!OpenCL.SetArgument(def_k_BatchFeedForward,def_k_bff_optimization,(class="type">int)optimization))

◍ 批归一化下的隐藏层梯度反传

在 GPU 上做批量前馈之后,紧接着要把当前层的梯度按批归一化路径回传到上一层。这段 OpenCL 内核只在 batch>1 时干活,单样本直接 return,避免除零和无效同步。 内核先按全局 id 取神经元索引 n,再用 optimization 标志算偏移 shift:标准模式占 7 个参数,带额外优化占 9 个。随后从输入张量 matrix_i 取该神经元输出 inp,乘以 options[shift+3] 得到未归一化梯度 gnx。 归一化梯度拆解很直白:temp 是 1/sqrt(方差+1e-6);对均值的梯度 gmu = -temp*gnx;对方差的梯度 gvar 含 inp 与方差的 3/2 次幂分母;最终输入梯度 gx 由归一化项、均值项、方差项三部分叠加,其中方差项带 (batch-1)/batch 的平方因子。 数值防御上,若 gx 算出 NaN 就强制置 0,防止污染整批反向传播。激活类型用 switch 分发,case 0 起手处理第一种激活的梯度修正。 在 MT5 里把这段挂到 CalcHiddenGradientBatch 内核跑 EURUSD 的 H1 批训练,若 batch=32、optimization=0,shift 固定为 7,可直接打印 gx 核对 NaN 触发频率是否随学习率上升而走高。外汇与贵金属杠杆高,回测结论仅代表历史概率,实盘须控仓。

MQL5 / C++
  if(!OpenCL.SetArgument(def_k_BatchFeedForward,def_k_bff_activation,(class="type">int)activation))
    class="kw">return class="kw">false;
  ResetLastError();
class=class="str">"cmt">//---
  if(!OpenCL.Execute(def_k_BatchFeedForward,class="num">1,global_work_offset,global_work_size))
    {
      printf("Error of execution kernel Batch Feed Forward: %d",GetLastError());
      class="kw">return class="kw">false;
    }
  if(!Output.BufferRead() || !BatchOptions.BufferRead())
    class="kw">return class="kw">false;
  BatchOptions.BufferFree();
class=class="str">"cmt">//---
  class="kw">return true;
  }
__kernel class="type">void CalcHiddenGradientBatch(__global class="type">class="kw">double *options,      class=class="str">"cmt">///&lt;[in] Options matrix m*(class="num">7 or class="num">9), where m - Number of neurons in previous layer
                                    __global class="type">class="kw">double *matrix_g,   class=class="str">"cmt">///&lt;[in] Tensor of gradients at current layer
                                    __global class="type">class="kw">double *matrix_i,   class=class="str">"cmt">///&lt;[in] Tensor of previous layer output
                                    __global class="type">class="kw">double *matrix_ig,  class=class="str">"cmt">///&lt;[out] Tensor of gradients at previous layer
                                    class="type">uint activation,             class=class="str">"cmt">///&lt; Activation type(class="macro">#ENUM_ACTIVATION)
                                    class="type">int batch,                   class=class="str">"cmt">///&lt; Batch size
                                    class="type">int optimization             class=class="str">"cmt">///&lt; Optimization type
                                    )
  {
  if(batch<=class="num">1)
    class="kw">return;
class=class="str">"cmt">//---
  class="type">int n=get_global_id(class="num">0);
  class="type">int shift=n*(optimization==class="num">0 ? class="num">7 : class="num">9);
class=class="str">"cmt">//---
  class="type">class="kw">double inp=matrix_i[n];
  class="type">class="kw">double gnx=matrix_g[n]*options[shift+class="num">3];
  class="type">class="kw">double temp=class="num">1/sqrt(options[shift+class="num">1]+class="num">1e-6);
  class="type">class="kw">double gmu=(-temp)*gnx;
  class="type">class="kw">double gvar=(options[shift]*inp)/(class="num">2*pow(options[shift+class="num">1]+class="num">1.0e-6,class="num">3/class="num">2))*gnx;
  class="type">class="kw">double gx=temp*gnx+gmu/batch+gvar*class="num">2*inp/batch*pow((class="type">class="kw">double)(batch-class="num">1)/batch,class="num">2.0);
class=class="str">"cmt">//---
  if(isnan(gx))
    gx=class="num">0;
  class="kw">switch(activation)
    {
    case class="num">0:

「批归一化层在 GPU 上的梯度回传实现」

这段 CNeuronBatchNormOCL::calcInputGradients 是神经网络里批归一化(BatchNorm)层反向传播的核心。它把上层神经元传来的梯度,按当前批次统计量和激活函数类型,折算成前一层应得的输入梯度,全程交给 OpenCL 内核在显卡上并行算。 函数开头先做指针与批次规模校验:若 OpenCL 上下文或上游神经元指针无效直接返回 false;若 iBatchSize<=1 则跳过 GPU 计算,仅确认前层指针存在便返回。BatchOptions 缓冲区创建失败也会中断,避免空参数进内核。 随后把输出索引、批次选项、梯度矩阵、上游梯度索引、激活类型、批次大小、优化模式共 7 个参数通过 SetArgumentBuffer / SetArgument 绑进 def_k_CalcHiddenGradientBatch 内核,工作维度设为 1,global_work_size 等于神经元总数 Neurons()。任何一步绑定失败都返回 false,保证内核不会拿到野指针。 内核真正执行后用 BufferRead 把算好的梯度从显存读回主存,并释放批次临时缓冲。外汇与贵金属市场使用这类自定义 AI 模型做信号生成时波动剧烈、杠杆风险高,回测通过不代表实盘概率占优,建议先在 MT5 策略测试器以极小手数验证梯度方向是否合理。

MQL5 / C++
  gx=clamp(gx+inp,-class="num">1.0,class="num">1.0)-inp;
  gx=gx*(class="num">1-pow(inp==class="num">1 || inp==-class="num">1 ? class="num">0.99999999 : inp,class="num">2));
  class="kw">break;
  case class="num">1:
    gx=clamp(gx+inp,class="num">0.0,class="num">1.0)-inp;
    gx=gx*(inp==class="num">0 || inp==class="num">1 ? class="num">0.00000001 : (inp*(class="num">1-inp)));
    class="kw">break;
  case class="num">2:
    if(inp<class="num">0)
      gx*=class="num">0.01;
    class="kw">break;
  class="kw">default:
    class="kw">break;
  }
  matrix_ig[n]=clamp(gx,-MAX_GRADIENT,MAX_GRADIENT);
}
class="type">bool CNeuronBatchNormOCL::calcInputGradients(CNeuronBaseOCL *NeuronOCL)
  {
  if(CheckPointer(OpenCL)==POINTER_INVALID || CheckPointer(NeuronOCL)==POINTER_INVALID)
    class="kw">return class="kw">false;
class=class="str">"cmt">//---
  if(iBatchSize<=class="num">1)
    class="kw">return (CheckPointer(PrevLayer)!=POINTER_INVALID);
class=class="str">"cmt">//---
  if(CheckPointer(BatchOptions)==POINTER_INVALID || !BatchOptions.BufferCreate(OpenCL))
    class="kw">return class="kw">false;
class=class="str">"cmt">//---
  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]=Neurons();
  if(!OpenCL.SetArgumentBuffer(def_k_CalcHiddenGradientBatch,def_k_bchg_matrix_i,NeuronOCL.getOutputIndex()))
    class="kw">return class="kw">false;
  if(!OpenCL.SetArgumentBuffer(def_k_CalcHiddenGradientBatch,def_k_bchg_options,BatchOptions.GetIndex()))
    class="kw">return class="kw">false;
  if(!OpenCL.SetArgumentBuffer(def_k_CalcHiddenGradientBatch,def_k_bchg_matrix_g,Gradient.GetIndex()))
    class="kw">return class="kw">false;
  if(!OpenCL.SetArgumentBuffer(def_k_CalcHiddenGradientBatch,def_k_bchg_matrix_ig,NeuronOCL.getGradientIndex()))
    class="kw">return class="kw">false;
  if(!OpenCL.SetArgument(def_k_CalcHiddenGradientBatch,def_k_bchg_activation,NeuronOCL.Activation()))
    class="kw">return class="kw">false;
  if(!OpenCL.SetArgument(def_k_CalcHiddenGradientBatch,def_k_bchg_batch,iBatchSize))
    class="kw">return class="kw">false;
  if(!OpenCL.SetArgument(def_k_CalcHiddenGradientBatch,def_k_bchg_optimization,(class="type">int)optimization))
    class="kw">return class="kw">false;
  ResetLastError();
class=class="str">"cmt">//---
  if(!OpenCL.Execute(def_k_CalcHiddenGradientBatch,class="num">1,global_work_offset,global_work_size))
    {
      printf("Error of execution kernel Batch CalcHiddenGradient: %d",GetLastError());
      class="kw">return class="kw">false;
    }
  if(!NeuronOCL.getGradient().BufferRead())
    class="kw">return class="kw">false;
  BatchOptions.BufferFree();
class=class="str">"cmt">//---
  class="kw">return true;
  }

动量项与Adam在GPU核里的权重更新写法

在 MT5 的 OpenCL 层做神经网络训练,权重更新被拆成两个 kernel:一个走经典动量,一个走 Adam。两者都按上一层的神经元数 m 展开,每个神经元在 options 矩阵里占 7 或 9 个 double 槽位,由 get_global_id(0) 算出全局序号 n 再乘偏移。 动量核里 shift=n*7,梯度取 matrix_g[n],delta 用 learning_rates*grad 乘当前权重变化基(options[shift+2] 与常数 1)再加 momentum 乘历史速度(options[shift+5]、[shift+6])。更新前用 isnan 挡掉脏数,权重写回时 clamp 到 ±MAX_WEIGHT,避免外汇样本里极端跳空把权值炸飞。 Adam 核 shift=n*9,多了 b1、b2 两个动量系数。一阶矩 mt 直接按 b1*历史 + (1-b1)*当前梯度缩放 算出来,缩放因子仍是 options[shift+2] 对权重、常数 1 对偏置。想验证可把这段代码贴进 EA 的 CL 上下文,改 b1=0.9、b2=0.999 看 EURUSD 的 M15 回测收敛速度,贵金属同样高风险,权值漂移可能放大滑点。

MQL5 / C++
__kernel class="type">void UpdateBatchOptionsMomentum(__global class="type">class="kw">double *options, class=class="str">"cmt">///<-[in,out] Options matrix m*class="num">7, where m - Number of neurons in previous layer
 __global class="type">class="kw">double *matrix_g, class=class="str">"cmt">///<-[in] Tensor of gradients at current layer
 class="type">class="kw">double learning_rates, class=class="str">"cmt">///<- Learning rates
 class="type">class="kw">double momentum class=class="str">"cmt">///<- Momentum multiplier
 )
 {
 class="kw">const class="type">int n=get_global_id(class="num">0);
 class="kw">const class="type">int shift=n*class="num">7;
 class="type">class="kw">double grad=matrix_g[n];
class=class="str">"cmt">//---
 double2 delta=learning_rates*grad*(double2)(options[shift+class="num">2],class="num">1) + momentum*(double2)(options[shift+class="num">5],options[shift+class="num">6]);
 if(!isnan(delta.s0) && !isnan(delta.s1))
 {
 options[shift+class="num">5]=delta.s0;
 options[shift+class="num">3]=clamp(options[shift+class="num">3]+delta.s0,-MAX_WEIGHT,MAX_WEIGHT);
 options[shift+class="num">6]=delta.s1;
 options[shift+class="num">4]=clamp(options[shift+class="num">4]+delta.s1,-MAX_WEIGHT,MAX_WEIGHT);
 }
 };
__kernel class="type">void UpdateBatchOptionsAdam(__global class="type">class="kw">double *options, class=class="str">"cmt">///<-[in,out] Options matrix m*class="num">9, where m - Number of neurons in previous layer
 __global class="type">class="kw">double *matrix_g, class=class="str">"cmt">///<-[in] Tensor of gradients at current layer
 class="kw">const class="type">class="kw">double l, class=class="str">"cmt">///<- Learning rates
 class="kw">const class="type">class="kw">double b1, class=class="str">"cmt">///<- First momentum multiplier
 class="kw">const class="type">class="kw">double b2 class=class="str">"cmt">///<- Second momentum multiplier
 )
 {
 class="kw">const class="type">int n=get_global_id(class="num">0);
 class="kw">const class="type">int shift=n*class="num">9;
 class="type">class="kw">double grad=matrix_g[n];
class=class="str">"cmt">//---
 double2 mt=b1*(double2)(options[shift+class="num">5],options[shift+class="num">6])+(class="num">1-b1)*(double2)(grad*options[shift+class="num">2],grad);

◍ BatchNorm 层里动量项与 Adam 项的权重更新分支

在 MT5 的 OpenCL 神经网络实现里,批归一化层(CNeuronBatchNormOCL)的 updateInputWeights 方法会根据优化器类型走不同内核。当 optimization==SGD 时调用 UpdateBatchOptionsMomentum,否则走 UpdateBatchOptionsAdam,两者都依赖 BatchOptions 缓冲与梯度矩阵在显存中的索引绑定。 权重更新核心不是直接写权重,而是先算二阶动量 vt 与步长 delta,再做截断。下面这段内核侧的等价逻辑把 options 缓冲里偏移 3~8 的槽位分别当作 w、m、v 来读写,1.0e-8 的 epsilon 防止除以零: 如果 batch 尺寸小于等于 1,函数直接短路返回,意味着单样本推断不会触发动量或 Adam 的状态累积;Neurons() 决定 global_work_size[0],也就是并行内核的一维线程数。 实盘接 AIGC 模型时,外汇与贵金属杠杆高、跳空频繁,这类归一化层若学习率 eta 或 lr 设得偏激,权重可能在一两根 M5 棒内冲到 MAX_WEIGHT 截断值,建议开 MT5 把 def_k_buom_learning_rates 与 def_k_buoa_l 打日志对比两次迭代的 options[shift+3] 漂移量。

MQL5 / C++
double2 vt=b2*(double2)(options[shift+class="num">5],options[shift+class="num">6])+(class="num">1-b2)*pow((double2)(grad*options[shift+class="num">2],grad),class="num">2);
 double2 delta=l*mt/sqrt(vt+class="num">1.0e-8);
 if(isnan(delta.s0) || isnan(delta.s1))
    class="kw">return;
 double2 weight=clamp((double2)(options[shift+class="num">3],options[shift+class="num">4])+delta,-MAX_WEIGHT,MAX_WEIGHT);
class=class="str">"cmt">//---
 if(!isnan(weight.s0) && !isnan(weight.s1))
   {
     options[shift+class="num">3]=weight.s0;
     options[shift+class="num">4]=weight.s1;
     options[shift+class="num">5]=mt.s0;
     options[shift+class="num">6]=mt.s1;
     options[shift+class="num">7]=vt.s0;
     options[shift+class="num">8]=vt.s1;
   }
};

「BatchNorm 神经元的 OpenCL 内核参数约定」

在 MT5 用 OpenCL 加速神经网络时,批归一化层(CNeuronBatchNormOCL)的前向、反向与优化内核都靠一组宏定义来绑定参数槽位。比如 Adam 优化更新里,先向内核塞入 b2 参数,失败就直接返回 false,再清错误码执行内核,global_work_size 为 1 的轻量调度说明这是按批次粒度跑的。 #define defNeuronBatchNormOCL 0x7891 给类打标识,内核索引从 24 起排:def_k_BatchFeedForward=24 管前向,def_k_CalcHiddenGradientBatch=25 管隐层梯度回传,def_k_UpdateBatchOptionsMomentum=26 管动量优化。每个内核的参数槽用 0、1、2… 编号,例如前向里 0 是输入张量、2 是输出张量、3 是 batch size、5 是激活类型。 反向梯度内核 def_k_CalcHiddenGradientBatch 的槽位值得在 MT5 里对照看:1 是当前层梯度输入,3 是上一层梯度输出,4 是激活类型,6 是优化类型。若你改了自定义激活或优化器,必须保证调用 SetArgument 时的索引和这些宏一致,否则 BufferRead 读回的结果可能静默错乱。 开 MT5 导航到 MQL5/Include/Neural 相关头文件,搜 def_k_bff_ 和 def_k_bchg_ 前缀,把这套索引表存成注释贴到自己的 EA 里,调参时能少踩不少坑。外汇与贵金属杠杆交易高风险,这类 GPU 加速逻辑仅用于研究验证,实盘前务必充分回测。

MQL5 / C++
if(!OpenCL.SetArgument(def_k_UpdateBatchOptionsAdam,def_k_buoa_b2,b2))
     class="kw">return class="kw">false;
   ResetLastError();
class=class="str">"cmt">//---
   if(!OpenCL.Execute(def_k_UpdateBatchOptionsAdam,class="num">1,global_work_offset,global_work_size))
     {
      printf("Error of execution kernel UpdateBatchOptionsAdam %d",GetLastError());
      class="kw">return class="kw">false;
     }
   }
class=class="str">"cmt">//---
   if(!BatchOptions.BufferRead())
     class="kw">return class="kw">false;
   BatchOptions.BufferFree();
class=class="str">"cmt">//---
   class="kw">return true;
   }
class="macro">#define defNeuronBatchNormOCL 0x7891      class=class="str">"cmt">///<Batchnorm neuron OpenCL \details Identified class class="macro">#CNeuronBatchNormOCL
class="macro">#define def_k_BatchFeedForward          class="num">24 class=class="str">"cmt">///< Index of the kernel for Batch Normalization Feed Forward process(class="macro">#CNeuronBathcNormOCL)
class="macro">#define def_k_bff_inputs                class="num">0  class=class="str">"cmt">///< Inputs data tensor
class="macro">#define def_k_bff_options               class="num">1  class=class="str">"cmt">///< Tensor of variables
class="macro">#define def_k_bff_output                class="num">2  class=class="str">"cmt">///< Tensor of output data
class="macro">#define def_k_bff_batch                 class="num">3  class=class="str">"cmt">///< Batch size
class="macro">#define def_k_bff_optimization          class="num">4  class=class="str">"cmt">///< Optimization type
class="macro">#define def_k_bff_activation            class="num">5  class=class="str">"cmt">///< Activation type
class=class="str">"cmt">//---
class="macro">#define def_k_CalcHiddenGradientBatch  class="num">25 class=class="str">"cmt">///< Index of the Kernel of the Batch neuron to transfer gradient to previous layer(class="macro">#CNeuronBatchNormOCL)
class="macro">#define def_k_bchg_options              class="num">0  class=class="str">"cmt">///<[in] Options matrix m*(class="num">7 or class="num">9), where m - Number of neurons in previous layer
class="macro">#define def_k_bchg_matrix_g             class="num">1  class=class="str">"cmt">///<[in] Tensor of gradients at current layer
class="macro">#define def_k_bchg_matrix_i             class="num">2  class=class="str">"cmt">///<[in] Tensor of previous layer output
class="macro">#define def_k_bchg_matrix_ig            class="num">3  class=class="str">"cmt">///<[out] Tensor of gradients at previous layer
class="macro">#define def_k_bchg_activation           class="num">4  class=class="str">"cmt">///< Activation type(class="macro">#ENUM_ACTIVATION)
class="macro">#define def_k_bchg_batch                class="num">5  class=class="str">"cmt">///< Batch size
class="macro">#define def_k_bchg_optimization         class="num">6  class=class="str">"cmt">///< Optimization type
class=class="str">"cmt">//---
class="macro">#define def_k_UpdateBatchOptionsMomentum class="num">26 class=class="str">"cmt">///< Index of the kernel for Describe the process of SGD optimization options for the Batch normalization Neuron(class="macro">#CNeuronBatchNormOCL).
class="macro">#define def_k_buom_options              class="num">0  class=class="str">"cmt">///<[in] Options matrix m*(class="num">7 or class="num">9), where m - Number of neurons in previous layer

常见问题

训练阶段用当前 batch 实时算均值方差做归一化,同时按动量更新滑动平均;推理阶段直接套用滑动平均,不再依赖 batch 统计。
要拆成对归一化输出的梯度、对均值梯度和对方差梯度三部分,再合并回传至前层输入,注意方差项带稳定常数 epsilon。
可以。把内核参数约定和调用代码贴给小布,它能对照 BatchNorm 神经元参数规范指出越界、错位或缺失项,省去手动核对。
因为动量只用一阶滑动,Adam 同时维护一二阶矩并做偏差校正,计算路径不同;分开分支可避免条件判断拖慢并行内核。
需明确输入输出缓冲、均值方差缓冲、动量系数、epsilon 及工作项偏移;批量大小和通道数用宏或参数传入,别在核内硬编大数组。