Caffe实战(十五):数据层及参数设置

数据层是每个模型的最底层,是模型的入口,不仅提供数据的输入,也提供数据从Blobs转换成别的格式进行保存输出。通常数据的预处理(如减去均值, 放大缩小, 裁剪和镜像等),也在这一层设置参数实现。
 
caffe的各种数据层在caffe.proto文件中定义,通过对定义的caffe.proto文件进行编译,产生支持各种层操作的C++代码。
caffe支持的数据来源主要有:
  • 高效的数据库(LMDB或LevelDB);
  • 内存;
  • 硬盘文件,如HDF5格式或图片格式的文件;此种方式效率较差,目前一般是现将原生数据转换为LMDB或LevelDB数据库格式,然后再进行训练。

不同层的共有属性

所有的数据层的都具有的公用参数
layer {
  name: "cifar"
  type: "Data"
  top: "data"
  top: "label"
  include {
    phase: TRAIN
  }
  transform_param {
    mean_file: "examples/cifar10/mean.binaryproto"
  }
  data_param {
    source: "examples/cifar10/cifar10_train_lmdb"
    batch_size: 100
    backend: LMDB
  }
}
  • name: 表示该层的名称,可随意取
  • type: 层类型,如果是Data,表示数据来源于LevelDB或LMDB。根据数据的来源不同,数据层的类型也不同(后面会详细阐述)。一般在练习的时候,我们都是采 用的LevelDB或LMDB数据,因此层类型设置为Data。
  • top或bottom: 每一层用bottom来输入数据,用top来输出数据。如果只有top没有bottom,则此层只有输出,没有输入。反之亦然。如果有多个 top或多个bottom,表示有多个blobs数据的输入和输出。
  • data 与 label: 在数据层中,至少有一个命名为data的top。如果有第二个top,一般命名为label。 这种(data,label)配对是分类模型所必需的。
  • include: 一般训练的时候和测试的时候,模型的层是不一样的。该层(layer)是属于训练阶段的层,还是属于测试阶段的层,需要用include来指定。如果没有include参数,则表示该层既在训练模型中,又在测试模型中。
  • Transformations: 数据的预处理,可以将数据变换到定义的范围内。如设置scale为0.00390625,实际上就是1/255, 即将输入数据由0-255归一化到0-1之间
 
绝大部分数据层在设置时,都可以先对数据进行一定的预处理,包括归一化scale,去中心化(减去平均值),水平镜像mirror,随机裁剪/中心裁剪crop等四种预处理方式该预处理方式可以通过Layer的transform_params属性(HDF5 Layer没有该属性)来设定,如下所示:
transform_param {
    # 实际上就是1/255, 即将输入数据由0-255归一化到0-1之间
    scale: 0.00390625
    # randomly horizontally mirror the image
    # 1表示开启镜像,0表示关闭,也可用ture和false来表示
    mirror: 1
    # corp a crop_sizex corp_sizepatch:
    # -at random during training
    # -from the center during testing
    # 剪裁一个 227*227的图块,在训练阶段随机剪裁,在测试阶段从中间裁剪
    crop_size: 227
    # substract mean value(RGB three channel) 用一个配置文件来进行均值操作
    mean_file: "examples/cifar10/mean.binaryproto"
    # mean_value: 104
    # mean_value: 117
    # mean_value: 123
  }

 

源码“data_transformer.cpp”可知,执行顺序是mean(file)、mean(value)、crop、mirror、scale。如有其它特殊的预处理可在此添加代码。
此外由  const bool do_mirror = param_.mirror() && Rand(2); 可知每次batch是随机的做mirror(水平翻转)的。
 

数据层参数shuffle介绍

训练网络的时候一般需要随机打乱训练样本数据,因为是采用min-batch SGD 方法进行优化的,随机打乱一定程度上可以防止陷入局部最优,有利于网络收敛。
 
而caffe读取数据时是一个batch一个batch顺序读取的,不同的数据形式实现shuffle的机制不同:
  • leveldb and lmdb格式,一般在生成的时候就打乱顺序的了,然后caffe就顺序读取,并不再shuffle。
  • filelist方式,caffe在每个epoch读取前都会整个数据集shuffle一次(shuffle =true的话)。
  • hdf5也是在生成的时候自定义shuffle。

 

数据来自于数据库(如LevelDB和LMDB)

  层类型(layer type):Data
 
必须设置的参数:
  •   source: 包含数据库的目录名称,如examples/mnist/mnist_train_lmdb
  •   batch_size: 每次处理的数据个数,如64
 
可选的参数:
  •   rand_skip: 在开始的时候,跳过某个数据的输入。通常对异步的SGD很有用。
  •   backend: 选择是采用LevelDB还是LMDB, 默认是LevelDB.
LMDB(Lightning MemoryMapped Databases),由于caffe的文件读取方式使得该格式的额数据输入最适合用于1-k分类问题。
layer {
  name: "mnist"
  type: "Data"
  top: "data"
  top: "label"
  include {
    phase: TRAIN
  }
  transform_param {
    scale: 0.00390625
  }
  data_param {
    source: "examples/mnist/mnist_train_lmdb"
    batch_size: 64
    backend: LMDB
  }
}

 

在caffe代码中,Net初始化时是从bottom到top逐层初始化各层的;在初始化data层时,调用DataLayer构造函数
template <typename Dtype>
DataLayer<Dtype>::DataLayer(const LayerParameter& param)
  : BasePrefetchingDataLayer<Dtype>(param),
    reader_(param) {
}

 

其中reader_是DataReader类,用于读取lmdb或leveldb格式的数据;caffe实现了多线程读取数据,在DataReader类中,InternalThreadEntry()函数实现了多线程并行读取lmdb/leveldb数据的,具体的对lmdb/leveldb读取操作的实现在db.cpp,db_lmdb.cpp和db_leveldb.cpp文件中。
void DataReader::Body::InternalThreadEntry() {
  shared_ptr<db::DB>  db(db::GetDB(param_.data_param().backend()));
  db->Open(param_.data_param().source(), db::READ);
  shared_ptr<db::Cursor> cursor(db->NewCursor());
  vector<shared_ptr<QueuePair> > qps;
  try {
    int solver_count = param_.phase() == TRAIN ?  Caffe::solver_count() : 1;
    // To ensure deterministic runs, only start running once  all solvers
    // are ready. But solvers need to peek on one item  during initialization,
    // so read one item, then wait for the next solver.
    for (int i = 0; i < solver_count; ++i) {
      shared_ptr<QueuePair> qp(new_queue_pairs_.pop());
      read_one(cursor.get(), qp.get());
      qps.push_back(qp);
    }
    // Main loop
    while (!must_stop()) {
      for (int i = 0; i < solver_count; ++i) {
        read_one(cursor.get(), qps[i].get());
      }
      // Check no additional readers have been created. This  can happen if
      // more than one net is trained at a time per process,  whether single
      // or multi solver. It might also happen if two data  layers have same
      // name and same source.
      CHECK_EQ(new_queue_pairs_.size(), 0);
    }
  } catch (boost::thread_interrupted&) {
    // Interrupted exception is expected on shutdown
  }
}

 

数据来自于内存

可以很快从内存中直接读取数据。使用该方法来读取数据时,可以靠调用MemoryDataLayer::Reset(from C++) or Net.set_input_arrays(from python)来制定一个具体的数据地址;如通常的存放所有输入数据的四维数组的首地址,这样就可以每次从该地址内存中读取batch_size大小的数据。
 
层类型:MemoryData
 
必须设置的参数:
  • batch_size:每一次处理的数据个数,比如2
  • channels:通道数
  • height:高度
  • width: 宽度
layer {
  top: "data"
  top: "label"
  name: "memory_data"
  type: "MemoryData"
  memory_data_param{
    batch_size: 2
    height: 100
    width: 100
    channels: 1
  }
  transform_param {
    scale: 0.0078125
    mean_file: "mean.proto"
    mirror: false
  }
}

 

数据来自于HDF5

不太适合图像,因为HDF5格式数据采用FP2格式的数据,而图像采用uint8,因此若将文件转换为该格式则会很大。与其它层不同,该层没有transform_params属性。
 
层类型:HDF5Data
 
必须设置的参数:
  • source: 读取的文件名称
  • batch_size: 每一次处理的数据个数
 
可选设置:
  • shuffle:默认为0,表示忽略
layer {
  name: "data"
  type: "HDF5Data"
  top: "data"
  top: "label"
  hdf5_data_param {
    source: "examples/hdf5_classification/data/train.txt"
    batch_size: 10
  }
}

 

数据来自于图片

直接从文本文件读入所有要处理的图像文件的路径与标签label。
 
层类型:ImageData
 
必须设置的参数:
  •   source: 一个文本文件的名字(txt),每一行给定一个图片文件的名称和标签(label)
  •   batch_size: 每一次处理的数据个数,即图片数
 
可选参数:
  •   rand_skip: 在开始的时候,跳过某个数据的输入。通常对异步的SGD很有用。
  •   shuffle: 随机打乱顺序,默认值为false
  •   new_height,new_width: 如果设置,则将图片进行resize
layer {
  name: "data"
  type: "ImageData"
  top: "data"
  top: "label"
  transform_param {
    mirror: false
    crop_size: 227
    mean_file: "data/ilsvrc12/imagenet_mean.binaryproto"
  }
  image_data_param {
    source: "examples/_temp/file_list.txt"
    batch_size: 50
    new_height: 256
    new_width: 256
  }
}

 

图像和标签文件来源txt文本,文本内容如下所示,最后一列为图像的标签
/path/to/images/img3423.jpg 2
/path/to/images/img3424.jpg 13
/path/to/images/img3425.jpg 8
...

 

ImageNet数据集使用过程,ImageNet数据集给出的是图像格式文件,在一个txt文件中包含要训练的图片名称以及标签,如同上面介绍的。由于ImageNet图像的分辨率不统一,则通过image_data_param参数中的new_height和new_width对输入源数据进行分辨率统一,然后在进行预处理操作,transform_param中的crop获得模型需要的227*227大小的图片格式。
 

数据来源于原始图片的窗口(Windows)

最适合目标检测任务,因为目标检测的训练样本都是标注好的窗口,而不是整张图像。训练时是针对每一个窗口进行训练,而不是一个图像。
 
层类型:WindowData
 
必须设置的参数:
  •   source: 一个文本文件的名字
  •   batch_size: 每一次处理的数据个数,即图片数
layer {
  name: "data"
  type: "WindowData"
  top: "data"
  top: "label"
  include {
    phase: TRAIN
  }
  transform_param {
    mirror: true
    crop_size: 227
    mean_file: "data/ilsvrc12/imagenet_mean.binaryproto"
  }
  window_data_param {
    source: "examples/finetune_pascal_detection/window_file_2007_trainval.txt"
    batch_size: 128
    fg_threshold: 0.5
    bg_threshold: 0.5
    fg_fraction: 0.25
    context_pad: 16
    crop_mode: "warp"
  }
}
 
其中windows_file_2007_trainval.txt文件如下:
1. # 0  
2. /home/xxx/0001.jpg  
3. 641  
4. 7  
5. 1 1.0 353 356 393 396  
6. 1 0.5 338 344 379 384  
7. 2 0.7 339 336 379 376  
8. 5 0 334 330 374 370  
9. 4 1.0 330 324 370 364  
10. 4 1.0 335 319 375 359  
11. 4 1.0 341 313 381 353  
12. # 1  
13. /home/xxx/0002.jpg  
14. 600  
15. 3  
16. 2 1.0 353 356 393 396  
17. 2 0.5 338 344 379 384  
18. 3 0.7 339 336 379 376  
 
其中第一行是图片的index, 从0开始, 接下来三行依此是图片的路径, height, width。再接下来的每一行都是一个bounding box, 第一个数字表示label, 第二个数字表示与真实goundtruth 的overlap, 接下来的四个数字表示x1, y1, x2, y2。负样本的标签可以随意指定。
 
该层必须要设置的参数:
  • source:包含原始图像,窗口位置大小,窗口类别的文本文件
  • mean_file:整张图像的的mean_file
  • batch_szie
 
可选参数:
  • mirror
  • crop_size:裁剪的窗口的大小
  • crop_mode:裁剪方式,“warps”代表将窗口固定大小crop_size,“square”表示能够包围窗口的最小正方形;默认“warp”
  • fg_threshold:foreground overlap threshold ,默认0.5,代表只有BoundingBox与GroundTruth的重合面积比大于0.5时才认为是正样本
  • bg_threshold:background overlap threshold,默认0.5,代表只有BoundingBox与GroundTruth的重合比例小于0.5时才认为是负样本
  • fg_fraction:默认0.25,一个batch里正样本窗口的比例
  • context_pad:默认10个像素点,代表输入窗口数据的时候会自动在窗口周围数据补充10个像素点,像素值填充0.
 
如下图所示,最外围的一圈即为context填充,此时context_pad为1:
Caffe实战(十五):数据层及参数设置
 

其它数据层

Input type:常用来测试网络的前向和后向传递时间;用在deploy文件测试模型效果,需要代码中手动指定网络输入数据,唯一的参数BlobShape设定输入数据的维度。
 
deploy文件是测试模型效果,而一般的train_test等文件是为了训练模型。deploy文件是在train_test文件的基础上获得的,二者之间的区别如下:
  • 输入层:train_test文件需要指定数据来源,数据类型等,如type:data;而deploy文件只需要指定输入数据的维度,如type:input;
  • 权值参数:train_test文件需要指定权值参数(可学习参数)的初始化值;而deploy文件不需要,因此需要删除这些初始值的设定;
  • 输出层:train_test文件的输出一般有loss层(训练阶段),accuracy(测试阶段);而deploy的输出一般为“prob”,类型为“softmax”,即预测输出的概率。
layer {
    name:"data"    #设定当前layer名称为data
    type:"Input"   #设置当前layer类型为Input
    top:"data"     #设置当前layer输出blob名称为data
    #定义输入数据维度为 batchsize =1 channel=1 height=42 dim=42
    input_param {shape: {dim: 1 dim: 1 dim: 42 dim:42}}
}
 
 
“DummyData”type:常用来debug,也可以用来测试网络传递时间
layer {  
  name: "data"  
  type: "DummyData"  
  top: "data"  
  include {  
    phase: TRAIN  
  }  
  dummy_data_param {  
    data_filler {  
      type: "constant"  
      value: 0.01  
    }  
    shape {  
      dim: 32  
      dim: 3  
      dim: 227  
      dim: 227  
    }  
  }  
}  
layer {  
  name: "data"  
  type: "DummyData"  
  top: "label"  
  include {  
    phase: TRAIN  
  }  
  dummy_data_param {  
    data_filler {  
      type: "constant"  
    }  
    shape {  
      dim: 32  
    }  
  }  
}  

 

在这个例子中,有两个数据层,一个blob一个层,data一个,label一个。在HDF5,Data数据库,ImageData,都是data和label放在一个层里。这样方便调试。
 

参考:

 
 

Caffe实战(十五):数据层及参数设置

上一篇:B树和B+树区别


下一篇:百度地图开发-在地图上检索数据 08