一、卷積網絡原理
1、動機
卷積神經網絡(CNN)是多層感知機(MLP)的一個變種模型,它是從生物學概念中演化而來的。從Hubel和Wiesel早期對貓的視覺皮層的研究工作,我們知道在視覺皮層存在一種細胞的複雜分佈,,這些細胞對於外界的輸入局部是很敏感的,它們被稱爲“感受野”(細胞),它們以某種方法來覆蓋整個視覺域。這些細胞就像一些濾波器一樣,它們對輸入的圖像是局部敏感的,因此能夠更好地挖掘出自然圖像中的目標的空間關係信息。
此外,視覺皮層存在兩類相關的細胞,S細胞(Simple Cell)和C(Complex Cell)細胞。S細胞在自身的感受野內最大限度地對圖像中類似邊緣模式的刺激做出響應,而C細胞具有更大的感受野,它可以對圖像中產生刺激的模式的空間位置進行精準地定位。
視覺皮層作爲目前已知的最爲強大的視覺系統,廣受關注。學術領域出現了很多基於它的神經啓發式模型。比如:NeoCognitron [Fukushima], HMAX [Serre07] 以及本教程要討論的重點 LeNet-5 [LeCun98]。
2、稀疏連接
CNNs通過加強神經網絡中相鄰層之間節點的局部連接模式(Local Connectivity Pattern)來挖掘自然圖像(中的興趣目標)的空間局部關聯信息。第m層隱層的節點與第m-1層的節點的局部子集,並具有空間連續視覺感受野的節點(就是m-1層節點中的一部分,這部分節點在m-1層都是相鄰的)相連。可以用下面的圖來表示這種連接。
假設,m-1層爲視網膜輸入層(接受自然圖像)。根據上圖的描述,在m-1層上面的m層的神經元節點都具有寬度爲3的感受野,m層每一個節點連接下面的視網膜層的3個相鄰的節點。m+1層的節點與它下面一層的節點有着相似的連接屬性,所以m+1層的節點仍與m層中3個相鄰的節點相連,但是對於輸入層(視網膜層)連接數就變多了,在本圖中是5。這種結構把訓練好的濾波器(corresponding to the input producing the strongest response)構建成了一種空間局部模式(因爲每個上層節點都只對感受野中的,連接的局部的下層節點有響應)。根據上面圖,多層堆積形成了濾波器(不再是線性的了),它也變得更具有全局性了(如包含了一大片的像素空間)。比如,在上圖中,第m+1層能夠對寬度爲5的非線性特徵進行編碼(就像素空間而言)。
3、權值共享
在CNNs中,每一個稀疏濾波器hi在整個感受野中是重複疊加的,這些重複的節點形式了一種特徵圖(feature map),這個特種圖可以共享相同的參數,比如相同的權值矩陣和偏置向量。
在上圖中,屬於同一個特徵圖的三個隱層節點,因爲需要共享相同顏色的權重, 他們的被限制成相同的。在這裏, 梯度下降算法仍然可以用來訓練這些共享的參數,只需要在原算法的基礎上稍作改動即可。共享權重的梯度可以對共享參數的梯度進行簡單的求和得到。
二、網絡的分析
上面這些內容,基本就是CNN的精髓所在了,下面結合LeNet做具體的分析。
結構圖:
LeNet算上輸入輸出一共爲八層,下面逐層分析。
第一層:數據輸入層
CNN的強項在於圖片的處理,lenet的輸入爲32*32的矩陣圖片。這裏需要注意的點:
1、數據的歸一化,這裏的歸一化是廣義的,不一定要歸到0-1,但要是相同的一個區間範圍,一般我們的灰度圖爲0-255。
2、數據的去均值,如果樣本有非零的均值,而且與測試部分的非零均值不一致,可能就會導致識別率的下降。當然這不一定發生,我們這麼做是爲了增加系統的魯棒性。
第二層:卷積層c1
卷積層是卷積神經網絡的核心,通過不同的卷積核,來獲取圖片的特徵。卷積核相當於一個濾波器,不同的濾波器提取不同特徵。打個比方,對於手寫數字識別,某一個卷積核提取‘一’,另一個卷積核提取‘|’,所以這個數字很有可能就判定爲‘7’。當然實際要比這複雜度得多,但原理大概就是這個樣子。
第三層:pooling層
基本每個卷積層後邊都會接一個pooling層,目的是爲了降維。一般都將原來的卷積層的輸出矩陣大小變爲原來的一半,方便後邊的運算。另外,pooling層增加了系統的魯棒性,把原來的準確描述變爲了概略描述(原來矩陣大小爲28*28,現在爲14*14,必然有一部分信息丟失,一定程度上防止了過擬合)。
第四層:卷積層
與之前類似,在之前的特徵中進一步提取特徵,對原樣本進行更深層次的表達。注意:這裏不是全連接。這裏不是全連接。這裏不是全連接。X代表連接,空白代表不連。
第五層:pooling層
與之前類似。
第六層:卷積層(全連接)
這裏有120個卷積核,這裏是全連接的。將矩陣卷積成一個數,方便後邊網絡進行判定。
第七層:全連接層
和MLP中的隱層一樣,獲得高維空間數據的表達。
第八層:輸出層
這裏一般採用RBF網絡,每個RBF的中心爲每個類別的標誌,網絡輸出越大,代表越不相似,輸出的最小值即爲網絡的判別結果。
三、卷積網絡的BP訓練
前面的都很好理解,卷積神經網絡的難度在於BP過程。網上zouxy09的博文寫的很好,可以看一下,自己搞明白。傳送門:CNN的BP推導
四、代碼部分
關於MNIST數據集,網上有很多現成的代碼對其進行提取,但提取出來的都是亂序的很不利於使用。這裏有提取好的分類後的,詳情傳送門
簡單起見,我們的代碼選用一層卷積層。
CNN_simple_mian.m
- %%% matlab實現LeNet-5
- %%% 作者:xd.wp
- %%% 時間:2016.10.22 14:29
- %% 程序說明
- % 1、池化(pooling)採用平均2*2
- % 2、網絡結點數說明:
- % 輸入層:28*28
- % 第一層:24*24(卷積)*20
- % tanh
- % 第二層:12*12(pooling)*20
- % 第三層:100(全連接)
- % 第四層:10(softmax)
- % 3、網絡訓練部分採用800個樣本,檢驗部分採用100個樣本
- clear all;clc;
- %% 網絡初始化
- layer_c1_num=20;
- layer_s1_num=20;
- layer_f1_num=100;
- layer_output_num=10;
- %權值調整步進
- yita=0.01;
- %bias初始化
- bias_c1=(2*rand(1,20)-ones(1,20))/sqrt(20);
- bias_f1=(2*rand(1,100)-ones(1,100))/sqrt(20);
- %卷積核初始化
- [kernel_c1,kernel_f1]=init_kernel(layer_c1_num,layer_f1_num);
- %pooling核初始化
- pooling_a=ones(2,2)/4;
- %全連接層的權值
- weight_f1=(2*rand(20,100)-ones(20,100))/sqrt(20);
- weight_output=(2*rand(100,10)-ones(100,10))/sqrt(100);
- disp('網絡初始化完成......');
- %% 開始網絡訓練
- disp('開始網絡訓練......');
- for iter=1:20
- for n=1:20
- for m=0:9
- %讀取樣本
- train_data=imread(strcat(num2str(m),'_',num2str(n),'.bmp'));
- train_data=double(train_data);
- % 去均值
- % train_data=wipe_off_average(train_data);
- %前向傳遞,進入卷積層1
- for k=1:layer_c1_num
- state_c1(:,:,k)=convolution(train_data,kernel_c1(:,:,k));
- %進入激勵函數
- state_c1(:,:,k)=tanh(state_c1(:,:,k)+bias_c1(1,k));
- %進入pooling1
- state_s1(:,:,k)=pooling(state_c1(:,:,k),pooling_a);
- end
- %進入f1層
- [state_f1_pre,state_f1_temp]=convolution_f1(state_s1,kernel_f1,weight_f1);
- %進入激勵函數
- for nn=1:layer_f1_num
- state_f1(1,nn)=tanh(state_f1_pre(:,:,nn)+bias_f1(1,nn));
- end
- %進入softmax層
- for nn=1:layer_output_num
- output(1,nn)=exp(state_f1*weight_output(:,nn))/sum(exp(state_f1*weight_output));
- end
- %% 誤差計算部分
- Error_cost=-output(1,m+1);
- % if (Error_cost<-0.98)
- % break;
- % end
- %% 參數調整部分
- [kernel_c1,kernel_f1,weight_f1,weight_output,bias_c1,bias_f1]=CNN_upweight(yita,Error_cost,m,train_data,...
- state_c1,state_s1,...
- state_f1,state_f1_temp,...
- output,...
- kernel_c1,kernel_f1,weight_f1,weight_output,bias_c1,bias_f1);
-
- end
- end
- end
- disp('網絡訓練完成,開始檢驗......');
- count=0;
- for n=1:20
- for m=0:9
- %讀取樣本
- train_data=imread(strcat(num2str(m),'_',num2str(n),'.bmp'));
- train_data=double(train_data);
- % 去均值
- % train_data=wipe_off_average(train_data);
- %前向傳遞,進入卷積層1
- for k=1:layer_c1_num
- state_c1(:,:,k)=convolution(train_data,kernel_c1(:,:,k));
- %進入激勵函數
- state_c1(:,:,k)=tanh(state_c1(:,:,k)+bias_c1(1,k));
- %進入pooling1
- state_s1(:,:,k)=pooling(state_c1(:,:,k),pooling_a);
- end
- %進入f1層
- [state_f1_pre,state_f1_temp]=convolution_f1(state_s1,kernel_f1,weight_f1);
- %進入激勵函數
- for nn=1:layer_f1_num
- state_f1(1,nn)=tanh(state_f1_pre(:,:,nn)+bias_f1(1,nn));
- end
- %進入softmax層
- for nn=1:layer_output_num
- output(1,nn)=exp(state_f1*weight_output(:,nn))/sum(exp(state_f1*weight_output));
- end
- [p,classify]=max(output);
- if (classify==m+1)
- count=count+1;
- end
- fprintf('真實數字爲%d 網絡標記爲%d 概率值爲%d \n',m,classify-1,p);
- end
- end
init_kernel.m
- function [kernel_c1,kernel_f1]=init_kernel(layer_c1_num,layer_f1_num)
- %% 卷積核初始化
- for n=1:layer_c1_num
- kernel_c1(:,:,n)=(2*rand(5,5)-ones(5,5))/12;
- end
- for n=1:layer_f1_num
- kernel_f1(:,:,n)=(2*rand(12,12)-ones(12,12));
- end
- end
convolution.m
- function [state]=convolution(data,kernel)
- %實現卷積層操作
- [data_row,data_col]=size(data);
- [kernel_row,kernel_col]=size(kernel);
- for m=1:data_col-kernel_col+1
- for n=1:data_row-kernel_row+1
- state(m,n)=sum(sum(data(m:m+kernel_row-1,n:n+kernel_col-1).*kernel));
- end
- end
- end
pooling.m
- function state=pooling(data,pooling_a)
- %% 實現取樣層pooling操作
- [data_row,data_col]=size(data);
- [pooling_row,pooling_col]=size(pooling_a);
- for m=1:data_col/pooling_col
- for n=1:data_row/pooling_row
- state(m,n)=sum(sum(data(2*m-1:2*m,2*n-1:2*n).*pooling_a));
- end
- end
- end
convolution_f1.m
- function [state_f1,state_f1_temp]=convolution_f1(state_s1,kernel_f1,weight_f1)
- %% 完成卷積層2操作
- layer_f1_num=size(weight_f1,2);
- layer_s1_num=size(weight_f1,1);
-
- %%
- for n=1:layer_f1_num
- count=0;
- for m=1:layer_s1_num
- temp=state_s1(:,:,m)*weight_f1(m,n);
- count=count+temp;
- end
- state_f1_temp(:,:,n)=count;
- state_f1(:,:,n)=convolution(state_f1_temp(:,:,n),kernel_f1(:,:,n));
- end
- end
CNN_upweight.m
- function [kernel_c1,kernel_f1,weight_f1,weight_output,bias_c1,bias_f1]=CNN_upweight(yita,Error_cost,classify,train_data,state_c1,state_s1,state_f1,state_f1_temp,...
- output,kernel_c1,kernel_f1,weight_f1,weight_output,bias_c1,bias_f1)
- %%% 完成參數更新,權值和卷積核
- %% 結點數目
- layer_c1_num=size(state_c1,3);
- layer_s1_num=size(state_s1,3);
- layer_f1_num=size(state_f1,2);
- layer_output_num=size(output,2);
-
- [c1_row,c1_col,~]=size(state_c1);
- [s1_row,s1_col,~]=size(state_s1);
-
- [kernel_c1_row,kernel_c1_col]=size(kernel_c1(:,:,1));
- [kernel_f1_row,kernel_f1_col]=size(kernel_f1(:,:,1));
- %% 保存網絡權值
- kernel_c1_temp=kernel_c1;
- kernel_f1_temp=kernel_f1;
-
- weight_f1_temp=weight_f1;
- weight_output_temp=weight_output;
- %% Error計算
- label=zeros(1,layer_output_num);
- label(1,classify+1)=1;
- delta_layer_output=output-label;
- %% 更新weight_output
- for n=1:layer_output_num
- delta_weight_output_temp(:,n)=delta_layer_output(1,n)*state_f1';
- end
- weight_output_temp=weight_output_temp-yita*delta_weight_output_temp;
-
- %% 更新bias_f1以及kernel_f1
- for n=1:layer_f1_num
- count=0;
- for m=1:layer_output_num
- count=count+delta_layer_output(1,m)*weight_output(n,m);
- end
- %bias_f1
- delta_layer_f1(1,n)=count*(1-tanh(state_f1(1,n)).^2);
- delta_bias_f1(1,n)=delta_layer_f1(1,n);
- %kernel_f1
- delta_kernel_f1_temp(:,:,n)=delta_layer_f1(1,n)*state_f1_temp(:,:,n);
- end
- bias_f1=bias_f1-yita*delta_bias_f1;
- kernel_f1_temp=kernel_f1_temp-yita*delta_kernel_f1_temp;
- %% 更新weight_f1
- for n=1:layer_f1_num
- delta_layer_f1_temp(:,:,n)=delta_layer_f1(1,n)*kernel_f1(:,:,n);
- end
- for n=1:layer_s1_num
- for m=1:layer_f1_num
- delta_weight_f1_temp(n,m)=sum(sum(delta_layer_f1_temp(:,:,m).*state_s1(:,:,n)));
- end
- end
- weight_f1_temp=weight_f1_temp-yita*delta_weight_f1_temp;
-
- %% 更新 bias_c1
- for n=1:layer_s1_num
- count=0;
- for m=1:layer_f1_num
- count=count+delta_layer_f1_temp(:,:,m)*weight_f1(n,m);
- end
- delta_layer_s1(:,:,n)=count;
- delta_layer_c1(:,:,n)=kron(delta_layer_s1(:,:,n),ones(2,2)/4).*(1-tanh(state_c1(:,:,n)).^2);
- delta_bias_c1(1,n)=sum(sum(delta_layer_c1(:,:,n)));
- end
- bias_c1=bias_c1-yita*delta_bias_c1;
- %% 更新 kernel_c1
- for n=1:layer_c1_num
- delta_kernel_c1_temp(:,:,n)=rot90(conv2(train_data,rot90(delta_layer_c1(:,:,n),2),'valid'),2);
- end
- kernel_c1_temp=kernel_c1_temp-yita*delta_kernel_c1_temp;
-
- %% 網絡權值更新
- kernel_c1=kernel_c1_temp;
- kernel_f1=kernel_f1_temp;
-
- weight_f1=weight_f1_temp;
- weight_output=weight_output_temp;
-
- end
程序運行結果:
檢驗200個,196個識別正確,4個識別錯誤。