本文地址:http://blog.csdn.net/ismarvellous/article/details/79069661,转载请注明出处。

本文涉及的所有完整文件可在我的github下载。

1. L1 Loss的计算推导

与欧式距离(L2 Loss)相似,L1 Loss也是两个输入向量直接距离的一种度量。但L2 Loss的梯度在接近零点的时候梯度值也会接近于0,使学习进程变慢,而L1 Loss的梯度是一个常数,不存在这个问题。L1 Loss 和 L2 Loss 还有一些不同的特点,各有使用的场合,不过这不是本文的重点。本文主要关注如何在caffe中实现 L1 Loss。
L1 Loss的前向和后向都比较简单,下面简单概括一下。

1.1 前向计算

L1 Loss的前向计算就是两个输入向量 x 1 , x 2 \mathbf x_1, \mathbf x_2 x1​,x2​的L1距离,具体地:
L = 1 N ∑ i N ∣ ∣ x 1 ( i ) − x 2 ( i ) ∣ ∣ 1 \mathcal L = \frac{1}{N} \sum_i^N ||\mathbf x_1^{(i)} - \mathbf x_2^{(i)}||_1 L=N1​i∑N​∣∣x1(i)​−x2(i)​∣∣1​
这里,N代表输入样本对的数量。

1.2 反向计算

L1 Loss本身没有参数,所以只需要计算对输入数据导数即可:
$$
\frac{\partial \mathcal L}{\partial \mathbf x_1^{(i)}} =
\begin{cases}
\frac{1}{N}, & x_1^{(i)} > x_2^{(i)} \

  • \frac{1}{N}, & x_1^{(i)} < x_2^{(i)}
    \end{cases}
    $$

$$
\frac{\partial \mathcal L}{\partial \mathbf x_2^{(i)}} =
\begin{cases}

  • \frac{1}{N}, & x_1^{(i)} > x_2^{(i)} \
    \frac{1}{N}, & x_1^{(i)} < x_2^{(i)}
    \end{cases}
    $$

2. caffe实现

在caffe中添加层一般需要以下几个步骤:

  1. include/caffe/layers/l1_loss_layer.hpp中添加声明。
  2. src/caffe/layers/l1_loss_layer.cpp中进行实现。
  3. 如果需要GPU版本,在src/caffe/layers/l1_loss_layer.cu中进行实现。
  4. 在cpp文件中用layer_factory.hpp提供的宏实例化并注册新的层。假如新的层叫做L1LossLayer
INSTANTIATE_CLASS(L1LossLayer);
REGISTER_LAYER_CLASS(L1Loss);

5.在src/caffe/test/test_l1_loss_layer.cpp中写测试。
6. 编译:

make -j
make test -j
make runtest GTEST_FILTER='L1LossLayerTest/*'

2.1 前向计算

前向计算主要是实现Forward_cpu和Forward_gpu两个函数。
CPU版本:

// src/caffe/layers/l1_loss_layer.cpp
template <typename Dtype>
void L1LossLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,const vector<Blob<Dtype>*>& top) {int count = bottom[0]->count();caffe_sub(count,bottom[0]->cpu_data(),bottom[1]->cpu_data(),diff_.mutable_cpu_data());Dtype loss = caffe_cpu_asum(count, diff_.cpu_data()) / bottom[0]->num();top[0]->mutable_cpu_data()[0] = loss;
}

GPU版本:

template <typename Dtype>
void L1LossLayer<Dtype>::Forward_gpu(const vector<Blob<Dtype>*>& bottom,const vector<Blob<Dtype>*>& top) {int count = bottom[0]->count();caffe_gpu_sub(count,bottom[0]->gpu_data(),bottom[1]->gpu_data(),diff_.mutable_gpu_data());Dtype asum;caffe_gpu_asum(count, diff_.gpu_data(), &asum);   // gpu函数,使用gpu_data()Dtype loss = asum / bottom[0]->num();top[0]->mutable_cpu_data()[0] = loss;             // 这里没有使用gpu函数,是普通的cpu运算,所以使用cpu_data()
}

2.2 反向计算

反向计算主要是实现Backward_cpu和Backward_gpu两个函数。
CPU版本:

template <typename Dtype>
void L1LossLayer<Dtype>::Backward_cpu(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]) {const Dtype sign = (i == 0) ? 1 : -1;      // 对两个输入的反向计算的差异仅是正负号,所以根据输入blob的序号确定一个符号即可const Dtype alpha = sign * top[0]->cpu_diff()[0] / bottom[i]->num();  // alpha = 1/N. top[0]->cpu_diff()[0]是weight_loss// 使用diff_的符号来判断两个输入blob哪个大caffe_cpu_sign(bottom[i]->count(),diff_.cpu_data(),bottom[i]->mutable_cpu_diff());// caffe_cpu_scale(n, alpha, x, y): y = alpha * xcaffe_cpu_scale(bottom[i]->count(),alpha,bottom[i]->cpu_diff(),bottom[i]->mutable_cpu_diff());}}
}

解释一下上面函数中的top[0]->cpu_diff()[0]。我们知道,每一层回传的梯度是由上一层传回来的梯度乘以本层的梯度得到的。但我们现在本来就是loss层了,后面没有层了,那这个top[0]->cpu_diff()[0]是什么呢?注意,这里只是取了top[0]->cpu_diff()的第一个元素,其实它就是我们在prototxt中定义的loss_weight
类似的,GPU版本:

template <typename Dtype>
void L1LossLayer<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]) {const Dtype sign = (i == 0) ? 1 : -1;const Dtype alpha = sign * top[0]->cpu_diff()[0] / bottom[i]->num();   // 这里是cpu运算,使用cpu_diff()caffe_gpu_sign(bottom[i]->count(),diff_.gpu_data(),bottom[i]->mutable_gpu_diff());caffe_gpu_scale(bottom[i]->count(),alpha,bottom[i]->gpu_diff(),bottom[i]->mutable_gpu_diff());}}
}

2.3 测试文件编写

测试文件是用来检查我们编写的层的前向和后向计算是否正确的。主要分为以下几个部分。

通过L1LossLayerTest类中的TestForward()成员来检查loss是否可以被loss weight正确放缩。

void TestForward() {// 不指定loss weight,得到一个loss值loss_weight_1,相当于loss weight为1。LayerParameter layer_param;L1LossLayer<Dtype> layer_weight_1(layer_param);layer_weight_1.SetUp(this->blob_bottom_vec_, this->blob_top_vec_);const Dtype loss_weight_1 =layer_weight_1.Forward(this->blob_bottom_vec_, this->blob_top_vec_);// 指定一个特定的loss weight,再得到一个loss值loss_weight_2,// 然后检查loss_weight_2是否被正确地放缩。const Dtype kLossWeight = 3.7;layer_param.add_loss_weight(kLossWeight);L1LossLayer<Dtype> layer_weight_2(layer_param);layer_weight_2.SetUp(this->blob_bottom_vec_, this->blob_top_vec_);const Dtype loss_weight_2 =layer_weight_2.Forward(this->blob_bottom_vec_, this->blob_top_vec_);const Dtype kErrorMargin = 1e-5;EXPECT_NEAR(loss_weight_1 * kLossWeight, loss_weight_2, kErrorMargin);// 确保loss不会过小const Dtype kNonTrivialAbsThresh = 1e-1;EXPECT_GE(fabs(loss_weight_1), kNonTrivialAbsThresh);}

利用数值方法计算梯度,然后和本层的梯度计算进行比较,检查是否正确。这是通过调用caffe提供的GradientChecker实现的。

// 通过和数值计算的梯度值对比,检查本层梯度计算是否正确。
TYPED_TEST(L1LossLayerTest, TestGradient) {typedef typename TypeParam::Dtype Dtype;LayerParameter layer_param;const Dtype kLossWeight = 3.7;layer_param.add_loss_weight(kLossWeight);L1LossLayer<Dtype> layer(layer_param);layer.SetUp(this->blob_bottom_vec_, this->blob_top_vec_);GradientChecker<Dtype> checker(1e-4, 1e-2, 1701);    // 1e-4为梯度数值计算的步长,1e-2为比较的阈值checker.CheckGradientExhaustive(&layer, this->blob_bottom_vec_,this->blob_top_vec_);
}

本文涉及的所有完整文件可在我的github下载。

在caffe中添加新层 L1 Loss layer相关推荐

  1. 学习Caffe(二)使用Caffe:Caffe加载模型+Caffe添加新层+Caffe finetune

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/u014230646/article/details/51934150 如何使用Caffe Caffe ...

  2. 在caffe 中添加Crowd counting 数据层

    #目录 [TOC] 一.简介 Crowd counting一般以人群图像作为输入,网络回归该图像对应的密度图.以往做法是先在matlab中根据图像的label(人头位置)生成密度图,然后将输入图像及密 ...

  3. 在caffe中调用python层的问题和解决方案

    之前很早以前在caffe中用过python写的loss layer,很久不用后在新的服务器上使用就各种"No module named WeightedEuclideanLossLayer2 ...

  4. caffe中的batchNorm层(caffe 中为什么bn层要和scale层一起使用)

    caffe中的batchNorm层 链接: http://blog.csdn.net/wfei101/article/details/78449680 caffe 中为什么bn层要和scale层一起使 ...

  5. 16.WireShark学习-在WireShark中添加新协议

    16. 在Wireshark中添加新协议 WireShark编程基础 使用Lua开发简单扩展功能 使用WireShark开发新的协议解析器 测试新协议 WireShark支持Lua语言编写的脚本 16 ...

  6. 怎么在CAD图库中添加新图块?

    在使用国产CAD软件绘制电气图纸的过程中,经常会用到CAD图库,那么在浩辰电气CAD软件中怎么在CAD图库中添加新的图块呢?接下来的CAD教程就让小编来给大家介绍一下国产CAD软件--浩辰电气CAD软 ...

  7. 在VirtualBox中的Ubuntu中添加新硬盘

    2019独角兽企业重金招聘Python工程师标准>>> 在VirtualBox中的Ubuntu中添加新硬盘 VitrualBox是不允许更改重置硬盘大小的,所以当硬盘不足时,只能添加 ...

  8. Sharepoint学习笔记—Ribbon系列-- 5. 在Ribbon中添加新控件(针对用户自定义Tab)

    前面我们实现了向用户自定义的Tab中添加新的Group,并向其中创建了两个Button按钮.这里我们看看如何向这个已经创建好的Group中再另外添加新的Button控件(当然,你可以添加其它控件,实现 ...

  9. Android 驱动(17)---如何在linux中添加新的kernel module

    如何在linux中添加新的kernel module 该SOP针对客户如何添加一个kernel module,并把生成的.ko打包进system.img的过程. 解决方案 L版本(version> ...

最新文章

  1. 阿里二面:redis分布式锁过期了但业务还没有执行完,怎么办
  2. git本地ben远程分支_git 本地分支与远程分支
  3. [项目更新] 集成RabbitMQ队列与EventBus总线
  4. 周记之琢磨下计算机网络(2018/10/22-2018/10/28)
  5. __name__ == ‘__main__‘的原理
  6. 线程池和任务工厂实现多线程异步运行
  7. Oracle 统计信息收集
  8. solr的两种启动方式
  9. MySQL四种SQL性能分析工具
  10. 使用nps做内网穿透远程桌面
  11. 航天金税502设置模拟环境步骤
  12. 云计算三种架构(IaaS, PaaS, SaaS)及部署模型
  13. 如何用mysql命令 复制表_mysql中用命令行复制表结构的方法
  14. Pytorch为什么总要设置随机种子
  15. LiveNVR监控摄像头Onvif/RTSP视频流媒体服务平台概览负载信息实时展示取流中、播放中、录像中等使用数目说明
  16. Android如何解决文字转语音播报的问题
  17. 软件设计领域没有银弹,但代码大师MaxKanat-Alexander的建议绝对能给你带来启发...
  18. Git - git checkout git branch 创建/删除分支用法及区别
  19. C++ 文件查找 _findfirst、_findnext和_fineclose的使用
  20. 拿下阿里三面是后,面试官问我:你是怎么学习Redis的?

热门文章

  1. laravel mews/captcha 图形验证码 前后端分离
  2. 微分中值定理,常数K值法
  3. 55.深度解密五十五:利用互联网思维进行实体行业“吸粉和变现”(实用性)
  4. 信息学奥赛一本通:2073:【例2.16 】三角形面积
  5. Vue组件滚动加载、懒加载功能的实现,无限滚动加载组件实例演示
  6. Python量化交易10——资产组合比例优化(CAMP,VAR,CVAR)
  7. Java春招面试宝典300题(2023)
  8. 日本NEC公司确认2016年国防业务部门内部服务器存在安全漏洞
  9. 细说Oracle数据库OLAP和OLTP的区别
  10. 中本聪更新个人状态;中国区块链技术应用反诈骗中心正式成立