1. 程式人生 > >MatConvNet的簡單介紹和手寫識別運用

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識別,原始碼下載