Blobs, Layers, and Nets: anatomy of a Caffe model
Caffe从输入数据到loss定义了DL网络的层,并能进行前向和后项传播算法。
记录操作信息的矩阵叫做blobs。blob是标准的矩阵,并且是这个框架下统一存储的接口,像C里面的结构体一样理解吧。
Layer就是网络里面的层,是模型和计算的基础。
Net就是层与层间的连接。
blobs描述了信息是如何在layers和nets中存储和交流的。
solving是配置来做optimization的。
Blob storage and communication
blob是Caffe处理的数据的封装,并且在CPU和GPU之间提供了同步。说白了,blob是一个连续存储的N维矩阵。Caffe用blob来存储和交换信息,blob提供了统一的存储接口来存储数据,比如图片(输入),模型参数,偏差。它可以根据需要来分配CPU和GPU,并且内存使用十分高效。
通常,对于图像数据,blob的存储的是number N x channel K x height H x width W。其内存是连续存储的,比如一个4D的blob,其index为
(n,k,h,w)
,则其物理地址为
((n∗K+k)∗H+h)∗W+w
。
其中Number(N)是数据的批量,批量处理使得设备更好的处理数据;Channel(K)是feature的维度,比如RGB图像k=3。
虽然Caffe示例中许多存储图像的blob都是4D的,但对非图像的其他维度的存储也是有效的。比如2D的blob,调用InnerProducLayer。
存储参数的blob大小由其类型决定,比如对于一个有11*11维,3个输入,96个滤波的卷积层,其blob大小为 96∗3∗11∗11 ;再比如一个有1000个输出和1024个输入的全连接层,其blob大小为 1000∗1024 。
数据存储完成后,layers会完成余下的模式化的工作。
Implementation Details
一个blob存储着两种数据,data和diff,正常的数据和gradient。
由于数据存储在CPU和GPU里面,因此有两种访问方法:不改变数据值的const way和改变数据值的mutable way。
const Dtype* cpu_data() const;
Dtype* mutable_cpu_data();
gpu的数据和diff的数据访问也是一样的形式。
这样设计的原因是blob使用一个SyncedMem类来同步CPU和GPU之间的数据,以便能够隐藏同步的细节和减小数据转化的开销。一个小技巧是,如果不想改变数据值,始终使用const访问的方法,并且只调用函数来获得指针,因为SyncedMem需要这个来指出什么时候复制数据。
在实际中,当有GPUs时,由CPU代码从disk载入数据到blob中,再调用GPU来计算。隐藏底层细节,只要所有层都调用GPU计算,所有中间数据(data and gradients)都会留在GPU中。
下面例子展示了在什么时候blob会copy data:
// 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.
Layer computation and connections
layer是网络计算的基本单元。包括filters,pool,take inner products,apply nonlinearities like rectified-linear and sigmoid and other elementwise transformations,normalize,load data,and compute losses like softmax and hinge等等操作。
下图是一个简单的layer结构:
一个layer从bottom blob输入,经过操作之后,由top blob输出。
每个layer有三个操作:
- Setup:在模型初始化的时候初始化layer数据和连接。
- Forward:从bottom接收输入,计算,并从top输出。
- Backward:从top计算gradient,送回bottom。
这三个操作有CPU和GPU两种类型。
Net definition and operation
Caffe是一个端到端(end-to-end)的ML引擎。之前不明白这句话,现在总算明白了。因为net定义了层与层间的连接和操作,中间细节忽略之后,就直接是从输入端到输出端了。
Net是一系列layer连接在一个DAG(有向无环图)上。
如下一个简单logistic regression classifier的例子:
可定义为:
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;其次是调用layers的SetUp()函数。其他还会检查网络结构的合法性。
由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上运行,由Caffe::mode()和Caffe::set_mode()来改变。其转换是无缝的,与模型无关。
Model format
模型在明文协议缓冲模式(prototxt)中定义,而训练后的模型被序列化为二进制协议缓冲(binaryproto).caffemodel文件。
Forward and Backward
这里用了一个简单的logistic regression classifier来说明这两个操作。
Forward pass:从buttom到top的函数计算。
Backward pass:从top到buttom,计算出每个weights和bias的gradient,便于训练。
调用方法:
- Net::Forward(),Net::Backward()是整个网络的计算;Layer::Forward(),Layer::Backward()是层的计算。
- 每个操作都有forward_{cpu,gpu}(),backward_{cpu,gpu}()两种类型。
Loss
在Caffe中,训练由loss function(也叫做error, cost, objective function)驱动。训练的目标是minimizes loss function。
在Caffe中,loss由Forward pass计算。例如,在one-versus-all分类问题中,常用的loss function是SoftmaxWithLoss function,可以如下定义:
layer {
name: "loss"
type: "SoftmaxWithLoss"
bottom: "pred"
bottom: "label"
top: "loss"
}
SoftmaxWithLoss函数中,top blob是一个标量,是所有loss的均值。
Loss weights
任何层都可以使用loss,只要加上一个定义loss_weight:到top blob中。在带有后缀loss的层中,隐含定义了loss_weight:1。其它层则隐含定义了loss_weight:0。
因此上面那个SoftmaxWithLoss层可以等价定义为:
layer {
name: "loss"
type: "SoftmaxWithLoss"
bottom: "pred"
bottom: "label"
top: "loss"
loss_weight: 1
}
Caffe的final loss是所有weight loss的和:
loss := 0
for layer in layers:
for top, loss_weight in layer.tops, layer.loss_weights:
loss += loss_weight * sum(top)
Solver
Caffe包含了以下solver:
- Stochastic Gradient Descent (type: “SGD”)
- AdaDelta (type: “AdaDelta”)
- Adaptive Gradient (type: “AdaGrad”)
- Adam (type: “Adam”)
- Nesterov’s Accelerated Gradient (type: “Nesterov”)
- RMSprop (type: “RMSProp”)
Solver完成以下工作:
- optimization, creates the training network for learning, test network for evaluation.
- iteratively optimizes by calling forward/backward and updating parameters.
- periodically evaluates the test networks.
- snapshots the model and solver state throughout the optimization.
在每次迭代,有如下操作:
- calls network forward to compute the output and loss.
- calls network backward to compute the gradients.
- incorporates the gradients into parameter updates according to the solver method.
- updates the solver state according to learning rate, history, and method.
slvers也有CPU/GPU两种模式。
Methods
Solver解决的问题是一个优化问题:loss最小化。
对于数据集
D
,loss为:
其中
实际中,
|D|
可能很大,因此在每次迭代中,常用一个随机的mini-batch来代替上式,其中
N<<|D|
:
模型在forward中计算
fw
,backward中计算
∇fw
。
SGD
Stochastic gradient descent (type: “SGD”) 方法用
∇L(W)
来更新
Vt
,再用
Vt
来更新weights。Learning rate为
α
,momentum为
μ
。
具体来说,其更新法则为:
AdaDelta
AdaDelta (type: “AdaDelta”) ,更新法则如下:
更新weights:
AdaGrad
adaptive gradient (type: “AdaGrad”) ,其思想是记录之前所有迭代中的
(∇L(W))t′)
,其中
t′∈{1,2,...,t}
来更新。更新法则为:
Adam
Adam (type: “Adam”)。
NAG
Nesterov’s accelerated gradient (type: “Nesterov”) 。
RMSprop
RMSprop (type: “RMSProp”)。
Scaffolding
Solver在Solver::Presolve()中,准备optimization的方法和初始化模型。
Updating Parameters
参数的计算由 Solver::ComputeUpdateValue() 来进行,然后由 Blob::Update 更新。
Snapshotting and Resuming
Solver::Snapshot()和Solver::SnapshotSolverState()保存指定迭代次数中的参数并停止,相当于断点,由Solver::Restore()和Solver::RestoreSolverState()继续运行。
Layers
这部分介绍了常用的Caffe的层类型。相当于工具手册用吧。(http://caffe.berkeleyvision.org/tutorial/layers.html)
Interfaces
这部分介绍了Caffe对于command line,Python,Matlab的接口。
(http://caffe.berkeleyvision.org/tutorial/interfaces.html)
Install
CUDA7.5
Nouveau是CPU驱动程序,这里把它删除了,然后安装了nvidia-340的驱动。
CUDA安装程序在 https://developer.nvidia.com/cuda-downloads 下载。
……