Caffe: LMDB 及其数据转换
Preface
這兩天文章也看了不少,Caffe、Theano、Torch 也都用過(guò)。其實(shí)個(gè)人認(rèn)為,這本書(shū)對(duì)于已經(jīng)深入這個(gè)領(lǐng)域已定時(shí)間的人來(lái)說(shuō),幫助不大。本書(shū)講述的只是“術(shù)“,有點(diǎn)像深度學(xué)習(xí)的說(shuō)明書(shū),講的很淺。
但是翻了一翻,還是有點(diǎn)收獲的,這個(gè) MNIST 手寫(xiě)數(shù)字識(shí)別是深度學(xué)習(xí)入門(mén)很經(jīng)典的例子。基本上所有的深度學(xué)習(xí)框架,在讓初學(xué)者入門(mén)使用的時(shí)候都有這個(gè)例子。
我一直對(duì) Caffe 中使用的 LMDB、LEVELDB 數(shù)據(jù)組織比較疑惑,很多時(shí)候不明白該怎么樣組織圖像數(shù)據(jù)、以及其對(duì)應(yīng)的標(biāo)簽。之前都是按照別人的代碼生成的,自己其實(shí)懵懵的。所以,我想通過(guò) MNIST 輸入數(shù)據(jù)生成過(guò)程,熟悉一下 LMDB、LEVELDB 的基本使用方法。
那下面就進(jìn)入正題,下面是我看的筆記。
?
MNIST 數(shù)據(jù)集
MINIST(Mixed National Institute of Stanfords and Technology)是一個(gè)大型的手寫(xiě)數(shù)字?jǐn)?shù)據(jù)庫(kù),廣泛用于機(jī)器學(xué)習(xí)領(lǐng)域的訓(xùn)練和測(cè)試,由紐約大學(xué) Yann LeCun 教授整理。MNIST 包括 60000個(gè)訓(xùn)練集和 10000個(gè)測(cè)試集,每張圖都已經(jīng)進(jìn)行尺寸歸一化,數(shù)字居中處理,固定尺寸為 28×28。如下圖所示:
?
MNIST 數(shù)據(jù)格式描述
MNIST 具體的文件格式描述如下面的表所示:
MNIST 原始數(shù)據(jù)文件?
?
訓(xùn)練集圖片文件格式描述(train-images-idx3-ubyte)?
?
訓(xùn)練集標(biāo)簽文件格式描述(train-labels-idx1-ubyte)?
?
測(cè)試集圖片文件格式描述(t10k-images-idx3-ubyte)?
?
測(cè)試集標(biāo)簽文件格式描述(t10k-labels-idx1-ubyte)?
注意:圖片文件中像素按行組織,像素值 0表示背景(白色),像素值 255表示前景(黑色)。
?
轉(zhuǎn)換格式、create_mnist_data.cpp 源碼解析
先說(shuō)一下 Caffe 為什么采用 LMDB、LEVELDB,而不是直接讀取原始數(shù)據(jù)?
原因是,一方面,數(shù)據(jù)類(lèi)型多種多樣,有二進(jìn)制文件、文本文件、編碼后的圖像文件(如 JPEG、PNG、網(wǎng)絡(luò)爬取的數(shù)據(jù)等),不可能用一套代碼實(shí)現(xiàn)所有類(lèi)型的輸入數(shù)據(jù)讀取,轉(zhuǎn)換為統(tǒng)一格式可以簡(jiǎn)化數(shù)據(jù)讀取層的實(shí)現(xiàn);
另一方面,使用 LMDB、LEVELDB 可以提高磁盤(pán) IO 利用率。
?
轉(zhuǎn)換格式
下載到的原始數(shù)據(jù)為二進(jìn)制文件,需要轉(zhuǎn)換為 LEVELDB 或 LMDB 才能被 Caffe 識(shí)別。
我們 Git 得到的 Caffe 中,在 examples/mnist/ 下有一個(gè)腳本文件:create_mnist.sh ,這個(gè)就可以將原始的二進(jìn)制數(shù)據(jù),生成 LMDB 格式數(shù)據(jù)。
運(yùn)行后,會(huì)生成 examples/mnist/mnist_train_lmdb/ 和 examples/mnist/mnist_test_lmdb/ 這兩個(gè)目錄。每個(gè)目錄下都有兩個(gè)文件:data.mdb 和 lock.mdb。
看一下腳本文件:create_mnist.sh 里面是什么:
#!/usr/bin/env sh # This script converts the mnist data into lmdb/leveldb format, # depending on the value assigned to $BACKEND.EXAMPLE=examples/mnist DATA=data/mnist BUILD=build/examples/mnistBACKEND="lmdb"echo "Creating ${BACKEND}..."rm -rf $EXAMPLE/mnist_train_${BACKEND} rm -rf $EXAMPLE/mnist_test_${BACKEND}$BUILD/convert_mnist_data.bin $DATA/train-images-idx3-ubyte \$DATA/train-labels-idx1-ubyte $EXAMPLE/mnist_train_${BACKEND} --backend=${BACKEND} $BUILD/convert_mnist_data.bin $DATA/t10k-images-idx3-ubyte \$DATA/t10k-labels-idx1-ubyte $EXAMPLE/mnist_test_${BACKEND} --backend=${BACKEND}echo "Done."?
create_mnist_data.cpp 源碼解析
可以看到,上面腳本最核心的部分,就是調(diào)用 convert_mnist_data.bin 這個(gè)可執(zhí)行程序,對(duì)應(yīng)的源文件為 examples/mnist/convert_mnist_data.cpp,對(duì)這個(gè)源代碼的解讀如下,深入這段代碼可以更清楚的了解 LMDB 是如何生成的。
// 這段代碼將 MNIST 數(shù)據(jù)集轉(zhuǎn)換為(默認(rèn)的)lmdb 或者 leveldb(--backend=leveldb) 格式,用于在使用 caffe 的時(shí)候讀取數(shù)據(jù) // 使用方法: // convert_mnist_data [FLAGS] input_image_file input_label_file output_db_file// gflags: 命令行參數(shù)解析頭文件 #include // glog: 記錄程序日志頭文件 #include // 解析 *.prototxt 文件 #include #include #include #include #include #include #include // NOLINT(readability/streams) #include // 解析caffe中proto類(lèi)型文件的頭文件 #include "caffe/proto/caffe.pb.h"using namespace caffe; // NOLINT(build/namespace) using std::string;// GFLAGS 工具定義命令行選項(xiàng) backend, 默認(rèn)值為 lmdb, 即: --backend=lmdb DEFINE_string(backend, "lmdb", "The backend for storing the result");// 大小端轉(zhuǎn)換, MNIST 原始數(shù)據(jù)文件中 32 位整型值為大端存儲(chǔ), C/C++ 變量為小端存儲(chǔ),因此需要加入轉(zhuǎn)換機(jī)制 uint32_t swap_endian(uint32_t val) {val = ((val << 8) & 0xFF00FF00) | ((val >> 8) & 0xFF00FF);return (val << 16) | (val >> 16); }// 轉(zhuǎn)換數(shù)據(jù)集函數(shù) void convert_dataset(const char* image_filename, const char* label_filename,const char* db_path, const string& db_backend) {// 用 C++ 輸入文件流以二進(jìn)制方式打開(kāi)// 定義, 打開(kāi)圖像文件 對(duì)象: image_file(讀入的文件名, 讀入方式), 此處以二進(jìn)制的方式std::ifstream image_file(image_filename, std::ios::in | std::ios::binary);// 定義, 打開(kāi)標(biāo)簽文件 對(duì)象: label_file(讀入的文件名, 讀入方式), 此處以二進(jìn)制的方式std::ifstream label_file(label_filename, std::ios::in | std::ios::binary);// CHECK: 用于檢測(cè)文件能否正常打開(kāi)函數(shù)CHECK(image_file) << "Unable to open file " << image_filename;CHECK(label_file) << "Unable to open file " << label_filename;// 讀取魔數(shù)與基本信息// uint32_t 用 typedef 來(lái)自定義的一種數(shù)據(jù)類(lèi)型, unsigned int32, 每個(gè)int32整數(shù)占用4個(gè)字節(jié), 這樣做是為了程序的可擴(kuò)展性u(píng)int32_t magic; // 魔數(shù)uint32_t num_items; // 文件包含條目總數(shù) uint32_t num_labels; // 標(biāo)簽值uint32_t rows; // 行數(shù)uint32_t cols; // 列數(shù)// 讀取魔數(shù): magic// image_file.read( 讀取內(nèi)容的指針, 讀取的字節(jié)數(shù) ) , magic 是一個(gè) int32 類(lèi)型的整數(shù),每個(gè)占 4 個(gè)字節(jié),所以這里指定為 4// reinterpret_cast 為 C++ 中定義的強(qiáng)制轉(zhuǎn)換符, 這里把 &magic, 即 magic 的地址(一個(gè) 16 進(jìn)制的數(shù)), 轉(zhuǎn)變成 char 類(lèi)型的指針image_file.read(reinterpret_cast(&magic), 4);// 大端到小端的轉(zhuǎn)換magic = swap_endian(magic);// 校驗(yàn)圖像文件中魔數(shù)是否為 2051, 不是則報(bào)錯(cuò)CHECK_EQ(magic, 2051) << "Incorrect image file magic.";// 同理, 校驗(yàn)標(biāo)簽文件中的魔數(shù)是否為 2049, 不是則報(bào)錯(cuò)label_file.read(reinterpret_cast(&magic), 4);magic = swap_endian(magic);CHECK_EQ(magic, 2049) << "Incorrect label file magic.";// 讀取圖片的數(shù)量: num_itemsimage_file.read(reinterpret_cast(&num_items), 4);num_items = swap_endian(num_items); // 大端到小端轉(zhuǎn)換// 讀取圖片標(biāo)簽的數(shù)量label_file.read(reinterpret_cast(&num_labels), 4);num_labels = swap_endian(num_labels); // 大端到小端轉(zhuǎn)換// 圖片數(shù)量應(yīng)等于其標(biāo)簽數(shù)量, 檢查兩者是否相等CHECK_EQ(num_items, num_labels);// 讀取圖片的行大小image_file.read(reinterpret_cast(&rows), 4);rows = swap_endian(rows); // 大端到小端轉(zhuǎn)換// 讀取圖片的列大小image_file.read(reinterpret_cast(&cols), 4);cols = swap_endian(cols); // 大端到小端轉(zhuǎn)換// lmdb 相關(guān)句柄MDB_env *mdb_env;MDB_dbi mdb_dbi;MDB_val mdb_key, mdb_data;MDB_txn *mdb_txn;// leveldb 相關(guān)句柄leveldb::DB* db;leveldb::Options options;options.error_if_exists = true;options.create_if_missing = true;options.write_buffer_size = 268435456;level::WriteBatch* batch = NULL;// 打開(kāi) dbif (db_backend == "leveldb") { // leveldbLOG(INFO) << "Opening leveldb " << db_path;leveldb::Status status = leveldb::DB::Open(options, db_path, &db);CHECK(status.ok()) << "Failed to open leveldb " << db_path << ". Is it already existing?";batch = new leveldb::WriteBatch();}else if (db_backend == "lmdb") { // lmdbLOG(INFO) << "Opening lmdb " << db_path;CHECK_EQ(mkdir(db_path, 0744), 0) << "mkdir " << db_path << "failed";CHECK_EQ(mdb_env_create(&mdb_env), MDB_SUCCESS) << "mdb_env_create failed";CHECK_EQ(mdb_env_set_mapsize(mdb_env, 1099511627776), MDB_SUCCESS) << "mdb_env_set_mapsize failed"; // 1TBCHECK_EQ(mdb_env_open(mdb_env, db_path, 0, 0664), MDB_SUCCESS) << "mdb_env_open_failed";CHECK_EQ(mdb_txn_begin(mdb_env, NULL, 0, &mdb_txn), MDB_SUCCESS) << "mdb_txn_begin failed";CHECK_EQ(mdb_open(mdb_txn, NULL, 0, &mdb_dbi), MDB_SUCCESS) << "mdb_open failed. Does the lmdb already exist?";} else {LOG(FATAL) << "Unknown db backend " << db_backend;}// 將讀取數(shù)據(jù)保存至 dbchar label;char* pixels = new char[rows * cols];int count = 0;const int kMaxKeyLength = 10;char key_cstr[kMaxKeyLength];string value;// 設(shè)置datum數(shù)據(jù)對(duì)象的結(jié)構(gòu),其結(jié)構(gòu)和源圖像結(jié)構(gòu)相同Datum datum;datum.set_channels(1);datum.set_height(rows);datum.set_width(cols);// 輸出 Log, 輸出圖片總數(shù)LOG(INFO) << "A total of " << num_items << " items.";// 輸出 Log, 輸出圖片的行、列大小LOG(INFO) << "Rows: " << rows << " Cols: " << cols;// 讀取圖片數(shù)據(jù)以及 label 存入 protobuf 定義好的數(shù)據(jù)結(jié)構(gòu)中,// 序列化成字符串儲(chǔ)存到數(shù)據(jù)庫(kù)中,// 這里為了減少單次操作帶來(lái)的帶寬成本(驗(yàn)證數(shù)據(jù)包完整等), // 每 1000 次執(zhí)行一次操作for (int item_id = 0; item_id < num_items; ++item_id) {// 從數(shù)據(jù)中讀取 rows * cols 個(gè)字節(jié), 圖像中一個(gè)像素值(應(yīng)該是 int8 類(lèi)型)用一個(gè)字節(jié)表示即可image_file.read(pixels, rows * cols);// 讀取標(biāo)簽label_file.read(&label, 1);// set_data 函數(shù), 把源圖像值放入 datum 對(duì)象datum.set_data(pixels, rows*cols);// set_label 函數(shù), 把標(biāo)簽值放入 datumdatum.set_label(label);// snprintf(str1, size_t, "format", str), 把 str 按照 format 的格式以字符串的形式寫(xiě)入 str1, size_t 表示寫(xiě)入的字符個(gè)數(shù) // 這里是把 item_id 轉(zhuǎn)換成 8 位長(zhǎng)度的十進(jìn)制整數(shù),然后在變成字符串復(fù)制給 key_str, 如:item_id=1500(int), 則 key_cstr = 00015000(string, \0為字符串結(jié)束標(biāo)志)snprintf(key_cstr, kMaxKeyLength, "%08d", item_id);datum.SerializeToString(&value);// 感覺(jué)是將 datum 中的值序列化成字符串,保存在變量 value 內(nèi),通過(guò)指針來(lái)給 value 賦值string keystr(key_cstr);// 放到數(shù)據(jù)庫(kù)中if (db_backend == "leveldb") { // leveldb// 通過(guò) batch 中的子方法 Put, 把數(shù)據(jù)寫(xiě)入 datum 中(此時(shí)在內(nèi)存中)batch->Put(keystr, value);} else if (db_backend == "lmdb") { // lmdb// mv 應(yīng)該是 move value, 應(yīng)該是和 write() 和 read() 函數(shù)文件讀寫(xiě)的方式一樣, 以固定的子節(jié)長(zhǎng)度按照地址進(jìn)行讀寫(xiě)操作// 獲取 value 的子節(jié)長(zhǎng)度, 類(lèi)似 sizeof() 函數(shù)mdb_data.mv_size = value.size()// 把 value 的首個(gè)字符地址轉(zhuǎn)換成空類(lèi)型的指針mdb_data.mv_data = reinterpret_cast(&value[0]);mdb_key.mv_size = keystr.size();mdb_key.mv_data = reinterpret_cast(&keystr[0]);// 通過(guò) mdb_put 函數(shù)把 mdb_key 和 mdb_data 所指向的數(shù)據(jù), 寫(xiě)入到 mdb_dbiCHECK_EQ(mdb_put(mdb_txn, mdb_dbi, &mdb_key, &mdb_data, 0), MDB_SUCCESS) << "mdb_put failed";} else {LOG(FATAL) << "Unknown db backend " << db_back_end;}// 把 db 數(shù)據(jù)寫(xiě)入硬盤(pán)// 選擇 1000 個(gè)樣本放入一個(gè) batch 中,通過(guò) batch 以批量的方式把數(shù)據(jù)寫(xiě)入硬盤(pán)// 寫(xiě)入硬盤(pán)通過(guò) db.write() 函數(shù)來(lái)實(shí)現(xiàn)if (++count % 1000 == 0) {// 批量提交更改if(db_backend == "leveldb") { // leveldb// 把batch寫(xiě)入到 db 中,然后刪除 batch 并重新創(chuàng)建db->Write(leveldb::WriteOptions(), batch);delete batch;batch = new leveldb::WriteBatch();} else if (db_backend == "lmdb") { // lmdb// 通過(guò) mdb_txn_commit 函數(shù)把 mdb_txn 數(shù)據(jù)寫(xiě)入到硬盤(pán)CHECK_EQ(mdb_txn_commit(mdb_txn), MDB_SUCCESS) << "mdb_txn_commit failed";// 重新設(shè)置 mdb_txn 的寫(xiě)入位置, 追加(繼續(xù))寫(xiě)入CHECK_EQ(mdb_txn_begin(mdb_env, NULL, 0, &mdb_txn), MDB_SUCCESS) << "mdb_txn_begin failed";} else {LOG(FATAL) << "Unknown db backend " << db_backend;}} // if (++count % 1000 == 0) } // for (int item_id = 0; item_id < num_items; ++item_id)// 寫(xiě)最后一個(gè) batch if (count % 1000 != 0) {if (db_backend == "leveldb") { // leveldbdb->Write(leveldb::WriteOptions(), batch);delete batch;delete db; // 刪除臨時(shí)變量,清理內(nèi)存占用} else if (db_backend == "lmdb") { // lmdbCHECK_EQ(mdb_txn_commit(mdb_txn), MDB_SUCCESS) << "mdb_txn_commit failed";// 關(guān)閉 mdb 數(shù)據(jù)對(duì)象變量mdb_close(mdb_env, mdb_dbi);// 關(guān)閉 mdb 操作環(huán)境變量mdb_env_close(mdb_env);} else {LOG(FATAL) << "Unknown db backend " << db_backend;}LOG(ERROE) << "Processed " << count << " files.";}delete[] pixels; } // void convert_dataset(const char* image_filename, const char* label_filename, const char* db_path, const string& db_backend)int main(int argc, char** argv) { #ifndef GFLAGS_GFLAGS_Hnamespace gflags = google; #endifgflags::SetUsageMessage("This script converts the MNIST dataset to \n""the lmdb/leveldb format used by Caffe to load data. \n""Usage:\n"" convert_mnist_data [FLAGS] input_image_file input_label_file ""output_db_file\n""The MNIST dataset could be downloaded at\n"" http://yann.lecun.com/exdb/mnist/\n""You should gunzip them after downloading,""or directly use the data/mnist/get_mnist.sh\n");gflags::ParseCommandLineFlags(&argc, &argv, true);// FLAGS_backend 在前面通過(guò) DEFINE_string 定義,是字符串類(lèi)型const string& db_backend = FLAGS_backend;if (argc != 4) {gflags::ShowUsageWithFlagsRestrict(argv[0], "examples/mnist/convert_mnist_data");} else {google::InitGoogleLogging(argv[0]);convert_dataset(argv[1], argv[2], argv[3], db_backend);}return 0; }?
LMDB 相關(guān)句柄
| MDB_dbi mdb_dbi | 在數(shù)據(jù)庫(kù)環(huán)境中的一個(gè)獨(dú)立的數(shù)據(jù)句柄 |
| MDB_env *mdb_env | 數(shù)據(jù)庫(kù)環(huán)境的“不透明結(jié)構(gòu)”,不透明類(lèi)型是一種靈活的類(lèi)型,他的大小是未知的 |
| MDB_val mdb_key, mdb_data | 用于從數(shù)據(jù)庫(kù)輸入輸出的通用結(jié)構(gòu) |
| MDB_txn *mdb_txn | 不透明結(jié)構(gòu)的處理句柄,所有的數(shù)據(jù)庫(kù)操作都需要處理句柄,處理句柄可指定為只讀或讀寫(xiě) |
?
LMDB 相關(guān)函數(shù)
mdb_env_create ( &mdb_env )
MDB_env *mdb_env, 環(huán)境句柄
創(chuàng)建一個(gè) lmdb 環(huán)境句柄,此函數(shù)給 mdb_env 結(jié)構(gòu)分配內(nèi)存;
釋放內(nèi)存或者關(guān)閉句柄可以通過(guò) mdb_env_close( ) 函數(shù)來(lái)操作。在使用 mdb_env_create( ) 句柄前,必須使用 mdb_env_open( ) 函數(shù)打開(kāi)
mdb_env_open ( mdb_env, db_path, 0, 0664)
打開(kāi)環(huán)境句柄,其中:
mdb_env, 是 mdb_env_create ( ) 函數(shù)返回的環(huán)境句柄
db_path, 數(shù)據(jù)庫(kù)文件隸屬的文件夾,文件夾必須存在而且是可讀的。
mdb_env_set_mapsize ( mdb_env, 1099511627776 )
設(shè)置當(dāng)前環(huán)境的內(nèi)存映射(內(nèi)存地圖)的尺寸
mdb_txn_begin ( mdb_env, NULL, 0, &mdb_txn )
在環(huán)境內(nèi)創(chuàng)建一個(gè)用來(lái)使用的“處理” transaction 句柄
MDB_env *mdb_ env, 環(huán)境句柄
MDB_txn *mdb_txn
mdb_open( mdb_txn, NULL, 0, &mdb_dbi )
mdb_put ( mdb_txn, mdb_dbi, &mdb_key, &mdb_data, 0 )
把數(shù)據(jù)條目保存到數(shù)據(jù)庫(kù);函數(shù)把 key / data(鍵值對(duì)) 保存到數(shù)據(jù)庫(kù)
MDB_txn *mdb_txn
MDB_dbi mdb_dbi
MDB_val mdb_key: key
MDB_val mdb_data: data
mdb_txn_commit ( mdb_txn )
提交所有 transaction 操作到數(shù)據(jù)庫(kù)中;
交易句柄必須是 “自由的”;
在本次調(diào)用之后,他和它本身的“光標(biāo)(指針)”不能夠被在此使用,需要再一次指定 txn
?
LMDB 流程圖
?
小端存儲(chǔ)、大端存儲(chǔ)(Little-Endian、Big-Endian)
上面的源碼中,有一個(gè)函數(shù)是進(jìn)行大端存儲(chǔ)到小端存儲(chǔ)的轉(zhuǎn)換的。這部分沒(méi)有計(jì)算機(jī)匯編的基礎(chǔ),一開(kāi)始一頭霧水……參考的一篇博客:http://www.cnblogs.com/passingcloudss/archive/2011/05/03/2035273.html
不同的CPU有不同的字節(jié)序類(lèi)型,這些字節(jié)序是指整數(shù)在內(nèi)存中保存的順序。最常見(jiàn)的有兩種:
1. Little-endian:將低序字節(jié)存儲(chǔ)在起始地址(低位編址)
2. Big-endian:將高序字節(jié)存儲(chǔ)在起始地址(高位編址)
LE(little-endian):
最符合人的思維的字節(jié)序,地址低位存儲(chǔ)值的低位 ,地址高位存儲(chǔ)值的高位 。
這種存儲(chǔ)最符合人的思維的字節(jié)序,因?yàn)閺娜说牡谝挥^感來(lái)說(shuō),低位值小,就應(yīng)該放在內(nèi)存地址小的地方,也即內(nèi)存地址低位。反之,高位值就應(yīng)該放在內(nèi)存地址大的地方,也即內(nèi)存地址高位
BE(big-endian):
最直觀的字節(jié)序,地址低位存儲(chǔ)值的高位,地址高位存儲(chǔ)值的低位
為什么說(shuō)直觀,不要考慮對(duì)應(yīng)關(guān)系,只需要把內(nèi)存地址從左到右按照由低到高的順序?qū)懗?#xff0c;把值按照通常的高位到低位的順序?qū)懗觥烧邔?duì)照,一個(gè)字節(jié)一個(gè)字節(jié)的填充進(jìn)去 。
注:×86系列的 CPU 都是 Little-Endian 的字節(jié)序。
例子1:在內(nèi)存中雙字 0x01020304(DWORD) 的存儲(chǔ)方式:
??內(nèi)存地址為:4000 4001 4002 4003
??小端存儲(chǔ): 04 03 02 01
??大端存儲(chǔ): 01 02 03 04
注:每個(gè)地址存 1 個(gè)字節(jié),每個(gè)字有 4 字節(jié)。2 位 16 進(jìn)制數(shù)是 1 個(gè)字節(jié)(0xFF = 11111111)。
例子2:如果我們將 0x1234abcd 寫(xiě)入到以 0x0000 開(kāi)始的內(nèi)存中,則結(jié)果為:
| 0x0000 | 0x12 | 0xcd |
| 0x0001 | 0x23 | 0xab |
| 0x0002 | 0xab | 0x34 |
| 0x0003 | 0xcd | 0x12 |
?
總結(jié)
以上是生活随笔為你收集整理的Caffe: LMDB 及其数据转换的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Ubuntu 16.04 LTS, 64
- 下一篇: caffe web demo 搭建