Caffe的深度学习训练全过程(MNIST数据集手写数字识别为例)

首先,任何一个常规的监督学习任务主要包含训练与预测两个大的步骤。以Caffe中自带的例子——MNIST数据集手写数字识别为例,它主要包括以下步骤。

1.数据预处理(建立数据库)
2.网络结构与模型训练的配置
3.训练与在训练
4.训练日志分析
5.预测检验与分析
6.性能测试
1 数据预处理
把待分析识别的图像进行简单的预处理,然后保存到数据库中。为什么不直接从图像文件中读取数据呢?因为,训练数据量可能非常大,从图像文件直接读取数据并进行初始化的效率非常低。对数据预处理能够加快训练的速度。

为了直观解释,为什么要采用图像预处理后的数据库的方式存储数据而不是直接读取图像?我们可以用系统time分别测试两种方式所需的时间。

这里写图片描述
将MNIST的数据以jpeg的格式保存成图像,并测试读取速度(以Caffe python使用的scikit image为例),代码如下所示:
这里写图片描述
所需时间如下所示:
这里写图片描述

2 网络结构与模型训练的配置
Caffe采用读入配置文件的方式进行训练。Caffe的配置文件一般由两部分组成:
solver.prototxt:对应caffe系统架构中求解器Solver。
net.prototxt:对应caffe系统架构中网络结构Net。

先来看看相对简短的solver.prototxt的内容,配置信息加入了注释:
这里写图片描述

为了方便大家理解,这里将examples/mnist/lenet_solver.prototxt中的内容进行重新排序,整个配置文件相当于回答了下面几个问题:

1.网络结构的文件在哪?
2.用什么计算资源训练?CPU还是GPU?
3.训练多久?训练和测试的比例是如何安排的,什么时候输出些给我们瞧瞧?
4.优化的学习率怎么设定?还有其他的优化参数——如动量和正则呢?
5.要时刻记得存档啊,不然大侠得从头来过了……

net.prototxt内容:(此处忽略了每个网络层的参数配置,只展示了网络的基本结构和类型配置)

name: “LeNet”
layer {
name: “mnist”
type: “Data”
top: “data”
top: “label”
}
layer {
name: “conv1”
type: “Convolution”
bottom: “data”
top: “conv1”
}
layer {
name: “pool1”
type: “Pooling”
bottom: “conv1”
top: “pool1”
}
layer {
name: “conv2”
type: “Convolution”
bottom: “pool1”
top: “conv2”
}
layer {
name: “pool2”
type: “Pooling”
bottom: “conv2”
top: “pool2”
}
layer {
name: “ip1”
type: “InnerProduct”
bottom: “pool2”
top: “ip1”
}
layer {
name: “relu1”
type: “ReLU”
bottom: “ip1”
top: “ip1”
}
layer {
name: “ip2”
type: “InnerProduct”
bottom: “ip1”
top: “ip2”
}
layer {
name: “loss”
type: “SoftmaxWithLoss”
bottom: “ip2”
bottom: “label”
top: “loss”
}
网络结构的基础配置文件,可以直接编辑出来。但Caffe提供了一套接口,大家可以通过写代码的形式生成网络结构配置文件。使编写模型配置的工作也变得简单不少。下面展示了一段生成LeNet网络结构的代码:
这里写图片描述
这里写图片描述

最终生成的结果大家都熟知,这里就不给出了。

layer {
name: “data”
type: “Data”
top: “data”
top: “label”
transform_param {
scale: 0.00390625
mirror: false
}
data_param {
source: “123”
batch_size: 128
backend: LMDB
}
}
layer {
name: “conv1”
type: “Convolution”
bottom: “data”
top: “conv1”
convolution_param {
num_output: 20
kernel_size: 5
stride: 1
weight_filler {
type: “xavier”
}
bias_filler {
type: “constant”
}
}
}
layer {
name: “pool1”
type: “Pooling”
bottom: “conv1”
top: “pool1”
pooling_param {
pool: MAX
kernel_size: 2
stride: 2
}
}
layer {
name: “conv2”
type: “Convolution”
bottom: “pool1”
top: “conv2”
convolution_param {
num_output: 50
kernel_size: 5
stride: 1
weight_filler {
type: “xavier”
}
bias_filler {
type: “constant”
}
}
}
layer {
name: “pool2”
type: “Pooling”
bottom: “conv2”
top: “pool2”
pooling_param {
pool: MAX
kernel_size: 2
stride: 2
}
}
layer {
name: “ip1”
type: “InnerProduct”
bottom: “pool2”
top: “ip1”
inner_product_param {
num_output: 500
weight_filler {
type: “xavier”
}
bias_filler {
type: “constant”
}
}
}
layer {
name: “relu1”
type: “ReLU”
bottom: “ip1”
top: “ip1”
}
layer {
name: “ip2”
type: “InnerProduct”
bottom: “ip1”
top: “ip2”
inner_product_param {
num_output: 10
weight_filler {
type: “xavier”
}
bias_filler {
type: “constant”
}
}
}
layer {
name: “loss”
type: “SoftmaxWithLoss”
bottom: “ip2”
bottom: “label”
top: “loss”
}
大家可能觉得上面的代码并没有节省太多篇幅,实际上如果将上面的代码模块化做得更好些,它就会变得非常简洁。这里就不做演示了,欢迎大家自行尝试。

3 训练与再训练
1.准备好了数据
2.确定了训练相关的网络结构与模型训练的配置
下面正式开始训练:
训练需要启动这个脚本,
这里写图片描述
然后经过一段时间的训练,命令行产生了大量日志,训练过程也宣告完成。这时训练好的模型目录多出了这几个文件:
这里写图片描述
这几个文件保存了训练过程中的一些内容:
caffemodel文件保存了caffe模型中的参数
solverstate文件保存了训练过程中的一些中间结果。保存中间结果的目:
1.‘断点训练’功能,如果模型训练突然中断训练而历史信息又丢失了,那么模型只能从头训练。这样的深度学习框架就不具备“断点训练”的功能了,只有”重头再来”的功能。现在的大型深度学习模型都需要很长的时间训练,有的需要训练好几天,如果框架不提供断点训练的功能,一旦机器出现问题导致程序崩溃,模型就不得不重头开始训练,这会对工程师的身心造成巨大打击……所以这个存档机制极大地提高了模型训练的可靠性。
2.分离存储的方式方便操作,如果模型训练彻底结束,这些历史信息就变得无用了。caffemodel文件需要保存下来,而solverstate这个文件可以被直接丢弃.

那么中间信息具体包含那些内容呢?在src/caffe/proto/caffe.proto文件中找到solverstate的内容定义。
这里写图片描述
从定义中可以很清楚的看出其内容的含义。其中history是一个比较有意思的信息,他存储了历史的参数优化信息。这个信息有什么作用呢?由于很多算法都依赖历史更新信息,如果有一个模型训练了一半停止了下来,现在想基于之前训练的成果继续训练,那么需要历史的优化信息帮助继续训练。

“再训练”包含两种模式
1.其中一种就是上面提到的“断点训练”。从前面的配置文件中可以看出,训练的总迭代轮数是10000轮,每训练5000轮,模型就会被保存一次。如果模型在训练的过程中被一些不可抗力打断了(比方说机器断电了),那么大家可以从5000轮迭代时保存的模型和历史更新参数恢复出来。
第二种“再训练”的方式则是有理论基础支撑的训练模式。这个模式会在之前训练的基础上,对模型结构做一定的修改,然后应用到其他的模型中。这种学习方式被称作迁移学习(Transfer Learning)。这里举一个简单的例子,在当前模型训练完成之后,模型参数将被直接赋值到一个新的模型上,然后让这个新模型重头开始训练。

4 训练日志分析
训练过程中Caffe产生了大量的日志,这些日志包含很多训练过程的信息,非常很值得分析。分析的内容有很多,其中之一就是分析训练过程中目标函数loss的变化曲线。在这个例子中,可以分析随着迭代轮数不断增加,Softmax Loss的变化情况。首先将训练过程的日志信息保存下来,比方说日志信息被保存到mnist.log文件中,然后用下面的命令可以将Iteration和Loss的信息提取并保存下来:
这里写图片描述
提取后的信息可以用另一个脚本完成Loss曲线的绘图工作:

import matplotlib.pyplot as plt
x = []
y = []
with open(‘loss_data’) as f:
for line in f:
sps = line[:-1].split()
x.append(int(sps[0]))
y.append(float(sps[1]))
plt.plot(x,y)
plt.show()

结果如图1所示,可见Loss很快就降到了很低的地方,模型的训练速度很快。这个优异的表现可以说明很多问题,但这里就不做过多地分析了。
这里写图片描述
除此之外,日志中输出的其他信息也可以被观察分析,比方说测试环节的精确度等,它们也可以通过上面的方法解析出来。正常训练过程中,日志里只会显示每一组迭代后模型训练的整体信息,如果想要了解更多详细的信息,就要将solver.prototxt中的调试信息打开,这样就可以获得更多有用的信息供大家分析。
模型完成训练后,就要对它的训练表现做验证,看看它在其他测试数据集上的正确性。Caffe提供了另外一个功能用于输出测试的结果。以下就是它的脚本
5 预测检验与分析
脚本的输出结果如下所示:
这里写图片描述

除了完成测试的验证,有时大家还需要知道模型更多的运算细节,这就需要深入模型内部去观察模型产生的中间结果。使用Caffe提供的借口,每一层网络输出的中间结果都可以用可视化的方法显示出来,供大家观测、分析模型每一层的作用。其中的代码如下所示:
这里写图片描述这里写图片描述

执行上面的代码就可以生成如图2到图5这几张图像,它们各代表一个模型层的输出图像:
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

这一组图展示了卷积神经网络是如何把一个数字转变成特征编码的。这样的方法虽然可以很好地看到模型内部的表现,比方说conv1的结果图中有的提取了数字的边界,有的明确了前景像素所在的位置。但是到了conv2的结果图中,模型的输出就变得让人有些看不懂了。实际上想要真正看懂这些图像想表达的内容确实有些困难的。

6 性能测试
除了在测试数据上的准确率,模型的运行时间也非常值得关心。如果模型的运行时间太长,甚至到了不可用的程度,那么即使它精度很高也没有实际意义。测试时间的脚本如下所示:
这里写图片描述
Caffe会正常的完成前向后向的计算,并记录其中的时间。以下是使一次测试结果的时间记录:
这里写图片描述
可以看出在性能测试的过程中,Lenet模型只需要不到1毫秒的时间就可以完成前向计算,这个速度还是很快的。当然这是在一个相对不错的GPU上运行的,那么如果在一个条件差的GPU上运行,结果如何呢?
这里写图片描述
可以看到不同的环境对于模型运行的时间影响很大。

总结,任何一个常规的监督学习任务主要包含训练预测两个大的步骤。以Caffe中自带的例子——MNIST数据集手写数字识别为例,它主要包括以下步骤。

1.数据预处理(建立数据库)
2.网络结构与模型训练的配置
3.训练与在训练
4.训练日志分析
5.预测检验与分析
6.性能测试

本文整理自:http://www.infoq.com/cn/articles/whole-process-of-caffe-depth-learning-training

猜你喜欢

转载自blog.csdn.net/zcCATzc/article/details/81221828
今日推荐