MatConvNet的簡單介紹和手寫識別運用
1.MatConvNet的簡介
MatConvNet是一個實現卷積神經網路(CNN)的MATLAB工具箱,用於計算機視覺應用。 它簡單,高效,並且可以執行和學習最先進的CNN。 許多用於影象分類,分割,人臉識別和文字檢測的預訓練CNN都有提供。
2.配置過程
首先從官網下載matconvnet(點選官網首頁的Download)
接著執行根目錄中matlab資料夾下的vl_compilenn.m。這其實是一個編譯檔案,因為工具箱中有一部分程式碼是用c\c++寫的,所以要把他們編譯成matlab可以執行的mex檔案。
編譯完成後,命令視窗會出現下列資訊,如果全是successfully就可以進行下一步了。理論上不會出現什麼錯誤的,至少我沒有碰到,如果出現錯誤了再具體問題具體分析了。
配置過程就是這麼簡單,接下來我們執行它的一個例子,並給出手寫識別的demo。
注:預設情況下是無GPU版本的,如果需要GPU則需要修改vl_compilenn.m相應的引數。
3.執行minist資料集
在examples目錄下有個minist檔案,這是一個著名的手寫識別專案,也比較基礎,正好適合作為入門。
直接執行裡面的cnn_minist_experiments,就開始訓練資料了,接下來就看到命令列裡面顯示出了當前迭代訓練的結果,看起來很帶感。執行完成後會有一張圖片展現訓練結果:
我們嘗試簡單分析下里面的檔案怎麼寫的, 方便我們以後訓練自己的資料集:
首先先看下,cnn_mnist_experiments.m
[net_bn, info_bn] = cnn_mnist(...
'expDir', 'data/mnist-bnorm', 'batchNormalization', true);
[net_fc, info_fc] = cnn_mnist(...
'expDir', 'data/mnist-baseline', 'batchNormalization', false);
figure(1) ; clf ;
subplot(1,2,1) ;
semilogy([info_fc.val.objective]', 'o-') ; hold all ;
semilogy([info_bn.val.objective]' , '+--') ;
xlabel('Training samples [x 10^3]'); ylabel('energy') ;
grid on ;
h=legend('BSLN', 'BNORM') ;
set(h,'color','none');
title('objective') ;
subplot(1,2,2) ;
plot([info_fc.val.top1err]', 'o-') ; hold all ;
plot([info_fc.val.top5err]', '*-') ;
plot([info_bn.val.top1err]', '+--') ;
plot([info_bn.val.top5err]', 'x--') ;
h=legend('BSLN-val','BSLN-val-5','BNORM-val','BNORM-val-5') ;
grid on ;
xlabel('Training samples [x 10^3]'); ylabel('error') ;
set(h,'color','none') ;
title('error') ;
drawnow ;
我們可以看到其實這個函式就是呼叫了兩次cnn_mnist.m,並傳入不同的引數進行訓練,最後將結果畫出。所以訓練的重點是在cnn_mnist.m,我們接下來看看這個函式。
function [net, info] = cnn_mnist(varargin)
%CNN_MNIST Demonstrates MatConvNet on MNIST
%% 訓練引數的設定,以及建立權值儲存的位置
run(fullfile(fileparts(mfilename('fullpath')),...
'..', '..', 'matlab', 'vl_setupnn.m')) ;%檢查是否配置成功,如果沒有配置好則會彈出警告,配置完成後可以把這句話刪了。
opts.batchNormalization = false ;
opts.network = [] ;
opts.networkType = 'simplenn' ;
[opts, varargin] = vl_argparse(opts, varargin) ;
sfx = opts.networkType ;
if opts.batchNormalization, sfx = [sfx '-bnorm'] ; end
opts.expDir = fullfile(vl_rootnn, 'data', ['mnist-baseline-' sfx]) ;
[opts, varargin] = vl_argparse(opts, varargin) ;
opts.dataDir = fullfile(vl_rootnn, 'data', 'mnist') ;
opts.imdbPath = fullfile(opts.expDir, 'imdb.mat');
opts.train = struct() ;
opts = vl_argparse(opts, varargin) ;
if ~isfield(opts.train, 'gpus'), opts.train.gpus = []; end;
% --------------------------------------------------------------------
% Prepare data
% --------------------------------------------------------------------
%% 初始化神經網路
if isempty(opts.network)
net = cnn_mnist_init('batchNormalization', opts.batchNormalization, ...
'networkType', opts.networkType) ;
else
net = opts.network ;
opts.network = [] ;
end
%% 下載資料
if exist(opts.imdbPath, 'file')
imdb = load(opts.imdbPath) ;
else
imdb = getMnistImdb(opts) ;
mkdir(opts.expDir) ;
save(opts.imdbPath, '-struct', 'imdb') ;
end
net.meta.classes.name = arrayfun(@(x)sprintf('%d',x),1:10,'UniformOutput',false) ;
% --------------------------------------------------------------------
% Train
% --------------------------------------------------------------------
switch opts.networkType
case 'simplenn', trainfn = @cnn_train ;
case 'dagnn', trainfn = @cnn_train_dag ;
end
%% 開始訓練,並將結果儲存
[net, info] = trainfn(net, imdb, getBatch(opts), ...
'expDir', opts.expDir, ...
net.meta.trainOpts, ...
opts.train, ...
'val', find(imdb.images.set == 3)) ;
% --------------------------------------------------------------------
function fn = getBatch(opts)
% --------------------------------------------------------------------
switch lower(opts.networkType)
case 'simplenn'
fn = @(x,y) getSimpleNNBatch(x,y) ;
case 'dagnn'
bopts = struct('numGpus', numel(opts.train.gpus)) ;
fn = @(x,y) getDagNNBatch(bopts,x,y) ;
end
% --------------------------------------------------------------------
function [images, labels] = getSimpleNNBatch(imdb, batch)
% --------------------------------------------------------------------
images = imdb.images.data(:,:,:,batch) ;
labels = imdb.images.labels(1,batch) ;
% --------------------------------------------------------------------
function inputs = getDagNNBatch(opts, imdb, batch)
% --------------------------------------------------------------------
images = imdb.images.data(:,:,:,batch) ;
labels = imdb.images.labels(1,batch) ;
if opts.numGpus > 0
images = gpuArray(images) ;
end
inputs = {'input', images, 'label', labels} ;
% --------------------------------------------------------------------
function imdb = getMnistImdb(opts)
% --------------------------------------------------------------------
% Preapre the imdb structure, returns image data with mean image subtracted
files = {'train-images-idx3-ubyte', ...
'train-labels-idx1-ubyte', ...
't10k-images-idx3-ubyte', ...
't10k-labels-idx1-ubyte'} ;
if ~exist(opts.dataDir, 'dir')
mkdir(opts.dataDir) ;
end
for i=1:4
if ~exist(fullfile(opts.dataDir, files{i}), 'file')
url = sprintf('http://yann.lecun.com/exdb/mnist/%s.gz',files{i}) ;
fprintf('downloading %s\n', url) ;
gunzip(url, opts.dataDir) ;
end
end
f=fopen(fullfile(opts.dataDir, 'train-images-idx3-ubyte'),'r') ;
x1=fread(f,inf,'uint8');
fclose(f) ;
x1=permute(reshape(x1(17:end),28,28,60e3),[2 1 3]) ;
f=fopen(fullfile(opts.dataDir, 't10k-images-idx3-ubyte'),'r') ;
x2=fread(f,inf,'uint8');
fclose(f) ;
x2=permute(reshape(x2(17:end),28,28,10e3),[2 1 3]) ;
f=fopen(fullfile(opts.dataDir, 'train-labels-idx1-ubyte'),'r') ;
y1=fread(f,inf,'uint8');
fclose(f) ;
y1=double(y1(9:end)')+1 ;
f=fopen(fullfile(opts.dataDir, 't10k-labels-idx1-ubyte'),'r') ;
y2=fread(f,inf,'uint8');
fclose(f) ;
y2=double(y2(9:end)')+1 ;
set = [ones(1,numel(y1)) 3*ones(1,numel(y2))];
data = single(reshape(cat(3, x1, x2),28,28,1,[]));
dataMean = mean(data(:,:,:,set == 1), 4);
data = bsxfun(@minus, data, dataMean) ;
imdb.images.data = data ;
imdb.images.data_mean = dataMean;
imdb.images.labels = cat(2, y1, y2) ;
imdb.images.set = set ;
imdb.meta.sets = {'train', 'val', 'test'} ;
imdb.meta.classes = arrayfun(@(x)sprintf('%d',x),0:9,'uniformoutput',false) ;
我們這個函式大體分成了四部分:
1.訓練引數的設定,以及建立權值儲存的位置
根據傳入的引數確定部分網路引數,接著在minist資料夾下建立了data資料夾,儲存網路訓練後的權重,以及樣本矩陣。
2.下載資料
判斷你是否下載了minist資料集,如果沒有則呼叫getMnistImdb這個子函式下載,並生成樣本矩陣imdb(樣本進行了減均值處理)。
3.初始化神經網路
初始化操作是在cnn_mnist_init中進行的,如果要修改網路引數可以對這個函式中進行修改。
4.訓練神經網路
訓練神經網路,並將每次迭代的權值進行儲存。
接下來看看cnn_mnist_init.m函式
function net = cnn_mnist_init(varargin)
% CNN_MNIST_LENET Initialize a CNN similar for MNIST
opts.batchNormalization = true ;
opts.networkType = 'simplenn' ;
opts = vl_argparse(opts, varargin) ;
rng('default');
rng(0) ;
%網路結構的定義
f=1/100 ;
net.layers = {} ;
net.layers{end+1} = struct('type', 'conv', ...
'weights', {{f*randn(5,5,1,20, 'single'), zeros(1, 20, 'single')}}, ...
'stride', 1, ...
'pad', 0) ;
net.layers{end+1} = struct('type', 'pool', ...
'method', 'max', ...
'pool', [2 2], ...
'stride', 2, ...
'pad', 0) ;
net.layers{end+1} = struct('type', 'conv', ...
'weights', {{f*randn(5,5,20,50, 'single'),zeros(1,50,'single')}}, ...
'stride', 1, ...
'pad', 0) ;
net.layers{end+1} = struct('type', 'pool', ...
'method', 'max', ...
'pool', [2 2], ...
'stride', 2, ...
'pad', 0) ;
net.layers{end+1} = struct('type', 'conv', ...
'weights', {{f*randn(4,4,50,500, 'single'), zeros(1,500,'single')}}, ...
'stride', 1, ...
'pad', 0) ;
net.layers{end+1} = struct('type', 'relu') ;
net.layers{end+1} = struct('type', 'conv', ...
'weights', {{f*randn(1,1,500,10, 'single'), zeros(1,10,'single')}}, ...
'stride', 1, ...
'pad', 0) ;
net.layers{end+1} = struct('type', 'softmaxloss') ;
% optionally switch to batch normalization
%是否加入batchNorm層,一般在卷積層後加
if opts.batchNormalization
net = insertBnorm(net, 1) ;
net = insertBnorm(net, 4) ;
net = insertBnorm(net, 7) ;
end
% Meta parameters
net.meta.inputSize = [28 28 1] ;%輸入影象的大小
net.meta.trainOpts.learningRate = 0.001 ;%學習率
net.meta.trainOpts.numEpochs = 20 ;%迭代次數
net.meta.trainOpts.batchSize = 100 ;%批大小,即一次梯度下降所用的樣本數
% Fill in defaul values
net = vl_simplenn_tidy(net) ;
% Switch to DagNN if requested
switch lower(opts.networkType)
case 'simplenn'
% done
case 'dagnn'
net = dagnn.DagNN.fromSimpleNN(net, 'canonicalNames', true) ;
net.addLayer('top1err', dagnn.Loss('loss', 'classerror'), ...
{'prediction', 'label'}, 'error') ;
net.addLayer('top5err', dagnn.Loss('loss', 'topkerror', ...
'opts', {'topk', 5}), {'prediction', 'label'}, 'top5err') ;
otherwise
assert(false) ;
end
% --------------------------------------------------------------------
function net = insertBnorm(net, l)
% --------------------------------------------------------------------
assert(isfield(net.layers{l}, 'weights'));
ndim = size(net.layers{l}.weights{1}, 4);
layer = struct('type', 'bnorm', ...
'weights', {{ones(ndim, 1, 'single'), zeros(ndim, 1, 'single')}}, ...
'learningRate', [1 1 0.05], ...
'weightDecay', [0 0]) ;
net.layers{l}.weights{2} = [] ; % eliminate bias in previous conv layer
net.layers = horzcat(net.layers(1:l), layer, net.layers(l+1:end)) ;
這個函式定義了網路的結構,初始化網路的引數。根據函式的初始化,這個網路並不完全和Lenet一樣。
所以對於初學者來說,如果想用這個工具箱對自己的資料進行訓練,只需要以minist這個例子為基礎,然後修改由訓練和測試樣本生成的矩陣imdb,以及cnn_mnist_init.m中的引數,就可以簡單、高效地得到結果。
4.手寫識別demo
接下來,我們根據訓練得到的權值,來生成一個手寫識別的GUI。介面與我之前寫過的KNN沒有什麼變化,演算法由KNN變成了CNN。效果如下:
可以看到,演算法具有較強的魯棒性,體驗要遠遠好於之前的寫的KNN識別,原始碼下載。