详解Matconvnet使用imagenet模型训练自己的数据集

时间:2024-03-16 19:54:35

上节讨论过如何使一个简单的cnn网络训练mnist数据集,该节介绍复杂并且使用广泛的使用imagenet网络的预训练模型训练自己的数据集。

Ok首先是自己的数据集了。Matconvnet中训练imagenet的数据集的准备不像caffe这些工具箱弄得那么好,弄个train文件夹,test文件夹,以及两个txt索引就好了,感觉很不人性。后面我将会将其输入改为这种人性的类型输入格式。

但是其类别索引是从0开始的,这在matlab中是不符合的,所以我将其改成从1开始的。同时添加了一个类class标签的txt,改完的

下载完打开这个文件夹看到:
详解Matconvnet使用imagenet模型训练自己的数据集

其中train就是训练所用到的所有图片,test为测试所有图片,train_label为对应图片的名字以及跟随的类标签(从1开始),打开txt可以看到为:

详解Matconvnet使用imagenet模型训练自己的数据集

这种格式的txt相信应该很容易从你自己的数据集中弄到。依次类推,test.txt中存放的是test文件夹所有图片的名字以及其类别。

Classind 就是每一类表示的分类的名字。
详解Matconvnet使用imagenet模型训练自己的数据集

数据准备好了,放在哪呢?我们在Matconvnet的工具箱目录下新建一个文件夹为data,然后将这个数据集放进去,如下:

详解Matconvnet使用imagenet模型训练自己的数据集

我们是在训练好的model上继续训练,所以需要一个model,再在这文件夹下建立一个models文件夹,然后把imagenet-vgg-f.mat放入到models里面。这里我们使用的是vgg-f的model,这个model在前两节说到了,自己去下载。

接着就是网络训练了。再建立一个文件夹train,可以编写函数了。

首先是主函数:
这里复制一下examples中的imagenet里面的一个主函数cnn_dicnn,然后修改一下里面的路径,程序为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
function [net, info] = cnn_dicnn(varargin)
%CNN_DICNN Demonstrates fine-tuning a pre-trained CNN on imagenet dataset
 
run(fullfile(fileparts(mfilename('fullpath')), ...
  '..', 'matconvnet', 'matlab', 'vl_setupnn.m')) ;
% 修改读入文件夹的路径
opts.dataDir = fullfile('data','image') ;
opts.expDir  = fullfile('exp', 'image') ;
% 导入预训练的model
opts.modelPath = fullfile('models','imagenet-vgg-f.mat');
[opts, varargin] = vl_argparse(opts, varargin) ;
 
opts.numFetchThreads = 12 ;
 
opts.lite = false ;
opts.imdbPath = fullfile(opts.expDir, 'imdb.mat');
 
opts.train = struct() ;
opts.train.gpus = [];
opts.train.batchSize = 8 ;
opts.train.numSubBatches = 4 ;
opts.train.learningRate = 1e-4 * [ones(1,10), 0.1*ones(1,5)];
 
opts = vl_argparse(opts, varargin) ;
if ~isfield(opts.train, 'gpus'), opts.train.gpus = []; end;
 
% -------------------------------------------------------------------------
%                                                             Prepare model
% -------------------------------------------------------------------------
net = load(opts.modelPath);
% 修改一下这个model
net = prepareDINet(net,opts);
% -------------------------------------------------------------------------
%                                                              Prepare data
% -------------------------------------------------------------------------
% 准备数据格式
if exist(opts.imdbPath,'file')
  imdb = load(opts.imdbPath) ;
else
  imdb = cnn_image_setup_data('dataDir', opts.dataDir, 'lite', opts.lite) ;
  mkdir(opts.expDir) ;
  save(opts.imdbPath, '-struct', 'imdb') ;
end
 
imdb.images.set = imdb.images.sets;
 
% Set the class names in the network
net.meta.classes.name = imdb.classes.name ;
net.meta.classes.description = imdb.classes.name ;
 
% % 求训练集的均值
imageStatsPath = fullfile(opts.expDir, 'imageStats.mat') ;
if exist(imageStatsPath)
  load(imageStatsPath, 'averageImage') ;
else
    averageImage = getImageStats(opts, net.meta, imdb) ;
    save(imageStatsPath, 'averageImage') ;
end
% % 用新的均值改变均值
net.meta.normalization.averageImage = averageImage;
% -------------------------------------------------------------------------
%                                                                     Learn
% -------------------------------------------------------------------------
% 索引训练集==1  和测试集==3
opts.train.train = find(imdb.images.set==1) ;
opts.train.val = find(imdb.images.set==3) ;
% 训练
[net, info] = cnn_train_dag(net, imdb, getBatchFn(opts, net.meta), ...
                      'expDir', opts.expDir, ...
                      opts.train) ;
 
% -------------------------------------------------------------------------
%                                                                    Deploy
% -------------------------------------------------------------------------
% 保存训练完的网络
net = cnn_imagenet_deploy(net) ;
modelPath = fullfile(opts.expDir, 'net-deployed.mat');
 
net_ = net.saveobj() ;
save(modelPath, '-struct', 'net_') ;
clear net_ ;
 
% -------------------------------------------------------------------------
function fn = getBatchFn(opts, meta)
% -------------------------------------------------------------------------
useGpu = numel(opts.train.gpus) > 0 ;
 
bopts.numThreads = opts.numFetchThreads ;
bopts.imageSize = meta.normalization.imageSize ;
bopts.border = meta.normalization.border ;
% bopts.averageImage = [];
bopts.averageImage = meta.normalization.averageImage ;
% bopts.rgbVariance = meta.augmentation.rgbVariance ;
% bopts.transformation = meta.augmentation.transformation ;
 
fn = @(x,y) getDagNNBatch(bopts,useGpu,x,y) ;
 
 
% -------------------------------------------------------------------------
function inputs = getDagNNBatch(opts, useGpu, imdb, batch)
% -------------------------------------------------------------------------
% 判断读入数据为训练还是测试
for i = 1:length(batch)
    if imdb.images.set(batch(i)) == 1 %1为训练索引文件夹
        images(i) = strcat([imdb.imageDir.train filesep] , imdb.images.name(batch(i)));
    else
        images(i) = strcat([imdb.imageDir.test filesep] , imdb.images.name(batch(i)));
    end
end;
isVal = ~isempty(batch) && imdb.images.set(batch(1)) ~= 1 ;
 
if ~isVal
  % training
  im = cnn_imagenet_get_batch(images, opts, ...
                              'prefetch', nargout == 0) ;
else
  % validation: disable data augmentation
  im = cnn_imagenet_get_batch(images, opts, ...
                              'prefetch', nargout == 0, ...
                              'transformation', 'none') ;
end
 
if nargout > 0
  if useGpu
    im = gpuArray(im) ;
  end
  labels = imdb.images.label(batch) ;
  inputs = {'input', im, 'label', labels} ;
end
 
% 求训练样本的均值
% -------------------------------------------------------------------------
function averageImage = getImageStats(opts, meta, imdb)
% -------------------------------------------------------------------------
train = find(imdb.images.set == 1) ;
batch = 1:length(train);
fn = getBatchFn(opts, meta) ;
train = train(1: 100: end);
avg = {};
for i = 1:length(train)
    temp = fn(imdb, batch(train(i):train(i)+99)) ;
    temp = temp{2};
    avg{end+1} = mean(temp, 4) ;
end
 
averageImage = mean(cat(4,avg{:}),4) ;
% 将GPU格式的转化为cpu格式的保存起来(如果有用GPU)
averageImage = gather(averageImage);

这里涉及到了几个函数需要自己写,其他的都有调用的。第一个函数就是数据格式的准备。Matconvnet训练是有自己的格式要求的,一般都是生成imdb结构体保存起来。下面是如何将我们这种格式的训练样本集生成要求的格式,建立一个m函数cnn_image_setup_data代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
function imdb = cnn_image_setup_data(varargin)
 
opts.dataDir = fullfile('data','image') ;
opts.lite = false ;
opts = vl_argparse(opts, varargin) ;
 
% ------------------------------------------------------------------------
%                                                  Load categories metadata
% -------------------------------------------------------------------------
 
metaPath = fullfile(opts.dataDir, 'classInd.txt') ;
 
fprintf('using metadata %s\n', metaPath) ;
tmp = importdata(metaPath);
nCls = numel(tmp);
% 判断类别与设定的是否一样 10为样本的类别总数(自己的数据集需要修改)
if nCls ~= 10
  error('Wrong meta file %s',metaPath);
end
% 将名字分离出来
cats = cell(1,nCls);
for i=1:numel(tmp)
  t = strsplit(tmp{i});
  cats{i} = t{2};
end
% 数据集文件夹选择
imdb.classes.name = cats ;
imdb.imageDir.train = fullfile(opts.dataDir, 'train') ;
imdb.imageDir.test = fullfile(opts.dataDir, 'test') ;
 
%% -----------------------------------------------------------------
%                                              load image names and labels
% -------------------------------------------------------------------------
 
name = {};
labels = {} ;
imdb.images.sets = [] ;
%%
fprintf('searching training images ...\n') ;
% 导入训练类别标签
train_label_path = fullfile(opts.dataDir, 'train_label.txt') ;
train_label_temp = importdata(train_label_path);
temp_l = train_label_temp.data;
for i=1:numel(temp_l)
    train_label{i} = temp_l(i);
end
if length(train_label) ~= length(dir(fullfile(imdb.imageDir.train, '*.jpg')))
    error('training data is not equal to its label!!!');
end
 
i = 1;
for d = dir(fullfile(imdb.imageDir.train, '*.jpg'))'
    name{end+1} = d.name;
    labels{end+1} = train_label{i} ;
    if mod(numel(name), 10) == 0, fprintf('.') ; end
    if mod(numel(name), 500) == 0, fprintf('\n') ; end
    imdb.images.sets(end+1) = 1;%train
    i = i+1;
end
%%
fprintf('searching testing images ...\n') ;
% 导入测试类别标签
test_label_path = fullfile(opts.dataDir, 'test_label.txt') ;
test_label_temp = importdata(test_label_path);
temp_l = test_label_temp.data;
for i=1:numel(temp_l)
    test_label{i} = temp_l(i);
end
if length(test_label) ~= length(dir(fullfile(imdb.imageDir.test, '*.jpg')))
    error('testing data is not equal to its label!!!');
end
i = 1;
for d = dir(fullfile(imdb.imageDir.test, '*.jpg'))'
    name{end+1} = d.name;
    labels{end+1} = test_label{i} ;
    if mod(numel(name), 10) == 0, fprintf('.') ; end
    if mod(numel(name), 500) == 0, fprintf('\n') ; end
    imdb.images.sets(end+1) = 3;%test
    i = i+1;
end
%%
labels = horzcat(labels{:}) ;
imdb.images.id = 1:numel(name) ;
imdb.images.name = name ;
imdb.images.label = labels ;

这个函数里面有几点需要注意的是,类别总数需要视自己的数据集修改。

之后是对导入的预训练model进行一点处理,建立一个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
% -------------------------------------------------------------------------
function net = prepareDINet(net,opts)
% -------------------------------------------------------------------------
% replace fc8
fc8l = cellfun(@(a) strcmp(a.name, 'fc8'), net.layers)==1;
 
%%  note: 下面这个是类别数,一定要和自己的类别数吻合(这里为10类)
nCls = 10;
sizeW = size(net.layers{fc8l}.weights{1});
% 将权重初始化
if sizeW(4)~=nCls
  net.layers{fc8l}.weights = {zeros(sizeW(1),sizeW(2),sizeW(3),nCls,'single'), ...
    zeros(1, nCls, 'single')};
end
 
% change loss  添加一个loss层用于训练
net.layers{end} = struct('name','loss', 'type','softmaxloss') ;
 
% convert to dagnn dagnn网络,还需要添加下面这几层才能训练
net = dagnn.DagNN.fromSimpleNN(net, 'canonicalNames', true) ;
 
net.addLayer('top1err', dagnn.Loss('loss', 'classerror'), ...
    {'prediction','label'}, 'top1err') ;
net.addLayer('top5err', dagnn.Loss('loss', 'topkerror', ...
    'opts', {'topK',5}), ...
{'prediction','label'}, 'top5err') ;

这里有一个重要的参数就是你的类别数nCls,还是是多少类就修改多少。

上面的几个函数都放在train文件夹下面就可以了,如下:

详解Matconvnet使用imagenet模型训练自己的数据集

这里还有许多参数需要说明,一个重要的参数是迭代次数,以及是否选择使用gpu。
对于迭代次数,在cnn_dicnn函数中的训练函数cnn_train_dag
函数中,打开如下:
详解Matconvnet使用imagenet模型训练自己的数据集

在训练的时候一般默认的迭代次数都会很多,还好每一代训练的结果都会保存,即使中断了,再运行的时候,程序会接着上一次的代数接着训练的。这样的一个好处是,假如你发现结果好的差不多了,又不想训练了,可以终止程序,把这个迭代次数改到目前的代数,那么下一次程序读入后发现到训练代数了。就不会在训练了,直接结束,执行下面的保存最终网络的程序过程,这一点操作还是很重要的很好用的。

对于是否需要选择gpu,这里有一个参数,同时最好也把主函数里面的gpu=[]也修改一样的,[]中填的是你的电脑gpu索引号码,比如gpu=[1],就是使用gpu 1来进行训练。

Ok做完这些操作后,就可以训练了,如果选择gpu出错了,先改成cpu试一试,要是cpu没有出错,那么就是gpu没装好。否则就是编译出错了。我实验的是我的gpu版本的matconvnet可以同时工作在cpu和GPU模式。

下面就是训练了。这个训练相对来说不算长,训练100代也就1个多小时,数据库小的缘故。正确训练的截图:
详解Matconvnet使用imagenet模型训练自己的数据集

经过一段时间训练,到达指定代数后就会停止,想让它早点停止就按照上面一个方法。停止完后,会在\matconvnet_test\exp\image文件夹下面生成一个net-deployed.mat最终的model,像我让他训练了106代就强制停止了,然后把echo迭代次数从300改成106,再运行主函数,之后就可以得到net-deployed.mat了,这里把106之间的训练结果都删除后,如下:

详解Matconvnet使用imagenet模型训练自己的数据集

有了这个model,我们就可以测试了,这个model就是我们训练的最终model。

下面我们来测试,同样对这个数据集中的测试样本进行测试:写一个test_accuracy脚本,添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
clc
clear
% 导入model
net1 = dagnn.DagNN.loadobj(load('D:\myself\matlab\matlab_documents\matconvnet_test\exp\image\net-deployed.mat')) ;
net1.mode = 'test' ;
% 导入准备数据
imdb = load('D:\myself\matlab\matlab_documents\matconvnet_test\exp\image\imdb.mat') ;
 
opts.dataDir = fullfile('data','image') ;
opts.expDir  = fullfile('exp', 'image') ;
% 找到训练与测试集
opts.train.train = find(imdb.images.sets==1) ;
opts.train.val = find(imdb.images.sets==3) ;
 
for i = 1:length(opts.train.val)
    i
    index = opts.train.val(i);
    label = imdb.images.label(index);
    % 读取测试的样本
    im_ =  imread(fullfile(imdb.imageDir.test,imdb.images.name{index}));
    im_ = single(im_);
    im_ = imresize(im_, net1.meta.normalization.imageSize(1:2)) ;
    im_ = bsxfun(@minus, im_, net1.meta.normalization.averageImage) ;
    % 测试
    net1.eval({'input',im_}) ;
    scores = net1.vars(net1.getVarIndex('prob')).value ;
    scores = squeeze(gather(scores)) ;
 
    [bestScore, best] = max(scores) ;
    truth(i) = label;
    pre(i) = best;
end
% 计算准确率
accurcy = length(find(pre==truth))/length(truth);
disp(['accurcy = ',num2str(accurcy*100),'%']);

这样得到结果为accurcy = 89%。

如果大家看这个数据集会发现,这个简易的数据集还是很复杂的,图片都是原始搜集者在淘宝上找的,能有这样的准确率其实还算可以。

至此,使用现存的imagenet网络训练自己的数据库就到此结束了。剩下的只是修改准备自己的数据库即可了。特别需要注意的是,文件夹的名称一定要按我给的来,或者自己修改了要在程序里面去修改。其次是类别数一定记得改成自己的。注意这几点,那么使用大型的cnn训练自己的数据库就很容易了。