模型推理谈谈caffe的bn和scale算子(代码片段)

极智视界 极智视界     2023-01-06     540

关键词:

欢迎关注我的公众号 [极智视界],获取我的更多笔记分享

O_o>_<o_OO_o~_~o_O

  本文聊一聊 caffe 的 bn 和 scale 算子。

  caffe 中的 bn 算子和其他框架如 pytorch、darknet 中的有点区别,故这里拎出来聊一聊,避免大家走坑。前面也写过一篇关于 bn 的文章,有兴趣的同学可以查阅 《【模型推理】从部署的角度看 bn 和 in 算子》。

  这里主要聊一下 caffe 中的 bn 相比于其他框架有什么区别,以及 caffe 框架中对于 bn 的 cpu 和 gpu 的前向推理实现。

1、caffe bn 的特殊之处

  在我的文章《【模型推理】从部署的角度看 bn 和 in 算子》中有写过,整个 bn 算子的计算过程数学表达如下:

  基本过程是:

  (1) 求均值;

  (2) 求方差;

  (3) 归一化;

  (4) 缩放和偏置;

​ 其中,(1) 求均值 和 (2) 求方差的过程在训练的时候就做了,所以对于推理来说就只要加载离线的权重就好了。来说一下 caffe 中的 bn 有什么不一样,caffe 中的 bn 其实只做了 (3) 归一化,而 (4) 缩放和偏置 由 scale 算子来做,所以整个 bn 的计算在 caffe 里是需要 bn + scale 两个算子来实现的,这和其他框架如 pytorch 或 darknet 里是不一样的。

  来对比一下 netron 的网络结构会更加直观,先来看一下 pytorch 的 conv + bn + activation 块,可以看到三个算子是拆开的,比较有条理,bn 也是完整的 bn。

  来看一下 onnx 的样子,onnx 的算子粒度比较细,这里把 activation 拆开了,实际 bn 还是完整的 bn。

  再来看一下 darknet,可以看到 darknet 中的 bn 是默认写在 conv 里的,所以在 darknet 里 conv + bn + activation (请自动忽略这里的激活不是 relu,我随便找了个) 是一个融合大算子,由 batch_normalize 标志位来决定是否有 bn,同样这里的 bn 是完整的 bn。

  最后再来看一下 caffe 里的 conv + bn + activation 块,可以看到 caffe 里的情况不太一样,bn 会被拆成 BatchNorm + Scale,所以 conv + bn + activation 块在 caffe 里是 conv + bn + scale + activation。这里的 bn 不是完整的 bn,这就是 caffe 中的 bn 和 其他框架中的 bn 不一样的地方。

  再来深入看一下 caffe 里 bn 的参数,可以看到 bn 里有个参数 use_global_stats,这个参数的意思是值为真,就使用保存的 均值 和 方差,否则使用滑动平均计算新的 均值 和 方差,这里的 bn 其实只做归一化。

  来看一下 scale 的参数,看到 bias_term 这个参数的时候,也许你会恍然大悟,一般的 scale 只做缩放,带 bias 的 scale 会做 缩放 + 偏置,当把这个和上面 bn 的归一化过程结合起来,其实就是一个完整的 bn 过程。

  下面结合 caffe 源码说一下。


2、caffe bn forward_cpu 实现

  先看 caffe.proto 里的 BatchNormParameter,源码里注释很多,为了看起来简洁,我这里把注释扔了,如下:

message BatchNormParameter 
  optional bool use_global_stats = 1;

  optional float moving_average_fraction = 2 [default = .999];
  
  optional float eps = 3 [default = 1e-5];

  可以看到有三个超参,其中 use_global_stats 上面提到过,用来定义是否直接使用离线 均值 和 方差 权重,moving_average_fraction 为滑动平均系数,如果 use_global_stats 为真,则用不上 moving_average_fraction,否则就用 moving_average_fraction 来更新均值。eps 是一个防止分母为零的超参。

  来看一下 bn 的头,我这里把命名空间 caffe 给省略了:

template <typename Dtype>
class BatchNormLayer : public Layer<Dtype> 
 public:
  explicit BatchNormLayer(const LayerParameter& param)
      : Layer<Dtype>(param) 
  virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top);
  virtual void Reshape(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top);

  virtual inline const char* type() const  return "BatchNorm"; 
  virtual inline int ExactNumBottomBlobs() const  return 1; 
  virtual inline int ExactNumTopBlobs() const  return 1; 

 protected:
  virtual void Forward_cpu(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top);
  virtual void Forward_gpu(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top);
  virtual void Backward_cpu(const vector<Blob<Dtype>*>& top,
      const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);
  virtual void Backward_gpu(const vector<Blob<Dtype>*>& top,
     const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);

  Blob<Dtype> mean_, variance_, temp_, x_norm_;
  bool use_global_stats_;
  Dtype moving_average_fraction_;
  int channels_;
  Dtype eps_;

  // extra temporarary variables is used to carry out sums/broadcasting
  // using BLAS
  Blob<Dtype> batch_sum_multiplier_;
  Blob<Dtype> num_by_chans_;
  Blob<Dtype> spatial_sum_multiplier_;
;

  BatchNormLayer 算子类里声明了几个关键的函数:Forward_cpu、Forward_gpu、Backward_cpu、Backward_gpu,其中 Backward 主要用于训练梯度反传,对于推理我们不关心。先来看一下 Forward_cpu,实现其实也比较简单,加载 均值 和 方差,按 use_global_stats_ 是否为真来分为两块,如果 为真,非常简单,直接加载 均值 和 方差,推理的话走这个 flow 就可以了;如果为否则用 滑动平均 先计算 均值,再根据这个均值来更新方差,这个一般用于训练的时候更新 均值 和 方差。

  先来说一下这个滑动平均更新 均值 和 方差,训练的过程并不是一次前向计算就结束,而是从总样本中抽取 mini-batch 个样本,进行多次前向计算,这样的话需要考虑每次计算得到的 均值 和 方差 怎么结合,caffe 里的算法并不是简简单单的将每次计算的 均值 和 方差 累加,而是把前一次计算的 均值 和 方差 的影响减小(乘以一个小于1的变量),再加上本次计算的结果,即所谓的 滑动平均更新方式。

  前向计算均值代码如下:

if (use_global_stats_) 
    // use the stored mean/variance estimates.
    const Dtype scale_factor = this->blobs_[2]->cpu_data()[0] == 0 ?
        0 : 1 / this->blobs_[2]->cpu_data()[0];
    caffe_cpu_scale(variance_.count(), scale_factor,
        this->blobs_[0]->cpu_data(), mean_.mutable_cpu_data());
    caffe_cpu_scale(variance_.count(), scale_factor,
        this->blobs_[1]->cpu_data(), variance_.mutable_cpu_data());
   else 
    // compute mean
    caffe_cpu_gemv<Dtype>(CblasNoTrans, channels_ * num, spatial_dim,
        1. / (num * spatial_dim), bottom_data,
        spatial_sum_multiplier_.cpu_data(), 0.,
        num_by_chans_.mutable_cpu_data());
    caffe_cpu_gemv<Dtype>(CblasTrans, num, channels_, 1.,
        num_by_chans_.cpu_data(), batch_sum_multiplier_.cpu_data(), 0.,
        mean_.mutable_cpu_data());

  然后根据这个计算出来的 均值 来计算 方差,整个代码就复杂在这里。

if (!use_global_stats_) 
  // compute variance using var(X) = E((X-EX)^2)
  caffe_sqr<Dtype>(top[0]->count(), top_data,
                   temp_.mutable_cpu_data());  // (X-EX)^2
  caffe_cpu_gemv<Dtype>(CblasNoTrans, channels_ * num, spatial_dim,
      1. / (num * spatial_dim), temp_.cpu_data(),
      spatial_sum_multiplier_.cpu_data(), 0.,
      num_by_chans_.mutable_cpu_data());
  caffe_cpu_gemv<Dtype>(CblasTrans, num, channels_, 1.,
      num_by_chans_.cpu_data(), batch_sum_multiplier_.cpu_data(), 0.,
      variance_.mutable_cpu_data());  // E((X_EX)^2)

  // compute and save moving average
  this->blobs_[2]->mutable_cpu_data()[0] *= moving_average_fraction_;
  this->blobs_[2]->mutable_cpu_data()[0] += 1;
  caffe_cpu_axpby(mean_.count(), Dtype(1), mean_.cpu_data(),
      moving_average_fraction_, this->blobs_[0]->mutable_cpu_data());
  int m = bottom[0]->count()/channels_;
  Dtype bias_correction_factor = m > 1 ? Dtype(m)/(m-1) : 1;
  caffe_cpu_axpby(variance_.count(), bias_correction_factor,
      variance_.cpu_data(), moving_average_fraction_,
      this->blobs_[1]->mutable_cpu_data());

  下面来看归一化的过程,减均值:

// subtract computed_mean
caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, num, channels_, 1, 1,
    batch_sum_multiplier_.cpu_data(), mean_.cpu_data(), 0.,
    num_by_chans_.mutable_cpu_data());
caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, channels_ * num,
    spatial_dim, 1, -1, num_by_chans_.cpu_data(),
    spatial_sum_multiplier_.cpu_data(), 1., top_data);

  这里怎么就实现了减均值的操作呢,来看一下 caffe_cpu_gemm 做了什么:

template<>
void caffe_cpu_gemm<float>(const CBLAS_TRANSPOSE TransA,
    const CBLAS_TRANSPOSE TransB, const int M, const int N, const int K,
    const float alpha, const float* A, const float* B, const float beta,
    float* C) 
  int lda = (TransA == CblasNoTrans) ? K : M;
  int ldb = (TransB == CblasNoTrans) ? N : K;
  cblas_sgemm(CblasRowMajor, TransA, TransB, M, N, K, alpha, A, lda, B,
      ldb, beta, C, N);

  caffe_cpu_gemm 的功能是矩阵乘,即 C=alpha*A*B+beta*C,其中 A,B,C 是输入矩阵(一维数组格式);CblasRowMajor : 数据是行主序的(二维数据也是用一维数组储存的);TransA, TransB:是否要对A和B做转置操作(CblasTrans CblasNoTrans);M: A、C 的行数;N: B、C 的列数;K: A 的列数, B 的行数;lda : A的列数(不做转置)行数(做转置);ldb: B的列数(不做转置)行数(做转置)。所以:

/// 原计算
caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, num, channels_, 1, 1,
    batch_sum_multiplier_.cpu_data(), mean_.cpu_data(), 0.,
    num_by_chans_.mutable_cpu_data());
/// 相当于
num_by_chans_.mutable_cpu_data() = batch_sum_multiplier_.cpu_data() *  mean_.cpu_data()

/// 原计算
caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, channels_ * num,
    spatial_dim, 1, -1, num_by_chans_.cpu_data(),
    spatial_sum_multiplier_.cpu_data(), 1., top_data);
/// 相当于
top_data = - num_by_chans_.cpu_data() * spatial_sum_multiplier_.cpu_data() + top_data

/// 而 top_data 是什么呢,之前源码里有一句
if (bottom[0] != top[0]) 
  caffe_copy(bottom[0]->count(), bottom_data, top_data);

  这样就比较清楚了。

  接下来给方差进行平滑,并开标准差:

// normalize variance
caffe_add_scalar(variance_.count(), eps_, variance_.mutable_cpu_data());
caffe_sqrt(variance_.count(), variance_.cpu_data(),
variance_.mutable_cpu_data());

  其中 caffe_add_scalar 的实现如下,就是给每个 Y 加上一个 alpha。结合我们这里就是给每个方差加上一个很小的 eps,防止它为 0。caffe_sqrt 就不用多说了,就是开方,所以经过了这波操作后就得到了标准差,存放在这里 variance_.cpu_data()。

template <>
void caffe_add_scalar(const int N, const float alpha, float* Y) 
  for (int i = 0; i < N; ++i) 
    Y[i] += alpha;
  

  最后做归一化,前面讲到有两个比较重要的存储数据的变量,减均值后 feature_map 数据放在这 top_data,标准差放在这 variance_.cpu_data(),带着这两个记忆来看代码:

// replicate variance to input size
caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, num, channels_, 1, 1,
batch_sum_multiplier_.cpu_data(), variance_.cpu_data(), 0.,
num_by_chans_.mutable_cpu_data());
caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, channels_ * num,
spatial_dim, 1, 1., num_by_chans_.cpu_data(),
spatial_sum_multiplier_.cpu_data(), 0., temp_.mutable_cpu_data());
caffe_div(temp_.count(), top_data, temp_.cpu_data(), top_data);
// TODO(cdoersch): The caching is only needed because later in-place layers
//                 might clobber the data.  Can we skip this if they won't?
caffe_copy(x_norm_.count(), top_data,
x_norm_.mutable_cpu_data());

  比较关键的是:

caffe_div(temp_.count(), top_data, temp_.cpu_data(), top_data);

  来看一下 caffe_div 的实现,即做 element-wise 的除法,所以很好理解,就是拿 减均值后的数据 除以 标准差,至此完成了归一化操作。

template <>
void caffe_div<float>(const int n, const float* a, const float* b,
    float* y) 
  vsDiv(n, a, b, y);

  caffe 源码中的 bn forward_cpu 到这里就结束了,可以看到只有归一化操作,没有缩放和偏置的操作,这也进一步验证了 caffe 中的 bn 是拆成 bn + scale 来做的。

3、caffe bn forward_cuda 实现

  高性能计算离不开 cuda,让我们一起来看下 caffe bn forward_cuda 的实现代码。其实逻辑很简单,我把整个代码贴上先:

template <typename Dtype>
void BatchNormLayer<Dtype>::Forward_gpu(const vector<Blob<Dtype>*>& bottom,
    const vector<Blob<Dtype>*>& top) 
  const Dtype* bottom_data = bottom[0]->gpu_data();
  Dtype* top_data = top[0]->mutable_gpu_data();
  int num = bottom[0]->shape(0);
  int spatial_dim = bottom[0]->count()/(channels_*bottom[0]->shape(0));

  if (bottom[0] != top[0]) 
    caffe_copy(bottom[0]->count(), bottom_data, top_data);
  

  if (use_global_stats_) 
    // use the stored mean/variance estimates.
    const Dtype scale_factor = this->blobs_[2]->cpu_data()[0] == 0 ?
        0 : 1 / this->blobs_[2]->cpu_data()[0];
    caffe_gpu_scale(variance_.count(), scale_factor,
        this->blobs_[0]->gpu_data(), mean_.mutable_gpu_data());
    caffe_gpu_scale(variance_.count(), scale_factor,
        this->blobs_[1]->gpu_data(), variance_.mutable_gpu_data());
   else 
    // compute mean
    caffe_gpu_gemv<Dtype>(CblasNoTrans, channels_ * num, spatial_dim,
        1. / (num * spatial_dim), bottom_data,
        spatial_sum_multiplier_.gpu_data(), 0.,
        num_by_chans_.mutable_gpu_data());
    caffe_gpu_gemv<Dtype>(CblasTrans, num, channels_, 1.,
        num_by_chans_.gpu_data(), batch_sum_multiplier_.gpu_data(), 0.,
        mean_.mutable_gpu_data());
  

  // subtract mean
  caffe_gpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, num, channels_, 1, 1,
      batch_sum_multiplier_.gpu_data(), mean_.gpu_data(), 0.,
      num_by_chans_.mutable_gpu_data());
  caffe_gpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, channels_ * num,
      spatial_dim, 1, -1, num_by_chans_.gpu_data(),
      spatial_sum_multiplier_.gpu_data(), 1., top_data);

  if (!use_global_stats_) 
    // compute variance using var(X) = E((X-EX)^2)
    caffe_gpu_mul(top[0]->count(), top[0]->gpu_data(), top[0]->gpu_data(),
        temp_.mutable_gpu_data());  // (X-EX)^2
    caffe_gpu_gemv<Dtype>(CblasNoTrans, channels_ * num, spatial_dim,
        1. / (num * spatial_dim), temp_.gpu_data(),
        spatial_sum_multiplier_.gpu_data(), 0.,
        num_by_chans_.mutable_gpu_data());
    caffe_gpu_gemv<Dtype>(CblasTrans, num, channels_, Dtype(1.),
        num_by_chans_.gpu_data(), batch_sum_multiplier_.gpu_data(), Dtype(0.),
        variance_.mutable_gpu_data());  // E((X_EX)^2)

    // compute and save moving average
    this->blobs_[2]->mutable_cpu_data()[0] *= moving_average_fraction_;
    this->blobs_[2]->mutable_cpu_data()[0] += 1;
    caffe_gpu_axpby(mean_.count(), Dtype(1), mean_.gpu_data(),
        moving_average_fraction_, this->blobs_[0]->mutable_gpu_data());
    int m = bottom[0]->count()/channels_;
    Dtype bias_correction_factor = m > 1 ? Dtype(m)/(m-1) : 1;
    caffe_gpu_axpby(variance_.count(), bias_correction_factor,
        variance_.gpu_data(), moving_average_fraction_,
        this->blobs_[1]->mutable_gpu_data());
  

  // normalize variance
  caffe_gpu_add_scalar(variance_.count(), eps_, variance_.mutable_gpu_data());
  caffe_gpu_sqrt(variance_.count(), variance_.gpu_data(),
      variance_.mutable_gpu_data());

  // replicate variance to input size
  caffe_gpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, num, channels_, 1, 1,
      batch_sum_multiplier_.gpu_data(), variance_.gpu_data(), 0.,
      num_by_chans_.mutable_gpu_data());
  caffe_gpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, channels_ * num,
      spatial_dim, 1, 1., num_by_chans_.gpu_data(),
      spatial_sum_multiplier_.gpu_data(), 0., temp_.mutable_gpu_data());
  caffe_gpu_div(temp_.count(), top_data, temp_.gpu_data(), top_data);
  // TODO(cdoersch): The caching is only needed because later in-place layers
  //                 might clobber the data.  Can we skip this if they won't?
  caffe_copy(x_norm_.count(), top_data,
      x_norm_.mutable_gpu_data());

  我们来看一下 bn forward_gpu 和 forward_cpu 有什么区别,逻辑上可以说是一毛一样,函数传参也是一毛一样,拿 beyond compare 简单直观对比一下,你看区别都是些啥,caffe_gpu_scale、caffe_gpu_gemv、caffe_gpu_gemm…

  所以我觉得 caffe bn forward_gpu 代码没啥好说的,参考 forward_cpu 就可以。


  收工了,有问题欢迎讨论~


 【公众号传送】

《谈谈 caffe 的 bn 和 scale 算子》



扫描下方二维码即可关注我的微信公众号【极智视界】,获取更多AI经验分享,让我们用极致+极客的心态来迎接AI !

模型推理从部署的角度看bn和in算子(代码片段)

 欢迎关注我的公众号[极智视界],获取我的更多笔记分享 O_o >_< o_O O_o ~_~ o_O 本文介绍一下从部署角度来看bn和in的实现与对比。 做深度学习的同学应该都接触过残差网络(Resnet)这个经典且实用的网络,一... 查看详情

模型推理谈谈darknetyolo的route算子(代码片段)

 欢迎关注我的公众号[极智视界],获取我的更多笔记分享 O_o >_< o_O O_o ~_~ o_O 本文聊一聊darknetyolo网络中的route算子。 yolo是目标检测算法落地常用的网络,具有速度快、精度高的优点,相信很多同学... 查看详情

极智ai|谈谈caffe框架(代码片段)

...力硬件),这些“自研推理框架”大多会优先支持caffe模型的导入。理由可能也比较简单:(1)具备规划推理框架的“大佬”,可能最开始就是用caffe的,对此会比较熟悉;(2)caffe算子定义清晰,使用googleprotobuf,... 查看详情

模型推理谈谈为什么卷积加速更喜欢nhwclayout(代码片段)

 本文主要讨论一下为什么卷积加速更加喜欢NHWC的数据排布。 我目前接触过的数据排布类型(主要针对卷积)有NCHW(pytorch、caffe),NHWC(Tensorflow,也是TVMGPU和寒武纪MLUCore上更喜欢的dataLayout),CHW(TensorRT里不考虑动态batch... 查看详情

caffe中为啥bn层要和scale层一起使用

...过迭代学习的。那么caffe中的bn层其实只做了第一件事,scale层做了第二件事,所以两者要一起使用。一,在Caffe中使用BatchNormalization需要注意以下两点:1.要配合Scale层一起使用。2. 训练的时候,将BN层的use_global_stats设置为fals... 查看详情

模型推理谈谈为什么量化能加速推理(代码片段)

... o_O O_o ~_~ o_O 本文主要讨论一下为什么量化能加速模型推理。 前面已经写过几篇关于模型量化相关的文章:《【模型推理】谈谈几种量化策略:MinMax、KLD、ADMM、EQ》、《【模型推理】谈谈模型量化组织方式》、... 查看详情

模型推理谈谈模型量化组织方式(代码片段)

...O_o >_< o_O O_o ~_~ o_O 本文主要聊一下深度学习模型量化组织方式。 在我的这篇《【模型推理】谈谈推理引擎的推理组织流程》文章里对模型量化策略进行了一些介绍,有兴趣的同学可以翻看一下。今天这里主要... 查看详情

模型推理谈谈非线性激活函数的量化方式(代码片段)

...O_o >_< o_O O_o ~_~ o_O 本文主要聊一聊深度学习模型量化中对激活函数的处理方式。 之前已经写过几篇关于模型量化的文章:《【模型推理】谈谈几种量化策略:MinMax、KLD、ADMM、EQ》、《【模型推理】谈谈模... 查看详情

模型推理教你tensorrt实现mish算子(代码片段)

 欢迎关注我的公众号[极智视界],获取我的更多笔记分享 O_o >_< o_O O_o ~_~ o_O 本文介绍了使用tensorrt实现mish算子的方法。 相信做过目标检测的同学对yolo肯定比较熟悉了,yolov4是2020年初提出的,相继... 查看详情

模型推理教你简化onnxupsample算子(代码片段)

... 实际部署中经常会涉及到pytorch/tensorflow/darknet->onnx的模型转换过程。本身模型转换过程就比较麻烦(当然pytorchexportonnx十分方便)&#x 查看详情

模型推理谈谈推理引擎的推理组织流程

 本文主要讨论一下推理引擎的推理组织流程,包括英伟达tensorrt、华为CANN以及TVM。 对于用户和大多开发者来说,其实不用太关心推理引擎内部是怎么实现推理的,比如你在使用tensorrt的时候你只要知道使用流程... 查看详情

理论+实践,揭秘昇腾cann算子开发(代码片段)

...作者:昇腾CANN。开发者在利用昇腾硬件进行神经网络模型训练或者推理的过程中,可能会遇到以下场景:训练场景下,将第三方框架(例如TensorFlow、PyTorch等)的网络训练脚本迁移到昇腾AI处理器时遇到了... 查看详情

神经网络推理加速:合并卷积和bn层运算原理及实验(代码片段)

1. 为什么要合并BN层在训练深度网络模型时,BN(BatchNormalization)层能够加速网络收敛,并且能够控制过拟合,一般放在卷积层之后。BN层将数据归一化后,能够有效解决梯度消失与梯度爆炸问题。虽然BN层... 查看详情

markdownatlas300实验:模型验证caffe_npu推理误差(代码片段)

查看详情

模型推理谈谈几种量化策略:minmaxkldadmmeq(代码片段)

...O_o >_< o_O O_o ~_~ o_O 本文主要聊一下深度学习模型量化相关策略。 模型小型化是算法部署的关键技术,模型小型化的过程通常用模型量化来描述。量化通常是高比特位到低比特位的映射过程,量化的对象既... 查看详情

神经网络推理加速:合并卷积和bn层运算原理及实验(代码片段)

1. 为什么要合并BN层在训练深度网络模型时,BN(BatchNormalization)层能够加速网络收敛,并且能够控制过拟合,一般放在卷积层之后。BN层将数据归一化后,能够有效解决梯度消失与梯度爆炸问题。虽然BN层... 查看详情

模型推理聊一聊昇腾canntbe算子开发方式(代码片段)

 欢迎关注我的公众号[极智视界],获取我的更多笔记分享 O_o >_< o_O O_o ~_~ o_O 本文主要聊一聊华为昇腾CANNTBE开发方式。 之前也写过几篇关于昇腾部署相关的文章,在做昇腾卡部署或有兴趣的同学可以查... 查看详情

batchnormalization的正确打开方式(代码片段)

...,gamma一般设定为可训练参数,即trainable=True。training表示模型当前的模式,如果为True,则模型在训练模式,否则为推理模式。要非常注意这个模式的设定,这个参数默认值为False。如果在训练时采用了默认值False,则滑动均值movi... 查看详情