caffe源码简单解析——Blob(1)

使用caffe也有一段时间了,但更多是使用Python的接口,使用现有的ImageNet训练好的模型进行图片分类。为了更好的了解caffe这个框架,也为了提高自己的水平,在对卷积神经网络有了一些研究之后,终于开始研读caffe的源码了,今天看了Blob类的一些内容,做个总结。

看过caffe官方文档的话,应该会知道,它可以分为三层:Blob、Layer、Net。Blob是一个四维的数组,用于存储数据,包括输入数据、输出数据、权值等等;Layer层则是神经网络中具体的各层结构,主要是计算的作用,在根据配置文件初始化结构后,前向计算结果,反向更新参数,都是它要做的,而它的输入和输出都是Blob数据;Net的话,就是多个Layer组合而成的有向无环图结构,也就是具体的网络了。Layer和Net的代码有待深入,尤其是Layer的代码,caffe实现了差不多40种不同的Layer层,里面有不同的激活函数,这个要好好研究下。

Blob源码解析

#include "caffe/common.hpp"
#include "caffe/proto/caffe.pb.h"
#include "caffe/syncedmem.hpp"
#include "caffe/util/math_functions.hpp"

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

caffe/common.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,这里面封装了很多cblas矩阵运算,真是密密麻麻,看的我眼花缭乱、如痴如醉。比如:

void caffe_cpu_gemm<float>(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, B, ldb, beta, C, N),这个计算得到的结果为C=alphaAB+beta*C,也即是A和B两个矩阵的乘积。这里有详细的解释。

void caffe_cpu_gemv<float>(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<float>(const int N, const float alpha, const float* X, float* Y)

封装了cblas_saxpy,实现的是Y=alpha*X+Y

里面都是诸如此类的函数,基本是些矩阵和向量的一些处理函数。

回到blob类,里面定义了data_(),diff_()指针,用于存放数据,而num_, channel_, height_, width_则主要用来做定位offsetreshape处理。对于输入(n, c, h, w)位置的数据位置为((n*channels_+c)*height_+h)*width_+w,可以依据位置取data_()diff_()中的数据。

对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,可以看到定义了四个optionalint32类型的名字(name)numchannelsheightwidthoptional意味着Blob可以有一个或者没有这个参数,每个名字(name)后面都有一个数字,这个数字是其名字的一个标签。这个数字就是用来在生成的二进制文件中搜索查询的标签(怪不得会快呢^_^)。关于这个数字,1到15会花费1byte的编码空间,16到2047花费2byte。所以一般建议把那些频繁使用的名字的标签设为1到15之间的值~而后面的repeated意味着float类型的datadiff可以重复任意次,而加上[packed = true]是为了更高效的编码。

到这里基本上Blob就很清楚了,主要数据有两个datadiff,用numchannelsheightwidth这四个维度来确定数据的具体位置,做一些数据查询和Blobreshape的操作。

关于Blob就这么多内容,毕竟就是一个统一的数据存取接口,后续会重点读一下Layer的源码,毕竟各层的输入输出和计算更新过程都在里面,还需要补充一些相关的知识~~

目前的感受,是学到了一些封装的手法,可以看看封装cblas函数的那个文件,以及CPU和GPU一些接口的封装上;另一方面是对于Protocol Buffer有了一些了解,目前看起来确实方便,以后如果遇到类似的场景可以试着用一下~~