Caffe:使用lenet5模型训练自己的数据集

一、前言

本文主要尝试将自己的数据集制作成lmdb格式,送进lenet作训练和测试,参考了https://blog.csdn.net/ap1005834/article/details/74783452和https://blog.csdn.net/u014380165/article/details/77493943这两篇博文


二、训练模型之前的准备工作

(1)图像数据准备

 由于主要是使用lenet模型训练自己的图片数据,我的图像数据共有10个类别,分别是0~9,相应地保存在名为0~9的文

夹,在/home/您的用户名/下新建一文件夹char_images,用于保存图像数据,在/home/您的用户名/char_images/下新建两

个文件夹,名字分别为train和val,各自都包含了名为0~9的文件夹,例如文件夹0内存放的是字符”0”的图像,我的文件夹 

如下:



(2)对图像数据作统一缩放至28*28,并生成txt标签

为了计算均值文件,需要将所有图片缩放至统一的尺寸,在trainval文件夹所在路径下创建python文件,命名getPath.py,并写

入以下内容:

 
 
  1. #coding:utf-8  
  2.   
  3. import cv2  
  4. import os  
  5.   
  6. def IsSubString( SubStrList , Str):  #判断SubStrList的元素  
  7.     flag = True                  #是否在Str内  
  8.     for substr in SubStrList:  
  9.         if not ( substr in Str):  
  10.             flag = False            
  11.    
  12.     return flag  
  13.   
  14. def GetFileList(FindPath,FlagStr=[]):  #搜索目录下的子文件路径  
  15.     FileList=[]    
  16.     FileNames=os.listdir(FindPath)    
  17.     if len(FileNames)>0:    
  18.         for fn in FileNames:    
  19.             if len(FlagStr)>0:    
  20.                 if IsSubString(FlagStr,fn): #不明白这里判断是为了啥  
  21.                     fullfilename=os.path.join(FindPath,fn)    
  22.                     FileList.append(fullfilename)    
  23.             else:    
  24.                 fullfilename=os.path.join(FindPath,fn)    
  25.                 FileList.append(fullfilename)    
  26.         
  27.     if len(FileList)>0:    
  28.         FileList.sort()    
  29.             
  30.     return FileList  
  31.   
  32.   
  33. train_txt = open('train.txt' , 'w'#制作标签数据  
  34. classList =['0','1','2','3','4','5','6','7','8','9']  
  35. for idx in range(len(classList)) :  
  36.     imgfile=GetFileList('train/'+ classList[idx])#将数据集放在与.py文件相同目录下       
  37.     for img in imgfile:   
  38.         srcImg = cv2.imread( img);  
  39.                 resizedImg = cv2.resize(srcImg , (28,28))  
  40.                 cv2.imwrite( img  ,resizedImg)   
  41.         strTemp=img+' '+classList[idx]+'\n'        #用空格代替转义字符 \t     
  42.             train_txt.writelines(strTemp)    
  43. train_txt.close()   
  44.   
  45.   
  46. test_txt = open('val.txt' , 'w'#制作标签数据  
  47. for idx in range(len(classList)) :  
  48.     imgfile=GetFileList('val/'+ classList[idx])  
  49.     for img in imgfile:    
  50.                 srcImg = cv2.imread( img);  
  51.                 resizedImg = cv2.resize(srcImg , (28,28))  
  52.                 cv2.imwrite( img  ,resizedImg)  
  53.         strTemp=img+' '+classList[idx]+'\n'        #用空格代替转义字符 \t     
  54.             test_txt.writelines(strTemp)    
  55. test_txt.close()  
  56.   
  57. print("成功生成文件列表")  

运行该py文件,可将所有图片缩放至28*28大小,并且在rain和val文件夹所在路径下生成训练和测试图像数据的标签txt文件,文件内容为:

 

(3)生成lmdb格式的数据集

首先于caffe路径下新建一文件夹My_File,并在My_File下新建两个文件夹Build_lmdb和Data_label,将(2)中生成文本文件train.txt和val.txt搬至Data_label下



将caffe路径下 examples/imagenet/create_imagenet.sh 复制一份到Build_lmdb文件夹下

打开create_imagenet.sh ,并做如下修改:

  1. #!/usr/bin/env sh  
  2. # Create the imagenet lmdb inputs  
  3. # N.B. set the path to the imagenet train + val data dirs  
  4. set -e  
  5.   
  6. EXAMPLE=/home/你的用户名/caffe/My_File/Build_lmdb         #生成的lmdb格式数据保存地址  
  7. DATA=/home/你的用户名/caffe/My_File/Data_label                 #两个txt标签文件所在路径  
  8. TOOLS=/home/你的用户名/caffe/build/tools                            #caffe自带工具,不用管,最好加绝对路径  
  9.   
  10. TRAIN_DATA_ROOT=/home/你的用户名/char_images/   #预先准备的训练图片路径,该路径和train.txt上写的路径合起来是图片完整路径  
  11. VAL_DATA_ROOT=/home/你的用户名/char_images/         #预先准备的测试图片路径,...  
  12.   
  13. # Set RESIZE=true to resize the images to 256x256. Leave as false if images have  
  14. # already been resized using another tool.  
  15. RESIZE=false           #因为在这之前已经将图像归一化为28*28,所以设置为false
  16. if $RESIZE; then  
  17.   RESIZE_HEIGHT=28  
  18.   RESIZE_WIDTH=28  
  19. else  
  20.   RESIZE_HEIGHT=0  
  21.   RESIZE_WIDTH=0  
  22. fi  
  23.   
  24. if [ ! -d "$TRAIN_DATA_ROOT" ]; then  
  25.   echo "Error: TRAIN_DATA_ROOT is not a path to a directory: $TRAIN_DATA_ROOT"  
  26.   echo "Set the TRAIN_DATA_ROOT variable in create_imagenet.sh to the path" \  
  27.        "where the ImageNet training data is stored."  
  28.   exit 1  
  29. fi  
  30.   
  31. if [ ! -d "$VAL_DATA_ROOT" ]; then  
  32.   echo "Error: VAL_DATA_ROOT is not a path to a directory: $VAL_DATA_ROOT"  
  33.   echo "Set the VAL_DATA_ROOT variable in create_imagenet.sh to the path" \  
  34.        "where the ImageNet validation data is stored."  
  35.   exit 1  
  36. fi  
  37.   
  38. echo "Creating train lmdb..."  
  39.   
  40. GLOG_logtostderr=1 $TOOLS/convert_imageset \  
  41.     --resize_height=$RESIZE_HEIGHT \  
  42.     --resize_width=$RESIZE_WIDTH \  
  43.     --shuffle \  
  44.     --gray \        #灰度图像加上这个  
  45.     $TRAIN_DATA_ROOT \  
  46.     $DATA/train.txt \  
  47.     $EXAMPLE/train_lmdb                   #生成的lmdb格式训练数据集所在的文件夹  
  48.   
  49. echo "Creating val lmdb..."  
  50.   
  51. GLOG_logtostderr=1 $TOOLS/convert_imageset \  
  52.     --resize_height=$RESIZE_HEIGHT \  
  53.     --resize_width=$RESIZE_WIDTH \  
  54.     --shuffle \  
  55.     --gray \        #灰度图像加上这个  
  56.     $VAL_DATA_ROOT \  
  57.     $DATA/val.txt \  
  58.     $EXAMPLE/val_lmdb              #生成的lmdb格式训练数据集所在的文件夹  
  59.   
  60. echo "Done."  

以上只是为了说明修改的地方才添加汉字注释,实际时sh文件不要出现汉字,运行该sh文件,可在Build_lmdb文件夹内生成2个文件夹train_lmdb和val_lmdb,里面各有2个lmdb格式的文件


(4)更改lenet_solver.prototxt和lenet_train_test.prototxt

将caffe/examples/mnist下的 train_lenet.sh 、lenet_solver.prototxt 、lenet_train_test.prototxt 这三个文件复制至 My_File,首先修改train_lenet.sh 如下,只改了solver.prototxt的路径

  1. #!/usr/bin/env sh  
  2. set -e  
  3.   
  4. ./build/tools/caffe train --solver=My_File/lenet_solver.prototxt $@    #改路径 

然后再更改lenet_solver.prototxt,如下:

  1. # The train/test net protocol buffer definition  
  2. net: "My_File/lenet_train_test.prototxt"            #改这里  
  3. # test_iter specifies how many forward passes the test should carry out.  
  4. # In the case of MNIST, we have test batch size 100 and 100 test iterations,  
  5. # covering the full 10,000 testing images.  
  6. test_iter: 100  
  7. # Carry out testing every 500 training iterations.  
  8. test_interval: 500  
  9. # The base learning rate, momentum and the weight decay of the network.  
  10. base_lr: 0.01  
  11. momentum: 0.9  
  12. weight_decay: 0.0005  
  13. # The learning rate policy  
  14. lr_policy: "inv"  
  15. gamma: 0.0001  
  16. power: 0.75  
  17. # Display every 100 iterations  
  18. display: 100  
  19. # The maximum number of iterations  
  20. max_iter: 10000  
  21. # snapshot intermediate results  
  22. snapshot: 5000  
  23. snapshot_prefix: "My_File/"         #改这里  
  24. # solver mode: CPU or GPU  
  25. solver_mode: GPU  (/CPU)

最后修改lenet_train_test.prototxt ,如下:

  1. name: "LeNet"  
  2. layer {  
  3.   name: "mnist"  
  4.   type: "Data"  
  5.   top: "data"  
  6.   top: "label"  
  7.   include {  
  8.     phase: TRAIN  
  9.   }  
  10.   transform_param {  
  11.     scale: 0.00390625  
  12.   }  
  13.   data_param {  
  14.     source: "My_File/Build_lmdb/train_lmdb"       #改成自己的  
  15.     batch_size: 64  
  16.     backend: LMDB  
  17.   }  
  18. }  
  19. layer {  
  20.   name: "mnist"  
  21.   type: "Data"  
  22.   top: "data"  
  23.   top: "label"  
  24.   include {  
  25.     phase: TEST  
  26.   }  
  27.   transform_param {  
  28.     scale: 0.00390625  
  29.   }  
  30.   data_param {  
  31.     source: "My_File/Build_lmdb/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: "ip1"  
  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: 10  
  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. }  

运行 My_File/train_lenet.sh ,得到最后的训练结果,在My_File下生成训练的caffemodel和solverstate。


(5)生成均值文件
均值文件主要用于图像预测的时候,由caffe/build/tools/compute_image_mean生成,在My_File文件夹下新建一文件夹Mean,用于存放均值文件,在caffe/下执行:
build/tools/compute_image_mean My_File/Build_lmdb/train_lmdb My_File/Mean/mean.binaryproto

可在My_File/Mean/下生成均值文件mean.binaryproto 

(6)生成deploy.prototxt

deploy.prototxt是在lenet_train_test.prototxt的基础上删除了开头的Train和Test部分以及结尾的Accuracy、SoftmaxWithLoss层,并在开始时增加了一个data层描述,结尾增加softmax层,可以参照博文http://blog.csdn.net/lanxuecc/article/details/52474476 使用python生成,也可以直接由train_val.prototxt上做修改,在My_File文件夹下新建一文件夹Deploy,将 lenet_train_test.prototxt复制至文件夹Deploy下,并重命名为deploy.prototxt ,修改里面的内容如下:

  1. name: "LeNet"  
  2. layer {                   #删去原来的Train和Test部分,增加一个data层  
  3.   name: "data"  
  4.   type: "Input"  
  5.   top: "data"  
  6.   input_param { shape: { dim: 1 dim: 3 dim: 28 dim: 28 } }   #这个需要和create_imagenet.sh中设置的尺寸保持一致,不然会报错
  7. }  
  8. layer {  
  9.   name: "conv1"  
  10.   type: "Convolution"  
  11.   bottom: "data"  
  12.   top: "conv1"  
  13.   param {  
  14.     lr_mult: 1  
  15.   }  
  16.   param {  
  17.     lr_mult: 2  
  18.   }  
  19.   convolution_param {  
  20.     num_output: 20  
  21.     kernel_size: 5  
  22.     stride: 1  
  23.     weight_filler {  
  24.       type: "xavier"  
  25.     }  
  26.     bias_filler {  
  27.       type: "constant"  
  28.     }  
  29.   }  
  30. }  
  31. layer {  
  32.   name: "pool1"  
  33.   type: "Pooling"  
  34.   bottom: "conv1"  
  35.   top: "pool1"  
  36.   pooling_param {  
  37.     pool: MAX  
  38.     kernel_size: 2  
  39.     stride: 2  
  40.   }  
  41. }  
  42. layer {  
  43.   name: "conv2"  
  44.   type: "Convolution"  
  45.   bottom: "pool1"  
  46.   top: "conv2"  
  47.   param {  
  48.     lr_mult: 1  
  49.   }  
  50.   param {  
  51.     lr_mult: 2  
  52.   }  
  53.   convolution_param {  
  54.     num_output: 50  
  55.     kernel_size: 5  
  56.     stride: 1  
  57.     weight_filler {  
  58.       type: "xavier"  
  59.     }  
  60.     bias_filler {  
  61.       type: "constant"  
  62.     }  
  63.   }  
  64. }  
  65. layer {  
  66.   name: "pool2"  
  67.   type: "Pooling"  
  68.   bottom: "conv2"  
  69.   top: "pool2"  
  70.   pooling_param {  
  71.     pool: MAX  
  72.     kernel_size: 2  
  73.     stride: 2  
  74.   }  
  75. }  
  76. layer {  
  77.   name: "ip1"  
  78.   type: "InnerProduct"  
  79.   bottom: "pool2"  
  80.   top: "ip1"  
  81.   param {  
  82.     lr_mult: 1  
  83.   }  
  84.   param {  
  85.     lr_mult: 2  
  86.   }  
  87.   inner_product_param {  
  88.     num_output: 500  
  89.     weight_filler {  
  90.       type: "xavier"  
  91.     }  
  92.     bias_filler {  
  93.       type: "constant"  
  94.     }  
  95.   }  
  96. }  
  97. layer {  
  98.   name: "relu1"  
  99.   type: "ReLU"  
  100.   bottom: "ip1"  
  101.   top: "ip1"  
  102. }  
  103. layer {  
  104.   name: "ip2"  
  105.   type: "InnerProduct"  
  106.   bottom: "ip1"  
  107.   top: "ip2"  
  108.   param {  
  109.     lr_mult: 1  
  110.   }  
  111.   param {  
  112.     lr_mult: 2  
  113.   }  
  114.   inner_product_param {  
  115.     num_output: 10  
  116.     weight_filler {  
  117.       type: "xavier"  
  118.     }  
  119.     bias_filler {  
  120.       type: "constant"  
  121.     }  
  122.   }  
  123. }  
  124. layer {                   #增加softmax层  
  125.   name: "prob"  
  126.   type: "Softmax"  
  127.   bottom: "ip2"  
  128.   top: "prob"  
  129. }  
(7)预测图片
在My_File文件夹下创建一文件夹Pic,用于存放测试的图片;在My_File文件夹下创建另一文件夹Synset,在其中新建synset_words.txt文件,之后在里面输入:
0
1
2
3
4
5
6
7
8

9


三、模型测试

(1)单张图片测试

使用caffe/build/examples/cpp_classification/classification.bin对单张图片作预测,在终端输入:


(2)Python版本

这是一个python脚本,用训练好的caffemodel来测试图片,接下来直接上代码,里面有详细解释,大部分你要修改的只是路径,另外在这个脚本的基础上你可以根据自己的需要进行改动。

需要的东西:训练好的caffemodel,deploy.prototxt(可以从你的train.prototxt修改得到),可以用的caffe,待测试的图像(比如jpg)

import sys

caffe_root='/your/caffe/root/' #修改成你的Caffe项目路径

sys.path.append(caffe_root+'python')

import caffe

caffe.set_mode_gpu() #设置为GPU运行

from pylab import *

# 修改成你的deploy.prototxt文件路径

model_def = 'your/deploy.prototxt/path'

model_weights = '/your/caffemodel/path' # 修改成你的caffemodel文件的路径

net = caffe.Net(model_def, # defines the structure of the model

                model_weights, # contains the trained weights

                caffe.TEST) # use test mode (e.g., don't perform dropout)

#这是一个由mean.binaryproto文件生成mean.npy文件的函数

def convert_mean(binMean,npyMean):

   blob = caffe.proto.caffe_pb2.BlobProto()

    bin_mean = open(binMean, 'rb' ).read()

    blob.ParseFromString(bin_mean)

    arr = np.array( caffe.io.blobproto_to_array(blob) )

    npy_mean = arr[0]

    np.save(npyMean, npy_mean )

    binMean='your/mean.binaryproto/path' #修改成你的mean.binaryproto文件的路径

    npyMean='which/path/you/want/to/save/mean.npy' #你想把生成的mean.npy文件放在哪个路径下

    convert_mean(binMean,npyMean)

binMean='your/mean.binaryproto/path' #修改成你的mean.binaryproto文件的路径

npyMean='which/path/you/want/to/save/mean.npy' #你想把生成的mean.npy文件放在哪个路径下

convert_mean(binMean,npyMean)

transformer = caffe.io.Transformer({'data': net.blobs['data'].data.shape})

transformer.set_transpose('data', (2,0,1)) # 通道变换,例如从(530,800,3) 变成 (3,530,800)

transformer.set_mean('data', np.load(npyMean).mean(1).mean(1)) #如果你在训练模型的时候没有对输入做mean操作,那么这边也不需要

transformer.set_raw_scale('data', 255) # rescale from [0, 1] to [0, 255]

transformer.set_channel_swap('data', (2, 1, 0)) # swap channels from RGB to BGR

with open('your/txt file/path') as image_list: # 修改成你要测试的txt文件的路径,这个txt文件的内容一般是:每行表示图像的路径,然后空格,然后是标签,也就是说每行都是两列

    with open('predict/result/you/want/to/save','w') as result: # 如果你想把预测的结果写到一个txt文件中,那么把这个路径修改成你想保存这个txt文件的路径

        count_right=0

        count_all=0

        while 1:

            list_name=image_list.readline()

            if list_name == '\n' or list_name == '': #如果txt文件都读完了则跳出循环

                break

            image_type=list_name[0:-3].split('.')[-1]

            if image_type == 'gif': #这里我对gif个数的图像直接跳过

                continue

            image = caffe.io.load_image('/your/image/path/'+list_name)

            # 这里要添加你的图像所在的路径,根据你的list_name灵活调整,总之就是图像路径

            #imshow(image)

            transformed_image = transformer.preprocess('data', image)

            # 用转换后的图像代替net.blob中的data

            net.blobs['data'].data[...] = transformed_image

            net.blobs['data'].reshape(1, 3, 224, 224)

            ### perform classification

            output = net.forward()

        # 读取预测结果和真实label

            output_prob = net.blobs['prob'].data[0]

            true_label = int(list_name[-2:-1])

            # 如果预测结果和真实label一样,则count_right+1

            if(output_prob.argmax()==true_label):

                count_right=count_right+1

            count_all=count_all+1

            # 保存预测结果,这个可选

            result.writelines(list_name[0:-1]+' '+str(output_prob.argmax())+'\n')

            #可以每预测完100个样本就打印一些,这样好知道预测的进度,尤其是要预测几万或更多样本的时候,否则你还以为代码卡死了

            if(count_all%100==0):

                print count_all

            # 打印总的预测结果

            print 'Accuracy: '+ str(float(count_right)/float(count_all))

            print 'count_all: ' + str(count_all)

            print 'count_right: ' + str(count_right)

            print 'count_wrong: ' + str(count_all-count_right)


猜你喜欢

转载自blog.csdn.net/zidaoziyande/article/details/80563365