caffe 源码分析:Euclidean loss layer

原文地址:https://blog.csdn.net/seashell_9/article/details/68064294

Euclidean loss layer的代码分析

以gpu版本的为例:


一. 前向函数

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

1.首先明确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()函数,如下:

[cpp]  view plain  copy
  1. template <>  
  2. void caffe_gpu_sub<float>(const int N, const float* a, const float* b,  
  3.     float* y) {  
  4.   // NOLINT_NEXT_LINE(whitespace/operators)  
  5.   sub_kernel<float><<<CAFFE_GET_BLOCKS(N), CAFFE_CUDA_NUM_THREADS>>>(  
  6.       N, a, b, y);  
  7. }  
 可以看到,该函数又调用了sub_kernel函数:
[cpp]  view plain  copy
  1. template <typename Dtype>  
  2. __global__ void sub_kernel(const int n, const Dtype* a,  
  3.     const Dtype* b, Dtype* y) {  
  4.   CUDA_KERNEL_LOOP(index, n) {  
  5.     y[index] = a[index] - b[index];  
  6.   }  
  7. }  

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

3.查看caffe_gpu_dot()函数:

[cpp]  view plain  copy
  1. template <>  
  2. void caffe_gpu_dot<float>(const int n, const float* x, const float* y,  
  3.     float* out) {  
  4.   CUBLAS_CHECK(cublasSdot(Caffe::cublas_handle(), n, x, 1, y, 1, out));  
  5. }  
 可以看到调用了cublasSdot()函数,在cuda文档中看到该函数的作用:

即计算两个输入向量的点乘

4.最后就是除以2N了


二.反向函数


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


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: 

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

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参数,如:

[python]  view plain  copy
  1. layer {  
  2.   name: "loss"  
  3.   type: "SoftmaxWithLoss"  
  4.   bottom: "pred"  
  5.   bottom: "label"  
  6.   top: "loss"  
  7.   loss_weight: 1  
  8. }  

默认的loss_weight都是1

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


5.接下来就是一个caffe_gpu_axpby()函数:

[cpp]  view plain  copy
  1. template <>  
  2. void caffe_gpu_axpby<float>(const int N, const float alpha, const float* X,  
  3.     const float beta, float* Y) {  
  4.   caffe_gpu_scal<float>(N, beta, Y); // Y = beta*Y  
  5.   caffe_gpu_axpy<float>(N, alpha, X, Y);// Y = Y + alpha*X  
  6. }  
做的运算其实就是bottom[i]->mutable_gpu_diff() = alpha*diff.gpu_data() + beta*bottom[i]->mutable_gpu_diff() = alpha*diff.gpu_data() (因为beta = 0)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值