tensorflow2caffe(1) : caffemodel解析,caffemodel里面到底记录了什么?

来源:互联网 发布:网络侵权管辖最新规定 编辑:程序博客网 时间:2024/05/16 07:58

   在本文正式开始之前,笔者要先和各位读者朋友们道个歉。因为身为研一小白的笔者实在身不由己,除了各种任务之外,还要应付繁忙的课程,忙于各种考试像一只咸鱼。因此耽误了博文的撰写,对不起各位读者朋友,笔者在忙完6月进入研二之后一定再接再厉。下面开始干货~

   本篇还是一个插播的博客,旨在向大家分享caffemodel里面记录的信息。在我们使用caffe框架训练网络时,最终总会生成一个caffemodel,我们大概知道里面记录了模型的参数。而作为初学者的笔者,总是不分青红皂白地就直接在模型的验证阶段就调用了。从来没有去看过caffemodel里面到底讲了什么,那么,本篇中笔者就解析一下caffemodel。

   有读者朋友未免会问,笔者是出于什么目的想解析caffemodel的呢?这说来话长,是笔者最近做的项目需要将model拆开并进行改动,因此需要去解析caffemodel。那么笔者是怎么摸索的呢?各位读者朋友们是否还记得,笔者前两期的博客里面提及的caffe官方提供的classification.cpp文件,里面在执行Classifier类的构造函数的时候,有一个net_指针,并且执行了一个操作:

net_->CopyTrainedLayersFrom(trained_file);

   这个CopyTrainedLayersFrom函数就是从caffemodel里面载入我们需要的参数了。然后笔者就从这个函数开始挖,进入net.cpp文件:

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函数的下方:

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里面:

 

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里面:

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里面已经定义了这种接口:

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做了个测试:

#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文件为:

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

故上兵伐谋,其次伐交,其次伐兵,其下攻城;攻城之法为不得已。

原创粉丝点击