天天看點

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

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

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

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

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

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

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

    卷積神經網絡也應用于為物體貼标簽,下面是執行個體,我想它如果運用于Google眼鏡的話,“當我們看到一幅自然圖檔,眼鏡就會顯示你看到的所有東西,并給于标簽,那麼人的學習認知能力有多強大啊。

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

圖檔貼标簽執行個體

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

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

    1.1 卷積層

    1.2 采樣層

    1.3 全連接配接層

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

  3.卷積和相關的差別

  4.核函數的標明

  5.BP回報調節參數(回報網絡)

  6.優化方法的標明

  7.實驗

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

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

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

   在上圖的這個架構下,輸入的是RGB圖像,用到顔色資訊,如果你處理的灰階圖像,那麼你的輸入層數就為1層,但是這會降低最後識别率。輸入圖像(input)為32×32×3,當然你要處理的圖像尺寸不一樣,那麼你的輸入層的大小就不一樣了,輸入的圖像經過一個3×5×5的濾波器就得到了一個featuremap,由于此處的濾波未填充邊緣,一個濾波器就産生一張28×28的featuremap,那麼用64個濾波器進行卷積,就會産生64張featuremaps,這就叫做特征圖(卷積層C)。不同的濾波器提取出來的特征當然不一樣,這時就需要通過采樣層了,采樣層采用的是2×2的核,采用的均值采樣,最終得到的featuremaps變為14×14×64(采樣層S),得到特征再利用64個5×5×64的濾波器濾波得到64張10×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給出的網絡結構

[plain] view plain copy

  1. <span style="font-family:Times New Roman;font-size:14px;"><span style="font-family:Times New Roman;"><span style="font-size:18px;">cnn.layers = {    
  2.     struct('type', 'i','inputmaps',3 ,'inputsize',32) %input layer    
  3.     struct('type', 'c', 'outputmaps', 64, 'kernelsize', 5) %convolution layer    
  4.     struct('type', 's', 'scale', 2) %sub sampling layer    
  5.     struct('type', 'c', 'outputmaps', 64, 'kernelsize', 5) %convolution layer    
  6.     struct('type', 's', 'scale', 2) %subsampling layer  
  7.     struct('type', 'f1', 'neuron', 1024) %full-connection layer  
  8.     struct('type', 'f', 'neuron', 512)  %full-connection layer  
  9.     struct('type', 'o', 'scale', 10)  
  10. };  </span></span></span>  

   可以看到該卷積神經網絡一共有 8層(層數可以自己設定),可以看出,整個網絡有1層輸入層,2層卷積層,2層采樣層,2層全連階層,以及1層輸出層。

 1.1卷積層

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

    另外我們來看卷積過後的圖像大小,其實在前面的博文中已經指出來了,卷積神經網絡做卷積的時候,原圖是不進行擴充的,是以卷積過後圖檔是會減小的,比如,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×3的RGB圖像做卷積,卷積核是3層,RGB也為3層,他們分别做卷積然後相加,這樣就得到一張28×28的圖像,此處的圖像就為一個通道了。我們可以用64個卷積核對圖像進行卷積,這樣就得到64張28×28大小的圖像。(每一個卷積後得到值都要經過非線性變換,如一些非線性的核函數)卷積後再用2×2的采樣核,得到14×14大小的圖像,由于上一層有64張featuremaps,采樣不會改變featuremaps的數量,還是64個featuremaps。然後經過第二次卷積,這時的卷積核是5×5×64的大小,是個3D的核函數,一共含有64層,每一層與上層采樣的64張featuremaps分别做卷積得到一張featuremap,同樣的用64個5×5×64核函數就得到64張featuremaps,(每一個卷積後得到值都要經過非線性變化)。卷積後産生了64張10×10大小的圖像,這時再經過采樣得到64個5×5大小的子圖,一共加起來為1600個神經元,當然此時還有可以用64個5×5×64的卷積核卷積,得到64個單獨的神經元。現在我沒有加入這層,而是1600個神經元。這時就可以用普通的神經網絡再進行降維。

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

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

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

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

cnnff.m

[plain] view plain copy

  1. <span style="font-family:Times New Roman;font-size:14px;">function net = cnnff(net, x)  
  2. [A B C D]=size(x);  
  3. inputmaps = net.layers{1}.inputmaps; % 輸入層隻有一個特征map,也就是原始的輸入圖像  
  4. n = numel(net.layers); % 層數  
  5. for i=1:inputmaps  
  6.     channel=reshape(x(:,:,i,:),net.layers{1}.inputsize,net.layers{1}.inputsize,D);  
  7.     net.layers{1}.a{i}=channel; % 網絡的第一層就是輸入,但這裡的輸入包含了多個訓練圖像  
  8. end  
  9. for l = 2 : n   %  for each layer  
  10.     if strcmp(net.layers{l}.type, 'c') % 卷積層  
  11.         %  !!below can probably be handled by insane matrix operations  
  12.         % 對每一個輸入map,或者說我們需要用outputmaps個不同的卷積核去卷積圖像  
  13.         for j = 1 : net.layers{l}.outputmaps   %  for each output map  
  14.             %  create temp output map  
  15.             % 對上一層的每一張特征map,卷積後的特征map的大小就是  
  16.             % (輸入map寬 - 卷積核的寬 + 1)* (輸入map高 - 卷積核高 + 1)  
  17.             % 對于這裡的層,因為每層都包含多張特征map,對應的索引儲存在每層map的第三維  
  18.             % 是以,這裡的z儲存的就是該層中所有的特征map了  
  19.             z = zeros(size(net.layers{l - 1}.a{1}) - [net.layers{l}.kernelsize - 1 net.layers{l}.kernelsize - 1 0]);  
  20.             for i = 1 : inputmaps   %  for each input map  
  21.                 %  convolve with corresponding kernel and add to temp output map  
  22.                 % 将上一層的每一個特征map(也就是這層的輸入map)與該層的卷積核進行卷積  
  23.                 % 然後将對上一層特征map的所有結果加起來。也就是說,目前層的一張特征map,是  
  24.                 % 用一種卷積核去卷積上一層中所有的特征map,然後所有特征map對應位置的卷積值的和  
  25.                 % 另外,有些論文或者實際應用中,并不是與全部的特征map連結的,有可能隻與其中的某幾個連接配接  
  26.                 z = z + convn(net.layers{l - 1}.a{i}, net.layers{l}.k{i}{j}, 'valid');  
  27.             end  
  28.             %  add bias, pass through nonlinearity  
  29.             % 加上對應位置的基b,然後再用sigmoid函數算出特征map中每個位置的激活值,作為該層輸出特征map  
  30.             net.layers{l}.a{j} = relu(z + net.layers{l}.b{j});  
  31.         end  
  32.         %  set number of input maps to this layers number of outputmaps  
  33.         inputmaps = net.layers{l}.outputmaps;  
  34.     elseif strcmp(net.layers{l}.type, 's') % 下采樣層  
  35.         %  downsample  
  36.         for j = 1 : inputmaps  
  37.             %  !! replace with variable  
  38.             % 例如我們要在scale=2的域上面執行mean pooling,那麼可以卷積大小為2*2,每個元素都是1/4的卷積核  
  39.             z = convn(net.layers{l - 1}.a{j}, ones(net.layers{l}.scale) / (net.layers{l}.scale ^ 2), 'valid');  
  40.             % 因為convn函數的預設卷積步長為1,而pooling操作的域是沒有重疊的,是以對于上面的卷積結果  
  41.             % 最終pooling的結果需要從上面得到的卷積結果中以scale=2為步長,跳着把mean pooling的值讀出來  
  42.             net.layers{l}.a{j} = z(1 : net.layers{l}.scale : end, 1 : net.layers{l}.scale : end, :);  
  43.         end  
  44.     elseif strcmp(net.layers{l}.type, 'f1')  
  45.         net.layers{l-1}.fv = [];  
  46.         for j = 1 : numel(net.layers{l-1}.a) % 最後一層的特征map的個數  
  47.             sa = size(net.layers{l-1}.a{j}); % 第j個特征map的大小  
  48.             % 将所有的特征map拉成一條列向量。還有一維就是對應的樣本索引。每個樣本一列,每列為對應的特征向量  
  49.             net.layers{l-1}.fv = [net.layers{l-1}.fv; reshape(net.layers{l-1}.a{j}, sa(1) * sa(2), sa(3))];  
  50.         end  
  51.         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)));  
  52.     elseif strcmp(net.layers{l}.type, 'f')  
  53.         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)));  
  54.     elseif strcmp(net.layers{l}.type, 'o')  
  55.         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)));  
  56.     end  
  57. end  
  58. end</span>  

3.卷積和相關的差別

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

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

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

rot180.m

[plain] view plain copy

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

4.核函數的標明

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

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

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

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

    一般的神經網絡選用的是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

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

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

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

cnnbp.m

[plain] view plain copy

  1. <span style="font-family:Times New Roman;font-size:14px;">function net = cnnbp(net, y)</span>  

[plain] view plain copy

  1. <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); % 網絡層數  
  2. %  error  
  3. net.layers{n}.e = net.layers{n}.a - y;  
  4. %  loss function  
  5. % 代價函數是 均方誤差  
  6. net.layers{n}.L = 1/2* sum(net.layers{n}.e(:) .^ 2) / size(net.layers{n}.e, 2);  
  7. %%  backprop deltas  
  8. % 這裡可以參考 UFLDL 的 反向傳導算法 的說明  
  9. % 輸出層的 靈敏度 或者 殘差  
  10.         net.layers{8}.od = net.layers{8}.e .* ReluInv(net.layers{8}.a);   %  output delta  
  11.         % 殘差 反向傳播回 前一層  
  12.         net.layers{7}.d = (net.layers{7}.ffW' * net.layers{8}.od) .* ReluInv(net.layers{7}.a); %  feature vector delta  
  13.         net.layers{6}.d = (net.layers{6}.ffW' * net.layers{7}.d) .* ReluInv(net.layers{6}.a);  
  14.         net.layers{5}.fvd = (net.layers{5}.ffW' * net.layers{6}.d);  
  15.         %     net.layers{l-1}.d = net.layers{l-1}.fvd .* (net.layers{l-1}.o .* (1-net.layers{l-1}));  
  16.         %     if strcmp(net.layers{n}.type, 'c')         %  only conv layers has sigm function  
  17.         %         net.fvd = net.fvd .* (net.fv .* (1 - net.fv));  
  18.         %     end  
  19.         %  reshape feature vector deltas into output map style  
  20.         sa = size(net.layers{5}.a{1}); % 最後一層特征map的大小。這裡的最後一層都是指輸出層的前一層  
  21.         fvnum = sa(1) * sa(2); % 因為是将最後一層特征map拉成一條向量,是以對于一個樣本來說,特征維數是這樣  
  22.         for j = 1 : numel(net.layers{5}.a) % 最後一層的特征map的個數  
  23.             % 在fvd裡面儲存的是所有樣本的特征向量(在cnnff.m函數中用特征map拉成的),是以這裡需要重新  
  24.             % 變換回來特征map的形式。d 儲存的是 delta,也就是 靈敏度 或者 殘差  
  25.             net.layers{5}.d{j} = reshape(net.layers{5}.fvd(((j - 1) * fvnum + 1) : j * fvnum, :), sa(1), sa(2), sa(3));  
  26.         end        
  27.         % 對于 輸出層前面的層(與輸出層計算殘差的方式不同)  
  28.    for l = (n - 4) : -1 : 1  
  29.     if strcmp(net.layers{l}.type, 'c')  
  30.         for j = 1 : numel(net.layers{l}.a) % 該層特征map的個數  
  31.             % net.layers{l}.d{j} 儲存的是 第l層 的 第j個 map 的 靈敏度map。 也就是每個神經元節點的delta的值  
  32.             % expand的操作相當于對l+1層的靈敏度map進行上采樣。然後前面的操作相當于對該層的輸入a進行sigmoid求導  
  33.             % 這條公式請參考 Notes on Convolutional Neural Networks  
  34.             % for k = 1:size(net.layers{l + 1}.d{j}, 3)  
  35.             % 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;  
  36.             % end  
  37.             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);  
  38.         end  
  39.     elseif strcmp(net.layers{l}.type, 's')  
  40.         for i = 1 : numel(net.layers{l}.a) % 第l層特征map的個數  
  41.             z = zeros(size(net.layers{l}.a{1}));  
  42.             for j = 1 : numel(net.layers{l + 1}.a) % 第l+1層特征map的個數  
  43.                 z = z + convn(net.layers{l + 1}.d{j}, rot180(net.layers{l + 1}.k{i}{j}), 'full');  
  44.             end  
  45.             net.layers{l}.d{i} = z;  
  46.         end  
  47.     end  
  48. end  
  49. %%  calc gradients  
  50. % 這裡與 Notes on Convolutional Neural Networks 中不同,這裡的 子采樣 層沒有參數,也沒有  
  51. % 激活函數,是以在子采樣層是沒有需要求解的參數的  
  52. for l = 2 : n  
  53.     if strcmp(net.layers{l}.type, 'c')  
  54.         for j = 1 : numel(net.layers{l}.a)  
  55.             for i = 1 : numel(net.layers{l - 1}.a)  
  56.                 % dk 儲存的是 誤差對卷積核 的導數  
  57.                 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);  
  58.             end  
  59.             % db 儲存的是 誤差對于bias基 的導數  
  60.             net.layers{l}.db{j} = sum(net.layers{l}.d{j}(:)) / size(net.layers{l}.d{j}, 3);  
  61.         end  
  62.     elseif strcmp(net.layers{l}.type, 'f1')  
  63.         net.layers{l-1}.dffW = net.layers{l}.d * net.layers{l-1}.fv'/ size(net.layers{l}.d, 2);  
  64.         net.layers{l-1}.dffb = mean(net.layers{l}.d, 2);  
  65.     elseif strcmp(net.layers{l}.type, 'f')  
  66.         net.layers{l-1}.dffW = net.layers{l}.d * net.layers{l-1}.a'/ size(net.layers{l}.d, 2);  
  67.         net.layers{l-1}.dffb = mean(net.layers{l}.d, 2);  
  68.     elseif strcmp(net.layers{l}.type, 'o')  
  69.         net.layers{l-1}.dffW = net.layers{l}.od * net.layers{l-1}.a'/ size(net.layers{l}.od, 2);  
  70.         net.layers{l-1}.dffb = mean(net.layers{l}.od, 2);  
  71.     end  
  72. end  
  73. end</span>  

6.優化方法標明

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

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

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

7.實驗

實驗采用的是cifar-10資料庫,如下圖所示

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

cnnexample.m

[plain] view plain copy

  1. <span style="font-family:Times New Roman;font-size:14px;">clear all; close all; clc;   </span>  

[plain] view plain copy

  1. <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');  
  2. load('testdata.mat');  
  3. load('trainlabel.mat');  
  4. load('testlabel.mat');  
  5. train_x = traindata;    
  6. test_x = testdata;   
  7. trainlabel=double(trainlabel);  
  8. trainlabel(trainlabel==0) = 10;  
  9. train_y  = full(sparse(trainlabel, 1:50000, 1));  
  10. testlabel=double(testlabel);  
  11. testlabel(testlabel==0) = 10;  
  12. test_y  = full(sparse(testlabel, 1:10000, 1));  
  13. %% ex1     
  14. %will run 1 epoch in about 200 second and get around 11% error.     
  15. %With 100 epochs you'll get around 1.2% error    
  16. clear traindata testdata trainlabel testlabel    
  17. cnn.layers = {    
  18.     struct('type', 'i','inputmaps',3 ,'inputsize',32) %input layer    
  19.     struct('type', 'c', 'outputmaps', 64, 'kernelsize', 5) %convolution layer    
  20.     struct('type', 's', 'scale', 2) %sub sampling layer    
  21.     struct('type', 'c', 'outputmaps', 64, 'kernelsize', 5) %convolution layer    
  22.     struct('type', 's', 'scale', 2) %subsampling layer  
  23.     struct('type', 'f1', 'neuron', 1024) %full-connection layer  
  24.     struct('type', 'f', 'neuron', 512)  %full-connection layer  
  25.     struct('type', 'o', 'scale', 10)  
  26. };    
  27. % 這裡把cnn的設定給cnnsetup,它會據此建構一個完整的CNNs網絡,并傳回    
  28. cnn = cnnsetup(cnn, train_x, train_y);    
  29. % 學習率    
  30. opts.alpha = 0.5;    
  31. % 每次挑出一個batchsize的batch來訓練,也就是每用batchsize個樣本就調整一次權值,而不是    
  32. % 把所有樣本都輸入了,計算所有樣本的誤差了才調整一次權值    
  33. opts.batchsize = 200;     
  34. % 訓練次數,用同樣的樣本集。我訓練的時候:    
  35. % 1的時候 11.41% error    
  36. % 5的時候 4.2% error    
  37. % 10的時候 2.73% error    
  38. opts.numepochs = 50;    
  39. % 然後開始把訓練樣本給它,開始訓練這個CNN網絡    
  40. cnn = cnntrain(cnn, train_x, train_y, opts);    
  41. % 然後就用測試樣本來測試    
  42. [er, bad] = cnntest(cnn, test_x, test_y);    
  43. %plot mean squared error    
  44. plot(cnn.rL);    
  45. %show test error    
  46. disp([num2str(er*100) '% error']); </span>  

cnntrain.m

[plain] view plain copy

  1. <span style="font-family:Times New Roman;font-size:14px;">function net = cnntrain(net, x, y, opts)    
  2.     m = size(x, 4); % m 儲存的是 訓練樣本個數    
  3.     numbatches = m / opts.batchsize;    
  4.     % rem: Remainder after division. rem(x,y) is x - n.*y 相當于求餘    
  5.     % rem(numbatches, 1) 就相當于取其小數部分,如果為0,就是整數    
  6.     if rem(numbatches, 1) ~= 0    
  7.         error('numbatches not integer');    
  8.     end    
  9.     net.rL = [];    
  10.     for i = 1 : opts.numepochs    
  11.         % disp(X) 列印數組元素。如果X是個字元串,那就列印這個字元串    
  12.         disp(['epoch ' num2str(i) '/' num2str(opts.numepochs)]);    
  13.         % tic 和 toc 是用來計時的,計算這兩條語句之間所耗的時間    
  14.         tic;    
  15.         % P = randperm(N) 傳回[1, N]之間所有整數的一個随機的序列,例如    
  16.         % randperm(6) 可能會傳回 [2 4 5 6 1 3]    
  17.         % 這樣就相當于把原來的樣本排列打亂,再挑出一些樣本來訓練    
  18.         kk = randperm(m);    
  19.         for l = 1 : numbatches    
  20.             % 取出打亂順序後的batchsize個樣本和對應的标簽    
  21.             batch_x = x(:, :, : ,kk((l - 1) * opts.batchsize + 1 : l * opts.batchsize));    
  22.             batch_y = y(:,    kk((l - 1) * opts.batchsize + 1 : l * opts.batchsize));    
  23.             % 在目前的網絡權值和網絡輸入下計算網絡的輸出    
  24.             net = cnnff(net, batch_x); % Feedforward    
  25.             % 得到上面的網絡輸出後,通過對應的樣本标簽用bp算法來得到誤差對網絡權值    
  26.             %(也就是那些卷積核的元素)的導數    
  27.             net = cnnbp(net, batch_y); % Backpropagation    
  28.             % 得到誤差對權值的導數後,就通過權值更新方法去更新權值    
  29.             net = cnnapplygrads(net, opts);    
  30.             if isempty(net.rL)    
  31.                 net.rL(1) = net.layers{8}.L; % 代價函數值,也就是誤內插補點    
  32.             end    
  33.             net.rL(end + 1) = 0.99 * net.rL(end) + 0.01 * net.layers{8}.L; % 儲存曆史的誤內插補點,以便畫圖分析    
  34.         end    
  35.         toc;    
  36.     end    
  37. end  </span>  

cnntest.m [plain] view plain copy

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

flipall.m

[plain] view plain copy

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

relu.m [plain] view plain copy

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

ReluInv.m

[plain] view plain copy

  1. <span style="font-family:Times New Roman;font-size:14px;">function reluInv=ReluInv(x)  
  2. reluInv = exp(x)./(1 + exp(x));  
  3. end</span>  

expand.m [plain] view plain copy

  1. <span style="font-family:Times New Roman;font-size:14px;">function B = expand(A, S)  
  2. if nargin < 2  
  3.     error('Size vector must be provided.  See help.');  
  4. end  
  5. SA = size(A);  % Get the size (and number of dimensions) of input.  
  6. if length(SA) ~= length(S)  
  7.    error('Length of size vector must equal ndims(A).  See help.')  
  8. elseif any(S ~= floor(S))  
  9.    error('The size vector must contain integers only.  See help.')  
  10. end  
  11. T = cell(length(SA), 1);  
  12. for ii = length(SA) : -1 : 1  
  13.     H = zeros(SA(ii) * S(ii), 1);   %  One index vector into A for each dim.  
  14.     H(1 : S(ii) : SA(ii) * S(ii)) = 1;   %  Put ones in correct places.  
  15.     T{ii} = cumsum(H);   %  Cumsumming creates the correct order.  
  16. end  
  17. B = A(T{:});  </span>  

cnnsetup.m

[plain] view plain copy

  1. <span style="font-family:Times New Roman;font-size:14px;">function net = cnnsetup(net, x, y)  
  2. inputmaps = net.layers{1}.inputmaps;  
  3. % B=squeeze(A) 傳回和矩陣A相同元素但所有單一維都移除的矩陣B,單一維是滿足size(A,dim)=1的維。  
  4. % train_x中圖像的存放方式是三維的reshape(train_x',28,28,60000),前面兩維表示圖像的行與列,  
  5. % 第三維就表示有多少個圖像。這樣squeeze(x(:, :, 1))就相當于取第一個圖像樣本後,再把第三維  
  6. % 移除,就變成了28x28的矩陣,也就是得到一幅圖像,再size一下就得到了訓練樣本圖像的行數與列數了  
  7. mapsize = size(squeeze(x(:, :, 1)));  
  8. % 下面通過傳入net這個結構體來逐層建構CNN網絡  
  9. % n = numel(A)傳回數組A中元素個數  
  10. % net.layers中有五個struct類型的元素,實際上就表示CNN共有五層,這裡範圍的是5  
  11. for l = 1 : numel(net.layers)   %  layer  
  12.     if strcmp(net.layers{l}.type, 's') % 如果這層是 子采樣層  
  13.         % subsampling層的mapsize,最開始mapsize是每張圖的大小28*28  
  14.         % 這裡除以scale=2,就是pooling之後圖的大小,pooling域之間沒有重疊,是以pooling後的圖像為14*14  
  15.         % 注意這裡的右邊的mapsize儲存的都是上一層每張特征map的大小,它會随着循環進行不斷更新  
  16.         mapsize = floor(mapsize / net.layers{l}.scale);  
  17.         for j = 1 : inputmaps % inputmap就是上一層有多少張特征圖  
  18.             net.layers{l}.b{j} = 0; % 将偏置初始化為0  
  19.         end  
  20.     end  
  21.     if strcmp(net.layers{l}.type, 'c') % 如果這層是 卷積層  
  22.         % 舊的mapsize儲存的是上一層的特征map的大小,那麼如果卷積核的移動步長是1,那用  
  23.         % kernelsize*kernelsize大小的卷積核卷積上一層的特征map後,得到的新的map的大小就是下面這樣  
  24.         mapsize = mapsize - net.layers{l}.kernelsize + 1;  
  25.         % 該層需要學習的參數個數。每張特征map是一個(後層特征圖數量)*(用來卷積的patch圖的大小)  
  26.         % 因為是通過用一個核視窗在上一個特征map層中移動(核視窗每次移動1個像素),周遊上一個特征map  
  27.         % 層的每個神經元。核視窗由kernelsize*kernelsize個元素組成,每個元素是一個獨立的權值,是以  
  28.         % 就有kernelsize*kernelsize個需要學習的權值,再加一個偏置值。另外,由于是權值共享,也就是  
  29.         % 說同一個特征map層是用同一個具有相同權值元素的kernelsize*kernelsize的核視窗去感受輸入上一  
  30.         % 個特征map層的每個神經元得到的,是以同一個特征map,它的權值是一樣的,共享的,權值隻取決于  
  31.         % 核視窗。然後,不同的特征map提取輸入上一個特征map層不同的特征,是以采用的核視窗不一樣,也  
  32.         % 就是權值不一樣,是以outputmaps個特征map就有(kernelsize*kernelsize+1)* outputmaps那麼多的權值了  
  33.         % 但這裡fan_out隻儲存卷積核的權值W,偏置b在下面獨立儲存  
  34.         fan_out = net.layers{l}.outputmaps * net.layers{l}.kernelsize ^ 2;  
  35.         for j = 1 : net.layers{l}.outputmaps  %  output map  
  36.             % fan_out儲存的是對于上一層的一張特征map,我在這一層需要對這一張特征map提取outputmaps種特征,  
  37.             % 提取每種特征用到的卷積核不同,是以fan_out儲存的是這一層輸出新的特征需要學習的參數個數  
  38.             % 而,fan_in儲存的是,我在這一層,要連接配接到上一層中所有的特征map,然後用fan_out儲存的提取特征  
  39.             % 的權值來提取他們的特征。也即是對于每一個目前層特征圖,有多少個參數鍊到前層  
  40.             fan_in = inputmaps * net.layers{l}.kernelsize ^ 2;  
  41.             for i = 1 : inputmaps  %  input map  
  42.                 % 随機初始化權值,也就是共有outputmaps個卷積核,對上層的每個特征map,都需要用這麼多個卷積核  
  43.                 % 去卷積提取特征。  
  44.                 % rand(n)是産生n×n的 0-1之間均勻取值的數值的矩陣,再減去0.5就相當于産生-0.5到0.5之間的随機數  
  45.                 % 再 *2 就放大到 [-1, 1]。然後再乘以後面那一數,why?  
  46.                 % 反正就是将卷積核每個元素初始化為[-sqrt(6 / (fan_in + fan_out)), sqrt(6 / (fan_in + fan_out))]  
  47.                 % 之間的随機數。因為這裡是權值共享的,也就是對于一張特征map,所有感受野位置的卷積核都是一樣的  
  48.                 % 是以隻需要儲存的是 inputmaps * outputmaps 個卷積核。  
  49.                 net.layers{l}.k{i}{j} = (rand(net.layers{l}.kernelsize) - 0.5) * 2 * sqrt(6 / (fan_in + fan_out));  
  50.             end  
  51.             net.layers{l}.b{j} = 0; % 将偏置初始化為0  
  52.         end  
  53.         % 隻有在卷積層的時候才會改變特征map的個數,pooling的時候不會改變個數。這層輸出的特征map個數就是  
  54.         % 輸入到下一層的特征map個數  
  55.         inputmaps = net.layers{l}.outputmaps;  
  56.     end  
  57.     if strcmp(net.layers{l}.type, 'f1')  
  58.         fvnum = prod(mapsize) * inputmaps;  
  59.         onum = net.layers{l}.neuron;  
  60.         net.layers{l-1}.ffb = zeros(onum, 1);  
  61.         net.layers{l-1}.ffW = (rand(onum, fvnum) - 0.5) * 2 * sqrt(6 / (onum + fvnum));  
  62.     end  
  63.     if strcmp(net.layers{l}.type, 'f')  
  64.         fvnum = net.layers{l-1}.neuron;  
  65.         onum = net.layers{l}.neuron;  
  66.         net.layers{l-1}.ffb = zeros(onum, 1);  
  67.         net.layers{l-1}.ffW = (rand(onum, fvnum) - 0.5) * 2 * sqrt(6 / (onum + fvnum));  
  68.     end  
  69.     if strcmp(net.layers{l}.type, 'o')  
  70.         fvnum = net.layers{l-1}.neuron;  
  71.         onum = net.layers{l}.scale;  
  72.         net.layers{l-1}.ffb = zeros(onum, 1);  
  73.         net.layers{l-1}.ffW = (rand(onum, fvnum) - 0.5) * 2 * sqrt(6 / (onum + fvnum));  
  74.     end  
  75.     % fvnum 是輸出層的前面一層的神經元個數。  
  76.     % 這一層的上一層是經過pooling後的層,包含有inputmaps個特征map。每個特征map的大小是mapsize。  
  77.     % 是以,該層的神經元個數是 inputmaps * (每個特征map的大小)  
  78.     % prod: Product of elements.  
  79.     % For vectors, prod(X) is the product of the elements of X  
  80.     % 在這裡 mapsize = [特征map的行數 特征map的列數],是以prod後就是 特征map的行*列  
  81.     %fvnum = prod(mapsize) * inputmaps;  
  82.     % onum 是标簽的個數,也就是輸出層神經元的個數。你要分多少個類,自然就有多少個輸出神經元  
  83.     %onum = size(y, 1);  
  84.     % 這裡是最後一層神經網絡的設定  
  85.     % ffb 是輸出層每個神經元對應的基biases  
  86.     %net.ffb = zeros(onum, 1);  
  87.     % ffW 輸出層前一層 與 輸出層 連接配接的權值,這兩層之間是全連接配接的  
  88.     %net.ffW = (rand(onum, fvnum) - 0.5) * 2 * sqrt(6 / (onum + fvnum));  
  89. 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

繼續閱讀