blob是caffe中的基本數據結構,它聲明在blob.hpp中,這個文件位於caffe根目錄的include/caffe/路徑下,在這篇文章中,我們對blob.hpp文件進行詳細解讀,以便對blob數據結構有深刻的認識。
#ifndef CAFFE_BLOB_HPP_
#define CAFFE_BLOB_HPP_
#include <algorithm>
#include <string>
#include <vector>
#include "caffe/common.hpp"
#include "caffe/proto/caffe.pb.h"
#include "caffe/syncedmem.hpp"
這幾行代碼是blob.hpp文件的開頭,主要是該文件包含的頭文件。
const int kMaxBlobAxes = 32;
這一行定義了一個int型變量,它表示的是blob的最大維數,老版本的caffe包含的維數是num、channels、height、width共四個維度,新版本的caffe最多可以支持32個維度。
namespace caffe {
這一行定義caffe命名空間
template <typename Dtype>
這一行表明這個類是模板類,Dtype爲類型名,在c++中,通過這種方法可以實現多種數據類型的處理,比如Dtype可以float、double等。
class Blob {
public:
Blob()
: data_(), diff_(), count_(0), capacity_(0) {}
這幾行定義了Blob類,並聲明瞭Blob的默認構造函數。
explicit Blob(const int num, const int channels, const int height, const int width);
explicit Blob(const vector<int>& shape);
這兩行聲明瞭兩個Blob的顯式構造函數,分別採用兩種不同的數據類型來構造Blob類,第一個函數是使用num、channels、height、width這四個維度信息來構造Blob類,它是用來兼容老版本caffe的,第二個函數是使用int型的向量shape來構造Blob, shape最大維度爲32。
void Reshape(const int num, const int channels, const int height, const int width);
這一行定義了一個變形函數,它的作用是通過num、channels、height、width這四個維度信息來改變原有的Blob的形狀,這個函數主要是用來兼容老版本caffe的。
void Reshape(const vector<int>& shape);
這一行定義了另一個變形函數,它是通過shape向量來改變Blob的形狀。
void Reshape(const BlobShape& shape);
這一行定義的變形函數是根據Blob描述文件中的形狀信息來變形的函數。
void ReshapeLike(const Blob& other);
這一行依然是一個變形函數,它把本類的形狀變成與other類形狀相同。
inline string shape_string() const {
ostringstream stream;
for (int i = 0; i < shape_.size(); ++i) {
stream << shape_[i] << " ";
}
stream << "(" << count_ << ")";
return stream.str();
}
上面定義的是一個函數,它的作用是將Blob的形狀信息變成字符串並返回。在函數中首先定義了一個流輸出變量stream,然後在for循環中,把shape_的每一個維度轉換爲字符串掛接在stream後邊,最後把元素數目count_掛接在stream後邊,形成一個字符串輸出。其中,shape_和count_是Blob了的成員變量,shape_是表示Blob形狀的向量,count_是表示Blob元素數量的int型變量。caffe中成員變量是以下劃線結尾的,比較號區分。
inline const vector<int>& shape() const { return shape_; }
這一行定義的是讀取Blob形狀的的函數,直接返回成員變量shape_。
inline int shape(int index) const {
return shape_[CanonicalAxisIndex(index)];
}
這一行定義的函數是讀取某一個維度上的尺寸,在函數中調用了CanonicalAxisIndex()函數,它的作用是確認維度數index是否可用,在下面有詳細定義。
inline int num_axes() const { return shape_.size(); }
這個函數用來讀取Blob的維度數,直接返回成員變量shape_的尺寸。
inline int count() const { return count_; }
這個函數用來讀取Blob的元素數目,直接返回成員變量count_。
inline int count(int start_axis, int end_axis) const {
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;
}
上面這個函數用來計算從起始維度start_axis到結束維度end_axis的子集的元素數目,函數的前幾行用來檢測維度數是否合規,其中CHECK_LE(start_axis, end_axis);用來檢測start_axis要小於或等於end_axis。CHECK_LE是檢測函數,LE表示,檢測的變量前面的變量要小於或等於後面的變量。GE表示大於或等於。LT表示小於,GT表示大於。其他幾行讀者可以自行分析所起的作用。下面通過一個for循環來計算元素數,計算方法爲用count乘以所指定維度的尺寸。最後返回元素數目。
inline int count(int start_axis) const {
return count(start_axis, num_axes());
}
這個函數是計算從起始維度start_axis到最後維度的子集元素數目,調用第一個count函數來計算。
inline int CanonicalAxisIndex(int axis_index) const {
CHECK_GE(axis_index, -num_axes())
<< "axis " << axis_index << " out of range for " << num_axes()
<< "-D Blob with shape " << shape_string();
CHECK_LT(axis_index, num_axes())
<< "axis " << axis_index << " out of range for " << num_axes()
<< "-D Blob with shape " << shape_string();
if (axis_index < 0) {
return axis_index + num_axes();
}
return axis_index;
}
這個函數的作用是確認維度數是否合規,爲了兼容老版本caffe,維度索引的取值範圍爲[-num_axes(),num_axes()),這個函數首先判斷被判斷的維度值axis_index是否在這個範圍內,不在則退出,如果滿足條件,則將axis_index轉換到普通索引[0,num_axes() )範圍內,負數的轉換方法爲axis_index + num_axes()。最後返回轉換後的維度數。
inline int num() const { return LegacyShape(0); }
inline int channels() const { return LegacyShape(1); }
inline int height() const { return LegacyShape(2); }
inline int width() const { return LegacyShape(3); }
這幾個函數返回兼容老版本的num、channels、height、width這四個維度的尺寸。函數中調用了LegacyShape()函數,下面進行解釋。
inline int LegacyShape(int index) const {
CHECK_LE(num_axes(), 4)
<< "Cannot use legacy accessors on Blobs with > 4 axes.";
CHECK_LT(index, 4);
CHECK_GE(index, -4);
if (index >= num_axes() || index < -num_axes()) {
return 1;
}
return shape(index);
}
這個函數是支持老版本的返回某個維度形狀的函數,首先是判斷維度索引index是否合規,合規則調用上面的shape函數返回第index維度的尺寸。
inline int offset(const int n, const int c = 0, const int h = 0,
const int w = 0) const {
CHECK_GE(n, 0);
CHECK_LE(n, num());
CHECK_GE(channels(), 0);
CHECK_LE(c, channels());
CHECK_GE(height(), 0);
CHECK_LE(h, height());
CHECK_GE(width(), 0);
CHECK_LE(w, width());
return ((n * channels() + c) * height() + h) * width() + w;
}
這個函數的作用是返回Blob中某個元素在內存中的偏移值。雖然Blob中的數據是多維數組,但是數據在內存中依然是順序排列的。這個函數的輸入爲num、channels、height、width這四個維度的取值,輸出爲在[n][c][h][w]位置上的元素的偏移量。
inline int offset(const vector<int>& indices) const {
CHECK_LE(indices.size(), num_axes());
int offset = 0;
for (int i = 0; i < num_axes(); ++i) {
offset *= shape(i);
if (indices.size() > i) {
CHECK_GE(indices[i], 0);
CHECK_LT(indices[i], shape(i));
offset += indices[i];
}
}
return offset;
}
上面的代碼定義了另一個offset函數,它的輸入是indices向量,indices中包含的是每個維度上的索引值。
void CopyFrom(const Blob<Dtype>& source, bool copy_diff = false,
bool reshape = false);
這個函數的作用是從source對象中複製數據到當前類中。copy_diff標誌位指定是複製data還是複製diff,reshape標誌位用來指定是否允許改變Blob形狀。
inline Dtype data_at(const int n, const int c, const int h,
const int w) const {
return cpu_data()[offset(n, c, h, w)];
}
這個函數的作用是返回在num、channels、height、width這四個維度的n、c、h、w索引處的data元素值。調用offset函數返回內存中的data值。
inline Dtype diff_at(const int n, const int c, const int h,
const int w) const {
return cpu_diff()[offset(n, c, h, w)];
}
這個函數的作用是返回在num、channels、height、width這四個維度的n、c、h、w索引處的diff元素值。調用offset函數返回內存中的diff值。
inline Dtype data_at(const vector<int>& index) const {
return cpu_data()[offset(index)];
}
這個函數的作用與上面的第一個data_at()函數類似,返回的是內存中的data元素值,只不過它的輸入爲索引的向量。
inline Dtype diff_at(const vector<int>& index) const {
return cpu_diff()[offset(index)];
}
這個函數與上一個函數類似,是通過索引的向量來返回內存中的diff值。
inline const shared_ptr<SyncedMemory>& data() const {
CHECK(data_);
return data_;
}
上面的函數用來返回內存中的data的地址,其中SyncedMemory是一個用來同步CPU和GPU中的數據的類,它在caffe根目錄下的include/caffe/syncedmem.hpp文件中定義,有興趣可以對照着這個文件對SyncedMemory的作用進行研究。
inline const shared_ptr<SyncedMemory>& diff() const {
CHECK(diff_);
return diff_;
}
上面的函數用來返回內存中的diff的地址。
const Dtype* cpu_data() const;
這一行聲明瞭一個返回cpu中的data的地址的函數,對cpu_data的訪問爲只讀模式。
void set_cpu_data(Dtype* data);
這一行聲明的函數的作用是設置cpu_data,數據源爲data。
const int* gpu_shape() const;
這一行的作用是聲明一個返回gpu_shape的函數
const Dtype* gpu_data() const;
這一行的作用是返回gpu_data的地址,訪問方式爲只讀。
void set_gpu_data(Dtype* data);
這一行聲明的函數的作用是設置gpu_data,數據源爲data。
const Dtype* cpu_diff() const;
這一行的作用是返回cpu_diff的地址,訪問方式爲只讀。
const Dtype* gpu_diff() const;
這一行的作用是返回gpu_diff的地址,訪問方式爲只讀。
Dtype* mutable_cpu_data();
Dtype* mutable_gpu_data();
Dtype* mutable_cpu_diff();
Dtype* mutable_gpu_diff();
這四行聲明的是以讀寫方式訪問cpu_data、gpu_data、cpu_diff、gpu_diff這四種數據的函數。
void Update();
Blob的更新函數,作用是計算data與diff的對應元素的和。
void FromProto(const BlobProto& proto, bool reshape = true);
上面這個函數的作用是從BlobProto中讀取出一個Blob到本類。
void ToProto(BlobProto* proto, bool write_diff = false) const;
將本類寫入到BlobProto中。
Dtype asum_data() const;
Dtype asum_diff() const;
這兩個函數的作用是計算data和diff中的元素的的絕對值之和(L1範數)。
Dtype sumsq_data() const;
Dtype sumsq_diff() const;
這兩個函數的作用是計算data和diff中的元素的的平方和(L2範數)。
void scale_data(Dtype scale_factor);
void scale_diff(Dtype scale_factor);
這兩個函數的作用是將data和diff的各元素乘以一個標量係數。
void ShareData(const Blob& other);
void ShareDiff(const Blob& other);
這兩個函數的作用是共享另一個Blob的data和diff。
bool ShapeEquals(const BlobProto& other);
這個函數的作用是判斷本類的Blob與other的形狀是否相同。
protected:
shared_ptr<SyncedMemory> data_;
shared_ptr<SyncedMemory> diff_;
shared_ptr<SyncedMemory> shape_data_;
vector<int> shape_;
int count_;
int capacity_;
DISABLE_COPY_AND_ASSIGN(Blob);
}; // class Blob
} // namespace caffe
這些代碼是blob.hpp的結尾,定義的是Blob的若干個成員變量。data_爲data的內存地址,diff_爲diff的內存地址,shape_data_爲shape數據的地址,shape_爲shape的向量,count_爲Blob的元素數。capacity_爲blob的容量信息。DISABLE_COPY_AND_ASSIGN(Blob);表示禁用拷貝構造函數、賦值運算符重載。
至此,我們完成了blob.hpp文件的解讀。