caffe源码简单解析——Blob(1)November26,2014caffe,源码阅读,blob使用caffe也有一段时间了,但更多是使用Python的接口,使用现有的ImageNet训练好的模型进行图片分类。为了更好的了解caffe这个框架,也为了提高自己的水平,在对卷积神经网络有了一些研究之后,终于开始研读caffe的源码了,今天看了Blob类的一些内容,做个总结。看过caffe官方文档的话,应该会知道,它可以分为三层:Blob、Layer、Net。Blob是一个四维的数组,用于存储数据,包括输入数据、输出数据、权值等等;Layer层则是神经网络中具体的各层结构,主要是计算的作用,在根据配置文件初始化结构后,前向计算结果,反向更新参数,都是它要做的,而它的输入和输出都是Blob数据;Net的话,就是多个Layer组合而成的有向无环图结构,也就是具体的网络了。Layer和Net的代码有待深入,尤其是Layer的代码,caffe实现了差不多40种不同的Layer层,里面有不同的激活函数,这个要好好研究下。Blob源码解析#includecaffe/common.hpp#includecaffe/proto/caffe.pb.h#includecaffe/syncedmem.hpp#includecaffe/util/math_functions.hpp从blob.hpp包含的四个头文件入手,其中caffe.pb.h是googleprotocolbuffer根据caffe.proto自动生成的,可以到src/caffe/proto/caffe.proto里看下caffe里面用到的各个数据的定义,比如BlobProto,Datum,NetParameter等。使用这个protocolbuffer看起来确实方便,一方面可以用文本文件定义结构化的数据类型,另一方面可以生成查询效率更高、占空间更小的二进制文件,具体的教程可以看看这里。在caffe/common.hpp,主要singleton化Caffe类,并封装了boost和CUDA随机数生成的函数,提供了统一的接口。而在caffe/syncedmem.hpp中,定义了以下的接口:inlinevoidCaffeMallocHost(void**ptr,size_tsize)inlinevoidCaffeFreeHost(void*ptr)主要是分配内存和释放内存的。而classSyncedMemory定义了内存分配管理和CPU与GPU之间同步的函数,也没啥特别的。比较重要的是caffe/util/math_functions.hpp,这里面封装了很多cblas矩阵运算,真是密密麻麻,看的我眼花缭乱、如痴如醉。比如:voidcaffe_cpu_gemmfloat(constCBLAS_TRANSPOSETransA,constCBLAS_TRANSPOSETransB,constintM,constintN,constintK,constfloatalpha,constfloat*A,constfloat*B,constfloatbeta,float*C)封装了cblas_sgemm(CblasRowMajor,TransA,TransB,M,N,K,alpha,A,lda,B,ldb,beta,C,N),这个计算得到的结果为C=alphaAB+beta*C,也即是A和B两个矩阵的乘积。这里有详细的解释。voidcaffe_cpu_gemvfloat(constCBLAS_TRANSPOSETransA,constintM,constintN,constfloatalpha,constfloat*A,constfloat*x,constfloatbeta,float*y)是对cblas_sgemv的封装,实现的矩阵与向量的乘积,结果为y=alphaAx+beta*y。voidcaffe_axpyfloat(constintN,constfloatalpha,constfloat*X,float*Y)封装了cblas_saxpy,实现的是Y=alpha*X+Y里面都是诸如此类的函数,基本是些矩阵和向量的一些处理函数。回到blob类,里面定义了data_(),diff_()指针,用于存放数据,而num_,channel_,height_,width_则主要用来做定位offset和reshape处理。对于输入(n,c,h,w)位置的数据位置为((n*channels_+c)*height_+h)*width_+w,可以依据位置取data_()或diff_()中的数据。对blob的理解还要结合caffe.proto里面BlobProto的定义:messageBlobProto{optionalint32num=1[default=0];optionalint32channels=2[default=0];optionalint32height=3[default=0];optionalint32width=4[default=0];repeatedfloatdata=5[packed=true];repeatedfloatdiff=6[packed=true];}对于BlobProto,可以看到定义了四个optional的int32类型的名字(name)num、channels、height和width,optional意味着Blob可以有一个或者没有这个参数,每个名字(name)后面都有一个数字,这个数字是其名字的一个标签。这个数字就是用来在生成的二进制文件中搜索查询的标签(怪不得会快呢^_^)。关于这个数字,1到15会花费1byte的编码空间,16到2047花费2byte。所以一般建议把那些频繁使用的名字的标签设为1到15之间的值~而后面的repeated意味着float类型的data和diff可以重复任意次,而加上[packed=true]是为了更高效的编码。到这里基本上Blob就很清楚了,主要数据有两个data和diff,用num、channels、height和width这四个维度来确定数据的具体位置,做一些数据查询和Blobreshape的操作。关于Blob就这么多内容,毕竟就是一个统一的数据存取接口,后续会重点读一下Layer的源码,毕竟各层的输入输出和计算更新过程都在里面,还需要补充一些相关的知识~~目前的感受,是学到了一些封装的手法,可以看看封装cblas函数的那个文件,以及CPU和GPU一些接口的封装上;另一方面是对于ProtocolBuffer有了一些了解,目前看起来确实方便,以后如果遇到类似的场景可以试着用一下~~caffe源码简单解析——Layer层December3,2014caffe,源码,layer前言老实说,caffe中的layer层代码比较多,各种抽象看起来比较绕。官方关于Layer的教程写的很清楚,我根据这个文档,简单画了个图,再理解起来就方便了一些。layer.hpp和layer相关的头文件有:common_layers.hppdata_layers.hpplayer.hpploss_layers.hppneuron_layers.hppvision_layers.hpp其中``layer.hpp是抽象出来的基类,其他都是在其基础上的继承,也即剩下的五个头文件和上图中的五个部分。在layer.hpp`头文件里,包含了这几个头文件:#includecaffe/blob.hpp#includecaffe/common.hpp#includecaffe/proto/caffe.pb.h#includecaffe/util/device_alternate.hpp在device_alternate.hpp中,通过#ifdefCPU_ONLY定义了一些宏来取消GPU的调用:#defineSTUB_GPU(classname)#defineSTUB_GPU_FORWARD(classname,funcname)#defineSTUB_GPU_BACKWARD(classname,funcname)layer中有这三个主要参数:LayerParameterlayer_param_;//这个是protobuf文件中存储的layer参数vectorshare_ptrBlobDtypeblobs_;//这个存储的是layer的参数,在程序中用的vectorboolparam_propagate_down_;//这个bool表示是否计算各个blob参数的diff,即传播误差Layer类的构建函数explicitLayer(constLayerParameter¶m):layer_param_(param)会尝试从protobuf文件读取参数。其三个主要接口:virtualvoidSetUp(constvectorBlobDtype*&bottom,vectorBlobDtype**top)inlineDtypeForward(constvectorBlobDtype*&bottom,vectorBlobDtype**top);inlinevoidBackward(constvectorBlobDtype*&top,constvectorbool&propagate_down,constBlobDtype**bottom);SetUp函数需要根据实际的参数设置进行实现,对各种类型的参数初始化;Forward和Backward对应前向计算和反向更新,输入统一都是bottom,输出为top,其中Backward里面有个propagate_down参数,用来表示该Layer是否反向传播参数。在Forward和Backward的具体实现里,会根据Caffe::mode()进行对应的操作,即使用cpu或者gpu进行计算,两个都实现了对应的接口Forward_cpu、Forward_gpu和Backward_cpu、Backward_gpu,这些接口都是virtual,具体还是要根据layer的类型进行对应的计算(注意:有些layer并没有GPU计算的实现,所以封装时加入了CPU的计算作为后备)。另外,还实现了ToProto的接口,将Layer的参数写入到protocolbuffer文件中。data_layers.hppdata_layers.hpp这个头文件包含了这几个头文件:#includeboost/scoped_ptr.hpp#includehdf5.h#includeleveldb/db.h#includelmdb.h#includecaffe/blob.hpp#includecaffe/common.hpp#includecaffe/filler.hpp#includecaffe/internal_thread.hpp#includecaffe/layer.hpp#includecaffe/proto/caffe.pb.h看到hdf5、leveldb、lmdb,确实是与具体数据相关了。data_layer作为原始数据的输入层,处于整个网络的最底层,它可以从数据库leveldb、lmdb中读取数据,也可以直接从内存中读取,还可以从hdf5,甚至是原始的图像读入数据。关于这几个数据库,简介如下:LevelDB是Google公司搞的一个高性能的key/value存储库,调用简单,数据是被Snappy压缩,据说效率很多,可以减少磁盘I/O,具体例子可以看看维基百科。而LMDB(LightningMemory-MappedDatabase),是个和levelDB类似的key/value存储库,但效果似乎更好些,其首页上写道“ultra-fast,ultra-compact”,这个有待进一步学习啊~~HDF(HierarchicalDataFormat)是一种为存储和处理大容量科学数