一、制作自己的数据集
首先将所有图片放在一个文件夹中,文件夹中根据类别建几个子文件夹,每个子文件夹放某一类所有图片,并根据类别编号。
如我这里是要对stanfdog数据集进行分类,该数据集总共有20580张狗狗的图片,分成120类,每类狗的图片张数不一样但相差不是很大. 我的狗狗图片放在Stanford-Dogs(文件夹命名最好不要出现空格,避免中文)中,Stanford-Dogs 中有些子文件夹,每个子文件夹代表一类,子文件夹中放该类狗的图片
我这里把训练用的图片集和训练用的图片集放在一起了,你也可以把训练图片集和验证图片集分开放。
然后可以用下面的matlab代码,生成||图片路径/图片名 类别名||的trainval.txt文件,和||类别编号 类别名||的labels.txt文件,进而生成训练集的||图片路径/图片名 类别名||的train.txt文件,||图片路径/图片名 类别名||val.txt文件。( 注在生成trainval.txt和labels.txt后注意检查下生成的文件内容对不对,有时可能会出现一些其他乱七八糟的符号,直接删除保存就好,再生成train.txt和val.txt,Stanford-Dogs文件夹下不要包含其他无关文件)
clc;
clear;
%%下面生成顺序的trainval.txt和labels文件
%先设置train占数据集的百分比,余下部分为val
% maindir='caffe-new/data/Stanford-Dogs/';
maindir='/home/nielsen/caffe-new/data/Stanford-Dogs/'; %图片文件夹存放路径,注意这里windows下是‘\',linnux下是'/'
wf = fopen('trainval.txt','w');
lbf=fopen('labels.txt','w');
train_percent=0.6;%val_percent=1-train_percent
subdir = dir(maindir); %获取图片目录下的子目录个数(注意这里最好不要放除子图片文件夹以外的文件夹和文件,否则会一起统计进去)
ii=-1;
numoffile=0;
for i = 1:length(subdir)%第一层目录
if ~strcmp(subdir(i).name ,'.') && ~strcmp(subdir(i).name,'..')
ii=ii+1; %标签从0开始
label = subdir(i).name; %子文件夹的名字
fprintf(lbf,'%s\n',label);
label=strcat(label,'/');
subsubdir = dir(strcat(maindir,label));
for j=1:length(subsubdir) %第二层目录,即子目录下的图片
if ~strcmp(subsubdir(j).name ,'.') && ~strcmp(subsubdir(j).name,'..')
numoffile=numoffile+1; %记录行数,即图片总数
fprintf(wf,'%s%s %d\n',label,subsubdir(j).name,ii); %图片路径/图片名 图片所属类别编号
fprintf('处理标签为%d的第%d张图片\n',ii,j-2); %j之所以-2是因为生成的子文件目录都包含'.'和'..',j要将这两种情况排除在外
end
end
end
end
fclose(wf);
fclose(lbf);
if 0 %不需要打乱顺序
%%
%下面将trainval的顺序打乱
file=cell(1,numoffile);
fin=fopen('trainval.txt','r');
i=1;
while ~feof(fin)
tline=fgetl(fin); %获得每行信息
file{i}=tline;
i=i+1;
end
fclose(fin);
fprintf('\ntrainval.txt共%d行,开始打乱顺序....\n',numoffile);
pause(1);
rep=randperm(numoffile);
fout=fopen('trainval.txt','w');
for i=1:numoffile
fprintf(fout,'%s\n',file{rep(i)}); %按rep生成的顺序重排文本内容
end
fprintf('生成的trainval.txt已打乱顺序.\n');
fclose(fout);
end
%%
%下面根据打乱顺序的trainval.txt生成train.txt和val.txt
fprintf('开始生成train.txt和val.txt...\n');
pause; %暂停一下检查一下生成的trainval.txt生成的是不是对的
train_file=fopen('train.txt','w');
text_file=fopen('val.txt','w');
trainvalfile=fopen('trainval.txt','r');
num_train=sort(randperm(numoffile,floor(numoffile*train_percent))); %随机取出 train_percent的数据
num_test=setdiff(1:numoffile,num_train); %去掉num_train的数据编号就是num_test数据编号
i=1;
%分割图像文件生成训练文件和测试文件
while ~feof(trainvalfile)
tline=fgetl(trainvalfile);
if ismember(i,num_train)
fprintf(train_file,'%s\n',tline); %图片路径/图片名 图片所属类别编号(不需要加图片编号,txt会自动编号)
else
fprintf(text_file,'%s\n',tline);
end
i=i+1;
end
fclose(train_file);
fclose(text_file);
fclose(trainvalfile);
fprintf('共有图片%d张!\n',numoffile);
fprintf('Done!\n');
生成的val.txt文件如下:
生成的labels.txt文件如下:
(这里前面的编号是txt文档编辑器的编号,我们在制作txt文件时不需要加编号)
二、用creat_imagenet.sh把数据转换成LMDB或LEVELDB格式
将编译好的caffe文件夹中的examples/imagenet中的create_imagenet.sh复制到你需要的路径下,我这里是“lxq/caffe/models/bvlc_alexnet/stanfdog”,并对create_imagenet.sh文件做些修改,主要是路径的修改,还有就是我的图片大小都不一样,所以需要resize,将RESIZE=false改为RESIZE=true(这里是true而非True)
create_imagenet.sh
#!/usr/bin/env sh
# Create the imagenet lmdb inputs
# N.B. set the path to the imagenet train + val data dirs
set -e
EXAMPLE=lxq/caffe/models/bvlc_alexnet/stanfdog #生成的lmdb文件存放路径
DATA=lxq/caffe/models/bvlc_alexnet/stanfdog #train.txt和val.txt存放路径
TOOLS=lxq/caffe/build/tools #convert_imageset.cpp文件存放路径
TRAIN_DATA_ROOT=/home/nielsen/caffe-new/data/Stanford-Dogs/ #图片存放路径(这里没有把train和val分开)
VAL_DATA_ROOT=/home/nielsen/caffe-new/data/Stanford-Dogs/
# Set RESIZE=true to resize the images to 256x256. Leave as false if images have
# already been resized using another tool.
RESIZE=true
if $RESIZE; then
RESIZE_HEIGHT=227
RESIZE_WIDTH=227
else
RESIZE_HEIGHT=0
RESIZE_WIDTH=0
fi
if [ ! -d "$TRAIN_DATA_ROOT" ]; then
echo "Error: TRAIN_DATA_ROOT is not a path to a directory: $TRAIN_DATA_ROOT"
echo "Set the TRAIN_DATA_ROOT variable in create_imagenet.sh to the path" \
"where the ImageNet training data is stored."
exit 1
fi
if [ ! -d "$VAL_DATA_ROOT" ]; then
echo "Error: VAL_DATA_ROOT is not a path to a directory: $VAL_DATA_ROOT"
echo "Set the VAL_DATA_ROOT variable in create_imagenet.sh to the path" \
"where the ImageNet validation data is stored."
exit 1
fi
echo "Creating train lmdb..."
GLOG_logtostderr=1 $TOOLS/convert_imageset \
--resize_height=$RESIZE_HEIGHT \
--resize_width=$RESIZE_WIDTH \
--shuffle \ #随机排序
$TRAIN_DATA_ROOT \ #原始图片路径
$DATA/train.txt \ #train.txt路径
$EXAMPLE/stanfdog_train_lmdb #生成的lmdb文件名及存放路径
echo "Creating val lmdb..."
GLOG_logtostderr=1 $TOOLS/convert_imageset \
--resize_height=$RESIZE_HEIGHT \
--resize_width=$RESIZE_WIDTH \
--shuffle \
$VAL_DATA_ROOT \
$DATA/val.txt \
$EXAMPLE/stanfdog_val_lmdb
echo "Done."
(注意create_imagenet.sh中最好不要出现中文,哪怕我这里是注释了的中文,刚开始时我是用这个添加了注释的文件运行,结果出错,也不知道错在哪儿,后来把里面的中文删掉了重新运行还是出错,最后干脆又重新复制了个文件过来改好路径,运行就对啦,我想可能是添加的中文影响了文件结构还是怎么回事,总之尽量避免出现中文)
在终端输入:
./lxq/caffe/models/bvlc_alexnet/stanfdog/create_imagenet.sh
如图:
在我对应的文件路径下生成了stanfdog_train_lmdb 和stanfdog_val_lmdb 文件夹
三、生成训练图片的均值文件
将编译好的caffe文件夹中的examples/imagenet中的make_imagenet_mean.sh复制到你需要的路径下,我这里是“lxq/caffe/models/bvlc_alexnet/stanfdog”,并对make_imagenet_mean.sh中的路径做对应的修改
make_imagenet_mean.sh
#!/usr/bin/env sh
# Compute the mean image from the imagenet training lmdb
# N.B. this is available in data/ilsvrc12
EXAMPLE=lxq/caffe/models/bvlc_alexnet/stanfdog
DATA=lxq/caffe/models/bvlc_alexnet/stanfdog
TOOLS=lxq/caffe/build/tools
$TOOLS/compute_image_mean $EXAMPLE/stanfdog_train_lmdb \
$DATA/imagenet_mean.binaryproto
echo "Done."
在终端输入:
./lxq/caffe/models/bvlc_alexnet/stanfdog/make_imagenet_mean.sh
在lxq/caffe/models/bvlc_alexnet/stanfdog文件夹下生成文件imagenet_mean.binaryproto
四、下载基于ImageNet训练的AlexNet模型参数文件bvlc_alexnet.caffemodel。
这个可以在https://www.cnblogs.com/leoking01/p/7123154.html 上下载,里面还有很多其他模型
五、修改模型文件train_val_Alexnet.prototxt
1. 修改数据层mean_file路径(imagenet_mean.binaryproto文件的路径),数据存放路径(stanfdog_train_lmdb,stanfdog_val_lmdb文件路径),batch_size大小
2. 由于是基于ImageNet训练的AlexNet训练我们的数据,属于fine-tuning ,ImagNet数据集有1000类,原AlexNet的fc8有1000个输出,我们只有120类,所以这一层的输出个数要改为120,由于这一层参数与原网络不同需要重新训练,将这一层参数的学习效率适当调大,一般调大为原来的10倍,由于之前的模型的参数是通过各层的名称加载的,最后一层的名称需要修改,以告诉机器这一层参数不需要加载,随之后面的层次也要做相应的修改
3. 修改模型求解文件 train_val_Alexnet_solver.prototxt
关于上面的各个参数的说明:
4. 修改模型执行文件train_val_Alexnet.sh
#!/usr/bin/env sh
set -e
TOOLS=./lxq/caffe/build/tools
MODEL=lxq/caffe/models/bvlc_alexnet/stanfdog
#画出网络模型图
./lxq/caffe/python/draw_net.py ./$MODEL/train_val_Alexnet.prototxt ./$MODEL/train_val_Alexnet.png
#开始训练
$TOOLS/caffe train \
--solver=$MODEL/train_val_Alexnet_solver.prototxt \
--weights=$MODEL/bvlc_alexnet.caffemodel 2>&1| tee $MODEL/stanfdog_Alexnet.log $@ #保存屏幕输出到日志
经过漫长的等待,训练完成后生成:
网络模型图train_val_Alexnet.png,网络模型参数快照stanfdog_Alexnet_iter_4000.caffemodel(即我们训练得到的模型),网络训练记录文件stanfdog_Alexnet_iter_4000.solverstate(以外中断时可直接用该文件接着训练),生成的log文件stanfdog_Alexnet.log(保存屏幕输出,方便以后分析和画图)
六、测试
修改stanfdog_deploy.prototxt文件,这个可以对照文件train_val_Alexnet.prototxt修改,主要是对网络模型的修改,去掉卷积层、全连接层的参数设置,还有就是最后一层需要softmax的输出。
编写test.sh执行测试
#!/usr/bin/env sh
set -e
ROOT=lxq/caffe/build/examples
MODEL=lxq/caffe/models/bvlc_alexnet/stanfdog
./$ROOT/cpp_classification/classification.bin \
$MODEL/stanfdog_deploy.prototxt \
$MODEL/stanfdog_Alexnet_iter_4000.caffemodel \
$MODEL/imagenet_mean.binaryproto \
$MODEL/labels.txt \
caffe-new/data/test_Stanford-Dogs/QQ截图20161224093349.jpg
主要是相应的路径一定要写对,这里对文件的命名一定要避免空格,因为看classification.cpp可以知道classification.bin是根据空格分隔参数的,如果文件中的命名有空格,classification.bin会认为输入了多余的参数而不能运行
在命令行输入:
./lxq/caffe/models/bvlc_alexnet/stanfdog/test.sh
即可得到softmax输出的前5个最大的概率所对应的类别
后记
我在这里训练的过程中,发现当迭代2000次的时候,正确率有69.46%,而且正确率变化很缓慢,到4000次的时候正确率是69.9%,这个其实在2000次的时候已经发现loss变化也基本没变化,如果我这个时候需要做调整,该怎么调整呢,如果中断训练的话,由于我的snapshot=4000,如果现在中断,我之前跑得结果岂不是都没有了,这种情况下该怎么办呢