Caffe_notes
Table of Contents

Caffe,全称Convolutional Architecture for Fast Feature Embedding,是一个兼具表达性、速度和思维模块化的深度学习框架。由伯克利人工智能研究小组和伯克利视觉和学习中心开发。虽然其内核是用C++编写的,但Caffe有Python和Matlab 相关接口。Caffe支持多种类型的深度学习架构,面向图像分类和图像分割,还支持CNN、RCNN、LSTM和全连接神经网络设计。Caffe支持基于GPU和CPU的加速计算内核库,如NVIDIA cuDNN和Intel MKL。


Caffe 依赖库

ProtoBuffer

Google 开发的一种内存与非易失存储介质(如硬盘文件)交换的协议接口。

Caffe 用 PB 来保存 权值模型参数

使用统一的参数描述文件 (.proto),根据 .proto 文件生成协议细节代码。

Boost

强大的开源 C++ 库,C++ 标准库的后备。

Caffe 使用了 Boost 的智能指针,利用 Boost Python 实现了 pycaffe

GFLAGS

Google 的开源命令行参数解析库。

GLOG

Google 的开源日志库。

BLAS

CPU 端矩阵计算库。

HDF5

HDF, Hierarchical Data File,一种数据格式。实现高效的存储、分发。

OpenCV

图像读写、预处理操作。Caffe 实际上用到 OpenCV 的部分很少。

LMDB 和 LevelDB

LMDB -- Lightning Memory-Mapped Database Manager,在 Caffe 中提供数据管理功能,将图像、二进制数据等统一用 key-value 形式存储,便于 DataLayer 读取。

LevelDB 是老版本 Caffe 使用的数据库,目前大部分例子使用 LMDB。

Snappy

一个用来压缩和解压缩的 C++ 库,比 zlib 快,但是文件要更大。


Caffe 代码阅读建议

利用 grep 来迅速定位代码:

grep -n -H -R "xxx" *

# -n 显示行号
# -H 显示文件名
# -R 递归查找子目录

Caffe 前馈神经网络例子

卷积层

convolution_param {
    num_output: 50  // 对应上面的 L
    kernel_size: 5  // 对应上面的 I J
    stride: 1  // 滑动窗口每次移动的步长
    ......
}

全连接层

inner_product_param {
    num_output: 500  // 输出向量维数
    ......
}

激活函数


Caffe 中的数据结构

Blob

一个 Blob 在内存中表示 4 维数组:(width, height, channels, num)

width 和 height 表示图像宽高,channels 表示通道数,num 表示第几张图

Blob 用于存储数据或权值、权值增量

在进行网络计算时,每一层的输入、输出都需要 Blob 对象缓冲。

Blob 对象有 ToProto(), FromProto() 方法,可以实现内存与磁盘数据的交互。(通过 BlobProto 类型读写)

通过 Blob 来看使用 PB 的好处:(为什么不在 C++ 中直接定义结构体)

- 结构体的序列化/反序列化需要额外编程
- 不同结构体难以做到标准统一

Blob 是一个模版类,其中封装了 SyncedMemory 类对象(存放data、diff)

Layer

Layer 是 Caffe 的基本计算单元,至少有一个输入 Blob 和一个输出 Blob

输入是 bottom

输出是 top

一些 Layer 有 weight 和 bias

Layer 有两个运算方向:前向传播和反向传播

- 前向传播会对输入 Blob 进行某种处理(有 weight 和 bias 的层会利用这些信息对输入进行处理),得到输出 Blob
- 反向传播计算对输出 Blob 的 diff 进行某种处理,得到输入 Blob 的 diff( weight 和 bias 的 diff 可能会被用到)

下面四个函数会在各个 Layer 的派生类中经常看到:

Layer 这个类是一个虚基类,大部分函数都没有实现

Net

Net 在 Caffe 中代表一个完整的 CNN 模型,它包含若干 Layer 实例。LeNet,AlexNet 等这些网络用 prototxt 描述,在 Caffe 中就是一个 Net 对象

Net 中既有 Layer 对象,又有 Blob 对象。

Blob 对象用于存放每个 Layer 输入/输出中间结果,Layer 对象则根据 Net 描述对指定的输入 Blob 进行某些计算处理;输入 Blob 和输出 Blob 可能为同一个

注意:Blob 名字和 Layer 名字相同并不代表它们有直接关系

有两种 blob,分别为数据 blob 和权值 blob;深度学习的目的就是不断从“数据”中获取知识,存储到“模型”中,应用于后来的“数据”。

Net -> Layer -> Blob

Blob 提供了数据容器的机制,Layer 通过不通的策略使用它们,实现多元化的计算处理过程,同时提供深度学习的各种基本算法(卷积,池化,损失函数计算等);Net 则利用 Layer 的这些机制,组合为一个完整的深度学习模型。


Caffe IO 模块

之前的例子:将数据转换为 LMDB 格式,训练网络时由数据读取层(DataLayer)不断从 LMDB 读取数据,送入后续卷积、池化等层。

数据读取层

除了从 LMDB 、LEVELDB 等读取外,也支持从原始图像直接读取(ImageDataLayer)

数据读取层代码: include/caffe/data_layers.hpp

数据转换器

Data Transformer

一些预处理方法:随机镜像、随机切块、去均值、灰度变换等。

其他数据层:memory_data_layer, window_data_layer, hdf5_data_layer


Caffe 模型

一个完整的深度学习系统:数据 + 模型

一个深度学习模型通常有三部分:

可学习参数在内存中使用 Blob 对象保持,必要时以二进制保存在磁盘上(.caffemodel),便于 finetune 、共享、与性能评估(benchmark)

结构参数通过 ProtoBuffer 文本格式描述,通过该描述文件构建 Net 对象、Layer 对象,形成 DAG(有向无环图),在 Layer 与 Layer 之间、Net 输入与输出之间均为持有数据和中间结果的 Blob 对象。

训练超参数也是通过 prototxt 保存,训练时利用该描述文件构建 Solver,该对象按照一定规则在训练网络时自动调节这些超参数

Caffe Model Zoo

对于规模很大的模型,是否每次都需要从头训练?

Caffe Model Zoo 提供了一个分享模型的平台


Caffe 前向传播计算

CNN 训练时一般包括了前向传播和反向传播两个阶段

前向传播阶段,数据从数据读取层出发,经过若干处理层,达到最后一层(损失层或特征层)

网络中的权值在 FP 过程中不发生变化,可以看作常量。

网络路径是一个有向无环图

Caffe 中 FP 通过 Net + Layer 组合完成,中间结果和最终结果通过 Blob 承载。

前向传播:从输入层到输出层,一层一层利用已有权值来更新 loss 和最终输出

反向传播:从输出层到输入层,从输出层总误差开始一层一层更新权值

前向传播在训练和预测中都要用到

反向传播只在训练时用到

Loss Layer 是 CNN 的终点,接受两个 Blob 作为输入,其中一个为 CNN 的预测值,另一个是真实标签。 损失层将这两个输入进行一系列运算,计算损失函数值 L(\theta), \theta 是模型的权值,训练的目标就是得到最优权值,使得 Loss 最小。

损失函数是在前向传播计算中得到的,同时也是反向传播的起点

Caffe 中实现了多种损失层,分别用于不同场合,其中 SoftmaxWithLossLayer 实现了 Softmax + 交叉熵损失函数计算过程

Caffe 训练时的的问题“为什么 Loss 总在 6.9 左右?”,因为 -ln(0.001) = 6.907755... (ImageNet-1000 分类问题初始状态为均匀分布,每个类别的分类概率为 0.001 )。这说明没有收敛的迹象,需要调大学习率或者修改权值初始化方式

一个 Blob 中有 data 和 diff 两部分数据,数据读取层提供了 data, 损失层提供了 diff


Caffe 反向传播计算

从 Loss 层向数据层方向传播 diff ,更新权值


Caffe 最优化求解

Caffe 求解器的职责:

Caffe 求解器每次迭代中做的事情:

Caffe 中的求解器

求解器方法的重点在于最小化损失函数的全局优化问题

SGD算法,利用负梯度和权值更新历史的线性组合更新权值 W


Caffe 工具

tools/ 下有一些利用 caffe 编写的程序,如训练、预测、预处理等


Caffe GPU 加速

略,CUDA + cuDNN


Caffe 可视化

训练脚本时这么写:

xxx.sh 2>&1 xxx.log &
# 2>&1 表示 stdout  stderr 都重定向到 xxx.log
# 最后的 & 表示在后台运行

可以用 tail -f xxx.log 观察 log 文件的更新

可以使用如下命令提取 log 文件中的 loss 值:

cat xxx.log | grep "Train net output" | awk '{print $11}'