当前位置: 代码迷 >> 综合 >> Caffe学习1 :ProtoBuffer
  详细解决方案

Caffe学习1 :ProtoBuffer

热度:56   发布时间:2024-01-04 22:00:29.0

真真接触caffe,在电脑上成功安装且运行caffe也有半年多时间了
之前做了不少训练和调参之类的工作,看了一些论文,了解了一些模型,如今有必要去更深地去了解一下Caffe框架了,也觉得需要去写点东西了,今天姑且把Protobuf这个东东写一下吧。
1、Protobuf
什么是Protobuf?
Protobuf是由Google开发的一种可以实现内存与非易失存储介质(如硬盘文件)交换的协议接口。
protobuf 是一个灵活、高效,使用自动化机制的结构化数据序列工具,类似于XML,但比XML更小巧、更快、而且也更简单。只需要定义一次数据结构,你就可以使用代码生成器生成各种编程语言和各种流式文件的结构化读取和写入。甚至可以在无需重新编译部署新程序的情况下更新新的结构化数据。
Caffe源码中大量使用Protobuf作为权重和模型参数的载体。利用protoc编译能让协议细节等关键部分代码自动生成,所以使用Protobuf工具可以节省大量的开发和调试时间。
prtotocol buffer是google于2008年开源的一款非常优秀的序列化反序列化工具,caffe中的参数管理基本都是由它实现的。
既然Protobuf这么好,那该怎么用?
一、Protobuf消息定义

消息由至少一个字段组合而成,类似于C语言中的结构。每个字段都有一定的格式。
字段格式:限定修饰符① | 数据类型② | 字段名称③ | = | 字段编码值④ | [字段默认值⑤]

首先,我们用一个实例说明。在这里我们定义一个搜索请求消息的格式:每一条搜索请求有一个string类型的请求,一个我们希望搜索的页数,以及每一页期望返回多少结果。那么我们可以在.proto文件中定义以下的结构:

message SearchRequest{required string query = 1;optional int32 page_number = 2;optional int32 result_per_page = 3;
}

添加更多的消息类型

多个消息类型可以定义在同一个.proto文件内,这对定义多个有关联的消息是是十分有用的。例如,如果你想定义一个用于回复SearchResponse消息,你可以像这样在.proto内添加。

message SearchRequest {  required string query = 1;  optional int32 page_number = 2;  optional int32 result_per_page = 3;  
}  message SearchResponse {  ...  
}  
message SearchRequest {  required string query = 1;  optional int32 page_number = 2;  optional int32 result_per_page = 3;  
}  message SearchResponse {  ...  
}  

1)限定修饰符包含 required\optional\repeated

Required(必须的): 表示是一个必须字段,必须相对于发送方,在发送消息之前必须设置该字段的值,对于接收方,必须能够识别该字段的意思。发送之前没有设置required字段或者无法识别required字段都会引发编解码异常,导致消息被丢弃。

Optional(可选的):表示是一个可选字段,可选对于发送方,在发送消息时,可以有选择性的设置或者不设置该字段的值。对于接收方,如果能够识别可选字段就进行相应的处理,如果无法识别,则忽略该字段,消息中的其它字段正常处理。—因为optional字段的特性,很多接口在升级版本中都把后来添加的字段都统一的设置为optional字段,这样老的版本无需升级程序也可以正常的与新的软件进行通信,只不过新的字段无法识别而已,因为并不是每个节点都需要新的功能,因此可以做到按需升级和平滑过渡。

Repeated(重复的):表示该字段可以包含0~N个元素。其特性和optional一样,但是每一次可以包含多个值。可以看作是在传递一个数组的值。
因为历史的原因:repeated字段如果是基本的数字类型的话会无法编码。新的代码应该使用特殊的关键字[packed=true] 来使其得到有效的编码.例如

repeated int32 samples = 4 [packed=true];  

注意:你应该小心将字段设置为required,如果你希望在某些情况下取消required字段的读写,它将改变字段为optional属性,旧的的读取方将会认为此消息不完全。可能会无意的将其丢弃。你应该考虑自定义一个消息检查程序。google的一些工程师认为使用optinal字段的好处大于required。但是显然这个观点并不是通用的。

(2)数据类型
这里写图片描述

(3)字段名称

字段名称的命名与C、C++、Java等语言的变量命名方式几乎是相同的。

protobuf建议字段的命名采用以下划线分割的驼峰式。例如 first_name 而不是firstName.

(4)字段编码值

有了该值,通信双方才能互相识别对方的字段。当然相同的编码值,其限定修饰符和数据类型必须相同。编码值的取值范围为 1~2^32(4294967296)。其中 1~15的编码时间和空间效率都是最高的,编码值越大,其编码的时间和空间效率就越低(相对于1-15),当然一般情况下相邻的2个值编码效率的是相同的,除非2个值恰好实在4字节,12字节,20字节等的临界区。比如15和16,还有1900~2000编码值为Google protobuf 系统内部保留值,建议不要在自己的项目中使用。

protobuf 还建议把经常要传递的值把其字段编码设置为1-15之间的值。消息中的字段的编码值无需连续,只要是合法的,并且不能在同一个消息中有字段包含相同的编码值。

建议:项目投入运营以后涉及到版本升级时的新增消息字段全部使用optional或者repeated,尽量不实用required。

(4)默认值

当在传递数据时,对于required数据类型,如果用户没有设置值,则使用默认值传递到对端。当接受数据是,对于optional字段,如果没有接收到optional字段,则设置为默认值。
如果一个optional字段没有被指定其默认值。其默认值被自动替换为:

1.字符串:为空字符串.

2.bool:为false.

3.数字类型:为0;

4.枚举值:为第一个枚举值

例如:设置optional域默认值的语法是:

optional int32 restult_per_page = 3 [default = 10];

二、注意事项

(1)关于import

protobuf 接口文件可以像C语言的h文件一个,分离为多个,在需要的时候通过 import导入需要对文件。其行为和C语言的#include或者java的import的行为大致相同。

(2)关于package

避免名称冲突,可以给每个文件指定一个package名称,对于java解析为java中的包。对于C++则解析为名称空间。

(3)关于message

支持嵌套消息,消息可以包含另一个消息作为其字段。也可以在消息内定义一个新的消息。
例如,我们希望在一个message中使用另一个message类型,则可以使用如下方法:

message SearchResponse{repeated Result result = 1;
}message Result{required string url = 1;optional string title = 2;repeated string snippets = 3;
}

(4)关于enum

枚举的定义和C++相同,但是有一些限制。

枚举值必须大于等于0的整数。

使用分号(;)分隔枚举变量而不是C++语言中的逗号(,)

当我们需要定义Message中的一个域,这个域的取值是我们预定义的一个集合中的一个元素。比如说,我们要在SearchRequest中添加一个域,这个域的取值是:UNIVERSAL, WEB, IMAGES, LOCAL, NEWS, PRODUCTS或者VIDEO中的一个,这时我们就需要枚举来帮忙了。

通过使用枚举,我们可以在message中添加一个枚举域,在这个域中定义一些常量。示例代码:

    message SearchRequest{required string query = 1;optional int32 page_numer =2;optional int32 result_per_page = 3[default = 10];enum Corpus{ UNIVERAL = 0; WEB = 1; IMAGES = 2; LOCAL = 3; NEWS = 4; PRODUCTS = 5; VIDEO = 6;}optional Corpus corpus = 4 [default = UNIVERSAL];}

我们还可以在枚举中设置多个name对应一个相同的值,这是protocol buffer的别名机制(alias)。当然,我们必须首先设置这个别名,即,在enum中将allow_alias的选项设置为true。代码示例:

    enum EnumAllowingAlias{option allow_alias = true;UNKOWN = 0;STARTED = 1;RUNNING = 1;    }enum EnumNotAllowingAlias{UNKNOWN = 0;STARTED = 1;}

枚举类型必须能够用32bit整数表示,并且由于编码效率原因不建议使用负数。我们还可以在.proto文件中利用已经声明的枚举类型来声明一个域的类型。在不同message中可以使用相同的枚举类型。通过MessageType.EnumType使用(与C++中对象使用的方式类似)。

还有许多许多的需要详细阅读Google Developers中的文档https://developers.google.com/protocol-buffers/docs/proto

.proto文件会生成什么?

当你使用protobuf编译器编译一个.proto文件,它会生成在.proto内你描述的消息类型的操作代码,这些代码是根据你所选择的编程功能语言决定的。这些操作代码内包含了设置字段值 和读取字段值,以及序列化到输出流 和 从输入流反序列化。

C++:编译器会按照每个.proto文件生成与其对应的.h和.cc文件,每个消息类似都有独立的消息操作类。

Java:编译器将会生成一个.java文件和一个操作类,此操作类为所有消息类型所共有, 使用一个特别的Builder类为每个消息类型实例化.

Python:有一点不同 – 编译器会为每个消息生成一个模块每个模块有一个静态描述符, 该模块与一个元类在运行时创建一个需要的数据操作类。

参考
http://www.cnblogs.com/yymn/p/5167013.html
http://www.cnblogs.com/yymn/p/4483370.html
http://www.cnblogs.com/yymn/p/4483370.html
http://blog.csdn.net/sylar_d/article/details/51325987
https://developers.google.com/protocol-buffers/docs/proto
https://developers.google.com/protocol-buffers/docs/overview?csw=1