【caffe学习笔记——mnist】mnist手写数据集训练和测试

来源:互联网 发布:ie11浏览器修复软件 编辑:程序博客网 时间:2024/05/19 19:13

http://blog.csdn.NET/liumaolincycle/article/details/47336921

本文主要来自Caffe作者Yangqing Jia网站给出的examples。

@article{jia2014caffe,  Author = {Jia, Yangqing and Shelhamer, Evan and Donahue, Jeff and Karayev, Sergey and Long, Jonathan and Girshick, Ross and Guadarrama, Sergio and Darrell, Trevor},  Journal = {arXiv preprint arXiv:1408.5093},  Title = {Caffe: Convolutional Architecture for Fast Feature Embedding},  Year = {2014}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

1.准备数据集

首先从MNIST网站上下载数据集,运行:

cd $CAFFE_ROOT ./data/mnist/get_mnist.sh 
  • 1
  • 2

下载到四个文件,从左至右依次是测试集图像、测试集标签、训练集图像、训练集标签:

图1

转换数据格式:

./examples/mnist/create_mnist.sh
  • 1

在 examples/mnist下出现两个新的文件夹:

图2

create_mnist.sh这个脚本是将训练集和测试集分别转换成了lmdb格式。

图3

2.LeNet: MNIST分类模型

本实验用的网络模型是LeNet,它是公认在数字分类任务上效果很好的网络。实验中在原始 LeNet基础上做了一点改动,对于神经元的激活,用ReLU替换了sigmoid。 
LeNet的设计包括一个卷积层,后随一个pooling层,再一个卷积层,后随一个pooling层,再两个全连接层,类似于传统的多层感知器。经典LeNet如图:

图4

这些层的定义在examples/mnist/lenet_train_test.prototxt中。

3.定义MNIST网络

在定义自己的网络之前可以运行示例中给出的代码训练网络:

sh examples/mnist/train_lenet.sh
  • 1

过程与CIFAR-10中的一样,所用solver是examples/mnist/lenet_solver.prototxt,在这个solver中可以看到对训练与测试的简单设置,所用的网络定义就是examples/mnist/lenet_train_test.prototxt。 
下面详细学习examples/mnist/lenet_train_test.prototxt的模型定义,学习的基础是对Google Protobuf比较熟悉,可参考Google Protocol Buffer的使用和原理,还要读过Caffe使用的protobuf定义,这个定义在src/caffe/proto/caffe.proto中。 
为了更深入地了解网络是怎么定义的,我们自己写一个caffe网络参数的protobuf。首先新建一个prototxt文件,我这里的命名是lenet_train_lml.prototxt。给网络取个名字:

name: "LeNet"
  • 1

3.1 写数据层

现在要从之前创建的lmdb中读取MNIST数据,定义如下的数据层:

layer {     name: "mnist"  #该层的名字    type: "Data"    #输入的类型    data_param {    #数据参数        source: "mnist_train_lmdb"  #数据来源,从 mnist_train_lmdb中读入数据        backend: LMDB           batch_size: 64  #批次大小,即一次处理64条数据        scale: 0.00390625   #像素灰度归一化参数,1/256    }     top: "data"     #该层生成两个blob,分别是data和label    top: "label" }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

3.2 写卷积层

定义第一个卷积层:

layer {     name: "conv1"   #该层的名字    type: "Convolution"     #该层的类型    param { lr_mult: 1 }    #学习率,权重的和solver的一样    param { lr_mult: 2 }    #偏移的是solver的两倍    convolution_param {         num_output: 20  #卷积核个数        kernel_size: 5  #卷积核大小            stride: 1   #步长            weight_filler {              type: "xavier"  #用xavier算法基于输入输出神经元数自动初始化权重        }             bias_filler {             type: "constant"    #简单初始化偏移为常数,默认为0         }     }     bottom: "data"  #该层的上一层是 data    top: "conv1"    #该层生成conv1 blob}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

3.3 写pooling层

pooling层就好定义多了:

layer {     name: "pool1"   #该层的名字    type: "Pooling"     #该层的类型    pooling_param {             kernel_size: 2  #pooling的核是2×2        stride: 2   #步长是2,也就是说相邻pooling区域之间没有重叠        pool: MAX   #pooling的方式    }     bottom: "conv1"     #该层的上一层是conv1    top: "pool1"    #该层生成pool1 blob}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

然后就可以自己写第二个卷积层和pooling层了,细节参考examples/mnist/lenet_train_test.prototxt。

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     } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

3.4 写全连接层

全连接层也比较简单:

layer {     name: "ip1" #全连接层的名字    type: "InnerProduct"    #全连接层的类型    param { lr_mult: 1 }     param { lr_mult: 2 }     inner_product_param {         num_output: 500     #输出500个节点        weight_filler {             type: "xavier"         }         bias_filler {             type: "constant"         }     }     bottom: "pool2"     top: "ip1" }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

3.5 写ReLU层

layer {     name: "relu1"     type: "ReLU"     bottom: "ip1"     top: "ip1" }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

因为ReLU是元素级操作,所以可以用一种叫做in-place(猜测可以翻译为在原位置,也就是不开辟新内存)的操作来节省内存,这是通过简单地把bottom blob和top blob设成同样的名字来实现,当然了,不要在其他类型的层中这么干。 
然后再写一个全连接层:

layer {     name: "ip2"     type: "InnerProduct"     param { lr_mult: 1 }     param { lr_mult: 2 }     inner_product_param {         num_output: 10         weight_filler {             type: "xavier"         }         bias_filler {             type: "constant"         }     }     bottom: "ip1"     top: "ip2" }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

3.6 写loss层

最后写一个loss层:

layer {     name: "loss"     type: "SoftmaxWithLoss"     bottom: "ip2"     bottom: "label" }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

softmax_loss层实现了Softmax和多项Logistic损失(节省了时间,同时提高了数据稳定性)。它需要两个blob,第一个是预测,第二个是数据层生成的label。该层不产生输出,只是计算loss函数的值,在反向传播的时候使用,并初始化关于ip2的梯度。

3.7 写层次规则

层次定义包含的规则是这些层是否以及什么时候包含在网络定义中,像这样:

layer {     #...layer definition...     include: { phase: TRAIN } }
  • 1
  • 2
  • 3
  • 4

这个规则基于现有网络状态,控制网络中的层次包含关系,可以查看src/caffe/proto/caffe.proto来获取层次规则及模型概要的更多信息。 
在上面的例子中,这个层只会包含在TRAIN阶段中,如果把TRAIN改为TEST,这个层就只会被用于TEST阶段。如果不写TRAIN或TEST的话,那么这个层TRAIN阶段和TEST阶段都会被用到,所以lenet_train_test.prototxt中定义了两个DATA层,我们参考它也分别定义两个DATA层:

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    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

另外,TEST阶段有一个Accuracy层,它是用来每100次迭代报告一次模型准确率的,lenet_solver.prototxt中给出了定义,我们同样也把它加上:

layer {    name: "accuracy"    type: "Accuracy"    bottom: "ip2"    bottom: "label"    top: "accuracy"    include {        phase: TEST    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

4.定义MNIST的solver文件

再看一下lenet_solver.prototxt中都定义了些什么:

# 训练/检测网络的protobuf定义net: "examples/mnist/lenet_train_lml.prototxt"# test_iter指的是测试的迭代次数,这里是100,测试批次大小也是100,这样就覆盖了10000个测试图像test_iter: 100# 每训练迭代500次就测试一次test_interval: 500# 学习率,动量,权重下降base_lr: 0.01momentum: 0.9weight_decay: 0.0005# 学习率规则lr_policy: "inv"gamma: 0.0001power: 0.75# 每迭代100次显示一次display: 100# 最大迭代次数max_iter: 10000# 每5000次迭代存储一次快照snapshot: 5000snapshot_prefix: "examples/mnist/lenet"# 选择CPU还是GPU模式solver_mode: GPU
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

5.训练与测试模型

在写完网络定义和solver之后,就可以训练模型了,运行:

./examples/mnist/train_lenet.sh
  • 1

会在终端看到这样的消息,这些消息显示了每一层的细节,即连接关系与输出的形状,在调试的时候是很有用的:

图5

初始化以后就开始训练了:

图6

solver中设置每100次迭代打印出训练的loss,每1000次迭代打印出测试的loss:

图7

迭代完结果就出来了:

图8

最后的模型存储在一个二进制的protobuf文件lenet_iter_10000.caffemodel中,在训练其他数据集的时候可以把它作为基础模型。

6.其他

通过这个实验,终于知道网络要怎么设置了,还有其他不同的solver值得一试。例如autoencoder网络,运行命令:

./examples/mnist/train_mnist_autoencoder.sh
  • 1

或者:

./examples/mnist/train_mnist_autoencoder_adagrad.sh
  • 1

或者:

./examples/mnist/train_mnist_autoencoder_nesterov.sh


阅读全文
1 0
原创粉丝点击