blob是caffe中的基本数据结构,它声明在blob.hpp中,这个文件位于caffe根目录的include/caffe/路径下,在这篇文章中,我们对blob.hpp文件进行详细解读,以便对blob数据结构有深刻的认识。
1
2
3
4
5
6
7
8
9
10
11 1#ifndef CAFFE_BLOB_HPP_
2#define CAFFE_BLOB_HPP_
3
4#include <algorithm>
5#include <string>
6#include <vector>
7
8#include "caffe/common.hpp"
9#include "caffe/proto/caffe.pb.h"
10#include "caffe/syncedmem.hpp"
11
这几行代码是blob.hpp文件的开头,主要是该文件包含的头文件。
1
2 1const int kMaxBlobAxes = 32;
2
这一行定义了一个int型变量,它表示的是blob的最大维数,老版本的caffe包含的维数是num、channels、height、width共四个维度,新版本的caffe最多可以支持32个维度。
1
2 1namespace caffe {
2
这一行定义caffe命名空间
1
2 1template <typename Dtype>
2
这一行表明这个类是模板类,Dtype为类型名,在c++中,通过这种方法可以实现多种数据类型的处理,比如Dtype可以float、double等。
1
2
3
4
5 1class Blob {
2 public:
3 Blob()
4 : data_(), diff_(), count_(0), capacity_(0) {}
5
这几行定义了Blob类,并声明了Blob的默认构造函数。
1
2
3 1 explicit Blob(const int num, const int channels, const int height, const int width);
2 explicit Blob(const vector<int>& shape);
3
这两行声明了两个Blob的显式构造函数,分别采用两种不同的数据类型来构造Blob类,第一个函数是使用num、channels、height、width这四个维度信息来构造Blob类,它是用来兼容老版本caffe的,第二个函数是使用int型的向量shape来构造Blob, shape最大维度为32。
1
2 1 void Reshape(const int num, const int channels, const int height, const int width);
2
这一行定义了一个变形函数,它的作用是通过num、channels、height、width这四个维度信息来改变原有的Blob的形状,这个函数主要是用来兼容老版本caffe的。
1
2 1 void Reshape(const vector<int>& shape);
2
这一行定义了另一个变形函数,它是通过shape向量来改变Blob的形状。
1
2 1 void Reshape(const BlobShape& shape);
2
这一行定义的变形函数是根据Blob描述文件中的形状信息来变形的函数。
1
2 1 void ReshapeLike(const Blob& other);
2
这一行依然是一个变形函数,它把本类的形状变成与other类形状相同。
1
2
3
4
5
6
7
8
9 1 inline string shape_string() const {
2 ostringstream stream;
3 for (int i = 0; i < shape_.size(); ++i) {
4 stream << shape_[i] << " ";
5 }
6 stream << "(" << count_ << ")";
7 return stream.str();
8 }
9
上面定义的是一个函数,它的作用是将Blob的形状信息变成字符串并返回。在函数中首先定义了一个流输出变量stream,然后在for循环中,把shape_的每一个维度转换为字符串挂接在stream后边,最后把元素数目count_挂接在stream后边,形成一个字符串输出。其中,shape_和count_是Blob了的成员变量,shape_是表示Blob形状的向量,count_是表示Blob元素数量的int型变量。caffe中成员变量是以下划线结尾的,比较号区分。
1
2 1 inline const vector<int>& shape() const { return shape_; }
2
这一行定义的是读取Blob形状的的函数,直接返回成员变量shape_。
1
2
3
4 1 inline int shape(int index) const {
2 return shape_[CanonicalAxisIndex(index)];
3 }
4
这一行定义的函数是读取某一个维度上的尺寸,在函数中调用了CanonicalAxisIndex()函数,它的作用是确认维度数index是否可用,在下面有详细定义。
1
2
3 1 inline int num_axes() const { return shape_.size(); }
2
3
这个函数用来读取Blob的维度数,直接返回成员变量shape_的尺寸。
1
2 1 inline int count() const { return count_; }
2
这个函数用来读取Blob的元素数目,直接返回成员变量count_。
1
2
3
4
5
6
7
8
9
10
11
12
13 1 inline int count(int start_axis, int end_axis) const {
2 CHECK_LE(start_axis, end_axis);
3 CHECK_GE(start_axis, 0);
4 CHECK_GE(end_axis, 0);
5 CHECK_LE(start_axis, num_axes());
6 CHECK_LE(end_axis, num_axes());
7 int count = 1;
8 for (int i = start_axis; i < end_axis; ++i) {
9 count *= shape(i);
10 }
11 return count;
12 }
13
上面这个函数用来计算从起始维度start_axis到结束维度end_axis的子集的元素数目,函数的前几行用来检测维度数是否合规,其中CHECK_LE(start_axis, end_axis);用来检测start_axis要小于或等于end_axis。CHECK_LE是检测函数,LE表示,检测的变量前面的变量要小于或等于后面的变量。GE表示大于或等于。LT表示小于,GT表示大于。其他几行读者可以自行分析所起的作用。下面通过一个for循环来计算元素数,计算方法为用count乘以所指定维度的尺寸。最后返回元素数目。
1
2
3
4 1 inline int count(int start_axis) const {
2 return count(start_axis, num_axes());
3 }
4
这个函数是计算从起始维度start_axis到最后维度的子集元素数目,调用第一个count函数来计算。
1
2
3
4
5
6
7
8
9
10
11
12
13 1 inline int CanonicalAxisIndex(int axis_index) const {
2 CHECK_GE(axis_index, -num_axes())
3 << "axis " << axis_index << " out of range for " << num_axes()
4 << "-D Blob with shape " << shape_string();
5 CHECK_LT(axis_index, num_axes())
6 << "axis " << axis_index << " out of range for " << num_axes()
7 << "-D Blob with shape " << shape_string();
8 if (axis_index < 0) {
9 return axis_index + num_axes();
10 }
11 return axis_index;
12 }
13
这个函数的作用是确认维度数是否合规,为了兼容老版本caffe,维度索引的取值范围为[-num_axes(),num_axes()),这个函数首先判断被判断的维度值axis_index是否在这个范围内,不在则退出,如果满足条件,则将axis_index转换到普通索引[0,num_axes() )范围内,负数的转换方法为axis_index + num_axes()。最后返回转换后的维度数。
1
2
3
4
5 1 inline int num() const { return LegacyShape(0); }
2 inline int channels() const { return LegacyShape(1); }
3 inline int height() const { return LegacyShape(2); }
4 inline int width() const { return LegacyShape(3); }
5
这几个函数返回兼容老版本的num、channels、height、width这四个维度的尺寸。函数中调用了LegacyShape()函数,下面进行解释。
1
2
3
4
5
6
7
8
9
10
11 1 inline int LegacyShape(int index) const {
2 CHECK_LE(num_axes(), 4)
3 << "Cannot use legacy accessors on Blobs with > 4 axes.";
4 CHECK_LT(index, 4);
5 CHECK_GE(index, -4);
6 if (index >= num_axes() || index < -num_axes()) {
7 return 1;
8 }
9 return shape(index);
10 }
11
这个函数是支持老版本的返回某个维度形状的函数,首先是判断维度索引index是否合规,合规则调用上面的shape函数返回第index维度的尺寸。
1
2
3
4
5
6
7
8
9
10
11
12
13 1 inline int offset(const int n, const int c = 0, const int h = 0,
2 const int w = 0) const {
3 CHECK_GE(n, 0);
4 CHECK_LE(n, num());
5 CHECK_GE(channels(), 0);
6 CHECK_LE(c, channels());
7 CHECK_GE(height(), 0);
8 CHECK_LE(h, height());
9 CHECK_GE(width(), 0);
10 CHECK_LE(w, width());
11 return ((n * channels() + c) * height() + h) * width() + w;
12 }
13
这个函数的作用是返回Blob中某个元素在内存中的偏移值。虽然Blob中的数据是多维数组,但是数据在内存中依然是顺序排列的。这个函数的输入为num、channels、height、width这四个维度的取值,输出为在[n][c][h][w]位置上的元素的偏移量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14 1 inline int offset(const vector<int>& indices) const {
2 CHECK_LE(indices.size(), num_axes());
3 int offset = 0;
4 for (int i = 0; i < num_axes(); ++i) {
5 offset *= shape(i);
6 if (indices.size() > i) {
7 CHECK_GE(indices[i], 0);
8 CHECK_LT(indices[i], shape(i));
9 offset += indices[i];
10 }
11 }
12 return offset;
13 }
14
上面的代码定义了另一个offset函数,它的输入是indices向量,indices中包含的是每个维度上的索引值。
1
2
3 1 void CopyFrom(const Blob<Dtype>& source, bool copy_diff = false,
2 bool reshape = false);
3
这个函数的作用是从source对象中复制数据到当前类中。copy_diff标志位指定是复制data还是复制diff,reshape标志位用来指定是否允许改变Blob形状。
1
2
3
4
5 1 inline Dtype data_at(const int n, const int c, const int h,
2 const int w) const {
3 return cpu_data()[offset(n, c, h, w)];
4 }
5
这个函数的作用是返回在num、channels、height、width这四个维度的n、c、h、w索引处的data元素值。调用offset函数返回内存中的data值。
1
2
3
4
5 1 inline Dtype diff_at(const int n, const int c, const int h,
2 const int w) const {
3 return cpu_diff()[offset(n, c, h, w)];
4 }
5
这个函数的作用是返回在num、channels、height、width这四个维度的n、c、h、w索引处的diff元素值。调用offset函数返回内存中的diff值。
1
2
3
4 1 inline Dtype data_at(const vector<int>& index) const {
2 return cpu_data()[offset(index)];
3 }
4
这个函数的作用与上面的第一个data_at()函数类似,返回的是内存中的data元素值,只不过它的输入为索引的向量。
1
2
3
4 1 inline Dtype diff_at(const vector<int>& index) const {
2 return cpu_diff()[offset(index)];
3 }
4
这个函数与上一个函数类似,是通过索引的向量来返回内存中的diff值。
1
2
3
4
5 1 inline const shared_ptr<SyncedMemory>& data() const {
2 CHECK(data_);
3 return data_;
4 }
5
上面的函数用来返回内存中的data的地址,其中SyncedMemory是一个用来同步CPU和GPU中的数据的类,它在caffe根目录下的include/caffe/syncedmem.hpp文件中定义,有兴趣可以对照着这个文件对SyncedMemory的作用进行研究。
1
2
3
4
5 1 inline const shared_ptr<SyncedMemory>& diff() const {
2 CHECK(diff_);
3 return diff_;
4 }
5
上面的函数用来返回内存中的diff的地址。
1
2 1 const Dtype* cpu_data() const;
2
这一行声明了一个返回cpu中的data的地址的函数,对cpu_data的访问为只读模式。
1
2 1 void set_cpu_data(Dtype* data);
2
这一行声明的函数的作用是设置cpu_data,数据源为data。
1
2 1 const int* gpu_shape() const;
2
这一行的作用是声明一个返回gpu_shape的函数
1
2 1 const Dtype* gpu_data() const;
2
这一行的作用是返回gpu_data的地址,访问方式为只读。
1
2 1 void set_gpu_data(Dtype* data);
2
这一行声明的函数的作用是设置gpu_data,数据源为data。
1
2 1 const Dtype* cpu_diff() const;
2
这一行的作用是返回cpu_diff的地址,访问方式为只读。
1
2 1 const Dtype* gpu_diff() const;
2
这一行的作用是返回gpu_diff的地址,访问方式为只读。
1
2
3
4
5 1 Dtype* mutable_cpu_data();
2 Dtype* mutable_gpu_data();
3 Dtype* mutable_cpu_diff();
4 Dtype* mutable_gpu_diff();
5
这四行声明的是以读写方式访问cpu_data、gpu_data、cpu_diff、gpu_diff这四种数据的函数。
1
2 1 void Update();
2
Blob的更新函数,作用是计算data与diff的对应元素的和。
1
2 1 void FromProto(const BlobProto& proto, bool reshape = true);
2
上面这个函数的作用是从BlobProto中读取出一个Blob到本类。
1
2 1 void ToProto(BlobProto* proto, bool write_diff = false) const;
2
将本类写入到BlobProto中。
1
2
3
4 1 Dtype asum_data() const;
2 Dtype asum_diff() const;
3
4
这两个函数的作用是计算data和diff中的元素的的绝对值之和(L1范数)。
1
2
3 1 Dtype sumsq_data() const;
2 Dtype sumsq_diff() const;
3
这两个函数的作用是计算data和diff中的元素的的平方和(L2范数)。
1
2
3 1 void scale_data(Dtype scale_factor);
2 void scale_diff(Dtype scale_factor);
3
这两个函数的作用是将data和diff的各元素乘以一个标量系数。
1
2
3 1 void ShareData(const Blob& other);
2 void ShareDiff(const Blob& other);
3
这两个函数的作用是共享另一个Blob的data和diff。
1
2 1 bool ShapeEquals(const BlobProto& other);
2
这个函数的作用是判断本类的Blob与other的形状是否相同。
1
2
3
4
5
6
7
8
9
10
11
12
13 1 protected:
2 shared_ptr<SyncedMemory> data_;
3 shared_ptr<SyncedMemory> diff_;
4 shared_ptr<SyncedMemory> shape_data_;
5 vector<int> shape_;
6 int count_;
7 int capacity_;
8
9 DISABLE_COPY_AND_ASSIGN(Blob);
10}; // class Blob
11
12} // namespace caffe
13
这些代码是blob.hpp的结尾,定义的是Blob的若干个成员变量。data_为data的内存地址,diff_为diff的内存地址,shape_data_为shape数据的地址,shape_为shape的向量,count_为Blob的元素数。capacity_为blob的容量信息。DISABLE_COPY_AND_ASSIGN(Blob);表示禁用拷贝构造函数、赋值运算符重载。
至此,我们完成了blob.hpp文件的解读。