当前位置:   article > 正文

深度学习与计算机视觉 实践学习(3)最简单的图片分类——手写数字识别(基于Caffe和LeNet-5)_基于lenet-5架构的手写数字识别算法研究开题报告

基于lenet-5架构的手写数字识别算法研究开题报告

最简单的图片分类——手写数字识别

LeNet-5在MNIST数据集上训练做手写数字识别——图片分类中的Hello World

1. 准备数据——MNIST

在大多数框架的例子中,用MNIST训练LeNet-5的例子都被脚本高度封装了。只需要执行脚本就可以完成从下载数据到训练的过程。比如在MXNet中,直接到mxnet/example下执行train_mnist.py即可,Caffe中也有类似的shell脚本。

然后这样是不利于初学者了解到底发生了什么。本文将数据准备的部分剥离开,把每个训练都具体到一张图片,然后从头开始完整地过一遍流程。了解这个流程,基本上就了解了如何从图片数据开始到训练一个模型进行分类。

在Linux直接用wget下载即可:

>> wget http://deeplearning.net/data/mnist/mnist.pkl.gz

下载下来的mnist.pkl.gz这个压缩包中其实是数据的训练集、验证集和测试集用pickle导出的文件被压缩为gzip格式,所以用python中的gzip模块当成文件就可以读取。其中每个数据集是一个元组,第一个元素存储的是手写数字图片,表示每张图片是长度为28*28=784的一维浮点型numpy数组,这个数组就是单通道灰度图片按行展开得到,最大值为1,代表白色部分,最小值为0,代表黑色部分。元组中的第二个元素是图片对应的标签,是一个一维的整型numpy数组,按照下标位置对应图片中的数字。基于以上将数据集转换成图片的代码如下,

  1. import os
  2. import pickle, gzip
  3. from matplotlib import pyplot
  4. print('Loading data from mnist.pkl.gz ...')
  5. with gzip.open('mnist.pkl.gz', 'rb') as f:
  6. train_set, valid_set, test_set = pickle.load(f)
  7. imgs_dir = 'mnist'
  8. os.system('mkdir -p {}'.format(imgs_dir))
  9. datasets = {'train': train_set, 'val': valid_set, 'test': test_set}
  10. for dataname, dataset in datasets.items():
  11. print('Converting {} dataset ...'.format(data_dir))
  12. for i, (img, label) in enumerate(zip(*dataset)):
  13. filename = '{:0>6d}_{}.jpg'.format(i, label)
  14. filepath = os.sep.join([data_dir, filename])
  15. img = img.reshape((28, 28))
  16. pyplot.imsave(filepath, img, cmap='gray')
  17. if(i+1) % 10000 == 0:
  18. print('{} images converted!'.format(i+1))

这个脚本首先创建了一个叫mnist的文件夹,然后在mnist下创建3个子文件夹train、val和test,分别包含训练图片、验证图片和测试图片,分别用来保存对应的3个数据集转换后产生的图片。每个文件的命名规则为第一个字段是序号,第二个字段是数字的值,保存为JPG格式。

2.基于Caffe和LeNet-5训练一个用于手写数字识别的模型,并对模型进行评估和测试

(1)制作LMDB

如果是基于Caffe实现,需要先制作LMDB数据,LMDB是Caffe中最常用的一种数据库格式,全称Lightning Memory-Mapped Database(闪电般快速的内存映射型数据库)。除了快,LMDB还支持多程序同时对数据进行读取,这是相比Caffe更早支持的LevelDB的优点。现在LMDB差不多是Caffe用来训练图片最常用的数据格式。

Caffe提供了专门为图像分类任务将图片转换为LMDB的官方工具,路径为caffe/build/tools/convert_imageset。要使用这个工具,第一步是生成一个图片文件路径的列表,每一行是文件路径和对应标签(的下标),用space键或者制表符(Tab)分开。

将前面MNIST文件夹下生成的3个文件夹,train,val和test中的图片路径和对应标签,转换为上面的格式,代码如下,

  1. import os
  2. import sys
  3. input_path = sys.argv[1].rstrip(os.sep)
  4. output_path = sys.argv[2]
  5. filenames = os.listdir(input_path)
  6. with open(output_path, 'w') as f:
  7. for filename in filenames:
  8. filepath = os.sep.join([input_path, filename])
  9. label = filename[:filename.rfind('.')].split('_')[1]
  10. line = '{} {}\n'.format(filepath, label)
  11. f.write(line)

把这个文件保存为gen_caffe_imglist.py,然后依次执行下面命令:

>> python gen_caffe_imglist.py mnist/train train.txt

>> python gen_caffe_imglist.py mnist/val val.txt

>> python gen_caffe_imglist.py mnist/test test.txt

这样就生成了3个数据集的文件列表和对应标签。然后直接调用convert_imageset就可以制作lmdb了。

>> /path/to/caffe/build/tools/convert_imageset ./ train.txt train_lmdb --gray --shuffle

>> /path/to/caffe/build/tools/convert_imageset ./ val.txt train_lmdb --gray --shuffle

>> /path/to/caffe/build/tools/convert_imageset ./ test.txt train_lmdb --gray --shuffle

其中,--gray是单通道读取灰度图的选项,--shuffle是个常用的选项,作用是打乱文件列表顺序,但是在本例可有可无,因为本来顺序就是乱的。执行这个工具就是读取图片为opencv的Mat,然后保存到lmdb中。更多convert_imageset的用法可以执行下面命令或者参考源码:

>> /path/to/caffe/build/tools/convert_imageset -h

(2)训练LeNet-5

与Caffe官方例子的版本没有区别,只是输入的数据层 变成了自制的LMDB,用于描述数据源和网络结构的lenet_train_val.prototxt如下,

  1. name: "LeNet"
  2. layer {
  3. name: "mnist"
  4. type: "Data"
  5. top: "data"
  6. include {
  7. phase: TRAIN
  8. }
  9. transform_param {
  10. mean_value: 128
  11. scale: 0.00390625
  12. }
  13. data_param {
  14. source: "../data/train_lmdb"
  15. batch_size: 50
  16. backend: LMDB
  17. }
  18. }
  19. layer {
  20. name: "mnist"
  21. type: "Data"
  22. top: "data"
  23. include {
  24. phase: TEST
  25. }
  26. transform_param {
  27. mean_value: 128
  28. scale: 0.00390625
  29. }
  30. data_param {
  31. source: "../data/val_lmdb"
  32. batch_size: 100
  33. backend: LMDB
  34. }
  35. }
  36. layer {
  37. name: "conv1"
  38. type: "Convolution"
  39. bottom: "data"
  40. top: "conv1"
  41. param {
  42. lr_mult: 1
  43. }
  44. param {
  45. lr_mult: 2
  46. }
  47. convolution_param {
  48. num_output: 20
  49. kernel_size: 5
  50. stride: 1
  51. weight_filler {
  52. type: "xavier"
  53. }
  54. bias_filler {
  55. type: "constant"
  56. }
  57. }
  58. }
  59. layer {
  60. name: "pool1"
  61. type: "Pooling"
  62. bottom: "conv1"
  63. top: "pool1"
  64. pooling_param {
  65. pool: MAX
  66. kernel_size: 2
  67. stride: 2
  68. }
  69. }
  70. layer {
  71. name: "conv2"
  72. type: "Convolution"
  73. bottom: "pool1"
  74. top: "conv2"
  75. param {
  76. lr_mult: 1
  77. }
  78. param {
  79. lr_mult: 2
  80. }
  81. convolution_param {
  82. num_output: 50
  83. kernel_size: 5
  84. stride: 1
  85. weight_filler {
  86. type: "xavier"
  87. }
  88. bias_filler {
  89. type: "constant"
  90. }
  91. }
  92. }
  93. layer {
  94. name: "pool2"
  95. type: "Pooling"
  96. bottom: "conv2"
  97. top: "pool2"
  98. pooling_param {
  99. pool: MAX
  100. kernel_size: 2
  101. stride: 2
  102. }
  103. }
  104. layer {
  105. name: "ip1"
  106. type: "InnerProduct"
  107. bottom: "pool2"
  108. top: "ip1"
  109. param {
  110. lr_mult: 1
  111. }
  112. param {
  113. lr_mult: 2
  114. }
  115. inner_product_param {
  116. num_output: 500
  117. weight_filler {
  118. type: "xavier"
  119. }
  120. bias_filler {
  121. type: "constant"
  122. }
  123. }
  124. }
  125. layer {
  126. name: "relu1"
  127. type: "ReLU"
  128. bottom: "ip1"
  129. top: "relu1"
  130. }
  131. layer {
  132. name: "ip2"
  133. type: "InnerProduct"
  134. bottom: "ip1"
  135. top: "ip2"
  136. param {
  137. lr_mult: 1
  138. }
  139. param {
  140. lr_mult: 2
  141. }
  142. inner_product_param {
  143. num_output: 500
  144. weight_filler {
  145. type: "xavier"
  146. }
  147. bias_filler {
  148. type: "constant"
  149. }
  150. }
  151. }
  152. layer{
  153. name: "accuracy"
  154. type: "Accuracy"
  155. bottom: "ip2"
  156. bottom: "label"
  157. top: "accuracy"
  158. include {
  159. phase: TEST
  160. }
  161. }
  162. layer {
  163. name: "loss"
  164. type: "SoftmaxWithLoss"
  165. bottom: "ip2"
  166. bottom: "label"
  167. top: "loss"
  168. }

数据层的参数,指定均值和缩放比例的作用是,数据减去mean_value然后乘以scale,具体到mnist图片,就是把0~255之间的值缩放到-0.5~0.5,帮助收敛;卷积核的学习率为基础学习率乘以lr_mult,偏置的学习率为基础学习率乘以lr_mult;weight_filler用于初始化参数,xavier是一种初始化方法,源于Bengio组2010年论文《Understanding the difficulty of training deep feedforward neural networks》;在卷积层后面接Pooling层;ReLU单元比起原版的Sigmoid有更好的收敛效果;Accuracy层只是用在验证/测试阶段,用于计算分类的准确率。

除了网络结构和数据,还需要配置一个lenet_solver.prototxt,

  1. net: "lenet_train_val.prototxt"
  2. test_iter: 100
  3. test_interval: 500
  4. base_lr: 0.01
  5. momentum: 0.9
  6. weight_decay: 0.0005
  7. lr_policy: "inv"
  8. gamma: 0.0001
  9. power: 0.75
  10. display: 100
  11. max_iter: 36000
  12. snapshot: 5000
  13. snapshot_prefix: "mnist_lenet"
  14. solver_mode: GPU

更多Solver的详细内容可以参考Caffe官网http://caffe.berkeleyvision.org/tutorial/solver.html

接下来,就可以调用如下命令进行训练啦,

>> /path/to/caffe/build/tools/caffe train -solver lenet_solver.prototxt -gpu 0 -log_dir ./

或者双短线开头的参数命令,

>> /path/to/caffe/build/tools/caffe train --solver=lenet_solver.prototxt --gpu=0 --log_dir=./

不过第二种方式无法使用终端的自动补全,所以没有第一种方式方便哦!

其中,gpu参数是指定要用哪块GPU训练(如果有多块的话,比如一台多卡GPU服务器),如果确实需要,可以用-gpu all参数对所有卡进行训练。log_dir参数指定输出log文件的路径,前提是这个路径必须提前存在。执行命令后会看到打印。

 

注意因为指定了TEST的数据层,所以输出里按照solver中指定的间隔会输出当前模型在val_lmdb上的准确率和loss。训练完毕,就会生成几个以caffemodel和solverstate结尾的文件,这个就是模型参数和solver状态在指定迭代次数以及训练结束时的存档,名字前缀就是在lenet_solver.prototxt中指定的前缀。当然同时生成的还有log文件,命名是:

caffe.[主机名].[域名].[用户名].log.INFO.[年月日]-[时分秒].[微秒]

Caffe官方也有提供可视化log文件的工具,在caffe\tools\extra下有个plot_training_log.py.example,把这个文件复制一份命名为plot_training_log.py,就可以用来画图,这个脚本的输入参数分别是,图的类型、生成图片的路径和log的路径。

其中,图片类型的输入和对应类型如下:

0:测试准确率 vs. 迭代次数

1:测试准确率 vs. 训练时间(秒)

2:测试loss vs. 迭代次数

3:测试准确率 vs. 迭代次数

4:测试准确率 vs. 训练时间(秒)

5:测试loss vs. 迭代次数

6:测试准确率 vs. 训练时间(秒)

7:测试loss vs. 迭代次数

另外,这个脚本log文件必须以.log结尾。我们用mv命令把log文件名改成mnist_train.log,比如像看看测试准确率和测试的loss随迭代次数的变化,依次执行,

>> python plot_training_log.py 0 test_acc_vs_iters.png mnist_train.log

>> python plot_training_log.py 2 test_loss_vs_iters.png mnist_train.log

 

(3)测试和评估

测试模型准确率

训练好模型之后,就需要对模型进行测试和评估。其实在训练过程中,每迭代500次就已经在val_lmdb上对模型进行了准确率的评估。不过MNIST除了验证集外还有一个测试集,对于数据以测试集为准进行评估。

评估模型性能

一般来说主要是评估速度和内存占用。

(4)识别手写数字

有了训练好的模型,就可以用来识别手写数字了。我们测试用的是test数据集的图片和之前生成的列表。

 

(5)增加平移和旋转扰动

 

直接在样本基础上做扰动增加数据,只是数据增加的方法之一,并且不是一个好的方案,因为增加的数据量有限,并且还要占用原有样本额外的硬盘空间。最好的方法是训练的时候实时对数据进行扰动,这样等效于无限多的随机扰动。其实Caffe的数据层已经自带了最基础的数据扰动功能,不过只限于随机裁剪和随机镜像,并不是很好用。Github上有一些开源的第三方实现的实时扰动的Caffe层,会包含各种常见的数据扰动方式,只需要到github的搜索框中搜caffe augmentation就能找到很多。

 

(缺少的部分待后续补充)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/623145
推荐阅读
相关标签
  

闽ICP备14008679号