caffe 源码分析【三】:Euclidean loss layer

以下是Euclidean loss layer的代码分析,转自:

https://blog.csdn.net/seashell_9/article/details/68064294

一. 前向函数

template <typename Dtype>
void EuclideanLossLayer<Dtype>::Forward_gpu(const vector<Blob<Dtype>*>& bottom,
    const vector<Blob<Dtype>*>& top) {
  int count = bottom[0]->count(); //这里的count就是你的batchsize的大小
  caffe_gpu_sub(
      count,                 
      bottom[0]->gpu_data(), //网络的输出值
      bottom[1]->gpu_data(), //标签值
      diff_.mutable_gpu_data());//存储bottom[0] - bottom[1]
  Dtype dot;
  caffe_gpu_dot(count, diff_.gpu_data(), diff_.gpu_data(), &dot);//做点乘运算
  Dtype loss = dot / bottom[0]->num() / Dtype(2); //除以总数再除以2
  top[0]->mutable_cpu_data()[0] = loss; //将loss值赋给输出
}

1.Euclidean loss函数

首先明确caffe中Euclidean loss函数:(多除了个2,方便后面求梯度时刚好约掉)

其次是代码里面一些变量的意义:

count: count其实等于num*channels*height*width,也就是整个Blob元素的数量,但是因为此层中channels height width都为1,所以这里的count()与num()实际上是相等的,都代表输入图片的数量,也就是batchsize的大小,也即公式里的N

bottom[0]->gpu_data(): 网络的输出值,注意这个变量并不只是单个的输出值,而是包含整个batchsize每张图片的输出值,也就是一个含有N个元素的向量

bottom[1]->gpu_data(): 真实标签值,也是个含有N个元素的向量

diff_.mutable_gpu_data(): 上述两个向量做元素减法得到的向量。这里说明一下为什么是diff_.mutable_gpu_data()而不是diff_.gpu_data(),因为caffe定义了gpu_data()  为只读变量,而mutable_gpu_data()为可变变量,也就是说读操作用gpu_data(),写操作用mutable_gpu_data()

dot: 对diff_.gpu_data()进行点乘运算得到的值(点乘运算得到的是一个数值)

top[0]->mutable_cpu_data()[0]:该层的输出给下一层的变量。这里top[0]的“0”指的是第一个输出值(就像上面bottom[0]指第一个输入值,bottom[1]指第二个输入值),由于这个层只有一个输出值,因此也就只有0这个索引

2.caffe_gpu_sub()函数

在math_function.cu里可以查到caffe_gpu_sub()函数,如下:

template <>
void caffe_gpu_sub<float>(const int N, const float* a, const float* b,
    float* y) {
  // NOLINT_NEXT_LINE(whitespace/operators)
  sub_kernel<float><<<CAFFE_GET_BLOCKS(N), CAFFE_CUDA_NUM_THREADS>>>(
      N, a, b, y);
}

可以看到,该函数又调用了sub_kernel函数:

template <typename Dtype>
__global__ void sub_kernel(const int n, const Dtype* a,
    const Dtype* b, Dtype* y) {
  CUDA_KERNEL_LOOP(index, n) {
    y[index] = a[index] - b[index];
  }
}

从这个函数就可以明白了,caffe_gpu_sub()就是做了这样一个运算:把a向量和b向量对应元素相减,然后赋给y向量。放到我们Euclidean loss代码里面,也就是bottom[0]->gpu_data()(网络输出值)和bottom[1]->gpu_data()(真实标签值)做对应元素相减,然后赋给diff_.mutable_gpu_data()
3.caffe_gpu_dot()函数:

template <>
void caffe_gpu_dot<float>(const int n, const float* x, const float* y,
    float* out) {
  CUBLAS_CHECK(cublasSdot(Caffe::cublas_handle(), n, x, 1, y, 1, out));
}

可以看到调用了cublasSdot()函数,在cuda文档中看到该函数的作用:

即计算两个输入向量的点乘。最后就是除以2N了

二.反向函数

template <typename Dtype>
void EuclideanLossLayer<Dtype>::Backward_gpu(const vector<Blob<Dtype>*>& top,
    const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {
  for (int i = 0; i < 2; ++i) {
    if (propagate_down[i]) {    //对于输入的第i个Blob propagate_dowm 为1(该变量即为该Blob输入后是否要向前面的层提供反向传播的梯度)
      const Dtype sign = (i == 0) ? 1 : -1; 
      const Dtype alpha = sign * top[0]->cpu_diff()[0] / bottom[i]->num();
      caffe_gpu_axpby(
          bottom[i]->count(),              // count
          alpha,                              // alpha
          diff_.gpu_data(),                   // a
          Dtype(0),                           // beta
          bottom[i]->mutable_gpu_diff());  // b
    }
  }
}

1.因为loss层没有参数,所以求导时是对两个输入求偏导,即:

Derive (X1) = [(x11-x21)+…+(x1n-x2n)]/N

Derive (X2) = -[(x11-x21)+…+(x1n-x2n)]/N(注意前面有个负号)

所以代码中for循环两次就是分别对X1和X2求偏导

2.propagate_down[i]:这里两个propagate_down都是1的。如果要了解propagate_down的含义,可以看来自百度的一个描述:

caffe中怎么固定前面的网络参数,训练后面层的参数? 
这里面就用到了propagate_down, 有两种情况:比如有4个全连接层A->B->C->D 
a. 你希望C层的参数不会改变,C前面的AB层的参数也不会改变,这种情况也就是D层的梯度不往前反向传播到D层的输入blob(也就是C层的输出blob 没有得到梯度),你可以通过设置D层的propagate_down为false来做到。 

propagate_down的数量与输入blob的数量相同,假如你某个层有2个输入blob,那么你应该在该layer的Param里面写上两行: 

propagate_down : 0 # 第1个输入blob不会得到反向传播的梯度 
propagate_down : 0 # 第2个输入blob不会得到反向传播的梯度 
这样的话,你这个layer的梯度就不会反向传播啦,前面的所有layer的参数也就不会改变了 
b. 你希望C层的参数不会改变,但是C前面的AB层的参数会改变,这种情况,只是固定了C层的参数,C层得到的梯度依然会反向传播给前面的B层。只需要将对应的参数blob的学习率调整为0: 

layer { 
type: "InnerProduct" 
    param { # 对应第1个参数blob的配置,也就是全连接层的参数矩阵的配置 
         lr_mult: 0 # 学习率为0,其他参数可以看caffe.proto里面的ParamSpec这个类型 
    } 
    param { # 对应第2个参数blob的配置,也就是全连接层的偏置项的配置 
        lr_mult: 0 # 学习率为0 
    } 
} 

3.sign的作用:

   第一次求偏导是对X1,前面不需要加负号;第二次求偏导是对X2,前面需要乘-1

4.top[0]->cpu_diff()[0]:

在反向传播中,top代表从高一层反向传过来的变量,所以top[0]->cpu_diff()表示从高一层传过来的error。但问题来了,这明明是loss层,也就是最后一层,为什么还有所谓的再高一层呢?其实大家可以发现,这里用的是top[0]->cpu_diff()[0],而不是top[0]->cpu_diff()。caffe中反向传给低层error时其实用户还可以给这个error乘以一个倍数,这个倍数就存储在top[0]->cpu_diff()的第一个元素,也就是top[0]->cpu_diff()[0]。而用户设置这个倍数则是通过在layer参数中添加loss_weight参数

layer {
  name: "loss"
  type: "SoftmaxWithLoss"
  bottom: "pred"
  bottom: "label"
  top: "loss"
  loss_weight: 1
}

默认的loss_weight都是1

参见:http://stackoverflow.com/questions/31099233/euclidean-loss-layer-in-caffe

5.caffe_gpu_axpby()函数:

template <>
void caffe_gpu_axpby<float>(const int N, const float alpha, const float* X,
    const float beta, float* Y) {
  caffe_gpu_scal<float>(N, beta, Y); // Y = beta*Y
  caffe_gpu_axpy<float>(N, alpha, X, Y);// Y = Y + alpha*X
}

做的运算其实就是bottom[i]->mutable_gpu_diff() = alpha*diff.gpu_data() + beta*bottom[i]->mutable_gpu_diff() = alpha*diff.gpu_data() (因为beta = 0)
 

猜你喜欢

转载自blog.csdn.net/xiaoxu2050/article/details/82880547