caffe源码阅读

时间:2022-10-06 16:54:41

参考网址:https://www.cnblogs.com/louyihang-loves-baiyan/p/5149628.html

1、caffe代码层次
熟悉blob,layer,net,solver几类,
blob:作为数据输出的媒介,无论是网络权重参数,还是输入数据,都是转化为blob数据结构来存储。
layer:作为网络的基础单元,神经网络中层与层间的数据节点、前后传递都在数据结构中被实现,
net:作为网络的整体骨架,决定了网络中的层次数目以及各个层的类别
solver:作为网络的求解策略,涉及到求解优化问题的策略选择以及参数确定方面,修改这个模块的话一般都是会研究DL的优化求解的方向。

1、1、blob的类型描述
caffe 内部采用的数据类型主要是对protocol buffer所定义的数据结构的继承,因此可以在尽可能小的内存占用下获得很高的效率,blob看成一个4维的结构体(包含数据和梯度),实际上,它们只是一维的指针而已,其4维结构通过shape属性得以计算
1、2、blob的重要成员函数和变量
shared_ptr<SyncedMemory> data_//数据
shared_ptr<SyncedMemory> diff_//梯度
void blob<Dtype>::Reshape(const int num,const int channels,const int height,const int width)
重新修改blob的形状(4维),并根据形状来申请动态内存存储数据和梯度。
inline int count(int start_axis,int end_axis)const
计算blob所需要的基本数据单元的数量。
在更高一级的layer中blob用下面的形式表示学习到的参数
vector<shared_ptr<Blob<Dtype>>> blobs_;
这里使用的是一个blob的容器是因为某些layer包含多组学习参数,比如多个卷积核的卷积层。
vector<Blob<Dtype>*> &bottom;
vector<Blob<Dtype>*> *top
2、2layer:
2、2、15大layer派生类型
caffe十分强调网络的层次性具体五大类
NeuronLayer类定义于neuron_layers.hpp中,其派生类主要是元素级别的运算(Dropout运算,激活函数ReLu,Sigmoid等),运算均为同址计算(in-place computation,返回值覆盖原值而占用新的内存)。
LossLayer定义于loss_layers.hpp中,其派生类会产生loss,
数据层定义于data_layer.hpp中,作为网络的最底层,主要实现数据格式的转换。
特征表达层 vision_layers.hpp,特征表达功能,具体包含卷积操作,pooling操作
网络连接层和激活函数 定义于common_layers.hpp,caffe提供了单个层与多个层的连接,并在这个头文件中声明。还包括了常用的全连接层innerProductLayer
####################################
2.2.2layer的重要成员函数
在layer内部,数据主要有两种传递方式,正向传导和反向传导。forward和backward有cpu和gpu两种实现。caffe中所有的layer都要用这两种方法传递数据。
virtual void Forward(const vector<Blob<Dtype>*>&bottom,vector<Blob<Dtype>*>*top)=0;
virtual void Backward(const vector<Blob<Dtype>*>&top,const vector<bool> &propagate_down,vector<Blob<Dtype>*>*bottom)=0;
layer类派生出来的层类通过实现这两个虚函数,产生了各式各样功能的层类。Forward是根据bottom计算top的过程,backward则相反(根据top计算bottom)
对于大多数layer来说输入和输出都各连接只有一个layer,对于某些layer存在一对多的情况,比如losslayer和某些连接层。在网络结构定义文件(*.proto)中每一层的参数bottom和top数目决定了vector中元素数目。
layers{
 bottom:"decode1neuron" //该层底下连接的第一个layer
 bottom:"flatdata" //该层底下连接的第二个layer
 top:"l2_error" //该层顶上连接的一个layer
 name:"loss" //该层的名字
 type:EUCLIDEAN_LOSS//该层的类型
 loss_weight:0
}
2.2.3layer的重要成员变量
loss
vector<Dtype> loss_;
每一层又有一个loss_值,大多数layer都是0,只有losslayer可能产生非0的loss_。计算loss是把所有层的loss_相加
learnable parameteres
vector<shared_ptr<Blob<Dtype>>>blobs_;
2.3.Net:
net用容器的形式将多个layer有序的放在一起,其自身功能主要是对逐层layer进行初始化,以及提供update()的接口,
vector<shared_ptr<Layer<Dtype>>> layers_;
vector<Blob<Dtype>*>& Forward(const vector<Blob<Dtype>*>&bottom,Dtype* loss=NULL);
void Net<Dtype>::Backward();
2.4solver
这个类中包含一个net的指针,主要是实现了训练模型参数采用的优化算法,它派生的类就可以对整个网络进行训练了。
shared_ptr<Net<Dtype>>net_;
不同的模型训练方法通过重载函数ComputeUpdateValue()实现计算update参数的核心功能
virtual void ComputeUpdateValue() = 0;
最后当进行整个网络训练过程时候,实际上在运行caffe.cpp中的train()函数,而这个函数实际上是实例化一个solver对象,初始化后调用了solver中的solve()方法。
ComputeUpdateValue();
net_->Update();
######################################################
caffe源码解析:blob
explicit关键字的作用是禁止单参数构造函数的隐式转换。
inline的作用,将代码进行复制,扩充,可以节省调用的开销,提高执行的效率
1主要变量
shared_ptr<SyncedMemory>data_;
shared_ptr<SyncedMemory>diff_;
shared_ptr<SyncedMemory>shape_data_;
vector<int>shape_;
int count_;
int capacity_;
data_指针,指针类型是shared_ptr,属于boost库的一个智能指针,这一部分主要用来申请内存存储data,diff_用来存储偏差,update data,shape_data和shape_都是存储blob的形状,一个是老版本一个是新版本。count表示blob中的元素个数,也就是个数×通道数×高度×宽度,capacity表示当前的元素个数。

2、主要函数
template<typename Dtype>
class Blob{
  public:
   Blob()
     :data_(),diff_(),count_(0),capacity_(0){}
     explicit Blob(const int num,const int channels,const int height,const int width);
     explicit Blob(const vector<int>& shape);
     void Reshape(const int num,const int channels,const int height,const int width);     
}
blob是一个最基础的类,其中构造函数开辟一个内存空间来存储数据,reshape函数在Layer中的reshape或者forward操作中adjust dimension。同时在改变blob大小时,内存将会被重新分配如果内存大小不够,额外的内存将不会被释放。对input的blob进行reshape,如果立马调用Net::Backward是会出错的,因为reshape之后,要么Net::forward或者Net::Reshape就会被调用来将新的input shape传播到高层。
blob类里面有重载很多个count()函数,主要还是为了统计blob的容量(volume),或者是某一片(slice),从某个axis到具体某个axis的shape乘积
inline int count(int start_axis,int end_axis)
并且blob的index是可以从负坐标开始读
inline int CanonicalAxisIndex(int axis_index)
对于blob中的4个基本变量num,channel,height,width可以直接通过shape(0),shape(1),shape(2),shape(3)来访问
计算offset
inline int offset(const int n,const int c=0,const int h=0,const int w=0)
inline int offset(const vector<int>& indices)
offset计算的方式也支持两种方式,一种直接指定n,c,h,w,或者放到一个vector中进行计算,偏差是根据对应的n,c,h,w,返回的offset是(((n*channels())+c)*height()+h)*width()+w

void CopyFrom(const Blob<Dtype>& source,bool copy_diff=false,bool reshape=false);
从一个blob中copy数据,通过开关控制是否copy_diff,如果是false则copy data,
inline Dtype data_at(const int n,const int c,const int h,const int w)
inline Dtype diff_at(const int n,const int c,const int h,const int w)
inline Dtype data_at(const vector<int>& index)
inline Dtype diff_at(const vector<int>& index)
inline const shared_ptr<SyncedMemory>& data()
inline const shared_ptr<SyncedMemory>& diff()
这一部分函数主要通过给定的位置访问数据,根据位置计算与数据起始的偏差offset,在通过cpu_data*指针获得地址。
const Dtype* cpu_data() const;
void set_cpu_data(Dtype* data);
const int* gpu_shape() const;
const Dtype* cpu_diff() const;
const Dtype* gpu_diff() const;
Dtype* mutable_cpu_data();
Dtype* mutable_gpu_data();
data主要是存储前向传递的数据,后者存储的是后向传播中的梯度。
void Update();
看到update里面调用了caffe_axpy<float>(const int N,const float alpha,const float* X,float* Y){cblas_saxpy(N,alpha,X,1,Y,1);}
void FromProto(const BlobProto& proto,bool reshape=true);
void ToProto(BlobProto* proto,bool write_diff =false) const;
这两个函数主要是将数据序列化,存储到blobproto,这里说到proto是谷歌的一个数据序列化的存储格式,可以实现语言、平台无关、可扩展的序列化结构数据格式。
Dtype asum_data() const;//计算data的L1范数
Dtype asum_diff() const;//
Dtype sumsq_data() const;//计算data的L2范数
Dtype sumsq_data() const;
void scale_data(Dtype scale_factor);//将data部分乘以一个因子
void scale_diff(Dtype scale_factor);

这几个函数是一些零散的功能,
void ShareData(const Blob& other);
void ShareDiff(const Blob& other);
这两个函数是共享data,具体就是将别的blob的data和响应的diff指针给这个blob,实现数据的共享。这个操作会引起blob里面的SyncedMemory被释放,因为shared_ptr指针重置的时候回调用响应的析构器。

#caffe源码解析2:SyncedMem
SyncedMem是内存同步操作
首先是两个全局的内联函数,通过t粗大cudaMallocHost分配的host memory将会被pinned,pinned的意思是内存不会被paged out,内存里是由页作为基本的管理单元。分配的内存可以常驻在内存空间中,空间不会被别的进程所抢占。对多个gpu的并行可以提高稳定性。
这里两个封装过的函数,内部通过cuda来分配主机和释放内存的接口
inline void CaffeMallocHost(void** ptr,size_t size,bool* use_cuda){
#ifndef CPU_ONLY
   if(Caffe::mode()==Caffe::GPU){
     CUDA_CHECK(cudaMallocHost(ptr,size));//GPU模式下cuda分配内存
     *use_cuda =true;
     return;
   }
#endif
  *ptr =malloc(size);//如果没有cuda则通过c的malloc分配
  *use_cuda=false;
  CHECK(*ptr)<<"host allocation of size"<<size<<"failed";
}
inline void CaffeFreeHost(void* ptr,bool use_cuda){
#ifndef CPU_ONLY
  if(use_cuda){
    CUDA_CHECK(cudaFreeHost(ptr));//cuda的主机内存释放操作
    return ;
  }
#endif
 free(ptr);//c的释放操作
}
SyncedMemory类,首先是构造函数和析构函数
class SyncedMomory{
  public:
    SyncedMemory() //参数构造函数,负责初始化
       :cpu_ptr_(NULL),gpu_ptr_(NULL),size_(0),head_(UNINITIALIZED),
        own_cpu_data_(false),cpu_malloc_use_cuda_(false),own_gpu_data_(false),gpu_device_(-1){}
    explicit SyncedMemory(size_t size)//带explicit关键字的,单个参数构造函数,explicit禁止单参数构造函数的隐式转换。
   :cpu_ptr_(NULL),gpu_ptr_(NULL),size_(0),head_(UNINITIALIZED),
        own_cpu_data_(false),cpu_malloc_use_cuda_(false),own_gpu_data_(false),gpu_device_(-1){}
    ~SyncedMemory();//其在析构时调用的也是CaffeFreeHost
}

这几个函数分别是
const void* cpu_data();
void set_cpu_data(void* data);
const void* gpu_data();
void set_gpu_data(void* data);

cpu_data()主要是获得cpu上data的地址,set_cpu_data是将cpu的data指针指向一个新的区域由data指针传入,并且将原来的申请的内存释放。

void* mutable_cpu_data();
void* mutable_gpu_data();
enum SyncedHead{UNINITIALIZED,HEAD_AT_CPU,HEAD_AT_GPU,SYNCED};
SyncedHead head(){return head_;}
size_t size(){return size_;}

前两个分别是返回cpu和gpu上的data指针,并且状态为head_=HEAD_AT_CPU和响应的gpu版本。SyncedHead主要是个枚举类型,用来设定head_的状态,head()函数返回相应的数据状态,size()函数返回数据大小。

#ifndef CPU_ONLY
  void async_gpu_push(const cudaS& stream);
#endif
cuda拷贝的异步传输,数据从cpu拷贝到gpu,异步传输是已经假定caller会在使用之前做操作。

private:
 void to_cpu();
 void to_gpu();
 void* cpu_ptr_;
 void* gpu_ptr_;
 size_t size_;
 SyncedHead head_;
 bool own_cpu_data_;
 bool cpu_malloc_use_cuda_;
 bool own_gpu_data_;
 int gpu_device_;
 DISABLE_COPY_AND_ASSIGN(SyncedMemory);//禁止该类的拷贝与赋值
 
 cpu_ptr和gpu_ptr分别是cpu和gpu的数据指针。
 
 #######################caffe:Layer
 
layer必须实现一个forward function,caffe网络的前一层叫bottom,从bottom中获取blob,并且计算输出blob,根据input的blob以及output blob的error gradient梯度误差计算得到该层的梯度误差。
template<typename Dtype>
class Layer{
  public:
    explicit Layer(const LayerParameter& param)
     : Layer_param_(param),is_shared_(false){
     //set phase and copy blobs(if there are any).
     phase_=param.phase();
     if(layer_param_.blobs_size()>0)
       blobs_.resize(layer_param_.blobs_size());
       
     }

}