模式識別hw2-------基於matconvnet,用CNN實現人臉圖片性別識別

bizer_csdn發表於2017-01-27

主要來源模式識別課程大作業,本文首先感謝當初的助教和一起完成作業的隊友


matconvnet在matlab下封裝了CNN常見演算法,網址http://www.vlfeat.org/matconvnet/,本文采用matconvnet-1.0-beta16.tar.gz


第一題: 運用matconvet提供的mnist網路結構,

通過train資料夾中人臉圖片訓練網路(訓練集),通過val資料夾調整網路結構(驗證集),最後通過test資料夾測試網路效能(測試集)


第一題: 設計一個神經網路,並與第一題比較


第一題

1.利用train和val資料夾進行訓練和調參:

    利用matconvnet工具包中對mnist手寫數字體設計的cnn結構,識別人臉性別。主程式為“cnn_mnist.m”,而cnn結構初始化在“cnn_mnist_init.m”中完成,網路結構不需要重新設計,只需要在cnn_mnist.m中更改資料的輸入,在cnn_mnist_init.m更改網路的輸出。

    對於本題資料集,“train”中圖片為訓練集,“val”中為驗證集,“test”中為測試集。首先利用“train”和“val”中人臉圖片訓練cnn網路權重,最後對訓練好的網路用“test”中資料進行測試。

    本題主程式所在M檔案為“cnn_mnist_new.m”;網路結構檔案為“cnn_mnist_init_new.m”;“test_p1.m”為對訓練好的網路進行測試的主程式。

   網路啟用函式,在預設情況下,預設為“sigmoid”。

   首先在“cnn_mnist_init_new.m”中先拷貝“cnn_mnist.m”中程式碼,然後主要更改其中“getMnistImdb”函式。由於mnist資料集中的影像資料28*28 ,而本次實驗中,所用圖片大小為100*100 ,因此,需要把這些數字影像壓縮為28*28 。

   函式“getMnistImdb”中變數“x1”儲存訓練集中的圖片,男性和女性的人臉圖片給300張,其前300維儲存男性,後300維儲存女性;而變數“y1”儲存訓練集對應的標籤,也是其前300維儲存男性,後300維儲存女性,這裡需要注意的是,matconvnet不能把類別標籤設定為“0”,否則對其他一些類別不能再識別;而本次作業為二分類問題,這裡,男性對應於標籤“1”,女性對應於標籤“2”。同理,“x2”和“y2”對應於驗證集中的資料,其中男性和女性圖片各80張。

   作為說明,這裡只給出讀取訓練集資料中女性的示例程式碼:

%x1(:,:,1:300) stores female images in the train set
%x1(:,:,301:600) stores male images in the train set
x1=zeros(28,28,600);
%y1(1:300) stores female labels(1) in the train set
%y1(301:600) stores male labels(2) in the train set
y1=cat(2,ones(1,300),ones(1,300)+1);
%讀取訓練集圖片-female
img_file_path=fullfile(vl_rootnn,'examples\mnist\image\image\new\train\female');
img_dir=dir(img_file_path);
for i=1:length(img_dir)-2    filename=sprintf('%s\\%s\\fy.bmp',img_file_path,img_dir(i+2).name);
    I=imread(filename);
    I=imresize(I,[28,28]); %將圖片壓縮為28*28大小
    x1(:,:,i)=I;
end

另外,為了防止過擬合,“cnn_mnist_init_new.m”中加入dropout,所謂“dropout”就是在訓練過程中,隨機讓一些節點不參與計算,加入方式為:

net.layers{end+1} = struct('type', 'dropout', 'rate', 0.5);

並且迭代次數設定為300,最重要的是,要把“softmaxloss”前一全連線層中神經元輸出節點改2,即:

net.layers{end+1} = struct('type', 'conv', ...
                           'weights', {{f*randn(1,1,500,2, 'single'), zeros(1,2,'single')}}, ...
                           'stride', 1, ...
                           'pad', 0) ;

執行“cnn_mnist_init_new.m”可以得到如下結果:




其中,“energy”訓練過程中訓練集和和驗證集的總的誤差能量之和;而“error”分別對應訓練集和驗證機的錯誤率,“top1err”是“softmax”層輸出過程中最高得分項對應錯誤率,而由於是二分類問題,“top5err”(前5項得分最高項對應錯誤率)是沒有意義的。

這裡需要說明一下,由於是二分類問題,最後分類器一般“logistic regression”,而“softmax”是“logistic regression”升級版,支援多分類問題。由於,我們網路最後是全連線到2個神經元上,其中“1”代表女性,“2”代表男性(需要注意的是,運用“softmax”,matconvnet類別標籤是不支援0的),並且通過“softmax”進行分類。

2.利用 test資料夾測試:

“test_p1.m”是對已經訓練好的網路測試“test”資料夾下資料的主程式。測試集共265張圖片,其中男性56張,女性209張。讀取資料過程和“cnn_mnist_new.m”相似,這裡不再贅述。而載入網路主要過程:

train_imdb_path=fullfile(vl_rootnn,'\examples\mnist\problem1\problem-1-net-epoch\imdb.mat');
 load(net_path);
net.layers{end}.type = 'softmax';

其中,“net_path”為網路所在檔案路徑,並且要主要載入網路之後,要把原來的“softmaxloss”改為“softmax”。並且處理測試集資料時候,要把測試集的資料減去訓練集的均值(因為訓練時候,預處理過程含有去均值的過程),其中,均值儲存在訓練集中的“imdb”結構體中,

示例程式碼如下:

test_set=single(test_set);
train_imdb_path=fullfile(vl_rootnn,'\examples\mnist\problem1\problem-1-net-epoch\imdb.mat');
train_imdb=load(train_imdb_path);
train_data_mean=train_imdb.images.data_mean;
test_set=bsxfun(@minus,test_set,train_data_mean);

而這個前向過程的輸出主要利用函式“vl_simplenn”,示例如下:

err_cnt=0; 
best_scores=zeros(1,female_num+male_num);
err_id=zeros(1,female_num+male_num);
for i=1:(female_num+male_num)
    res=vl_simplenn(net,test_set(:,:,i));
    scores=squeeze(gather(res(end).x));
    [best_score, best_id] = max(scores);
    best_scores(i)=best_score;
    if abs(str2double(net.meta.classes.name{best_id})-test_label(i))>1e-1;
        err_cnt=err_cnt+1;
        err_id(i)=1;
    end
    
end
err_rate=err_cnt/(female_num+male_num);
實驗結果,最低錯誤率為6.79%

執行test_p1.m檔案對網路進行測試。本檔案共重複10次實驗,實驗結果分佈如上所示。其中平均錯誤率為8.26%,最低錯誤率為6.79%。

第二題

1.利用train和val資料夾進行訓練和調參:

與第一題類似,“my_cnn.m”是訓練過程的主程式,“my_cnn_init.m”對應網路結構,“test_my_cnn.m” 為對訓練好的網路進行測試的主程式。讀取圖片資料與第一題類似,這裡不再贅述。

而“my_cnn_init.m”中網路結構對應為:

該網路通過卷積和池化降低維度,並且網路最後全連線到兩個神經元上,通過“softmax”進行分類。然後“relu”作為啟用函式,訓練時候一般需要"batch normalization"

核心程式碼如下:
net.layers = {} ;
%得到96*96
net.layers{end+1} = struct('type', 'conv', ...   
                           'weights', {{f*randn(5,5,1,20, 'single'), zeros(1, 20, 'single')}}, ...
                           'stride', 1, ...
                           'pad', 0) ;
net.layers{end+1} = struct('type', 'relu') ; 
%得到48*48
net.layers{end+1} = struct('type', 'pool', ...
                           'method', 'avg', ...
                           'pool', [2 2], ...
                           'stride', 1, ...
                           'pad', 0) ;
net.layers{end+1} = struct('type', 'dropout', 'rate', 0.2);
%得到44*44
net.layers{end+1} = struct('type', 'conv', ...
                           'weights', {{f*randn(5,5,10,20, 'single'),zeros(1,20,'single')}}, ...
                           'stride', 1, ...
                           'pad', 0) ;
%得到22*22
net.layers{end+1} = struct('type', 'pool', ...
                           'method', 'avg', ...
                           'pool', [2 2], ...
                           'stride', 1, ...
                           'pad', 0) ;
net.layers{end+1} = struct('type', 'dropout', 'rate', 0.2);
%得到18*18
net.layers{end+1} = struct('type', 'conv', ...
                           'weights', {{f*randn(5,5,20,40, 'single'),zeros(1,40,'single')}}, ...
                           'stride', 1, ...
                           'pad', 0) ;
net.layers{end+1} = struct('type', 'dropout', 'rate', 0.2);
%得到9*9
net.layers{end+1} = struct('type', 'conv', ...
                           'weights', {{f*randn(5,5,40,100, 'single'), zeros(1,100,'single')}}, ...
                           'stride', 1, ...
                           'pad', 0) ;
net.layers{end+1} = struct('type', 'relu') ;
%得到5*5
net.layers{end+1} = struct('type', 'conv', ...
                           'weights', {{f*randn(5,5,100,100, 'single'), zeros(1,100,'single')}}, ...
                           'stride', 1, ...
                           'pad', 0) ;
net.layers{end+1} = struct('type', 'relu') ;
%得到1*1
net.layers{end+1} = struct('type', 'conv', ...
                           'weights', {{f*randn(5,5,100,2, 'single'), zeros(1,2,'single')}}, ...
                           'stride', 1, ...
                           'pad', 0) ;
net.layers{end+1} = struct('type', 'softmaxloss') ;

執行“my_cnn.m”可以得到如下結果:



觀察上圖可知,當訓練次數為第222次時,該網路對val集達到了最低預測錯誤率,大約為7%。其後有細微的上升趨勢。故在下文中選擇第222次網路結果進行test集的測試。


2.利用 test資料夾測試:

“test_p2.m”是對已經訓練好的網路測試“test”資料夾下資料的主程式。測試集共265張圖片,其中男性56張,女性209張。載入網路主要過程:

train_imdb_path=fullfile(vl_rootnn,'\examples\mnist\problem2\problem-2-net-epoch\imdb.mat');
train_imdb=load(train_imdb_path);
net_path=fullfile(vl_rootnn, '\examples\mnist\problem2\problem-2-net-epoch\','net-epoch-222.mat') ;
load(net_path);
net.layers{end}.type = 'softmax';

利用函式“vl_simplenn”進行預測,本部分同問題一,故不再複述。

實驗結果,最低錯誤率為4.53%

執行test_p2.m檔案對網路進行測試。本檔案共重複10次實驗,實驗結果分佈如上所示。其中平均錯誤率為7.85%,最低錯誤率為4.53%。




一些思考:

為何不能分塊:

在完成第一題的時候,除了利用imresize函式對影像進行縮放外,我們還試圖對影像進行分塊處理,如將100*100的影像分割為16張28*28的子圖,如下所示:


然後再對子圖進行cnn訓練,但是效果並不理想,甚至訓練過程都不收斂:



分析這個現象的原因,我們認為,人臉影像並不能分割為子塊。當分割為子塊時,人臉不再是人臉,而只是各個部分離散的多張圖片,其中男女影像各個部分高度相似。甚至我們人類在判別的時候也不能分清一個子圖屬於男還是屬於女。如下圖第1排的第1張圖片,我們很難判斷該子圖屬於女性。
影像的分割,造成了資訊的丟失,部分之和再也不能代替整體!



為何28*28與100*100的精度近似相等



觀察下圖,左為第一題訓練結果,右為第二題訓練結果。可以看到,28*28與100*100大小的圖片經過cnn訓練後,對男女性別的判斷,在精度上近似相等。

究其原因,我們認為,圖片大小的改變對人類來說,並不影響判斷結果,丟失的資訊不足以影響二值化判斷。





程式碼已上傳:http://download.csdn.net/detail/bizer_csdn/9827175
使用時候替換安裝matconvnet資料夾下的examples資料夾


相關文章