deep learning 卷積神經網絡的實現(Convolution Neural Networks)

    本節將會講到卷積神經網絡的實現。說到卷積神經網絡,在圖像識別和目標檢測方面已經取得了不錯的效果,爲什麼要叫做卷積神經網絡呢?主要是因爲在特徵提取的時候,輸入圖像會通過卷積覈對原始圖像進行特徵抽取,然後再通過神經網絡進一步進行特徵提取,也可以稱爲降維,再通過分類器得到分類或者識別的結果,斯坦福大學研究人員通過卷積神經網絡訓練貓的圖像,在YouTube視頻中找到了關於貓的視頻,這也是一個強大的應用。此外在圖片和視頻場景理解方面,Google和斯坦福的研究人員,研發出了一款能夠描述圖片場景的軟件,裏面的算法也有卷積神經網絡,如下圖所示:


人:“A group of men playing Frisbee in the park.”

計算機:“A group of young people playing a game of Frisbee.”

人: “Elephants of mixed ages standing in a muddy landscape.”
計算機: “A herd of elephants walking across a dry grass field.”

    卷積神經網絡也應用於爲物體貼標籤,下面是實例,我想它如果運用於Google眼鏡的話,“當我們看到一幅自然圖片,眼鏡就會顯示你看到的所有東西,並給於標籤,那麼人的學習認知能力有多強大啊。


圖片貼標籤實例

    目前,卷積神經網絡無疑成爲計算機視覺以及模式識別領域的熱門話題,它對處理多分類,大型的圖片分類有着非常好的效果,本文主要包括以下幾個內容:

  1.卷積神經網絡的整體框架

    1.1 卷積層

    1.2 採樣層

    1.3 全連接層

  2.卷積神經網絡的建立(前饋網絡)

  3.卷積和相關的區別

  4.核函數的選定

  5.BP反饋調節參數(反饋網絡)

  6.優化方法的選定

  7.實驗


1.卷積神經網絡的整體框架

   下面先給出圖,然後再說明這個框架。

   在上圖的這個框架下,輸入的是RGB圖像,用到顏色信息,如果你處理的灰度圖像,那麼你的輸入層數就爲1層,但是這會降低最後識別率。輸入圖像(input)32×32×3,當然你要處理的圖像尺寸不一樣,那麼你的輸入層的大小就不一樣了,輸入的圖像經過一個3×5×5的濾波器就得到了一個featuremap,由於此處的濾波未填充邊緣,一個濾波器就產生一張28×28featuremap,那麼用64個濾波器進行卷積,就會產生64featuremaps,這就叫做特徵圖(卷積層C)。不同的濾波器提取出來的特徵當然不一樣,這時就需要通過採樣層了,採樣層採用的是2×2的核,採用的均值採樣,最終得到的featuremaps變爲14×14×64(採樣層S),得到特徵再利用64個5×5×64的濾波器濾波得到6410×10×64(卷積層C),這個時候是立體的濾波,不同於平面濾波,但是原理都一樣。又通過2×2的核採樣得到64張5×5的featuremaps(採樣層S),這時就有64×5×5=1600個神經元了,(當然你還可以用5×5×64的濾波器濾波,這時得到featuremap就爲1×1了,當然這只是討論),這1600個神經元,是最後一層採樣層的每一張featuremap拉伸成一個向量的結果,一張featuremap的向量爲25維,那麼64張就爲1600維。接下來做的工作就是降維了,可以通過普通神經網絡進行降維,此處稱爲“全連接層”,這裏我設定第一層全連接層f1的神經元個數爲1024,第二層全連接層f的神經元個數爲512,第二層神經元就稱爲一張圖片的特徵值,這時你要連接一個分類器,此處的分類器爲softmax分類器,可以實現多分類的。

這個框架可以參見前兩篇博文:

http://blog.csdn.net/hlx371240/article/details/41208515

http://blog.csdn.net/hlx371240/article/details/40015395

這樣一個卷積神經網絡的框架就介紹完了,裏面的參數(如卷積核的大小,卷積核的數量,採樣層的採樣方式,如均值,max值,全連接層神經元的個數,層數)大家都可以自己進行設計,沒有統一的標準。

下面是MATLAB給出的網絡結構

<span style="font-family:Times New Roman;font-size:14px;"><span style="font-family:Times New Roman;"><span style="font-size:18px;">cnn.layers = {  
    struct('type', 'i','inputmaps',3 ,'inputsize',32) %input layer  
    struct('type', 'c', 'outputmaps', 64, 'kernelsize', 5) %convolution layer  
    struct('type', 's', 'scale', 2) %sub sampling layer  
    struct('type', 'c', 'outputmaps', 64, 'kernelsize', 5) %convolution layer  
    struct('type', 's', 'scale', 2) %subsampling layer
    struct('type', 'f1', 'neuron', 1024) %full-connection layer
    struct('type', 'f', 'neuron', 512)  %full-connection layer
    struct('type', 'o', 'scale', 10)
};  </span></span></span>
   可以看到該卷積神經網絡一共有8層(層數可以自己設定),可以看出,整個網絡有1層輸入層,2層卷積層,2層採樣層,2層全連階層,以及1層輸出層。

 1.1卷積層

   此處的卷積跟圖像裏面的卷積一樣,但是不是事先訓練好的,而是隨機產生的卷積核,而且卷積核的大小都是自己設定的。你可以設定爲任意尺寸大小,如5×5,6×6等,當然卷積核不一定都爲正方形,可以是矩形,如7×6,8×9等等都行,這只是單層核, 也可以是多層核,假如輸入圖像是RGB的圖像,含有3通道,那麼你需要設定一個3D核,一個3D核與輸入圖像就得到一張特徵圖featuremap643D核就得到64featuremaps

    另外我們來看卷積過後的圖像大小,其實在前面的博文中已經指出來了,卷積神經網絡做卷積的時候,原圖是不進行擴充的,所以卷積過後圖片是會減小的,比如,20×20的圖片,現在有5×5的核去卷積,那麼就得到(20-5)+1×(20-5)+1=16×16大小的featuremap,對於3D核也是一樣的。

可以參見博文:http://blog.csdn.net/hlx371240/article/details/41208515

  1.2  採樣層
      
採樣層一般是正方形的核函數,這個核函數可以是均值核,也可以是求取核區域的最大值,用均值核叫做meanpooling,用求取區域最大值的核稱爲maxpooling。對於卷積得到的大小爲30×30的圖像,假如我們採用2×2的核函數採樣,那麼就得到15×15大小的圖像,如果採用3×3的核函數,就產生了10×10大小的圖像,如果有人用4×4的核,那麼30不能整除4,所以選擇這個尺寸的核函數不太合適,你可以選擇5×5的核。採樣是圖像特徵的進一步抽取。

可以參見博文:http://blog.csdn.net/hlx371240/article/details/41208515

2.卷積神經網絡的建立(前饋網絡)

   卷積神經網絡分爲前饋網絡和反饋網絡,前饋網絡可以說是得到最終概率值然後進行判別。

第一層:一個5×5×3的卷積核和輸入的32×32×3RGB圖像做卷積,卷積核是3層,RGB也爲3層,他們分別做卷積然後相加,這樣就得到一張28×28的圖像,此處的圖像就爲一個通道了。我們可以用64個卷積覈對圖像進行卷積,這樣就得到6428×28大小的圖像。(每一個卷積後得到值都要經過非線性變換,如一些非線性的核函數)卷積後再用2×2的採樣核,得到14×14大小的圖像,由於上一層有64featuremaps,採樣不會改變featuremaps的數量,還是64featuremaps。然後經過第二次卷積,這時的卷積核是5×5×64的大小,是個3D的核函數,一共含有64層,每一層與上層採樣的64張featuremaps分別做卷積得到一張featuremap,同樣的用645×5×64核函數就得到64featuremaps(每一個卷積後得到值都要經過非線性變化)。卷積後產生了64張10×10大小的圖像,這時再經過採樣得到645×5大小的子圖,一共加起來爲1600個神經元,當然此時還有可以用64個5×5×64的卷積核卷積,得到64個單獨的神經元。現在我沒有加入這層,而是1600個神經元。這時就可以用普通的神經網絡再進行降維。



當然最後一層接的是Softmax分類器。

可以參見博文:http://blog.csdn.net/hlx371240/article/details/40015395

Ng的文章Sparse autoencoderhttp://nlp.stanford.edu/~socherr/sparseAutoencoder_2011new.pdf


cnnff.m

<span style="font-family:Times New Roman;font-size:14px;">function net = cnnff(net, x)
[A B C D]=size(x);
inputmaps = net.layers{1}.inputmaps; % 輸入層只有一個特徵map,也就是原始的輸入圖像
n = numel(net.layers); % 層數
for i=1:inputmaps
    channel=reshape(x(:,:,i,:),net.layers{1}.inputsize,net.layers{1}.inputsize,D);
    net.layers{1}.a{i}=channel; % 網絡的第一層就是輸入,但這裏的輸入包含了多個訓練圖像
end

for l = 2 : n   %  for each layer
    if strcmp(net.layers{l}.type, 'c') % 卷積層
        %  !!below can probably be handled by insane matrix operations
        % 對每一個輸入map,或者說我們需要用outputmaps個不同的卷積核去卷積圖像
        for j = 1 : net.layers{l}.outputmaps   %  for each output map
            %  create temp output map
            % 對上一層的每一張特徵map,卷積後的特徵map的大小就是
            % (輸入map寬 - 卷積核的寬 + 1)* (輸入map高 - 卷積核高 + 1)
            % 對於這裏的層,因爲每層都包含多張特徵map,對應的索引保存在每層map的第三維
            % 所以,這裏的z保存的就是該層中所有的特徵map了
            z = zeros(size(net.layers{l - 1}.a{1}) - [net.layers{l}.kernelsize - 1 net.layers{l}.kernelsize - 1 0]);
            for i = 1 : inputmaps   %  for each input map
                %  convolve with corresponding kernel and add to temp output map
                % 將上一層的每一個特徵map(也就是這層的輸入map)與該層的卷積核進行卷積
                % 然後將對上一層特徵map的所有結果加起來。也就是說,當前層的一張特徵map,是
                % 用一種卷積核去卷積上一層中所有的特徵map,然後所有特徵map對應位置的卷積值的和
                % 另外,有些論文或者實際應用中,並不是與全部的特徵map鏈接的,有可能只與其中的某幾個連接
                z = z + convn(net.layers{l - 1}.a{i}, net.layers{l}.k{i}{j}, 'valid');
            end
            %  add bias, pass through nonlinearity
            % 加上對應位置的基b,然後再用sigmoid函數算出特徵map中每個位置的激活值,作爲該層輸出特徵map
            net.layers{l}.a{j} = relu(z + net.layers{l}.b{j});
        end
        %  set number of input maps to this layers number of outputmaps
        inputmaps = net.layers{l}.outputmaps;
    elseif strcmp(net.layers{l}.type, 's') % 下采樣層
        %  downsample
        for j = 1 : inputmaps
            %  !! replace with variable
            % 例如我們要在scale=2的域上面執行mean pooling,那麼可以卷積大小爲2*2,每個元素都是1/4的卷積核
            z = convn(net.layers{l - 1}.a{j}, ones(net.layers{l}.scale) / (net.layers{l}.scale ^ 2), 'valid');
            % 因爲convn函數的默認卷積步長爲1,而pooling操作的域是沒有重疊的,所以對於上面的卷積結果
            % 最終pooling的結果需要從上面得到的卷積結果中以scale=2爲步長,跳着把mean pooling的值讀出來
            net.layers{l}.a{j} = z(1 : net.layers{l}.scale : end, 1 : net.layers{l}.scale : end, :);
        end
    elseif strcmp(net.layers{l}.type, 'f1')
        net.layers{l-1}.fv = [];
        for j = 1 : numel(net.layers{l-1}.a) % 最後一層的特徵map的個數
            sa = size(net.layers{l-1}.a{j}); % 第j個特徵map的大小
            % 將所有的特徵map拉成一條列向量。還有一維就是對應的樣本索引。每個樣本一列,每列爲對應的特徵向量
            net.layers{l-1}.fv = [net.layers{l-1}.fv; reshape(net.layers{l-1}.a{j}, sa(1) * sa(2), sa(3))];
        end
        net.layers{l}.a = relu(net.layers{l-1}.ffW * net.layers{l-1}.fv + repmat(net.layers{l-1}.ffb, 1, size(net.layers{l-1}.fv, 2)));
    elseif strcmp(net.layers{l}.type, 'f')
        net.layers{l}.a = relu(net.layers{l-1}.ffW * net.layers{l-1}.a+repmat(net.layers{l-1}.ffb, 1, size(net.layers{l-1}.a, 2)));
    elseif strcmp(net.layers{l}.type, 'o')
        net.layers{l}.a = relu(net.layers{l-1}.ffW * net.layers{l-1}.a+repmat(net.layers{l-1}.ffb, 1, size(net.layers{l-1}.a, 2)));
    end
end
end</span>


3.卷積和相關的區別
         下面直接給一張圖來說明卷積和相關的區別


    相關是核直接與原圖像做線性運算,而卷積是需要把核做180度然後再做線性相加運算。

rot180.m

<span style="font-family:Times New Roman;">function X = rot180(X)
X = flipdim(flipdim(X, 1), 2);
end</span>


4.核函數的選定

   下面介紹3種核函數tanh,sigmiod,Relu核函數,分別給出它們的圖像


下面分別畫出它們的導數的圖像


    一般的神經網絡選用的是sigmoid核函數,但是對於深度網絡來說,反饋時的係數太小,導致從最後一層反饋到第一層的參數已經太小,深度網絡很多人選用Relu核,在這篇文章中Imagenet classification with deep convolutional neural networks選用的Relu核,併產生了很好的效果,代碼在https://code.google.com/p/cuda-convnet/中,這個代碼是用C++python寫的。


5.BP反饋調節參數(反饋網絡)

參考文章:http://nlp.stanford.edu/~socherr/sparseAutoencoder_2011new.pdf

Jake Bouvrie, Notes on Convolutional Neural Networks


現在我只講卷積反饋,普通神經網絡的反饋可以參見文章http://nlp.stanford.edu/~socherr/sparseAutoencoder_2011new.pdf

第一層全連接層通過反饋得到1600個神經元的誤差d,然後組合成5×5×64的形式,這個是誤差featuremaps,上採樣得到10×10×64featuremaps,這是卷積層的誤差d,一共有64層,每一層需要跟前饋網絡的14×14×64分別做卷積,得到一個5×5×64的核的誤差d(也可以稱爲梯度),這樣64層分別與前饋網絡做卷積就能到645×5×64個核函數的誤差d,可以參見上圖的3,最後要乘以核函數的導數,再通過4求出每一個參數的△W△b.

cnnbp.m

<span style="font-family:Times New Roman;font-size:14px;">function net = cnnbp(net, y)</span>
<div style="text-align: left;"><span style="font-family:Times New Roman;font-size:14px;"><strong style="line-height: 26px; color: rgb(102, 102, 0); background-color: rgb(255, 255, 255);"></strong></span></div><span style="font-family:Times New Roman;font-size:14px;">n = numel(net.layers); % 網絡層數
%  error
net.layers{n}.e = net.layers{n}.a - y;
%  loss function
% 代價函數是 均方誤差
net.layers{n}.L = 1/2* sum(net.layers{n}.e(:) .^ 2) / size(net.layers{n}.e, 2);

%%  backprop deltas
% 這裏可以參考 UFLDL 的 反向傳導算法 的說明
% 輸出層的 靈敏度 或者 殘差
        net.layers{8}.od = net.layers{8}.e .* ReluInv(net.layers{8}.a);   %  output delta
        % 殘差 反向傳播回 前一層
        net.layers{7}.d = (net.layers{7}.ffW' * net.layers{8}.od) .* ReluInv(net.layers{7}.a); %  feature vector delta
        net.layers{6}.d = (net.layers{6}.ffW' * net.layers{7}.d) .* ReluInv(net.layers{6}.a);
        net.layers{5}.fvd = (net.layers{5}.ffW' * net.layers{6}.d);
        %     net.layers{l-1}.d = net.layers{l-1}.fvd .* (net.layers{l-1}.o .* (1-net.layers{l-1}));
        %     if strcmp(net.layers{n}.type, 'c')         %  only conv layers has sigm function
        %         net.fvd = net.fvd .* (net.fv .* (1 - net.fv));
        %     end
        %  reshape feature vector deltas into output map style
        sa = size(net.layers{5}.a{1}); % 最後一層特徵map的大小。這裏的最後一層都是指輸出層的前一層
        fvnum = sa(1) * sa(2); % 因爲是將最後一層特徵map拉成一條向量,所以對於一個樣本來說,特徵維數是這樣
        for j = 1 : numel(net.layers{5}.a) % 最後一層的特徵map的個數
            % 在fvd裏面保存的是所有樣本的特徵向量(在cnnff.m函數中用特徵map拉成的),所以這裏需要重新
            % 變換回來特徵map的形式。d 保存的是 delta,也就是 靈敏度 或者 殘差
            net.layers{5}.d{j} = reshape(net.layers{5}.fvd(((j - 1) * fvnum + 1) : j * fvnum, :), sa(1), sa(2), sa(3));
        end      
        % 對於 輸出層前面的層(與輸出層計算殘差的方式不同)
   for l = (n - 4) : -1 : 1
    if strcmp(net.layers{l}.type, 'c')
        for j = 1 : numel(net.layers{l}.a) % 該層特徵map的個數
            % net.layers{l}.d{j} 保存的是 第l層 的 第j個 map 的 靈敏度map。 也就是每個神經元節點的delta的值
            % expand的操作相當於對l+1層的靈敏度map進行上採樣。然後前面的操作相當於對該層的輸入a進行sigmoid求導
            % 這條公式請參考 Notes on Convolutional Neural Networks
            % for k = 1:size(net.layers{l + 1}.d{j}, 3)
            % net.layers{l}.d{j}(:,:,k) = net.layers{l}.a{j}(:,:,k) .* (1 - net.layers{l}.a{j}(:,:,k)) .*  kron(net.layers{l + 1}.d{j}(:,:,k), ones(net.layers{l + 1}.scale)) / net.layers{l + 1}.scale ^ 2;
            % end
            net.layers{l}.d{j} = ReluInv(net.layers{l}.a{j}) .* (expand(net.layers{l + 1}.d{j}, [net.layers{l + 1}.scale net.layers{l + 1}.scale 1]) / net.layers{l + 1}.scale ^ 2);
        end
    elseif strcmp(net.layers{l}.type, 's')
        for i = 1 : numel(net.layers{l}.a) % 第l層特徵map的個數
            z = zeros(size(net.layers{l}.a{1}));
            for j = 1 : numel(net.layers{l + 1}.a) % 第l+1層特徵map的個數
                z = z + convn(net.layers{l + 1}.d{j}, rot180(net.layers{l + 1}.k{i}{j}), 'full');
            end
            net.layers{l}.d{i} = z;
        end
    end
end

%%  calc gradients
% 這裏與 Notes on Convolutional Neural Networks 中不同,這裏的 子採樣 層沒有參數,也沒有
% 激活函數,所以在子採樣層是沒有需要求解的參數的
for l = 2 : n
    if strcmp(net.layers{l}.type, 'c')
        for j = 1 : numel(net.layers{l}.a)
            for i = 1 : numel(net.layers{l - 1}.a)
                % dk 保存的是 誤差對卷積核 的導數
                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
            % db 保存的是 誤差對於bias基 的導數
            net.layers{l}.db{j} = sum(net.layers{l}.d{j}(:)) / size(net.layers{l}.d{j}, 3);
        end
    elseif strcmp(net.layers{l}.type, 'f1')
        net.layers{l-1}.dffW = net.layers{l}.d * net.layers{l-1}.fv'/ size(net.layers{l}.d, 2);
        net.layers{l-1}.dffb = mean(net.layers{l}.d, 2);
    elseif strcmp(net.layers{l}.type, 'f')
        net.layers{l-1}.dffW = net.layers{l}.d * net.layers{l-1}.a'/ size(net.layers{l}.d, 2);
        net.layers{l-1}.dffb = mean(net.layers{l}.d, 2);
    elseif strcmp(net.layers{l}.type, 'o')
        net.layers{l-1}.dffW = net.layers{l}.od * net.layers{l-1}.a'/ size(net.layers{l}.od, 2);
        net.layers{l-1}.dffb = mean(net.layers{l}.od, 2);
    end
end
end</span>


6.優化方法選定

   優化方法包括梯度法,共軛梯度法,牛頓法,擬牛頓法,但是很多國外大牛都直接用隨機批量梯度法,學習率(步長)是按照經驗取的,下面給出更新公式,可以結合上面一節。


   大家在更新的時候不用管λW這一項,m代表每次批量處理的圖像。


7.實驗

實驗採用的是cifar-10數據庫,如下圖所示


cnnexample.m

<span style="font-family:Times New Roman;font-size:14px;">clear all; close all; clc;   </span>
<div style="text-align: left;"><span style="font-family:Times New Roman;font-size:14px;"><strong style="color: rgb(51, 0, 153); line-height: 26px; text-align: center; background-color: rgb(255, 255, 255);"></strong></span></div><span style="font-family:Times New Roman;font-size:14px;">load('traindata.mat');
load('testdata.mat');
load('trainlabel.mat');
load('testlabel.mat');
train_x = traindata;  
test_x = testdata; 
trainlabel=double(trainlabel);
trainlabel(trainlabel==0) = 10;
train_y  = full(sparse(trainlabel, 1:50000, 1));
testlabel=double(testlabel);
testlabel(testlabel==0) = 10;
test_y  = full(sparse(testlabel, 1:10000, 1));
%% ex1   
%will run 1 epoch in about 200 second and get around 11% error.   
%With 100 epochs you'll get around 1.2% error  
clear traindata testdata trainlabel testlabel  
cnn.layers = {  
    struct('type', 'i','inputmaps',3 ,'inputsize',32) %input layer  
    struct('type', 'c', 'outputmaps', 64, 'kernelsize', 5) %convolution layer  
    struct('type', 's', 'scale', 2) %sub sampling layer  
    struct('type', 'c', 'outputmaps', 64, 'kernelsize', 5) %convolution layer  
    struct('type', 's', 'scale', 2) %subsampling layer
    struct('type', 'f1', 'neuron', 1024) %full-connection layer
    struct('type', 'f', 'neuron', 512)  %full-connection layer
    struct('type', 'o', 'scale', 10)
};  
  
% 這裏把cnn的設置給cnnsetup,它會據此構建一個完整的CNNs網絡,並返回  
cnn = cnnsetup(cnn, train_x, train_y);  
  
% 學習率  
opts.alpha = 0.5;  
% 每次挑出一個batchsize的batch來訓練,也就是每用batchsize個樣本就調整一次權值,而不是  
% 把所有樣本都輸入了,計算所有樣本的誤差了才調整一次權值  
opts.batchsize = 200;   
% 訓練次數,用同樣的樣本集。我訓練的時候:  
% 1的時候 11.41% error  
% 5的時候 4.2% error  
% 10的時候 2.73% error  
opts.numepochs = 50;  
  
% 然後開始把訓練樣本給它,開始訓練這個CNN網絡  
cnn = cnntrain(cnn, train_x, train_y, opts);  
  
% 然後就用測試樣本來測試  
[er, bad] = cnntest(cnn, test_x, test_y);  
  
%plot mean squared error  
plot(cnn.rL);  
%show test error  
disp([num2str(er*100) '% error']); </span>

cnntrain.m

<span style="font-family:Times New Roman;font-size:14px;">function net = cnntrain(net, x, y, opts)  
    m = size(x, 4); % m 保存的是 訓練樣本個數  
    numbatches = m / opts.batchsize;  
    % rem: Remainder after division. rem(x,y) is x - n.*y 相當於求餘  
    % rem(numbatches, 1) 就相當於取其小數部分,如果爲0,就是整數  
    if rem(numbatches, 1) ~= 0  
        error('numbatches not integer');  
    end  
      
    net.rL = [];  
    for i = 1 : opts.numepochs  
        % disp(X) 打印數組元素。如果X是個字符串,那就打印這個字符串  
        disp(['epoch ' num2str(i) '/' num2str(opts.numepochs)]);  
        % tic 和 toc 是用來計時的,計算這兩條語句之間所耗的時間  
        tic;  
        % P = randperm(N) 返回[1, N]之間所有整數的一個隨機的序列,例如  
        % randperm(6) 可能會返回 [2 4 5 6 1 3]  
        % 這樣就相當於把原來的樣本排列打亂,再挑出一些樣本來訓練  
        kk = randperm(m);  
        for l = 1 : numbatches  
            % 取出打亂順序後的batchsize個樣本和對應的標籤  
            batch_x = x(:, :, : ,kk((l - 1) * opts.batchsize + 1 : l * opts.batchsize));  
            batch_y = y(:,    kk((l - 1) * opts.batchsize + 1 : l * opts.batchsize));  
  
            % 在當前的網絡權值和網絡輸入下計算網絡的輸出  
            net = cnnff(net, batch_x); % Feedforward  
            % 得到上面的網絡輸出後,通過對應的樣本標籤用bp算法來得到誤差對網絡權值  
            %(也就是那些卷積核的元素)的導數  
            net = cnnbp(net, batch_y); % Backpropagation  
            % 得到誤差對權值的導數後,就通過權值更新方法去更新權值  
            net = cnnapplygrads(net, opts);  
            if isempty(net.rL)  
                net.rL(1) = net.layers{8}.L; % 代價函數值,也就是誤差值  
            end  
            net.rL(end + 1) = 0.99 * net.rL(end) + 0.01 * net.layers{8}.L; % 保存歷史的誤差值,以便畫圖分析  
        end  
        toc;  
    end  
      
end  </span>

cnntest.m

<span style="font-family:Times New Roman;font-size:14px;">function [er, bad] = cnntest(net, x, y)  
    %  feedforward  
    net = cnnff(net, x); % 前向傳播得到輸出  
    % [Y,I] = max(X) returns the indices of the maximum values in vector I  
    [~, h] = max(net.o); % 找到最大的輸出對應的標籤  
    [~, a] = max(y);     % 找到最大的期望輸出對應的索引  
    bad = find(h ~= a);  % 找到他們不相同的個數,也就是錯誤的次數  
    er = numel(bad) / size(y, 2); % 計算錯誤率  
end  </span>

flipall.m

<span style="font-family:Times New Roman;font-size:14px;">function X=flipall(X)
    for i=1:ndims(X)
        X = flipdim(X,i);
    end
end</span>

relu.m

<span style="font-family:Times New Roman;font-size:14px;">function X = relu(P)
     X=log(1+exp(P));
end</span>
ReluInv.m
<span style="font-family:Times New Roman;font-size:14px;">function reluInv=ReluInv(x)
reluInv = exp(x)./(1 + exp(x));
end</span>
expand.m

<span style="font-family:Times New Roman;font-size:14px;">function B = expand(A, S)
if nargin < 2
    error('Size vector must be provided.  See help.');
end

SA = size(A);  % Get the size (and number of dimensions) of input.

if length(SA) ~= length(S)
   error('Length of size vector must equal ndims(A).  See help.')
elseif any(S ~= floor(S))
   error('The size vector must contain integers only.  See help.')
end

T = cell(length(SA), 1);
for ii = length(SA) : -1 : 1
    H = zeros(SA(ii) * S(ii), 1);   %  One index vector into A for each dim.
    H(1 : S(ii) : SA(ii) * S(ii)) = 1;   %  Put ones in correct places.
    T{ii} = cumsum(H);   %  Cumsumming creates the correct order.
end

B = A(T{:});  </span>


cnnsetup.m

<span style="font-family:Times New Roman;font-size:14px;">function net = cnnsetup(net, x, y)
inputmaps = net.layers{1}.inputmaps;
% B=squeeze(A) 返回和矩陣A相同元素但所有單一維都移除的矩陣B,單一維是滿足size(A,dim)=1的維。
% train_x中圖像的存放方式是三維的reshape(train_x',28,28,60000),前面兩維表示圖像的行與列,
% 第三維就表示有多少個圖像。這樣squeeze(x(:, :, 1))就相當於取第一個圖像樣本後,再把第三維
% 移除,就變成了28x28的矩陣,也就是得到一幅圖像,再size一下就得到了訓練樣本圖像的行數與列數了
mapsize = size(squeeze(x(:, :, 1)));

% 下面通過傳入net這個結構體來逐層構建CNN網絡
% n = numel(A)返回數組A中元素個數
% net.layers中有五個struct類型的元素,實際上就表示CNN共有五層,這裏範圍的是5
for l = 1 : numel(net.layers)   %  layer
    if strcmp(net.layers{l}.type, 's') % 如果這層是 子採樣層
        % subsampling層的mapsize,最開始mapsize是每張圖的大小28*28
        % 這裏除以scale=2,就是pooling之後圖的大小,pooling域之間沒有重疊,所以pooling後的圖像爲14*14
        % 注意這裏的右邊的mapsize保存的都是上一層每張特徵map的大小,它會隨着循環進行不斷更新
        mapsize = floor(mapsize / net.layers{l}.scale);
        for j = 1 : inputmaps % inputmap就是上一層有多少張特徵圖
            net.layers{l}.b{j} = 0; % 將偏置初始化爲0
        end
    end
    if strcmp(net.layers{l}.type, 'c') % 如果這層是 卷積層
        % 舊的mapsize保存的是上一層的特徵map的大小,那麼如果卷積核的移動步長是1,那用
        % kernelsize*kernelsize大小的卷積核卷積上一層的特徵map後,得到的新的map的大小就是下面這樣
        mapsize = mapsize - net.layers{l}.kernelsize + 1;
        % 該層需要學習的參數個數。每張特徵map是一個(後層特徵圖數量)*(用來卷積的patch圖的大小)
        % 因爲是通過用一個核窗口在上一個特徵map層中移動(核窗口每次移動1個像素),遍歷上一個特徵map
        % 層的每個神經元。核窗口由kernelsize*kernelsize個元素組成,每個元素是一個獨立的權值,所以
        % 就有kernelsize*kernelsize個需要學習的權值,再加一個偏置值。另外,由於是權值共享,也就是
        % 說同一個特徵map層是用同一個具有相同權值元素的kernelsize*kernelsize的核窗口去感受輸入上一
        % 個特徵map層的每個神經元得到的,所以同一個特徵map,它的權值是一樣的,共享的,權值只取決於
        % 核窗口。然後,不同的特徵map提取輸入上一個特徵map層不同的特徵,所以採用的核窗口不一樣,也
        % 就是權值不一樣,所以outputmaps個特徵map就有(kernelsize*kernelsize+1)* outputmaps那麼多的權值了
        % 但這裏fan_out只保存卷積核的權值W,偏置b在下面獨立保存
        fan_out = net.layers{l}.outputmaps * net.layers{l}.kernelsize ^ 2;
        for j = 1 : net.layers{l}.outputmaps  %  output map
            % fan_out保存的是對於上一層的一張特徵map,我在這一層需要對這一張特徵map提取outputmaps種特徵,
            % 提取每種特徵用到的卷積核不同,所以fan_out保存的是這一層輸出新的特徵需要學習的參數個數
            % 而,fan_in保存的是,我在這一層,要連接到上一層中所有的特徵map,然後用fan_out保存的提取特徵
            % 的權值來提取他們的特徵。也即是對於每一個當前層特徵圖,有多少個參數鏈到前層
            fan_in = inputmaps * net.layers{l}.kernelsize ^ 2;
            for i = 1 : inputmaps  %  input map
                % 隨機初始化權值,也就是共有outputmaps個卷積核,對上層的每個特徵map,都需要用這麼多個卷積核
                % 去卷積提取特徵。
                % rand(n)是產生n×n的 0-1之間均勻取值的數值的矩陣,再減去0.5就相當於產生-0.5到0.5之間的隨機數
                % 再 *2 就放大到 [-1, 1]。然後再乘以後面那一數,why?
                % 反正就是將卷積核每個元素初始化爲[-sqrt(6 / (fan_in + fan_out)), sqrt(6 / (fan_in + fan_out))]
                % 之間的隨機數。因爲這裏是權值共享的,也就是對於一張特徵map,所有感受野位置的卷積核都是一樣的
                % 所以只需要保存的是 inputmaps * outputmaps 個卷積核。
                net.layers{l}.k{i}{j} = (rand(net.layers{l}.kernelsize) - 0.5) * 2 * sqrt(6 / (fan_in + fan_out));
            end
            net.layers{l}.b{j} = 0; % 將偏置初始化爲0
        end
        % 只有在卷積層的時候纔會改變特徵map的個數,pooling的時候不會改變個數。這層輸出的特徵map個數就是
        % 輸入到下一層的特徵map個數
        inputmaps = net.layers{l}.outputmaps;
    end
    if strcmp(net.layers{l}.type, 'f1')
        fvnum = prod(mapsize) * inputmaps;
        onum = net.layers{l}.neuron;
        net.layers{l-1}.ffb = zeros(onum, 1);
        net.layers{l-1}.ffW = (rand(onum, fvnum) - 0.5) * 2 * sqrt(6 / (onum + fvnum));
    end
    if strcmp(net.layers{l}.type, 'f')
        fvnum = net.layers{l-1}.neuron;
        onum = net.layers{l}.neuron;
        net.layers{l-1}.ffb = zeros(onum, 1);
        net.layers{l-1}.ffW = (rand(onum, fvnum) - 0.5) * 2 * sqrt(6 / (onum + fvnum));
    end
    if strcmp(net.layers{l}.type, 'o')
        fvnum = net.layers{l-1}.neuron;
        onum = net.layers{l}.scale;
        net.layers{l-1}.ffb = zeros(onum, 1);
        net.layers{l-1}.ffW = (rand(onum, fvnum) - 0.5) * 2 * sqrt(6 / (onum + fvnum));
    end
    % fvnum 是輸出層的前面一層的神經元個數。
    % 這一層的上一層是經過pooling後的層,包含有inputmaps個特徵map。每個特徵map的大小是mapsize。
    % 所以,該層的神經元個數是 inputmaps * (每個特徵map的大小)
    % prod: Product of elements.
    % For vectors, prod(X) is the product of the elements of X
    % 在這裏 mapsize = [特徵map的行數 特徵map的列數],所以prod後就是 特徵map的行*列
    %fvnum = prod(mapsize) * inputmaps;
    % onum 是標籤的個數,也就是輸出層神經元的個數。你要分多少個類,自然就有多少個輸出神經元
    %onum = size(y, 1);
    % 這裏是最後一層神經網絡的設定
    % ffb 是輸出層每個神經元對應的基biases
    %net.ffb = zeros(onum, 1);
    % ffW 輸出層前一層 與 輸出層 連接的權值,這兩層之間是全連接的
    %net.ffW = (rand(onum, fvnum) - 0.5) * 2 * sqrt(6 / (onum + fvnum));
end</span>


數據準備:整個數據爲4-D的double型數據

每一維數據是這樣的,前2維代表每一幀圖像的大小,如文章中指的32×32,第3維代表輸入圖片的通道,一共爲3通道,那麼前3維就組成了一個樣本,第4維代表樣本的數量。這樣就生成了訓練集。測試集也一樣。

數據的標籤:

trainlabel=double(trainlabel);

trainlabel(trainlabel==0) = 10;

train_y  = full(sparse(trainlabel, 1:50000, 1));

每一類分別給出相應的標籤,如1,2,3,…,n


                                                                                                                                             懷柔風光


發佈了59 篇原創文章 · 獲贊 60 · 訪問量 46萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章