caffe之protobuf的使用方法

protobuf在caffe中的作用主要是两个:
一. 是作为配置文件的parser,可以利用protobuf格式编写网络的结构等配置文件,然后利用protobuf来parse配置文件,变成内存中的structure。
二. 将training好的网络权重通过protobuf压缩编码后写到硬盘存储,并且可以在需要时从硬盘中读取出来parse成内存中的structure结构。

这里记录以下protobuf的一些简单用法和知识。
1.protobuf用到的数据类型:
.proto类型------------------C++类型-------------备注
double-----------------------double
float -------------------------float
int32--------------------------int32 使用可变长编码方式。编码负数时不够高效——如果你的字段可能含有负数,那么请使用sint32。
int64--------------------------int64 使用可变长编码方式。编码负数时不够高效——如果你的字段可能含有负数,那么请使用sint64。
unit32------------------------unit32 使用可变长编码方式。
unit64------------------------unit64 使用可变长编码方式。
sint32------------------------int32 使用可变长编码方式。有符号的整型值。编码时比通常的int32高效。
sint64------------------------int64 使用可变长编码方式。有符号的整型值。编码时比通常的int64高效。
fixed32----------------------unit32 总是4个字节。如果数值总是比2^28大的话,这个类型会比uint32高效。
fixed64----------------------unit64 总是8个字节。如果数值总是比2^56大的话,这个类型会比uint64高效
sfixed32---------------------int32 总是4个字节。
sfixed64---------------------int64 总是8个字节。
bool--------------------------bool
string------------------------string 一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。
bytes------------------------string 可能包含任意顺序的字节数据
特殊字段
英文-------------中文------------------------------备注
enum----------枚举(数字从零开始) ---------作用是为字段指定某”预定义值序列” enum Type {MAN = 0;WOMAN = 1; OTHER= 3;}
message-----消息体-----------------------------message User{}
repeated------数组/集合------------------------repeated User users = 1, 字段可出现任意多次(包括 0)
required ------必须存在的元素 -----------------required int32 id = 2, 字段只能也必须出现 1 次
optional-------可选的元素-----------------------optional int32 value = 1, 字段可出现 0 次或1次
import---------导入定义-------------------------import “protos/other_protos.proto”
// --------------注释--------------------------------//用于注释
extend ------扩展--------------------------------extend User {}
package-----包名------------------------------相当于命名空间,用来防止不同消息类型的命名冲突

2.protobuf的编译命令
编写一个*.proto文件之后,就可以使用命令
protoc --cpp_out=./ ./protobuf_test.proto 来编译,编译之后就会得到protobuf_test.pb.h和protobuf_test.pb.cc两个文件。

文档:
https://www.jianshu.com/p/b33ca81b19b5

在caffe源码src/caffe/util/io.cpp文件中提供了函数从prototxt文件中parse出参数的值。

下面提供一个例子来演示在caffe中用到的上面两个功能:
1.首先,我们定义一个.proto文件,来定义一个structure: 文件名为protobuf_test.proto

syntax = "proto2";  //版本信息,使用proto2
 /*定义一个Person message, 这样,protobuf就会定义一个Person类,里面的元素都是Person类的成员变量*/
message Person {
    required string name = 1;
    required int32 age = 2;
    optional string email = 3
    message child {  //内嵌的message 定义
      required int32 age = 1;
      required string name = 2;
    }
    repeated child children = 4;
    repeated string address = 5;

定义好这个文件protobuf_test.proto后,利用protoc命令来编译这个文件:

//来编译,编译之后就会得到protobuf_test.pb.h和protobuf_test.pb.cc两个文件
protoc --cpp_out=./  ./protobuf_test.proto 

下面我们首先提供一个sample code, 来展示怎么把内存中的structure串行化后形成一个二进制字符串,然后写到文件中,然后再从文件中读取出来,解串行化为内存中的structure. 里面有几个头文件是我自己的通用库的一些函数,都是基本的辅助函数。这个例子就展示了在caffe中是怎么把training好的各个权重structure保存到文件中和从文件中将权重参数加载到内存上。

#include "base_include.h"
#include "cc_define.h"
#include "cc_log.h"
#include "cc_file.h"

#include "protobuf_test.pb.h"

using namespace std;
int main()
{
    // 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;
    int ret = 0;
    Person father;
    do {
        father.set_name("father");
        father.set_age(36);
        father.set_email("xx@xx.com");
        father.add_address("Address1");
        father.add_address("Address2");
        father.set_gender(Person_Gender_Boy);
        for (int i = 0; i < 2; ++ i) {
            if (!father.add_children()) {
                ERROR("Add children error.");
                ret = -1;
                break;
            }
        }
        if (ret < 0) {
            break;
        }
        ERROR("father children number is %d", father.children_size());
        Person_Child *child1 = father.mutable_children(0);
        child1->set_age(4);
        child1->set_name("child1");
        child1->set_gender(Person_Gender_Boy);
        Person_Child *child2 = father.mutable_children(1);
        child2->set_age(8);
        child2->set_name("child2");
        child2->set_gender(Person_Gender_Girl);

        string result;
        father.SerializeToString(&result);//串型化为二进制字符串,可以存储到文件中保存。
        string debug_result = father.DebugString();//转换为可以人类阅读的字符串,方便debug.
        ERROR("result is : %s", result.c_str());
        ERROR("debug result is : %s", debug_result.c_str());
        CCFile write_file("/home/ccjing/development/opensource/caffe/unit_test/protobuf/protobuf_test/Person_info.bin");
        if (!write_file.open(CCFile::CC_FILE_CREATE)) {
            PERROR("Failed to create file.");
            ret = -1;
            break;
        }
        if (write_file.write_reliable(result.c_str(), result.size()) != ssize_t(result.size())) {
            ERROR("Write file error.");
            write_file.close();
            ret = -1;
            break;
        }
        write_file.close();
//我们将二进制数据从文件中读出来,然后parse成Person对象。
        CCFile read_file("/home/ccjing/development/opensource/caffe/unit_test/protobuf/protobuf_test/Person_info.bin");
        if (!read_file.open(CCFile::CC_FILE_READONLY)) {
            PERROR("Failed to open file");
            read_file.close();
            ret = -1;
            break;
        }
        char buffer[512] = {0};
        ssize_t read_len = read_file.read(buffer, sizeof(buffer));
        ERROR("read data len is %d", read_len);
        Person copy_father;
        copy_father.ParseFromArray(buffer, int(read_len));
        string debug_copy_father = copy_father.DebugString();
        ERROR("copy father debug string is %s", debug_copy_father.c_str());
        read_file.close();
    } while (false);
    // Optional:  Delete all global objects allocated by libprotobuf.
    // @@@ ShutdownProtobufLibrary
    google::protobuf::ShutdownProtobufLibrary();
    return ret;

下面这个例子来展示怎么parse配置文件,也就是在caffe中解析描述网络结构的配置文件。
我们还是利用上面编译好的Person类,但这时就不是从二进制文件中读取字符串了,而是读取prototxt文件。
首先我们展示我们的配置文件:protobuf_test.prototxt

name: "ccjing_prototxt"
age: 36
gender : Boy
email : "xx@xx.com"
children {
  age: 4
  name: "child1"
  gender:Boy
}
children {
  age: 6
  name: "child2"
  gender:Girl
}
address : "address1"
address : "address2"

然后用code parse这个配置文件

int main()
{
    // 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;
       int ret = 0;
    google::protobuf::io::FileInputStream* file_input = nullptr;
    int fd = -1;
    do {
        Person person_from_text;
        string file_name = "/home/ccjing/development/opensource/caffe/unit_test/protobuf/protobuf_test/protobuf_test.prototxt";
        fd = open(file_name.c_str(), O_RDONLY);
        if (fd < 0) {
            PERROR("Failed to open prototxt file.");
            ret = -1;
            break;
        }
        file_input = new google::protobuf::io::FileInputStream(fd);
        if (!file_input) {
            ERROR("Failed to create FileInputStream.");
            ret = -1;
            break;
        }
        bool success = google::protobuf::TextFormat::Parse(file_input, &person_from_text);
        if (!success) {
            ERROR("Parse file error.");
            ret = -1;
            break;
        }
        string text = person_from_text.DebugString();
        ERROR("###########################################################");
        ERROR("text is %s", text.c_str());
    } while (false);
    if (file_input) {
     delete file_input;
    }
    if (fd >= 0) {
     close(fd);
    }
    // Optional:  Delete all global objects allocated by libprotobuf.
    // @@@ ShutdownProtobufLibrary
    google::protobuf::ShutdownProtobufLibrary();
    return ret;
}

通过上面两个例子,我们就实现了在caffe中用到的protobuf的两个功能,保存和加载权重参数和parse网络结构。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值