Caffe 是一个清晰而高效的深度学习框架,其作者是博士毕业于 UC Berkeley 的贾扬清,目前在 Facebook 工作。Caffe 是纯粹的 C++/CUDA 架构,支持命令行、Python 和 MATLAB 接口,可以在 CPU 和 GPU 上直接无缝切换。
易上手
模型与相应优化都是以文本形式而非代码形式给出。Caffe 给出了模型的定义、最优化设置以及预训练的权重,方便立即上手。
速度快
能够运行最棒的模型与海量的数据。 Caffe 与 cuDNN 结合使用,测试 AlexNet 模型,在 K40 上处理每张图片只需要1.17ms。
模块化
方便扩展到新的任务和设置上。可以使用 Caffe 提供的各层类型来定义自己的模型。
开放性
公开的代码、可参考的模型和可再现性。
社区好
通过共同讨论和以 BSD-2 协议共同开发这个项目,学术研究、起步阶段的原型和工业应用可以共享各自的力量。
以上统计来自 Caffe 的 github主页。
Caffe 需要预先安装比较多的依赖项,CUDA,snappy,leveldb,gflags,glog,szip,lmdb,OpenCV,hdf5,BLAS,boost等等,下面仅介绍一下 Centos 下安装 Caffe 的过程。
基本库安装
sudo rpm -Uvh http://download.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm
$ wget -O boost_1_55_0.tar.bz2 http://downloads.sourceforge.net/project/boost/boost/1.55.0/boost_1_55_0.tar.bz2?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Fboost%2Ffiles%2Fboost%2F1.55.0%2F&ts=1385953406&use_mirror=softlayer-ams
$ tar jxvf boost_1_55_0.tar.bz2
$ cd boost_1_55_0
$ ./bootstrap.sh
$ ./b2
$ sudo ./b2 install
安装opencv-devel
$ sudo yum -y install cmake pkgconfig gtk2-devel python-devel numpy tbb-devel libpng-devel
$ wget http://sourceforge.net/projects/opencvlibrary/files/opencv-unix/2.4.10/opencv-2.4.10.zip
$ unzip opencv-2.4.10.zip
$ cd opencv-2.4.10
$ mkdir build
$ cd build
$ cmake ../ -DCMAKE_BUILD_TYPE=RELEASE -DCMAKE_INSTALL_PREFIX=/usr/local \
-DBUILD_EXAMPLES=ON -DBUILD_NEW_PYTHON_SUPPORT=ON \
-DINSTALL_PYTHON_EXAMPLES=ON
$ make -j 2
$ sudo make install
安装BLAS
sudo yum install atlas-devel
$ cp Makefile.config.example Makefile.config
# Adjust Makefile.config (for example, if using Anaconda Python)
$ make all
$ make test
$ make runtest
过程略显痛苦,如果有 GPU 再操这心吧。。。 官网写的很简单,但看网上的朋友都安装的很痛苦 :sweat:,这里也提供一份链接。
Blob
Blob 是 Caffe 中处理和传递实际数据的数据封装包,并且在 CPU 与 GPU 之间具有同步处理能力。从数学意义上说,Blob 是按 C 风格连续存储的 N 维数组。
Caffe 基于 Blobs 存储和交换数据。为了便于优化,blobs 提供统一的内存接口来存储某种类型的数据,例如批量图像数据、模型参数以及用来进行优化的导数。 Blobs 可根据 CPU 主机到 GPU 设备的同步需要,屏蔽 CPU/GPU 混和运算在计算上的开销。主机和设备上的内存按需求分配(lazily),以提高内存的使用效率。
对于 Blob 中的数据,我们关心的是 values(值)和 gradients(梯度),所以一个 Blob 单元存储了两块数据: data 和 diff。前者是在网络中传送的普通数据,后者是通过网络计算得到的梯度。而且,由于数据既可存储在 CPU 上,也可存储在 GPU 上,因而有两种数据访问方式: 静态方式,不改变数值;动态方式,改变数值。
C++
const Dtype* cpu_data() const;
Dtype* mutable_cpu_data();
(gpu 和 diff 的操作与之类似)
之所以这么设计是因为 Blob 使用了一个 SyncedMem 类来同步 CPU 和 GPU 上的数值,以隐藏同步的细节和最小化传送数据。一个经验准则是:如果不想改变数值,就一直使用常量调用,而且绝不要在自定义类中存储指针。每次操作 Blob 时,调用相应的函数来获取它的指针,因为 SyncedMem 需要用这种方式来确定何时需要复制数据。实际上,使用 GPU 时,Caffe 中 CPU 代码先从磁盘中加载数据到Blob,同时请求分配一个 GPU 设备核(device kernel) 以使用 GPU 进行计算,再将计算好的 Blob 数据送入下一层,这样既实现了高效运算,又忽略了底层细节。只要所有 layers 均有 GPU 实现,这种情况下所有的中间数据和梯度都会保留在 GPU 上。 这里有一个示例,用以确定 Blob 何时会复制数据:
C++
// 假设数据在cpu上进行初始化,并且我们有一个blob
const Dtype* foo;
Dtype* bar;
foo = blob.gpu_data(); // 数据从CPU复制到GPU
foo = blob.cpu_data(); // 没有数据复制,两者都有最新的内容
bar = blob.mutable_gpu_data(); // 没有数据复制
// ... 一些操作 ...
bar = blob.mutable_gpu_data(); // 仍在GPU,没有数据复制
foo = blob.cpu_data(); // 由于 GPU 修改了数值,数据从 GPU 复制到 CPU
foo = blob.gpu_data(); // 没有数据复制,两者都有最新的内容
bar = blob.mutable_cpu_data(); // 依旧没有数据复制
bar = blob.mutable_gpu_data(); // 数据从CPU复制到GPU
bar = blob.mutable_cpu_data(); // 数据从GPU复制到CPU
Layer
Layer 是 Caffe 模型的本质内容和执行计算的基本单元。Layer 可以进行很多运算,如: convolve(卷积)、pool(池化)、inner product(内积),rectified-linear 和 sigmoid 等非线性运算,元素级的数据变换,normalize(归一化)、load data(数据加载)、softmax 和 hinge 等losses(损失计算)。可在 Caffe 官方文档的 layer catalogue 中查看所有操作,其囊括了绝大部分目前最前沿的深度学习任务所需要的层类型。
一个 layer 通过 bottom(底部)连接层接收数据,通过 top(顶部)连接层输出数据。每一个 layer 都定义了 3 种重要的运算:setup (初始化设置),forward (前向传播),backward (反向传播)。
特别地,Forward 和 Backward 函数分别有 CPU 和 GPU 两种实现方式。如果没有实现 GPU 版本,那么 layer 将转向作为备用选项的CPU 方式。尽管这样会增加额外的数据传送成本(输入数据由 GPU 上复制到 CPU,之后输出数据从 CPU 又复制回到 GPU),但是对于做一些快速实验这样操作还是很方便的。
总的来说,layer 承担了网络的两个核心操作:forward pass (前向传播) —— 接收输入并计算输出;backward pass (反向传播) —— 接收关于输出的梯度,计算相对于参数和输入的梯度并反向传播给在它前面的层。由此组成了每个 layer 的前向和反向通道。
由于 Caffe 网络的组合性和其代码的模块化,自定义 layer 是很容易的。只要定义好 layer 的 setup (初始化设置)、forward (前向通道)和backward (反向通道),就可将 layer 纳入到网络中。
Net
通过合成和自动微分,网络同时定义了一个函数和其对应的梯度。通过合成各层的输出来计算这个函数,来执行给定的任务,并通过合成各层的后向传播过程来计算来自损失函数的梯度,从而学习任务。Caffe 模型是端到端的机器学习引擎。
准确的说,Net 是由一系列层组成的有向无环 (DAG) 计算图,Caffe 保留了计算图中所有的中间值以确保前向和反向迭代的准确性。一个典型的 Net 开始于 data layer ——从磁盘中加载数据,终止于 loss layer —— 计算如分类和重构这些任务的目标函数。
Net 由一系列层和它们之间的相互连接构成,用的是一种文本建模语言。一个简单的逻辑回归分类器的定义如下:
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()
进行模型的初始化。初始化主要实现两个操作:创建 blobs 和 layers 以搭建整个网络 DAG 图,以及调用 layers 的SetUp()函数。初始化时也会做另一些记录,例如确认整个网络结构的正确与否等。另外,初始化期间,Net 会打印其初始化日志到INFO 信息中。
Caffe 中网络的构建与设备无关,可回忆下我们之前的解释,blobs 和 layers 在模型定义时是隐藏了实现细节的。网络构建完之后,通过设置 Caffe::mode() 函数中的 Caffe::set_mode(), 即可实现在 CPU 或 GPU 上的运行。采用 CPU 或 GPU 计算得到的结果相同,CPU 与 GPU 无缝切换并且独立于模型定义。对于研究和调用来说,将模型定义和实现分离开来是最好不过了。
Model
模型是利用文本 protocol buffer (prototxt) 语言定义的,学习好的模型会被序列化地存储在二进制 protocol buffer (binaryproto).caffemodel 文件中。
模型格式用 protobuf 语言定义在 caffe.proto 文件中。大部分源文件中都带有解释。
Caffe 使用 Google Protocol Buffer 有以下优势:按序排列时二进制字符串尺寸最小,高效序列化,易读的文本格式与二进制版本兼容,可用多种语言实现高效的接口,尤其是 C++ 和 Python。这些优势造就了 Caffe 模型的灵活性与扩展性。
Forward
forward 过程为给定的待推断的输入计算输出。在forward 过程中,Caffe 组合每一层的计算以得到整个模型的计算“函数”。本过程自底向上进行。数据 x 通过一个内积层得到 g(x),然后通过 softmax 层得到 h(g(x)),通过 softmax loss 得到 fw(x)。
Backward
backward 过程根据损失来计算梯度从而进行学习。在backward 过程中,Caffe 通过自动求导并反向组合每一层的梯度来计算整个网络的梯度。这就是反传过程的本质。本过程自顶向下进行。
Forward 和 Backward的实现
只要定义好了模型,这些计算就可以立即进行:Caffe 已经准备好了 forward 和 backward 的实现方法。
Net::Forward()
和 Net::Backward()
方法实现网络的 forward 和 backward,而Layer::Forward()
和Layer::Backward()
计算每一层的 forward 和 backward;forward_{cpu, gpu}()
和backward_{cpu, gpu}
方法来适应不同的计算模式。由于条件限制或者为了使用便利,一个层可能仅实现了 CPU 或者 GPU 模式。Loss
与大多数的机器学习模型一样,在 Caffe 中,学习是由一个损失函数驱动的(通常也被称为误差、代价或者目标函数)。一个损失函数通过将参数集(即当前的网络权值)映射到一个可以标识这些参数 “不良程度” 的标量值来学习目标。因此,学习的目的是找到一个网络权重的集合,使得损失函数最小。
在 Caffe 中,损失是通过网络的前向计算得到的。每一层由一系列的输入 blobs (bottom),然后产生一系列的输出 blobs (top)。这些层的某些输出可以用来作为损失函数。典型的一对多分类任务的损失函数是 softMaxWithLoss 函数,使用以下的网络定义,例如:
layer {
name: "loss"
type: "SoftmaxWithLoss"
bottom: "pred"
bottom: "label"
top: "loss"
}
在 softMaxWithLoss 函数中,top blob 是一个标量数值,该数值是整个 batch 的损失平均值 (由预测值 pred 和真实值 label 计算得到)。
Loss weights
对于含有多个损失层的网络 (例如,一个网络使用一个 softMaxWithLoss 输入分类并使用 EuclideanLoss 层进行重构),损失权值可以被用来指定它们之间的相对重要性。
按照惯例,有着 Loss 后缀的 Caffe 层对损失函数有贡献,其他层被假定仅仅用于中间计算。然而,通过在层定义中添加一个loss_weight:<float> 字段到由该层的 top blob,任何层都可以作为一个 loss。对于带后缀 Loss 的层来说,其对于该层的第一个top blob 含有一个隐式的 loss_weight:1;其他层对应于所有 top blob 有一个隐式的 loss_weight:0。因此,上面的softMaxWithLoss 层等价于:
layer {
name: "loss"
type: "SoftmaxWithLoss"
bottom: "pred"
bottom: "label"
top: "loss"
loss_weight: 1
}
然而,任何可以 backward 的层,可允许给予一个非 0 的 loss_weight,例如,如果需要,对网络的某些中间层所产生的激活进行正则化。对于具有相关非 0 损失的非单输出,损失函数可以通过对所有 blob 求和来进行简单的计算。
那么,在 Caffe 中最终的损失函数可以通过对整个网络中所有的权值损失进行求和计算获得,正如以下的伪代码:
loss := 0
for layer in layers:
for top, loss_weight in layer.tops, layer.loss_weights:
loss += loss_weight * sum(top)
Solver
Solver 通过协调 Net 的前向推断计算和反向梯度计算 (forward inference and backward gradients),来对参数进行更新,从而达到减小loss 的目的。Caffe 模型的学习被分为两个部分:由 Solver 进行优化、更新参数,由 Net 计算出 loss 和 gradient。
Caffe 支持的 Solver 包括有:
各项 Solver 的具体说明可以参见这里。
为了创建一个 caffe 模型,我们需要在一个 protocol buffer(prototxt) 文件中定义模型的结 构。 在 caffe 中,层和相应的参数都定义在caffe.proto 文件里。
视觉层 Vision Layers
头文件:./include/caffe/vision_layers.hpp
视觉层的输入与输出均为图像。一个典型的图像通常为单通道的灰度图或三通道的 RBG 彩色图。但本文所指图像是一个广义的概念,明显特性来自于空间结构:高和宽通常均大于 1 而通道数不限,类似结构的数据均可理解为图像。这种结构可以帮助 caffe 的层决定如何处理输入数据,具体来说,大多数视觉层通常是在输入数据的某块区域执行特定操作来产生对应的输出。相反的,其它类型的层通常会忽略空间结构而把输入图像看作是一个维度为 chw 的 “单个大向量”。
损失层 Loss Layers
Loss 设置了一个损失函数用来比较网络的输出和目标值,通过最小化损失来驱动网络的训练。网络的损失通过前向操作计算,网络参数相对于损失函数的梯度则通过反向操作计算。
激活层 Activation / Neuron Layers
一般来说,激活层执行逐个元素的操作,输入一个底层 blob,输出一个尺寸相同的 顶层 blob。 在以下列出的这些层中,我们将忽略输入和输出 blob 的尺寸,因为它们是相同的。
数据层 DataLayers
数据能过数据层进入 caffe 网络:数据层处于网络的最底层,数据可以从高效率的数据库中读取 (如 LevelDB 或 LMDB),可以直接从内存中读取,若对读写效率要求不高也可以从硬盘上的 HDFT 文件或者普通的图片文件读取。常见的数据预处理操作 (减均值,尺度变换,随机裁剪或者镜像) 可以通过设定参数 TransformationParameter 来实现。
普通层 Common Layers
普通层主要负责做一些诸如:内积/全连接、分裂(Splitting)、平摊 (Flattening)、变形 (Reshape)、连结 (Concatenation)、切片 (Slicing)、计算均值等操作。
Caffe 有命令行、Python 和 MATLAB 三种接口,来实现日常使用、研究代码的交互以及实现快速原型。Caffe 以 C++ 库为核心,其在开发中使用模块化接口,而不是每次都调用其定义的编译。cmdcaffe,pycaffe 与 matcaffe 接口都可供用户使用。
命令行
命令行接口 cmdcaffe 是 caffe 中用来训练模型,计算得分以及方法判断的工具。
训练模型
caffe train 命令可以从零开始学习模型,也可以从已保存的快照继续学习,或将已经训练好的模型应用在新的数据与任务上进行微调即fine-tuning 学习:
例如,可以运行如下代码:
# 训练 LeNet
caffe train -solver examples/mnist/lenet_solver.prototxt
# 在2号GPU上训练
caffe train -solver examples/mnist/lenet_solver.prototxt -gpu 2
# 从中断点的 snapshot 继续训练
caffe train -solver examples/mnist/lenet_solver.prototxt -snapshot examples/mnist/lenet_iter_5000.solverstate
对于 fine-tuning 的完整例子,可以参考 examples/finetuningonflickr_style,但是只调用训练命令如下:
# 微调 CaffeNet 模型的权值以完成风格识别任务(style recognition)
caffe train –solver examples/finetuning_on_flickr_style/solver.prototxt \
-weights models/bvlc_reference_caffenet/bvlc_reference_caffenet.caffemodel
测试
caffe test 命令通过在 test phase 中运行模型得到分数,并且用这分数表示网络输出的最终结果。网络结构必须被适当定义,生成accuracy 或 loss 作为其结果。测试过程中,终端会显示每个 batch 的得分,最后输出全部 batch 得分的平均值。
# 对于网络结构文件 lenet_train_test.prototxt 所定义的网络
# 用 validation set 得到已训练的 LeNet 模型的分数
caffe test -model examples/mnist/lenet_train_test.prototxt \
-weights examples/mnist/lenet_iter_10000.caffemodel -gpu 0 -iterations 100
Benchmarking
caffe time 命令通过逐层计时与同步,执行模型检测。这是用来检测系统性能与测量模型相对执行时间。
# 在 CPU 上,10 iterations 训练 LeNet 的时间
caffe time -model examples/mnist/lenet_train_test.prototxt -iterations 10
# 在 GPU 上,默认的 50 iterations 训练 LeNet 的时间
caffe time -model examples/mnist/lenet_train_test.prototxt -gpu 0
# 在第一块 GPU 上,10 iterations 训练已给定权值的网络结构的时间
caffe time -model examples/mnist/lenet_train_test.prototxt \
-weights examples/mnist/lenet_iter_10000.caffemodel -gpu 0 -iterations 10
诊断
caffe device_query 命令对于多 GPU 机器上,在指定的 GPU 运行,输出 GPU 细节信息用来参考与检测设备序号。
# 查询第一块 GPU
caffe device_query -gpu 0
并行模式
caffe 工具的 -gpu 标识,对于多 GPU 的模式下,允许使用逗号分开不同 GPU 的 ID 号。 solver 与 net 在每个 GPU 上都会实例化,因此 batch size 由于具有多个 GPU 而成倍增加,增加的倍数为使用的 GPU 块数。如果要重现单个 GPU 的训练,可以在网络定义中适当减小 batch size。
# 在序号为0和1的GPU上训练 ( 双倍的batch size )
caffe train -solver examples/mnist/lenet_solver.prototxt -gpu 0,1
# 在所有GPU上训练 ( 将batch size乘上GPU数量)
caffe train -solver examples/mnist/lenet_solver.prototxt -gpu all
Python 接口
Python 接口 pycaffe 是 caffe 的一个模块,其脚本保存在 caffe/python。通过 import caffe 加载模型,实现 forward 与 backward、IO、网络可视化以及求解模型等操作。所有的模型数据,导数与参数都可读取与写入。
make pycaffe 可编译 pycaffe。通过 export PYTHONPATH= /path/to/caffe/python:$PYTHONPATH 将模块目录添加到自己的 $PYTHONPATH 目录,或者相似的指令来实现 import caffe。
Matlab接口
MATLAB 接口 matcaffe 是在 caffe/matlab 路径中的 caffe 软件包。在 matcaffe 的基础上,可将 Caffe 整合进 Matlab 代码中。
详情可参考官方接口文档。
数据相关
输入与输出
Caffe 中数据流以 Blobs 进行传输。数据层将输入转换为 blob 加载数据,将 blob 转换为其他格式保存输出。均值消去、特征缩放等基本数据处理都在数据层进行配置。新的数据格式输入需要定义新的数据层,网络的其余部分遵循 Caffe 中层目录的模块结构设定。
数据层的定义实例(加载 MNIST 数字数据):
layer {
name: "mnist"
# 数据层加载 leveldb 或 lmdb 的数据库存储格式保证快速传输
type: "Data"
# 第一个顶部(top)是数据本身:“data”的命名只是方便使用
top: "data"
# 第二个顶部(top)是数据标签:“label”的命名只是方便使用
top: "label"
# 数据层具体配置
data_param {
# 数据库路径
source: "examples/mnist/mnist_train_lmdb"
# 数据库类型:LEVELDB 或 LMDB(LMDB 支持并行读取)
backend: LMDB
# 批量处理,提高效率
batch_size: 64
}
# 常用数据转换
transform_param {
# 特征归一化系数,将范围为[0, 255]的 MNIST 数据归一化为[0, 1]
scale: 0.00390625
}
}
顶部和底部 (TopandBottom):数据层从 top的 blobs 向模型输出数据,但没有 bottom 的 blobs,因为数据层没有输入。
数据与标签 (Data and Label):数据层至少要有一个 top 输出,规范化的命名为 data, 第二个 top 输出,规范化地命名为 label。这两个 top 只是简单地生成 blobs, 它们的名称没有特殊含义。(data,label)映射关系对于分类模型更方便。
转换 (Transformations):在数据层的定义中,数据预处理通过转换参数来定义。
layer {
name: "data"
type: "Data"
[...]
transform_param {
scale: 0.1
mean_file_size: mean.binaryproto
# 对 images 进行水平镜像处理或者随机裁剪处理
# 可作为简单的数据增强处理
mirror: 1 # 1 = on, 0 = off
# 裁剪块大小为 `crop_size` x `crop_size`:
# - 训练时随机处理
# - 测试时从中间开始
crop_size: 227
}
}
预获取 (Prefetching):为了提高网络吞吐量,数据层在网络计算当前数据块的同时在后台获取并准备下一个数据块。
多个输入 (Multiple Inputs):网络可以有任意数量和类型的输入。可根据需要定义任意数量的数据层,只要保证它们有唯一的 name 和 top。多输入对于非常见形式的 ground truth 是十分有用的。例如一个数据层读取真实数据,另一个数据层读取 ground truth。在这种设计下,data 和 label 可以是任意的 4D 数组。多输入的更进一步的应用是多模型和序列模型。在这些情况下,您可能需要实现自己的数据准备程序或者构建一个特殊的数据层。
格式
参照数据层的章节,可以查看 Caffe 中数据格式的具体细节。
部署输入
对于 “动态计算部署” (on-the-fly computation deployment) 环境中,网络在输入域中定义其input:这些网络直接接收数据来进行在线或交互式计算。
Caffe 是一个高效实用的深度学习框架,拥有广泛的用户群体,代码灵活,模块化程度高,由于大量实用了 prototxt 来描述和定义模型,并且支持在命令行工具下进行通常的模型训练和优化,所以也非常适合于编码能力不强的研究人员和学习者。但是,安装过程稍显繁琐,文档不太易读,好在社区和成熟,代码丰富,入手相对容易。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。