浅度理解caffe
一. caffe 简介
caffe是一个开源的深度学习框架,可读性高,它默认实现了一些现有的通用的网络模型和优化方法,同时允许自由选择CPU或者GPU训练网络,不需要自己编写程序,只需要通过protobuf文件来配置网络以及其参数。相对于TenserFlow来说,caffe在基本功能具备的情况下,速度快,代码精简,层次分明,对于源码的学习和深度学习工作原理比较有利。
二. caffe依赖以及安装
依赖
caffe
|-基础库:Boost+Protobuf+hdf5+glog+gflags
|-BLAS计算库: ATAS/MKL/OpenBLAS
|-(IO库):lmdb leveldb(-snappy)
|-(图片预处理库):Opencv
|-(GPU支持):CUDA+Cudnn
|-(GPU多卡):nccl
|-->(python支持)、(matlab支持)
依赖安装
在有必要的情况,需要将依赖库一个一个编译安装,比较繁琐,但是一般情况下都可以直接通过系统的包管理器直接安装(如debian系的apt-get,等等)参考caffe官网安装指导
编译与安装
caffe可以直接将其当做一个工程,并在其上进行修改或者加入自己的程序代码,通过 make 编译整个工程来进行使用,同时也可以用 make install 将其编译出的库与头文件导出当做程序的依赖库进行使用。编译caffe可以修改 Makefile.config 配置项,直接 make 编译,也可以用cmake工具来进行配置构建工程。 相关操作可以参考caffe官网安装指导
三. caffe源码浅析
caffe目录结构
caffe
|-cmake cmake工程构建文件
|-data 存放预置的样例训练数据
|-docs 帮助文档
|-example 一些样例程序
|-matlab matlab接口文件
|-python python接口文件
|-model 一些配置好的网络模型参数
|-scripts 一些工具脚本
|-
|-tools 可执行工具程序的源码
|-include caffe的头文件
|-src caffe的实现源码
caffe源码结构
src
|-gtest google test的测试库
|-caffe
|-test 使用gtest的测试代码
|-util 调用第三方依赖库的一些工具代码
|-proto Protubuf的一些定义
|-layers 各种layer层实现文件,全部继承Layer类,也可以加入自定义层实现
|-*.cpp caffe的核心代码文件
|blob 的基本数据结构Blob类
|common Caffe类
|net 网络结构类Net
|solver 优化方法类Solver
|layer_factory 层类Layer的工厂类
|data_transformer 输入数据的基本操作类DataTransformer
|internal_thread 多线程使用boost::thread库
|syncedmem 内存管理类CaffeMallocHost,用于同步cpu,gpu数据
caffe基本类描述
Blob: 作为数据传输的媒介,无论是网络权重参数,还是输入数据,都是转化为Blob数据结构来存储。
- Blob的类型描述
Caffe内部采用的数据类型主要是对protocol buffer所定义的数据结构的继承,因此可以在尽可能小的内存占用下获得很高的效率,虽然追求性能的同时Caffe也会牺牲了一些代码可读性。直观来说,可以把Blob看成一个有4维的结构体(包含数据和梯度),而实际上,它们只是一维的指针而已,其4维结构通过shape属性得以计算出来。
-
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) //变形 inline int count(int start_axis, int end_axis) const //Blob大小
Layer: 作为网络的基础单元,神经网络中层与层间的数据节点、前后传递都在该数据结构中被实现,层类种类丰富,比如常用的卷积层、全连接层、pooling层等等,大大地增加了网络的多样性。
- Layer的类型描述
Layer是网络模型和计算的核心,在数据存储上,主要分成bottom_vecs、top_vecs、weights&bias三个部分;在数据传递上,也主要分为LayerSetUp、Reshape、Forward、Backward四个过程,符合直观上对层与层之间连接的理解。
-
Layer的重要成员函数和变量
vector<Dtype> loss_ //每一层都会有一个loss值,但只有LossLayer才会产生非0的loss vector<shared_ptr<Blob<Dtype>>> blobs_ //Layer所学习的参数,包括权值和偏差 virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) //通过bottom Blob对象的形状以及LayerParameter(从prototxt读入)来确定Layer的学习参数(以Blob类型存储)的形状。 virtual void Reshape(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) //通过bottom Blob对象的形状以及Layer的学习参数的形状来确定top Blob对象的形状。 virtual void Forward(const vector<Blob<Dtype>*> &bottom, vector<Blob<Dtype>*> *top) = 0 //Layer内部数据正向传播,从bottom到top方向, 重载实现。 virtual void Backward(const vector<Blob<Dtype>*> &top, const vector<bool> &propagate_down, vector<Blob<Dtype>*> *bottom) = 0 //Layer内部梯度反向传播,从top到bottom方向, 重载实现。
Net: 作为网络的整体骨架,决定了网络中的层次数目以及各个层的类别等信息。
- Net的类型描述
Net用容器的形式将多个Layer有序地放在一起,其自身实现的功能主要是对逐层Layer进行初始化,以及提供Update( )的接口(更新网络参数),本身不能对参数进行有效地学习过程。
-
Net的重要成员函数和变量
vector<shared_ptr<Layer<Dtype>>> layers_ //构成该net的layers vector<vector<Blob<Dtype>*>> bottom_vecs_ //每一层layer中的bottom Blobs vector<vector<Blob<Dtype>*>> top_vecs_ //每一层layer中的top Blobs vector<shared_ptr<Blob<Dtype>>> params_ //整个net中的learnable parameter void Init(const NetParameter& param) //net初始化,简单的来说就是先把网络中所有层的bottom Blobs&top Blobs(无重复)实例化,并从输入层开始,逐层地进行Setup的工作,从而完成了整个网络的搭建,为后面的数据前后传输打下基础。 vector<Blob<Dtype>*>& Forward(const vector<Blob<Dtype>* > & bottom, Dtype* loss = NULL) //前向传播 void Net<Dtype>::Backward() //反向传播,计算loss
Solver: 作为网络的求解策略,涉及到求解优化问题的策略选择以及参数确定方面,修改这个模块的话一般都会是研究DL的优化求解的方向。
- Solver的类型描述
Solver类中包含一个Net的指针,主要是实现了训练模型参数所采用的优化算法,根据优化算法的不同会派生不同的类,而基于这些子类就可以对网络进行正常的训练过程。
-
Solver的重要成员函数和变量
shared_ptr<Net<Dtype> > net_ //net对象 void Step(int iters) //对已初始化后的网络进行固定次数的训练迭代过程。 ComputeUpdateValue(); net_->Update(); //不同的模型训练方法通过重载函数 ComputeUpdateValue() 实现 update 参数的核心功能。
caffe程序基本调用流程
caffe中caffe.cpp提供了一个用来训练和测试的现有模型的工具,编译之后既/build/tools/caffe, 通过命令和参数进行相关操作(命令:train、test、device_query、time)。下面我们将从该入口描述caffe的整个调用流程:
./build/tools/caffe train --solver lenet_solver.prototxt
- 进入程序入口 caffe.cpp main()
- caffe.cpp main() 根据参数调用 caffe.cpp train()
- caffe.cpp train() 读取xxx.prototxt参数,调用 solver.cpp Solver() 的构造函数创建 Solver 对象
- solver.cpp Solver() 调用 solver.cpp Init() 函数来初始化模型的网络
- solver.cpp Init() 函数调用 solver.cpp InitTrainNet() 和InitTestNets()函数来分别初始化训练和测试网络
- solver.cpp InitTrainNet() 和 InitTestNets() 通过 xxx.prototxt 指定的xxxnet.prototxt 读取net的参数,调用 net.cpp Net() 的构造函数,分别创建训练网络和测试网络
- net.cpp Net() 调用 net.cpp Init() 函数,循环->
- 创建网络中每一个 Layer 对象
- 调用 layer.cpp Setup()
- solver.cpp 创建测试网络,与 InitTrainNet()类似
- 运行返回到 caffe.cpp train() 中,利用创建好的 solver 对象调用 solver.cpp Solve() 函数
- solver.cpp Solve() 调用 solver.cpp Step() 函数,迭代->
- 调用 net.cpp ForwardBackward() 来前向以及后向传播
- 调用 solve.cpp ApplyUpdate() 更新参数
- 每一定轮次运行 solver.cpp TestAll()
- 运行结束
调用图如下:
最后
其实caffe是个很老的深度学习框架了,现阶段对于比较新的网络支持也不是很完善,但是就深度学习入门来说caffe还是很不错的,毕竟网上的相关资料多如牛毛,源码也相对清晰易读,这篇文章其实也是参考了很多别人的资料,因为最近接触caffe,所以记录下来。