1. 程式人生 > >Deep Learning學習 之 CNN程式碼解析(MATLAB)

Deep Learning學習 之 CNN程式碼解析(MATLAB)

MATLAB實現CNN一般會用到deepLearnToolbox-master。但是根據Git上面的說明,現在已經停止更新了,而且有很多功能也不太能夠支援,具體的請大家自習看一看Git中的README。

deepLearnToolbox-master是一個深度學習matlab包,裡面含有很多機器學習演算法,如卷積神經網路CNN,深度信念網路DBN,自動編碼AutoEncoder(堆疊SAE,卷積CAE)的作者是 Rasmus Berg Palm ([email protected])
程式碼下載:https://github.com/rasmusbergpalm/DeepLearnToolbox

這裡寫圖片描述

這裡我們介紹deepLearnToolbox-master中的CNN部分。

函式

這裡寫圖片描述

呼叫關係為:

這裡寫圖片描述

該模型使用了mnist的數字mnist_uint8.mat作為訓練樣本,作為CNN的一個使用樣例,每個樣本特徵為一個28×28=

網路結構為:

這裡寫圖片描述

Test_example_CNN

Test_example_CNN:

  • 1設定CNN的基本引數規格,如卷積、降取樣層的數量,卷積核的大小、降取樣的降幅
  • 2 cnnsetup函式 初始化卷積核、偏置等
  • 3 cnntrain函式 訓練cnn,把訓練資料分成batch,然後呼叫
    • 3.1cnnff 完成訓練的前向過程
    • 3.2 cnnbp計算並傳遞神經網路的error,並計算梯度(權重的修改量)
    • 3.3 cnnapplygrads 把計算出來的梯度加到原始模型上去
  • 4 cnntest 函式,測試當前模型的準確率

該模型採用的資料為mnist_uint8.mat,含有70000個手寫數字樣本其中60000作為訓練樣本,10000作為測試樣本。
把資料轉成相應的格式,並歸一化。

load mnist_uint8;
train_x = double(reshape(traub.x',28,28,60000))/255;
test_x = double(reshape(test_x',28,28,10000))/255;
train_y = double
(train_y'); test_y = double(test_y');

設定網路結構及訓練引數

%% ex1 Train a 6c-2s-12c-2s Convolutional neural network
%% will run 1 epoch in about 200 second and get around 11% error
%% with 100 epochs you' will get around 1.2% error

rand('state',0);
cnn.layers = {
    struct('type','i') %input layer
    struct('type','c','outputmaps',6,'kernelsize',5) % convolution layer
    struct('type','s','scale',2) %sub sampling layer
    struct('type','c','outputmaps',12,'kernelsize',5) % convolutional layer
    struct('type','s','scale',2) % sub sampling layer
%% 訓練選項,alpha學習效率(不用),batchsiaze批訓練總樣本的數量,numepoches迭代次數
opts.alpha = 1;
opts.batchsize = 50;
opts.numepochs = 1;

初始化網路,對資料進行批訓練,驗證模型準確率。

cnn = cmmsetup(cnn, train_x, train_y);
cnn = cnntrain(cnn, train_x, train_y, opts);
[er, bad] = cnntest(cnn, test_x, test_y);

繪製均方誤差曲線

%plot mean squared error
figure; plot(cnn.rL);

Cnnsetup.m

該函式你用於初始化CNN的引數。
設定各層的mapsize大小,
初始化卷積層的卷積核、bias
尾部單層感知機的引數設定

bias統一初始化為0

權重設定為:random(-1,1)/6+

對於卷積核權重,輸入輸出為fan_in, fan_out
fan_out = net.layers{l}.outputmaps * net.layers{l}.kernelsize ^ 2;

%卷積核初始化,1層卷積為1*6個卷積核,2層卷積一共6*12=72個卷積核。對於每個卷積輸出featuremap,
%fan_in = 表示該層的一個輸出map,所對應的所有卷積核,包含的神經元的總數。1*25,6*25
fan_in = numInputmaps * net.layers{l}.kernelsize ^ 2;
fin =1*25 or 6*25
fout=1*6*25 or 6*12*25

net.layers{l}.k{i}{j} = (rand(net.layers{l}.kernelsize) - 0.5) * 2 * sqrt(6 / (fan_in + fan_out));

卷積降取樣的引數初始化

numInputmaps = 1;
mapsize = size(squeeze(x(:, :, 1))); %[28, 28];一個行向量。x(:, :, 1)是一個訓練樣本。
% 下面通過傳入net這個結構體來逐層構建CNN網路
for l = 1 : numel(net.layers)
    if strcmp(net.layers{l}.type, 's')%降取樣層sub sampling
    %降取樣圖的大小mapsize由28*28變為14*14。net.layers{l}.scale = 2。
        mapsize = mapsize / net.layers{l}.scale;
        for j = 1 : numInputmaps % 一個降取樣層的所有輸入map,b初始化為0
            net.layers{l}.b{j} = 0;
        end
    end
    if strcmp(net.layers{l}.type, 'c')%如果是卷積層
        %得到卷積層的featuremap的size,卷積層fm的大小 為 上一層大小 - 卷積核大小 + 1(步長為1的情況)
        mapsize = mapsize - net.layers{l}.kernelsize + 1;
        %fan_out該層的所有連線的數量 = 卷積核數 * 卷積核size = 6*(5*5),12*(5*5)
        fan_out = net.layers{l}.outputmaps * net.layers{l}.kernelsioze ^ 2;
        %卷積核初始化,1層卷積為1*6個卷積核,2層卷積一共有6*12=72個卷積核。
        for j = 1 : net.layers{l}.outputmaps % output map
            %輸入做卷積
            % fan_in = 本層的一個輸出map所對應的所有卷積核,包含的權值的總數 = 1*25,6*25
            fan_in = numInputmaps * net,layers{l}.kernelsize ^ 2;
            for i = 1 : numInputmaps % input map
                %卷積核的初始化生成一個5*5的卷積核,值為-1~1之間的隨機數
                %再乘以sqrt(6/(7*25)),sqrt(6/(18*25))
                net.layers{l}.k{i}{j} = (rand(net.layers{l}.kernelsize) - 0.5 * 2 * sqrt(6 / (fan_in, fan_out)))
            end
            %偏置初始化為0,每個輸出map只有一個bias,並非每個filter一個bias
            net.layers{l}.b{j} = 0;
        end
        numInputmaps = net.layers{l}.outputmaps;
    end
end

尾部單層感知機的引數(權重和偏量)設定

%%尾部單層感知機(全連線層)的引數設定
fvnum = prod(mapsize) * numInputmaps;%prod函式用於求陣列元素的乘積,fvnum = 4*4*12 = 192,是全連線層的輸入數量
onum = size(y, 1);%輸出節點的數量
net.ffb = zeros(onum, 1);
net.ffW = (rand(onum, fvnum) - 0.5) * 2 * sqrt(6 / (onum + fvnum));

cnntrain.m

該函式用於訓練CNN。
生成隨機序列,每次選取一個batch(50)個樣本進行訓練。
批訓練:計算50個隨機樣本的梯度,求和之後一次性更新到模型權重中。
在批訓練過程中呼叫:
Cnnff.m 完成前向過程
Cnnbp.m 完成誤差傳導和梯度計算過程
Cnnapplygrads.m 把計算出來的梯度加到原始模型上去

%net為網路,x是資料,y為訓練目標,opts (options)為訓練引數 
function net = cnntrain(net, x, y, opts)
    %m為圖片祥本的數量,size(x) = 28*28*60000
    m = size(x,3); 
    %batchsize為批訓練時,一批所含圖片樣本數 
    nunbatches = m / opts. batchsize;%分批洲練,得到訓練批次 
    net.rl = [];%rL是最小均方誤差的平滑序列,繪圖要用 
    for i = 1 : opts.numepochs %訓練迭代 
    %顯示訓練到第幾個epoch,一共多少個epoch
    disp(['epoch' num2str(i) '/' num2str(opts.numepochs)]); 
    %Matlab自帶函式randperm(n)產生1到n的整數的無重複的隨機排列,可以得到無重複的隨機數。 
    %生成m(圖片數量)1~n整數的隨機無重複排列,用於打亂訓練順序 . 
    kk = randperm(n); 
    for 1 = 1 : numbatches%訓練每個batch 
        %得到訓練訊號,一個樣本是一層x(:, :, sampleOrder),每次訓練,取50個樣本. 
        batch_x = x(:, :, kk((l-1) * opts.batchsize + 1 : l * opts.batchsize)); 
        %教師訊號,一個樣本是一列
        batch_y = y(:, kk((l-1) * opts.batchsize + 1 : l * opts.batchsize)); 
        %NN訊號前傳導過程
        net = cnnff (net, batch_x);
        %計算誤差並反向傳導,計算梯度
        net = cnnbp (net, batch_y); 
        %應用梯度,模型更新
        net = cnnapplygrads(net, opts); 
        %net.L為模型的costFunction,即最小均方誤差mse
        %net.rL是最小均方誤差的平滑序列
        if isempty(net.rL)  
            net.rL(1) = net.L;
        end
        net.rL(end+1) = 0.99 * net.rL(end) + 0.01 * net.L; 
    end
end 

cnnff.m

取得CNN的輸入

numLayers = numel(net.layers);
net.layers{1}.a{1} = x;%a是輸入map,時一個[28, 28, 50]的矩陣(具體情況具體定)
numInputmaps = 1;

兩次卷積核降取樣層處理

for l = 2 : numLayers %for each layer 
    if strcmp(net.layers{l}.type, 'c')
        for j = 1net.layers{l}.outputmaps %for each output map
        % z = zeros([28,28,50]-[4,4,0]) = zeros([24,24,50])  
        z = zeros(size(net.layers{l一1}.a{1}) - [net.layers{l}.kernelsize — 1, net.layers{l}.kernelsize — 1, 0]);
        for i = 1 : numlnputmaps% for each input map 
            z = z + convn(net.layers{l-1}.a{i}, net.layers{l}.k{i}{j}, 'valid');
        end 
        %加上bias並sigmo出來理 
        net.layers{l}.k{i}{j} = sigm(z + net.layers{l}.b{j});
    end

    %下層輸入feacureMap的裡等於本層輸出的feateMap的數量
    numlnputmaps = net.layers{l}.outputmaps;
    elseif strcmp(net.layers{l}.type,'s') 
        %down sample
        for j = 1 : numlnputmaps 
            %這子取樣是怎麼做到的?卷積一個[0.25,0,25;0.25,0,5]的卷積核,然後降取樣。這裡有重複計算
            z = convon(net.layers{l-1}).a{j}, ones(net.layers{l}.scale) / (net.layers{l}.scale ^ 2),'valid');
            %這裡設定scale長度的步長,窗移一》scale,但是這裡有計算浪費
            net.layers{l}·a{j} = z(1 : net.layers{1}.scale : end, 1 : net.layers{l}.scale : end, :);
        end
    end
end

尾部單層感知機的資料處理

需要把subFeatureMap2連線成為一個(4*4)*12=192的向量,但是由於採用了50樣本批訓練的方法,subFeatureMap2被拼合成為一個192*50的特徵向量fv;
Fv作為單層感知機的輸入,全連線的方式得到輸出層

%尾部單層感知機的資料處理。 concatenate all end layer feature maps into vector
net.fv = [];
for j = 1 : numel(net.layers{numLayers}.a) %fv每次拼合入subFeaturemap2[j],[包含50個樣本]
    sa = size(net.layers{numLayers}.a{j});%size of a  = sa = [4, 4, 50];得到Sfm2的一個輸入圖的大小
    %reshape(A,m,n);
    %把矩陣A改變形狀,程式設計m行n列。(元素個數不變,原矩陣按列排成一隊,再按行排成若干隊)
    %把所有的Sfm2拼合成為一個列向量fv,[net.fv; reshape(net.layers{numLayers}.a{j}, 4*4, 50)];
    net.fv = [net.fv; reshape(net.layers{numLayers}.a{j}, sa(1) * sa(2), sa(3))]
    %最後的fv是一個[16*12*50]的矩陣
end
%feedforvard into output perceptrons 
%net.ffW是[10,192]的權重矩陣
%net.ffW * net.fv是一個[10,50]的矩陣 
%repmat(net.ffb, 1, size(net.fv, 2)把 bias複製成50份排開
net.o = sigm(net.ffW * net.fv + repmat(net.ffb, 1, size(net.fv, 2)));

cnnbp.m

該函式實現2部分功能,計算並傳遞誤差,計算梯度

計算誤差和LossFunction

numLayers = numel(net.layers);
%error 
net.e = net.o - y;
% loss function 均方誤差 
net.L = 1 / 2 * sun(net.e(:) . ^ 2 ) / size(net.e, 2);

計算尾部單層感知機的誤差

net.od = net.e .* (net.o .*(1 - net.o)); %output dalta, sigmoid 誤差
%fvd, feature vector delta, 特徵向裡誤差,上一層收集下層誤差,size = [192*50]
net.fvd = (net.ffW' * net.od);
%如果MLP的前一層(特徵抽取最後一層)是卷積層,卷積層的輸出經過sigmoid函式處理,error求導
%在數字識別這個網路中不要用到
if strcmp(net.layers{numLayers}.type, 'c')
    %對於卷基層,它的特徵向裡誤差再求導(net.fv .* (1-net.fv))
    net.fvd = net.fvd .*(net.fv .* (1 - net.fv));
end

改變誤差矩陣形狀

把單層感知機的輸入層featureVector的誤差矩陣,恢復為subFeatureMap2的4*4二維矩陣形式

reshape feature vector deltas into output map style 
sizeA = size(net.layers{numlayers}.a{1}));%size(a{1}) = [4*4*50],一共有a{1}~a{12}; 
fvnum = sizeA(1) * sizeA(2); %fvnum一個圖所含有的特徵向量的數量,4*4
for j = 1 : numel(net.layers{numLayers}.a) %subFeatureMap2的數量,1:12
    %net最後一層的delta,由特徵向量delta,儂次取一個featureMap大小,然後組臺成為一個featureMap的形狀 
    %在fvd裡面亻呆存的是所有祥本的特徵向量(在cnnff.m函式中用特徵map拉成的);這裡需要重新變換回來持徵map的 形式。
    %net.fvd(((j - 1) * fvnum + 1) : j * fvnum, : ) ,一個featureMap的誤差d 
    net.layers{numLayers}.d{j} = reshape(net.fvd(((j-1) * fvnum + 1);j * fvnum, :), sizeA(1), sizeA(2), sizeA(3));
    %size(net.layers{numLayers}.d{j}) = [4 * 4 * 50]
    %size(net.fvd) = [192 * 50]
end

插播一張圖片:

這裡寫圖片描述

誤差在特徵提取網路【卷積降取樣層】的傳播

如果本層是卷積層,它的誤差是從後一層(降取樣層)傳過來,誤差傳播實際上是用降取樣的反向過程,也就是降取樣層的誤差複製為2*2=4份。卷積層的輸入是經過sigmoid處理的,所以,從降取樣層擴充來的誤差要經過sigmoid求導處理。
如果本層是降取樣層,他的誤差是從後一層(卷積層)傳過來,誤差傳播實際是用卷積的反向過程,也就是卷積層的誤差,反捲積(卷積核轉180度)卷積層的誤差,原理參看插圖。

這裡寫圖片描述
這裡寫圖片描述

計算特徵抽取層和尾部單層感知機的梯度

%計算特徵抽取層(卷積+降取樣)的梯度
for l = 2 : numLayers
    if strcmp(net.layers{l}.type, 'c') %卷積層
        for j = 1 : numel(net.layers{l}.a) %l層的featureMap的數量
            for i = 1 : numel(net.layers{l - 1}.a) %l-1層的featureMap的數量
                %卷積核的修改量 = 輸入影象 * 輸出影象的delta
                net.layers{l}.dk{i}{j} = convn(flipall(net.layers{l - 1}.a{i}), net.layers{l}.d{j}, 'valid') / size(net.layers{l}.d{j}, 3);
            end
            %net.layers.d{j}(:)是一個24*24*50的矩陣,db僅除於50
            net.layers{l}.db{j} = sum{net.layers{l}.d{j}(:)} / size(net.layers{l}.d{j}, 3);
        end
    end
end
%計算機尾部單層感知機的梯度
%sizeof(net.od) = [10, 50]
%修改量,求和除於50(batch大小)
net.dffW = net.od * (net.fv)' / size(net.od, 2);
net.dffb = mean(net.od. 2);%第二維度取均值

cnnapplygrads.m

該函式完成權重修改,更新模型的功能
1更新特徵抽取層的權重 weight+bias
2 更新末尾單層感知機的權重 weight+bias

function net = cnnapplygrads(net, opts) %使用梯度
    %特徵抽取層(卷機降取樣)的權重更新
    for l = 2 : numel(net.layers) %從第二層開始
        if strcmp(net.layers{l}.type, 'c')%對於每個卷積層
            for j = 1 : numel(net.layers{l}.a)%列舉該層的每個輸出
                %列舉所有卷積核net.layers{l}.k{ii}{j}
                for ii = 1 : numel(net.layers{l - 1}.a)%列舉上層的每個輸出
                    net.layers{l}.k{ii}{j} = net.layers{l}.k{ii}{j} - opts.alpha * met.layers{l}.dk{ii}{j};
                end
                %修正bias
                net.layers{l}.b{j} = net.layers{l}.b{j} - opts.alpha * net.layers{l}.db{j};
            end
        end
    end
    %單層感知機的權重更新
    net.ffW = net.ffW - opts.alpha * net.dffW;
    net.ffb = net.ffb - opts.alpha * net.dffb;
end

cnntest.m

驗證測試樣本的準確率

%驗證測試樣本的準確率
function [er, bad] = cnntest(net, x, y)
    % feedforward
    net = cnnff(net, x);
    [~, h] = max(net.o);
    [~, a] = max(y);
    %find(x) FIND indices of nonzero elements
    bad = find(h ~= a); %計算預測錯誤的樣本數量
    er = numel(bad) / size(y, 2); % 計算錯誤率
end