根据caffe官方教程我们知道,在数据的前向传播和梯度的反向传播过程中,caffe以Blob的形式来存储、传递和操作数据,Blob是标准的array,也是框架的统一内存接口。
为了更明了,还是先把关于Blob的caffe官方教程贴过来吧。
Blob storage and communication
A Blob is a wrapper over the actual data being processed and passed along by Caffe, and also under the hood provides synchronization capability between the CPU and the GPU. Mathematically, a blob is an N-dimensional array stored in a C-contiguous fashion.
Caffe stores and communicates data using blobs. Blobs provide a unified memory interface holding data; e.g., batches of images, model parameters, and derivatives for optimization.
Blobs conceal the computational and mental overhead of mixed CPU/GPU operation by synchronizing from the CPU host to the GPU device as needed. Memory on the host and device is allocated on demand (lazily) for efficient memory usage.
The conventional blob dimensions for batches of image data are number N x channel K x height H x width W. Blob memory is row-major in layout, so the last / rightmost dimension changes fastest. For example, in a 4D blob, the value at index (n, k, h, w) is physically located at index ((n * K + k) * H + h) * W + w.
Number / N is the batch size of the data. Batch processing achieves better throughput for communication and device processing. For an ImageNet training batch of 256 images N = 256.
Channel / K is the feature dimension e.g. for RGB images K = 3.
Note that although many blobs in Caffe examples are 4D with axes for image applications, it is totally valid to use blobs for non-image applications. For example, if you simply need fully-connected networks like the conventional multi-layer perceptron, use 2D blobs (shape (N, D)) and call the InnerProductLayer (which we will cover soon).
Parameter blob dimensions vary according to the type and configuration of the layer. For a convolution layer with 96 filters of 11 x 11 spatial dimension and 3 inputs the blob is 96 x 3 x 11 x 11. For an inner product / fully-connected layer with 1000 output channels and 1024 input channels the parameter blob is 1000 x 1024.
For custom data it may be necessary to hack your own input preparation tool or data layer. However once your data is in your job is done. The modularity of layers accomplishes the rest of the work for you.
Implementation Details
As we are often interested in the values as well as the gradients of the blob, a Blob stores two chunks of memories, data and diff. The former is the normal data that we pass along, and the latter is the gradient computed by the network.
Further, as the actual values could be stored either on the CPU and on the GPU, there are two different ways to access them: the const way, which does not change the values, and the mutable way, which changes the values:
const Dtype* cpu_data() const;
Dtype* mutable_cpu_data();```
(similarly for gpu and diff).
The reason for such design is that, a Blob uses a SyncedMem class to synchronize values between the CPU and GPU in order to hide the synchronization details and to minimize data transfer. A rule of thumb is, always use the const call if you do not want to change the values, and never store the pointers in your own object. Every time you work on a blob, call the functions to get the pointers, as the SyncedMem will need this to figure out when to copy data.
In practice when GPUs are present, one loads data from the disk to a blob in CPU code, calls a device kernel to do GPU computation, and ferries the blob off to the next layer, ignoring low-level details while maintaining a high level of performance. As long as all layers have GPU implementations, all the intermediate data and gradients will remain in the GPU.
If you want to check out when a Blob will copy data, here is an illustrative example:
// Assuming that data are on the CPU initially, and we have a blob.
const Dtype* foo;
Dtype* bar;
foo = blob.gpu_data(); // data copied cpu->gpu.
foo = blob.cpu_data(); // no data copied since both have up-to-date contents.
bar = blob.mutable_gpu_data(); // no data copied.
// ... some operations ...
bar = blob.mutable_gpu_data(); // no data copied when we are still on GPU.
foo = blob.cpu_data(); // data copied gpu->cpu, since the gpu side has modified the data
foo = blob.gpu_data(); // no data copied since both have up-to-date contents
bar = blob.mutable_cpu_data(); // still no data copied.
bar = blob.mutable_gpu_data(); // data copied cpu->gpu.
bar = blob.mutable_cpu_data(); // data copied gpu->cpu.
通过上面的教程我们对Blob会有一个整体的了解,接下来通过阅读源码来进一步学习caffe中的Blob。学习的流程,包括后面的学习,先以注释的方式阅读源码,然后总结。
1. 源码
(本人对C++并不熟悉,所以希望通过这样的过程来学习,如果有错误希望大家批评指正)
blob.hpp
#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"
上面时blob.hpp包含的头文件,先简单了解一下这些头文件(除了前三个标准库)都做了些什么。
这个头文件里面首先是一堆头文件,接下来是一堆宏定义,这里先不细看。再接下来就时两个命名空间:
namespace cv { class Mat; }
这个暂时也不知道有什么用。
namespace caffe
这个命名空间里面有一个类caffe,这个应该是commo.hpp的主要部分了。
接下来看caffe类中都有些什么呢?
public:
~Caffe(); // 析构函数
// Thread local context for Caffe. Moved to common.cpp instead of
// including boost/thread.hpp to avoid a boost/NVCC issues (#1009, #1010)
// on OSX. Also fails on Linux with CUDA 7.0.18.
static Caffe& Get(); // 在新的线程创建caffe类对象,并返回它的引用?(需要大家指正)
enum Brew { CPU, GPU }; // 枚举
// This random number generator facade hides boost and CUDA rng
// implementation from one another (for cross-platform compatibility).
class RNG; // 一个产生随机数的类
// Getters for boost rng, curand, and cublas handles
// 不太明白这个函数作用。
inline static RNG& rng_stream();
// 接下来这几个比较好理解。
// Returns the mode: running on CPU or GPU.
inline static Brew mode() { return Get().mode_; }
// The setters for the variables
// Sets the mode. It is recommended that you don't change the mode halfway
// into the program since that may cause allocation of pinned memory being
// freed in a non-pinned way, which may cause problems - I haven't verified
// it personally but better to note it here in the header file.
inline static void set_mode(Brew mode) { Get().mode_ = mode; }
// Sets the random seed of both boost and curand
static void set_random_seed(const unsigned int seed);
// Sets the device. Since we have cublas and curand stuff, set device also
// requires us to reset those values.
static void SetDevice(const int device_id);
// Prints the current GPU status.
static void DeviceQuery();
// Check if specified device is available
static bool CheckDevice(const int device_id);
// Search from start_id to the highest possible device ordinal,
// return the ordinal of the first available device.
static int FindDevice(const int start_id = );
// Parallel training info
inline static int solver_count() { return Get().solver_count_; }
inline static void set_solver_count(int val) { Get().solver_count_ = val; }
inline static bool root_solver() { return Get().root_solver_; }
inline static void set_root_solver(bool val) { Get().root_solver_ = val; }
protected:
#ifndef CPU_ONLY
cublasHandle_t cublas_handle_;
curandGenerator_t curand_generator_;
#endif
shared_ptr<RNG> random_generator_;
Brew mode_;
int solver_count_;
bool root_solver_;
private:
// The private constructor to avoid duplicate instantiation.
Caffe();
DISABLE_COPY_AND_ASSIGN(Caffe);
总之,在common.hpp中,主要时singleton化caffe类,封装了boost和CUDA随机数生成的函数,提供了统一的接口。
对于#include "caffe/syncedmem.hpp"
,主要包含:
// 在cpu或gpu上分配内存
inline void CaffeMallocHost(void** ptr, size_t size, bool* use_cuda);
// 在cpu或gpu上释放内存
inline void CaffeFreeHost(void* ptr, bool use_cuda);
// 在cpu和gpu之间同步内存的类
class SyncedMemory;
caffe.pb.h是有caffe.proto生成的,可以参考我之前写的学习笔记。根据caffe.proto中关于Blob的定义:
message BlobShape {
repeated int64 dim = [packed = true];
}
message BlobProto {
optional BlobShape shape = ;
repeated float data = [packed = true];
repeated float diff = [packed = true];
repeated double double_data = [packed = true];
repeated double double_diff = [packed = true];
// 4D dimensions -- deprecated. Use "shape" instead.
optional int32 num = [default = ];
optional int32 channels = [default = ];
optional int32 height = [default = ];
optional int32 width = [default = ];
}
// The BlobProtoVector is simply a way to pass multiple blobproto instances
// around.
message BlobProtoVector {
repeated BlobProto blobs = ;
}
所以,Blob应该会用protocol buffer提供的接口,从文件中解析和序列化存储数据。
const int kMaxBlobAxes = ; // Blob的最大维数?
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.
*/
// BLOB是SyncedMemory的封装器,一个模板
template <typename Dtype>
class Blob {
public:
// 构造函数
Blob()
: data_(), diff_(), count_(), capacity_() {}
/// @brief Deprecated; use <code>Blob(const vector<int>& shape)</code>.
explicit Blob(const int num, const int channels, const int height,
const int width);
explicit Blob(const vector<int>& shape);
// reshape成员函数,容易理解
/// @brief Deprecated; use <code>Reshape(const vector<int>& shape)</code>.
void Reshape(const int num, const int channels, const int height,
const int width);
/**
* @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);
void Reshape(const BlobShape& shape);
void ReshapeLike(const Blob& other);
// 返回一个字符串,该字符串包含每一维度上的维数,用空格分开,最后是总的元素个数。
inline string shape_string() const {
ostringstream stream;
for (int i = ; i < shape_.size(); ++i) {
stream << shape_[i] << " ";
}
stream << "(" << count_ << ")";
return stream.str();
}
// 返回shape信息
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 {
return shape_[CanonicalAxisIndex(index)];
}
// 返回维度
inline int num_axes() const { return shape_.size(); }
// 返回元素总数
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.
*/
// 计算从start_axis维度到end_axis维度的元素总数
inline int count(int start_axis, int end_axis) const {
CHECK_LE(start_axis, end_axis);
CHECK_GE(start_axis, );
CHECK_GE(end_axis, );
CHECK_LE(start_axis, num_axes());
CHECK_LE(end_axis, num_axes());
int count = ;
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.
*/
// 计算从start_axis到最后维度的元素总数
inline int count(int start_axis) const {
return count(start_axis, num_axes());
}
/**
* @brief Returns the 'canonical' version of a (usually) user-specified axis,
* allowing for negative indexing (e.g., -1 for the last axis).
*
* @param axis_index the axis index.
* If 0 <= index < num_axes(), return index.
* If -num_axes <= index <= -1, return (num_axes() - (-index)),
* e.g., the last axis index (num_axes() - 1) if index == -1,
* the second to last if index == -2, etc.
* Dies on out of range index.
*/
// 计算维度索引,如果是负数,则从后往前数
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 < ) {
return axis_index + num_axes();
}
return axis_index;
}
/// @brief Deprecated legacy shape accessor num: use shape(0) instead.
inline int num() const { return LegacyShape(); }
/// @brief Deprecated legacy shape accessor channels: use shape(1) instead.
inline int channels() const { return LegacyShape(); }
/// @brief Deprecated legacy shape accessor height: use shape(2) instead.
inline int height() const { return LegacyShape(); }
/// @brief Deprecated legacy shape accessor width: use shape(3) instead.
inline int width() const { return LegacyShape(); }
// 检查维度数和维度的维数
inline int LegacyShape(int index) const {
// 不能检查维度大于4的Blob
CHECK_LE(num_axes(), )
<< "Cannot use legacy accessors on Blobs with > 4 axes.";
// 检查index是否在[-4,3]中
CHECK_LT(index, );
CHECK_GE(index, -);
// 如果维度数小于4,但是index不在[-num_axes(),num_axes() )内,返回1
if (index >= num_axes() || index < -num_axes()) {
// Axis is out of range, but still in [0, 3] (or [-4, -1] for reverse
// indexing) -- this special case simulates the one-padding used to fill
// extraneous axes of legacy blobs.
return ;
}
// 返回指定维度的维数
return shape(index);
}
// 计算offset
inline int offset(const int n, const int c = , const int h = ,
const int w = ) const {
CHECK_GE(n, );
CHECK_LE(n, num());
CHECK_GE(channels(), );
CHECK_LE(c, channels());
CHECK_GE(height(), );
CHECK_LE(h, height());
CHECK_GE(width(), );
CHECK_LE(w, width());
return ((n * channels() + c) * height() + h) * width() + w;
}
inline int offset(const vector<int>& indices) const {
CHECK_LE(indices.size(), num_axes());
int offset = ;
for (int i = ; i < num_axes(); ++i) {
offset *= shape(i);
if (indices.size() > i) {
CHECK_GE(indices[i], );
CHECK_LT(indices[i], shape(i));
offset += indices[i];
}
}
return offset;
}
/**
* @brief Copy from a source Blob.
*
* @param source the Blob to copy from
* @param copy_diff if false, copy the data; if true, copy the diff
* @param reshape if false, require this Blob to be pre-shaped to the shape
* of other (and die otherwise); if true, Reshape this Blob to other's
* shape if necessary
*/
// 从source Blob中拷贝数据,如果copy_diff=true则新的blob复制的是diff,如果reshape=true则改变新blob的形状
void CopyFrom(const Blob<Dtype>& source, bool copy_diff = false,
bool reshape = false);
// 获取在内存下的数据(前向传播所用的数据)
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)];
}
// 获取在内存下的diff数据(反传数据)
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)];
}
// 获取在内存下的数据(前向传播所用的数据)
inline Dtype data_at(const vector<int>& index) const {
return cpu_data()[offset(index)];
}
// 获取在内存下的diff数据(反传数据)
inline Dtype diff_at(const vector<int>& index) const {
return cpu_diff()[offset(index)];
}
// 同步内存shared_ptr
inline const shared_ptr<SyncedMemory>& data() const {
CHECK(data_);
return data_;
}
inline const shared_ptr<SyncedMemory>& diff() const {
CHECK(diff_);
return diff_;
}
// 这就是官方教程举例用到的函数(在blob.cpp中了解他们)
const Dtype* cpu_data() const;
void set_cpu_data(Dtype* data);
const int* gpu_shape() const;
const Dtype* gpu_data() const;
const Dtype* cpu_diff() const;
const Dtype* gpu_diff() const;
Dtype* mutable_cpu_data();
Dtype* mutable_gpu_data();
Dtype* mutable_cpu_diff();
Dtype* mutable_gpu_diff();
void Update();
// 从protobuf序列化文件读取blob对象
void FromProto(const BlobProto& proto, bool reshape = true);
// 将对象序列化为protobuf文件
void ToProto(BlobProto* proto, bool write_diff = false) const;
/// @brief Compute the sum of absolute values (L1 norm) of the data.
// 计算data的L1范数
Dtype asum_data() const;
/// @brief Compute the sum of absolute values (L1 norm) of the diff.
// 计算diff的L1范数
Dtype asum_diff() const;
/// @brief Compute the sum of squares (L2 norm squared) of the data.
// 计算data的L2范数
Dtype sumsq_data() const;
/// @brief Compute the sum of squares (L2 norm squared) of the diff.
// 计算diff的L2范数
Dtype sumsq_diff() const;
// 下面是将blob中的元素同乘一个数
/// @brief Scale the blob data by a constant factor.
void scale_data(Dtype scale_factor);
/// @brief Scale the blob diff by a constant factor.
void scale_diff(Dtype scale_factor);
/**
* @brief Set the data_ shared_ptr to point to the SyncedMemory holding the
* data_ of Blob other -- useful in Layer%s which simply perform a copy
* in their Forward pass.
*
* This deallocates the SyncedMemory holding this Blob's data_, as
* shared_ptr calls its destructor when reset with the "=" operator.
*/
// 将other中指向data的指针赋给this指向data的指针,同时this之前指向的data会被释放。
void ShareData(const Blob& other);
/**
* @brief Set the diff_ shared_ptr to point to the SyncedMemory holding the
* diff_ of Blob other -- useful in Layer%s which simply perform a copy
* in their Forward pass.
*
* This deallocates the SyncedMemory holding this Blob's diff_, as
* shared_ptr calls its destructor when reset with the "=" operator.
*/
// 与ShareData类似
void ShareDiff(const Blob& other);
// 判断this与other的形状是否相等
bool ShapeEquals(const BlobProto& other);
protected:
// protected 成员变量
//
shared_ptr<SyncedMemory> data_;
shared_ptr<SyncedMemory> diff_;
shared_ptr<SyncedMemory> shape_data_;
vector<int> shape_;
// count表示Blob中的元素个数,也就是个数*通道数*高度*宽度,capacity表示当前的元素个数,因为Blob可能会reshape。
int count_;
int capacity_;
DISABLE_COPY_AND_ASSIGN(Blob);
}; // class Blob
} // namespace caffe
#endif // CAFFE_BLOB_HPP_
下面看一下.cpp文件
blob.cpp
#include <climits>
#include <vector>
#include "caffe/blob.hpp"
#include "caffe/common.hpp"
#include "caffe/syncedmem.hpp"
#include "caffe/util/math_functions.hpp"
namespace caffe {
// 下面时reshape的实现方法
// 其实都是在调用Blob<Dtype>::Reshape(const vector<int>& shape)
template <typename Dtype>
void Blob<Dtype>::Reshape(const int num, const int channels, const int height,
const int width) {
vector<int> shape();
shape[] = num;
shape[] = channels;
shape[] = height;
shape[] = width;
Reshape(shape);
}
// 应该只有这个函数是在真正给数据分配空间
template <typename Dtype>
void Blob<Dtype>::Reshape(const vector<int>& shape) {
CHECK_LE(shape.size(), kMaxBlobAxes); //判断是否小于限制的最大维度
count_ = ;
shape_.resize(shape.size()); //获取shape的形状
// 储存形状信息的shape_data_如果没有分配空间或小于储存shape所需空间,则重新分配空间
// 注意shape_data_的类型,shared_ptr<SyncedMemory> shape_data_
if (!shape_data_ || shape_data_->size() < shape.size() * sizeof(int)) {
shape_data_.reset(new SyncedMemory(shape.size() * sizeof(int)));
}
// 类型转换,得到shape_data_数据的指针
int* shape_data = static_cast<int*>(shape_data_->mutable_cpu_data());
// 更新形状信息
for (int i = ; i < shape.size(); ++i) {
// 检查shape形状信息
CHECK_GE(shape[i], );
CHECK_LE(shape[i], INT_MAX / count_) << "blob size exceeds INT_MAX";
// 计算当前数据个数信息
count_ *= shape[i];
// 获取当前形状信息
shape_[i] = shape[i];
// 更新当前形状信息
shape_data[i] = shape[i];
// 在这里简单理解一下shape_和shape_data_的区别,首先两者都是存储shape信息的,shape_是vector<int>类型的,
// 就是用来存储即将要reshape的shape信息,而shape_data_是shared_ptr<SyncedMemory> shape_data_类型,
// 只有需要存储新的shape信息的空间比之前Blob存储shape信息的空间(shape_data_)大时,才会申请空间
// 下面的count_和capacity_与此类似
}
// 空间不够时,申请额外空间
if (count_ > capacity_) {
capacity_ = count_;
data_.reset(new SyncedMemory(capacity_ * sizeof(Dtype)));
diff_.reset(new SyncedMemory(capacity_ * sizeof(Dtype)));
}
}
template <typename Dtype>
void Blob<Dtype>::Reshape(const BlobShape& shape) {
CHECK_LE(shape.dim_size(), kMaxBlobAxes);
vector<int> shape_vec(shape.dim_size());
for (int i = ; i < shape.dim_size(); ++i) {
shape_vec[i] = shape.dim(i);
}
Reshape(shape_vec);
}
template <typename Dtype>
void Blob<Dtype>::ReshapeLike(const Blob<Dtype>& other) {
Reshape(other.shape());
}
// 下面两个构造函数也是用利用reshape给Blob数据申请空间
template <typename Dtype>
Blob<Dtype>::Blob(const int num, const int channels, const int height,
const int width)
// capacity_ must be initialized before calling Reshape
: capacity_() {
Reshape(num, channels, height, width);
}
template <typename Dtype>
Blob<Dtype>::Blob(const vector<int>& shape)
// capacity_ must be initialized before calling Reshape
// 现将capacity_初始化为零,然后reshape时就会申请空间
: capacity_() {
Reshape(shape);
}
// 获取Blob在gpu上存储数据的形状的指针,且是const的,不能通过该指针改变形状信息
template <typename Dtype>
const int* Blob<Dtype>::gpu_shape() const {
CHECK(shape_data_);
// gpu数据的形状数据存在gpu上
return (const int*)shape_data_->gpu_data();
}
// 与gpu类似
template <typename Dtype>
const Dtype* Blob<Dtype>::cpu_data() const {
CHECK(data_);
return (const Dtype*)data_->cpu_data();
}
// 将Blob中data_指向的数据释放掉(如果有数据),然后指向data指向的数据
// 这些数据上的操作都是由SyncedMemory类完成的
template <typename Dtype>
void Blob<Dtype>::set_cpu_data(Dtype* data) {
CHECK(data);
data_->set_cpu_data(data);
}
// 与上面cpu对应的gpu数据的操作,两者类似
template <typename Dtype>
const Dtype* Blob<Dtype>::gpu_data() const {
CHECK(data_);
return (const Dtype*)data_->gpu_data();
}
// 获取Blob中diff_指向cpu上数据的指针,并将其转换成Dtype型的(原来是void的),切是const的,不能通过该指针改变数据
template <typename Dtype>
const Dtype* Blob<Dtype>::cpu_diff() const {
CHECK(diff_);
return (const Dtype*)diff_->cpu_data();
}
// 与cpu类似
template <typename Dtype>
const Dtype* Blob<Dtype>::gpu_diff() const {
CHECK(diff_);
return (const Dtype*)diff_->gpu_data();
}
// 与cpu_data类似,区别在于可以通过返回的指针改变数据,下面几个mutable的函数类似
template <typename Dtype>
Dtype* Blob<Dtype>::mutable_cpu_data() {
CHECK(data_);
return static_cast<Dtype*>(data_->mutable_cpu_data());
}
template <typename Dtype>
Dtype* Blob<Dtype>::mutable_gpu_data() {
CHECK(data_);
return static_cast<Dtype*>(data_->mutable_gpu_data());
}
template <typename Dtype>
Dtype* Blob<Dtype>::mutable_cpu_diff() {
CHECK(diff_);
return static_cast<Dtype*>(diff_->mutable_cpu_data());
}
template <typename Dtype>
Dtype* Blob<Dtype>::mutable_gpu_diff() {
CHECK(diff_);
return static_cast<Dtype*>(diff_->mutable_gpu_data());
}
// 该函数在blob.hpp中有说明,简单说就是将this指向的data_中的数据换成other中data_中的数据
template <typename Dtype>
void Blob<Dtype>::ShareData(const Blob& other) {
CHECK_EQ(count_, other.count());
data_ = other.data();
}
// 与上面类似
template <typename Dtype>
void Blob<Dtype>::ShareDiff(const Blob& other) {
CHECK_EQ(count_, other.count());
diff_ = other.diff();
}
// The "update" method is used for parameter blobs in a Net, which are stored
// as Blob<float> or Blob<double> -- hence we do not define it for
// Blob<int> or Blob<unsigned int>.
// 主要针对parameter blobs,不提供int 和 unsigned int类型的操作
template <> void Blob<unsigned int>::Update() { NOT_IMPLEMENTED; }
template <> void Blob<int>::Update() { NOT_IMPLEMENTED; }
template <typename Dtype>
void Blob<Dtype>::Update() {
// We will perform update based on where the data is located.
// perform computation on CPU
// axpby即alpha * x plus beta *y 这个含义,blas中的函数
// template <> void caffe_axpy<float>(const int N, const float alpha, const float* X, float* Y) { cblas_saxpy(N,alpha, X, 1, Y, 1); }
// caffe_axpy计算的是Y=alpha * X + Y ,其中alpha=-1了这里
switch (data_->head()) {
case SyncedMemory::HEAD_AT_CPU:
// perform computation on CPU
caffe_axpy<Dtype>(count_, Dtype(-),
static_cast<const Dtype*>(diff_->cpu_data()),
static_cast<Dtype*>(data_->mutable_cpu_data()));
break;
case SyncedMemory::HEAD_AT_GPU:
case SyncedMemory::SYNCED:
#ifndef CPU_ONLY
// perform computation on GPU
caffe_gpu_axpy<Dtype>(count_, Dtype(-),
static_cast<const Dtype*>(diff_->gpu_data()),
static_cast<Dtype*>(data_->mutable_gpu_data()));
#else
NO_GPU;
#endif
break;
default:
LOG(FATAL) << "Syncedmem not initialized.";
}
}
// 下面几个计算L1和L2范数,以及scale的函数,功能上已经很清楚,实现细节应该参照mathfunctin里面封装的函数
template <> unsigned int Blob<unsigned int>::asum_data() const {
NOT_IMPLEMENTED;
return ;
}
template <> int Blob<int>::asum_data() const {
NOT_IMPLEMENTED;
return ;
}
template <typename Dtype>
Dtype Blob<Dtype>::asum_data() const {
if (!data_) { return ; }
switch (data_->head()) {
case SyncedMemory::HEAD_AT_CPU:
return caffe_cpu_asum(count_, cpu_data());
case SyncedMemory::HEAD_AT_GPU:
case SyncedMemory::SYNCED:
#ifndef CPU_ONLY
{
Dtype asum;
caffe_gpu_asum(count_, gpu_data(), &asum);
return asum;
}
#else
NO_GPU;
#endif
case SyncedMemory::UNINITIALIZED:
return ;
default:
LOG(FATAL) << "Unknown SyncedMemory head state: " << data_->head();
}
return ;
}
template <> unsigned int Blob<unsigned int>::asum_diff() const {
NOT_IMPLEMENTED;
return ;
}
template <> int Blob<int>::asum_diff() const {
NOT_IMPLEMENTED;
return ;
}
template <typename Dtype>
Dtype Blob<Dtype>::asum_diff() const {
if (!diff_) { return ; }
switch (diff_->head()) {
case SyncedMemory::HEAD_AT_CPU:
return caffe_cpu_asum(count_, cpu_diff());
case SyncedMemory::HEAD_AT_GPU:
case SyncedMemory::SYNCED:
#ifndef CPU_ONLY
{
Dtype asum;
caffe_gpu_asum(count_, gpu_diff(), &asum);
return asum;
}
#else
NO_GPU;
#endif
case SyncedMemory::UNINITIALIZED:
return ;
default:
LOG(FATAL) << "Unknown SyncedMemory head state: " << diff_->head();
}
return ;
}
template <> unsigned int Blob<unsigned int>::sumsq_data() const {
NOT_IMPLEMENTED;
return ;
}
template <> int Blob<int>::sumsq_data() const {
NOT_IMPLEMENTED;
return ;
}
template <typename Dtype>
Dtype Blob<Dtype>::sumsq_data() const {
Dtype sumsq;
const Dtype* data;
if (!data_) { return ; }
switch (data_->head()) {
case SyncedMemory::HEAD_AT_CPU:
data = cpu_data();
sumsq = caffe_cpu_dot(count_, data, data);
break;
case SyncedMemory::HEAD_AT_GPU:
case SyncedMemory::SYNCED:
#ifndef CPU_ONLY
data = gpu_data();
caffe_gpu_dot(count_, data, data, &sumsq);
#else
NO_GPU;
#endif
break;
case SyncedMemory::UNINITIALIZED:
return ;
default:
LOG(FATAL) << "Unknown SyncedMemory head state: " << data_->head();
}
return sumsq;
}
template <> unsigned int Blob<unsigned int>::sumsq_diff() const {
NOT_IMPLEMENTED;
return ;
}
template <> int Blob<int>::sumsq_diff() const {
NOT_IMPLEMENTED;
return ;
}
template <typename Dtype>
Dtype Blob<Dtype>::sumsq_diff() const {
Dtype sumsq;
const Dtype* diff;
if (!diff_) { return ; }
switch (diff_->head()) {
case SyncedMemory::HEAD_AT_CPU:
diff = cpu_diff();
sumsq = caffe_cpu_dot(count_, diff, diff);
break;
case SyncedMemory::HEAD_AT_GPU:
case SyncedMemory::SYNCED:
#ifndef CPU_ONLY
diff = gpu_diff();
caffe_gpu_dot(count_, diff, diff, &sumsq);
break;
#else
NO_GPU;
#endif
case SyncedMemory::UNINITIALIZED:
return ;
default:
LOG(FATAL) << "Unknown SyncedMemory head state: " << data_->head();
}
return sumsq;
}
template <> void Blob<unsigned int>::scale_data(unsigned int scale_factor) {
NOT_IMPLEMENTED;
}
template <> void Blob<int>::scale_data(int scale_factor) {
NOT_IMPLEMENTED;
}
template <typename Dtype>
void Blob<Dtype>::scale_data(Dtype scale_factor) {
Dtype* data;
if (!data_) { return; }
switch (data_->head()) {
case SyncedMemory::HEAD_AT_CPU:
data = mutable_cpu_data();
caffe_scal(count_, scale_factor, data);
return;
case SyncedMemory::HEAD_AT_GPU:
case SyncedMemory::SYNCED:
#ifndef CPU_ONLY
data = mutable_gpu_data();
caffe_gpu_scal(count_, scale_factor, data);
return;
#else
NO_GPU;
#endif
case SyncedMemory::UNINITIALIZED:
return;
default:
LOG(FATAL) << "Unknown SyncedMemory head state: " << data_->head();
}
}
template <> void Blob<unsigned int>::scale_diff(unsigned int scale_factor) {
NOT_IMPLEMENTED;
}
template <> void Blob<int>::scale_diff(int scale_factor) {
NOT_IMPLEMENTED;
}
template <typename Dtype>
void Blob<Dtype>::scale_diff(Dtype scale_factor) {
Dtype* diff;
if (!diff_) { return; }
switch (diff_->head()) {
case SyncedMemory::HEAD_AT_CPU:
diff = mutable_cpu_diff();
caffe_scal(count_, scale_factor, diff);
return;
case SyncedMemory::HEAD_AT_GPU:
case SyncedMemory::SYNCED:
#ifndef CPU_ONLY
diff = mutable_gpu_diff();
caffe_gpu_scal(count_, scale_factor, diff);
return;
#else
NO_GPU;
#endif
case SyncedMemory::UNINITIALIZED:
return;
default:
LOG(FATAL) << "Unknown SyncedMemory head state: " << diff_->head();
}
}
// 判断形状是否一样
template <typename Dtype>
bool Blob<Dtype>::ShapeEquals(const BlobProto& other) {
// 旧的Blob的判断
if (other.has_num() || other.has_channels() ||
other.has_height() || other.has_width()) {
// Using deprecated 4D Blob dimensions --
// shape is (num, channels, height, width).
// Note: we do not use the normal Blob::num(), Blob::channels(), etc.
// methods as these index from the beginning of the blob shape, where legacy
// parameter blobs were indexed from the end of the blob shape (e.g., bias
// Blob shape (1 x 1 x 1 x N), IP layer weight Blob shape (1 x 1 x M x N)).
return shape_.size() <= &&
LegacyShape(-) == other.num() &&
LegacyShape(-) == other.channels() &&
LegacyShape(-) == other.height() &&
LegacyShape(-) == other.width();
}
// 新的Blob的判断,维度不固定
vector<int> other_shape(other.shape().dim_size());
for (int i = ; i < other.shape().dim_size(); ++i) {
other_shape[i] = other.shape().dim(i);
}
return shape_ == other_shape;
}
// 从sourceBlob中复制数据,复制diff还是data由copy_diff确定
template <typename Dtype>
void Blob<Dtype>::CopyFrom(const Blob& source, bool copy_diff, bool reshape) {
if (source.count() != count_ || source.shape() != shape_) {
if (reshape) {
ReshapeLike(source);
} else {
LOG(FATAL) << "Trying to copy blobs of different sizes.";
}
}
switch (Caffe::mode()) {
case Caffe::GPU:
if (copy_diff) {
caffe_copy(count_, source.gpu_diff(),
static_cast<Dtype*>(diff_->mutable_gpu_data()));
} else {
caffe_copy(count_, source.gpu_data(),
static_cast<Dtype*>(data_->mutable_gpu_data()));
}
break;
case Caffe::CPU:
if (copy_diff) {
caffe_copy(count_, source.cpu_diff(),
static_cast<Dtype*>(diff_->mutable_cpu_data()));
} else {
caffe_copy(count_, source.cpu_data(),
static_cast<Dtype*>(data_->mutable_cpu_data()));
}
break;
default:
LOG(FATAL) << "Unknown caffe mode.";
}
}
// 从protocol buffer中读数据,包括形状信息,和data和diff数据
template <typename Dtype>
void Blob<Dtype>::FromProto(const BlobProto& proto, bool reshape) {
if (reshape) {
vector<int> shape;
if (proto.has_num() || proto.has_channels() ||
proto.has_height() || proto.has_width()) {
// Using deprecated 4D Blob dimensions --
// shape is (num, channels, height, width).
shape.resize();
shape[] = proto.num();
shape[] = proto.channels();
shape[] = proto.height();
shape[] = proto.width();
} else {
shape.resize(proto.shape().dim_size());
for (int i = ; i < proto.shape().dim_size(); ++i) {
shape[i] = proto.shape().dim(i);
}
}
Reshape(shape);
} else {
CHECK(ShapeEquals(proto)) << "shape mismatch (reshape not set)";
}
// copy data
Dtype* data_vec = mutable_cpu_data();
if (proto.double_data_size() > ) {
CHECK_EQ(count_, proto.double_data_size());
for (int i = ; i < count_; ++i) {
data_vec[i] = proto.double_data(i);
}
} else {
CHECK_EQ(count_, proto.data_size());
for (int i = ; i < count_; ++i) {
data_vec[i] = proto.data(i);
}
}
if (proto.double_diff_size() > ) {
CHECK_EQ(count_, proto.double_diff_size());
Dtype* diff_vec = mutable_cpu_diff();
for (int i = ; i < count_; ++i) {
diff_vec[i] = proto.double_diff(i);
}
} else if (proto.diff_size() > ) {
CHECK_EQ(count_, proto.diff_size());
Dtype* diff_vec = mutable_cpu_diff();
for (int i = ; i < count_; ++i) {
diff_vec[i] = proto.diff(i);
}
}
}
// 写protocol buffer数据,与上面的读相对应
template <>
void Blob<double>::ToProto(BlobProto* proto, bool write_diff) const {
proto->clear_shape();
for (int i = ; i < shape_.size(); ++i) {
proto->mutable_shape()->add_dim(shape_[i]);
}
proto->clear_double_data();
proto->clear_double_diff();
const double* data_vec = cpu_data();
for (int i = ; i < count_; ++i) {
proto->add_double_data(data_vec[i]);
}
if (write_diff) {
const double* diff_vec = cpu_diff();
for (int i = ; i < count_; ++i) {
proto->add_double_diff(diff_vec[i]);
}
}
}
template <>
void Blob<float>::ToProto(BlobProto* proto, bool write_diff) const {
proto->clear_shape();
for (int i = ; i < shape_.size(); ++i) {
proto->mutable_shape()->add_dim(shape_[i]);
}
proto->clear_data();
proto->clear_diff();
const float* data_vec = cpu_data();
for (int i = ; i < count_; ++i) {
proto->add_data(data_vec[i]);
}
if (write_diff) {
const float* diff_vec = cpu_diff();
for (int i = ; i < count_; ++i) {
proto->add_diff(diff_vec[i]);
}
}
}
INSTANTIATE_CLASS(Blob);
template class Blob<int>;
template class Blob<unsigned int>;
} // namespace caffe
2.总结
这是个人理解,有些理解不对和措辞不当的地方,希望得到指正。
通过阅读blob的源码,我们看到了Blob类对caffe中用到的数据做了很好的抽象,无论是对于数据,梯度,还是参数,都可以通过Blob类对数据操作,比如前向传播时应该会用到ShareData,优化参数时应该会用到Update和scale_diff等。另外还可以方便的同步cpu和gpu之间的数据,这个涉及到SyncedMemory类,后面再学习一下。
参考:
【1】http://blog.csdn.net/xizero00/article/details/50886829
【2】http://www.cnblogs.com/louyihang-loves-baiyan/p/5149628.html