一:训练模型
第一步:下载MNIST数据集
在Linux的终端输入:
wget http://deeplearning.net/data/mnist/mnist.pkl.gz
原版mnist数据集下载比较慢,所以使用这个bengio组封装好的数据包。
第二步:处理数据集(显示数据 - 图片)
(1)使用python下载数据集并转换成图片
# encoding: utf-8
# os模块提供了多数操作系统的功能接口函数。当os模块被导入后,它会自适应于不同的操作系统平台,
# 根据不同的平台进行相应的操作,在python编程时,经常和文件、目录打交道,所以离不了os模块
import os
import pickle ,gzip #pickle提供了一个简单的持久化功能。可以将对象以文件的形式存放在磁盘上。
import matplotlib #绘图工具包
import matplotlib.pyplot
#从原始文件读入mnist数据
print ('Loading data from mnist.pkl.gzip ...')
with gzip.open('mnist.pkl.gz', 'rb') as f :
train_set, vaild_set, test_set = pickle.load(f) #pickle.load(file)反序列化对象.将文件中的数据解析为一个Python对象。
#创建mnist文件夹
imgs_dir = 'mnist'
os.system('mkdir -p {}'.format(imgs_dir)) #python中format函数用于字符串的格式化
datasets = {'train': train_set, 'val': vaild_set, 'test': test_set}
#转换train,vai,test 数据集
for dataname, dataset in datasets.items(): #Python 字典 items() 方法以列表形式(并非直接的列表,若要返回列表值还需调用list函数)返回可遍历的(键, 值) 元组数组。
print('Converting {} dataset ...'.format(dataname))
data_dir = os.sep.join([imgs_dir, dataname])
#在mninst文件夹下创建子文件夹
os.system('mkdir -p {}'.format(data_dir))
# i代表序号,用zip()函数读取对应位置的图片和标签
for i, (img, label) in enumerate(zip(*dataset)):
#格式化生成文件名,第一个字段是序号,第二个字段是数字值
filename = '{:0>6d}_{}.jpg'.format(i,label)
filepath = os.sep.join([data_dir, filename])
#将展开的一维数组还原为二位数组
img = img.reshape((28, 28))
# 利用pyplot保存可以自动归一化生成像素值在0~255之间的灰度图片
matplotlib.pyplot.imsave(filepath, img, cmap='gray')
if (i+1)% 10000 == 0:
print('{} images converted!'.format(i+1))
这样就处首先创建mnist文件夹(在pychram工程文件夹同一目录下)然后得到了train,test,val三个子文件夹分别保存对应的3个数据集转化后产生的图片(28 * 28像素)。命名规则是:第一个字段是序号,第二个字段是数字的值,保存为jpg格式。
(2)利用CAFFE提供的工具制作LMDB数据
LMDB(Lightning Memery-Mapped Database,闪电般快速的内存映射型数据库)是caffe中最常用的一种数据库格式。
来自caffe提供了专门为图像分类任务将图片转换为的官方工具,路径是朱古力/建设/工具/ convert_imageset。
使用方法:
1.生成一个图片文件路径的列表。格式例如:mnist_images / val / 000000_3.jpg 3
mnist_images / val / 000000_4.jpg 4
每一行是文件路径和其对应的标签,中间用Tab键分开。
下面是使用代码制作保存上述格式的TXT文件
# encoding: utf-8
import os
import sys
#输入路径,包含mnist图片的文件路径
input_path = sys.argv[1].rstrip(os.sep) # https://www.cnblogs.com/aland-1415/p/6613449.html
#输出的文件名,内容就是图片路径-标签的列表
output_path = sys.argv[2]
#列出路径下所有的文件名
filenames = os.listdir(input_path)
with open(output_path, 'w') as f:
for filename in filenames:
#完整的图片文件路径
filepath = os.sep.join([input_path, filename])
#第二个字段的值就是标签
label = filename[:filename.rfind('.')].split('_')[1]
#生成路径-标签的格式并写入文件
line = '{} {}\n'.format(filepath, label)#注意两个大括号之间必须是一个空格,要加\n,否则无法制作lmdb数据
f.write(line)
之后在终端输入:
python get_lmdb.py minst/train train.txt
python get_lmdb.py minst/val val.txt
python get_lmdb.py minst/test test.txt
过了好久.....之后就会制作好三个带地址和标签的txt文件。PS:不要用gedit中打开,会很卡。
PS:第一次制作数据是出现了不少错误,1.上面python代码中路径出现错误,导致无法找到minst文件夹,上面代码是在主文件夹下运行的。2.生成txt文件时,内容格式错误,里面的内容格式一定要和下图一样,路径和标签之间只有一个空格,每个路径标签占用一行。否则在调用caffe生成lmdb数据工具时会出现错误。
2.调用convert_imageset制作lmdb
终端输入:
/home/lizhen/caffe/build/tools/convert_imageset ./ train.txt train_lmdb --gray --shuffle
/home/lizhen/caffe/build/tools/convert_imageset ./ test.txt test_lmdb --gray --shuffle
/home/lizhen/caffe/build/tools/convert_imageset ./ val.txt val_lmdb --gray --shuffle
其中--gray是单通道读取灰度图的选项, - shuffle是常选祥不用管,作用是打乱文件列表的顺序完成之后生成三个文件夹
这样就把我们需要的数据制作完成了。
第三步:训练LeNet -5模型
神经网络结构和Caffe官方例子相同,不同的是把数据换成了我们自己制作的LMDB数据集。
描述数据源和网络结构的lenet_train_val.prototxt的位置在caffe /examples/ mnist/ lenet_train_val.prototxt。
name: "LeNet" //网络名字是LeNet
layer { //定义一个层(Layer)
name: "mnist" //层的名字是mnist
type: "Data" //层的类型是数据层
top: "data" //层的输出blob有两个 data、label,blob是caffe中的数据块
top: "label" //
include {
phase: TRAIN//该层仅在训练阶段有效
}
transform_param {
scale: 0.00390625 //数据变换使用的数据缩放因子
}
data_param { //数据层参数
source: "examples/mnist/mnist_train_lmdb" //制作的LMDB数据的路径
batch_size: 64 //批量数目,一次读64张图
backend: LMDB //数据格式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 { //定义一个新的卷积层,conv1,输入为data 输出为conv1
name: "conv1"
type: "Convolution"
bottom: "data"
top: "conv1"
param {
lr_mult: 1 //权值学习速率倍乘因子,1表示保持与全局一致
}
param {
lr_mult: 2 //bias学习速率倍乘因子,是全局参数的2倍
}
convolution_param { //卷积参数
num_output: 20 //输出特征feature map数目是20
kernel_size: 5 //卷积核尺寸5*5
stride: 1 //步长1
weight_filler { //权值使用xavier填充器(这是啥??)
type: "xavier"
}
bias_filler { //bias使用常数填充,默认0
type: "constant"
}
}
}
layer { //定义下采样层,输入bolb为covn1输出pool1
name: "pool1"
type: "Pooling"
bottom: "conv1"
top: "pool1"
pooling_param { //使用最大值下采样方法
pool: MAX
kernel_size: 2 //下采样窗口尺寸2*2
stride: 2 //步长2
}
}
layer { //新的卷积层covn2
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 //输出元素个数500
weight_filler {
type: "xavier"
}
bias_filler {
type: "constant"
}
}
}
layer { //非线性层,激活函数
name: "relu1"
type: "ReLU"
bottom: "ip1"
top: "ip1"
}qi
layer { //新的全连接层
name: "ip2"
q 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 { //分类准确层,test阶段
name: "accuracy"
type: "Accuracy"
bottom: "ip2"
bottom: "label"
top: "accuracy"
include {
phase: TEST
}
}
layer { //损失层,
name: "loss"
type: "SoftmaxWithLoss"
bottom: "ip2"
bottom: "label"
top: "loss"
}
我们在训练时,将数据的路径改成我们自己的就可以了。
下面就是lenet_solver.prototxt,主要用来设置训练的基本参数,如迭代次数,训练方式(gpu or cpu)等。
# The train/test net protocol buffer definition
net: "examples/mnist/lenet_train_test.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 //
# Carry out testing every 500 training iterations.
test_interval: 500 //每训练500次执行一次test
# 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的学习策略 ,lr=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 //最大迭代次数
# snapshot intermediate results
snapshot: 5000 //每迭代5000次保存一次模型的参数和训练状态
snapshot_prefix: "examples/mnist/lenet"
# solver mode: CPU or GPU
solver_mode: GPU
最后开始训练:
在开始训练时会出现一点问题,我是把训练用的数据、描述网络结构、描述参数的prototxt文件放在了 caffe文件夹下自建的mymnist下,所以训练是终端输入会有点区别,如果路径不对的话也是会出现错误的。
我的命令是:
cd caffe
/home/lizhen/caffe/build/tools/caffe train -solver mymnist/lenet_solver.prototxt -gpu all -log_dir ./mymnist
其中-gpu all 表示使用所有gpu训练,如果是多卡的话可以选择0,1,2选择相应的gpu。-log_dir ./mnist,指定日志输出位置。caffe提供了可视化log文件工具,位置在caffe/tools/extra文件夹下的plot_training_log.py.example,使用时把extra整个文件夹拷贝一份到你需要的地方,去掉.example,就可以运行这个python程序了。
具体命令是:
$ cd caffe/mymnist/extra
$ python plot_training_log.py 0 test_acc_vs_iters.png mnist_train.log
mnist_train.log,是我们生成log文件改的名字,之前的名字太长了。
这个脚本的输入参数分别是 图的类型、生成图片的路径、log路径。
图类型的输入对应是:0:测试准确率vs迭代次数
1:测试准确率vs训练时间
2:测试loss vs 迭代次数
3:测试loss vs 训练时间
4:学习率vs迭代次数
5:学习率vs训练时间
6:训练loss vs 迭代次数
7:训练loss vs训练时间
附上一张log图:
差不多到这里就完事了。
笔者的电脑是i7-4700MQ+Nvidia GTX850M,在ubuntu16.4下使用Nvidia CUDA 9.2、CuDNN 7.1加速库,训练36000次用时6分16秒。
二:对模型进行评估
这里记录了训练的模型的测试和评估情况。
终端输入:
build / tools / caffe test -model mymnist / lenet_test.prototxt -weights examples / mnist / lenet_iter_36000.caffemodel -gpu all -iterations 100
lenet_test.prototxt,和之前的lenet_train_txt.prototxt,中网络模型相同。只不过把数据输入的位置改成我们的测试数据。
-weights后面的参数是之前在训练时caffe自动保存的模型。
(2)评估模型:
评估模型一般是评估模型的速度和内存占用。在caffe中只要有描述模型网络结构的prototxt文件就可以了,因为支持评估计算性能,所以不需要参数。可以直接使用caffe / examples / mnist下的lenet 。.prototxt作为评估的结构在终端输入:
build/tools/caffe time -model examples/mnist/lenet.prototxt -gpu 0
运行结果:
使用CPU:
build/tools/caffe time -model examples/mnist/lenet.prototxt
三:识别手写
有了训练好的模型,就可以用来识别手写数字了。我们测试用的是test数据集的图片,和之前生辰的列表,基于python接口实现。
后记:这个文章主要是机械工业出版社《深度学习与计算机视觉》这本书第八章内容的亲手实验,代码也是书上的但是其中还是出现了各种莫名奇妙的问题。真是心累啊!!不过解决问题之后的喜悦也冲淡了身体和心理上的疲惫。。。
纸上得来终觉浅,绝知此事要躬行