{承接CNN学习入门,笔者在这里对Caffe官方网站上的相关介绍进行了翻译总结,欢迎大家交流指正}
本文基于此刻最新的release,Caffe-rc3:
Introduction:
Deep Networks是一种组合模型,自然而然的由处理成块数据的交织层组合而成。
Caffe依照自身的模型架构(.prototxt),逐层的定义网络,自下而上(bottom to top),自数据输入层(input data)到损失函数层(loss)。
数据(data)以及偏导数(derivatives)以forward pass和backward pass的形式在网络中传播。
而Caffe以blob类的形式存储,处理和通信这些数据。对于框架而言,blob是一种标准化的序列存储形式,也提供了一种统一的内存操作接口。
Layer是模型及计算的基础,而Net又是Layer的堆叠与连接后的集合体。
blob类的实现细节交代了data以及derivatives是如何在Layer中存储又是如何在Net中传播的。
Blob storage and communication:
Blob是在Caffe中被处理和传递的真实数据的一种封装,同时在后台实现数据在CPU及GPU间的同步。
从数学角度来说,blob是以C风格连续存储的N维数组。
Blob提供了可以hold住,比如batches of images,模型参数(cpu_data/gpu_data),优化目标的偏导(cpu_diff_data/gpu_diff_data)等数据的统一的内存接口。并且对用户屏蔽了混合了CPU与GPU的操作,为了实现同步,需要将数据由CPU host迁移到GPU Device上。
内存空间是按需分配的(lazily),保证高效的内存使用。
传统意义上,对应于一个batch的图像数据的Blob的维度应该是number N x channel K x height H x width W,Blob在内存中以行优先形式存储,因此,最右侧下标应变化最快。举例而言,对于4维的Blob,位于坐标(n, k, h, w)处的值,物理上存储在下标 ((n * K + k) * H + h) * W + w处。
E.g.
Number or N 是一组数据的 batch-size,按照batch处理数据,在通信代价以及设备利用率上都将达到较好的效果。
对于ImageNet数据集而言,在training过程设定batch-size=256,那么N就是256。
channel or K 是特征的维度,对于RGB图像而言,其K=3。
需要注意,尽管在Caffe的示例中,很多Blob都是4维的图像相关应用,但是使用blob处理非图像应用也是完全可行的。
举个例子,如果你只需要像传统的多层感知机那种全连接网络,使用2维的blob,并调用Inner Product Layer就可以胜任。
Parameter Blob的维度根据Layer的类型及配置文件确定,对于一个拥有96个三通道且分辨率为11x11卷积核(Kernel == filter)的卷积层而言,其维度为96x3x11x11。对于一个inner product 或者称为 fully-connected 的层来说,其拥有1K个输出通道与1024个输入通道,那么其维度为1000x1024。
对于自定义的输入数据,需要先完成数据预处理或者定义data layer。一旦数据准备就绪,模块化的网络将会完成剩余的工作。
Implementation Details:
由于我们通常关心Blob中的权值与梯度数据,Blob存储了两块内存数据,data与diff。
前者用于pass过程,后者为网络反传时的梯度数据。
除此之外,由于真实数据被存储在CPU Host或GPU Device上,有两种方式去访问他们,const与mutable,顾名思义,只读与可变方式:
const Dtype* cpu_data() const;
Dtype* mutable_cpu_data();
GPU与diff也是类似。
这种设计的原因在于,Blob使用SyncedMem类在CPU Host与GPU Device之间同步数据以最小化数据传输代价。
根据经验,如果你不需要改变数值,请使用const指针访问数据,也请不要存储指针。
任何时候,请通过调用函数来获得指针,SyncedMem类需要依此判断是否需要进行数据拷贝。
在实践中,使用GPU时,CPU部分代码(.cpp)将会将数据由存储调入blob中,继而调用Device Kernel执行GPU运算。
然后将Blob中的数据逐层前向或后向传递。屏蔽了底层细节又维持了较高性能。只要是所有Layer都包含GPU实现(.cu),
那么所有的中间权值与梯度都将被保存在GPU上。
如果你想搞清楚Blob究竟什么时候会拷贝数据,下面就是说明性的例子:
// Assuming that data are on the CPU initially, and we have a blob.
const Dtype* foo;
Dtype* bar;
foo = blob.gpu_data(); // data copied cpu->gpu.
foo = blob.cpu_data(); // no data copied since both have up-to-date contents.
bar = blob.mutable_gpu_data(); // no data copied.
// ... some operations ...
bar = blob.mutable_gpu_data(); // no data copied when we are still on GPU.
foo = blob.cpu_data(); // data copied gpu->cpu, since the gpu side has modified the data
foo = blob.gpu_data(); // no data copied since both have up-to-date contents
bar = blob.mutable_cpu_data(); // still no data copied.
bar = blob.mutable_gpu_data(); // data copied cpu->gpu.
bar = blob.mutable_cpu_data(); // data copied gpu->cpu.
{译者注:
调用mutable函数意味着潜在的memory修改,因此需要考虑是否进行copy}
Layer computation and connections:
Layer是model与计算的核心,包括Convolutional Layer, inner product Layer, Pooling Layer, BN, LRN, Softmax等等用于最新的Deep learning Task的层类型都可以在这里找到。
Input -> bottom connections ->[neurons] ->top connections -> Output
每层都定义了三种重要的计算:setup forward backward
setup : 在model初始化时初始化层参数及连接
Forward : 依据给出的源自bottom的input计算output并传递给top
Backward: 依据给出的源自top output的梯度计算对应于input的梯度,并将其传递给bottom。含权值参数的层将会计算关于其权值参数的梯度,并将其存储在blob中。
具体来说,有两种Forward以及Backward函数,一种应用与CPU,一种应用于GPU,如果GPU版本未定义,那么CPU版本的函数将会作为备份选择。容易验证,但是这将带来额外的数据传输的cost ( input将会由GPU Device拷贝到CPU Host上而output将会由CPU Host再拷贝到GPU Host上 ) 。
开发自定义的Layer,由于网络与代码的模块化将很容易,只需要定义setup forward backward操作,该层将会融入网络中。
Net definition and operation:
网络每层的输出模块计算给定task的目标函数值,反传模块计算关于loss的梯度,Caffe模型是一种端到端的机器学习引擎。
网络是由computation graph(有向无环图DAG)形式连接的Layer的组合。Caffe记录下DAG中的所有层,确保forward与backward的正确性。典型的网络以data layer起始,自存储中装载数据,以loss layer结束,计算比如分类等任务的目标函数。
网络以Layer集合的形式定义,其连接关系以浅显的模型语言说明(.prototxt):
举例来说,简单的logistic regression 分类器:
name: "LogReg"
layer {
name: "mnist"
type: "Data"
top: "data"
top: "label"
data_param {
source: "input_leveldb"
batch_size: 64
}
}
layer {
name: "ip"
type: "InnerProduct"
bottom: "data"
top: "ip"
inner_product_param {
num_output: 2
}
}
layer {
name: "loss"
type: "SoftmaxWithLoss"
bottom: "ip"
bottom: "label"
top: "loss"
}
模型由:
Net::Init()
初始化,主要包含两步:
像搭脚手架一样,根据DAG逐层创建blobs以及layers(在net的整个生存周期中,blob及layer都将被保持),
然后调用layer的Setup()函数。也会打印出一些冗长的信息,比如确定网络的结构正确性。
在初始化过程中,Net将会说明其初始化的进程,利用LOG(INFO)之类的函数:
I0902 22:52:17.931977 2079114000 net.cpp:39] Initializing net from parameters:
name: "LogReg"
[...model prototxt printout...]
# construct the network layer-by-layer
I0902 22:52:17.932152 2079114000 net.cpp:67] Creating Layer mnist
I0902 22:52:17.932165 2079114000 net.cpp:356] mnist -> data
I0902 22:52:17.932188 2079114000 net.cpp:356] mnist -> label
I0902 22:52:17.932200 2079114000 net.cpp:96] Setting up mnist
I0902 22:52:17.935807 2079114000 data_layer.cpp:135] Opening leveldb input_leveldb
I0902 22:52:17.937155 2079114000 data_layer.cpp:195] output data size: 64,1,28,28
I0902 22:52:17.938570 2079114000 net.cpp:103] Top shape: 64 1 28 28 (50176)
I0902 22:52:17.938593 2079114000 net.cpp:103] Top shape: 64 (64)
I0902 22:52:17.938611 2079114000 net.cpp:67] Creating Layer ip
I0902 22:52:17.938617 2079114000 net.cpp:394] ip <- data
I0902 22:52:17.939177 2079114000 net.cpp:356] ip -> ip
I0902 22:52:17.939196 2079114000 net.cpp:96] Setting up ip
I0902 22:52:17.940289 2079114000 net.cpp:103] Top shape: 64 2 (128)
I0902 22:52:17.941270 2079114000 net.cpp:67] Creating Layer loss
I0902 22:52:17.941305 2079114000 net.cpp:394] loss <- ip
I0902 22:52:17.941314 2079114000 net.cpp:394] loss <- label
I0902 22:52:17.941323 2079114000 net.cpp:356] loss -> loss
# set up the loss and configure the backward pass
I0902 22:52:17.941328 2079114000 net.cpp:96] Setting up loss
I0902 22:52:17.941328 2079114000 net.cpp:103] Top shape: (1)
I0902 22:52:17.941329 2079114000 net.cpp:109] with loss weight 1
I0902 22:52:17.941779 2079114000 net.cpp:170] loss needs backward computation.
I0902 22:52:17.941787 2079114000 net.cpp:170] ip needs backward computation.
I0902 22:52:17.941794 2079114000 net.cpp:172] mnist does not need backward computation.
# determine outputs
I0902 22:52:17.941800 2079114000 net.cpp:208] This network produces output loss
# finish initialization and report memory usage
I0902 22:52:17.941810 2079114000 net.cpp:467] Collecting Learning Rate and Weight Decay.
I0902 22:52:17.941818 2079114000 net.cpp:219] Network initialization done.
I0902 22:52:17.941824 2079114000 net.cpp:220] Memory required for data: 201476
在网络建立之后,是在CPU上运行还是在GPU上运行只需要简单设定一个switch,其定义在Caffe::mode()中,通过Caffe::set_mode()来设定(译者注:在
solver.prototxt
文件中可以设定mode)。CPU与GPU运行结果应当一致,排除数值精度问题。
Model format:
网络结构模型定义在.prototxt文件中,而网络参数模型则以二进制序列化(binaryproto)的.caffemodel文件形式保存。
caffe.proto文件(/src/caffe/proto目录下)定义了对于网络结构模型的解释方法。
{译者注:对于proto及其序列化二进制存储,可以简单理解为一种依照.prototxt文件对.caffemodel文件进行数据压缩与还原的过程}
了解更多关于Google Proto。