收藏 分享(赏)

caffe源代码解析.doc

上传人:精品资料 文档编号:10246354 上传时间:2019-10-24 格式:DOC 页数:7 大小:255.71KB
下载 相关 举报
caffe源代码解析.doc_第1页
第1页 / 共7页
caffe源代码解析.doc_第2页
第2页 / 共7页
caffe源代码解析.doc_第3页
第3页 / 共7页
caffe源代码解析.doc_第4页
第4页 / 共7页
caffe源代码解析.doc_第5页
第5页 / 共7页
点击查看更多>>
资源描述

1、caffe 源码简单解析Blob(1)November 26, 2014 caffe, 源码阅读, blob使用 caffe 也有一段时间了,但更多是使用 Python 的接口,使用现有的ImageNet 训练好的模型进行图片分类。为了更好的了解 caffe 这个框架,也为了提高自己的水平,在对卷积神经网络有了一些研究之后,终于开始研读caffe 的源码了,今天看了 Blob 类的一些内容,做个总结。看过 caffe 官方文档的话,应该会知道,它可以分为三层:Blob、Layer、Net。Blob 是一个四维的数组,用于存储数据,包括输入数据、输出数据、权值等等;Layer 层则是神经网络中具

2、体的各层结构,主要是计算的作用,在根据配置文件初始化结构后,前向计算结果,反向更新参数,都是它要做的,而它的输入和输出都是 Blob 数据;Net 的话,就是多个 Layer 组合而成的有向无环图结构,也就是具体的网络了。Layer 和 Net 的代码有待深入,尤其是 Layer 的代码,caffe 实现了差不多 40 种不同的 Layer 层,里面有不同的激活函数,这个要好好研究下。Blob 源码解析#include “caffe/common.hpp“#include “caffe/proto/caffe.pb.h“#include “caffe/syncedmem.hpp“#includ

3、e “caffe/util/math_functions.hpp“从 blob.hpp包含的四个头文件入手,其中 caffe.pb.h是 google protocol buffer 根据 caffe.proto自动生成的,可以到 src/caffe/proto/caffe.proto里看下 caffe 里面用到的各个数据的定义,比如BlobProto,Datum,NetParameter 等。使用这个 protocol buffer 看起来确实方便,一方面可以用文本文件定义结构化的数据类型,另一方面可以生成查询效率更高、占空间更小的二进制文件,具体的教程可以看看这里。在 caffe/comm

4、on.hpp,主要 singleton 化 Caffe 类,并封装了 boost 和 CUDA随机数生成的函数,提供了统一的接口。而在 caffe/syncedmem.hpp中,定义了以下的接口:inline void CaffeMallocHost(void* ptr, size_t size)inline void CaffeFreeHost(void* ptr)主要是分配内存和释放内存的。而 class SyncedMemory定义了内存分配管理和CPU 与 GPU 之间同步的函数,也没啥特别的。比较重要的是 caffe/util/math_functions.hpp,这里面封装了很多

5、cblas 矩阵运算,真是密密麻麻,看的我眼花缭乱、如痴如醉。比如:void caffe_cpu_gemm(const CBLAS_TRANSPOSE TransA, const CBLAS_TRANSPOSE TransB, const int M, const int N, const int K, const float alpha, const float* A, const float* B, const float beta, float* C)封装了 cblas_sgemm(CblasRowMajor, TransA, TransB, M, N, K, alpha, A, lda

6、, B, ldb, beta, C, N),这个计算得到的结果为 C=alphaAB+beta*C,也即是 A 和 B 两个矩阵的乘积。这里有详细的解释。void caffe_cpu_gemv(const CBLAS_TRANSPOSE TransA, const int M, const int N, const float alpha, const float* A, const float* x, const float beta, float* y)是对 cblas_sgemv的封装,实现的矩阵与向量的乘积,结果为y=alphaAx+beta*y。void caffe_axpy(con

7、st int N, const float alpha, const float* 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_()中的数

8、据。对 blob 的理解还要结合 caffe.proto里面 BlobProto的定义:message BlobProto optional int32 num = 1 default = 0;optional int32 channels = 2 default = 0;optional int32 height = 3 default = 0;optional int32 width = 4 default = 0;repeated float data = 5 packed = true;repeated float diff = 6 packed = true;对于 BlobProto

9、,可以看到定义了四个 optional的 int32类型的名字(name)num、channels、height 和 width,optional 意味着 Blob可以有一个或者没有这个参数,每个名字(name)后面都有一个数字,这个数字是其名字的一个标签。这个数字就是用来在生成的二进制文件中搜索查询的标签(怪不得会快呢_)。关于这个数字,1 到 15 会花费 1byte 的编码空间,16 到 2047 花费2byte。所以一般建议把那些频繁使用的名字的标签设为 1 到 15 之间的值而后面的 repeated意味着 float类型的 data和 diff可以重复任意次,而加上packed =

10、 true是为了更高效的编码。到这里基本上 Blob就很清楚了,主要数据有两个 data和 diff,用num、channels、height 和 width这四个维度来确定数据的具体位置,做一些数据查询和 Blobreshape的操作。关于 Blob 就这么多内容,毕竟就是一个统一的数据存取接口,后续会重点读一下 Layer 的源码,毕竟各层的输入输出和计算更新过程都在里面,还需要补充一些相关的知识目前的感受,是学到了一些封装的手法,可以看看封装 cblas 函数的那个文件,以及 CPU 和 GPU 一些接口的封装上;另一方面是对于 Protocol Buffer有了一些了解,目前看起来确实

11、方便,以后如果遇到类似的场景可以试着用一下caffe 源码简单解析Layer 层December 3, 2014 caffe, 源码, layer前言老实说,caffe 中的 layer 层代码比较多,各种抽象看起来比较绕。官方关于Layer 的教程写的很清楚,我根据这个文档,简单画了个图,再理解起来就方便了一些。layer.hpp和 layer 相关的头文件有:common_layers.hppdata_layers.hpplayer.hpploss_layers.hppneuron_layers.hppvision_layers.hpp其中layer.hpp 是抽象出来的基类,其他都是在其

12、基础上的继承,也即剩下的五个头文件和上图中的五个部分。在 layer.hpp头文件里,包含了这几个头文件:#include “caffe/blob.hpp“#include “caffe/common.hpp“#include “caffe/proto/caffe.pb.h“#include “caffe/util/device_alternate.hpp“在 device_alternate.hpp中,通过#ifdef CPU_ONLY定义了一些宏来取消 GPU的调用:#define STUB_GPU(classname)#define STUB_GPU_FORWARD(classname,

13、 funcname)#define STUB_GPU_BACKWARD(classname, funcname)layer 中有这三个主要参数:LayerParameter layer_param_; / 这个是 protobuf 文件中存储的 layer 参数vector blobs_; / 这个存储的是 layer 的参数,在程序中用的vector param_propagate_down_; / 这个 bool 表示是否计算各个 blob 参数的 diff,即传播误差Layer 类的构建函数 explicit Layer(const LayerParameterinline void B

14、ackward(const vector*SetUp函数需要根据实际的参数设置进行实现,对各种类型的参数初始化;Forward和 Backward对应前向计算和反向更新,输入统一都是 bottom,输出为top,其中 Backward里面有个 propagate_down参数,用来表示该 Layer 是否反向传播参数。在 Forward和 Backward的具体实现里,会根据 Caffe:mode()进行对应的操作,即使用 cpu 或者 gpu 进行计算,两个都实现了对应的接口Forward_cpu、Forward_gpu 和 Backward_cpu、Backward_gpu,这些接口都是v

15、irtual,具体还是要根据 layer 的类型进行对应的计算(注意:有些 layer 并没有 GPU 计算的实现,所以封装时加入了 CPU 的计算作为后备)。另外,还实现了 ToProto的接口,将 Layer 的参数写入到 protocol buffer 文件中。data_layers.hppdata_layers.hpp这个头文件包含了这几个头文件:#include “boost/scoped_ptr.hpp“#include “hdf5.h“#include “leveldb/db.h“#include “lmdb.h“#include “caffe/blob.hpp“#include

16、 “caffe/common.hpp“#include “caffe/filler.hpp“#include “caffe/internal_thread.hpp“#include “caffe/layer.hpp“#include “caffe/proto/caffe.pb.h“看到 hdf5、leveldb、lmdb,确实是与具体数据相关了。data_layer 作为原始数据的输入层,处于整个网络的最底层,它可以从数据库 leveldb、lmdb 中读取数据,也可以直接从内存中读取,还可以从 hdf5,甚至是原始的图像读入数据。关于这几个数据库,简介如下:LevelDB 是 Google

17、公司搞的一个高性能的 key/value 存储库,调用简单,数据是被 Snappy 压缩,据说效率很多,可以减少磁盘 I/O,具体例子可以看看维基百科。而 LMDB(Lightning Memory-Mapped Database),是个和 levelDB 类似的key/value 存储库,但效果似乎更好些,其首页上写道“ultra-fast,ultra-compact”,这个有待进一步学习啊HDF(Hierarchical Data Format)是一种为存储和处理大容量科学数据而设计的文件格式及相应的库文件,当前最流行的版本是 HDF5,其文件包含两种基本数据对象: 群组(group):类

18、似文件夹,可以包含多个数据集或下级群组; 数据集(dataset):数据内容,可以是多维数组,也可以是更复杂的数据类型。以上内容来自维基百科,关于使用可以参考HDF5 小试 高大上的多对象文件格式(HDF5 小试高大上的多对象文件格式),后续会再详细的研究下怎么用。caffe/filler.hpp的作用是在网络初始化时,根据 layer 的定义进行初始参数的填充,下面的代码很直观,根据 FillerParameter指定的类型进行对应的参数填充。/ A function to get a specific filler from the specification given in/ Fill

19、erParameter. Ideally this would be replaced by a factory pattern,/ but we will leave it this way for now.template Filler* GetFiller(const FillerParameterif (type = “constant“) return new ConstantFiller(param); else if (type = “gaussian“) return new GaussianFiller(param); else if (type = “positive_un

20、itball“) return new PositiveUnitballFiller(param); else if (type = “uniform“) return new UniformFiller(param); else if (type = “xavier“) return new XavierFiller(param); else CHECK(false) *)(NULL);internal_thread.hpp里面封装了 pthread函数,继承的子类可以得到一个单独的线程,主要作用是在计算当前的一批数据时,在后台获取新一批的数据。关于 data_layer,基本要注意的我都在

21、图片上标注了。neuron_layers.hpp输入了 data 后,就要计算了,比如常见的 sigmoid、tanh 等等,这些都计算操作被抽象成了 neuron_layers.hpp里面的类 NeuronLayer,这个层只负责具体的计算,因此明确定义了输入 ExactNumBottomBlobs()和 ExactNumTopBlobs()都是常量 1,即输入一个 blob,输出一个 blob。common_layers.hppNeruonLayer仅仅负责简单的一对一计算,而剩下的那些复杂的计算则通通放在了 common_layers.hpp中。像ArgMaxLayer、ConcatLa

22、yer、FlattenLayer、SoftmaxLayer、SplitLayer 和SliceLayer等各种对 blob 增减修改的操作。loss_layers.hpp前面的 data layer和 common layer都是中间计算层,虽然会涉及到反向传播,但传播的源头来自于 loss_layer,即网络的最终端。这一层因为要计算误差,所以输入都是 2 个 blob,输出 1 个 blob。vision_layers.hppvision_layer主要是图像卷积的操作,像 convolusion、pooling、LRN 都在里面,按官方文档的说法,是可以输出图像的,这个要看具体实现代码了

23、。里面有个 im2col的实现,看 caffe 作者的解释,主要是为了加速卷积的,这个具体是怎么实现的要好好研究下后语结合官方文档,再加画图和看代码,终于对整个 layer层有了个基本认识:data负责输入,vision 负责卷积相关的计算, neuron和 common负责中间部分的数据计算,而 loss是最后一部分,负责计算反向传播的误差。具体的实现都在 src/caffe/layers里面,慢慢再研究研究。在这些抽象的基类头文件里,看起来挺累,好在各种搜索,也能学到一些技巧,如, 巧用宏定义来简写 C,C+代码,使用模板方法,将有大量重复接口和参数的类抽象为一个宏定义,达到简化代码的目的。

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 企业管理 > 管理学资料

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报