Caffe简明教程5:训练你的第一个Caffe模型-MNIST分类器
Posted xietx1995
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Caffe简明教程5:训练你的第一个Caffe模型-MNIST分类器相关的知识,希望对你有一定的参考价值。
您可以查看所有文章的索引:Caffe简明教程0:文章列表
如果你已经根据前面几篇文章成功地编译了Caffe,那么现在是时候训练你的第一个模型了。我准备借用Caffe官网的LeNet例子来写这篇文章,您也可以访问原始的文档:Training LeNet on MNIST with Caffe
Caffe在编译完成之后,在caffe根目录下有个
examples
文件夹,里面包含了很多Caffe的例子,其中就有MNIST。
1 准备数据
这次实验使用的是MNIST数据集,相信做计算机视觉的朋友都知道,MNIST数据集是一个由Yann LeCun及其同事整理和开放出来的手写数字图片的数据集。我们将使用Caffe来训练一个能够识别手写数字的模型。
首先我们需要下载MNIST数据集,运行caffe提供下载数据集的shell脚本:
cd $CAFFE_ROOT # $CAFFE_ROOT是你caffe的根目录
./data/mnist/get_mnist.sh # 此脚本将下载MNIST数据集
运行上面的脚本之后,目录$CAFFE_ROOT/data/mnist/
下将出现以下四个文件:
- train-images-idx3-ubyte(训练样本)
- train-labels-idx1-ubyte(训练样本标签)
- t10k-images-idx3-ubyte(测试样本)
- t10k-labels-idx1-ubyte(测试样本标签)
接着,我们需要把上面的数据转换为Caffe需要的数据形式(lmdb数据库形式,不了解lmdb的话可以暂时放在这里,后面的文章会详细解释),运行Caffe提供的数据转换脚本,将MNIST数据集转换为Caffe所需的lmdb文件:
./examples/mnist/create_mnist.sh # 将MNIST数据集转换为Caffe所需的lmdb文件
打开目录$CAFFE_ROOT/examples/mnist
你会发现多了两个文件夹:mnist_test_lmdb
和mnist_train_lmdb
,这两个文件夹分别保存了MNIST的以lmdb形式存储的测试集和训练集。
Trouble shooting
如果报错wget或者gnuzip没有安装,那么使用命令
sudo apt-get install wget gzip
安装它们。
wget用于从远程服务器获取文件,gzip用于解压缩文件。
2 LeNet: 用于MNIST数据集的分类模型
LeNet
是一个由Yann LeCun及其同事于1998年发明的手写数字识别模型,论文地址http://yann.lecun.com/exdb/publis/pdf/lecun-01a.pdf。LeNet是一个并不太复杂的模型,其示意图如下 所示:
简单介绍一下LeNet的结构:
- 输入为32x32的灰度图片
- 接着是一个卷积层
- 接着是一个采样层(池化层)
- 又是一个卷积层
- 又是一个采样层(池化层)
- 最后有一个10维的softmax输出层(分别对应数字0-9)
Andrew的图可能更清晰一点:
OK,了解了LeNet的结构之后,我们来看看如何在Caffe中定义这网络。
3 在Caffe中定义LeNet
在Caffe中定义一个网络的结构可能是入门者的大难题,但是,只要我们静下心仔细学习,你会发现在Caffe中定义一个网络其实是非常方便的,而且不需要我们写任何C++或者Python代码。
在Caffe中,定义网络的结构需要用的到Google的Protocol Buffer。我们现在可以先不急着去深入了解Protocol Buffer,我们只需要知道,Protocal Buffer就是一种用来描述数据结构的简单的语言(类似XML,但是比XML强大得多)。
幸运的是,Caffe的MNIST例子中已经有写好了的LeNet网络结构,这个网络结构的定义在这里:
$CAFFE_ROOT/examples/mnist/lenet_train_test.prototxt
。
现在你可以打开它查看一下,看看里面的内容是什么,是不是很像C语言里面的结构(struct)呀?看看自己能否看出来这些内容的含义。
注意:Protocol Buffer文件一般都是以.prototxt
结尾的文本文件。
现在,我们复制lenet_train_test.prototxt
中的内容,打开网址:http://ethereon.github.io/netscope/#/editor,粘帖进去,然后按shift+enter
,看看这个网络到底是什么样的。
看不出来没关系,下面会仔细讲解这个文件的内容。另外,虽然这里有个现成的LeNet结构定义文件,但是光看它的内容是不能掌握Caffe的,你还是应该亲自动手,在目录$CAFFE_ROOT/examples/mnist
下创建一个空白的文件,并命名为my_lenet.prototext
。下面我们就在my_lenet.prototext
中亲自定义一个LeNet。
3.1 给你的网络取个好听的名字
第一步,当然得给网络取个好听的名字,对吧?
用你喜欢的编辑器打开你刚刚创建的文件:$CAFFE_ROOT/examples/mnist/my_lenet.prototext
。现在,该文件是空的,我们在第一行写上:
name: "LeNet"
那么取名这个事情就完成了。
3.2 网络当然要有输入数据才能训练
名字取好之后,在新的一行,我们来定义数据的输入层,层(layer)这个概念在Caffe中是非常重要的,现在我们使用关键字layer
来定义网络的一个层,这里我们定义数据输入层。在文件$CAFFE_ROOT/examples/mnist/my_lenet.prototext
中接着添加如下内容:
layer
name: "mnist" // 网络层的名字
type: "Data" // 网络层的类型,这里Data指的是数据层,后面你还会看到其他类型的层
top: "data" // top属性指明本层的数据将输出到何处,这里数据将保存到data中
top: "label" // 数据的标签将保存在label中
include // 此属性用于确定在哪个过程中使用本层
phase: TRAIN // 只在训练的过程中使用本层
transform_param // 这个是网络层的数据转换属性,在Caffe的网络层中传输数据时,可以对数据进行处理
scale: 0.00390625 // 用于修改数据范围,就是所有输入的数据都乘以这个值,0.00390625=1/256
// 因为灰度图是0~255,乘以scale,那么所有的数据都在0~1之间,方便处理。
data_param // 这个用于定义数据的一些属性
source: "examples/mnist/mnist_train_lmdb" // 保存训练集的文件夹
backend: LMDB // 后端使用的是LMDB来保存数据,在文章开头讲过Caffe的数据集形式
batch_size: 64 // 这个就不用解释了吧。每一批输入64个样本
上面的层定义是不是看起来很头大,没关系,不是还有注释嘛,快看注释(.prototxt文件的注释和C语言中的注释方法相同)。
数据(data)和标签(label):Caffe规定,最开始的数据层必须有一个名为data的数据输出(top);另外还必须有一个名为label的标签输出(top)。Caffe从数据集中读取数据后,就将样本和标签分别保存在data和label中以便后面的层使用,这就是此例中Data层的作用。
data
和label
其实是Caffe内部的名为Blob
的类的实例,现在可以把Blob当作数组即可,后面会细讲,现在不用深究。
刚刚我们定义了训练数据的输入层(include中的phase属性指明什么时候使用该层),现在我们要定义测试阶段时数据的输入层。在文件$CAFFE_ROOT/examples/mnist/my_lenet.prototext
中接着添加如下内容:
layer
name: "mnist"
type: "Data"
top: "data"
top: "label"
include
phase: TEST // 测试阶段才使用本层
transform_param
scale: 0.00390625
data_param
source: "examples/mnist/mnist_test_lmdb" // 包含测试集的文件夹
batch_size: 100 // 每批测试100个样本
backend: LMDB
总结一下,目前,我们定义了两个layer,它们的类型都是Data,即数据层,都用于网络的输入层。第一个用于训练阶段的数据输入,第二个用于测试阶段的数据输入。
3.3 定义卷积层
定义了数据输入层之后,我们需要对样本进行卷积操作,那么接下来就让我们定义第一个卷积层,在文件$CAFFE_ROOT/examples/mnist/my_lenet.prototext
中接着添加如下内容:
layer
name: "conv1" // 该层的名字,可以自己随便取
type: "Convolution" // 该层的类型为卷积层
bottom: "data" // bottom和top对应,bottom表示数据的输入来源是什么,本层的输入为data
top: "conv1" // 执行卷积后,数据保存到conv1层中
param
lr_mult: 1 // 第一个param中的lr_mult用于设置weights的学习速率
// 与待会儿后面要设置的训练学习速率的比值
param
lr_mult: 2 // 第二个param中的lr_mult用于设置bias的学习速率
// 与待会儿后面要设置的训练学习速率的比值,设置为2收敛得更好
convolution_param // 设置卷积层的相关属性
num_output: 20 // 输出的通道数
kernel_size: 5 // 卷积核的大小
stride: 1 // 步长
weight_filler // weights的初始化的方法
type: "xavier" // 使用xavier初始化算法,此方法可以根据网络的规模自动初始化合适的参数值
bias_filler // bias的初始化方法
type: "constant" // 使用常数初始化偏置项,默认初始化为0
上面的注释已经比较清楚了,说一下lr_mult
,待会儿定义好网络结构之后,我们还需要定义一个训练文件,里面会设置网络的学习速率,这里的lr_mult
即是这个学习速率的倍数。
3.4 定义池化层
在LeNet中,第一个卷积层后面是池化层,在文件$CAFFE_ROOT/examples/mnist/my_lenet.prototext
中接着添加如下内容:
layer
name: "pool1"
type: "Pooling" // 类型为Pooling,即池化层
bottom: "conv1" // 该层的输入来自前面的卷积层conv1
top: "pool1" // 输出保存在本层中
pooling_param // 池化层的参数
pool: MAX // 池化方法(采样方法)
kernel_size: 2 // 核大小
stride: 2 // 步长
池化层很简单对不对。
3.5 第二个卷积层和池化层
上面的看懂了,接下来就不难啦。我们接着添加第二个卷积层,以及该卷积层后面的池化层。在文件$CAFFE_ROOT/examples/mnist/my_lenet.prototext
中接着添加如下内容:
layer // 定义第二个卷积层
name: "conv2"
type: "Convolution"
bottom: "pool1" // 输入来自前面的第一个池化层
top: "conv2" // 输出保存到本层
param
lr_mult: 1
param
lr_mult: 2
convolution_param
num_output: 50 // 输出通道数为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
好了,目前我们已经定义了LeNet的数据输入层、卷积层以及池化层。还剩下两个全连接层和一个Softmax
输出层。
3.6 定义全连接层
全连接层即Fully Connected Layer,一般简单地缩写为FC。全连接层很简单,就是标准神经网络中的网络层。在文件$CAFFE_ROOT/examples/mnist/my_lenet.prototext
中接着添加如下内容:
layer
name: "ip1"
type: "InnerProduct" // InnerProduct即Caffe中的全连接层,参数和输入做内积
bottom: "pool2" // 输入来自前面的池化层
top: "ip1" // 输出保存在本层
param
lr_mult: 1
param
lr_mult: 2
inner_product_param // 全连接层的参数
num_output: 500 // 输出神经元个数
weight_filler
type: "xavier"
bias_filler
type: "constant"
很简单吧。我们知道标准神经网络中都有激活函数,LeNet原始论文使用的是Sigmoid
,但是实践证明ReLU
效果更佳。所以我们要对上面的全连接层计算激活值。
3.6 定义ReLU层
在文件$CAFFE_ROOT/examples/mnist/my_lenet.prototext
中接着添加如下内容:
layer
name: "relu1"
type: "ReLU" // 该层类型为ReLU
bottom: "ip1" // 输入为之前的全连接层
top: "ip1" // 输出保存在该全连接层中(覆盖之前的值)
3.7 第二个全连接层
下面是第二个全连接层,在文件$CAFFE_ROOT/examples/mnist/my_lenet.prototext
中接着添加如下内容:
layer
name: "ip2"
type: "InnerProduct"
bottom: "ip1"
top: "ip2"
param
lr_mult: 1
param
lr_mult: 2
inner_product_param
num_output: 10 // 输出神经元的数量为10
weight_filler
type: "xavier"
bias_filler
type: "constant"
到此,LeNet的主体已经定义完成了。但是我们还需要定义loss,以及用于计算准确率的层。Caffe正好提供了各种各样的层供我们使用。
3.8 定义用于计算准确率的层
下面的层能够在测试阶段,计算模型的准确率。在文件$CAFFE_ROOT/examples/mnist/my_lenet.prototext
中接着添加如下内容:
layer
name: "accuracy"
type: "Accuracy" // 该层的类型为Accuracy,计算准确率
bottom: "ip2" // 第一个输入为之前的全连接层
bottom: "label" // 第二个输入为样本的标签
top: "accuracy" // 输出保存在本层中
include
phase: TEST // 只在测试阶段使用本层
3.9 定义loss
马上就要大功告成了,定义LeNet的最后一步,即定义loss。Caffe可以根据loss自动计算梯度,并进行反向传播。在文件$CAFFE_ROOT/examples/mnist/my_lenet.prototext
中接着添加如下内容:
layer
name: "loss"
type: "SoftmaxWithLoss" // 该层的类型为Softmax Loss
bottom: "ip2" // 第一个输入为上面的第二个全连接层
bottom: "label" // 第二个输入为样本标签
top: "loss" // 输出保存在本层中
Nice!整个LeNet已经定义完了。下面是文件my_lenet.prototext
的完整内容,看看自己少了什么没有,没有注释,看自己能不能看懂,有疑惑一定要尽情地google之:
name: "LeNet"
layer
name: "mnist"
type: "Data"
top: "data"
top: "label"
include
phase: TRAIN
transform_param
scale: 0.00390625
data_param
source: "examples/mnist/mnist_train_lmdb"
batch_size: 64
backend: LMDB
layer
name: "mnist"
type: "Data"
top: "data"
top: "label"
include
phase: TEST
transform_param
scale: 0.00390625
data_param
source: "examples/mnist/mnist_test_lmdb"
batch_size: 100
backend: LMDB
layer
name: "conv1"
type: "Convolution"
bottom: "data"
top: "conv1"
param
lr_mult: 1
param
lr_mult: 2
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"
param
lr_mult: 1
param
lr_mult: 2
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"
param
lr_mult: 1
param
lr_mult: 2
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"
param
lr_mult: 1
param
lr_mult: 2
inner_product_param
num_output: 10
weight_filler
type: "xavier"
bias_filler
type: "constant"
layer
name: "accuracy"
type: "Accuracy"
bottom: "ip2"
bottom: "label"
top: "accuracy"
include
phase: TEST
layer
name: "loss"
type: "SoftmaxWithLoss"
bottom: "ip2"
bottom: "label"
top: "loss"
没问题了?那么我们还需要写一个solver,即网络的解法,或者说我们要告诉Caffe如何去训练LeNet。
4 定义LeNet的解法文件
Caffe给的例子中已经写了,其内容如下,强烈推荐你自己试着写一遍。文件$CAFFE_ROOT/examples/mnist/lenet_solver.prototxt
的内容如下:
# The train/test net protocol buffer definition
net: "examples/mnist/my_lenet.prototxt" # 这里换成我们定义的网络结构文件
# test_iter specifies how many forward passes the test should carry out.
# In the case of MNIST, we have test batch size 100 and 100 test iterations,
# covering the full 10,000 testing images.
test_iter: 100 # 每次测试执行100次前向传播
# Carry out testing every 500 training iterations.
test_interval: 500 # 训练每迭代500次就测试一次
# The base learning rate, momentum and the weight decay of the network.
base_lr: 0.01 # 基本学习速率
momentum: 0.9 # 冲量梯度下降参数
weight_decay: 0.0005 # 正则化参数
# The learning rate policy
lr_policy: "inv" # 学习速率的更新模式
# inv: return base_lr * (1 + gamma * iter) ^ (- power) 此方法可逐渐降低学习速率,防止发散
gamma: 0.0001
power: 0.75
# Display every 100 iterations
display: 100 # 100次显示一次当前的loss
# The maximum number of iterations
max_iter: 10000 # 最多迭代10000次
# snapshot intermediate results
snapshot: 5000 # 5000次迭代保存一次模型
snapshot_prefix: "examples/mnist/lenet"
# solver mode: CPU or GPU
solver_mode: GPU # 使用GPU训练
上面的注释解释了这些设置的含义。总结一下,在Caffe中想要训练一个网络,必须提供下面这些东西:
- 数据(LMDB方式存储)
- 网络结构的定义
- 网络的solver(其中有个参数net的值即为网络结构定义文件)
5 训练LeNet模型
训练模型非常简单。编译Caffe时已经在目录$CAFFE_ROOT/build/tools
目录下生成了一个名为caffe的可执行文件,它就是我们用于训练网络的工具,输入如下命令进行训练:
$ cd $CAFFE_ROOT
$ ./build/tools/caffe train --solver=examples/mnist/lenet_solver.prototxt
当然你也可以使用caffe写好的训练脚本(内容和上面的差不多):
$ ./examples/mnist/train_lenet.sh
然后你就会看到刷拉拉地一行行训练信息不停地显示出来。MNIST数据集不大,很快就能训练完成。
下一篇文章:待续
以上是关于Caffe简明教程5:训练你的第一个Caffe模型-MNIST分类器的主要内容,如果未能解决你的问题,请参考以下文章