标签:
Caffe1——Mnist数据集创建lmdb或leveldb类型的数据
Caffe生成的数据分为2种格式:Lmdb和Leveldb。
它们都是键/值对(Key/Value Pair)嵌入式数据库管理系统编程库。
虽然lmdb的内存消耗是leveldb的1.1倍,但是lmdb的速度比leveldb快10%至15%,更重要的是lmdb允许多种训练模型同时读取同一组数据集。
因此lmdb取代了leveldb成为Caffe默认的数据集生成格式(http://blog.csdn.net/ycheng_sjtu/article/details/40361947)
LevelDb有如下一些特点:
首先,LevelDb是一个持久化存储的KV系统,和Redis这种内存型的KV系统不同,LevelDb不会像Redis一样狂吃内存,而是将大部分数据存储到磁盘上。
其次,LevleDb在存储数据时,是根据记录的key值有序存储的,就是说相邻的key值在存储文件中是依次顺序存储的,而应用可以自定义key大小比较函数,LevleDb会按照用户定义的比较函数依序存储这些记录。
再次,像大多数KV系统一样,LevelDb的操作接口很简单,基本操作包括写记录,读记录以及删除记录。也支持针对多条操作的原子批量操作。
另外,LevelDb支持数据快照(snapshot)功能,使得读取操作不受写操作影响,可以在读操作过程中始终看到一致的数据。
除此外,LevelDb还支持数据压缩等操作,这对于减小存储空间以及增快IO效率都有直接的帮助。LevelDb性能非常突出,官方网站报道其随机写性能达到40万条记录每秒,而随机读性能达到6万条记录每秒。总体来说,LevelDb的写操作要大大快于读操作,而顺序读写操作则大大快于随机读写操作。至于为何是这样,看了我们后续推出的LevelDb日知录,估计您会了解其内在原因。(http://www.cnblogs.com/haippy/archive/2011/12/04/2276064.html)
在Create.sh文件通过convert_mnist_data.bin来转换数据
通过命令行解析(gflags)解析后,以上可以理解为在编译平台上(gcc等)运行convert_mnist_data.bin程序,程序需要4个参数:
3个mian函数参数:1训练数据位置,2标签数据位置,3 lmdb数据存储位置。
1个程序中通过gflags宏定义的参数:转换的数据类型lmdb or leveldb。
convert_mnist_data.bin是由convert_mnist_data.cpp编译的可执行文件。
存放在硬盘中的mnist数据分为4个文件,训练和测试数据集,训练和测试标签集;其中数据集中存放了两类数据:图片结构数据和图片数据
1.引入必要的头文件和命名空间
#include <gflags/gflags.h>//gflags命令行参数解析的头文件
#include <glog/logging.h>//记录程序日志的glog头文件
#include <google/protobuf/text_format.h>//解析proto类型文件中,解析prototxt类型的头文件
#include <leveldb/db.h>//引入leveldb类型数据头文件
#include <leveldb/write_batch.h>//引入leveldb类型数据写入头文件
#include <lmdb.h>
#include <stdint.h>
#include <sys/stat.h>
#include <fstream> // NOLINT(readability/streams)
#include <string>
#include "caffe/proto/caffe.pb.h"//解析caffe中proto类型文件的头文件
using namespace caffe; // NOLINT(build/namespaces)
using std::string;
2.定义程序变量backend
通过宏定义字符串类型变量DEFINE_stringbackend(这个是通过gflags来定义的变量,在程序调用时,通过--backend=${BACKEND}来给变量命名)
3.main()函数
Argc为统计main函数接受的参数个数,正常调用时argc=4,argv为对应的参数值,
argv[1]=源数据路径,arg[2]=标签数据路径,arg[3]=保存lmdb数据的路径
4. convert_dataset()函数
4.1读取源数据
4.1.1打开源数据文件(文件先打开,才能读)
//引入std命名空间中的文件读入ifstream子空间,并创建“对象” image_file(要读入的文件名,文件读入的方式),此处以二进制的方式读入image_filename中的文件
//CHECK用于检测文件是否能够正常打开的函数,估计是定义在上面某个头文件里面的,具体哪个没有找到;感觉功能类似判断文件是否打开的函数image_file.is_open()
4.1.2定义数据结构文件
根据mnist的图像结构,长,宽,channel,样本个数等
//uint32_t用typedef来自定义的一种数据类型,unsigned int32 ,每个int32整数占用4个字节
4.1.3读取图片结构数据
//获取数据的结构信息,即图片的个数,width,height;这个数据的结果信息应该是一整型数据的方式存放在源数据的前n*4个字节里面;label的n=2(magic和num_labels),image的n=4(magic,num_items,width,height)
//文件读取通过read函数来完成,read(读取内容的指针,读取的字节数),这里magic是一个int32类型的整数,每个占4个字节,所以这里指定为4
//reinterpret_cast为C++中定义的强制转换符,这里把“&magic”,即magic的地址(一个16进制的数),转变成char类型的指针
4.2创建lmdb和leveldb相关变量
4.3 写入硬盘
Leveldb类型
4.3.1打开(创建)数据库文件
//通过leveldb::DB::Open()函数以options的方式,在db_path路径下创建或者打开lmdb类型文件
4.3.2创建数据“转移”的中间变量
4.3.3创建“转换”数据对象datum
4.3.4读取源数据值并“赋值”给datum
4.3.5将数据写入db数据对象batch中
batch->Put(keystr, value);//通过batch中的子方法Put,把数据写入datum中(此时在内存中)
4.3.6把db数据写入硬盘
代码选择1000个样本放入一个batch中,通过batch以批量的方式把数据写入硬盘;写入硬盘通过db.write()函数来实现。
//把batch写入到db中,然后删除batch并重新创建,这里为什么要删除重建有些不理解;删除可能是为了清理变量,减少内存占用吧,之后又重建了。
4.3.7写入最后一个batch
Lmdb类型
变量和函数说明
MDB_dbi :在数据库环境中的一个独立的数据句柄
MDB_env:数据库环境的“不透明结构”,不透明类型是一种灵活的类型,他的大小是未知的
MDB_val:用于从数据库输入输出的通用结构
MDB_txn:不透明结构的处理句柄,所有的数据库操作都需要处理句柄,处理句柄可指定为只读或读写
mdb_env_create(MDB_env ** env):
创建一个lmdb环境句柄,此函数给mdb_env结构分配内存;释放内存或者关闭句柄可以通过mdb_env_close()函数来操作。在使用meb_env_create()句柄前,必须使用ndb_env_open()函数打开。
参数:env 新句柄的存储地址
mdb_env_open(MDB_env * env,const char * path,unsigned int flags,mdb_mode_t mode )
打开环境句柄,
参数:1 env,是mdb_env_create()函数返回的环境句柄
2 path,数据库文件隶属的文件夹,文件夹必须存在而且是可读的。
mdb_env_set_mapsize (MDB_env *env , size_t size )
设置当前环境的内存映射(内存地图)的尺寸。
int mdb_txn_begin (MDB_env * env, MDB_txn * parent, unsigned int flags, MDB_txn ** txn )
在环境内创建一个用来使用的“处理”transaction句柄
参数:1,env,环境
4,MDB_txn** txn 新txn句柄存储的地址
mdb_open
通过宏定义的方式,把mdb_open()函数用msb_dbi_open()函数替代
#define mdb_open(txn, name, flags,dbi ) mdb_dbi_open(txn,name,flags,dbi)
mdb_dbi_open(txn,name,flags,dbi)
在环境中打开一个数据库
参数:
1,txn mdn_txn_begin()函数返回的处理句柄
2,name 要打开的数据库名称, 如果环境中只需要一个单独的数据库,这个值为null
3,flags 指定当前数据库的操作选项
4,dbi 新的mdb_dbi句柄存储的地址
int mdb_put (MDB_txn * txn,MDB_dbi dbi,MDB_val* key,MDB_val * data,unsigned int flags )
把数据条目保存到数据库;函数把key/data(键值对)保存到数据库
参数:
1,txn mdb_txn_begin()函数返回的transaction处理句柄
2,dbi mdb_dbi_open() 函数返回的数据库句柄
3,key 4,data
int mdb_txn_commit ( MDB_txn * txn )
提交所有transaction操作到数据库中;交易句柄必须是“自由的”freed;在本次调用之后,他和它本身的“光标(指针)”不能够被在此使用;需要再一次指定txn
5.3.1创建lmdb操作环境(输入输出环境)
1)创建lmdb操作环境,
2)设置环境参数,
3)在存储位置“打开”lmdb环境,
4)在环境内创建一个用来使用的“处理”transaction句柄
5)打开lmdb类型文件
5.3.2创建数据“转移”的中间变量
5.3.3创建“转换”数据对象datum
5.3.4读取源数据值并“赋值”给datum
见4.3.2,4.3.3,4.3.4
5.3.5把数据放入lmdb数据类型对象mdb_data(MDB_val类型)
5.3.6 lmdb数据类型对象写入mdb_txn中
5.3.7lmdb写入到硬盘
5.3.8写入最后一个batch
CPU处理器对多字节数据的存储方式,对二进制文件的可移植性有着决定性的影响;二进制文件里数据的排列顺序与他们在计算机内存的存储顺序完全一样。大端字节的计算机,数据的最高位存储在最前面;小端字节的计算机上数据的最低位存储在最前面;大端字节计算机上存储的二进制文件无法在小端计算机上正确读取,反之亦然。感觉mnist的数据集在制作存储的时候官方采用的CPU的存储方式可能和我们的CPU不一样,所以低于mnist需要进行大端小端的转换。
详细介绍参考:http://www.cnblogs.com/passingcloudss/archive/2011/05/03/2035273.html
//convert big endian to little endian in C ;http://stackoverflow.com/questions/2182002/convert-big-endian-to-little-endian-in-c-without-using-provided-funcuint32_t
//大端小端转换(大端小端为一种字节顺序存储的方式,不同的CPU有不同的存储方式)
五:以上代码注释为个人理解,如有遗漏,错误还望大家多多交流,指正,以便共同学习,进步!!
Caffe1——Mnist数据集创建lmdb或leveldb类型的数据
标签:
原文地址:http://www.cnblogs.com/yymn/p/4479216.html