caffe源码分析之数据结构Blob

Caffe Blob详解

Caffe提供了一些基础的数据结构,从上到下包括net —>layer —>blob.
可以看出blob就是构成caffe大厦的最基础的砖头。本篇就来介绍一下Blob类的一些知识。
1.Blob类是caffe用来存储数据的类,是caffe的基本存储单元,提供了一系列接口来set数据,get数据,同时可以存储diff。Blob不仅可以存放比如图像等数据,还可以存放权值以及权值的diff。不同的layer之间传递数据必须通过Blob。
2.Blob是一个模板类,所以创建对象的时候需要指定类型,比如float,int等。初始化为float,里面存储的数据就是float类型。
3.caffe还会利用protobuf来生成一个BlobProto类,这个类是用来帮助Blob存储为文件和从文件中载入。因为一个net training出来的各个layer的权重参数也是存储在Blob中的,要把这个train好的网络存储下来,就需要将Blob中的权重数据存储下来,所以BlobProto就可以帮助干这个事情。还能帮助从文件中将以前train好的网络载入到内存中使用。BlobProto是利用protobuf自动生成的。在Blob类的内部没有创建BlobProto类,而是提供了两个接口,FromProto()和ToProto()来实现从BlobProto类来初始化一个Blob和将Blob的数据转换为BlobProto来写入文件等。关于protobuf的内容,可以参考我的另一篇博客 caffe中protobuf的使用
4.Blob类中有描述自己的形状,shape, shape是一个vector<int>, 用来描述数据的格式,对于图片数据来说,一般是num x channel x height x width. 也就是说明一个Blob可以存放多张图片,也就是一批图片,一张图片包含channel x height x width. 里面的数据是放在一个数组中的,所以是顺序存放的,所以需要访问某一张图片的某一个channel的第几行的第几个像素值,需要利用num, channel, height, width来计算出这个像素在数组中的索引才能找到。
5. Blob中存放数据的数组是利用shared_ptr来管理的,所以创建Blob的时候,就会根据传入的num, channel, height, width来计算buffer的大小,分配好内存了,这是自己分配的内存,所以需要自己free。但同时Blob还可以从外部传入数据指针,这时Blob内部有一个flag来记录是不是自己独占的数据,如果是外部传来的指针,则不会free。所以可以实现两个Blob拥有同一套数据,也就是同一个buffer。
6. Blob基本只提供了对数据的管理,没有神经网络方面的内容,另外提供了两个简单的计算,计算L1范数(元素和)和L2范数(元素的平方和)。
7. Caffe因为要兼容cpu mode和GPU mode,所以blob内部也实现了cpu mode和gpu mode的兼容,并提供了接口来获得cpu data或者gpu data。

这篇博客写的更详细一些,可以参考:
https://blog.youkuaiyun.com/happyday_d/article/details/103132379?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

https://blog.youkuaiyun.com/u010327085/article/details/50448912

blob的简单test: http://www.pianshen.com/article/6535187156/

下面我们进行一些源码分析:
1.我们前面介绍过protobuf在caffe中的作用,在caffe源码中,在src/caffe/proto目录下有一个protobuf描述文件caffe.proto,描述了在caffe中使用的structure(基本caffe中用到的所有的protobuf的message定义,都在这个文件中),这个文件通过protoc命令的编译,就生成了caffe.pb.h和caffe.pb.cc两个文件,这个不熟悉的可以参考我上面那篇介绍protobuf的博客。涉及blob的主要是 下面几个:

// Specifies the shape (dimensions) of a Blob. 说明Blob的数据维度,比如图片一般为num * channels * height * width 4维,存权重的话可能只有2维等,这里是表明存储的数据是几维的。
message BlobShape {
  repeated int64 dim = 1 [packed = true];
}

message BlobProto {
  optional BlobShape shape = 7;//数据的维度
  repeated float data = 5 [packed = true]; //float类型的data,一般会编译成vector的形式
  repeated float diff = 6 [packed = true]; //float类型的diff 数据
  repeated double double_data = 8 [packed = true]; //double类型的数据
  repeated double double_diff = 9 [packed = true]; //double类型的diff 数据

  // 4D dimensions -- deprecated.  Use "shape" instead.这几个参数废弃了,用BlobShape来替代了
  optional int32 num = 1 [default = 0];
  optional int32 channels = 2 [default = 0];
  optional int32 height = 3 [default = 0];
  optional int32 width = 4 [default = 0];
}

// The BlobProtoVector is simply a way to pass multiple blobproto instances
// around.
message BlobProtoVector {
  repeated BlobProto blobs = 1;
}

Blob通过上面几个定义,就和protobuf联系起来了。然后Blob类定义了两个接口:

void FromProto(const BlobProto& proto, bool reshape = true);
void ToProto(BlobProto* proto, bool write_diff = false) const;

通过ToProto函数,我们就可以将Blob对象转换为BlobProto对象,然后就可以将Blob中的数据串行化后存储到文件中,当从文件中将参数load到内存中的BlobProto对象后,就可以通过FromProto函数转换为Blob对象,这样我们就实现了将Blob对象中存储的数据存到文件中和从文件中加载到Blob对象中的功能。所以说一定要搞清楚Blob类和BlobProto类的关系。

下面我们看一下Blob类的头文件:我们把Deprecated函数都暂时删掉了

#ifndef CAFFE_BLOB_HPP_
#define CAFFE_BLOB_HPP_

#include <algorithm>
#include <string>
#include <vector>

#include "caffe/common.hpp"
#include "caffe/proto/caffe.pb.h"  //caffe.proto生成的头文件
#include "caffe/syncedmem.hpp" //为了cpu和gpu数据同步设计的类

const int kMaxBlobAxes = 32;

namespace caffe {
/**
 * @brief A wrapper around SyncedMemory holders serving as the basic
 *        computational unit through which Layer%s, Net%s, and Solver%s
 *        interact.
 *
 * TODO(dox): more thorough description.
 */
template <typename Dtype>
class Blob {
 public:
  Blob()
       : data_(), diff_(), count_(0), capacity_(0) {}
  explicit Blob(const vector<int>& shape); //构造函数,构造的时候需要指定维度
  /**
   * @brief Change the dimensions of the blob, allocating new memory if
   *        necessary.
   *
   * This function can be called both to create an initial allocation
   * of memory, and to adjust the dimensions of a top blob during Layer::Reshape
   * or Layer::Forward. When changing the size of blob, memory will only be
   * reallocated if sufficient memory does not already exist, and excess memory
   * will never be freed.
   *
   * Note that reshaping an input blob and immediately calling Net::Backward is
   * an error; either Net::Forward or Net::Reshape need to be called to
   * propagate the new input shape to higher layers.
   */
  void Reshape(const vector<int>& shape); //重新根据输入的形状扩充这个blob。
  void Reshape(const BlobShape& shape); //根据protobuf输入的shape来扩充这个blob。
  void ReshapeLike(const Blob& other); 
  inline string shape_string() const {  //输出shape的字符串信息,便于打印Log.
    ostringstream stream;
    for (int i = 0; i < shape_.size(); ++i) {
      stream << shape_[i] << " ";
    }
    stream << "(" << count_ << ")";
    return stream.str();
  }
  inline const vector<int>& shape() const { return shape_; }
  /**
   * @brief Returns the dimension of the index-th axis (or the negative index-th
   *        axis from the end, if index is negative).
   *
   * @param index the axis index, which may be negative as it will be
   *        "canonicalized" using CanonicalAxisIndex.
   *        Dies on out of range index.
   */
  inline int shape(int index) const {  //返回对应index的维度。CanonicalAxisIndex函数是为了可以输入负数来从后面开始index
    return shape_[CanonicalAxisIndex(index)];
  }
  inline int num_axes() const { return shape_.size(); } //返回这个blob有几个维度
  inline int count() const { return count_; }

  /**
   * @brief Compute the volume of a slice; i.e., the product of dimensions
   *        among a range of axes.
   *
   * @param start_axis The first axis to include in the slice.
   *
   * @param end_axis The first axis to exclude from the slice.
   */
  inline int count(int start_axis, int end_axis) const { //返回对应维度的数据数量,比如返回height*width
    CHECK_LE(start_axis, end_axis);
    CHECK_GE(start_axis, 0);
    CHECK_GE(end_axis, 0);
    CHECK_LE(start_axis, num_axes());
    CHECK_LE(end_axis, num_axes());
    int count = 1;
    for (int i = start_axis; i < end_axis; ++i) {
      count *= shape(i);
    }
    return count;
  }
  /**
   * @brief Compute the volume of a slice spanning from a particular first
   *        axis to the final axis.
   *
   * @param start_axis The first axis to include in the slice.
   */
  inline int count(int start_axis) const {
    return count(start_axis, num_axes());
  }

  /**
   * @brief Returns the 'canonical' version of a (usually) user-specifie
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值