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 包括有:
- Stochastic Gradient Descent (type: "SGD"),随机梯度下降
- AdaDelta (type: "AdaDelta")
- Adaptive Gradient (type: "AdaGrad"),自适应梯度
- Adam (type: "Adam")
- Nesterov’s Accelerated Gradient (type: "Nesterov") and
- RMSprop (type: "RMSProp")
各项 Solver 的具体说明可以参见这里。
Layer 分类
为了创建一个 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 提供的接口
Caffe 有命令行、Python 和 MATLAB 三种接口,来实现日常使用、研究代码的交互以及实现快速原型。Caffe 以 C++ 库为核心,其在开发中使用模块化接口,而不是每次都调用其定义的编译。cmdcaffe,pycaffe 与 matcaffe 接口都可供用户使用。
命令行
命令行接口 cmdcaffe 是 caffe 中用来训练模型,计算得分以及方法判断的工具。
训练模型
caffe train 命令可以从零开始学习模型,也可以从已保存的快照继续学习,或将已经训练好的模型应用在新的数据与任务上进行微调即fine-tuning 学习:
- 所有的训练都需要添加 -solver solver.prototxt 参数完成 solver 的配置
- 继续训练需要添加 -snapshot model_iter_1000.solverstate 参数来加载 solver 快照
- fine-tuning 需要添加 -weights model.caffemodel 参数完成模型初始化
例如,可以运行如下代码:
# 训练 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、网络可视化以及求解模型等操作。所有的模型数据,导数与参数都可读取与写入。
- caffe.Net 是加载、配置和运行模型的中心接口
- caffe.Classsifier 与 caffe.Detector 为一般任务实现了便捷的接口
- caffe.SGDSolver 表示求解接口
- caffe.io 通过预处理与 protocol buffers,处理输入/输出
- caffe.draw 实现数据结构可视化
- Caffe blobs 通过 numpy ndarrays 实现易用性与有效性
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 来描述和定义模型,并且支持在命令行工具下进行通常的模型训练和优化,所以也非常适合于编码能力不强的研究人员和学习者。但是,安装过程稍显繁琐,文档不太易读,好在社区和成熟,代码丰富,入手相对容易。
参考资料
- 贾扬清个人主页;
- 官方文档 - Why Caffe?
- 官方文档 - tutorial
- 官方文档 - interfaces
优质内容筛选与推荐>>
1、通读SCRUM实战指南教材,提出5个问题。2、Xamarin.IOS之快速入门3、Java 设计模式_复合模式(2016-08-31)4、解压缩c#5、.NET------ 对象判等