Caffe从输入数据到loss定义了DL网络的层,并能进行前向和后项传播算法。
记录操作信息的矩阵叫做blobs。blob是标准的矩阵,并且是这个框架下统一存储的接口,像C里面的结构体一样理解吧。
Layer就是网络里面的层,是模型和计算的基础。
Net就是层与层间的连接。
blobs描述了信息是如何在layers和nets中存储和交流的。
solving是配置来做optimization的。
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会完成余下的模式化的工作。
一个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是网络计算的基本单元。包括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两种类型。
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()来改变。其转换是无缝的,与模型无关。
模型在明文协议缓冲模式(prototxt)中定义,而训练后的模型被序列化为二进制协议缓冲(binaryproto).caffemodel文件。
这里用了一个简单的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}()两种类型。在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,只要加上一个定义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)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两种模式。
Solver解决的问题是一个优化问题:loss最小化。 对于数据集 D ,loss为: 其中fw(X(i))是样例 X(i) 的loss, r(w) 是正规化项,其参数为 λ 。 实际中, |D| 可能很大,因此在每次迭代中,常用一个随机的mini-batch来代替上式,其中 N<<|D| : 模型在forward中计算 fw ,backward中计算 ∇fw 。
Stochastic gradient descent (type: “SGD”) 方法用 ∇L(W) 来更新 Vt ,再用 Vt 来更新weights。Learning rate为 α ,momentum为 μ 。 具体来说,其更新法则为:
AdaDelta (type: “AdaDelta”) ,更新法则如下: 更新weights:
adaptive gradient (type: “AdaGrad”) ,其思想是记录之前所有迭代中的 (∇L(W))t′) ,其中 t′∈{1,2,...,t} 来更新。更新法则为:
Adam (type: “Adam”)。
Nesterov’s accelerated gradient (type: “Nesterov”) 。
RMSprop (type: “RMSProp”)。
Solver在Solver::Presolve()中,准备optimization的方法和初始化模型。
参数的计算由 Solver::ComputeUpdateValue() 来进行,然后由 Blob::Update 更新。
Solver::Snapshot()和Solver::SnapshotSolverState()保存指定迭代次数中的参数并停止,相当于断点,由Solver::Restore()和Solver::RestoreSolverState()继续运行。
这部分介绍了常用的Caffe的层类型。相当于工具手册用吧。(http://caffe.berkeleyvision.org/tutorial/layers.html)
这部分介绍了Caffe对于command line,Python,Matlab的接口。 (http://caffe.berkeleyvision.org/tutorial/interfaces.html)
Nouveau是CPU驱动程序,这里把它删除了,然后安装了nvidia-340的驱动。
CUDA安装程序在 https://developer.nvidia.com/cuda-downloads 下载。
……