作者:Qian, Caihong
文档目的
本文以Kaggle的猫狗大战为例,介绍在Windows上如何利用Caffe对自己的图片进行训练以及进行简单的调参,并且利用训练好的模型进行测试和分类预测。
环境介绍
本文所述的工具和命令适用Windows+BVLC Caffe的CPU或GPU版本 (需要在提前机器上安装BVLC Caffe并成功编译); 以及Windows+ clCaffe的版本, 但clCaffe是基于Intel Skylake及以后的处理器核显做硬件加速的修改版,使用时要注意。
准备原始数据
原始数据即用来做训练的数据,数据可以是自己制作的,也可以在网上找现成的。这里使用的是kaggle的dogsvscats(猫狗大战)的图片,下载地址:https://www.kaggle.com/c/dogs-vs-cats-redux-kernels-edition/data。
猫狗大战的数据集来自kaggle上的一个竞赛,它是一个简单的二分类的例子,里面训练集有25000张图片,猫狗各占一半,训练集图片名字中带有标签信息;测试集有12500张图片,图片名称中无标签信息。项目的目的是用训练好的神经网络识别给出的图片是猫还是狗。
下载下来的数据内容如下:
我们从训练集(25000张)中选出5000张图片(猫狗各一半)作为验证集,验证集是用来验证模型的准确度,所以需要使用带标签的数据。注意在Caffe的很多文档中,验证集被直接称为测试集,但在这里验证集是指我们分出来的5000张带标签的图片,而不是测试集那12500张不带标签的图片,本文中主要用到训练集和验证集的数据。有的时候训练集和验证集也可以被用来作为测试集。
我们最后得到的数据集目录如下:
转换图像格式
为了更高效地读取数据,Caffe要求将待训练和验证的图片转换数据格式:leveldb或lmdb格式。这里使用的是caffe自带的convert_imageset.exe 工具来进行图片格式的转换,这个工具在caffe成功编译后会生成,位置在caffe\build\tools\Release 下。
这个工具的参数如下:
convert_imageset [FLAGS] ROOTFOLDER LISTFILE DB_NAME
参数解析:
ROOTFOLDER:表示需要处理的图像集的根目录;
LISTFILE:表示输入的图像文件列表,其每一行代表图像集中的每一个图样的路径和相应的标注,比如
上图“train/578.jpg 5”代表当前路径下的/train目录下的578.jpg这张图片的标签为5.
DB_NAME:为要生成的数据库的名字。
[FLAGS]:为可选参数, 可以指是否使用shuffle,颜色空间,编码等
可选参数FLAGS设置:
- gray:bool类型,默认为false;如果设置为true,则代表将图像当做灰度图像来处理,否则当做彩色图像来处理。
- shuffle:bool类型,默认为false,如果设置为true,则代表将图像集中的图像的顺序随机打乱。一般来说,为防止出现过分有规律的数据,导致训练结果overfit,我们都会讲训练数据打乱。
- backend:string类型,可取的值的集合为{"lmdb", "leveldb"},默认为"lmdb",代表采用何种形式来存储转换后的数据
- resize_width:int32的类型,默认值为0,如果为非0值,则代表图像的宽度将被resize成resize_width
- resize_height:int32的类型,默认值为0,如果为非0值,则代表图像的高度将被resize成resize_height
- check_size:bool类型,默认值为false,如果该值为true,则在处理数据的时候将检查每一条数据的大小是否相同
- encoded:bool类型,默认值为false,如果为true,代表将存储编码后的图像,具体采用的编码方式由参数encode_type指定
- encode_type:string类型,默认值为"",用于指定用何种编码方式存储编码后的图像,取值为编码方式的后缀(如'png','jpg',...)
举个例子:convert_imageset --gray=true --resize_width=160 --resize_height=160 ImgSetRootDir ImgFileList.txt imgSet.lmdb
所以,需要得到我们要的数据格式,需要以下几步:
先生成LISTFILE,我们使用cmd命令读取目录内全部文件的文件名并输出到文件中,命令格式:
dir target_folder /b > output_filelist
在本例中,提取..\dogsvscats\train 下的所以文件的文件名,并输出到filelist.txt中:
然后将输出文件中的关键字批量替换即可。
本例中的training数据最后生成的train_list.txt内容如下图。其中0代表cat, 1代表 dog:
同样的方法生成val_list.txt
注意:标签一定要从0开始,中间数字要连续,不能随便乱设。
新建一个convertdata.bat文件,将convert_imageset.exe写进去:
由于原始的图片大小不一致,所以这里需要将图片resize 一下,全部resize为208x208.
格式不指定的话默认输出为lmdb格式。
如果需要改成leveldb格式的话需要增加flag参数”--backend=leveldb”。这里我们使用的是lmdb格式。
运行这个bat,在几分钟之后就生成lmdb数据成功了:
在当前目录下查看生成的lmdb文件:
里面生成的文件是这样的:
如果转换的是leveldb格式的话,数据是这样的:
- 到此,需要的train和val数据文件都准备好了
计算图像均值
图像的均值就是计算所有训练样本的平均值,计算出来后保存为一个平均值。图片减去这个均值后再进行训练和测试,这样会提高训练的速度和精度。
这里也是使用Caffe自带的计算均值的工具compute_image_mean.exe,这个工具在caffe成功编译后会生成,位置在caffe\build\tools\Release 下。
功能:计算训练数据库的平均图像,因为平均归一化训练图像会对结果有提升。
使用该工具的命令行格式如下:
compute_image_mean [FLAGS] INPUT_DB OUTPUT_FILE
参数解析:
- INPUT_DB: 数据源:(LevelDB或者LMDB),图像必须事先转换成LEVELDB或LMDB格式,才能到这一步
- FLAGS参数:可选,参考上面的解释。默认为LMDB格式;但如果处理的是LEVELDB格式,必须添加flag参数”--backend=leveldb”
- OUTPUT_FILE: 计算出来结果输出文件名,不提供的话,不保存平均图像blob
还是新建一个image_mean.bat文件,输入如下命令:
运行这个bat,会在当前路径生成所需要的均值文件dogsvscats.mean.binaryproto
这里,验证集不需要再一次计算均值文件,而是直接使用训练集得到的均值文件。
创建模型文件
Caffe和核心文件是*_solver.prototxt,这个文件定义了模型的迭代次数,使用的算法和参数更新的策略等;
另一个文件是*_train_test.prototxt, 这个文件是训练和验证的配置文件,在*_solver.prototxt中调用它。当然,train和test阶段也可以分开配置。
这里我们将基于caffe自带的CIFAR10 examples下的cifar10_quick_solver.prototxt和cifar10_quick_train_test.prototxt进行修改。先将这2个文件复制到我们自己的目录下,将文件名相应修改为dogsvscats_solver.prototxt和dogsvscats_train_val.prototxt。
dogsvscats_solver.prototxt修改以后的内容以及相关的解释:
# The train/val net protocol buffer definition net: "dogsvscats_train_val.prototxt" # //定义网络结构文件为“dogsvscats_train_val. Prototxt”,每一个模型就是一个net,需要在一个专门的配置文件中对net进行配置,每个net由许多的layer所组成。每一个layer的具体配置方式在下文中参见。要注意的是:文件的路径要从dogsvscats_solver.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 5,000 testing images. test_iter: 100 # //这个要与test layer中的batch_size结合起来理解,注意这个数字其实只跟验证集的图片数量有关而跟训练集的图片数量无关。mnist数据中测试样本总数为10000,它是分了100个iter每个batch_size为100来全部执行完。但本例中我们有5000个验证集样本,所以分了100次迭代,每次迭代的batch_size为50,这样100次迭代后正好处理了5000个样本. # 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.001 # // base_lr用于设置基础学习率,这是一个非常重要的参数,在迭代的过程中,可以对基础学习率进行调整。怎么样进行调整,是由lr_policy来设置。 momentum: 0.9 # //上一次梯度更新的权重 weight_decay: 0.0005 # //权重衰减系数,防止过拟合的一个参数 # The learning rate policy lr_policy: "inv" # //设置学习率的相关优化策略。lr_policy可以设置为下面这些值,相应的学习率的策略为: • - fixed: 保持base_lr不变. • - step: 如果设置为step,则还需要设置一个stepsize, 返回 base_lr * gamma ^ (floor(iter / stepsize)),其中iter表示当前的迭代次数 • - exp: 返回base_lr * gamma ^ iter, iter为当前迭代次数 • - inv: 如果设置为inv,还需要设置一个power, 返回base_lr * (1 + gamma * iter) ^ (- power) • - multistep: 如果设置为multistep,则还需要设置一个stepvalue。这个参数和step很相似,step是均匀等间隔变化,而multistep则是根据stepvalue值变化 • - poly: 学习率进行多项式误差, 返回 base_lr (1 - iter/max_iter) ^ (power) • - sigmoid: 学习率进行sigmod衰减,返回 base_lr ( 1/(1 + exp(-gamma * (iter - stepsize)))) gamma: 0.0001 #//上面介绍计算base_lr要用到 power: 0.75 #//上面介绍计算base_lr要用到 # Display every 100 iterations display: 100 # //每训练100次,在屏幕上显示一次相关信息。如果设置为0,则不显示。 # The maximum number of iterations max_iter: 10000 # //最大迭代次数 。这个数如果设置太小,会导致没有收敛,精确度很低。设置太大,会导致震荡,浪费时间。所以在实际训练时要根据情况进行调整 # snapshot intermediate results snapshot: 5000 # //每迭代5000次,保存一次结果 snapshot_prefix: "stored_model" # //保存结果路径和名称。将训练出来的model和solver状态进行保存,snapshot用于设置训练多少次后进行保存,默认为0,则不保存。 # solver mode: CPU or GPU solver_mode: CPU # //设置运行模式为CPU还是GPU。默认为GPU,如果你没有GPU,则需要改成CPU,否则会出错。在配置了GPU的PC上只要把这个参数改成GPU即可实现GPU训练
- dogsvscats_train_val.prototxt的内容以及相关的解释:
name: "Dogsvscats" #//该文件的名称,可随意取名 layer { name: "dog" #//layer名 type: "Data" #//这一层的类型为Data top: "data" #//一般用bottom表示输入,top表示输出,多个top表示有多个输出 top: "label" include { phase: TRAIN #//include表示此层只属于TRAIN阶段 } transform_param { mean_file: "dogsvscats.mean.binaryproto" #//均值文件路径 } data_param { source: "train_imgSet.lmdb" #//训练数据图片路径,如果路径不同,要加上路径 batch_size: 50 #//一个批次采用的图片数量 backend: LMDB #//数据格式。如果不写,则默认为LEVELDB } } layer { name: "dog" type: "Data" top: "data" top: "label" include { phase: TEST #//TEST阶段(其实是验证阶段) } transform_param { mean_file: "dogsvscats.mean.binaryproto" } data_param { source: "val_imgSet.lmdb" batch_size: 50 backend: LMDB } } layer { name: "conv1" type: "Convolution" #//type为Convolution代表此layer为卷积层 bottom: "data" #//bottom代表这一层的上一层为data top: "conv1" param { lr_mult: 1 #//第一个表示权值的学习率 } param { lr_mult: 2 #//第二个表示偏置项的学习率,一般偏置项的学习率是权值学习率的两倍。 } convolution_param { num_output: 32 #//卷积核(filter)的个数 pad: 2 #//pad设置为2,则四个边缘都扩充2个像素,即宽度和高度都扩充了4个像素 kernel_size: 3 #//卷积核的大小为3x3 stride: 1 #//卷积核每次移动的步长 weight_filler { type: "gaussian" #//权值初始化,默认为“constant",值全为0,很多时候我们用"xavier"算法来进行初始化,也可以设置为”gaussian" std: 0.0001 #//高斯分布的方差,std越小,证明高斯曲线越平滑 } bias_filler { type: "constant" #//偏置初始化为常数,0 } } } layer { name: "pool1" type: "Pooling" #//这一层为池化层 bottom: "conv1" top: "pool1" pooling_param { pool: MAX #//采用最大池化。平均值法的话设为AVE kernel_size: 3 #///池化的kernel为3x3 stride: 2 #//池化的步长,默认为1。一般设置为2,即不重叠 } } layer { name: "relu1" type: "ReLU" #//激活层 bottom: "pool1" top: "pool1" } layer { name: "conv2" type: "Convolution" bottom: "pool1" top: "conv2" param { lr_mult: 1 } param { lr_mult: 2 } convolution_param { num_output: 32 pad: 2 kernel_size: 5 stride: 1 weight_filler { type: "gaussian" std: 0.01 } bias_filler { type: "constant" } } } layer { name: "relu2" type: "ReLU" bottom: "conv2" top: "conv2" } layer { name: "pool2" type: "Pooling" bottom: "conv2" top: "pool2" pooling_param { pool: AVE kernel_size: 3 stride: 2 } } layer { name: "conv3" type: "Convolution" bottom: "pool2" top: "conv3" param { lr_mult: 1 } param { lr_mult: 2 } convolution_param { num_output: 32 pad: 2 kernel_size: 5 stride: 1 weight_filler { type: "gaussian" std: 0.01 } bias_filler { type: "constant" } } } layer { name: "relu3" type: "ReLU" bottom: "conv3" top: "conv3" } layer { name: "pool3" type: "Pooling" bottom: "conv3" top: "pool3" pooling_param { pool: AVE kernel_size: 3 stride: 2 } } layer { name: "ip1" type: "InnerProduct" #//第1个全连接层 bottom: "pool3" top: "ip1" param { lr_mult: 1 } param { lr_mult: 2 } inner_product_param { num_output: 16 #//输出为16 weight_filler { type: "gaussian" std: 0.1 } bias_filler { type: "constant" } } } layer { name: "ip2" type: "InnerProduct" #//第2个全连接层 bottom: "ip1" top: "ip2" param { lr_mult: 1 } param { lr_mult: 2 } inner_product_param { num_output: 32 weight_filler { type: "gaussian" std: 0.1 } bias_filler { type: "constant" } } } layer { name: "ip3" type: "InnerProduct" #//第3个全连接层 bottom: "ip2" top: "ip3" param { lr_mult: 1 } param { lr_mult: 2 } inner_product_param { num_output: 2 #//作为一个2分类问题,此处作为最后一层必须输出为2 weight_filler { type: "gaussian" std: 0.1 } bias_filler { type: "constant" } } } layer { name: "accuracy" type: "Accuracy" #//输出分类(预测)精确度,只有TEST阶段才有,因此需要加入include参数 bottom: "ip3" bottom: "label" top: "accuracy" include { phase: TEST } } layer { name: "loss" type: "SoftmaxWithLoss" bottom: "ip3" bottom: "label" top: "loss" }
开始训练
在dogsvscats_solver.prototxt和dogsvscats_train_val.prototxt同级目录下创建run_training.bat运行训练的命令如下:
执行这个bat文件即开始运行训练。
最后训练在达到max_iter后完成。在这个模型里最后得到的准确度为0.8812。 想要提高精度的话,需要调整模型参数,或者增加训练数据,可能需要尝试多次,此文暂不涉及。
测试模型
对于已训练好的模型(或者下载得到的模型),如果想测试一下模型的准确度,可以使用命令caffe test + 模型参数,如下:
解析:
caffe.exe test #//表示只做预测,不进行参数更新 --model=dogsvscats_train_val2.prototxt #//指定模型描述文件 -weights=stored_model_iter_10000.caffemodel #//指定预先训练好的模型权值文件 -iterations 100 #//指定测试迭代次数。参与测试样例数目 = 迭代次数 * batch_size
读取已训练的模型追加训练次数
假设当前已存储下来的训练好的模型为:
现在想要在这个模型基础上再追加训练2000次,那么,先修改solver文件到合适的max_iter(增加2000),
接着,在新建的bat文件中输入以下命令,执行这个bat则会在stored_model_iter_15000的基础上继续训练。
分类预测
前面VAL目录下的数据只是用来训练过程中的验证,它们是本身就设置了标签的,这里的测试数据是指没有标签数据,纯粹由训练好的模型来预测类别结果。
Caffe提供了一个分类工具classification.exe,位置在\build\examples\cpp_classification\Release 下:
注意这个工具只能对单个图片进行分类预测,如果需要批量预测,需要自行修改工具。
运行这个classification这个命令需要提供5个输入:1)deploy.prototxt文件;2)训练好的model文件;3)mean file; 4)label file; 5)待预测的图片。
首先要准备deploy文件,这个文件可以由网络文件“dogsvscats_train_val.prototxt”修改而来(官方参考: https://github.com/BVLC/caffe/wiki/Using-a-Trained-Network:-Deploy )。其内容和*_train_val.prototxt文件内容比较类似,但不同的是后者是用于训练目的,而它是用于网络完成训练之后。打开“dogsvscats_train_val.prototxt”,改名为“dogsvscats.prototxt”进行修改:
Deploy文件完成之后,再准备一个label文件,取名为”result.txt”,内容是输出分类的结果,内容很简单,其实就是两个单词。但这里每行的内容其实都映射到前面原始数据里的file list里的分类。
接下来就可以使用这个命令了。新建一个test.bat文件,输入如下命令:
这里,小数为预测结果为dog或cat的概率。
用这种方法就可以对测试集中的数据进行测试了。
至此,对自己的图片的训练,以及测试验证就全部完成了。
故障排除
第一次运行命令进行训练后,程序hang住,从log看应该是消耗的内存太大了在模型运行之初可以在这个地方找到需要的memory信息:
解决办法:将网络文件中的train和test的batch_size减小。
最开始训练的时候,发现每次验证的accuracy一直是0,导致训练无法继续,查阅资料后才发现是最开始生成file list中的label步是以0开头,而实际要求label必须以0开头,不可以随便乱取label的值。修改后问题得以解决
分类层的 num_output 设置问题
网络中最后一层的num_output的数字,其值必须是我们设置的类别总数,否则训练会不收敛。在猫狗大战这个例子里,这个值必须是2。像 MNIST手写数字分类问题,由于最后输出是10个类别,所以这个值就必须是10
其他一些错误基本是粗心导致,如相对路径,引用参数使用错误,只要观察log,细心谨慎,即可避免错误。
关于作者
钱彩红是英特尔软件与服务事业部的一名应用软件工程师,专注于在英特尔平台上与开发者的合作和业务拓展。力求将英特尔卓越的软硬件平台与合作开发者的软硬件产品完美结合,提供最优客户体验。