Caffe2——cifar10數據集創建lmdb或leveldb類型的數據


Caffe2——cifar10數據集創建lmdb或leveldb類型的數據

cifar10數據集和mnist數據集存儲方式不同,cifar10數據集把標簽和圖像數據以bin文件的方式存放在同一個文件內,這種存放方式使得每個子cifar數據bin文件的結構相同,所以cifar轉換數據代碼比mnist的代碼更加的模塊化,分為源數據讀取模塊(image_read函數),把lmdb(leveldb)數據轉換的變量聲明,句柄(函數)調用都放到定義的caffe::db子空間中,這樣簡化了代碼,而且使得代碼更加清晰。

 

一:程序開始

和轉換mnist數據不同的是,cifar並沒有使用gflags命令行解析工具;所以也沒有通過gflags的宏定義來指定要轉換的數據類型,而是把轉換的類型參數直接作為main()函數的參數(這種方式便於理解)。

在Create.sh文件中,調用convert_cifar_data.bin語句為:

./build/examples/cifar10/convert_cifar_data.bin$DATA $EXAMPLE $DBTYPE

convert_cifar_data.bin程序,程序需要3個參數,分別為源數據路徑,lmdb(leveldb)存儲路徑,要轉換的數據類型lmdb or leveldb

二:數據轉換流程圖

 

三:convert_cifar_data.cpp函數分析

 

1引入必要的頭文件和命名空間

 

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. #include <fstream>   
  2. #include <string>  
  3. #include "boost/scoped_ptr.hpp"  
  4. #include "glog/logging.h"  
  5. #include "google/protobuf/text_format.h"  
  6. #include "stdint.h"  
  7. #include "caffe/proto/caffe.pb.h"  
  8. #include "caffe/util/db.hpp"  

頭文件和convert_mnist_data.cpp的區別:

 

1,沒有引入gflags命令行解析工具;

2,沒有引入leveldb和lmdb的數據頭文件

3,引入了"boost/scoped_ptr.hpp"智能指針頭文件

4,引入"caffe/util/db.hpp"頭文件,里面包裝了對lmdb和leveldb數據對象的操作內容

 

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. using caffe::Datum;  
  2. using boost::scoped_ptr;  
  3. using std::string;  
  4. namespace db = caffe::db;  

命名空間區別:

 

1,沒有引入全部caffe命名空間,而是局部引入了兩個caffe命名空間下的子空間 caffe::Datum和caffe::db

2,引入boost::scoped_ptr;智能指針命名空間,智能指針,它能夠保證在離開作用域后對象被自動釋放;在mnist數據轉換代碼中,經常出現delete batch等刪除臨時變量的指令,通過智能指針可以自動刪除過期的變量,對於控制程序內存占用很實用。

2 main()函數

接收參數,調用轉換函數convet_dataset()

3 convet_dataset()函數

3.1以智能指針的方式創建db::DB類型的對象 train_db

 

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. scoped_ptr<db::DB>  train_db(db::GetDB(db_type));  

智能指針的創建方式類似泛型的格式,上面通過db.cpp內定義的命名的子命名空間中db的“成員函數”GetDB函數來初始化train_db對象

 

3.2 創建lmdb數據對象

3.2.1創建環境;設置環境參數,打開環境

調用tran_db對象的open方法,以“對db::NEW的方式,創建lmdb(leveldb)類型文件

 

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. train_db->Open(output_folder+ "/cifar10_train_" + db_type,db::NEW);  

db命名空間中open函數具體實現代碼:

 

 

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. void LMDB::Open(const string& source, Mode mode) {  
  2.   MDB_CHECK(mdb_env_create(&mdb_env_));//創建lmdb操作環境  
  3.  MDB_CHECK(mdb_env_set_mapsize(mdb_env_, LMDB_MAP_SIZE));//設置環境內訓映射  
  4.   if (mode == NEW) {  
  5.     CHECK_EQ(mkdir(source.c_str(),0744), 0) << "mkdir " << source <<"failed";  
  6.   }//檢查文件  
  7.   int flags = 0;  
  8.   if (mode == READ) {  
  9.     flags = MDB_RDONLY | MDB_NOTLS;  
  10.   }  
  11.   MDB_CHECK(mdb_env_open(mdb_env_,source.c_str(), flags, 0664));//打開創建的環境  
  12.   LOG(INFO) << "Openedlmdb " << source;  
  13. }  

3.2.2創建並打開transaction操作句柄,打開數據庫句柄

 

調用db命名空間中的Transaction方法,來創建句柄對象txn

scoped_ptr<db::Transaction>  txn(train_db->NewTransaction());

db命名空間中NewTransaction()函數代碼

//在lmdb環境中創建操作句柄

 

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. LMDBTransaction* LMDB::NewTransaction() {  
  2.   MDB_txn* mdb_txn;  
  3.   MDB_CHECK(mdb_txn_begin(mdb_env_,NULL, 0, &mdb_txn));//創建操作句柄  
  4.   MDB_CHECK(mdb_dbi_open(mdb_txn,NULL, 0, &mdb_dbi_));//打開數據庫環境  
  5.   return new LMDBTransaction(&mdb_dbi_,mdb_txn);  
  6. }  

3.3 定義數據結構文件

 

 

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. const int kCIFARSize =32;  
  2. const intkCIFARImageNBytes = 3072; //32*32=1024,RGB各占一個字節,感覺應該為uint8_t,0~255,  
  3. const intkCIFARBatchSize = 10000; //cifar共計5萬個訓練樣本,分成5份batches,每份1萬個  
  4. const int kCIFARTrainBatches= 5;  
  5.   // Data buffer  
  6.   int label;  
  7.   charstr_buffer[kCIFARImageNBytes]; //定義字符數組,一個數組可以存放一張圖片的數據  
  8.   Datum datum;  
  9.   datum.set_channels(3);  
  10.   datum.set_height(kCIFARSize);  
  11.   datum.set_width(kCIFARSize);  

3.4 打開源數據文件

 

下載的Cifar數據存放在6個bin文件內,從data_batch_1.bin到data_batch_5.bin;本文以循環的方式分別讀取每個bin文件。每個bin文件存儲1萬張圖片

 

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. for (int fileid = 0;fileid < kCIFARTrainBatches; ++fileid) {  
  2. snprintf(str_buffer, kCIFARImageNBytes, "/data_batch_%d.bin", fileid + 1);  
  3. std::ifstream data_file((input_folder + str_buffer).c_str(),std::ios::in| std::ios::binary);  
  4.     CHECK(data_file) << "Unable to open train file #" <<fileid + 1;  
  5.          //str_buffer=/data_batch_1.bin,等等,但str_buffer是個字符數組  
  6.          //以二進制和流輸入的方式打開文件data/cifar10/data_batch_1.bin  
  7.          //c_str() 以 char* 形式傳回 string 內含字符串  

3.5 讀取源數據文件

 

和mnist不同的是,mnist源數據集有4個文件;mnist讀取數據時,分別調用文件讀取函數read(),感覺這是由於mnist源數據中label數據和image數據中存儲的內容不統一,image文件中除了存儲圖像數據外,還存儲了圖像結構數據;而圖像結構數據和圖像數據讀取的方式不一樣,而且還涉及到大端小端的轉換;所以沒有定義一個統一的圖像讀取函數來讀取;本項由於image和標簽數據都存儲在同一個bin文件中,所以可以定義統一的圖片讀取函數read_image來讀取源數據內容。

 

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. for (int itemid = 0;itemid < kCIFARBatchSize; ++itemid) {  
  2.       read_image(&data_file, &label,str_buffer);  
  3. //調用read_image函數從.bin文件讀取數據,通過指針賦值給label和str_buffer  
  4. void read_image(std::ifstream* file,int* label, char*buffer) {  
  5.          charlabel_char;  
  6.          file->read(&label_char, 1);  
  7. //讀取label_char的內容;CIFAR10數據應該是一個類似結構體的數據對,有label和data兩個屬性,其中label用label_char來定義的  
  8.          *label = label_char; //把label_char的值,給label  
  9.          file->read(buffer,kCIFARImageNBytes);  
  10.          return;  
  11.          }  

3.6 讀取的數據賦值到“轉換”數據對象datum,並序列化

 

 

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. datum.set_label(label);  
  2. datum.set_data(str_buffer,kCIFARImageNBytes);  
  3. string out;  
  4. CHECK(datum.SerializeToString(&out));  

3.7 把數據寫入數據庫

 

 

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. int length =snprintf(str_buffer, kCIFARImageNBytes, "%05d",fileid *kCIFARBatchSize + itemid);  

//上一行代碼有兩個作用:

 

1,把fileid * kCIFARBatchSize + itemid的值賦值給str_buffer,此處的賦值為每個樣本(圖片)的id,

2,給length賦值,此處length=5

 

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. string out;  
  2. txn->Put(string(str_buffer, length),out);//string(str_buffer, length)用來截取str_buffer的前length個字符;  

//db命名空間中,Put函數代碼;

 

 

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. void LMDBTransaction::Put(conststring& key,const string& value) {  
  2.   MDB_val mdb_key, mdb_value;//聲明MDB_val不透明類型數據結構“對象”  
  3.   mdb_key.mv_data = const_cast<char*>(key.data());//通過指針的方式給mdb_key賦值  
  4.   mdb_key.mv_size = key.size();  
  5.   mdb_value.mv_data = const_cast<char*>(value.data());  
  6.   mdb_value.mv_size = value.size();  
  7.   MDB_CHECK(mdb_put(mdb_txn_, *mdb_dbi_,&mdb_key, &mdb_value, 0));  
  8. //通過mdb_put()句柄把mdb_key和mdb_value中的數據,寫入數據庫中  
  9. }  

3.8 把數據庫寫入lmdb文件並關閉寫入環境

 

//這個commit函數和close函數,不是在caffe:db命名空間中定義的函數,估計是caffe命名空間中自帶的函數。

 

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. txn->Commit();  
  2. train_db->Close();  

3.9用上面類似的方法把測試集寫入lmdb文件中

 

 

四,相關文件

convert_cifar10_data.cpp文件 (caffe-master/examples/cifar10/)

 

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. // This script converts the CIFAR dataset to the leveldb format used  
  2. // by caffe to perform classification.  
  3. // Usage:  
  4. //    convert_cifar_data input_folder output_db_file  
  5. // The CIFAR dataset could be downloaded at  
  6. //    http://www.cs.toronto.edu/~kriz/cifar.html  
  7.   
  8. #include <fstream>  // NOLINT(readability/streams),文件輸入輸出必備的文件流  
  9. #include <string>  
  10.   
  11. #include "boost/scoped_ptr.hpp"//智能指針  
  12. #include "glog/logging.h"//用於日志記錄,具體記錄什么不是很清楚,  
  13. #include "google/protobuf/text_format.h"//用於解析.prototxt文件的  
  14. #include "stdint.h"  
  15.   
  16. #include "caffe/proto/caffe.pb.h" //解析.prototxt文件的頭文件  
  17. #include "caffe/util/db.hpp" //db.cpp文件中定義了NewTransaction(),Open()等leveldb和lmdb操作函數  
  18.   
  19. using caffe::Datum;  
  20. using boost::scoped_ptr;//是一個簡單的智能指針,它能夠保證在離開作用域后對象被自動釋放。  
  21. using std::string;  
  22. namespace db = caffe::db;//引入caffe命名空間中的db子命名空間  
  23.   
  24. const int kCIFARSize = 32;  
  25. const int kCIFARImageNBytes = 3072;//32*32=1024,RGB各占一個字節,感覺應該為uint8_t,0~255,  
  26. const int kCIFARBatchSize = 10000;//cifar共計5萬個訓練樣本,分成5份batches,每份1萬個,  
  27. const int kCIFARTrainBatches = 5;  
  28.   
  29.   
  30.  void read_image(std::ifstream* file, int* label, char* buffer) {  
  31.     char label_char;  
  32.     file->read(&label_char, 1);//讀取label_char的內容;CIFAR10數據應該是一個類似結構體的數據對,有label和data兩個屬性,其中label用label_char來定義的  
  33.     *label = label_char;//把label_char的值,給label  
  34.     file->read(buffer, kCIFARImageNBytes);  
  35.     return;  
  36.     }  
  37.   
  38. //以值引用的方式傳遞參數(string& input_folder),  
  39. void convert_dataset(const string& input_folder, const string& output_folder,  
  40.     const string& db_type) {  
  41.   scoped_ptr<db::DB> train_db(db::GetDB(db_type));//以智能指針的方式創建db::DB類型的對象 train_db ,這個db::DB是什么東西有些不清楚,db.cpp中並沒有發現這個DB類型的命名空間。  
  42.   train_db->Open(output_folder + "/cifar10_train_" + db_type, db::NEW);//調用tran_db對象的open方法,以“對db::NEW的方式,創建(或打開)文件  
  43.   scoped_ptr<db::Transaction> txn(train_db->NewTransaction());//這個transaction暫時不清楚是干什么用的  
  44.   // Data buffer  
  45.   int label;  
  46.   char str_buffer[kCIFARImageNBytes];//定義字符數組,一個數組可以存放一張圖片的數據  
  47.   Datum datum;  
  48.   datum.set_channels(3);  
  49.   datum.set_height(kCIFARSize);  
  50.   datum.set_width(kCIFARSize);  
  51.   
  52.   LOG(INFO) << "Writing Training data";  
  53.   for (int fileid = 0; fileid < kCIFARTrainBatches; ++fileid) {//依次遍歷每個batches,共計5個  
  54.     // Open files  
  55.     LOG(INFO) << "Training Batch " << fileid + 1;  
  56.     snprintf(str_buffer, kCIFARImageNBytes, "/data_batch_%d.bin", fileid + 1); //str_buffer=/data_batch_1.bin,等等,但str_buffer是個字符數組  
  57.     std::ifstream data_file((input_folder + str_buffer).c_str(),//以二進制和流輸入的方式打開文件data/cifar10/data_batch_1.bin  
  58.         std::ios::in | std::ios::binary);//c_str() 以 char* 形式傳回 string 內含字符串  
  59.     CHECK(data_file) << "Unable to open train file #" << fileid + 1;  
  60.     for (int itemid = 0; itemid < kCIFARBatchSize; ++itemid) {  
  61.       read_image(&data_file, &label, str_buffer);//調用read_image函數從.bin文件讀取數據,給label和str_buffer賦值  
  62.       datum.set_label(label);  
  63.       datum.set_data(str_buffer, kCIFARImageNBytes);  
  64.       int length = snprintf(str_buffer, kCIFARImageNBytes, "%05d",  
  65.           fileid * kCIFARBatchSize + itemid);//給str_buffer賦值,此處的賦值為每個樣本(圖片)的id,length=5;其實是把str_buffer的前5個字符賦值為id  
  66.       string out;  
  67.       CHECK(datum.SerializeToString(&out));  
  68.       txn->Put(string(str_buffer, length), out);//string(str_buffer, length)用來截取str_buffer的前length個字符;  
  69.     }  
  70.   }  
  71.   txn->Commit();  
  72.   train_db->Close();  
  73.   
  74.   LOG(INFO) << "Writing Testing data";  
  75.   scoped_ptr<db::DB> test_db(db::GetDB(db_type));  
  76.   test_db->Open(output_folder + "/cifar10_test_" + db_type, db::NEW);  
  77.   txn.reset(test_db->NewTransaction());  
  78.   // Open files  
  79.   std::ifstream data_file((input_folder + "/test_batch.bin").c_str(),  
  80.       std::ios::in | std::ios::binary);  
  81.   CHECK(data_file) << "Unable to open test file.";  
  82.   for (int itemid = 0; itemid < kCIFARBatchSize; ++itemid) {  
  83.     read_image(&data_file, &label, str_buffer);  
  84.     datum.set_label(label);  
  85.     datum.set_data(str_buffer, kCIFARImageNBytes);  
  86.     int length = snprintf(str_buffer, kCIFARImageNBytes, "%05d", itemid);  
  87.     string out;  
  88.     CHECK(datum.SerializeToString(&out));  
  89.     txn->Put(string(str_buffer, length), out);  
  90.   }  
  91.   txn->Commit();  
  92.   test_db->Close();  
  93. }  
  94.   
  95. int main(int argc, char** argv) {  
  96.   if (argc != 4) {  
  97.     printf("This script converts the CIFAR dataset to the leveldb format used\n"  
  98.            "by caffe to perform classification.\n"  
  99.            "Usage:\n"  
  100.            "    convert_cifar_data input_folder output_folder db_type\n"  
  101.            "Where the input folder should contain the binary batch files.\n"  
  102.            "The CIFAR dataset could be downloaded at\n"  
  103.            "    http://www.cs.toronto.edu/~kriz/cifar.html\n"  
  104.            "You should gunzip them after downloading.\n");  
  105.   } else {  
  106.     google::InitGoogleLogging(argv[0]);  
  107.     convert_dataset(string(argv[1]), string(argv[2]), string(argv[3]));  
  108.     //sh文件傳遞的參數:./build/examples/cifar10/convert_cifar_data.bin $DATA $EXAMPLE $DBTYPE ,依次為argv[0] argv[1] argv[2] argv[3];  
  109.     //即執行程序名稱,原始數據存放位置,轉換后數據保存的位置,轉換的數據類型lmdb,以上參數都是以字符串形式進行傳遞的。  
  110.   }  
  111.   return 0;  
  112. }  

db.cpp 文件(caffe-master/src/caffe/util)

 

里面定義了caffe名字空間和其子空間db

 

[cpp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
  1. #include "caffe/util/db.hpp"  
  2.   
  3. #include <sys/stat.h>  
  4. #include <string>  
  5.   
  6. namespace caffe { namespace db {  
  7.   
  8. const size_t LMDB_MAP_SIZE = 1099511627776;  // 1 TB  
  9.   
  10. //在制定位置以options方式創建(或打開)leveldb類型數據文件,並檢查是否打開成功  
  11. void LevelDB::Open(const string& source, Mode mode) {  
  12.   leveldb::Options options;//創建leveldb中的options類型對象  
  13.   options.block_size = 65536;  
  14.   options.write_buffer_size = 268435456;  
  15.   options.max_open_files = 100;  
  16.   options.error_if_exists = mode == NEW;//mode=NEW時,是創建新leveldb類型文件,所以如果該文件以存在則報錯  
  17.   options.create_if_missing = mode != READ;//  
  18.   leveldb::Status status = leveldb::DB::Open(options, source, &db_);//通過leveldb空間中的DB子空間中的Open函數來創建(或打開)leveldb類型文件  
  19.   CHECK(status.ok()) << "Failed to open leveldb " << source  
  20.                      << std::endl << status.ToString();  
  21.   LOG(INFO) << "Opened leveldb " << source;  
  22. }  
  23.   
  24.   
  25. //Open函數主要負責,創建環境;設置環境參數,打開環境  
  26. void LMDB::Open(const string& source, Mode mode) {  
  27.   MDB_CHECK(mdb_env_create(&mdb_env_));//創建lmdb操作環境  
  28.   MDB_CHECK(mdb_env_set_mapsize(mdb_env_, LMDB_MAP_SIZE));//設置環境內訓映射  
  29.   if (mode == NEW) {  
  30.     CHECK_EQ(mkdir(source.c_str(), 0744), 0) << "mkdir " << source << "failed";  
  31.   }//檢查文件  
  32.   int flags = 0;  
  33.   if (mode == READ) {  
  34.     flags = MDB_RDONLY | MDB_NOTLS;  
  35.   }  
  36.   MDB_CHECK(mdb_env_open(mdb_env_, source.c_str(), flags, 0664));//打開創建的環境  
  37.   LOG(INFO) << "Opened lmdb " << source;  
  38. }  
  39.   
  40. LMDBCursor* LMDB::NewCursor() {  
  41.   MDB_txn* mdb_txn;  
  42.   MDB_cursor* mdb_cursor;  
  43.   MDB_CHECK(mdb_txn_begin(mdb_env_, NULL, MDB_RDONLY, &mdb_txn));  
  44.   MDB_CHECK(mdb_dbi_open(mdb_txn, NULL, 0, &mdb_dbi_));  
  45.   MDB_CHECK(mdb_cursor_open(mdb_txn, mdb_dbi_, &mdb_cursor));  
  46.   return new LMDBCursor(mdb_txn, mdb_cursor);  
  47. }  
  48.   
  49. //在lmdb環境中創建操作句柄  
  50. LMDBTransaction* LMDB::NewTransaction() {  
  51.   MDB_txn* mdb_txn;  
  52.   MDB_CHECK(mdb_txn_begin(mdb_env_, NULL, 0, &mdb_txn));//創建操作句柄  
  53.   MDB_CHECK(mdb_dbi_open(mdb_txn, NULL, 0, &mdb_dbi_));//打開數據庫環境  
  54.   return new LMDBTransaction(&mdb_dbi_, mdb_txn);  
  55. }  
  56.   
  57. void LMDBTransaction::Put(const string& key, const string& value) {  
  58.   MDB_val mdb_key, mdb_value;  
  59.   mdb_key.mv_data = const_cast<char*>(key.data());  
  60.   mdb_key.mv_size = key.size();  
  61.   mdb_value.mv_data = const_cast<char*>(value.data());  
  62.   mdb_value.mv_size = value.size();  
  63.   MDB_CHECK(mdb_put(mdb_txn_, *mdb_dbi_, &mdb_key, &mdb_value, 0));  
  64. }  
  65.   
  66. DB* GetDB(DataParameter::DB backend) {  
  67.   switch (backend) {  
  68.   case DataParameter_DB_LEVELDB:  
  69.     return new LevelDB();  
  70.   case DataParameter_DB_LMDB:  
  71.     return new LMDB();  
  72.   default:  
  73.     LOG(FATAL) << "Unknown database backend";  
  74.   }  
  75. }  
  76.   
  77. //創建cafe::db“命名空間”類型對象,cafe::db“命名空間”中包含了各種數據操作函數  
  78. DB* GetDB(const string& backend) {  
  79.   if (backend == "leveldb") {  
  80.     return new LevelDB();  
  81.   } else if (backend == "lmdb") {  
  82.     return new LMDB();  
  83.   } else {  
  84.     LOG(FATAL) << "Unknown database backend";  
  85.   }  
  86. }  
  87.   
  88. }  // namespace db  
  89. }  // namespace caffe  


五,以上代碼注釋為個人理解,如有遺漏,錯誤還望大家多多交流,指正,以便共同學習,進步!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM