? ?在本文正式開始之前,筆者要先和各位讀者朋友們道個歉。因為身為研一小白的筆者實在身不由己,除了各種任務之外,還要應付繁忙的課程,忙于各種考試像一只咸魚。因此耽誤了博文的撰寫,對不起各位讀者朋友,筆者在忙完6月進入研二之后一定再接再厲。下面開始干貨~
? ?本篇還是一個插播的博客,旨在向大家分享caffemodel里面記錄的信息。在我們使用caffe框架訓練網絡時,最終總會生成一個caffemodel,我們大概知道里面記錄了模型的參數。而作為初學者的筆者,總是不分青紅皂白地就直接在模型的驗證階段就調用了。從來沒有去看過caffemodel里面到底講了什么,那么,本篇中筆者就解析一下caffemodel。
? ?有讀者朋友未免會問,筆者是出于什么目的想解析caffemodel的呢?這說來話長,是筆者最近做的項目需要將model拆開并進行改動,因此需要去解析caffemodel。那么筆者是怎么摸索的呢?各位讀者朋友們是否還記得,筆者前兩期的博客里面提及的caffe官方提供的classification.cpp文件,里面在執行Classifier類的構造函數的時候,有一個net_指針,并且執行了一個操作:
[cpp]?view plaincopy
net_->CopyTrainedLayersFrom(trained_file);??
? ?這個CopyTrainedLayersFrom函數就是從caffemodel里面載入我們需要的參數了。然后筆者就從這個函數開始挖,進入net.cpp文件:
[cpp]?view plaincopy
template?<typename?Dtype>??void?Net<Dtype>::CopyTrainedLayersFrom(const?string?trained_filename)?{????if?(H5Fis_hdf5(trained_filename.c_str()))?{??????CopyTrainedLayersFromHDF5(trained_filename);????}?else?{??????CopyTrainedLayersFromBinaryProto(trained_filename);????}??}??
? ?在這里很明顯執行了else下面的語句,從二進制文件中去讀取了參數,然后,筆者又找到了CopyTrainedLayersFromBinaryProto函數,很巧就在CopyTrainedLayersFrom函數的下方:
[cpp]?view plaincopy
template?<typename?Dtype>??void?Net<Dtype>::CopyTrainedLayersFromBinaryProto(??????const?string?trained_filename)?{????NetParameter?param;????ReadNetParamsFromBinaryFileOrDie(trained_filename,?&parm);????CopyTrainedLayersFrom(param);??}??
? ?在里面首先執行了一個ReadNetParamsFromBinaryFileOrDie函數,把二進制文件中的參數讀到parm里面,parm是一個NetParameter類型的,NetParameter繼承了實際是一個Message,Message是proto類型的,詳細的筆者后話解析。然后筆者去找了一下ReadNetParamsFromBinaryFileOrDie函數,這個函數在upgrade_proto.cpp里面:
?
[cpp]?view plaincopy
void?ReadNetParamsFromBinaryFileOrDie(const?string&?param_file,????????????????????????????????????????NetParameter*?param)?{????CHECK(ReadProtoFromBinaryFile(param_file,?param))????????<<?"Failed?to?parse?NetParameter?file:?"?<<?param_file;????UpgradeNetAsNeeded(param_file,?param);??}??
? ?這個函數完成的功能是首先進行ReadProtoFromBinaryFile,然后執行了一個更新的操作。然后筆者就去找這個ReadProtoFromBinaryFile函數,這個函數在io.cpp里面:
[cpp]?view plaincopy
bool?ReadProtoFromBinaryFile(const?char*?filename,?Message*?proto)?{????int?fd?=?open(filename,?O_RDONLY);????CHECK_NE(fd,?-1)?<<?"File?not?found:?"?<<?filename;????ZeroCopyInputStream*?raw_input?=?new?FileInputStream(fd);????CodedInputStream*?coded_input?=?new?CodedInputStream(raw_input);????coded_input->SetTotalBytesLimit(kProtoReadBytesLimit,?536870912);??????bool?success?=?proto->ParseFromCodedStream(coded_input);??????delete?coded_input;????delete?raw_input;????close(fd);????return?success;??}??
? ?這個函數就比較底層了,讀者朋友們可以看到,這個函數里面就使用了open函數,和一些底層的google::protobuf的數據流。在這里我們其實就明白,是先把二進制文件(caffemodel)轉化成文件流,再放入proto里面。
? ?那么,筆者大膽猜想,能讀就能寫。
? ?其實io.cpp里面已經定義了這種接口:
[cpp]?view plaincopy
void?WriteProtoToTextFile(const?Message&?proto,?const?char*?filename)?{????int?fd?=?open(filename,?O_WRONLY?|?O_CREAT?|?O_TRUNC,?0644);????FileOutputStream*?output?=?new?FileOutputStream(fd);????CHECK(google::protobuf::TextFormat::Print(proto,?output));????delete?output;????close(fd);??}??
? ?也就是說:先用ReadProtoFromBinaryFile函數將二進制文件讀入proto里面,再將proto文件讀入txt文件就行了。
? ?筆者迫不及待地拿lenet網絡訓練生成的caffemodel做了個測試:
[cpp]?view plaincopy
#include?<caffe/caffe.hpp>??#include?<google/protobuf/io/coded_stream.h>??#include?<google/protobuf/io/zero_copy_stream_impl.h>??#include?<google/protobuf/text_format.h>??#include?<algorithm>??#include?<iosfwd>??#include?<memory>??#include?<string>??#include?<utility>??#include?<vector>??#include?<iostream>??#include?"caffe/common.hpp"??#include?"caffe/proto/caffe.pb.h"??#include?"caffe/util/io.hpp"????using?namespace?caffe;??using?namespace?std;??using?google::protobuf::io::FileInputStream;??using?google::protobuf::io::FileOutputStream;??using?google::protobuf::io::ZeroCopyInputStream;??using?google::protobuf::io::CodedInputStream;??using?google::protobuf::io::ZeroCopyOutputStream;??using?google::protobuf::io::CodedOutputStream;??using?google::protobuf::Message;????int?main()??{??????NetParameter?proto;??????ReadProtoFromBinaryFile("/home/cvlab/files/caffe-master/data/mnist/lenet_iter_10000.caffemodel",?&proto);??????WriteProtoToTextFile(proto,?"/home/cvlab/files/caffe-master/data/mnist/test.txt");??????return?0;??}??
? ?筆者訓練了一個lenet訓練生成的二進制文件,并將其寫入了一個名為test.txt的文件中。
? ?其對應的CMakeLists.txt文件為:
[cpp]?view plaincopy
cmake_minimum_required?(VERSION?2.8)????project?(pt_test)????add_executable(pt_test?pt.cpp)????include_directories?(?/home/cvlab/files/caffe-master/include??????/usr/local/include??????/usr/local/cuda/include??????/usr/include?)????target_link_libraries(pt_test??????/home/cvlab/files/caffe-master/build/lib/libcaffe.so??????/usr/lib/x86_64-linux-gnu/libglog.so??????/usr/lib/x86_64-linux-gnu/libboost_system.so??????)??
? ?編譯執行:
? ?然后我們打開test.txt文件可以見到:
? ?一共43萬多行,記錄了lenet-5的網絡參數。因為lenet-5使用了全連接層,因此參數規模是龐大的(全連接層的參數約占了總體參數規模的90%)。
? ?同時也可以看到,讀出的二進制文件中參數規格是按照caffe.proto中協定的格式來的。
? ?到此,我們就能清晰地看到caffemodel中記載的數據和格式了。
? ?歡迎閱讀筆者后續博客,各位讀者朋友的支持與鼓勵是我最大的動力!
written by jiong
故上兵伐謀,其次伐交,其次伐兵,其下攻城;攻城之法為不得已。
與50位技術專家面對面20年技術見證,附贈技術全景圖
總結
以上是生活随笔為你收集整理的tensorflow2caffe(1) : caffemodel解析,caffemodel里面到底记录了什么?的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。