数据层是每个模型的最底层,是模型的入口,不仅提供数据的输入,也提供数据从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_size‘x ‘corp_size‘patch: # -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:
其它数据层
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放在一个层里。这样方便调试。