Caffe,全称Convolutional Architecture for Fast Feature Embedding,是一个兼具表达性、速度和思维模块化的深度学习框架。由伯克利人工智能研究小组和伯克利视觉和学习中心开发。虽然其内核是用C++编写的,但Caffe有Python和Matlab 相关接口。Caffe支持多种类型的深度学习架构,面向图像分类和图像分割,还支持CNN、RCNN、LSTM和全连接神经网络设计。Caffe支持基于GPU和CPU的加速计算内核库,如NVIDIA cuDNN和Intel MKL。
Google 开发的一种内存与非易失存储介质(如硬盘文件)交换的协议接口。
Caffe 用 PB 来保存 权值 和 模型参数 。
使用统一的参数描述文件 (.proto),根据 .proto 文件生成协议细节代码。
强大的开源 C++ 库,C++ 标准库的后备。
Caffe 使用了 Boost 的智能指针,利用 Boost Python 实现了 pycaffe
。
Google 的开源命令行参数解析库。
Google 的开源日志库。
CPU 端矩阵计算库。
HDF, Hierarchical Data File,一种数据格式。实现高效的存储、分发。
图像读写、预处理操作。Caffe 实际上用到 OpenCV 的部分很少。
LMDB -- Lightning Memory-Mapped Database Manager,在 Caffe 中提供数据管理功能,将图像、二进制数据等统一用 key-value 形式存储,便于 DataLayer
读取。
LevelDB 是老版本 Caffe 使用的数据库,目前大部分例子使用 LMDB。
一个用来压缩和解压缩的 C++ 库,比 zlib 快,但是文件要更大。
src/caffe/proto/caffe.proto
,了解基本数据结构内存对象与磁盘文件的一一映射关系;cpp
和 cu
文件;ConvolutionLayer
去实现自己的卷积层tools
下面的工具去自己修改、编写新工具利用 grep 来迅速定位代码:
grep -n -H -R "xxx" * # -n 显示行号 # -H 显示文件名 # -R 递归查找子目录
L
个输出通道和 K
个输入通道,需要 L*K
个卷积核实现通道数转换;I*J
,每个输出通道的 feature map (特征图)大小均为 M*N
,则该层每个样本做一次前向传播时的计算量为: Calculations = I * J * M * N * K * L
convolution_param { num_output: 50 // 对应上面的 L kernel_size: 5 // 对应上面的 I J stride: 1 // 滑动窗口每次移动的步长 ...... }
Params = I * J * K * L
CPR = M * N
inner_product_param { num_output: 500 // 输出向量维数 ...... }
InnerProductLayer
;Layer
在 include/caffe/neural_layers.hpp
中,可以称为非线性层;blob
中的数值逐一进行非线性变换,再放回原 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 是 Caffe 的基本计算单元,至少有一个输入 Blob 和一个输出 Blob
输入是 bottom
输出是 top
一些 Layer 有 weight 和 bias
Layer 有两个运算方向:前向传播和反向传播
- 前向传播会对输入 Blob 进行某种处理(有 weight 和 bias 的层会利用这些信息对输入进行处理),得到输出 Blob
- 反向传播计算对输出 Blob 的 diff 进行某种处理,得到输入 Blob 的 diff( weight 和 bias 的 diff 可能会被用到)
下面四个函数会在各个 Layer 的派生类中经常看到:
Layer 这个类是一个虚基类,大部分函数都没有实现
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 的这些机制,组合为一个完整的深度学习模型。
之前的例子:将数据转换为 LMDB 格式,训练网络时由数据读取层(DataLayer)不断从 LMDB 读取数据,送入后续卷积、池化等层。
除了从 LMDB 、LEVELDB 等读取外,也支持从原始图像直接读取(ImageDataLayer)
数据读取层代码: include/caffe/data_layers.hpp
Data Transformer
一些预处理方法:随机镜像、随机切块、去均值、灰度变换等。
其他数据层:memory_data_layer, window_data_layer, hdf5_data_layer
一个完整的深度学习系统:数据 + 模型
一个深度学习模型通常有三部分:
可学习参数在内存中使用 Blob 对象保持,必要时以二进制保存在磁盘上(.caffemodel),便于 finetune 、共享、与性能评估(benchmark)
结构参数通过 ProtoBuffer 文本格式描述,通过该描述文件构建 Net 对象、Layer 对象,形成 DAG(有向无环图),在 Layer 与 Layer 之间、Net 输入与输出之间均为持有数据和中间结果的 Blob 对象。
训练超参数也是通过 prototxt 保存,训练时利用该描述文件构建 Solver,该对象按照一定规则在训练网络时自动调节这些超参数
对于规模很大的模型,是否每次都需要从头训练?
Caffe Model Zoo 提供了一个分享模型的平台
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
从 Loss 层向数据层方向传播 diff ,更新权值
Caffe 求解器的职责:
Caffe 求解器每次迭代中做的事情:
求解器方法的重点在于最小化损失函数的全局优化问题
SGD算法,利用负梯度和权值更新历史的线性组合更新权值 W
tools/ 下有一些利用 caffe 编写的程序,如训练、预测、预处理等
略,CUDA + cuDNN
训练脚本时这么写:
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}'