【caffe源码研究】第四章:完整案例源码篇(5) :LeNet反向过程

来源:互联网 发布:网络竞技游戏排行 编辑:程序博客网 时间:2024/04/29 03:36

本部分剖析Caffe中Net::Backward()函数,即反向传播计算过程。从LeNet网络角度出发,且调试网络为训练网络,共9层网络。

入口信息

Net::Backward()函数中调用BackwardFromTo函数,从网络最后一层到网络第一层反向调用每个网络层的Backward。

void Net<Dtype>::BackwardFromTo(int start, int end) {  for (int i = start; i >= end; --i) {    if (layer_need_backward_[i]) {      layers_[i]->Backward(          top_vecs_[i], bottom_need_backward_[i], bottom_vecs_[i]);      if (debug_info_) { BackwardDebugInfo(i); }    }  }}

第九层 SoftmaxWithLossLayer

代码实现如下:

void SoftmaxWithLossLayer<Dtype>::Backward_gpu(const vector<Blob<Dtype>*>& top,    const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {    // bottom_diff shape:64*10    Dtype* bottom_diff = bottom[0]->mutable_gpu_diff();    // prob_data shape:64*10    const Dtype* prob_data = prob_.gpu_data();    // top_data shape:(1)    const Dtype* top_data = top[0]->gpu_data();    // 将Softmax层预测的结果prob复制到bottom_diff中    caffe_gpu_memcpy(prob_.count() * sizeof(Dtype), prob_data, bottom_diff);    // label shape:64*1    const Dtype* label = bottom[1]->gpu_data();    // dim = 640 / 64 = 10    const int dim = prob_.count() / outer_num_;    // nthreads = 64 / 1 = 64    const int nthreads = outer_num_ * inner_num_;    // Since this memory is never used for anything else,    // we use to to avoid allocating new GPU memory.    Dtype* counts = prob_.mutable_gpu_diff();    // 该函数将bottom_diff(此时为每个类的预测概率)对应的正确类别(label)的概率值-1,其他数据没变。见公式推导。    SoftmaxLossBackwardGPU<Dtype><<<CAFFE_GET_BLOCKS(nthreads),        CAFFE_CUDA_NUM_THREADS>>>(nthreads, top_data, label, bottom_diff,        outer_num_, dim, inner_num_, has_ignore_label_, ignore_label_, counts);    // 代码展开开始,代码有修改    __global__ void SoftmaxLossBackwardGPU(...) {      CUDA_KERNEL_LOOP(index, nthreads) {         const int label_value = static_cast<int>(label[index]);        bottom_diff[index * dim + label_value] -= 1;        counts[index] = 1;              }    }    // 代码展开结束    Dtype valid_count = -1;    // 注意为loss的权值,对该权值(一般为1或者0)归一化(除以64)    // Scale gradient    const Dtype loss_weight = top[0]->cpu_diff()[0];    if (normalize_) {      caffe_scal(prob_.count(), loss_weight / count, bottom_diff);    } else {      caffe_scal(prob_.count(), loss_weight / outer_num_, bottom_diff);    }}

说明:

  • SoftmaxWithLossLayer是没有学习参数的,因此不需要对该层的参数做调整,只需要计算bottom_diff(理解反向传播算法的链式求导,求bottom_diff对上一层的输出求导,是为了进一步计算调整上一层权值)
  • 以上代码核心部分在SoftmaxLossBackwardGPU。该函数将bottom_diff(此时为每个类的预测概率)对应的正确类别(label)的概率值-1,其他数据没变。

这里写图片描述

这里写图片描述

第八层 InnerProduct

template <typename Dtype>void InnerProductLayer<Dtype>::Backward_gpu(const vector<Blob<Dtype>*>& top,    const vector<bool>& propagate_down,    const vector<Blob<Dtype>*>& bottom) {  //对参数求偏导,top_diff*bottom_data=blobs_diff  // 注意,此处(Dtype)1., this->blobs_[0]->mutable_gpu_diff()  // 中的(Dtype)1.:使得在一个solver的iteration中的多个iter_size  // 的梯度没有清零,而得以累加  if (this->param_propagate_down_[0]) {    const Dtype* top_diff = top[0]->gpu_diff();    const Dtype* bottom_data = bottom[0]->gpu_data();    // Gradient with respect to weight    caffe_gpu_gemm<Dtype>(CblasTrans, CblasNoTrans, N_, K_, M_, (Dtype)1.,        top_diff, bottom_data, (Dtype)1., this->blobs_[0]->mutable_gpu_diff());  }  // 对偏置求偏导top_diff*bias=blobs_diff  if (bias_term_ && this->param_propagate_down_[1]) {    const Dtype* top_diff = top[0]->gpu_diff();    // Gradient with respect to bias    caffe_gpu_gemv<Dtype>(CblasTrans, M_, N_, (Dtype)1., top_diff,        bias_multiplier_.gpu_data(), (Dtype)1.,        this->blobs_[1]->mutable_gpu_diff());  }  //对上一层输出求偏导top_diff*blobs_data=bottom_diff  if (propagate_down[0]) {    const Dtype* top_diff = top[0]->gpu_diff();    // Gradient with respect to bottom data    caffe_gpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, M_, K_, N_, (Dtype)1.,        top_diff, this->blobs_[0]->gpu_data(), (Dtype)0.,        bottom[0]->mutable_gpu_diff());  }}

这里写图片描述

第七层 ReLU

cpu代码分析如下,注,该层没有参数,只需对输入求导

void ReLULayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,    const vector<bool>& propagate_down,    const vector<Blob<Dtype>*>& bottom) {  if (propagate_down[0]) {    const Dtype* bottom_data = bottom[0]->cpu_data();    const Dtype* top_diff = top[0]->cpu_diff();    Dtype* bottom_diff = bottom[0]->mutable_cpu_diff();    const int count = bottom[0]->count();    //见公式推导    Dtype negative_slope = this->layer_param_.relu_param().negative_slope();    for (int i = 0; i < count; ++i) {      bottom_diff[i] = top_diff[i] * ((bottom_data[i] > 0)          + negative_slope * (bottom_data[i] <= 0));    }  }}

公式推导

这里写图片描述

第五层 Pooling

Maxpooling的cpu代码分析如下,注,该层没有参数,只需对输入求导

void PoolingLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,      const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {  const Dtype* top_diff = top[0]->cpu_diff();  Dtype* bottom_diff = bottom[0]->mutable_cpu_diff();  // bottom_diff初始化置0  caffe_set(bottom[0]->count(), Dtype(0), bottom_diff);  const int* mask = NULL;  // suppress warnings about uninitialized variables  ...    // 在前向计算时max_idx中保存了top_data中的点是有bottom_data中的点得来的在该feature map中的坐标    mask = max_idx_.cpu_data();    // 主循环,按(N,C,H,W)方式便利top_data中每个点    for (int n = 0; n < top[0]->num(); ++n) {      for (int c = 0; c < channels_; ++c) {        for (int ph = 0; ph < pooled_height_; ++ph) {          for (int pw = 0; pw < pooled_width_; ++pw) {            const int index = ph * pooled_width_ + pw;            const int bottom_index = mask[index];            // 见公式推导            bottom_diff[bottom_index] += top_diff[index];          }        }        bottom_diff += bottom[0]->offset(0, 1);        top_diff += top[0]->offset(0, 1);        mask += top[0]->offset(0, 1);      }    }}

这里写图片描述

第四层 Convolution

void ConvolutionLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,      const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {  const Dtype* weight = this->blobs_[0]->cpu_data();  Dtype* weight_diff = this->blobs_[0]->mutable_cpu_diff();  for (int i = 0; i < top.size(); ++i) {    const Dtype* top_diff = top[i]->cpu_diff();    const Dtype* bottom_data = bottom[i]->cpu_data();    Dtype* bottom_diff = bottom[i]->mutable_cpu_diff();    // Bias gradient, if necessary.    if (this->bias_term_ && this->param_propagate_down_[1]) {      Dtype* bias_diff = this->blobs_[1]->mutable_cpu_diff();      // 对于每个Batch中的样本,计算偏置的偏导      for (int n = 0; n < this->num_; ++n) {        this->backward_cpu_bias(bias_diff, top_diff + n * this->top_dim_);      }    }    if (this->param_propagate_down_[0] || propagate_down[i]) {      // 对于每个Batch中的样本,关于权值及输入求导部分代码展开了函数(非可运行代码)      for (int n = 0; n < this->num_; ++n) {        // gradient w.r.t. weight. Note that we will accumulate diffs.        //top_diff(50*64) * bottom_data(500*64,Transpose) = weight_diff(50*500)        // 注意,此处(Dtype)1., this->blobs_[0]->mutable_gpu_diff()        // 中的(Dtype)1.:使得在一个solver的iteration中的多个iter_size        // 的梯度没有清零,而得以累加        caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasTrans, conv_out_channels_ / group_,          kernel_dim_, conv_out_spatial_dim_,          (Dtype)1., top_diff + n * this->top_dim_, bottom_data + n * this->bottom_dim_,          (Dtype)1., weight_diff);        // gradient w.r.t. bottom data, if necessary.        // weight(50*500,Transpose) * top_diff(50*64) = bottom_diff(500*64)        caffe_cpu_gemm<Dtype>(CblasTrans, CblasNoTrans, kernel_dim_,          conv_out_spatial_dim_, conv_out_channels_ ,          (Dtype)1., weight, top_diff + n * this->top_dim_,          (Dtype)0., bottom_diff + n * this->bottom_dim_);      }    }  }}
  • 第四层的bottom维度(N,C,H,W)=(64,20,12,12),top的维度bottom维度(N,C,H,W)=(64,50,8,8),由于每个样本单独处理,所以只需要关注(C,H,W)的维度,分别为(20,12,12)和(50,8,8)
  • 根据(Caffe)卷积的实现,该层可以写成矩阵相乘的形式Weight_data×Bottom_dataT=Top_data
  • Weight_data的维度为Cout×(C∗K∗K)=50×500
  • Bottom_data的维度为(H∗W)×(C∗K∗K)=64×500,64为8∗8个卷积核的位置,500=C∗K∗K=20∗5∗5
    Top_data的维度为64×50
  • 写成矩阵表示后,从某种角度上与全连接从(也是表示成矩阵相乘)相同,因此,可以借鉴全连接层的推导。
0 0
原创粉丝点击