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

   在本文正式开始之前,笔者要先和各位读者朋友们道个歉。因为身为研一小白的笔者实在身不由己,除了各种任务之外,还要应付繁忙的课程,忙于各种考试像一只咸鱼。因此耽误了博文的撰写,对不起各位读者朋友,笔者在忙完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

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

  • 42
    点赞
  • 74
    收藏
    觉得还不错? 一键收藏
  • 31
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 31
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值