caffe源码 数据结构 google protobuf

本文介绍了Caffe中使用的数据结构及ProtoBuf的应用。Caffe利用ProtoBuf定义网络结构和参数,确保跨平台兼容性和高效性。文章详细讲解了如何定义、编译和使用ProtoBuf,以实现对网络结构的灵活配置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

caffe的数据结构,除了使用blob作为数据块,大部分的数据结构都用proto文件来定义。

我们为表达网络结构所写prototxt文件就是protobuf读取的文件,从其中,protobuf可以获取层、参数的设置,反馈NetParameter、LayerParameter等重要初始化信息用于网络、层的建立和设置。

caffe编译时,第一个编译的就是caffe.proto,它是所有文件的基础。

什么是protobuf,为什么选用它?

Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准,目前已经正在使用的有超过 48,162 种报文格式定义和超过 12,183 个 .proto 文件。他们用于 RPC 系统和持续数据存储系统。

Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python 三种语言的 API。

Protocol buffers是一种灵活,高效,自动化的方案,用于解决这类问题。有了protocol buffers, 你可以编写.proto文件去存储你想描述的数据结构。Protocol buffers编译器会创造一个类来将protocol buffer数据自动编码和解析成高效的二进制格式。生成的类为protocol buffer中的字段提供了getters和setters接口,而且还把读写protocal的细节封装成了一个单元。最重要的,protocol buffer支持扩展格式,随着时间的推移,这样的代码仍然可以用旧的格式读取数据编码。

通过使用,我们可以直观的了解它的作用及用法,使用方法:


1.定义你的protocol format,写.proto:

.proto文件是十分简单的:你需要为每个你想序列化的数据结构写一个消息message,并为每一个在消息里的字段指定名称和类型。

1.1 *.proto文件中数据类型可以分为两大类:

复合数据类型 + 标准数据类型

复合数据类型包括:枚举和message类型

标准数据类型包含:整型,浮点,字符串等

1.2 数据类型前面修饰词:

①required: 必须赋值,不能为空,否则该条message会被认为是“uninitialized”。build一个“uninitialized” message会抛出一个RuntimeException异常,解析一条“uninitialized” message会抛出一条IOException异常。除此之外,“required”字段跟“optional”字段并无差别。

②optional:字段可以赋值,也可以不赋值。假如没有赋值的话,会被赋上默认值。对于简单的类型, 您可以指定自己的默认值, [default=HIT]。否则,会用系统提供的值:数值类型为0,字符类型为空, 布尔类型为false。对于嵌入式消息,默认值总是“默认实例”或“原型”这些消息里没有设置的字段。调用访问器来获得一个可选的(或要求)字段的值(没有被显式地设置),总是会返回字段的默认值的.

③repeated: 该字段可以重复任意次数,包括0次。重复数据的顺序将会保存在protocol buffer中,将这个字段想象成一个可以自动设置size的数组就可以了,repeated字段将作动态数组。

1.3.每个字段要给数字:

该Number是用来标记该字段在序列化后的二进制数据中所在的field,每个字段的Number在message内部都是独一无二的。也不能进行改变,否则数据就不能正确的解包。

1.4 数据类型

Protobuf支持的基本数据类型的表


1.5 例子

这里有一个.proto文件已经定义好你的消息addressbook.proto,引自官方教程:

https://developers.google.com/protocol-buffers/docs/cpptutorial

syntax = "proto2";

package tutorial;//包名称声明,预防命名冲突,生成的充当数据结构的类会被放在相应的命名空间中。

message Person {//用message定义一个数据结构
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;

  enum PhoneType {//枚举类型,丰富了数据结构
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {//嵌套数据类型
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phones = 4;//可加多个电话号码和电话类型
}

message AddressBook {
  repeated Person people = 1;//嵌套数据类型
}
消息message: 一个消息只是一些类型字段的集合。许多标准简单的数据类型可以作为字段的类型,包括bool,int32,float,double和string。你还可以进一步构建你的消息通过使用其他消息类型作为字段类型——在上面的例子中,Person消息包含了PhoneNumber消息,而AddressBook消息也包含了Person消息。你甚至可以定义消息类型嵌套在其他消息中——如你所见,PhoneNumber类型在Person中定义。您还可以定义枚举类型,如果你想你的一个字段有一个预定义的值列表——在这里你想指定一个电话号码可以是MOBILE,HOME,WORK。


每个元素中的“= 1”,“= 2”标记符是识别使用的二进制编码字段的独特的“标签”。标签号码1-15要求的字节会比号码高的少点,所以你想优化一下,你可以使用这些标签用于常用的或repeated元素,把标签16和更高的标签给那些不常用或optional元素。每一个repeated的元素需要重新编码标记号,所以repeated字段特别适合优化。


2.编译Protocol Buffers:

现在你已经有了.proto文件,你需要做的下一件事是生成你需要read and write AddressBook(和后面的Person和PhoneNumber)消息的类。要做到这一点,您需要运行protocol buffer编译器protoc编译你的.proto :

现在运行编译器, 指定源码目录(你的应用源码呆的地方——如果你不提供这个值,默认为现在的目录),目标目录(你希望生成代码的目录;通常会与源码目录相同),和你的.proto文件的路径。在这种情况下,你...:

protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto

因为你想用C++的类,所以你要写--cpp_out选项——类似的选项对其他受支持的语言也有提供,例如java:--java_out,python:--python_out

这个会在你指定目标目录下生成下列文件c++:

  • addressbook.pb.h,声明你生成的类的头文件。//caffe编译caffe.proto生成的caffe.pb.h被很多重要类引用,实现数据结构的创建和使用
  • addressbook.pb.cc, 包含你的类的实现文件。

python会生成 addressbook_pb2.py


3.Protocol Buffer的API

让我们来看看一些生成的代码,看看编译器生成了哪些类和函数给你。如果你看了tutorial.pb.h,你可以看到,你有一个您在tutorial.proto中指定每个消息的类。 再看看Person类,您可以看到,编译为每个字段生成了访问器。比如,对于name,id,email,和phone字段,c++你会有这些方法


    // name  
      inline bool has_name() const;  
      inline void clear_name();  
      inline const ::std::string& name() const;  
      inline void set_name(const ::std::string& value);  
      inline void set_name(const char* value);  
      inline ::std::string* mutable_name();  
      
      // id  
      inline bool has_id() const;  
      inline void clear_id();  
      inline int32_t id() const;  
      inline void set_id(int32_t value);  
      
      // email  
      inline bool has_email() const;  
      inline void clear_email();  
      inline const ::std::string& email() const;  
      inline void set_email(const ::std::string& value);  
      inline void set_email(const char* value);  
      inline ::std::string* mutable_email();  
      
      // phone  
      inline int phone_size() const;  
      inline void clear_phone();  
      inline const ::google::protobuf::RepeatedPtrField< ::tutorial::Person_PhoneNumber >& phone() const;  
      inline ::google::protobuf::RepeatedPtrField< ::tutorial::Person_PhoneNumber >* mutable_phone();  
      inline const ::tutorial::Person_PhoneNumber& phone(int index) const;  
      inline ::tutorial::Person_PhoneNumber* mutable_phone(int index);  
      inline ::tutorial::Person_PhoneNumber* add_phone();  

getters有完整的名称包含小写的字段,setter函数始于set_。还有has_函数为每个单独的字段(required和optional)验证是否已经被设置。最后,每个字段都有一个clear_函数使该字段重置到空的状态。

然而,数字类型id字段只有基本的set,而name和email字段因为是字符串,所以有一对额外的函数。——一个让你可以得到一个直接指向字符串的指针(可修改目标)的mutable_ getter,和一个额外的setter。注意,您可以调用mutable_email(),即使email还没有设置;它将被自动初始化为一个空字符串。如果你有一个单独的消息字段在这个例子中,它还将会有一个mutable_函数但不会是一个set_函数。

Repeated字段也有一些特殊的函数——看repeated phone字段的函数,你可以发现

  • 检查repeated字段的_size(换句话说,有多少电话号码与这个Person有关)。
  • 得到一个使用其索引指定的电话号码。
  • 在指定的索引中更新现有的电话号码。
  • 添加另一个您可以编辑的电话号码的消息(repeated标量类型有一个add_可以让你使用新值)。


python比较不同,并不直接生成数据访问代码,而是生成特定的描述器为所有messages, enums, and fields,和一些空类,每个对应一种数据类型:

class Person(message.Message):
  __metaclass__ = reflection.GeneratedProtocolMessageType

  class PhoneNumber(message.Message):
    __metaclass__ = reflection.GeneratedProtocolMessageType
    DESCRIPTOR = _PERSON_PHONENUMBER
  DESCRIPTOR = _PERSON

class AddressBook(message.Message):
  __metaclass__ = reflection.GeneratedProtocolMessageType
  DESCRIPTOR = _ADDRESSBOOK

__metaclass__ = reflection.GeneratedProtocolMessageType这个很重要,可当作用于创建类的模板。加载时,GeneratedProtocolMessageType metaclass用特定的描述器来创建所有的python方法,可以使用Person class 当作它 定义了 each field of the Message base class as a regular field,例如:

import addressbook_pb2
person = addressbook_pb2.Person()
person.id = 1234
person.name = "John Doe"
person.email = "jdoe@example.com"
phone = person.phones.add()
phone.number = "555-4321"
phone.type = addressbook_pb2.Person.HOME


4.caffe.proto 中选段编译:

取了caffe.proto 中关于Blob结构定义的部分,单独取出来:

syntax = "proto2";

package blob_paractice;

// Specifies the shape (dimensions) of a Blob.
message BlobShape {
  repeated int64 dim = 1 [packed = true];
}

message BlobProto {
  optional BlobShape shape = 7;
  repeated float data = 5 [packed = true];
  repeated float diff = 6 [packed = true];
  repeated double double_data = 8 [packed = true];
  repeated double double_diff = 9 [packed = true];

  // 4D dimensions -- deprecated.  Use "shape" instead.
  optional int32 num = 1 [default = 0];
  optional int32 channels = 2 [default = 0];
  optional int32 height = 3 [default = 0];
  optional int32 width = 4 [default = 0];
}

然后进到目录里编译:

protoc --cpp_out=./ ./blob.proto

生成了blob.pb.h     blob.pb.cc,其中blob.pb.h:

namespace blob_paractice {

// Internal implementation detail -- do not call these.
void protobuf_AddDesc_blob_2eproto();
void protobuf_AssignDesc_blob_2eproto();
void protobuf_ShutdownFile_blob_2eproto();

class BlobProto;
class BlobShape;

// ===================================================================

class BlobShape : public ::google::protobuf::Message /* @@protoc_insertion_point(class_definition:blob_paractice.BlobShape) */ {
 public:
  BlobShape();
  virtual ~BlobShape();

  BlobShape(const BlobShape& from);

  inline BlobShape& operator=(const BlobShape& from) {
    CopyFrom(from);
    return *this;
  }

这段程序之前有一些头文件引用,省略了,之后还有一些定义了类的功能部分也略去了,只看咱们要用的API部分:

  // nested types ----------------------------------------------------

  // accessors -------------------------------------------------------

  // repeated int64 dim = 1 [packed = true];
  int dim_size() const;
  void clear_dim();
  static const int kDimFieldNumber = 1;
  ::google::protobuf::int64 dim(int index) const;
  void set_dim(int index, ::google::protobuf::int64 value);
  void add_dim(::google::protobuf::int64 value);
  const ::google::protobuf::RepeatedField< ::google::protobuf::int64 >&
      dim() const;
  ::google::protobuf::RepeatedField< ::google::protobuf::int64 >*
      mutable_dim();

从上面,可以清楚的看到对BlobShape结构中repeated int64 dim = 1 [packed = true];的功能:

大小、清理、查看、设置、重复定义和mutable_指针获取

当然还有BlobProto的:

  // nested types ----------------------------------------------------

  // accessors -------------------------------------------------------

  // optional .blob_paractice.BlobShape shape = 7;
  bool has_shape() const;
  void clear_shape();
  static const int kShapeFieldNumber = 7;
  const ::blob_paractice::BlobShape& shape() const;
  ::blob_paractice::BlobShape* mutable_shape();
  ::blob_paractice::BlobShape* release_shape();
  void set_allocated_shape(::blob_paractice::BlobShape* shape);

  // repeated float data = 5 [packed = true];
  int data_size() const;
  void clear_data();
  static const int kDataFieldNumber = 5;
  float data(int index) const;
  void set_data(int index, float value);
  void add_data(float value);
  const ::google::protobuf::RepeatedField< float >&
      data() const;
  ::google::protobuf::RepeatedField< float >*
      mutable_data();

  // repeated float diff = 6 [packed = true];
  int diff_size() const;
  void clear_diff();
  static const int kDiffFieldNumber = 6;
  float diff(int index) const;
  void set_diff(int index, float value);
  void add_diff(float value);
  const ::google::protobuf::RepeatedField< float >&
      diff() const;
  ::google::protobuf::RepeatedField< float >*
      mutable_diff();

  // repeated double double_data = 8 [packed = true];
  int double_data_size() const;
  void clear_double_data();
  static const int kDoubleDataFieldNumber = 8;
  double double_data(int index) const;
  void set_double_data(int index, double value);
  void add_double_data(double value);
  const ::google::protobuf::RepeatedField< double >&
      double_data() const;
  ::google::protobuf::RepeatedField< double >*
      mutable_double_data();

  // repeated double double_diff = 9 [packed = true];
  int double_diff_size() const;
  void clear_double_diff();
  static const int kDoubleDiffFieldNumber = 9;
  double double_diff(int index) const;
  void set_double_diff(int index, double value);
  void add_double_diff(double value);
  const ::google::protobuf::RepeatedField< double >&
      double_diff() const;
  ::google::protobuf::RepeatedField< double >*
      mutable_double_diff();

  // optional int32 num = 1 [default = 0];
  bool has_num() const;
  void clear_num();
  static const int kNumFieldNumber = 1;
  ::google::protobuf::int32 num() const;
  void set_num(::google::protobuf::int32 value);

  // optional int32 channels = 2 [default = 0];
  bool has_channels() const;
  void clear_channels();
  static const int kChannelsFieldNumber = 2;
  ::google::protobuf::int32 channels() const;
  void set_channels(::google::protobuf::int32 value);

  // optional int32 height = 3 [default = 0];
  bool has_height() const;
  void clear_height();
  static const int kHeightFieldNumber = 3;
  ::google::protobuf::int32 height() const;
  void set_height(::google::protobuf::int32 value);

  // optional int32 width = 4 [default = 0];
  bool has_width() const;
  void clear_width();
  static const int kWidthFieldNumber = 4;
  ::google::protobuf::int32 width() const;
  void set_width(::google::protobuf::int32 value);

一应功能,说来就来,方便快捷,运行效率有保证。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值