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网络结构。