(1)Caffe源碼閱讀路線圖應該是從CAFFE_ROOT/src/caffe/proto/caffe.proto開始,瞭解各類資料結構,主要是記憶體對象和序列化磁碟檔案的一一對應關係,知道如何從磁碟Load一個對象到記憶體,以及如何將記憶體對象Save到磁碟,中間的過程實現都是由Protobuf自動完成的。
(2)第二步就是看標頭檔,不用急於去看cpp檔案,先理解整個架構。Caffe中類數目眾多,但脈絡十分清晰。在Testing時,最外層的類是Caffe::Net,包含了多個Caffe::Layer對象,而Layer對象派生出神經網路多種不同層的類(DataLayer, ConvolutionLayer, InnerProductionLayer, AccurancyLayer等),每層會有相應的輸入輸出(Blob對象)以及層的參數(可選,Blob對象);Blob中包括了SyncedMemory對象,統一了CPU和GPU儲存空間。自頂向下去看這些類,結合理論知識很容易掌握使用方法。
(3)第三步就是有針對性地去看cpp和cu檔案了。一般而言,Caffe架構不需要修改,只需要增加新的層實現即可。例如你想自己實現卷積層,只需從ConvolutionLayer派生一個新類MyConvolutionLayer,然後將幾個虛函數改成自己的實現即可。所以這一階段關注點在演算法上,而不是源碼本身。
(4)第四步就很自由了,可以編寫各類工具,整合到Caffe內部。在CAFFE_ROOT/tools/下面有很多工具 + 生產力,可以根據需要修改。例如從訓練好的模型中抽取參數進行可視化可以用Python結合matplot實現。
(5)接下來,如果想更深層次學習,最好是自己重新寫一遍Caffe(時間充裕的情況)。跳出現有的架構,重新構建自己的架構,通過對比就能學到更多內容。
[原]Caffe代碼導讀(1):Protobuf例子
2014-11-12閱讀640 評論2
Protobuf是一種可以實現記憶體與外存交換的協議介面。這是由Google開發的開源工具,目前研究Caffe源碼時用到。
一個軟體項目 = 資料結構 + 演算法 + 參數,對於資料結構和演算法我們都已經有較多研究,但不同開發人員對參數管理卻各有千秋。有人喜歡TXT格式化的參數檔案,有人喜歡BIN簡單高效,也有人喜歡圖形化介面的直觀。不一致的參數管理帶來很多問題,例如一個項目組內不同成員必須約定一套統一的參數方案,或者稱為通訊協定,這樣便於模組整合。而Protobuf工具就完美解決了這個問題,關鍵區段代碼自動產生,節省了大量的開發、調試時間。
首先下載protobuf,地址(打不開。……不解釋)
這裡用Linux版本2.5.0
解壓:
tar zxvf protobuf-2.5.0.tar.gz
切到主目錄:
cd protobuf-2.5.0
編譯:
./configure
make
sudo make install
添加環境變數:
export PKG_CONFIG_PATH=$(pwd)
編譯examples:
cd examples/
make cpp
這裡我們只編譯C++代碼。
編譯完成,產生了以下可執行檔:
add_person_cpp
list_people_cpp
這是個通訊錄的例子。我們首先運行add_person_cpp:
./add_person_cpp zykzyk: File not found. Creating a new file.Enter person ID number: 123Enter name: zhaoyongkeEnter email address (blank for none): zhaoyongke@yeah.netEnter a phone number (or leave blank to finish): 188188188Is this a mobile, home, or work phone?(斷行符號)Unknown phone type. Using default.Enter a phone number (or leave blank to finish):(斷行符號)
然後運行list_people_cpp:
./list_people_cpp zykPerson ID: 123 Name: zhaoyongke E-mail address: zhaoyongke@yeah.net Home phone #: 188188188
可見我們產生了新的通訊錄zyk,裡面儲存了相應的資訊。
例子運行結束了,我們看下代碼是如何產生的。
protobuf使用前,先編寫proto檔案,這是描述我們需要配置參數的資料結構。這個例子裡面的proto如下:
// See README.txt for information and build instructions.package tutorial;option java_package = "com.example.tutorial";option java_outer_classname = "AddressBookProtos";message Person { required string name = 1; required int32 id = 2; // Unique ID number for this person. optional string email = 3; enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { required string number = 1; optional PhoneType type = 2 [default = HOME]; } repeated PhoneNumber phone = 4;}// Our address book file is just one of these.message AddressBook { repeated Person person = 1;}
前幾行是定義包的,可以忽略。
message Person{...}定義了一個需要傳輸的參數結構體,可見包括這麼幾個單元:name(string類型)、id(int32類型)、email(string類型)、phone(PhoneNumber類型,嵌套在Person內的類)。前面標記為“required”是必須有值的,而“optional“則為可選項,”repeated“表示後面單元為相同類型的一組向量。
有了如上定義,我們可以用protobuf工具產生介面代碼,命令如下:
protoc --cpp_out=. addressbook.proto
運行後產生了兩個檔案:addressbook.pb.cc 和addressbook.pb.h,代碼比較長就不貼了。我們的應用程式可以通過自動產生的介面實現參數的序列化/還原序列化,代碼如下:
//add_person.c#include <iostream>#include <fstream>#include <string>#include "addressbook.pb.h"using namespace std;// This function fills in a Person message based on user input.void PromptForAddress(tutorial::Person* person) { cout << "Enter person ID number: "; int id; cin >> id; person->set_id(id); cin.ignore(256, '\n'); cout << "Enter name: "; getline(cin, *person->mutable_name()); cout << "Enter email address (blank for none): "; string email; getline(cin, email); if (!email.empty()) { person->set_email(email); } while (true) { cout << "Enter a phone number (or leave blank to finish): "; string number; getline(cin, number); if (number.empty()) { break; } tutorial::Person::PhoneNumber* phone_number = person->add_phone(); phone_number->set_number(number); cout << "Is this a mobile, home, or work phone? "; string type; getline(cin, type); if (type == "mobile") { phone_number->set_type(tutorial::Person::MOBILE); } else if (type == "home") { phone_number->set_type(tutorial::Person::HOME); } else if (type == "work") { phone_number->set_type(tutorial::Person::WORK); } else { cout << "Unknown phone type. Using default." << endl; } }}// Main function: Reads the entire address book from a file,// adds one person based on user input, then writes it back out to the same// file.int main(int argc, char* argv[]) { // Verify that the version of the library that we linked against is // compatible with the version of the headers we compiled against. GOOGLE_PROTOBUF_VERIFY_VERSION; if (argc != 2) { cerr << "Usage: " << argv[0] << " ADDRESS_BOOK_FILE" << endl; return -1; } tutorial::AddressBook address_book; { // Read the existing address book. fstream input(argv[1], ios::in | ios::binary); if (!input) { cout << argv[1] << ": File not found. Creating a new file." << endl; } else if (!address_book.ParseFromIstream(&input)) { cerr << "Failed to parse address book." << endl; return -1; } } // Add an address. PromptForAddress(address_book.add_person()); { // Write the new address book back to disk. fstream output(argv[1], ios::out | ios::trunc | ios::binary); if (!address_book.SerializeToOstream(&output)) { cerr << "Failed to write address book." << endl; return -1; } } // Optional: Delete all global objects allocated by libprotobuf. google::protobuf::ShutdownProtobufLibrary(); return 0;}
可見只需要調用addressbook.pb.h中聲明的tutorial::AddressBook類、Person類中的介面(add_person(), add_phone(), set_number(), set_email()等)就能操作相應的參數,最後將記憶體中的參數序列化為檔案只需要執行SerializeToOstream()。相應的讀取參數檔案的操作為ParseFromIstream()。這裡貼出例子中的第二個程式如下:
// list_people.c#include <iostream>#include <fstream>#include <string>#include "addressbook.pb.h"using namespace std;// Iterates though all people in the AddressBook and prints info about them.void ListPeople(const tutorial::AddressBook& address_book) { for (int i = 0; i < address_book.person_size(); i++) { const tutorial::Person& person = address_book.person(i); cout << "Person ID: " << person.id() << endl; cout << " Name: " << person.name() << endl; if (person.has_email()) { cout << " E-mail address: " << person.email() << endl; } for (int j = 0; j < person.phone_size(); j++) { const tutorial::Person::PhoneNumber& phone_number = person.phone(j); switch (phone_number.type()) { case tutorial::Person::MOBILE: cout << " Mobile phone #: "; break; case tutorial::Person::HOME: cout << " Home phone #: "; break; case tutorial::Person::WORK: cout << " Work phone #: "; break; } cout << phone_number.number() << endl; } }}// Main function: Reads the entire address book from a file and prints all// the information inside.int main(int argc, char* argv[]) { // Verify that the version of the library that we linked against is // compatible with the version of the headers we compiled against. GOOGLE_PROTOBUF_VERIFY_VERSION; if (argc != 2) { cerr << "Usage: " << argv[0] << " ADDRESS_BOOK_FILE" << endl; return -1; } tutorial::AddressBook address_book; { // Read the existing address book. fstream input(argv[1], ios::in | ios::binary); if (!address_book.ParseFromIstream(&input)) { cerr << "Failed to parse address book." << endl; return -1; } } ListPeople(address_book); // Optional: Delete all global objects allocated by libprotobuf. google::protobuf::ShutdownProtobufLibrary(); return 0;}
相信做完這個實驗,你將不再對Caffe代碼中的參數初始化、參數儲存操作感到陌生,一切都很自然。
除了上述簡易功能,Protobuf還可以用來傳遞不同語言(C/C++與Java、Python)之間的參數,省去了自己手動維護資料結構的繁瑣工作。也可以支援用戶端/伺服器模式,在主機/從機之間傳遞參數。
在安裝時查看是否安裝成功:protoc --version
如果出現:libprotoc 2.4.1 則說明安裝成功。
如果出現錯誤: protoc: error while loading shared libraries: libprotobuf.so.0: cannot open shared object file: No such file or directory The issue is that Ubuntu 8.04 doesn't include /usr/local/lib in library paths. To fix it for your current terminal session, just type in export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
閃電般的記憶體映射型資料庫管理(LMDB)簡介LMDB是基於二叉樹的資料庫管理庫,建模基於伯克利資料庫的應用程式介面
閃電般的記憶體映射型資料庫管理(LMDB)
簡介
LMDB是基於二叉樹的資料庫管理庫,建模基於伯克利資料庫的應用程式介面,但做了大幅精簡。整個資料庫都是記憶體映射型的,所有資料擷取返回資料都是直接從映射的記憶體中返回,所以擷取資料時沒有malloc或memcpy發生。因此該資料庫仍是非常簡單的,因為它不需要自己的頁面緩衝層,並且非常高效、省記憶體。它在語義上完全符合ACID(原子性、一致性、隔離性、持久性)。當記憶體映射為唯讀時,資料庫完整性不會被應用程式的迷失指標寫破壞。
該庫也是線程可見的,支援來自多進程/線程的並發讀/寫訪問。資料頁使用寫時複製策略,故沒有活動資料頁被覆蓋寫入。這也提供了保護機制,經曆系統崩潰後不需要特殊恢複過程。寫入過程為完全串列的;一次只有一個寫會話是活動的,這保證了寫入者不可能死結。資料庫結構是多個版本,所以讀出者運行時不加鎖。寫入這不會阻塞讀出者,讀出者也不會阻塞寫入者。
不像其他熟知的資料庫機制(使用寫前會話日誌或資料僅追加寫),LMDB操作時不需要保持會話。前面兩種都需要周期性地檢查或者壓縮他們的日誌或資料庫檔案,否則會無限增長。LMDB記錄資料庫內的空頁面,在新的寫入操作時重用他們,所以正常使用時資料庫尺寸不會無限增加。
記憶體映射可以用作唯讀映射或讀寫映射。預設為唯讀映射,這提供了對破壞完全的免疫力。使用讀寫入模式提供了更高的寫效能,但增加了被惡意寫入破壞資料庫的可能性。當然如果你的應用代碼是已知無bug的,那麼這不是個嚴重的問題。