caffe源码 -- >blob访问数据方式

Original url:

http://www.voidcn.com/blog/iamzhangzhuping/article/p-4981696.html

Blob作为Caffe的四大模块之一,负责完成CPU/GPU存储申请、同步和数据持久化映射。Caffe内部数据存储和通讯都是通过Blob来完成,Blob提供统一的存储操作接口,可用来保存训练数据、模型参数等。Blob是一个高维连续数组,批处理图像数据时通常使用4维Blob,Blob的维度可以表示为(N, K, H, W),每个维度的意思分别是: 

N: 数据的个数,例如SGD时一次mini-batch的图像个数。 
K: 如果是图像,可以理解为通道数量;如果是网络中间结果,就是feature map的数量。 
H, W: 如果是图像数据,可以理解为图像的高度和宽度;如果是参数数据,可以理解为滤波核的高度和宽度。 
Caffe中通常只使用4维Blob完成图像应用,但是Blob完全可以合理地被用来存储任何数据,比如说学习到的参数。例如: 
1000幅640*480 RGBD图像数据,其Blob形状为(1000, 4, 480, 640)。 
96个大小11*11的滤波核,处理16通道的输入数据,其参数Blob的形状为(96,16,11,11)。 
1000个输出,1024个输入的全连接层,其参数Blob的形状为(1000,1024)。 
Blob是基础的数据结构,是用来保存学习到的参数以及网络传输过程中产生数据的类。在更高一级的Layer中Blob用下面的形式表示学习到的参数:vector<shared_ptr<Blob<Dtype> > > blobs_。 


blob.hpp主要定义了一个Blob类。 
首先看一下数据成员: 
protected: 
shared_ptr<SyncedMemory> data_; //shared_ptr应该为commom.hpp里引用的“using boost::shared_ptr”, 
shared_ptr<SyncedMemory> diff_; //SyncedMemory类封装了CPU/GPU内存申请、同步和释放 
shared_ptr<SyncedMemory> shape_data_; 
vector<int> shape_;//shape_是Blob维度参数 
int count_;//count表示Blob存储的元素个数(shape_所有元素乘积) 
int capacity_;//capacity_表示当前Blob的元素个数(控制动态分配) 


构造函数: 
默认构造函数完成最基本的初始化,两个显示构造函数会调用Reshape函数完成data_和diff_的共享内存对象SyncedMemory的申请。 

Reshape函数:

void Reshape(const vector<int>& shape);//主要完成数据成员shape_,shape_data_,count_,capacity_,data_,diff_最基本的初始化工作,主要包括内存分配,含初始化。

void Reshape(const BlobShape& shape);//特别是完成data_,diff_的共享内存对象SyncedMemory的申请。



Blob的数据访问方法: 
const Dtype* cpu_data() const; 
const Dtype* gpu_data() const; 
Dtype* mutable_cpu_data(); 
Dtype* mutable_gpu_data(); 
diff类似。Blob定义了两种数据访问方式:const方式只读,不允许改写数据;mutable方式可改写数据(对diff_的访问也是类似的)。以cpu_data()为例,看看数据访问是怎样完成的。 
//In blob.cpp 
template <typename Dtype> 
const Dtype* Blob<Dtype>::cpu_data() const { 
  CHECK(data_); 
  return (const Dtype*)data_->cpu_data(); 
}//data_是指向SyncedMemory类的智能指针,所以这里调用的是SyncedMemory类的cpu_data()方法.注意两个函数同名,但是属于不同类的方法。 
转向syncedmem.cpp 
//In syncedmem.cpp 
const void* SyncedMemory::cpu_data() { 
  to_cpu();//首先完成数据同步,第一次访问时会申请存储空间 
  return (const void*)cpu_ptr_;//返回内存指针--->void* cpu_ptr_;//In syncedmem.hpp内存指针 --->syncedmem.hpp里的几个数据成员如下: 

======================SyncedMemory.hpp部分数据成员============================= 
private: 
void to_cpu(); //数据由显存同步到内存 
void to_gpu(); //数据由内存同步到显存 
void* cpu_ptr_; //内存指针 
void* gpu_ptr_; //显存指针 
size_t size_; //数据大小 
SyncedHead head_; //当前数据状态,UNINITIALIZED, HEAD_AT_CPU, HEAD_AT_GPU, SYNCED 
bool own_cpu_data_; //是否分配了内存空间 
总结一下:Blob想要访问data_数据,由于Blob不关心细节,它会调用SyncedMemory的数据访问函数cpu_data(),由SyncedMemory的函数cpu_data()完成数据的同步并返回数据指针cpu_ptr_。 


Blob的数据持久化函数: 
Blob中存储了网络的中间处理结果和网络的参数,这些数据最终是要被存储到磁盘或从磁盘读入内存的,最后来看Blob的数据持久化函数是如何完成数据读写磁盘的。Caffe就是借助Google Protocol Buffers这个数据序列化和持久化库来完成的。 
Blob<double>::ToProto函数: 
template <> 
void Blob<double>::ToProto(BlobProto* proto, bool write_diff) const { 
  proto->clear_shape(); 
  for (int i = 0; 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();//调用Blob自己的cpu_data方法获取data_,然后拷贝 
  for (int i = 0; i < count_; ++i) { 
    proto->add_double_data(data_vec[i]); 
  } 
  if (write_diff) { 
    const double* diff_vec = cpu_diff();//调用Blob自己的cpu_diff方法获取diff_,然后拷贝 
    for (int i = 0; i < count_; ++i) { 
      proto->add_double_diff(diff_vec[i]); 
    } 
  } 
}//ToProto将Blob的shape_,data_,diff_分别copy到BlobProto的shape,data,diff,完成序列化. 


Blob<Dtype>::FromProto函数 
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(4); 
      shape[0] = proto.num(); 
      shape[1] = proto.channels(); 
      shape[2] = proto.height(); 
      shape[3] = proto.width(); 
    } else { 
      shape.resize(proto.shape().dim_size()); 
      for (int i = 0; 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() > 0) { 
    CHECK_EQ(count_, proto.double_data_size()); 
    for (int i = 0; i < count_; ++i) { 
      data_vec[i] = proto.double_data(i); 
    } 
  } else { 
    CHECK_EQ(count_, proto.data_size()); 
    for (int i = 0; i < count_; ++i) { 
      data_vec[i] = proto.data(i); 
    } 
  } 
  if (proto.double_diff_size() > 0) { 
    CHECK_EQ(count_, proto.double_diff_size()); 
    Dtype* diff_vec = mutable_cpu_diff(); 
    for (int i = 0; i < count_; ++i) { 
      diff_vec[i] = proto.double_diff(i); 
    } 
  } else if (proto.diff_size() > 0) { 
    CHECK_EQ(count_, proto.diff_size()); 
    Dtype* diff_vec = mutable_cpu_diff(); 
    for (int i = 0; i < count_; ++i) { 
      diff_vec[i] = proto.diff(i); 
    } 
  } 
}//FromProto将BlobProto的shape,data,diff分别copy到Blob的shape_,data_,diff_,完成数据解析。 


最后数据持久化函数由Protocol Buffers的工具实现,详见io.hpp 
// in io.hpp 
bool ReadProtoFromTextFile(const char* filename, Message* proto); 
bool ReadProtoFromBinaryFile(const char* filename, Message* proto); 
void WriteProtoToTextFile(const Message& proto, const char* filename); 
void WriteProtoToBinaryFile(const Message& proto, const char* filename); 
其中,数据可以text和binary两种格式被持久化。 


参数更新函数----Update方法: 
Blob还有一个参数更新函数也很重要Update, 它会被网络中存储参数的Blob调用,完成梯度下降过程中的参数更新。注意注释里说的“parameter blobs”,所以是针对存储参数的Blob进行参数更新。 
// 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>. 
template <typename Dtype> 
void Blob<Dtype>::Update() { 
  // We will perform update based on where the data is located. 
  switch (data_->head()) { 
  case SyncedMemory::HEAD_AT_CPU: 
    // perform computation on CPU 
    caffe_axpy<Dtype>(count_, Dtype(-1), 
        static_cast<const Dtype*>(diff_->cpu_data()), 
        static_cast<Dtype*>(data_->mutable_cpu_data()));//调用math_function.cpp中的模板函数caffe_axpy,它封装了cblas_saxpy函数,实际上就是2个向量的相加,具体网址https://developer.apple.com/library/mac/documentation/Accelerate/Reference/BLAS_Ref/#//apple_ref/c/func/cblas_saxpy 
    break; 
  case SyncedMemory::HEAD_AT_GPU: 
  case SyncedMemory::SYNCED: 
#ifndef CPU_ONLY 
    // perform computation on GPU 
    caffe_gpu_axpy<Dtype>(count_, Dtype(-1), 
        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."; 
  } 

核心计算就是梯度下降更新。

总结 Caffe中Blob封装了各种存储相关的操作,包括内存显存分配、同步、数据访问、数据读写磁盘等。它将作为基本数据模块被包含到Layer和Net中,后面将分析他们是如何被Layer和Net使用的。

(转载自:https://blog.csdn.net/junmuzi/article/details/52761379

猜你喜欢

转载自blog.csdn.net/zhanghenan123/article/details/81123049