caffe总结(十二)使用lmdb格式文件用于图像回归(regression)

简介

caffe分类已经做了很多,但是在现实生活中,连续性回归问题才是更加常见的,想到能不能利用深度学习解决回归性问题。通过caffe训练网络,其标签为连续性的实数值(为浮点数),这样可以做到对连续性实数值做到回归预测。

之前一篇博文对caffe做回归有一个比较好的介绍:
https://blog.csdn.net/weixin_42535423/article/details/103796195这篇博文使用的是HDF5+python的方式。
而这里采用的是直接修改caffe的.cpp文件,并重新编译的方式,两种方式各有利弊,我个人认为理解并修改源码对进一步理解caffe很有帮助。当然配置了faster-rcnn或者SSD之后也可以做回归。这个正在想办法实现

caffe本来就“擅长”于做分类任务,所以要拿caffe来做回归任务,就需要对caffe的源码做一些修改。修改的地方主要是下面两大部分:

  1. 制作lmdb文件相关的代码(即修改convert_imageset.cpp文件):image to Datum

  2. 读取lmdb文件相关代码(即修改data_layer.cpp文件):Datum to Blob


制作用于回归的lmdb文件

  • 首先,看一看用于分类的txt文件:
cat_1.jpg 0
dog_2.jpg 1

里面是图片的名称以及对应的类别(这里不考虑多标签的情况)。

  • 而用于做关键点回归的txt文件:
cat_1.jpg 0.03 0.45 0.55 0.66
cat_2.jpg 0.44 0.31 0.05 0.34
dog_1.jpg 0.67 0.25 0.79 0.56
dog_2.jpg 0.89 0.46 0.91 0.38

后面带有多个归一化的坐标(上面的是我随便举的例子,没有实际的意义),实际应用中它们可能代表着某一个BoundingBox的坐标,或者是脸部一些关键点的坐标。
下面我将一一列出需要修改代码的地方,带有//###标记的就是我修改的地方:

1-修改tools/convert_imageset.cpp

首先是对tools/convert_imageset.cpp进行修改,复制tools/convert_imageset.cpp,并重新命名,这里姑且命名为convert_imageset_regression.cpp,依然放在tools文件夹下面。
在这里插入图片描述

  • 上面的代码主要有两处进行了修改:
    1. 读取txt文件部分,
    2. ReadImageToDatum函数。

首先,原来的label是一个int类型的变量,现在的label是多个float类型的变量,所以就有了下面的修改:

  //std::vector<std::pair<std::string, int> > lines;  //###
  std::vector<std::pair<std::string, std::vector<float> > > lines;
  std::string line;
  //size_t pos;
  //int label;  //###
  std::vector<float> labels;

在这里插入图片描述

用float类型的vector来存放label,然后在读取txt文件的while循环中修改读取label部分的代码。

2-修改caffe\include\caffe\util\io.hpp

第一处修改完成之后,接下来需要对ReadImageToDatum函数进行修改,这个函数的作用是将图片的信息写入到Datum中,对Datum,Blob还不太了解的朋友可以参考下面这篇博文:http://www.cnblogs.com/yymn/articles/4479216.html,这里先暂时将Datum理解为一个存放图片信息(包括像素值和label) 的数据结构,用于将图片写入到lmdb文件。

ReadImageToDatum函数在io.hpp中声明,我是使用sublime text3打开(open folder)caffe文件夹,直接选中ReadImageToDatum右键就可以“Goto Definition”。

在io.hpp文件中,原来的ReadImageToDatum函数是像下面这样声明的:

在这里插入图片描述

我们可以不改动原来的函数声明(因为C++支持函数重载,这里指参数有所不同),而在它的下面接上:

在这里插入图片描述

容易注意到,我们参原来的参数

const int label

修改成:

const vector<float> labels

3-修改caffe\src\caffe\util\io.cpp

接着,我们需要在io.cpp函数中实现我们增加的重载函数:

在这里插入图片描述

在原来的ReadImageToDatum定义下面加上新的定义,(BTW:encoding部分对我暂时没有什么用,所以暂时注释掉)。这里使用:

datum->add_float_data(labels.at(i));

将label写入到Datum中。
好了!经过上面的步骤,回到caffe目录下,重新make编译一下,就会在build/tools/文件夹下面生成一个convert_imageset_regression.bin可执行文件了。

再接下来制作lmdb的方法就跟分类任务一样了,需要制作我们的train.txt以及test.txt,以及将我们用于train和test的图片放到相应的文件夹下面,然后调用convert_imageset_regression.bin来制作lmdb即可,经过上面的代码修改,convert_imageset_regression.bin已经“懂得”如何将后面带有多个浮点类型的数字的txt转换成lmdb文件啦!

这里,可能有的朋友还会有一点疑问,

datum->add_float_data(labels.at(i));

这个函数是怎么来的,第一次用的时候怎么会知道有这个函数?
这就得来看看caffe.proto文件了,里面关于Datum的代码如下:

message Datum {
  optional int32 channels = 1;
  optional int32 height = 2;
  optional int32 width = 3;
  // the actual image data, in bytes
  optional bytes data = 4;
  optional int32 label = 5;
  // Optionally, the datum could also hold float data.
  repeated float float_data = 6;
  // If true data contains an encoded image that need to be decoded
  optional bool encoded = 7 [default = false];
}

.proto文件是Google开发的一种协议接口,根据这个,可以自动生成caffe.pb.h和caffe.pb.cc文件。
其中,

  optional int32 label = 5;

就是用于分类的。而,

repeated float float_data = 6;

就是我们用来做回归的。

在caffe.pb.h文件中可以找到关于这部分自动生成的代码:

 // optional int32 label = 5;
  inline bool has_label() const;
  inline void clear_label();
  static const int kLabelFieldNumber = 5;
  inline ::google::protobuf::int32 label() const;
  inline void set_label(::google::protobuf::int32 value);
 
  // repeated float float_data = 6;
  inline int float_data_size() const;
  inline void clear_float_data();
  static const int kFloatDataFieldNumber = 6;
  inline float float_data(int index) const;
  inline void set_float_data(int index, float value);
  inline void add_float_data(float value);
  inline const ::google::protobuf::RepeatedField< float >&
      float_data() const;
  inline ::google::protobuf::RepeatedField< float >*
      mutable_float_data();

在这里就可以看到,关于操作label的一系列函数,如果我们不使用add_float_data,而是用set_float_data,也是可以的!


读取lmdb文件(Datum to Blob)

上面完成的实际上就是将训练和测试的图片的信息存放在Datum中,然后再序列化到lmdb文件中。这以上修改完成了数据的准备工作,而要跑通整个实验,还需要在data_layer.cpp中做一些相应的修改。

data_layer.cpp中的函数实现了从lmdb中读取图片信息,先是反序列化成Datum,然后再放进Blob中。仔细想一下可以知道,因为原先caffe的data_layer.cpp的实现是针对分类的情况,所以读取label部分的代码并不适用于回归的情况。

4-修改caffe\src\caffe\layers\data_layer.cpp

接下来介绍data_layer.cpp需要修改的代码,以及训练的时候需要注意的一些细节。

  • 下面是我修改后的data_layer.cpp文件,主要修改了两处地方:
    1. DataLayerSetup函数,
    2. load_batch函数。

有//###标记的就是我修改的地方:
在这里插入图片描述

在这里插入图片描述

其中,第一处修改是:

  //###
  int labelNum = 4;	//标签的数量,也就是txt中每一张图后面跟着的浮点数的数目
  if (this->output_labels_) {
    vector<int> label_shape;
    label_shape.push_back(batch_size);
    label_shape.push_back(labelNum);
    label_shape.push_back(1);
    label_shape.push_back(1);
    top[1]->Reshape(label_shape);
    for (int i = 0; i < this->PREFETCH_COUNT; ++i) {
      this->prefetch_[i].label_.Reshape(label_shape);
    }
  }

从DataLayerSetup函数传进来的参数可以看到,top是一个向量的地址,而向量的元素是Blob*。因为在caffe网络结构中,图片信息是分成两个Blob进行传递的,一个Blob记录图片的像素值,另外一个Blob记录图片的标签,这里的top[0],top[1]分别与之对应(所以实际上我们要修改的是top[1]相关的内容,top[0]相关的我们并不需要管)。

上面的代码是对top[1]的Reshape,push_back的四个值分别对应Blob的num,channels,height,width。因为top[1]对应的是标签,所以num设置为batch_size,channels设置为labelNum,height和width设置为1即可。这一步相当于是“塑造”一个适合我们数据label的Blob出来。

第二处修改的地方是:

//###
int labelNum = 4;
if (this->output_labels_) {
  for(int i=0;i<labelNum;i++){
    top_label[item_id*labelNum+i] = datum.float_data(i); //read float labels
  }

这个地方是将datum中的label值赋值给top_label。

完成了上面两处修改之后,需要回到caffe目录下,重新执行make编译一下data_layer.cpp。编译完成之后,我们的修改就生效了!这样一来,convert_imageset_regression完成了将回归数据制作成lmdb的任务,而data_layer则完成了将用于回归的lmdb成功送入后续网络的任务。


回归训练注意细节

那么,要成功运行caffe.bin进行训练,还需要注意一下下面的细节,主要是要注意网络配置文件(.prototxt):

1、最后一个全连接层的num_output应该与labelNum(即label的数目相等)

2、做分类任务的时候,一般是使用SoftmaxWithLoss类型的loss层,而在**做回归任务的时候,一般是用EuclideanLoss类型的loss层,**因为loss主要体现在网络最后一个全连接层的输出与ground true的欧氏距离

3、不使用Accuracy层,因为回归任务没有所谓的准确率

4、如果要在数据层做crop,scale,mirror等操作,应该先考虑一下变换之后你的label是否也需要变化,不能像分类任务那么“直接”地用

5、修改data_layer.cpp并重新编译之后,下次如果要进行分类任务,得记得改回去并重新编译(或者可以在github上git clone多个caffe下来,这样就不用来回修改)。

完成了上面所有的工作之后就可以对自己的数据进行训练和测试了。训练之后得到caffemodel,就可以拿来应用了。应用的时候,可以用caffe的Python接口或者是继续修改源码。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值