DeepLearning tutorial(5)CNN卷積神經網絡應用於人臉識別(詳細流程+代碼實現)

DeepLearning tutorial(5)CNN卷積神經網絡應用於人臉識別(詳細流程+代碼實現)python

 

@author:wepongit

@blog:http://blog.csdn.net/u012162613/article/details/43277187github

 

本文代碼下載地址:個人github算法

 

本文主要講解將CNN應用於人臉識別的流程,程序基於python+numpy+theano+PIL開發,採用相似LeNet5的CNN模型,應用於olivettifaces人臉數據庫,實現人臉識別的功能,模型的偏差降到了5%如下。本程序只是我的學習過程的一個toy implement,樣本很小,模型隨時都會過擬合。數據庫

可是,本文意在理清程序開發CNN模型的具體步驟,特別是針對圖像識別,從拿到圖像數據庫,到實現一個針對這個圖像數據庫的CNN模型,我以爲本文對這些流程的實現具備參考意義。數組

 

《本文目錄》網絡

1、olivettifaces人臉數據庫介紹框架

2、CNN的基本「構件」(LogisticRegression、HiddenLayer、LeNetConvPoolLayer)機器學習

3、組建CNN模型,設置優化算法,應用於Olivetti Faces進行人臉識別函數

4、訓練結果以及參數設置的討論

5、利用訓練好的參數初始化模型

6、一些須要說明的

 

1、olivettifaces人臉數據庫介紹

 

Olivetti Faces是紐約大學的一個比較小的人臉庫,由40我的的400張圖片構成,即每一個人的人臉圖片爲10張。每張圖片的灰度級爲8位,每一個像素的灰度大小位於0-255之間,每張圖片大小爲64×64。以下圖,這個圖片大小是1190*942,一共有20*20張人臉,故每張人臉大小是(1190/20)*(942/20)即57*47=2679:

 

本文所用的訓練數據就是這張圖片,400個樣本,40個類別,乍一看樣本好像比較小,用CNN效果會好嗎?先別下結論,請往下看。

要運行CNN算法,這張圖片必須先轉化爲數組(或者說矩陣),這個用到python的圖像庫PIL,幾行代碼就能夠搞定,具體的方法我以前恰好寫過一篇文章,也是用這張圖,考慮到文章冗長,就不復制過來了,連接在此:《利用Python PIL、cPickle讀取和保存圖像數據庫》

 

訓練機器學習算法,咱們通常將原始數據分紅訓練數據(training_set)、驗證數據(validation_set)、測試數據(testing_set)。本程序將training_set、validation_set、testing_set分別設置爲320、40、40個樣本。它們的label爲0~39,對應40個不一樣的人。這部分的代碼以下:

 

[python]  view plain  copy
 
  1. """ 
  2. 加載圖像數據的函數,dataset_path即圖像olivettifaces的路徑 
  3. 加載olivettifaces後,劃分爲train_data,valid_data,test_data三個數據集 
  4. 函數返回train_data,valid_data,test_data以及對應的label 
  5. """  
  6. def load_data(dataset_path):  
  7.     img = Image.open(dataset_path)  
  8.     img_ndarray = numpy.asarray(img, dtype='float64')/256  
  9.     faces=numpy.empty((400,2679))  
  10.     for row in range(20):  
  11.        for column in range(20):  
  12.         faces[row*20+column]=numpy.ndarray.flatten(img_ndarray [row*57:(row+1)*57,column*47:(column+1)*47])  
  13.   
  14.     label=numpy.empty(400)  
  15.     for i in range(40):  
  16.     label[i*10:i*10+10]=i  
  17.     label=label.astype(numpy.int)  
  18.   
  19.     #分紅訓練集、驗證集、測試集,大小以下  
  20.     train_data=numpy.empty((320,2679))  
  21.     train_label=numpy.empty(320)  
  22.     valid_data=numpy.empty((40,2679))  
  23.     valid_label=numpy.empty(40)  
  24.     test_data=numpy.empty((40,2679))  
  25.     test_label=numpy.empty(40)  
  26.   
  27.     for i in range(40):  
  28.     train_data[i*8:i*8+8]=faces[i*10:i*10+8]  
  29.     train_label[i*8:i*8+8]=label[i*10:i*10+8]  
  30.     valid_data[i]=faces[i*10+8]  
  31.     valid_label[i]=label[i*10+8]  
  32.     test_data[i]=faces[i*10+9]  
  33.     test_label[i]=label[i*10+9]  
  34.   
  35.     #將數據集定義成shared類型,才能將數據複製進GPU,利用GPU加速程序。  
  36.     def shared_dataset(data_x, data_y, borrow=True):  
  37.         shared_x = theano.shared(numpy.asarray(data_x,  
  38.                                                dtype=theano.config.floatX),  
  39.                                  borrow=borrow)  
  40.         shared_y = theano.shared(numpy.asarray(data_y,  
  41.                                                dtype=theano.config.floatX),  
  42.                                  borrow=borrow)  
  43.         return shared_x, T.cast(shared_y, 'int32')  
  44.   
  45.   
  46.   
  47.     train_set_x, train_set_y = shared_dataset(train_data,train_label)  
  48.     valid_set_x, valid_set_y = shared_dataset(valid_data,valid_label)  
  49.     test_set_x, test_set_y = shared_dataset(test_data,test_label)  
  50.     rval = [(train_set_x, train_set_y), (valid_set_x, valid_set_y),  
  51.             (test_set_x, test_set_y)]  
  52.     return rval  

 

 

2、CNN的基本「構件」(LogisticRegression、HiddenLayer、LeNetConvPoolLayer)

 
卷積神經網絡(CNN)的基本結構就是輸入層、卷積層(conv)、子採樣層(pooling)、全鏈接層、輸出層(分類器)。  卷積層+子採樣層通常都會有若干個,本程序實現的CNN模型參考LeNet5,有兩個「卷積+子採樣層」LeNetConvPoolLayer。全鏈接層至關於MLP(多層感知機)中的隱含層HiddenLayer。輸出層即分類器,通常採用softmax迴歸(也有人直接叫邏輯迴歸,其實就是多類別的logistics regression),本程序也直接用LogisticRegression表示。
 
總結起來,要組建CNN模型,必須先定義LeNetConvPoolLayer、HiddenLayer、LogisticRegression這三種layer,這一點在我上一篇文章介紹CNN算法時講得很詳細,包括代碼註解,由於太冗長,這裏給出連接: 《DeepLearning tutorial(4)CNN卷積神經網絡原理簡介+代碼詳解》
 

代碼太長,就不貼具體的了,只給出框架,具體能夠下載個人代碼看看:

 

[python]  view plain  copy
 
  1. #分類器,即CNN最後一層,採用邏輯迴歸(softmax)  
  2. class LogisticRegression(object):  
  3.     def __init__(self, input, n_in, n_out):  
  4.         self.W = ....  
  5.         self.b = ....  
  6.         self.p_y_given_x = ...  
  7.         self.y_pred = ...  
  8.         self.params = ...  
  9.     def negative_log_likelihood(self, y):  
  10.     def errors(self, y):  
  11.   
  12. #全鏈接層,分類器前一層  
  13. class HiddenLayer(object):  
  14.     def __init__(self, rng, input, n_in, n_out, W=None, b=None,activation=T.tanh):  
  15.         self.input = input  
  16.         self.W = ...  
  17.         self.b = ...  
  18.         lin_output = ...  
  19.         self.params = [self.W, self.b]  
  20.   
  21. #卷積+採樣層(conv+maxpooling)  
  22. class LeNetConvPoolLayer(object):  
  23.     def __init__(self, rng, input, filter_shape, image_shape, poolsize=(2, 2)):  
  24.         self.input = input  
  25.         self.W = ...  
  26.         self.b = ...  
  27.         # 卷積  
  28.         conv_out = ...  
  29.         # 子採樣  
  30.         pooled_out =...  
  31.         self.output = ...  
  32.         self.params = [self.W, self.b]  

 

 

 

3、組建CNN模型,設置優化算法,應用於Olivetti Faces進行人臉識別

 

上面定義好了CNN的幾個基本「構件」,如今咱們使用這些構件來組建CNN模型,本程序的CNN模型參考LeNet5,具體爲:input+layer0(LeNetConvPoolLayer)+layer1(LeNetConvPoolLayer)+layer2(HiddenLayer)+layer3(LogisticRegression)

 

這是一個串聯結構,代碼也很好寫,直接用第二部分定義好的各類layer去組建就好了,上一layer的輸出接下一layer的輸入,具體能夠看看代碼evaluate_olivettifaces函數中的「創建CNN模型」部分。

 

CNN模型組建好了,就剩下用優化算法求解了,優化算法採用批量隨機梯度降低算法(MSGD),因此要先定義MSGD的一些要素,主要包括:代價函數,訓練、驗證、測試model、參數更新規則(即梯度降低)。這部分的代碼在evaluate_olivettifaces函數中的「定義優化算法的一些基本要素」部分。

 

優化算法的基本要素也定義好了,接下來就要運用人臉圖像數據集來訓練這個模型了,訓練過程有訓練步數(n_epoch)的設置,每一個epoch會遍歷全部的訓練數據(training_set),本程序中也就是320我的臉圖。還有迭代次數iter,一次迭代遍歷一個batch裏的全部樣本,具體爲多少要看所設置的batch_size。關於參數的設定我在下面會討論。這一部分的代碼在evaluate_olivettifaces函數中的「訓練CNN階段」部分。

代碼很長,只貼框架,具體能夠下載個人代碼看看:

 

[python]  view plain  copy
 
  1. def evaluate_olivettifaces(learning_rate=0.05, n_epochs=200,  
  2.                     dataset='olivettifaces.gif',  
  3.                     nkerns=[5, 10], batch_size=40):     
  4.   
  5.     #隨機數生成器,用於初始化參數....  
  6.     #加載數據.....  
  7.     #計算各數據集的batch個數....  
  8.     #定義幾個變量,x表明人臉數據,做爲layer0的輸入......  
  9.   
  10.     ######################  
  11.     #創建CNN模型:  
  12.     #input+layer0(LeNetConvPoolLayer)+layer1(LeNetConvPoolLayer)+layer2(HiddenLayer)+layer3(LogisticRegression)  
  13.     ######################  
  14.     ...  
  15.     ....  
  16.     ......  
  17.   
  18.     #########################  
  19.     # 定義優化算法的一些基本要素:代價函數,訓練、驗證、測試model、參數更新規則(即梯度降低)  
  20.     #########################  
  21.     ...  
  22.     ....  
  23.     ......  
  24.   
  25.     #########################  
  26.     # 訓練CNN階段,尋找最優的參數。  
  27.     ########################  
  28.     ...  
  29.     .....  
  30.     .......  



 

另外,值得一提的是,在訓練CNN階段,咱們必須定時地保存模型的參數,這是在訓練機器學習算法時一個常常會作的事情,這一部分的詳細介紹我以前寫過一篇文章《DeepLearning tutorial(2)機器學習算法在訓練過程當中保存參數》。簡單來講,咱們要保存CNN模型中layer0、layer一、layer二、layer3的參數,因此在「訓練CNN階段」這部分下面,有一句代碼:

 

[python]  view plain  copy
 
  1. save_params(layer0.params,layer1.params,layer2.params,layer3.params)  

 

 

這個函數具體定義爲:

 

 

[python]  view plain  copy
 
  1. #保存訓練參數的函數  
  2. def save_params(param1,param2,param3,param4):    
  3.         import cPickle    
  4.         write_file = open('params.pkl', 'wb')     
  5.         cPickle.dump(param1, write_file, -1)  
  6.         cPickle.dump(param2, write_file, -1)  
  7.         cPickle.dump(param3, write_file, -1)  
  8.         cPickle.dump(param4, write_file, -1)  
  9.         write_file.close()    

 

 

若是在其餘算法中,你要保存的參數有五個六個甚至更多,那麼改一下這個函數的參數就行啦。

 

 

 

4、訓練結果以及參數設置的討論

 
ok,上面基本介紹完了CNN模型的構建,以及模型的訓練,我將它們的代碼都放在train_CNN_olivettifaces.py這個源文件中,將 Olivetti Faces這張圖片跟這個代碼文件放在同個目錄下,運行這個文件,幾分鐘就能夠訓練完模型,而且在同個目錄下獲得一個params.pkl文件,這個文件保存的就是最後的模型的參數,方便你之後直接使用這個模型。
 
好了,如今討論一下怎麼設置參數,具體來講,程序中能夠設置的參數包括:學習速率learning_rate、batch_size、n_epochs、nkerns、poolsize。下面逐一討論調節它們時對模型的影響。
 
  • 調節learning_rate
學習速率learning_rate就是運用SGD算法時梯度前面的係數,很是重要,設得太大的話算法可能永遠都優化不了,設得過小會使算法優化得太慢,並且可能還會掉入局部最優。能夠形象地將learning_rate比喻成走路時步子的大小,想象一下要從一個U形的山谷的一邊走到山谷最低點,若是步子特別大,像巨人那麼大,那會直接從一邊跨到另外一邊,而後又跨回這邊,如此往復。若是過小了,可能你走着走着就掉入了某些小坑,由於山路老是凹凸不平的(局部最優),掉入這些小坑後,若是步子仍是不變,就永遠走不出那個坑。
 
好,回到本文的模型,下面是我使用時的記錄,固定其餘參數,調節learning_rate:
(1)kerns=[20, 50], batch_size=40,poolsize=(2,2),learning_rate=0.1時,validation-error一直是97.5%,沒降下來,分析了一下,以爲應該是學習速率太大,跳過了最優。

(2)nkerns=[20, 50], batch_size=40,poolsize=(2,2),learning_rate=0.01時,訓練到epoch 60多時,validation-error降到5%,test-error降到15%

(3)nkerns=[20, 50], batch_size=40,poolsize=(2,2),learning_rate=0.05時,訓練到epoch 36時,validation-error降到2.5%,test-error降到5%

注意,驗證集和測試集都只有40張圖片,也就是說只有一兩張識別錯了,仍是不錯的,數據集再大點,偏差率能夠降到更小。最後我將learning_rate設置爲0.05。

PS:學習速率應該自適應地減少,是有專門的一些算法的,本程序沒有實現這個功能,有時間再研究一下。
 

 

 

 

  • 調節batch_size

由於咱們採用minibatch SGD算法來優化,因此是一個batch一個batch地將數據輸入CNN模型中,而後計算這個batch的全部樣本的平均損失,即代價函數是全部樣本的平均。而batch_size就是一個batch的所包含的樣本數,顯然batch_size將影響到模型的優化程度和速度。

 

 

回到本文的模型,首先由於咱們train_dataset是320,valid_dataset和test_dataset都是40,因此batch_size最好都是40的因子,也就是能讓40整除,好比40、20、十、五、二、1,不然會浪費一些樣本,好比設置爲30,則320/30=10,餘數時20,這20個樣本是沒被利用的。而且,若是batch_size設置爲30,則得出的validation-error和test-error只是30個樣本的錯誤率,並非所有40個樣本的錯誤率。這是設置batch_size要注意的。特別是樣本比較少的時候。


下面是我實驗時的記錄,固定其餘參數,改變batch_size:
batch_size=一、二、五、十、20時,validation-error一直是97.5%,沒降下來。我以爲多是樣本類別覆蓋率太小,由於咱們的數據是按類別排的,每一個類別10個樣本是連續排在一塊兒的,batch_size等於20時其實只包含了兩個類別,這樣優化會很慢。

所以最後我將batch_size設爲40,也就是valid_dataset和test_dataset的大小了,沒辦法,原始數據集樣本太少了。通常咱們都不會讓batch_size達到valid_dataset和test_dataset的大小的。

 

 

  • 關於n_epochs

n_epochs也就是最大的訓練步數,好比設爲200,那訓練過程最多遍歷你的數據集200遍,當遍歷了200遍你的dataset時,程序會中止。n_epochs就至關於一箇中止程序的控制參數,並不會影響CNN模型的優化程度和速度,只是一個控制程序結束的參數。

 

 

  • nkerns=[20, 50]

 

20表示第一個卷積層的卷積核的個數,50表示第二個卷積層的卷積核的個數。這個我也是瞎調的,暫時沒什麼經驗能夠總結。
不過從理論上來講,卷積核的個數其實就表明了特徵的個數,你提取的特徵越多,可能最後分類就越準。可是,特徵太多(卷積核太多),會增長參數的規模,加大了計算複雜度,並且有時候卷積核也不是越多越好,應根據具體的應用對象來肯定。因此我以爲,CNN雖號稱自動提取特徵,免去複雜的特徵工程,可是不少參數好比這裏的nkerns仍是須要去調節的,仍是須要一些「人工」的。


下面是個人實驗記錄,固定batch_size=40,learning_rate=0.05,poolsize=(2,2):

(1)nkerns=[20, 50]時,訓練到epoch 36時,validation-error降到2.5%,test-error降到5%

(2)nkerns=[10, 30]時,訓練到epoch 46時,validation-error降到5%,test-error降到5%

(3)nkerns=[5, 10]時,訓練到epoch 38時,validation-error降到5%,test-error降到7.5%

 

 

  • poolsize=(2, 2)

 

 

poolzize在本程序中是設置爲(2,2),即從一個2*2的區域裏maxpooling出1個像素,說白了就算4和像素保留成1個像素。本例程中人臉圖像大小是57*47,對這種小圖像來講,(2,2)時比較合理的。若是你用的圖像比較大,能夠把poolsize設的大一點。

 

 

 

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++分割線+++++++++++++++++++++++++++++++++++++++++++

 

 

上面部分介紹完了CNN模型構建以及模型訓練的過程,代碼都在train_CNN_olivettifaces.py裏面,訓練完能夠獲得一個params.pkl文件,這個文件保存的就是最後的模型的參數,方便你之後直接使用這個模型。之後只需利用這些保存下來的參數來初始化CNN模型,就獲得一個可使用的CNN系統,將人臉圖輸入這個CNN系統,預測人臉圖的類別。

接下來就介紹怎麼使用訓練好的參數的方法,這部分的代碼放在use_CNN_olivettifaces.py文件中。

 

 

 

5、利用訓練好的參數初始化模型

 
在train_CNN_olivettifaces.py中的LeNetConvPoolLayer、HiddenLayer、LogisticRegression是用隨機數生成器去隨機初始化的,咱們將它們定義爲能夠用參數來初始化的版本,其實很簡單,代碼只須要作稍微的改動,只須要在LogisticRegression、HiddenLayer、LeNetConvPoolLayer這三個class的__init__()函數中加兩個參數params_W,params_b,而後將params_W,params_b賦值給這三個class裏的W和b:
 
[python]  view plain  copy
 
  1. self.W = params_W  
  2. self.b = params_b  

params_W,params_b就是從params.pkl文件中讀取來的,讀取的函數:
[python]  view plain  copy
 
  1. #讀取以前保存的訓練參數  
  2. #layer0_params~layer3_params都是包含W和b的,layer*_params[0]是W,layer*_params[1]是b  
  3. def load_params(params_file):  
  4.     f=open(params_file,'rb')  
  5.     layer0_params=cPickle.load(f)  
  6.     layer1_params=cPickle.load(f)  
  7.     layer2_params=cPickle.load(f)  
  8.     layer3_params=cPickle.load(f)  
  9.     f.close()  
  10.     return layer0_params,layer1_params,layer2_params,layer3_params  


ok,能夠用參數初始化的CNN定義好了,那如今就將須要測試的人臉圖輸入該CNN,測試其類別。一樣的,須要寫一個讀圖像的函數load_data(),代碼就不貼了。將圖像數據輸入,CNN的輸出即是該圖像的類別,這一部分的代碼在use_CNN()函數中,代碼很容易看懂。
 
這一部分還涉及到theano.function的使用,我把一些筆記記在了use_CNN_olivettifaces.py代碼的最後,由於跟代碼相關,結合代碼來看會比較好,因此下面就不講這部分,有興趣的看看代碼。
 
最後說說測試的結果,我仍然以整副olivettifaces.gif做爲輸入,得出其類別後,跟真正的label對比,程序輸出被錯分的那些圖像,運行結果以下:
 
 
錯了五張,我標了三張:
 
 
 
 

6、一些須要說明的

 
首先是本文的嚴謹性:在文章開頭我就說這只是一個toy implement,400張圖片根本不適合用DL來作。
固然我寫這篇文章,只是爲了總結一下這個實現流程,這一點但願對讀者也有參考意義。
 
最後,個人代碼都放在這裏: github地址,能夠下載
 

歡迎留言交流,有任何錯誤請不吝指出!

相關文章
相關標籤/搜索