caffe输入层怎么编写?

caffe的输入层总结

前面已经总结了下caffe已内置的输入层,但是除了python layer外,其余的局限性都比较大,比如大家常用的BoundingBox数据就很不统一,py-faster-rcnn里面使用python layer作输入层直接读取从PASCAL VOC格式数据(其实是从它构建的roidb并缓存了),而SSD自己构建了AnnotatedDataLayer来处理保存在lmdb中的数据,caffe-yolo也自己构建了BoxDataLayer供自己使用,大家都很不统一,caffe官方也迟迟不统一一个BBoxDataLayer出来,局面很分裂。本文就算是总结下阅读这些代码的经验吧。

那输入层应该怎么写?先来搞清除这几个概念。

一些基础概念

Datum

Datum是LMDB/LevelDB每个条目的值。但是Datum的内容是在caffe.proto里规定好了的,原版只有bytesdatafloatfloat_data两个字段可供保存数据。如果想保存多种数据,没办法,只能先组装到一块再放到datum中。

DataReader

yolo和ssd的DataReader完全一样啊。本对象还实例化了一个继承自InternalThread的类负责启动一个线程单独的读取磁盘数据。

如果输入层直接继承InternalThread类,则可以不实现DataReader类,相当于自己就是读入类,线程入口是InternalThreadEntry

Caffe原装的DataLayer就已继承InternalThread,只需要实现load_batch即可。

Batch

Bath就是每次让DataReader读到的数据,这个数据在输入层一般直接就直接想下一层传递了。这个caffe默认的只有data_label_两个Blob成员,如果我们有多种类型数据,caffe默认的恐怕也不够用。

Prefetch队列

  virtual void load_batch(Batch<Dtype>* batch);
  vector<shared_ptr<Batch<Dtype> > > prefetch_;
  BlockingQueue<Batch<Dtype>*> prefetch_free_;
  BlockingQueue<Batch<Dtype>*> prefetch_full_;

load_batch函数的用途:batch_size次从数据库中读取出共计batch_sizedatum数据,将这些datum转成Batch对象。

InternalThreadEntry每次从prefetch_free_pop一个Batch对象出来,然后调用load_batch装满这个Batch对象,再把这个Batch对象pushprefetch_full_中去。

XXXDataLayer::Forward_cpu的作用就是将当前的prefetch_current_指向的Batch对象pushprefetch_free_中去,然后再用从prefetch_full_pop取出一个对象到prefetch_current_上来。

总结下方法

简单标签

如果你的数据只是一些图,每张图对应一个int型可以表示的label,那你直接把图文件名和label组成文本文件用ImageDataLayer读就可以了,想用LMDB的就把数据存到LMDB中也可以。

bbox标签

因为原版datum字段有限,特别是label只有一个,大家只好各显神通。

caffe-yolo的做法是,datumdata存放图片,float_data存放bbox六元组(标签,难易标识,四个坐标)。ssd的做法则是对datum做了更大的更改以支持bbox数据。我为了方便,直接给原版datum定义了扩展字段,允许自行扩展若干自定义字段。

也可以将datum视为最简单的容器,我们在其内的data字段内另建容器,把数据结构先序列化存到datum的data字段,使用时再反序列化出来。

protobuf extension的使用

在proto中扩展Datum

// message Datum 需要先声明扩展字段区间,比如 {
//   extensions 11 to 20;
// }
// 然后想扩展datum时,只需要另外如此声明即可
extend Datum {
  optional string imgfilename = 15;
}

python代码中对消息扩展字段的使用

datum.Extensions[caffe.proto.caffe_pb2.imgfilename] = "/path/to/imgfile"

CPP对对消息扩展字段的使用

// get
datum.GetExtension(imgfilename)
// set
datum.SetExtension(imgfilename, "/path/to/imgfile");

 

方法1:直接继承BasePrefetchingDataLayer

需要实现load_batch函数,这个非常简单,队列和线程方法都已定义,仅需自己实现load_bactchLayerSetup相关的函数即可。

有些继承自此方法的层还定义了DataReader另外起个线程来读数据,其实没什么必要,因为load_batch就已经是在单独的线程里的。

方法2:直接继承Layer和InternalThread来实现

需要实现InternalThread里的InternalThreadEntry方法,并定义数据缓冲队列供使用。这个线程里的方法来调用一些方法实现文件输入并放在队列里供Layer的Forward方法取用。这个方法等于自己实现了一遍BasePrefetchingDataLayerLayerSetup函数最后启动这个线程即可。

附:各输入层的继承关系



发表评论

电子邮件地址不会被公开。 必填项已用*标注