最近做项目需要修改Euclidean loss函数,所以就先分析了一下Euclidean loss layer的代码

以gpu版本的为例:

一. 前向函数

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); //除以总数再除以2top[0]->mutable_cpu_data()[0] = loss; //将loss值赋给输出
}

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()函数,如下:

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文档中看到该函数的作用:

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

4.最后就是除以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(),              // countalpha,                              // alphadiff_.gpu_data(),                   // aDtype(0),                           // betabottom[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*Ycaffe_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)

caffe 源码分析:Euclidean loss layer相关推荐

  1. caffe源码分析-layer

    本文主要分析caffe layer层,主要内容如下: 从整体上说明下caffe的layer层的类别,以及作用 通过proto定义与类Layer简要说明下Layer的核心成员变量; Layer类的核心成 ...

  2. caffe源码分析--SyncedMemory 内存管理机制

    caffe源码分析–SyncedMemory 内存管理机制 ​ SyncedMemory 是caffe中用来管理内存分配和CPU.GPU数据及同步的类,只服务于Blob类.SyncedMemory 对 ...

  3. 【AI】caffe源码分析(一)

    [一]caffe依赖开源库 [C++]google gflags详解 [C++]google glog详解 [C++]Google Protocol Buffer(protobuf)详解(一) [C+ ...

  4. 【Dual-Path-RNN-Pytorch源码分析】loss函数:SI-SNR

    DPRNN使用的loss函数是 SI-SNR SI-SNR 是scale-invariant source-to-noise ratio的缩写,中文翻译为尺度不变的信噪比,意思是不受信号变化影响的信噪 ...

  5. caffe源码分析:layer.hpp分析

    文件路径:caffe-master_github/include/caffe/ Backward函数: template <typename Dtype> inline void Laye ...

  6. caffe源码分析:softmax_layer.cpp softmax_loss_layer.cpp

    本文仅分析了softmax_layer.cpp 和 softmax_loss_layer.cpp两个文件中的forward函数,backward函数有待补充. 1.softmax_layer.cpp ...

  7. caffe源码分析:blob.hpp分析

    文件路径:caffe-master_github/include/caffe/ 如果想对blob有详细了解,参考Caffe官网教程:http://caffe.berkeleyvision.org/tu ...

  8. Caffe源码中layer文件分析

    Caffe源码(caffe version commit: 09868ac , date: 2015.08.15)中有一些重要的头文件,这里介绍下include/caffe/layer.hpp文件的内 ...

  9. Caffe源码中Pooling Layer文件分析

    Caffe源码(caffe version commit: 09868ac , date: 2015.08.15)中有一些重要的头文件,这里介绍下include/caffe/vision_layers ...

最新文章

  1. html5 ar开发,HTML5 WebAR开发
  2. iPhone4 FaceTime 联通官方教程
  3. C++:28 --- C++内存布局(上)
  4. 嵌入式linux文件系统
  5. appender log4j 扩展_Log4j扩展使用--输出地Appender
  6. JavaScript--变量、作用域及内存(12)
  7. esp8266 html文件,ESP8266 基ESP8266_RTOS_SDK (ESP-IDF )中嵌入网页文件(示例代码)
  8. logStash收集日志并存储到Elasticsearch
  9. 【GNN综述】图神经网络的解释性综述
  10. 无法上网之NOD32
  11. 锐起无盘服务器陈列设置,原创]锐起无盘安装全图文设置,含SCII设置在内
  12. 编程心得分享,送给刚入门学编程的小伙伴
  13. 俄勒冈州立大学计算机科学专业,俄勒冈州立大学电气工程与计算机科学专业介绍在这里哦!...
  14. java通过struts实现web中的文件上传
  15. 三元简化模型,助你加速团队成长
  16. golang zip压缩/解压缩用法
  17. python中文词频排序_python统计词频并排序
  18. STM32-(33):低功耗模式与唤醒
  19. 字体属性-参数图解-基线
  20. 流媒体流媒体ffmpeg_游戏流媒体服务将面临与流媒体电视相同的问题

热门文章

  1. linux内核论文分析,毕业论文LINUX内核分析.doc
  2. HttpServletResponse响应图片,文字
  3. 双目是个词吗_什么双目的四字词语
  4. No.179 念念随风上九霄
  5. 职称计算机考科目代码表,职称计算机考试科目
  6. python根据经纬度转换详细地址_如何将经纬度转换为街道地址
  7. 微信小程序---小程序中引入的echarts在滑动屏幕时抖动以及不跟随scroll滑动问题
  8. PHP检测及判断手机登录用户是安卓或爱疯(iPhone)客户端
  9. 计算理论中的莱斯定理(Rice's Theorem)——证明与应用
  10. android打开volte代码,Android8.1 源码修改之插入SIM卡默认启用Volte功能